
#ifndef __FIXED_H_
#define __FIXED_H_

#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <type_traits>

#if __GNUC__ >= 3
#define __unlikely(cond) __builtin_expect((cond), 0)
#define __likely(cond)   __builtin_expect((cond), 1)
#else
#define __unlikely(cond) (cond)
#define __likely(cond)   (cond)
#endif

template<typename IntegerType, typename FractionalType>
class fixed 
{
    static_assert(std::is_integral<IntegerType>::value,
                  "IntegerType must be an integral type");
    static_assert(std::is_integral<FractionalType>::value,
                  "FractionalType must be an integral type");

    static_assert(std::is_signed<IntegerType>::value,
                  "IntegerType must be a signed type");
    static_assert(std::is_unsigned<FractionalType>::value,
                  "FractionalType must be an unsigned type");

public:

    static constexpr size_t integer_bits = sizeof(IntegerType) * 8;
    static constexpr size_t fractional_bits = sizeof(FractionalType) * 8;

    static constexpr size_t integer_decimal_digits =
        std::floor(std::log10(std::numeric_limits<IntegerType>::max()));
    static constexpr size_t fractional_decimal_digits =
        std::floor(std::log10(std::numeric_limits<FractionalType>::max()));

    static constexpr size_t MAX_INTEGER_VALUE =
        (static_cast<uint64_t>(std::pow(10, integer_decimal_digits)) - 1);
    static constexpr size_t MIN_INTEGER_VALUE = 0;

    static constexpr size_t MAX_FRACTIONAL_VALUE =
        (static_cast<uint64_t>(std::pow(10, fractional_decimal_digits)) - 1);
    static constexpr size_t MIN_FRACTIONAL_VALUE = 0;

    static const uint64_t SCALE_VALUES[20];

    // Constructors
    fixed()
        : m_integer(0),
          m_fractional(0)
    {}

    fixed(IntegerType integerVal)
        : m_integer(__checkIntOverflow(integerVal)),
          m_fractional(0)
    {}

    fixed(IntegerType integerVal, FractionalType fractionalVal)
        : m_integer(__checkIntOverflow(integerVal)),
          m_fractional(__checkFracOverflow(fractionalVal))
    {
        // Scale the fractional value appropriately
        uint32_t idx = 0;
        for (; idx < fractional_decimal_digits; ++idx)
        {
            if (SCALE_VALUES[idx] > m_fractional)
            {
                break;
            }
        }

        m_fractional = __checkFracOverflow(
            m_fractional * SCALE_VALUES[fractional_decimal_digits - idx]);
    }

    // Default copy constructor, and assignment operator
    fixed(const fixed&) = default;
    fixed& operator=(const fixed&) = default;

    // Reassign value to type
    void assign(IntegerType integerVal, FractionalType fractionalVal)
    {
        m_integer = __checkIntOverflow(integerVal);
        // Scale the fractional value appropriately
        uint32_t idx = 0;
        for (; idx < fractional_decimal_digits; ++idx)
        {
            if (SCALE_VALUES[idx] > fractionalVal)
            {
                break;
            }
        }

        m_fractional = __checkFracOverflow(
            fractionalVal * SCALE_VALUES[fractional_decimal_digits - idx]);
    }

    // Parse value from string input
    uint32_t parse(const char* input)
    {
        char* endPtr = nullptr;
        m_integer = __checkIntOverflow(strtoll(input, &endPtr, 10));
        m_fractional = 0;

        if (std::isdigit(*endPtr))
        {
            throw std::out_of_range("Integer value is out of range.");
        }

        // If the ending char is a period we can now parse the fractional part
        if (*endPtr == '.')
        {
            char* fracEndPtr = nullptr;
            FractionalType fracTemp =
                __checkFracOverflow(strtoull(endPtr + 1, &fracEndPtr, 10));
            uint32_t fracLen = (fracEndPtr - endPtr) - 1;

            fracLen = fractional_decimal_digits - fracLen;

            m_fractional = __checkFracOverflow(fracTemp * SCALE_VALUES[fracLen]);
            endPtr = fracEndPtr;
        }
        
        // Calculate and return overall length
        return (endPtr - input);
    }

    constexpr inline fixed operator-() const noexcept = delete;
    constexpr inline fixed operator!() const noexcept = delete;
    constexpr inline fixed operator~() const noexcept = delete;
    inline fixed& operator+=(const fixed& y) noexcept = delete;
    inline fixed& operator-=(const fixed& y) noexcept = delete;
    inline fixed& operator*=(const fixed& y) noexcept = delete;
    inline fixed& operator/=(const fixed& y) noexcept = delete;

    // Comparison operators
    template<typename I, typename F>
    friend constexpr inline bool operator==(
        const fixed<I, F>& x, const fixed<I, F>& y) noexcept;

    template<typename I, typename F>
    friend constexpr inline bool operator!=(
        const fixed<I, F>& x, const fixed<I, F>& y) noexcept;

    template<typename I, typename F>
    friend constexpr inline bool operator<(
        const fixed<I, F>& x, const fixed<I, F>& y) noexcept;

    template<typename I, typename F>
    friend constexpr inline bool operator>(
        const fixed<I, F>& x, const fixed<I, F>& y) noexcept;

    template<typename I, typename F>
    friend constexpr inline bool operator<=(
        const fixed<I, F>& x, const fixed<I, F>& y) noexcept;

    template<typename I, typename F>
    friend constexpr inline bool operator>=(
        const fixed<I, F>& x, const fixed<I, F>& y) noexcept;

