#ifndef PORYGONLANG_BOUNDEXPRESSION_HPP
#define PORYGONLANG_BOUNDEXPRESSION_HPP

#include <string>
#include <memory>
#include <utility>
#include "../../ScriptTypes/ScriptType.hpp"
#include "../BoundOperators.hpp"
#include "../BoundVariables/BoundVariableKey.hpp"
#include "../../ScriptTypes/FunctionScriptType.hpp"

using namespace std;

namespace Porygon::Binder {
    enum class BoundExpressionKind : uint8_t {
        Bad,

        LiteralInteger,
        LiteralFloat,
        LiteralString,
        LiteralBool,
        Variable,

        Unary,
        Binary,
        FunctionCall,
        Index,
        PeriodIndex,
        NumericalTable,
        Table,
        Require,
    };

    class BoundExpression {
        const unsigned int _start;
        const unsigned int _length;
        const shared_ptr<const ScriptType> _type;
    public:
        BoundExpression(unsigned int start, unsigned int length, shared_ptr<const ScriptType> type)
                : _start(start),
                  _length(length),
                  _type(std::move(type)) {
        }

        virtual ~BoundExpression() = default;

        [[nodiscard]]
        virtual BoundExpressionKind GetKind() const = 0;

        [[nodiscard]]
        virtual const std::shared_ptr<const ScriptType> &GetType() const {
            return _type;
        };

        [[nodiscard]]
        inline unsigned int GetStartPosition() const {
            return _start;
        }

        [[nodiscard]]
        inline unsigned int GetLength() const {
            return _length;
        }
    };

