
#ifndef PORYGONLANG_PARSEDEXPRESSION_HPP
#define PORYGONLANG_PARSEDEXPRESSION_HPP

#include <utility>
#include <memory>

#include "../Token.hpp"
#include "../UnaryOperatorKind.hpp"
#include "../BinaryOperatorKind.hpp"
#include "../../Utilities/HashedString.hpp"

namespace Porygon::Parser {
    enum class ParsedExpressionKind : uint8_t {
        Bad,

        LiteralInteger,
        LiteralFloat,
        LiteralString,
        LiteralBool,
        Variable,

        Unary,
        Binary,
        Parenthesized,
        FunctionCall,
        Indexer,
        PeriodIndexer,
        NumericalTable,
        Table,
    };

    class ParsedExpression {
        const unsigned int _position;
        const unsigned int _length;
    public:
        ParsedExpression(unsigned int position, unsigned int length)
                : _position(position),
                  _length(length) {
        }

        virtual ~ParsedExpression() = default;

        virtual const ParsedExpressionKind GetKind() const = 0;

        inline const unsigned int GetStartPosition() const {
            return _position;
        }

        inline const unsigned int GetEndPosition() const {
            return _position + _length - 1;
        }

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

    class BadExpression : public ParsedExpression {
    public:
        BadExpression(unsigned int position, unsigned int length) : ParsedExpression(position, length) {}

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::Bad;
        }
    };

    class LiteralIntegerExpression : public ParsedExpression {
        const long _value;
    public:
        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::LiteralInteger;
        }

        explicit LiteralIntegerExpression(const IntegerToken *token)
                : ParsedExpression(token->GetStartPosition(), token->GetLength()),
                  _value(token->GetValue()) {
        }

