/**
 * Author: dkanus
 * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
 * License: GPL
 * Copyright 2022-2023 Anton Tarasenko
 *------------------------------------------------------------------------------
 * This file is part of Acedia.
 *
 * Acedia 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 3 of the License, or
 * (at your option) any later version.
 *
 * Acedia 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 Acedia.  If not, see <https://www.gnu.org/licenses/>.
 */
class BigInt extends AcediaObject
    dependson(MathApi);

/// A simple big integer implementation.
///
/// [`BigInt`]'s main purpose is to allow Acedia's databases to store integers of arbitrary size.
/// It can be used for long arithmetic computations, but it was mainly meant as a players'
/// statistics counter and, therefore, not optimized for performing large amount of operations.

/// [`BigInt`] data as a struct - meant to be used to store [`BigInt`]'s values inside
/// the local databases.
struct BigIntData {
    var bool negative;
    var array<byte> digits;
};

/// Result of comparison for [`BigInt`]s with each other.
enum BigIntCompareResult
{
    BICR_Less,
    BICR_Equal,
    BICR_Greater
};

/// Does stored [`BigInt`] have a negative sign?
var private bool negative;
/// Digits array, from least to most significant. For example, for 13524:
///
/// ```
/// `digits[0] = 4`
/// `digits[1] = 2`
/// `digits[2] = 5`
/// `digits[3] = 3`
/// `digits[4] = 1`
/// ```
///
/// Valid [`BigInt`] should not have this array empty: zero should be represented by an array with
/// a single `0`-element.
/// This isn't a most efficient representation for [`BigInt`], but it's easy to convert to and from
/// decimal representation.
///
/// # Invariants
///
/// This array must not have leading (in the sense of significance) zeroes.
/// That is, last element of the array should not be a `0`.
/// The only exception if if stored value is `0`, then `digits` must consist of
/// a single `0` element.
var private array<byte> digits;

/// Constants useful for converting [`BigInt`] back to [`int`], while avoiding overflow.
/// We can add less digits than that without any fear of overflow.
const DIGITS_IN_MAX_INT = 10;
/// Maximum [`int`] value is `2147483647`, so in case most significant digit is 10th and is `2`
/// (so number has a form of `2xxxxxxxxx`), to check for overflow we only need to compare
/// combination of the rest of the digits with this constant.
const ALMOST_MAX_INT = 147483647;
/// To add last digit we add/subtract that digit multiplied by this value.
const LAST_DIGIT_ORDER  = 1000000000;

protected function Constructor() {
    SetZero();
}

protected function Finalizer() {
    negative = false;
    digits.length = 0;
}

//  Auxiliary method to set current value to zero
private function SetZero() {
    negative = false;
    digits.length = 1;
    digits[0] = 0;
}

// Minimal [`int`] value `-2,147,483,648` is somewhat of a pain to handle, so just use this
// auxiliary pre-made constructor for it
private function SetMinimalNegative() {
    negative = true;
    digits.length = 10;
    digits[0] = 8;
    digits[1] = 4;
    digits[2] = 6;
    digits[3] = 3;
    digits[4] = 8;
    digits[5] = 4;
    digits[6] = 7;
    digits[7] = 4;
    digits[8] = 1;
    digits[9] = 2;
}

// Removes unnecessary zeroes from leading digit positions `digits`.
// Does not change contained value.
private final function TrimLeadingZeroes() {
    local int i, zeroesToRemove;

    // Finds how many leading zeroes there is.
    // Since `digits` stores digits from least to most significant, we need to check from the end of
    // `digits` array.
    for (i = digits.length - 1; i >= 0; i -= 1) {
        if (digits[i] != 0) {
            break;
        }
        zeroesToRemove += 1;
    }
    // `digits` must not be empty, enforce `0` value in that case
    if (zeroesToRemove  >= digits.length) {
        SetZero();
    }
    else {
        digits.length = digits.length - zeroesToRemove;
    }
}

/// Changes current value of [`BigInt`] to given value.
public final function Set(BigInt value)
{
    if (value != none) {
        value.TrimLeadingZeroes();
        digits = value.digits;
        negative = value.negative;
    }
}

/// Changes current value of [`BigInt`] to given value.
public final function SetInt(int value) {
    local MathApi.IntegerDivisionResult divisionResult;

    negative = false;
    digits.length = 0;
    if (value < 0) {
        //  Treat special case of minimal [`int`] value `-2,147,483,648` that
        //  won't fit into positive [`int`] as special and use pre-made
        //  specialized constructor `CreateMinimalNegative()`
        if (value < -maxInt) {
            SetMinimalNegative();
            return;
        } else {
            negative = true;
            value *= -1;
        }
    }
    if (value == 0) {
        digits[0] = 0;
    } else {
        while (value > 0) {
            divisionResult = __().math.IntegerDivision(value, 10);
            value                   = divisionResult.quotient;
            digits[digits.length]   = divisionResult.remainder;
        }
    }
    TrimLeadingZeroes();
}

