// Copyright (C) 2020-2023 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

// { dg-options "-std=gnu++2a" }
// { dg-do run { target c++2a } }
// { dg-timeout-factor 4 }

#include <limits>
#include <ranges>
#include <testsuite_hooks.h>

using max_size_t = std::ranges::__detail::__max_size_type;
using max_diff_t = std::ranges::__detail::__max_diff_type;
using rep_t = max_size_t::__rep;

static_assert(sizeof(max_size_t) == sizeof(max_diff_t));

static_assert(std::regular<max_size_t>);
static_assert(std::totally_ordered<max_size_t>);

static_assert(std::regular<max_diff_t>);
static_assert(std::totally_ordered<max_diff_t>);

// We can't use numeric_limits<rep_t>::max() here because __int128 is an
// integral type only in GNU mode.
constexpr max_size_t mu = max_size_t(~rep_t(0));
constexpr max_size_t ou = 1;
constexpr max_diff_t ns = -1;

void
test01()
{
  static_assert(max_size_t(7) % 3 == 1);
  static_assert(max_size_t(7) % 4 == 3);

  static_assert(-max_diff_t(1) == max_diff_t(-1));
  static_assert(max_diff_t(3) % 2 == 1);
  static_assert(max_diff_t(-3) / 2 == -1);
  static_assert(max_diff_t(-3) % 2 == -1);
  static_assert(max_diff_t(3) % -2 == 1);
  static_assert(max_diff_t(-3) << 1 == -6);
  static_assert(max_diff_t(-3) >> 1 == -2);
  static_assert(max_diff_t(3) >> 1 == 1);
  static_assert(max_diff_t(3) >> 2 == 0);

  static_assert(max_diff_t(-5) / 3 == -1);
  static_assert(max_diff_t(5) / -3 == -1);
  static_assert(max_diff_t(-5) / -3 == 1);
  static_assert(max_diff_t(5) / 3 == 1);

  static_assert(max_diff_t(-6) / 3 == -2);
  static_assert(max_diff_t(6) / -3 == -2);
  static_assert(max_diff_t(-6) / -3 == 2);
  static_assert(max_diff_t(6) / 3 == 2);

  static_assert(~max_size_t(-3) == 2);
  static_assert(~max_diff_t(-3) == 2);

  static_assert(max_diff_t(1) < max_diff_t(3));
  static_assert(max_diff_t(-1) < max_diff_t(3));
  static_assert(max_diff_t(1) > max_diff_t(-3));
  static_assert(max_diff_t(-1) > max_diff_t(-3));

  static_assert(max_diff_t(mu)/-1 == -max_diff_t(mu));
  static_assert(-max_diff_t(mu)/1 == -max_diff_t(mu));
  static_assert(max_diff_t(mu)>>1 == max_diff_t(mu)/2);
  static_assert(-max_diff_t(mu+1) == max_diff_t(mu+1));
  static_assert(-(mu+1) == mu+1);
  static_assert((mu+1)<<1 == 0);
  static_assert(max_diff_t(mu+1)<<1 == 0);
  static_assert(max_diff_t(mu+1)>>1 < 0);

  static_assert(int(max_diff_t(mu+1)) == 0);
  static_assert(rep_t(max_diff_t(mu+1)) == 0);
  static_assert(int(max_diff_t(mu)) == -1);
  static_assert(rep_t(max_diff_t(mu)) == rep_t(-1));

  static_assert(2*mu+1 > 2*mu);
  static_assert(~(2*mu+1) == 0);
  static_assert(mu/mu == 1);
  static_assert(2*mu > mu);
  static_assert(2*mu-mu == mu);
  static_assert((2*mu)/mu == 2);
  static_assert((2*mu+1)/mu == 2);
  static_assert((2*mu-1)/(mu-1) == 2);
  static_assert((2*mu-1)/mu == 1);
  static_assert((2*mu+-1)/mu == 1);
  static_assert(2*mu-1 < 2*mu);
  static_assert(2*mu-1 <= 2*mu);
  static_assert(2*mu+1 > 2*mu);
  static_assert(2*mu+1 >= 2*mu);
  static_assert((2*mu)/1 == 2*mu);
  static_assert(mu/mu-1 == 0);
  static_assert(mu*0 == 0);
  static_assert((2*mu-1)*0 == 0);
  static_assert((2*mu-1)>>1 == mu-1);
  static_assert(mu+-1+1 == mu);
  static_assert(mu+1+-1 == mu);
  static_assert(mu+1);
  static_assert((2*mu)/2 == mu);
  static_assert((2*mu)>>1 == mu);
  static_assert((mu<<1)>>1 == mu);
  static_assert(1/mu == 0);
  static_assert(mu/1 == mu);
  static_assert(((mu+1)|mu) == -1);
  static_assert((mu+1)+(mu+1) < mu+1);

  static_assert(max_size_t(ns) == -1);
  static_assert(-max_diff_t(ou) == -1);
  static_assert(-max_diff_t(-ou) == 1);
  static_assert(max_size_t(-max_diff_t(-ou)) == 1);
  static_assert(ns*ns == max_diff_t(ou));
  static_assert(max_size_t(ns)*max_size_t(ns) == ou);
  static_assert(-max_diff_t(0) == max_diff_t(0));
  static_assert(-ou-ou == -2*ou);

  static_assert(int(ns) == -1);
  static_assert(rep_t(ns) == rep_t(-1));

  static_assert(max_size_t() == 0);
  static_assert(max_diff_t() == 0);

  auto f = [] (auto a) { a /= a; return a; };
  static_assert(f(max_size_t(5)) == 1);
  static_assert(f(max_size_t(-5)) == 1);
  static_assert(f(max_diff_t(5)) == 1);

  auto g = [] (auto a) { a >>= a; return a; };
  static_assert(g(max_size_t(5)) == 0);
  static_assert(g(max_diff_t(5)) == 0);

  auto h = [] (auto a) { a <<= a; return a; };
  static_assert(h(max_size_t(3)) == 24);
  static_assert(h(max_diff_t(3)) == 24);

  auto w = [] (auto a) {
    const auto b = a;
    VERIFY( a++ == b   && a == b+1 );
    VERIFY( a-- == b+1 && a == b );

    VERIFY( ++(++a) == b+2 );
    VERIFY( --(--a) == b );
    return true;
  };
  static_assert(w(max_size_t(10)));
  static_assert(w(-max_diff_t(10)));

#if __cpp_lib_three_way_comparison
  static_assert(max_size_t{1} <=> max_size_t{9} == std::strong_ordering::less);
  static_assert(max_size_t{3} <=> max_size_t{2} == std::strong_ordering::greater);
  static_assert(max_size_t{5} <=> max_size_t{5} == std::strong_ordering::equal);
  static_assert(~max_size_t{1} <=> ~max_size_t{9} == std::strong_ordering::greater);
  static_assert(~max_size_t{3} <=> ~max_size_t{2} == std::strong_ordering::less);
  static_assert(~max_size_t{5} <=> ~max_size_t{5} == std::strong_ordering::equal);
  static_assert(~max_size_t{5} <=> max_size_t{9} == std::strong_ordering::greater);
  static_assert(~max_size_t{9} <=> max_size_t{5} == std::strong_ordering::greater);
  static_assert(max_size_t{5} <=> ~max_size_t{9} == std::strong_ordering::less);
  static_assert(max_size_t{9} <=> ~max_size_t{5} == std::strong_ordering::less);

  static_assert(max_diff_t{1} <=> max_diff_t{9} == std::strong_ordering::less);
  static_assert(max_diff_t{3} <=> max_diff_t{2} == std::strong_ordering::greater);
  static_assert(max_diff_t{5} <=> max_diff_t{5} == std::strong_ordering::equal);
  static_assert(max_diff_t{-1} <=> max_diff_t{-9} == std::strong_ordering::greater);
  static_assert(max_diff_t{-3} <=> max_diff_t{-2} == std::strong_ordering::less);
  static_assert(max_diff_t{-5} <=> max_diff_t{-5} == std::strong_ordering::equal);
  static_assert(max_diff_t{-5} <=> max_diff_t{9} == std::strong_ordering::less);
  static_assert(max_diff_t{-9} <=> max_diff_t{5} == std::strong_ordering::less);
  static_assert(max_diff_t{5} <=> max_diff_t{-9} == std::strong_ordering::greater);
  static_assert(max_diff_t{9} <=> max_diff_t{-5} == std::strong_ordering::greater);
#endif
}

