// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2020
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 4.0.2019.09.25

#pragma once

#include <algorithm>
#include <cstdint>
#include <limits>

// Support for determining the number of bits of precision required to compute
// an expression using BSNumber or BSRational.

namespace WwiseGTE
{
    class BSPrecision
    {
    public:
        enum Type
        {
            IS_FLOAT,
            IS_DOUBLE,
            IS_INT32,
            IS_INT64,
            IS_UINT32,
            IS_UINT64
        };

        struct Parameters
        {
            Parameters()
                :
                minExponent(0),
                maxExponent(0),
                maxBits(0),
                maxWords(0)
            {
            }

            Parameters(int inMinExponent, int inMaxExponent, int inMaxBits)
                :
                minExponent(inMinExponent),
                maxExponent(inMaxExponent),
                maxBits(inMaxBits),
                maxWords(GetMaxWords())
            {
            }

            inline int GetMaxWords() const
            {
                return maxBits / 32 + ((maxBits % 32) > 0 ? 1 : 0);
            }

            int minExponent, maxExponent, maxBits, maxWords;
        };

        Parameters bsn, bsr;

        BSPrecision() = default;

        BSPrecision(Type type)
        {
            switch (type)
            {
            case IS_FLOAT:
                bsn = Parameters(-149, 127, 24);
                break;
            case IS_DOUBLE:
                bsn = Parameters(-1074, 1023, 53);
                break;
            case IS_INT32:
                bsn = Parameters(0, 30, 31);
                break;
            case IS_INT64:
                bsn = Parameters(0, 62, 63);
                break;
            case IS_UINT32:
                bsn = Parameters(0, 31, 32);
                break;
            case IS_UINT64:
                bsn = Parameters(0, 63, 64);
                break;
            }
            bsr = bsn;
        }

        BSPrecision(int minExponent, int maxExponent, int maxBits)
            :
            bsn(minExponent, maxExponent, maxBits),
            bsr(minExponent, maxExponent, maxBits)
        {
        }
    };

    inline BSPrecision operator+(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        BSPrecision result;

        result.bsn.minExponent = std::min(bsp0.bsn.minExponent, bsp1.bsn.minExponent);
        if (bsp0.bsn.maxExponent >= bsp1.bsn.maxExponent)
        {
            result.bsn.maxExponent = bsp0.bsn.maxExponent;
            if (bsp0.bsn.maxExponent - bsp0.bsn.maxBits + 1 <= bsp1.bsn.maxExponent)
            {
                ++result.bsn.maxExponent;
            }

            result.bsn.maxBits = bsp0.bsn.maxExponent - bsp1.bsn.minExponent + 1;
            if (result.bsn.maxBits <= bsp0.bsn.maxBits + bsp1.bsn.maxBits - 1)
            {
                ++result.bsn.maxBits;
            }
        }
        else
        {
            result.bsn.maxExponent = bsp1.bsn.maxExponent;
            if (bsp1.bsn.maxExponent - bsp1.bsn.maxBits + 1 <= bsp0.bsn.maxExponent)
            {
                ++result.bsn.maxExponent;
            }

            result.bsn.maxBits = bsp1.bsn.maxExponent - bsp0.bsn.minExponent + 1;
            if (result.bsn.maxBits <= bsp0.bsn.maxBits + bsp1.bsn.maxBits - 1)
            {
                ++result.bsn.maxBits;
            }
        }
        result.bsn.maxWords = result.bsn.GetMaxWords();

        // Addition is n0/d0 + n1/d1 = (n0*d1 + n1*d0)/(d0*d1). The numerator
        // and denominator of a number are assumed to have the same
        // parameters, so for the addition, the numerator is used for the
        // parameter computations.

        // Compute the parameters for the multiplication.
        int mulMinExponent = bsp0.bsr.minExponent + bsp1.bsr.minExponent;
        int mulMaxExponent = bsp0.bsr.maxExponent + bsp1.bsr.maxExponent + 1;
        int mulMaxBits = bsp0.bsr.maxBits + bsp1.bsr.maxBits;

        // Compute the parameters for the addition. The number n0*d1 and n1*d0
        // are in the same arbitrary-precision set.
        result.bsr.minExponent = mulMinExponent;
        result.bsr.maxExponent = mulMaxExponent + 1; // Always a carry-out.
        result.bsr.maxBits = mulMaxExponent - mulMinExponent + 1;
        if (result.bsr.maxBits <= 2 * mulMaxBits - 1)
        {
            ++result.bsr.maxBits;
        }
        result.bsr.maxWords = result.bsr.GetMaxWords();

        return result;
    }

    inline BSPrecision operator-(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        return bsp0 + bsp1;
    }

    inline BSPrecision operator*(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        BSPrecision result;

        result.bsn.minExponent = bsp0.bsn.minExponent + bsp1.bsn.minExponent;
        result.bsn.maxExponent = bsp0.bsn.maxExponent + bsp1.bsn.maxExponent + 1;
        result.bsn.maxBits = bsp0.bsn.maxBits + bsp1.bsn.maxBits;
        result.bsn.maxWords = result.bsn.GetMaxWords();

        // Multiplication is (n0/d0) * (n1/d1) = (n0 * n1) / (d0 * d1). The
        // parameters are the same as for bsn.
        result.bsr = result.bsn;

        return result;
    }

    inline BSPrecision operator/(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        BSPrecision result;

        // BSNumber does not support division, so result.bsr has all members
        // set to zero.

        // Division is (n0/d0) / (n1/d1) = (n0 * d1) / (n1 * d0). The
        // parameters are the same as for multiplication.
        result.bsr.minExponent = bsp0.bsr.minExponent + bsp1.bsr.minExponent;
        result.bsr.maxExponent = bsp0.bsr.maxExponent + bsp1.bsr.maxExponent + 1;
        result.bsr.maxBits = bsp0.bsr.maxBits + bsp1.bsr.maxBits;
        result.bsr.maxWords = result.bsr.GetMaxWords();

        return result;
    }

    // Comparisons for BSNumber do not involve dynamic allocations, so
    // the results are the extremes of the inputs. Comparisons for BSRational
    // involve multiplications of numerators and denominators.
    inline BSPrecision operator==(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        BSPrecision result;

        result.bsn.minExponent = std::min(bsp0.bsn.minExponent, bsp1.bsn.minExponent);
        result.bsn.maxExponent = std::max(bsp0.bsn.maxExponent, bsp1.bsn.maxExponent);
        result.bsn.maxBits = std::max(bsp0.bsn.maxBits, bsp1.bsn.maxBits);
        result.bsn.maxWords = result.bsn.GetMaxWords();

        result.bsr.minExponent = bsp0.bsr.minExponent + bsp1.bsr.minExponent;
        result.bsr.maxExponent = bsp0.bsr.maxExponent + bsp1.bsr.maxExponent + 1;
        result.bsr.maxBits = bsp0.bsr.maxBits + bsp1.bsr.maxBits;
        result.bsr.maxWords = result.bsr.GetMaxWords();

        return result;
    }

    inline BSPrecision operator!=(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        return operator==(bsp0, bsp1);
    }

    inline BSPrecision operator<(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        return operator==(bsp0, bsp1);
    }

    inline BSPrecision operator<=(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        return operator==(bsp0, bsp1);
    }

    inline BSPrecision operator>(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        return operator==(bsp0, bsp1);
    }

    inline BSPrecision operator>=(BSPrecision const& bsp0, BSPrecision const& bsp1)
    {
        return operator==(bsp0, bsp1);
    }
}