/// Changes current value of [`BigInt`] to the value, given by decimal representation.
///
/// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given
/// as an argument, caller's value won't change.
/// Returns `true` in case of success and `false` otherwise.
public final function bool SetDecimal(BaseText value) {
    local int i;
    local byte nextDigit;
    local bool newNegative;
    local array<byte> newDigits; 
    local Parser parser;
    local Basetext.Character nextCharacter;

    if (value == none) {
        return false;
    }
    parser = value.Parse();
    newNegative = ParseSign(parser);
    // Reset to valid state whether sign was consumed or not
    parser.Confirm();
    parser.R();
    newDigits.length = parser.GetRemainingLength();
    // Parse new one
    i = newDigits.length - 1;
    while (!parser.HasFinished()){
        //  This should not happen, but just in case
        if (i < 0) {
            break;
        }
        parser.MCharacter(nextCharacter);
        nextDigit = __().text.CharacterToInt(nextCharacter, 10);
        if (nextDigit < 0) {
            return false;
        }
        newDigits[i] = nextDigit;
        i -= 1;
    }
    parser.FreeSelf();
    digits = newDigits;
    negative = newNegative;
    TrimLeadingZeroes();
    return true;
}

// Tries to parse either `+` or `-` and returns `true` iff it parsed `-`.
// If neither got parsed, `parser` will enter failed state.
// Assumes `parser` isn't `none`.
private final function bool ParseSign(Parser parser) {
    parser.Match(P("-"));
    negative = parser.Ok();
    if (parser.Ok()) {
        negative = true;
    }
    else {
        parser.R();
        parser.Match(P("+"));
    }
    return negative;
}

/// Changes current value of [`BigInt`] to the value, given by decimal representation.
///
/// If invalid decimal representation (digits only, possibly with leading sign) is given as
/// an argument, caller's value won't change.
/// Returns `true` in case of success and `false` otherwise.
public final function bool SetDecimal_S(string value) {
    local bool result;
    local MutableText wrapper;

    wrapper = __().text.FromStringM(value);
    result = SetDecimal(wrapper);
    wrapper.FreeSelf();
    return result;
}

// Auxiliary method for comparing two [`BigInt`]s by their absolute value.
private function BigIntCompareResult _compareAbsolute(BigInt other) {
    local int i;
    local array<byte> otherDigits;

    otherDigits = other.digits;
    if (digits.length == otherDigits.length)
    {
        for (i = digits.length - 1; i >= 0; i -= 1)
        {
            if (digits[i] < otherDigits[i]) {
                return BICR_Less;
            }
            if (digits[i] > otherDigits[i]) {
                return BICR_Greater;
            }
        }
        return BICR_Equal;
    }
    if (digits.length < otherDigits.length) {
        return BICR_Less;
    }
    return BICR_Greater;
}

/// Compares caller [`BigInt`] to [`other`].
///
/// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
/// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
/// it means that caller [`BigInt`] is smaller that `other`.
/// If argument is `none`, then it is considered to be less ([`BICR_Less`]) than caller [`BigInt`].
public function BigIntCompareResult Compare(BigInt other) {
    local BigIntCompareResult resultForModulus;

    if (other == none)                                  return BICR_Less;
    if (negative && !other.negative)                    return BICR_Less;
    if (!negative && other.negative)                    return BICR_Greater;
    resultForModulus = _compareAbsolute(other);
    if (resultForModulus == BICR_Equal)                 return BICR_Equal;
    if (negative && (resultForModulus == BICR_Greater)) return BICR_Less;
    if (!negative  &&  (resultForModulus == BICR_Less)) return BICR_Less;

    return BICR_Greater;
}

/// Compares caller [`BigInt`] to [`other`].
///
/// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
/// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
/// it means that caller [`BigInt`] is smaller that `other`.
public function BigIntCompareResult CompareInt(int other) {
    local BigInt wrapper;
    local BigIntCompareResult result;

    wrapper = _.math.ToBigInt(other);
    result = Compare(wrapper);
    wrapper.FreeSelf();
    return result;
}

/// Compares caller [`BigInt`] to a decimal representation of a number.
///
/// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
/// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
/// it means that caller [`BigInt`] is smaller that `other`.
/// If argument is `none` or is an invalid decimal representation (digits only, possibly with
/// leading sign, then it is considered to be less ([`BICR_Less`]) than caller [`BigInt`].
public function BigIntCompareResult CompareDecimal(BaseText other) {
    local BigInt wrapper;
    local BigIntCompareResult result;

    wrapper = _.math.MakeBigInt(other);
    result = Compare(wrapper);
    _.memory.Free(wrapper);
    return result;
}