template<bool signed_p, bool shorten_p>
void
test02()
{
  using hw_type = std::conditional_t<signed_p, signed rep_t, rep_t>;
  using max_type = std::conditional_t<signed_p, max_diff_t, max_size_t>;
  using shorten_type = std::conditional_t<shorten_p, hw_type, max_type>;
  const int hw_type_bit_size = sizeof(hw_type) * __CHAR_BIT__;
  const int limit = 1000;
  const int log2_limit = 10;
  static_assert((1 << log2_limit) >= limit);
  const int min = (signed_p ? -limit : 0);
  const int max = limit;
  for (hw_type i = min; i <= max; i++)
    {
      bool ok = true;
      if (signed_p || shorten_p)
	{
	  ok &= (~i == shorten_type(~max_type(i)));
	  ok &= (-i == shorten_type(-max_type(i)));
	}
      for (hw_type j = min; j <= max; j++)
	{
	  ok &= (i*j == shorten_type(max_type(i)*j));
	  ok &= (i+j == shorten_type(max_type(i)+j));
	  if (j != 0)
	    {
	      ok &= (i/j == shorten_type(max_type(i)/j));
	      ok &= (i%j == shorten_type(max_type(i)%j));
	    }
	  if (signed_p || shorten_p)
	    ok &= (i-j == shorten_type(max_type(i)-j));
	  ok &= ((i&j) == shorten_type(max_type(i)&j));
	  ok &= ((i|j) == shorten_type(max_type(i)|j));
	  ok &= ((i^j) == shorten_type(max_type(i)^j));
	  if (j >= 0 && j < hw_type(hw_type_bit_size)
	      && (shorten_p || j < hw_type(hw_type_bit_size) - log2_limit))
	    {
	      ok &= ((i>>j) == shorten_type(max_type(i)>>j));
	      ok &= ((i<<j) == shorten_type(max_type(i)<<j));
	    }
	  ok &= (i>j) == (max_type(i) > j);
	  ok &= (i<j) == (max_type(i) < j);
	  ok &= (i>=j) == (max_type(i) >= j);
	  ok &= (i<=j) == (max_type(i) <= j);
	  ok &= (i==j) == (max_type(i) == j);
	  ok &= (i!=j) == (max_type(i) != j);
	  if (!ok)
	    {
	      fprintf(stderr,
		      "Inconsistency found: %d %d %lld %lld\n",
		      signed_p, shorten_p, (long long)i, (long long)j) ;
	       VERIFY(0);
	    }
	}
    }
}

