/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef ECXX_CTL_MATH_UTILITY_H
#define ECXX_CTL_MATH_UTILITY_H


#include <eCxx/CTL/Foundation.h>


namespace eCxx {


//
// makeHalfSizeType<>
//
template <typename ValueT> struct makeHalfSizeType           {};
template <>                struct makeHalfSizeType< int64_t> { typedef  int32_t Type; };
template <>                struct makeHalfSizeType<uint64_t> { typedef uint32_t Type; };
template <>                struct makeHalfSizeType< int32_t> { typedef  int16_t Type; };
template <>                struct makeHalfSizeType<uint32_t> { typedef uint16_t Type; };
template <>                struct makeHalfSizeType< int16_t> { typedef   int8_t Type; };
template <>                struct makeHalfSizeType<uint16_t> { typedef  uint8_t Type; };


//
// maxIntegerDigits<>
//
template <typename ValueT>
struct maxIntegerDigits;

template <> struct maxIntegerDigits<Bool> {
    static constexpr uint8_t Bin = 1;
    static constexpr uint8_t Oct = 1;
    static constexpr uint8_t Dec = 1;
    static constexpr uint8_t Hex = 1;
};

template <> struct maxIntegerDigits<bool> {
    static constexpr uint8_t Bin = 1;
    static constexpr uint8_t Oct = 1;
    static constexpr uint8_t Dec = 1;
    static constexpr uint8_t Hex = 1;
};

template <> struct maxIntegerDigits<char> {
    static constexpr uint8_t Bin = 1 + 2 + 8; // sign ; prefix 0b ; digits
    static constexpr uint8_t Oct = 1 + 1 + 3; // sign ; prefix 0  ; digits
    static constexpr uint8_t Dec = 1 +     3; // sign ;             digits
    static constexpr uint8_t Hex = 1 + 2 + 2; // sign ; prefix 0x ; digits
};
template <> struct maxIntegerDigits< int8_t> : maxIntegerDigits<char> {};
template <> struct maxIntegerDigits<uint8_t> : maxIntegerDigits<char> {};

template <> struct maxIntegerDigits<int16_t> {
    static constexpr uint8_t Bin = 1 + 2 + 16; // sign ; prefix 0b ; digits
    static constexpr uint8_t Oct = 1 + 1 +  6; // sign ; prefix 0  ; digits
    static constexpr uint8_t Dec = 1 +      6; // sign ;             digits with thousands separator(s)
    static constexpr uint8_t Hex = 1 + 2 +  4; // sign ; prefix 0x ; digits
};
template <> struct maxIntegerDigits<uint16_t> : maxIntegerDigits<int16_t> {};

template <> struct maxIntegerDigits<int32_t> {
    static constexpr uint8_t Bin = 1 + 2 + 32; // sign ; prefix 0b ; digits
    static constexpr uint8_t Oct = 1 + 1 + 11; // sign ; prefix 0  ; digits
    static constexpr uint8_t Dec = 1 +     13; // sign ;             digits with thousands separator(s)
    static constexpr uint8_t Hex = 1 + 2 +  8; // sign ; prefix 0x ; digits
};
template <> struct maxIntegerDigits<uint32_t> : maxIntegerDigits<int32_t> {};

template <> struct maxIntegerDigits<int64_t> {
    static constexpr uint8_t Bin = 1 + 2 + 64; // sign ; prefix 0b ; digits
    static constexpr uint8_t Oct = 1 + 1 + 22; // sign ; prefix 0  ; digits
    static constexpr uint8_t Dec = 1 +     26; // sign ;             digits with thousands separator(s)
    static constexpr uint8_t Hex = 1 + 2 + 16; // sign ; prefix 0x ; digits
};
template <> struct maxIntegerDigits<uint64_t> : maxIntegerDigits<int64_t> {};


//
// signExtend<>()
//
template <typename ValueT, size_t SrcBitNum>
inline ValueT signExtend(ValueT val) noexcept
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");
    static_assert(eCxx::isSigned<ValueT>::Value, "template argument substituting ValueT is not a signed integral");

    struct { ValueT val : SrcBitNum; } s;
    return s.val = val;
}


