
#ifndef PORYGONLANG_EVALVALUE_HPP
#define PORYGONLANG_EVALVALUE_HPP

#include <string>
#include <sstream>
#include <memory>
#include "../../ScriptTypes/ScriptType.hpp"
#include "../EvaluationException.hpp"
namespace Porygon::Evaluation{
    class EvalValue;
    class Iterator;
}
#include "../Iterator/Iterator.hpp"
#include "../../Binder/BoundOperators.hpp"


namespace Porygon::Evaluation {
    class EvalValue {
    public:
        EvalValue() = default;

        virtual ~EvalValue() = default;

        [[nodiscard]]
        virtual TypeClass GetTypeClass() const = 0;

        [[nodiscard]]
        virtual bool operator==(const EvalValue *b) const = 0;

        [[nodiscard]]
        virtual bool operator!=(const EvalValue *b) const {
            return !(this->operator==(b));
        }

        [[nodiscard]]
        virtual EvalValue* Clone() const = 0;

        [[nodiscard]]
        virtual long EvaluateInteger() const {
            throw EvaluationException("Can't evaluate this EvalValue as integer.");
        }

        [[nodiscard]]
        virtual double EvaluateFloat() const {
            throw EvaluationException("Can't evaluate this EvalValue as float.");
        }

        [[nodiscard]]
        virtual bool EvaluateBool() const {
            throw EvaluationException("Can't evaluate this EvalValue as bool.");
        }

        [[nodiscard]]
        virtual std::u16string EvaluateString() const {
            throw EvaluationException("Can't evaluate this EvalValue as string.");
        }

        [[nodiscard]]
        virtual std::size_t GetHashCode() const = 0;

        [[nodiscard]]
        virtual const EvalValue* IndexValue(const EvalValue *val) const {
            throw EvaluationException("Can't index this EvalValue");
        }

        [[nodiscard]]
        virtual const EvalValue* IndexValue(uint32_t hash) const {
            throw EvaluationException("Can't index this EvalValue");
        }

        virtual void SetIndexValue(const EvalValue *key, const EvalValue* value) const {
            throw EvaluationException("Can't index this EvalValue");
        }

        [[nodiscard]]
        virtual Iterator * GetKeyIterator() const{
            throw EvaluationException("Can't iterate over this EvalValue");
        }

        [[nodiscard]]
        virtual EvalValue* BinaryOperation(Binder::BoundBinaryOperation operation, const EvalValue* b) const{
            throw EvaluationException("Binary operations are not implemented for this type.");
        }

        [[nodiscard]]
        virtual EvalValue* UnaryOperation(Binder::BoundUnaryOperation operation) const{
            throw EvaluationException("Unary operations are not implemented for this type.");
        }
    };

    class BooleanEvalValue : public EvalValue {
        const bool _value;
    public:
        explicit BooleanEvalValue(bool val)
                : _value(val) {
        }

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

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

        [[nodiscard]]
        inline bool EvaluateBool() const final {
            return _value;
        }

        [[nodiscard]]
        bool operator==(const EvalValue *b) const final {
            if (b->GetTypeClass() != TypeClass::Bool)
                return false;
            return this->EvaluateBool() == b->EvaluateBool();
        };

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

        [[nodiscard]]
        inline EvalValue* BinaryOperation(Binder::BoundBinaryOperation operation, const EvalValue* b) const final{
            auto bVal = b -> EvaluateBool();
            switch (operation){
                case Binder::BoundBinaryOperation::LogicalAnd: return new BooleanEvalValue(_value && bVal);
                case Binder::BoundBinaryOperation::LogicalOr: return new BooleanEvalValue(_value || bVal);
                default:
                    throw;
            }
        }

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

#endif //PORYGONLANG_EVALVALUE_HPP
