/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
  Pseudo_QC_Int.qpp

  Qore Programming Language

  Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

  Note that the Qore library is released under a choice of three open-source
  licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
  information.
*/

#include <qore/Qore.h>
#include "qore/intern/qore_number_private.h"

string to_base_intern(int64 i, int base);

//! Methods in this pseudo-class can be executed on @ref integer "integer values"
/**
 */
qclass <int> [arg=int64 i = v.getAsBigInt();vparent=<value>];

//! Returns Qore::NT_INT
/**
    @return Qore::NT_INT

    @par Example:
    @code{.py}
switch (i.typeCode()) {
    case NT_INT:
        printf("%y: is a int\n", i);
        break;
}
    @endcode
*/
int <int>::typeCode() [flags=CONSTANT] {
   return NT_INT;
}

//! Returns @ref True if the int is non-zero, @ref False if zero
/**
    @return @ref True if the int is non-zero, @ref False if zero

    @par Example:
    @code{.py}
bool b = i.val();
    @endcode

    @see
    - @ref perl-bool-eval "%perl-bool-eval"
    - @ref strict-bool-eval "%strict-bool-eval"
*/
bool <int>::val() [flags=CONSTANT] {
   return (bool)i;
}

//! Returns @ref True by default
/**
    @return @ref True by default

    @par Example:
    @code{.py}
if (n.intp())
    printf("%y: can be converted to an integer: %d\n", n, int(n));
    @endcode
*/
bool <int>::intp() [flags=CONSTANT] {
   return true;
}

//! Returns @ref True because integer values can be converted to strings
/**
    @return @ref True because integer values can be converted to strings

    @par Example:
    @code{.py}
if (n.strp())
    printf("%y: can be converted to a string: '%s'\n", n, string(n));
    @endcode
*/
bool <int>::strp() [flags=CONSTANT] {
   return true;
}

//! Returns a formatted string of the integer according to the format argument
/** @param fmt the format string has the following format:\n
    <em>\<thousands_separator\></em><tt>[</tt><em>\<decimal_separator\>[\<decimals\>]</em><tt>]</tt>\n
    where:\n
    - \a thousands_separator and \a decimal_separator are single characters defining the thousands and decimal separator characters respectively, and
    - \a decimals is one or more digits defining how may decimals should appear after the decimal point - if this is non-zero then the decimals will all be \c "0"; a negative number means to round that number of digits before the decimal place

    @return a string of the integer according to a format string; if the format string does not follow the given format, then the number without formatting is returned

    @par Example:
    @code{.py}
int i = -48392093894;
string nstr = i.format(".,3"); # returns "-48.392.093.894,000"
    @endcode

    @see
    - @ref <float>::format(string)
    - @ref <number>::format(string)
    - @ref <int>::format(int, string, string)
    - @ref <float>::format(int, string, string)
    - @ref <number>::format(int, string, string)

    @since %Qore 0.8.6
 */
string <int>::format(string fmt) [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(fmt->getEncoding()));
   rv->sprintf(QLLD, i);
   return qore_number_private::formatNumberString(**rv, *fmt, xsink) ? 0 : rv.release();
}

//! Returns a formatted string of the integer according to format arguments
/** @param prec the number of digits after the decimal point; a negative value means to round that number of digits before the decimal place, note that a positive value here will result in the given number of zeros appearing after the decimal place
    @param decimal_sep a single character that defines the decimal separator; an empty string means no decimal separator
    @param thousands_sep a single character that defines the thousands and further separators every three digits before the decimal point; an empty string means no thousands separator

    @return a string representing the integer according to the format arguments as described above

    @par Example:
    @code{.py}
int i = -48392093894;
string nstr = i.format(3, ",", "."); # returns "-48.392.093.894,000"
    @endcode

    @see
    - @ref <number>::format(int, string, string)
    - @ref <float>::format(int, string, string)
    - @ref <number>::format(string)
    - @ref <float>::format(string)
    - @ref <int>::format(string)

    @since %Qore 0.8.13
 */
string <int>::format(int prec = 2, string decimal_sep = ".", string thousands_sep = "") [flags=RET_VALUE_ONLY] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode);
   rv->sprintf(QLLD, i);
   if (qore_number_private::formatNumberString(**rv, prec, decimal_sep, thousands_sep, xsink))
      return 0;
   return rv.release();
}

//! Returns -1 if the number is negative, 0 if it is zero, or 1 if it is positive
/** @return -1 if the number is negative, 0 if it is zero, or 1 if it is positive

    @par Example:
    @code{.py}
printf("sign: %d\n", i.sign());
    @endcode

    @since %Qore 0.8.6
*/
int <int>::sign() [flags=CONSTANT] {
   if (!i)
      return 0;
   return i > 0 ? 1 : -1;
}

//! returns a binary object with the integer encoded in the given number of bytes in most significant byte order
/** @par Example:
    @code{.py}
b += i.encodeMsb(8);
    @endcode

    @param size the size of the encoded binary object; must be 1, 2, 4, or 8 or a SIZE-ERROR exception is raised

    @return a binary object with the integer encoded in the given number of bytes in most significant byte order

    @throw SIZE-ERROR the argument is not 1, 2, 4, or 8 or the argument cannot fit in the number of bytes requested

    @since %Qore 0.8.8
 */