template <typename ValueT>
inline ValueT signExtend(ValueT val, size_t srcBitNum) noexcept
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");
    static_assert(eCxx::isSigned<ValueT>::Value, "template argument substituting ValueT is not a signed integral");

    const ValueT mask = 1U << (srcBitNum - 1);

    val = val & ( (1U << srcBitNum) - 1 );
    return (val ^ mask) - mask;
}


//
// isNegative<>()
//
template <typename ValueT>
constexpr bool isNegative(ValueT val) noexcept
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");

    return static_cast<bool>(
        1 ^ ( static_cast<typename makeUnsigned<ValueT>::Type>(val) >> ( sizeof(ValueT) * CHAR_BIT - 1 ) )
    );
}


//
// isPowerOfTwo<>()
//
template <typename ValueT>
constexpr bool isPowerOfTwo(ValueT val) noexcept
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");

    return val && !( val & (val - 1) );
}


//
// abs<>()
//
template <typename ValueT>
constexpr ValueT abs(ValueT value) noexcept
{  return ( value < static_cast<ValueT>(0) ) ? -value : value; }


//
// pow()
//
constexpr size_t pow(size_t base, uint8_t exponent) noexcept
{ return ( exponent == 0 ) ? 1 : ( base * eCxx::pow( base, exponent - 1 ) ); }


//
// log2<>()
//
constexpr uint64_t log2(uint64_t n)
{ return (n < 2) ? 0 : ( 1 + eCxx::log2(n / 2) ); }


//
// log10<>()
//
constexpr uint64_t log10(uint64_t  n)
{ return (n < 10) ? 0 : ( 1 + eCxx::log10(n / 10) ); }


//
// roundToTheNextMultipleOf<>()
//
template <size_t M, typename ValueT>
constexpr ValueT roundToTheNextMultipleOf(ValueT val) noexcept
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");

    return eCxx::isPowerOfTwo(M)
           ? ( ( val + (M - 1) ) & ~(M - 1) )
           : ( ( val + (M - 1) ) / M * M    );
}


//
// lerp<>()
//
template <typename ValueT>
constexpr ValueT lerp(ValueT startValue, ValueT endValue, uint8_t stepNumber, uint8_t lastStepNumber)
{ return (endValue - startValue) * stepNumber / lastStepNumber + startValue; }


template <typename ValueT>
constexpr ValueT lerp(ValueT startValue, ValueT endValue, uint16_t stepNumber, uint16_t lastStepNumber)
{ return (endValue - startValue) * stepNumber / lastStepNumber + startValue; }


template <typename ValueT>
constexpr ValueT lerp(ValueT startValue, ValueT endValue, uint32_t stepNumber, uint32_t lastStepNumber)
{ return (endValue - startValue) * stepNumber / lastStepNumber + startValue; }


//
// mapValue<>()
//
namespace internal {

    template <typename ValueT>
    inline ValueT mapValueSim(ValueT in, ValueT inMin, ValueT inMax, ValueT outMin, ValueT outMax) noexcept
    { return (in - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; }

    template <typename ValueT>
    inline ValueT mapValueSym(ValueT in, ValueT inMin, ValueT inMax, ValueT outMin, ValueT outMax) noexcept
    {
        if(in <= inMin) return outMin;
        if(in >= inMax) return outMax;

        if(outMin < outMax) return (in - inMin) * (outMax - outMin + 1) / (inMax - inMin) + outMin;
        else                return (in - inMin) * (outMax - outMin - 1) / (inMax - inMin) + outMin;
    }

} // namespace internal

template <typename ValueT, bool Symmetrical = false>
inline ValueT mapValue(ValueT in, ValueT inMin, ValueT inMax, ValueT outMin, ValueT outMax) noexcept
{
    return Symmetrical ? internal::mapValueSym(in, inMin, inMax, outMin, outMax)
                       : internal::mapValueSim(in, inMin, inMax, outMin, outMax);
}


//
// gcd<>()
//
template <typename ValueT>
inline ValueT gcd(ValueT x, ValueT y)
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");

    if(!x) return y;

    while(y) {
        if(x > y) x = x - y;
        else      y = y - x;
    }

    return x;
}


//
// lcm<>()
//
template <typename ValueT>
inline ValueT lcm(ValueT x, ValueT y)
{
    static_assert(eCxx::isIntegral<ValueT>::Value, "template argument substituting ValueT is not an integral");

    return (x * y) / gcd(x, y);
}


} // namespace eCxx


#endif