/// Compares caller [`BigInt`] to a decimal representation of a number.
///
/// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
/// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
/// it means that caller [`BigInt`] is smaller that `other`.
/// If argument is an invalid decimal representation (digits only, possibly with leading sign, then
/// it is considered to be less ([`BICR_Less`]) than caller [`BigInt`].
public function BigIntCompareResult CompareDecimal_S(string other) {
    local BigInt wrapper;
    local BigIntCompareResult result;

    wrapper = _.math.MakeBigInt_S(other);
    result = Compare(wrapper);
    wrapper.FreeSelf();
    return result;
}

// Adds absolute values of caller [`BigInt`] and [`other`] with no changes to the sign.
private function _add(BigInt other) {
    local int i;
    local byte carry, digitSum;
    local array<byte> otherDigits;

    if (other == none) {
        return;
    }
    otherDigits = other.digits;
    if (digits.length < otherDigits.length) {
        digits.length = otherDigits.length;
    } else {
        otherDigits.length = digits.length;
    }
    carry = 0;
    for (i = 0; i < digits.length; i += 1) {
        digitSum = digits[i] + otherDigits[i] + carry;
        digits[i] = _.math.Remainder(digitSum, 10);
        carry = (digitSum - digits[i]) / 10;
    }
    if (carry > 0) {
        digits[digits.length] = carry;
    }
    //  No leading zeroes can be created here, so no need to trim
}

// Subtracts absolute value of [`other`] from the caller [`BigInt`], flipping caller's sign in case
// `other`'s absolute value is bigger.
private function _sub(BigInt other) {
    local int i;
    local int carry, nextDigit;
    local array<byte> minuendDigits, subtrahendDigits;
    local BigIntCompareResult resultForModulus;

    if (other == none) {
        return;
    }
    resultForModulus = _compareAbsolute(other);
    if (resultForModulus == BICR_Equal) {
        SetZero();
        return;
    }
    if (resultForModulus == BICR_Less) {
        negative = !negative;
        minuendDigits = other.digits;
        subtrahendDigits = digits;
    } else {
        minuendDigits = digits;
        subtrahendDigits = other.digits;
    }
    digits.length = minuendDigits.length;
    subtrahendDigits.length = minuendDigits.length;
    carry = 0;
    for (i = 0; i < digits.length; i += 1) {
        nextDigit = int(minuendDigits[i]) - int(subtrahendDigits[i]) + carry;
        if (nextDigit < 0) {
            nextDigit += 10;
            carry = -1;
        } else {
            carry = 0;
        }
        digits[i] = nextDigit;
    }
    TrimLeadingZeroes();
}

/// Adds another value to the caller [`BigInt`].
///
/// If argument is `none`, then given method does nothing.
public function Add(BigInt other) {
    if (other == none) {
        return;
    }
    if (negative == other.negative) {
        _add(other);
    } else {
        _sub(other);
    }
}

/// Adds another value to the caller [`BigInt`].
public function AddInt(int other) {
    local BigInt otherObject;

    otherObject = _.math.ToBigInt(other);
    Add(otherObject);
    _.memory.Free(otherObject);
}

/// Adds decimal representation of the number to the caller [`BigInt`].
///
/// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given -
/// does nothing.
public function AddDecimal(BaseText other) {
    local BigInt otherObject;

    if (other == none) {
        return;
    }
    otherObject = _.math.MakeBigInt(other);
    Add(otherObject);
    _.memory.Free(otherObject);
}

/// Adds decimal representation of the number to the caller [`BigInt`].
///
/// If invalid decimal representation (digits only, possibly with leading sign) is given -
/// does nothing.
public function AddDecimal_S(string other) {
    local BigInt otherObject;

    otherObject = _.math.MakeBigInt_S(other);
    Add(otherObject);
    _.memory.Free(otherObject);
}

/// Subtracts another value to the caller [`BigInt`].
///
/// If argument is `none`, then given method does nothing.
public function Subtract(BigInt other) {
    if (negative != other.negative) {
        _add(other);
    } else {
        _sub(other);
    }
}

/// Adds another value to the caller [`BigInt`].
public function SubtractInt(int other) {
    local BigInt otherObject;

    otherObject = _.math.ToBigInt(other);
    Subtract(otherObject);
    _.memory.Free(otherObject);
}

/// Subtracts decimal representation of the number to the caller [`BigInt`].
///
/// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given -
/// does nothing.
public function SubtractDecimal(BaseText other) {
    local BigInt otherObject;

    if (other == none) {
        return;
    }
    otherObject = _.math.MakeBigInt(other);
    Subtract(otherObject);
    _.memory.Free(otherObject);
}