    class BoundBadExpression : public BoundExpression {
    public:
        BoundBadExpression(unsigned int start, unsigned int length) : BoundExpression(start, length,
                                                                                      make_shared<ScriptType>(
                                                                                              TypeClass::Error)) {}

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::Bad;
        }
    };

    class BoundLiteralIntegerExpression : public BoundExpression {
        const long _value;
    public:
        BoundLiteralIntegerExpression(long value, unsigned int start, unsigned int length)
                : BoundExpression(start, length, make_shared<NumericScriptType>(true, false)),
                  _value(value) {
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::LiteralInteger;
        }

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

    class BoundLiteralFloatExpression : public BoundExpression {
        const double _value;
    public:
        BoundLiteralFloatExpression(double value, unsigned int start, unsigned int length)
                : BoundExpression(start, length, make_shared<NumericScriptType>(true, true)),
                  _value(value) {
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::LiteralFloat;
        }

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

    class BoundLiteralStringExpression : public BoundExpression {
        const u16string _value;
    public:
        BoundLiteralStringExpression(const u16string &value, unsigned int start, unsigned int length)
                : BoundExpression(start, length,
                                  make_shared<StringScriptType>(true, Utilities::HashedString::ConstHash(value.c_str()))),
                  _value(value) {
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::LiteralString;
        }

        [[nodiscard]]
        inline const u16string* GetValue() const {
            return &_value;
        }
    };

    class BoundLiteralBoolExpression : public BoundExpression {
        const bool _value;
    public:
        BoundLiteralBoolExpression(bool value, unsigned int start, unsigned int length)
                : BoundExpression(start, length, make_shared<ScriptType>(TypeClass::Bool)),
                  _value(value) {
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::LiteralBool;
        }

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

    class BoundVariableExpression : public BoundExpression {
        const BoundVariableKey *_key;
    public:
        BoundVariableExpression(BoundVariableKey *key, const shared_ptr<const ScriptType>& type, unsigned int start,
                                unsigned int length)
                : BoundExpression(start, length, type),
                  _key(key) {
        }

        ~BoundVariableExpression() override {
            delete _key;
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::Variable;
        }

        [[nodiscard]]
        inline const BoundVariableKey *GetKey() const {
            return _key;
        }
    };

    class BoundBinaryExpression : public BoundExpression {
        const BoundExpression *_left;
        const BoundExpression *_right;
        const BoundBinaryOperation _operation;
    public:
        BoundBinaryExpression(BoundExpression *left, BoundExpression *right, BoundBinaryOperation op,
                              shared_ptr<ScriptType> result,
                              unsigned int start, unsigned int length)
                : BoundExpression(start, length, std::move(result)),
                  _left(left),
                  _right(right),
                  _operation(op) {
        }

        ~BoundBinaryExpression() final {
            delete _left;
            delete _right;
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::Binary;
        }

        [[nodiscard]]
        inline const BoundExpression *GetLeft() const {
            return _left;
        }

        [[nodiscard]]
        inline const BoundExpression *GetRight() const {
            return _right;
        }

        [[nodiscard]]
        inline BoundBinaryOperation GetOperation() const {
            return _operation;
        }
    };

    class BoundUnaryExpression : public BoundExpression {
        const BoundExpression *_operand;
        const BoundUnaryOperation _operation;
    public:
        BoundUnaryExpression(BoundExpression *operand, BoundUnaryOperation op, shared_ptr<ScriptType> result,
                             unsigned int start, unsigned int length)
                : BoundExpression(start, length, std::move(result)),
                  _operand(operand),
                  _operation(op) {
        }

        ~BoundUnaryExpression() final {
            delete _operand;
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::Unary;
        }

        [[nodiscard]]
        inline const BoundExpression *GetOperand() const {
            return _operand;
        }

        [[nodiscard]]
        inline BoundUnaryOperation GetOperation() const {
            return _operation;
        }
    };

    class BoundIndexExpression : public BoundExpression {
        const BoundExpression *_indexableExpression;
        const BoundExpression *_indexExpression;
    public:
        BoundIndexExpression(BoundExpression *indexableExpression, BoundExpression *indexExpression,
                             shared_ptr<const ScriptType> result,
                             unsigned int start, unsigned int length)
                : BoundExpression(start, length, std::move(result)), _indexableExpression(indexableExpression),
                  _indexExpression(indexExpression) {}

        ~BoundIndexExpression() final {
            delete _indexableExpression;
            delete _indexExpression;
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::Index;
        }

        [[nodiscard]]
        inline const BoundExpression *GetIndexableExpression() const {
            return _indexableExpression;
        }

        [[nodiscard]]
        inline const BoundExpression *GetIndexExpression() const {
            return _indexExpression;
        }
    };

    class BoundPeriodIndexExpression : public BoundExpression {
        const BoundExpression *_indexableExpression;
        const Utilities::HashedString _index;
    public:
        BoundPeriodIndexExpression(BoundExpression *indexableExpression, const Utilities::HashedString& index,
                                   shared_ptr<const ScriptType> result,
                                   unsigned int start, unsigned int length)
                : BoundExpression(start, length, std::move(result)), _indexableExpression(indexableExpression),
                  _index(index) {}

        ~BoundPeriodIndexExpression() final {
            delete _indexableExpression;
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::PeriodIndex;
        }

        [[nodiscard]]
        inline const BoundExpression *GetIndexableExpression() const {
            return _indexableExpression;
        }

        [[nodiscard]]
        inline const Utilities::HashedString* GetIndex() const {
            return &_index;
        }
    };

    class BoundNumericalTableExpression : public BoundExpression {
        const vector<const BoundExpression *> _expressions;
    public:
        BoundNumericalTableExpression(vector<const BoundExpression *> expressions, shared_ptr<const ScriptType> type,
                                      unsigned int start, unsigned int length)
                : BoundExpression(start, length, std::move(type)),
                  _expressions(std::move(expressions)) {}

        ~BoundNumericalTableExpression() final {
            for (auto e: _expressions) {
                delete e;
            }
        }

        [[nodiscard]]
        inline BoundExpressionKind GetKind() const final {
            return BoundExpressionKind::NumericalTable;
        }

        [[nodiscard]]
        inline const vector<const BoundExpression *> *GetExpressions() const {
            return &_expressions;
        }
    };

}

#endif //PORYGONLANG_BOUNDEXPRESSION_HPP