template<bool signed_p, bool toggle_base_p>
void
test03()
{
  using hw_type = std::conditional_t<signed_p, signed rep_t, rep_t>;
  using max_type = std::conditional_t<signed_p, max_diff_t, max_size_t>;
  using base_type = std::conditional_t<toggle_base_p, hw_type, max_type>;
  constexpr int hw_type_bit_size = sizeof(hw_type) * __CHAR_BIT__;
  constexpr int limit = 1000;
  constexpr int log2_limit = 10;
  static_assert((1 << log2_limit) >= limit);
  const int min = (signed_p ? -limit : 0);
  const int max = limit;
  for (hw_type i = min; i <= max; i++)
    {
      bool ok = true;
      base_type k;
      for (hw_type j = min; j <= max; j++)
	{
	  k = i; k *= j;
	  ok &= (k == (max_type(i)*j));
	  k = i; k += j;
	  ok &= (k == (max_type(i)+j));
	  if (j != 0)
	    {
	      k = i; k /= j;
	      ok &= (k == (max_type(i)/j));
	      k = i; k %= j;
	      ok &= (k == (max_type(i)%j));
	    }
	  if (signed_p)
	    {
	      k = i; k -= j;
	      ok &= (k == (max_type(i)-j));
	    }
	  k = i; k &= j;
	  ok &= (k == (max_type(i)&j));
	  k = i; k |= j;
	  ok &= (k == (max_type(i)|j));
	  k = i; k ^= j;
	  ok &= (k == (max_type(i)^j));
	  if (j >= 0 && j < hw_type(hw_type_bit_size)
	      && (!toggle_base_p || j < hw_type(hw_type_bit_size) - log2_limit))
	    {
	      k = i; k >>= j;
	      ok &= (k == (max_type(i)>>j));
	      k = i; k <<= j;
	      ok &= (k == (max_type(i)<<j));
	    }
	  if (!ok)
	    {
	      fprintf(stderr,
		      "Inconsistency found: %d %d %lld %lld\n",
		      signed_p, toggle_base_p, (long long)i, (long long)j) ;
	       VERIFY(0);
	    }
	}
    }
}