/// Adds decimal representation of the number to the caller [`BigInt`].
///
/// If invalid decimal representation (digits only, possibly with leading sign) is given -
/// does nothing.
public function SubtractDecimal_S(string other) {
    local BigInt otherObject;

    otherObject = _.math.MakeBigInt_S(other);
    Subtract(otherObject);
    _.memory.Free(otherObject);
}

/// Checks if caller [`BigInt`] is negative.
///
/// Returns if stored value is negative and `false` otherwise.
/// Zero is not considered negative number.
public function bool IsNegative() {
    //  Handle special case of zero first (it ignores `negative` flag)
    if (digits.length == 1 && digits[0] == 0) {
        return false;
    }
    return negative;
}

/// Converts caller [`BigInt`] into [`int`] representation.
///
/// In case stored value is outside `int`'s value range
/// (`[-maxInt-1, maxInt] == [-2147483648; 2147483647]`), method returns either maximal or minimal
//  possible value, depending on the [`BigInt`]'s sign.
public function int ToInt() {
    local int i;
    local int accumulator;
    local int safeDigitsAmount;

    if (digits.length <= 0) {
        return 0;
    }
    if (digits.length > DIGITS_IN_MAX_INT) {
        if (negative) {
            return (-maxInt - 1);
        } else {
            return maxInt;
        }
    }
    // At most `DIGITS_IN_MAX_INT - 1` iterations
    safeDigitsAmount = Min(DIGITS_IN_MAX_INT - 1, digits.length);
    for (i = safeDigitsAmount - 1; i >= 0; i -= 1) {
        accumulator *= 10;
        accumulator += digits[i];
    }
    if (negative) {
        accumulator *= -1;
    }
    accumulator = AddUnsafeDigitToInt(accumulator);
    return accumulator;
}

/// Adding `DIGITS_IN_MAX_INT - 1` will never lead to an overflow, but adding the next digit can,
/// so we need to handle it differently and more carefully.
/// Assumes `digits.length <= DIGITS_IN_MAX_INT`.
private function int AddUnsafeDigitToInt(int accumulator) {
    local int unsafeDigit;
    local bool noOverflow;

    if (digits.length < DIGITS_IN_MAX_INT) {
        return accumulator;
    }
    unsafeDigit = digits[DIGITS_IN_MAX_INT - 1];
    // `maxInt` stats with `2`, so if last/unsafe digit is either `0` or `1`, there is no overflow,
    // otherwise - check rest of the digits
    noOverflow =  (unsafeDigit < 2);
    if (unsafeDigit == 2) {
        // Include `maxInt` and `-maxInt-1` (minimal possible value) into an overflow too - this way
        // we still give a correct result, but do not have to worry about `int`-arithmetic error
        noOverflow = noOverflow
            ||  (negative && (accumulator > -ALMOST_MAX_INT - 1))
            ||  (!negative && (accumulator < ALMOST_MAX_INT));
    }
    if (noOverflow) {
        if (negative) {
            accumulator -= unsafeDigit * LAST_DIGIT_ORDER;
        }
        else {
            accumulator += unsafeDigit * LAST_DIGIT_ORDER;
        }
        return accumulator;
    }
    //  Handle overflow
    if (negative) {
        return (-maxInt - 1);
    }
    return maxInt;
}

/// Converts caller [`BigInt`] into [`Text`] representation.
public function Text ToText() {
    return ToText_M().IntoText();
}

/// Converts caller [`BigInt`] into [`MutableText`] representation.
public function MutableText ToText_M() {
    local int i;
    local MutableText result;

    result = _.text.Empty();
    if (negative) {
        result.AppendCharacter(_.text.GetCharacter("-"));
    }
    for (i = digits.length - 1; i >= 0; i -= 1) {
        result.AppendCharacter(_.text.CharacterFromCodePoint(digits[i] + 48));
    }
    return result;
}

/// Converts caller [`BigInt`] into [`string`] representation.
public function string ToString() {
    local int i;
    local string result;

    if (negative) {
        result = "-";
    }
    for (i = digits.length - 1; i >= 0; i -= 1) {
        result = result $ digits[i];
    }
    return result;
}

/// Restores [`BigInt`] from the [`BigIntData`] value.
///
/// This method is created to make an efficient way to store [`BigInt`].
public function FromData(BigIntData data) {
    local int i;

    negative = data.negative;
    digits = data.digits;
    //  Deal with possibly erroneous data
    for (i = 0; i < digits.length; i += 1) {
        if (digits[i] > 9) {
            digits[i] = 9;
        }
    }
}

/// Converts caller [`BigInt`]'s value into [`BigIntData`].
///
/// This method is created to make an efficient way to store [`BigInt`].
public function BigIntData ToData() {
    local BigIntData result;

    result.negative = negative;
    result.digits = digits;
    return result;
}

defaultproperties {
}