        inline const long GetValue() const {
            return _value;
        }
    };

    class LiteralFloatExpression : public ParsedExpression {
        const double _value;
    public:
        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::LiteralFloat;
        }

        explicit LiteralFloatExpression(const FloatToken *token)
                : ParsedExpression(token->GetStartPosition(), token->GetLength()),
                  _value(token->GetValue()) {
        }

        inline const double GetValue() const {
            return _value;
        }
    };

    class LiteralStringExpression : public ParsedExpression {
        const u16string _value;
    public:
        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::LiteralString;
        }

        explicit LiteralStringExpression(const StringToken *token)
                : ParsedExpression(token->GetStartPosition(), token->GetLength()),
                  _value(token->GetValue()) {
        }

        inline const u16string &GetValue() const {
            return _value;
        }
    };

    class LiteralBoolExpression : public ParsedExpression {
        const bool _value;
    public:
        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::LiteralBool;
        }

        explicit LiteralBoolExpression(const Token *token)
                : ParsedExpression(token->GetStartPosition(), token->GetLength()),
                  _value(token->GetKind() == TokenKind::TrueKeyword) {
        }

        inline const bool GetValue() const {
            return _value;
        }
    };

    class VariableExpression : public ParsedExpression {
        const HashedString _value;
    public:
        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::Variable;
        }

        explicit VariableExpression(const IdentifierToken *token) : ParsedExpression(token->GetStartPosition(),
                                                                               token->GetLength()),
                                                              _value(HashedString(token->GetValue())) {
        }

        inline const HashedString GetValue() const {
            return _value;
        }
    };


    class ParenthesizedExpression : public ParsedExpression {
        const ParsedExpression *_expression;
    public:
        ~ParenthesizedExpression() override {
            delete _expression;
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::Parenthesized;
        }

        explicit ParenthesizedExpression(ParsedExpression *innerExpression, unsigned int start, unsigned int length)
                : ParsedExpression(start, length), _expression(innerExpression) {
        }

        inline const ParsedExpression *GetInnerExpression() const {
            return _expression;
        }
    };

    class UnaryExpression : public ParsedExpression {
        const UnaryOperatorKind _kind;
        const ParsedExpression *_operand;
    public:
        ~UnaryExpression() override {
            delete _operand;
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::Unary;
        }

        UnaryExpression(UnaryOperatorKind kind, ParsedExpression *operand, unsigned int start, unsigned int length)
                : ParsedExpression(start, length),
                  _kind(kind), _operand(operand) {
        }

        inline const UnaryOperatorKind GetOperatorKind() const {
            return _kind;
        }

        inline const ParsedExpression *GetOperand() const {
            return _operand;
        }
    };

    class BinaryExpression : public ParsedExpression {
        const BinaryOperatorKind _kind;
        const ParsedExpression *_left;
        const ParsedExpression *_right;
    public:
        ~BinaryExpression() override {
            delete _left;
            delete _right;
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::Binary;
        }

        BinaryExpression(BinaryOperatorKind kind, ParsedExpression *left, ParsedExpression *right, unsigned int start,
                         unsigned int length)
                : ParsedExpression(start, length),
                  _kind(kind), _left(left), _right(right) {
        }

        inline const BinaryOperatorKind GetOperatorKind() const {
            return _kind;
        }

        inline const ParsedExpression *GetLeft() const {
            return _left;
        }

        inline const ParsedExpression *GetRight() const {
            return _right;
        }
    };

    class FunctionCallExpression : public ParsedExpression {
        const std::unique_ptr<ParsedExpression> _function;
        const vector<const ParsedExpression *> _parameters;
    public:
        FunctionCallExpression(ParsedExpression *function, vector<const ParsedExpression *> parameters,
                               unsigned int start, unsigned int length)
                : ParsedExpression(start, length),
                  _function(function), _parameters(std::move(parameters)) {
        }

        ~FunctionCallExpression() final {
            for (auto p : _parameters) {
                delete p;
            }
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::FunctionCall;
        }

        inline const ParsedExpression *GetFunction() const {
            return _function.get();
        }

        inline const vector<const ParsedExpression *> *GetParameters() const {
            return &_parameters;
        }
    };

    class IndexExpression : public ParsedExpression {
        const ParsedExpression *_indexerExpression;
        const ParsedExpression *_indexExpression;
    public:
        IndexExpression(ParsedExpression *indexer, ParsedExpression *index, unsigned int start, unsigned int length)
                : ParsedExpression(start, length),
                  _indexerExpression(indexer), _indexExpression(index) {
        }

        ~IndexExpression() final {
            delete _indexerExpression;
            delete _indexExpression;
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::Indexer;
        }

        inline const ParsedExpression *GetIndexer() const {
            return _indexerExpression;
        }

        inline const ParsedExpression *GetIndex() const {
            return _indexExpression;
        }
    };

    class PeriodIndexExpression : public ParsedExpression {
        const ParsedExpression *_indexerExpression;
        const HashedString _index;
    public:
        PeriodIndexExpression(ParsedExpression *indexer, HashedString index, unsigned int start, unsigned int length)
                : ParsedExpression(start, length),
                  _indexerExpression(indexer), _index(index) {
        }

        ~PeriodIndexExpression() final {
            delete _indexerExpression;
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::PeriodIndexer;
        }

        inline const ParsedExpression *GetIndexer() const {
            return _indexerExpression;
        }

        inline const HashedString &GetIndex() const {
            return _index;
        }
    };


    class ParsedNumericalTableExpression : public ParsedExpression {
        const vector<const ParsedExpression *> _expressions;
    public:
        ParsedNumericalTableExpression(vector<const ParsedExpression *> expressions, unsigned int start,
                                       unsigned int length)
                : ParsedExpression(start, length), _expressions(std::move(expressions)) {
        }

        ~ParsedNumericalTableExpression() final {
            for (auto s: _expressions) {
                delete s;
            }
        }

        inline const vector<const ParsedExpression *> *GetExpressions() const {
            return &_expressions;
        }

        inline const ParsedExpressionKind GetKind() const final {
            return ParsedExpressionKind::NumericalTable;
        }
    };
}

#endif //PORYGONLANG_PARSEDEXPRESSION_HPP