    // Output stream operator
    template<typename I, typename F>
    friend inline std::ostream& operator<<(
        std::ostream& os, const fixed<I, F>& rhs);

private:

    static inline IntegerType __checkIntOverflow(IntegerType integerVal)
    {
        if (__unlikely(integerVal > MAX_INTEGER_VALUE))
        {
            std::ostringstream msg;
            msg << "Integer value: " << integerVal << " exceeds maximum "
                << "integer range of type (" << MAX_INTEGER_VALUE << ")!";
            throw std::out_of_range(msg.str());
        }
        return integerVal;
    }

    static inline FractionalType __checkFracOverflow(FractionalType fractionalVal)
    {
        if (__unlikely(fractionalVal > MAX_FRACTIONAL_VALUE))
        {
            std::ostringstream msg;
            msg << "Fractional value: " << fractionalVal << " exceeds maximum "
                << "fractional range of type (" << MAX_FRACTIONAL_VALUE << ")!";
            throw std::out_of_range(msg.str());
        }
        return fractionalVal;
    }

    IntegerType m_integer;
    FractionalType m_fractional;
};

// Predefined types
using fixed_8_8  = fixed<int8_t, uint8_t>;
using fixed_8_16 = fixed<int8_t, uint16_t>;
using fixed_8_32 = fixed<int8_t, uint32_t>;
using fixed_8_64 = fixed<int8_t, uint64_t>;

using fixed_16_8  = fixed<int16_t, uint8_t>;
using fixed_16_16 = fixed<int16_t, uint16_t>;
using fixed_16_32 = fixed<int16_t, uint32_t>;
using fixed_16_64 = fixed<int16_t, uint64_t>;

using fixed_32_8  = fixed<int32_t, uint8_t>;
using fixed_32_16 = fixed<int32_t, uint16_t>;
using fixed_32_32 = fixed<int32_t, uint32_t>;
using fixed_32_64 = fixed<int32_t, uint64_t>;

using fixed_64_8  = fixed<int64_t, uint8_t>;
using fixed_64_16 = fixed<int64_t, uint16_t>;
using fixed_64_32 = fixed<int64_t, uint32_t>;
using fixed_64_64 = fixed<int64_t, uint64_t>;

// Precomputed scale value constants
template<typename I, typename F>
const uint64_t fixed<I, F>::SCALE_VALUES[20] = 
{
    /*  0 */ 1ULL,
    /*  1 */ 10ULL,
    /*  2 */ 100ULL,
    /*  3 */ 1000ULL,
    /*  4 */ 10000ULL,
    /*  5 */ 100000ULL,
    /*  6 */ 1000000ULL,
    /*  7 */ 10000000ULL,
    /*  8 */ 100000000ULL,
    /*  9 */ 1000000000ULL,
    /* 10 */ 10000000000ULL,
    /* 11 */ 100000000000ULL,
    /* 12 */ 1000000000000ULL,
    /* 13 */ 10000000000000ULL,
    /* 14 */ 100000000000000ULL,
    /* 15 */ 1000000000000000ULL,
    /* 16 */ 10000000000000000ULL,
    /* 17 */ 100000000000000000ULL,
    /* 18 */ 1000000000000000000ULL,
    /* 19 */ 10000000000000000000ULL
};

template<typename I, typename F>
constexpr inline bool operator==(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept
{
    return (x.m_integer == y.m_integer && x.m_fractional == y.m_fractional);
}

template<typename I, typename F>
constexpr inline bool operator!=(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept
{
    return (! (x == y));
}

template<typename I, typename F>
constexpr inline bool operator<(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept
{
    if (x.m_integer == y.m_integer)
    {
        return (x.m_fractional < y.m_fractional);
    }
    else
    {
        return (x.m_integer < y.m_integer);
    }

    return false;
}

template<typename I, typename F>
constexpr inline bool operator>(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept
{
    if (x.m_integer == y.m_integer)
    {
        return (x.m_fractional > y.m_fractional);
    }
    else
    {
        return (x.m_integer > y.m_integer);
    }

    return false;
}

template<typename I, typename F>
constexpr inline bool operator<=(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept
{
    if (x.m_integer == y.m_integer)
    {
        return (x.m_fractional <= y.m_fractional);
    }
    else
    {
        return (x.m_integer < y.m_integer);
    }

    return false;
}

template<typename I, typename F>
constexpr inline bool operator>=(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept
{
    if (x.m_integer == y.m_integer)
    {
        return (x.m_fractional >= y.m_fractional);
    }
    else
    {
        return (x.m_integer > y.m_integer);
    }

    return false;
}

// Addition -- not yet supported
template<typename I, typename F>
constexpr inline fixed<I, F> operator+(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;

// Subtraction -- not yet supported
template<typename I, typename F>
constexpr inline fixed<I, F> operator-(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;

// Multiplication
template<typename I, typename F>
constexpr inline fixed<I, F> operator*(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;

// Division -- not yet supported
template<typename I, typename F>
constexpr inline fixed<I, F> operator/(
    const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;

// Stream output
template<typename I, typename F>
inline std::ostream& operator<<(std::ostream& os, const fixed<I, F>& rhs)
{
    return os << std::fixed << rhs.m_integer << "."
              << std::setw(fixed<I, F>::fractional_decimal_digits)
              << std::setfill('0') << rhs.m_fractional;
}

#endif  // FIXED_H_