void
test04()
{
  constexpr int limit = 1000;
  for (int i = -limit; i <= limit; i++)
    {
      VERIFY( -max_size_t(-i) == i );
      for (int j = i; j <= limit; j++)
	{
	  VERIFY( max_size_t(-i) * max_size_t(-j) == i*j );
	  VERIFY( max_size_t(-j) * max_size_t(-i) == j*i );
	  VERIFY( rep_t(((mu+1)+i)*((mu+1)+j)) == rep_t(i*j) );
	  VERIFY( rep_t(((mu+1)+j)*((mu+1)+i)) == rep_t(j*i) );
	  if (i >= 0 && j > 0)
	    {
	      auto r = (mu+i)-((mu+i)/j)*j;
	      VERIFY( r >= 0 && r < j );
	      VERIFY( r == (mu+i)%j );
	    }
	}
    }
}

void
test05()
{
#if __SIZEOF_INT128__
  max_size_t x = 0;
  x = static_cast<__int128>(0);
  x = static_cast<unsigned __int128>(0);

  max_diff_t y = 0;
  y = static_cast<__int128>(0);;
  y = static_cast<unsigned __int128>(0);
#endif
}

using std::numeric_limits;

static_assert(numeric_limits<max_size_t>::is_specialized);
static_assert(!numeric_limits<max_size_t>::is_signed);
static_assert(numeric_limits<max_size_t>::is_integer);
static_assert(numeric_limits<max_size_t>::is_exact);
// We can't unconditionally use numeric_limits here because __int128 is an
// integral type only in GNU mode.
#if __SIZEOF_INT128__
static_assert(numeric_limits<max_size_t>::digits == 129);
static_assert(numeric_limits<max_size_t>::digits10 == 38);
static_assert(numeric_limits<max_size_t>::max()
	      == 2*max_size_t(~rep_t(0)) + 1);
#else
static_assert(numeric_limits<max_size_t>::digits
	      == numeric_limits<rep_t>::digits + 1);
static_assert(numeric_limits<max_size_t>::digits10
	      == numeric_limits<rep_t>::digits10);
static_assert(numeric_limits<max_size_t>::max()
	      == 2*max_size_t(numeric_limits<rep_t>::max())+1);
#endif
static_assert(numeric_limits<max_size_t>::min() == 0);
static_assert(numeric_limits<max_size_t>::max()
	      == max_size_t(-1));
static_assert((numeric_limits<max_size_t>::max()
	       >> (numeric_limits<max_size_t>::digits-1)) == 1);
static_assert(numeric_limits<max_size_t>::lowest()
	      == numeric_limits<max_size_t>::min());

static_assert(numeric_limits<max_diff_t>::is_specialized);
static_assert(numeric_limits<max_diff_t>::is_signed);
static_assert(numeric_limits<max_diff_t>::is_integer);
static_assert(numeric_limits<max_diff_t>::is_exact);
static_assert(numeric_limits<max_diff_t>::digits
	      == numeric_limits<max_size_t>::digits - 1);
static_assert(numeric_limits<max_diff_t>::digits10
	      == numeric_limits<max_size_t>::digits10);
// We can't unconditionally use numeric_limits here because __int128 is an
// integral type only in GNU mode.
#if __SIZEOF_INT128__
static_assert(numeric_limits<max_diff_t>::min() == -max_diff_t(~rep_t(0))-1);
static_assert(numeric_limits<max_diff_t>::max() == ~rep_t(0));
#else
static_assert(numeric_limits<max_diff_t>::min()
	      == -max_diff_t(numeric_limits<rep_t>::max())-1);
static_assert(numeric_limits<max_diff_t>::max()
	      == numeric_limits<rep_t>::max());
#endif
static_assert(numeric_limits<max_diff_t>::lowest()
	      == numeric_limits<max_diff_t>::min());
static_assert(max_diff_t(max_size_t(1)
			 << (numeric_limits<max_size_t>::digits-1))
	      == numeric_limits<max_diff_t>::min());

int
main()
{
  test01();

  test02<false,false>();
  test02<false,true>();
  test02<true,false>();
  test02<true,true>();

  test03<false,false>();
  test03<false,true>();
  test03<true,false>();
  test03<true,true>();

  test04();
  test05();
}
