/* Copyright (c) 2018, MariaDB

   This program 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; version 2 of the License.

   This program 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 program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1335  USA */

#ifndef SQL_TYPE_INT_INCLUDED
#define SQL_TYPE_INT_INCLUDED


class Null_flag
{
protected:
  bool m_is_null;
public:
  bool is_null() const { return m_is_null; }
  Null_flag(bool is_null) :m_is_null(is_null) { }
};


class Longlong
{
protected:
  longlong m_value;
public:
  longlong value() const { return m_value; }
  Longlong(longlong nr) :m_value(nr) { }
  ulonglong abs()
  {
    if (m_value == LONGLONG_MIN) // avoid undefined behavior
      return ((ulonglong) LONGLONG_MAX) + 1;
    return m_value < 0 ? -m_value : m_value;
  }
};


class Longlong_null: public Longlong, public Null_flag
{
public:
  Longlong_null(longlong nr, bool is_null)
   :Longlong(nr), Null_flag(is_null)
  { }
};


class ULonglong
{
protected:
  ulonglong m_value;
public:
  ulonglong value() const { return m_value; }
  explicit ULonglong(ulonglong nr) :m_value(nr) { }

  static bool test_if_sum_overflows_ull(ulonglong arg1, ulonglong arg2)
  {
    return ULONGLONG_MAX - arg1 < arg2;
  }

  Longlong_null operator-() const
  {
    if (m_value > (ulonglong) LONGLONG_MAX) // Avoid undefined behaviour
    {
      return m_value == (ulonglong) LONGLONG_MAX + 1 ?
             Longlong_null(LONGLONG_MIN, false) :
             Longlong_null(0, true);
    }
    return Longlong_null(-(longlong) m_value, false);
  }

  // Convert to Longlong_null with the range check
  Longlong_null to_longlong_null() const
  {
    if (m_value > (ulonglong) LONGLONG_MAX)
      return Longlong_null(0, true);
    return Longlong_null((longlong) m_value, false);
  }

};


class ULonglong_null: public ULonglong, public Null_flag
{
public:
  ULonglong_null(ulonglong nr, bool is_null)
   :ULonglong(nr), Null_flag(is_null)
  { }

  /*
    Multiply two ulonglong values.

    Let a = a1 * 2^32 + a0 and b = b1 * 2^32 + b0. Then
    a * b = (a1 * 2^32 + a0) * (b1 * 2^32 + b0) = a1 * b1 * 2^64 +
            + (a1 * b0 + a0 * b1) * 2^32 + a0 * b0;
    We can determine if the above sum overflows the ulonglong range by
    sequentially checking the following conditions:
    1. If both a1 and b1 are non-zero.
    2. Otherwise, if (a1 * b0 + a0 * b1) is greater than ULONG_MAX.
    3. Otherwise, if (a1 * b0 + a0 * b1) * 2^32 + a0 * b0 is greater than
    ULONGLONG_MAX.
  */
  static ULonglong_null ullmul(ulonglong a, ulonglong b)
  {
    ulong a1= a >> 32;
    ulong b1= b >> 32;

    if (a1 && b1)
      return ULonglong_null(0, true);

    ulong a0= 0xFFFFFFFFUL & a;
    ulong b0= 0xFFFFFFFFUL & b;

    ulonglong res1= (ulonglong) a1 * b0 + (ulonglong) a0 * b1;
    if (res1 > 0xFFFFFFFFUL)
      return ULonglong_null(0, true);

    res1= res1 << 32;
    ulonglong res0= (ulonglong) a0 * b0;

    if (test_if_sum_overflows_ull(res1, res0))
      return ULonglong_null(0, true);
    return ULonglong_null(res1 + res0, false);
  }
};