binary <int>::encodeMsb(int size = 4) [flags=RET_VALUE_ONLY] {
   SimpleRefHolder<BinaryNode> b(new BinaryNode);
   switch (size) {
      case 1: {
         unsigned char c = i;
         if (i != c)
            return xsink->raiseException("SIZE-ERROR", "the integer value called with <int>::encodeMsb(size=1) (" QLLD ") is too large to fit in 1 byte", i);
         b->append((void*)&c, 1);
         break;
      }

      case 2: {
         uint16_t num = i;
         if (i != num)
            return xsink->raiseException("SIZE-ERROR", "the integer value called with <int>::encodeMsb(size=2) (" QLLD ") is too large to fit in 2 bytes", i);
         num = htons(num);
         b->append((void*)&num, 2);
         break;
      }

      case 4: {
         uint32_t num = i;
         if (i != num)
            return xsink->raiseException("SIZE-ERROR", "the integer value called with <int>::encodeMsb(size=4) (" QLLD ") is too large to fit in 4 bytes", i);
         num = htonl(num);
         b->append((void*)&num, 4);
         break;
      }

      case 8: {
         int64 num = i8MSB(i);
         b->append((void*)&num, 8);
         break;
      }

      default:
         return xsink->raiseException("SIZE-ERROR", "the size given to <int>::encodeMsb(" QLLD ") is not 1, 2, 4, or 8", size);
   }
   return b.release();
}

//! returns a binary object with the integer encoded in the given number of bytes in least significant byte order
/** @par Example:
    @code{.py}
b += i.encodeLsb(8);
    @endcode

    @param size the size of the encoded binary object; must be 1, 2, 4, or 8 or a SIZE-ERROR exception is raised

    @return a binary object with the integer encoded in the given number of bytes in least significant byte order

    @throw SIZE-ERROR the argument is not 1, 2, 4, or 8 or the argument cannot fit in the number of bytes requested

    @since %Qore 0.8.8
 */
binary <int>::encodeLsb(int size = 4) [flags=RET_VALUE_ONLY] {
   SimpleRefHolder<BinaryNode> b(new BinaryNode);
   switch (size) {
      case 1: {
         unsigned char c = i;
         if (i != c)
            return xsink->raiseException("SIZE-ERROR", "the integer value called with <int>::encodeLsb(size=1) (" QLLD ") is too large to fit in 1 byte", i);
         b->append((void*)&c, 1);
         break;
      }

      case 2: {
         uint16_t num = i;
         if (i != num)
            return xsink->raiseException("SIZE-ERROR", "the integer value called with <int>::encodeLsb(size=2) (" QLLD ") is too large to fit in 2 bytes", i);
         num = i2LSB(num);
         b->append((void*)&num, 2);
         break;
      }

      case 4: {
         uint32_t num = i;
         if (i != num)
            return xsink->raiseException("SIZE-ERROR", "the integer value called with <int>::encodeLsb(size=4) (" QLLD ") is too large to fit in 4 bytes", i);
         num = i4LSB(num);
         b->append((void*)&num, 4);
         break;
      }

      case 8: {
         int64 num = i8LSB(i);
         b->append((void*)&num, 8);
         break;
      }

      default:
         return xsink->raiseException("SIZE-ERROR", "the size given to <int>::encodeLsb(%d) is not 1, 2, 4, or 8", size);
   }
   return b.release();
}

//! Returns the absolute value of the number
/** @par Example:
    @code{.py}
i = i.abs();
    @endcode

    @return the absolute value of the number

    @note equivalent to abs(int)

    @since %Qore 0.8.8
*/
int <int>::abs() [flags=CONSTANT] {
   return i < 0 ? -i : i;
}

//! Returns a single character string in UTF-8 encoding for the integer value treated as a unicode value
/** @par Example:
    @code{.py}
string str = 0x00f8.toUnicode()
    @endcode

    @return a single character string in UTF-8 encoding for the integer value treated as a unicode value

    @note only the least-significant 4 bytes of the integer are used

    @since %Qore 0.8.8
*/
string <int>::toUnicode() [flags=RET_VALUE_ONLY] {
   SimpleRefHolder<QoreStringNode> str(new QoreStringNode(QCS_UTF8));
   str->concatUnicode(i);
   return *xsink ? 0 : str.release();
}

//! Converts the integer to a different <b>base</b> (and returns it as a string).
/** @param base the base to convert the integer to; this value must be 2 - 36 inclusive or an \c INVALID-BASE exception will be thrown

    @return the converted value as a string

    @par Example:
    @code{.py}
        27.toBase(2));       // returns "11011"
        033.toBase(2));      // returns "11011"
        0x1B.toBase(2));     // returns "11011"
        (-27).toBase(5));    // returns  "-102"
        (-0x1B).toBase(5));  // returns  "-102"
        27.toBase(8));       // returns    "33"
        033.toBase(8));      // returns    "33"
        27.toBase());        // returns    "27"
        033.toBase());       // returns    "27"
        0x1B.toBase());      // returns    "27"
        (-27).toBase(13));   // returns   "-21"
        27.toBase(16));      // returns    "1B"
        0x1B.toBase(16));    // returns    "1B"
        27.toBase(27));      // returns    "10"
        27.toBase(28));      // returns     "R"
        (-27).toBase(36));   // returns    "-R"
        35.toBase(36));      // returns     "Z"
    @endcode

    @throw INVALID-BASE the base is invalid; must be 2 - 36 inclusive

    @see \link <number>::toBase(int base) \endlink
    @see \link <float>::toBase(int base) \endlink

    @since %Qore 0.8.13
*/
string <int>::toBase(int base = 10) [flags=RET_VALUE_ONLY] {
    if (base < 2 || base > 36)
        return xsink->raiseException("INVALID-BASE", "base " QLLD " is invalid; base must be 2 - 36 inclusive", base);

    string s = to_base_intern(i, base);
    return new QoreStringNode(s);
}

string to_base_intern(int64 i, int base) {
    if (i < 0)
        return '-' + to_base_intern(-i, base);

    string s;
    while (true) {
        int rem = i % base;
        s = char((rem < 10 ? 48 : 55) + rem) + s;
        if ((i /= base) == 0)
            break;
    }
    return s;
}
