
#ifndef PORYGONLANG_NUMERICEVALVALUE_HPP
#define PORYGONLANG_NUMERICEVALVALUE_HPP

#include <sstream>
#include "EvalValue.hpp"
#include "../../Utilities/StringUtils.hpp"

namespace Porygon::Evaluation {
    class NumericEvalValue : public EvalValue {

        [[nodiscard]]
        virtual long GetIntegerValue() const = 0;

        [[nodiscard]]
        virtual double GetFloatValue() const = 0;

    public:
        [[nodiscard]]
        virtual bool IsFloat() const = 0;

        [[nodiscard]]
        inline TypeClass GetTypeClass() const final {
            return TypeClass::Number;
        }

        shared_ptr<const NumericEvalValue> operator+(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const NumericEvalValue> operator-(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const NumericEvalValue> operator*(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const NumericEvalValue> operator/(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const BooleanEvalValue> operator<(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const BooleanEvalValue> operator<=(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const BooleanEvalValue> operator>(const shared_ptr<const NumericEvalValue> &b) const;

        shared_ptr<const BooleanEvalValue> operator>=(const shared_ptr<const NumericEvalValue> &b) const;

        bool operator==(const EvalValue *b) const final;
    };

    class IntegerEvalValue : public NumericEvalValue {
        const long _value;

        [[nodiscard]]
        long GetIntegerValue() const final { return _value; }

        [[nodiscard]]
        double GetFloatValue() const final {
            throw EvaluationException("Attempting to retrieve float from int eval value.");
        }

    public:
        explicit IntegerEvalValue(long value) : _value(value) {
        }

        [[nodiscard]]
        inline bool IsFloat() const final {
            return false;
        }

        [[nodiscard]]
        inline long EvaluateInteger() const final {
            return _value;
        }

        [[nodiscard]]
        inline double EvaluateFloat() const final {
            return static_cast<double>(_value);
        }

        [[nodiscard]]
        inline std::u16string EvaluateString() const final{
            return Utilities::StringUtils::IntToString(_value);
        }

        [[nodiscard]]
        inline EvalValue* Clone() const final {
            return new IntegerEvalValue(_value);
        }

        [[nodiscard]]
        inline std::size_t GetHashCode() const final {
            return std::hash<long>{}(_value);
        }

        [[nodiscard]]
        EvalValue* BinaryOperation(Binder::BoundBinaryOperation operation, const EvalValue* b) const final;

        [[nodiscard]]
        EvalValue* UnaryOperation(Binder::BoundUnaryOperation operation) const final{
            switch (operation){
                case Binder::BoundUnaryOperation::Negation: return new IntegerEvalValue(-_value);
                default: throw;
            }
        }
    };

    class FloatEvalValue : public NumericEvalValue {
        const double _value;

        [[nodiscard]]
        inline long GetIntegerValue() const final {
            throw EvaluationException("Attempting to retrieve float from int eval value.");
        }

        [[nodiscard]]
        inline double GetFloatValue() const final {
            return _value;
        }

    public:
        explicit FloatEvalValue(double value) : _value(value) {
        }

        [[nodiscard]]
        inline bool IsFloat() const final {
            return true;
        }

        [[nodiscard]]
        inline double EvaluateFloat() const final {
            return _value;
        }

        [[nodiscard]]
        inline long EvaluateInteger() const final {
            return static_cast<long>(_value);
        }

        [[nodiscard]]
        inline std::u16string EvaluateString() const final{
            return Utilities::StringUtils::FloatToString(_value);
        }

        [[nodiscard]]
        inline EvalValue* Clone() const final {
            return new FloatEvalValue(_value);
        }

        [[nodiscard]]
        inline std::size_t GetHashCode() const final {
            return std::hash<double>{}(_value);
        }

        [[nodiscard]]
        inline EvalValue* BinaryOperation(Binder::BoundBinaryOperation operation, const EvalValue* b) const final{
            auto right = dynamic_cast<const NumericEvalValue*>(b);
            if (right->IsFloat()){
                auto rightVal = right->EvaluateFloat();
                switch (operation){
                    case Binder::BoundBinaryOperation::Addition: return new FloatEvalValue(_value + rightVal);
                    case Binder::BoundBinaryOperation::Subtraction: return new FloatEvalValue(_value - rightVal);
                    case Binder::BoundBinaryOperation::Multiplication: return new FloatEvalValue(_value * rightVal);
                    case Binder::BoundBinaryOperation::Division: return new FloatEvalValue(_value / rightVal);
                    case Binder::BoundBinaryOperation::LessThan: return new BooleanEvalValue(_value < rightVal);
                    case Binder::BoundBinaryOperation::LessThanEquals: return new BooleanEvalValue(_value <= rightVal);
                    case Binder::BoundBinaryOperation::GreaterThan: return new BooleanEvalValue(_value > rightVal);
                    case Binder::BoundBinaryOperation::GreaterThanEquals: return new BooleanEvalValue(_value >= rightVal);
                    default:
                        throw;
                }
            } else {
                auto rightVal = right->EvaluateInteger();
                switch (operation) {
                    case Binder::BoundBinaryOperation::Addition:
                        return new IntegerEvalValue((long) _value + rightVal);
                    case Binder::BoundBinaryOperation::Subtraction:
                        return new IntegerEvalValue((long) _value - rightVal);
                    case Binder::BoundBinaryOperation::Multiplication:
                        return new IntegerEvalValue((long) _value * rightVal);
                    case Binder::BoundBinaryOperation::Division:
                        return new IntegerEvalValue((long) _value / rightVal);
                    case Binder::BoundBinaryOperation::LessThan:
                        return new BooleanEvalValue(_value < rightVal);
                    case Binder::BoundBinaryOperation::LessThanEquals:
                        return new BooleanEvalValue(_value <= rightVal);
                    case Binder::BoundBinaryOperation::GreaterThan:
                        return new BooleanEvalValue(_value > rightVal);
                    case Binder::BoundBinaryOperation::GreaterThanEquals:
                        return new BooleanEvalValue(_value >= rightVal);
                    default:
                        throw;
                }
            }
        }

        [[nodiscard]]
        EvalValue* UnaryOperation(Binder::BoundUnaryOperation operation) const final{
            switch (operation){
                case Binder::BoundUnaryOperation::Negation: return new FloatEvalValue(-_value);
                default: throw;
            }
        }
    };
}

#endif //PORYGONLANG_NUMERICEVALVALUE_HPP