// A longlong/ulonglong hybrid. Good to store results of val_int().
class Longlong_hybrid: public Longlong
{
protected:
  bool m_unsigned;
  int cmp_signed(const Longlong_hybrid& other) const
  {
    return m_value < other.m_value ? -1 : m_value == other.m_value ? 0 : 1;
  }
  int cmp_unsigned(const Longlong_hybrid& other) const
  {
    return (ulonglong) m_value < (ulonglong) other.m_value ? -1 :
            m_value == other.m_value ? 0 : 1;
  }
public:
  Longlong_hybrid(longlong nr, bool unsigned_flag)
   :Longlong(nr), m_unsigned(unsigned_flag)
  { }
  bool is_unsigned() const { return m_unsigned; }
  bool is_unsigned_outside_of_signed_range() const
  {
    return m_unsigned && ((ulonglong) m_value) > (ulonglong) LONGLONG_MAX;
  }
  bool neg() const { return m_value < 0 && !m_unsigned; }
  ulonglong abs() const
  {
    if (m_unsigned)
      return (ulonglong) m_value;
    return Longlong(m_value).abs();
  }
  /*
    Convert to an unsigned number:
    - Negative numbers are converted to 0.
    - Positive numbers bigger than upper_bound are converted to upper_bound.
    - Other numbers are returned as is.
  */
  ulonglong to_ulonglong(ulonglong upper_bound) const
  {
    return neg() ? 0 :
           (ulonglong) m_value > upper_bound ? upper_bound :
           (ulonglong) m_value;
  }
  uint to_uint(uint upper_bound) const
  {
    return (uint) to_ulonglong(upper_bound);
  }


  Longlong_null val_int_signed() const
  {
    if (m_unsigned)
      return ULonglong((ulonglong) m_value).to_longlong_null();
    return Longlong_null(m_value, false);
  }

  Longlong_null val_int_unsigned() const
  {
    if (!m_unsigned && m_value < 0)
      return Longlong_null(0, true);
    return Longlong_null(m_value, false);
  }

  /*
    Return in Item compatible val_int() format:
    - signed numbers as a straight longlong value
    - unsigned numbers as a ulonglong value reinterpreted to longlong
  */
  Longlong_null val_int(bool want_unsigned_value) const
  {
    return want_unsigned_value ? val_int_unsigned() :
                                 val_int_signed();
  }

  int cmp(const Longlong_hybrid& other) const
  {
    if (m_unsigned == other.m_unsigned)
      return m_unsigned ? cmp_unsigned(other) : cmp_signed(other);
    if (is_unsigned_outside_of_signed_range())
      return 1;
    if (other.is_unsigned_outside_of_signed_range())
      return -1;
    /*
      The unsigned argument is in the range 0..LONGLONG_MAX.
      The signed argument is in the range LONGLONG_MIN..LONGLONG_MAX.
      Safe to compare as signed.
    */
    return cmp_signed(other);
  }
  bool operator==(const Longlong_hybrid &nr) const
  {
    return cmp(nr) == 0;
  }
  bool operator==(ulonglong nr) const
  {
    return cmp(Longlong_hybrid((longlong) nr, true)) == 0;
  }
  bool operator==(uint nr) const
  {
    return cmp(Longlong_hybrid((longlong) nr, true)) == 0;
  }
  bool operator==(longlong nr) const
  {
    return cmp(Longlong_hybrid(nr, false)) == 0;
  }
  bool operator==(int nr) const
  {
    return cmp(Longlong_hybrid(nr, false)) == 0;
  }
};


class Longlong_hybrid_null: public Longlong_hybrid,
                            public Null_flag
{
public:
  Longlong_hybrid_null(const Longlong_null &nr, bool unsigned_flag)
   :Longlong_hybrid(nr.value(), unsigned_flag),
    Null_flag(nr.is_null())
  { }
};


/*
  Stores the absolute value of a number, and the sign.
  Value range: -ULONGLONG_MAX .. +ULONGLONG_MAX.

  Provides a wider range for negative numbers than Longlong_hybrid does.
  Usefull to store intermediate results of an expression whose value
  is further needed to be negated. For example, these methods:
    - Item_func_mul::int_op()
    - Item_func_int_div::val_int()
    - Item_func_mod::int_op()
  calculate the result of absolute values of the arguments,
  then optionally negate the result.
*/
class ULonglong_hybrid: public ULonglong
{
  bool m_neg;
public:
  ULonglong_hybrid(ulonglong value, bool neg)
   :ULonglong(value), m_neg(neg)
  {
    if (m_neg && !m_value)
      m_neg= false;        // convert -0 to +0
  }
  Longlong_null val_int_unsigned() const
  {
    return m_neg ? Longlong_null(0, true) :
                   Longlong_null((longlong) m_value, false);
  }
  Longlong_null val_int_signed() const
  {
    return m_neg ? -ULonglong(m_value) : ULonglong::to_longlong_null();
  }

  /*
    Return in Item compatible val_int() format:
    - signed numbers as a straight longlong value
    - unsigned numbers as a ulonglong value reinterpreted to longlong
  */
  Longlong_null val_int(bool want_unsigned_value) const
  {
    return want_unsigned_value ? val_int_unsigned() :
                                 val_int_signed();
  }
};


#endif // SQL_TYPE_INT_INCLUDED
