// SPDX-License-Identifier: LGPL-2.1-or-later

/***************************************************************************
 *   Copyright (c) 2025 Kevin Martin <kpmartin@papertrail.ca>              *
 *                                                                         *
 *   This file is part of FreeCAD.                                         *
 *                                                                         *
 *   FreeCAD is free software: you can redistribute it and/or modify it    *
 *   under the terms of the GNU Lesser General Public License as           *
 *   published by the Free Software Foundation, either version 2.1 of the  *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   FreeCAD 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      *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with FreeCAD. If not, see                               *
 *   <https://www.gnu.org/licenses/>.                                      *
 *                                                                         *
 ***************************************************************************/


#ifndef SRC_BASE_UNLIMITEDUNSIGNED_H_
#define SRC_BASE_UNLIMITEDUNSIGNED_H_

#ifndef FC_GLOBAL_H
# include <FCGlobal.h>
#endif
#include <vector>
#include <string>
#include <stdexcept>

// ----------------------------------------------------------------------------

namespace Base
{
class UnlimitedUnsigned
{
private:
    // This should be a signed type so we can get transient negative values during calculations
    // without resorting to a larger type
    using PartType = int32_t;
    // The following must be a type that limits the value to >= 0 and < maxPartPlusOne
    using SmallDeltaType = unsigned char;
    explicit UnlimitedUnsigned(std::vector<PartType>&& partsVector)
        : parts(std::move(partsVector))
    {}
    // We take a really cheap floor(bitcount * log10(max PartType)). 3/10 is just a bit less
    // than log10(2) The value should be sizeof(PartType) * CHAR_BIT * 3 / 10 but we can't
    // calculate the corresponding maxPartPlusOne in a static const initializer, so we just wire
    // in the values for 4-byte PartType.
    static const size_t partDigitCount = 9;
    static const PartType maxPartPlusOne = 1000000000;  // (PartType)pow(10, partDigitCount); but
                                                        // can't call pow in a const ctor.

public:
    explicit UnlimitedUnsigned(SmallDeltaType value)
        : parts(std::vector<PartType>(1, value))
    {}
    // Parse a decimal digit string into an UnlimitedUnsigned. There should be no white space,
    // only digits. a zero-length string parses as zero.
    static UnlimitedUnsigned fromString(const std::string& text)
    {
        std::vector<PartType> result((text.size() + partDigitCount - 1) / partDigitCount);
        size_t minimumSize = 0;
        size_t lastStartPosition = text.size();
        // Parse chunks of digits starting with the least significant
        for (size_t i = 0; i < result.size(); i++) {
            if (partDigitCount >= lastStartPosition) {
                // partial chunk of leading digits
                result[i] = static_cast<PartType>(std::stoul(text.substr(0, lastStartPosition)));
            }
            else {
                lastStartPosition -= partDigitCount;
                result[i] = static_cast<PartType>(
                    std::stoul(text.substr(lastStartPosition, partDigitCount))
                );
            }
            if (result[i] != 0) {
                minimumSize = i + 1;
            }
        }
        result.resize(minimumSize);
        return UnlimitedUnsigned(std::move(result));
    }
    std::string toString()
    {
        std::string result;
        result.reserve(parts.size() * partDigitCount);
        // Format the chunks from most- to least-significant
        for (auto it = parts.rbegin(); it != parts.rend(); it++) {
            std::string partDigits = std::to_string(*it);
            if (!result.empty()) {
                size_t padding = partDigitCount - partDigits.size();
                if (padding > 0) {
                    result += std::string(padding, '0');
                }
            }
            result += partDigits;
        }
        return result;
    }
    // TODO: Some allocation of vector contents could be avoided by defining methods like
    // static UnlimitedUnsigned operator+(UnlimitedUnsigned&& left, const UnlimitedUnsigned&
    // right); static UnlimitedUnsigned operator+(const UnlimitedUnsigned& left,
    // UnlimitedUnsigned&& right); static UnlimitedUnsigned operator+(UnlimitedUnsigned&& left,
    // UnlimitedUnsigned&& right); which would reuse left.parts or right.parts after possibly
    // growing it. The last one would use the larger of the two buffers.
    UnlimitedUnsigned operator+(const UnlimitedUnsigned& right) const
    {
        size_t resultSize = std::max(parts.size(), right.parts.size());
        std::vector<PartType> result(resultSize);
        PartType carry = 0;
        for (size_t i = 0; i < resultSize; i++) {
            auto newPart = (i < parts.size() ? parts[i] : 0)
                + (i < right.parts.size() ? right.parts[i] : 0) + carry;
            if (newPart < maxPartPlusOne) {
                carry = 0;
            }
            else {
                carry = 1;
                newPart -= maxPartPlusOne;
            }
            result[i] = newPart;
        }
        if (carry > 0) {
            result.resize(resultSize + 1);
            result[resultSize] = carry;
        }
        return UnlimitedUnsigned(std::move(result));
    }
    UnlimitedUnsigned operator+(SmallDeltaType right) const
    {
        size_t resultSize = parts.size();
        std::vector<PartType> result(resultSize);
        PartType carry = right;
        for (size_t i = 0; i < resultSize; i++) {
            auto newPart = parts[i] + carry;
            if (newPart < maxPartPlusOne) {
                carry = 0;
            }
            else {
                carry = 1;
                newPart -= maxPartPlusOne;
            }
            result[i] = newPart;
        }
        if (carry > 0) {
            result.resize(resultSize + 1);
            result[resultSize] = carry;
        }
        return UnlimitedUnsigned(std::move(result));
    }
    UnlimitedUnsigned operator-(const UnlimitedUnsigned& right) const
    {
        size_t resultSize = std::max(parts.size(), right.parts.size());
        std::vector<PartType> result(resultSize);
        PartType borrow = 0;
        size_t lastNonZero = 0;
        for (size_t i = 0; i < resultSize; i++) {
            auto newPart = (i < parts.size() ? parts[i] : 0)
                - (i < right.parts.size() ? right.parts[i] : 0) - borrow;
            if (newPart >= 0) {
                borrow = 0;
            }
            else {
                borrow = 1;
                newPart += maxPartPlusOne;
            }
            result[i] = newPart;
            if (newPart != 0) {
                lastNonZero = i + 1;
            }
        }
        if (borrow > 0) {
            throw std::overflow_error("UnlimitedUnsigned arithmetic produced a negative result");
        }
        result.resize(lastNonZero);
        return UnlimitedUnsigned(std::move(result));
    }
    UnlimitedUnsigned operator-(SmallDeltaType right) const
    {
        size_t resultSize = parts.size();
        std::vector<PartType> result(resultSize);
        PartType borrow = right;
        for (size_t i = 0; i < resultSize; i++) {
            auto newPart = parts[i] - borrow;
            if (newPart >= 0) {
                borrow = 0;
            }
            else {
                borrow = 1;
                newPart += maxPartPlusOne;
            }
            result[i] = newPart;
        }
        if (borrow > 0) {
            throw std::overflow_error("UnlimitedUnsigned arithmetic produced a negative result");
        }
        // Note that by here resultSize > 0, otherwise the original this == 0 and we would have
        // the above exception.
        if (result[resultSize - 1] == 0) {
            result.resize(resultSize - 1);
        }
        return UnlimitedUnsigned(std::move(result));
    }
    bool operator<=(const UnlimitedUnsigned& right) const
    {
        // lexicographical_compare is logically a < operation, so we reverse the operands and
        // invert the result to get <= and we compare the most significant chunks first.
        return parts.size() < right.parts.size()
            || (parts.size() == right.parts.size()
                && !std::lexicographical_compare(
                    right.parts.rbegin(),
                    right.parts.rend(),
                    parts.rbegin(),
                    parts.rend()
                ));
    }
    bool operator>(const UnlimitedUnsigned& right) const
    {
        return !(*this <= right);
    }
    bool operator==(const UnlimitedUnsigned& right) const
    {
        return parts == right.parts;
    }
    bool operator==(SmallDeltaType right) const
    {
        return right == 0 ? parts.empty() : (parts.size() == 1 && parts[0] == right);
    }

private:
    std::vector<PartType> parts;
};
}  // namespace Base

#endif  // SRC_BASE_UNLIMITEDUNSIGNED_H_
