
#ifndef PORYGONLANG_PARSEDSTATEMENT_HPP
#define PORYGONLANG_PARSEDSTATEMENT_HPP

#include <utility>
#include <vector>
#include <string>

#include "../ParsedExpressions/ParsedExpression.hpp"
#include "../../Utilities/HashedString.hpp"
#include "../TypedVariableIdentifier.hpp"

namespace Porygon::Parser {
    enum class ParsedStatementKind : uint8_t {
        Bad,
        Break,
        Script,
        Block,
        Expression,
        Assignment,
        IndexAssignment,
        FunctionDeclaration,
        Return,
        Conditional,
        NumericalFor,
        GenericFor,
        While
    };

    class ParsedStatement {
        const unsigned int _start;
        const unsigned int _length;
    public:
        ParsedStatement(unsigned int start, unsigned int length)
                : _start(start), _length(length) {
        }

        virtual ~ParsedStatement() = default;

        virtual const ParsedStatementKind GetKind() const = 0;

        const unsigned int GetStartPosition() const {
            return _start;
        }

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

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

    class ParsedBadStatement : public ParsedStatement {
    public:
        ParsedBadStatement(unsigned int start, unsigned int length) : ParsedStatement(start, length) {};

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

    class ParsedBreakStatement : public ParsedStatement{
    public:
        ParsedBreakStatement(unsigned int start, unsigned int length) : ParsedStatement(start, length) {};

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::Break;
        }
    };

    class ParsedBlockStatement : public ParsedStatement {
        const std::vector<const ParsedStatement *> _statements;
    public:
        explicit ParsedBlockStatement(const std::vector<const ParsedStatement *>& statements)
                : ParsedStatement(statements.front()->GetStartPosition(),
                                  statements.back()->GetEndPosition() - statements.front()->GetStartPosition()),
                  _statements(statements) {}

        ParsedBlockStatement(std::vector<const ParsedStatement *> statements, unsigned int start) : ParsedStatement(
                start, 0), _statements(std::move(statements)) {
        }


        ~ParsedBlockStatement() override {
            for (auto s: _statements) {
                delete s;
            }
        }

        inline const ParsedStatementKind GetKind() const override {
            return ParsedStatementKind::Block;
        }

        inline const std::vector<const ParsedStatement *> *GetStatements() const {
            return &_statements;
        }
    };

    class ParsedScriptStatement : public ParsedBlockStatement {
    public:
        explicit ParsedScriptStatement(vector<const ParsedStatement *> statements) : ParsedBlockStatement(
                move(statements)) {}

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::Script;
        }
    };

    class ParsedExpressionStatement : public ParsedStatement {
        const ParsedExpression *_expression;
    public:
        explicit ParsedExpressionStatement(ParsedExpression *expression)
                : ParsedStatement(expression->GetStartPosition(), expression->GetLength()),
                  _expression(expression) {
        }

        ~ParsedExpressionStatement() override {
            delete _expression;
        }

        inline void NullifyExpression(){
            _expression = nullptr;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::Expression;
        }

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

    class ParsedFunctionDeclarationStatement : public ParsedStatement {
        const HashedString _identifier;
        const vector<TypedVariableIdentifier *> _parameters;
        const ParsedBlockStatement *_block;
    public:
        ParsedFunctionDeclarationStatement(const HashedString& identifier, vector<TypedVariableIdentifier *> parameters,
                                           ParsedBlockStatement *block,
                                           unsigned int start, unsigned int length)
                : ParsedStatement(start, length), _identifier(identifier), _parameters(std::move(parameters)),
                  _block(block) {};

        ~ParsedFunctionDeclarationStatement() override {
            for (auto v : _parameters) {
                delete v;
            }
            delete _block;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::FunctionDeclaration;
        }

        inline const HashedString GetIdentifier() const {
            return _identifier;
        }

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

        inline const ParsedBlockStatement *GetBlock() const {
            return _block;
        }
    };

    class ParsedAssignmentStatement : public ParsedStatement {
        const bool _local;
        const HashedString _identifier;
        const ParsedExpression *_expression;
    public:
        ParsedAssignmentStatement(bool local, const HashedString& identifier, ParsedExpression *expression,
                                  unsigned int start, unsigned int length)
                : ParsedStatement(start, length), _local(local), _identifier(identifier), _expression(expression) {
        }

        ~ParsedAssignmentStatement() final {
            delete _expression;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::Assignment;
        }

        inline const bool IsLocal() const {
            return _local;
        }

        inline const HashedString GetIdentifier() const {
            return _identifier;
        }

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

    class ParsedIndexAssignmentStatement : public ParsedStatement {
        const ParsedExpression *_indexExpression;
        const ParsedExpression *_valueExpression;
    public:
        ParsedIndexAssignmentStatement(ParsedExpression *indexExpression, ParsedExpression *valueExpression,
                                       unsigned int start,
                                       unsigned int length)
                : ParsedStatement(start, length),
                  _indexExpression(indexExpression), _valueExpression(valueExpression) {}

        ~ParsedIndexAssignmentStatement() final {
            delete _indexExpression;
            delete _valueExpression;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::IndexAssignment;
        }

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

        inline const ParsedExpression *GetValueExpression() const {
            return _valueExpression;
        }
    };

    class ParsedReturnStatement : public ParsedStatement {
        const ParsedExpression *_expression;
    public:
        ParsedReturnStatement(ParsedExpression *expression, unsigned int start, unsigned int length)
                : ParsedStatement(start, length),
                  _expression(expression) {
        }

        ~ParsedReturnStatement() final {
            delete _expression;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::Return;
        }

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

    class ParsedConditionalStatement : public ParsedStatement {
        const ParsedExpression *_condition;
        const ParsedStatement *_block;
        // This can be either else if or else
        const ParsedStatement *_elseStatement;
    public:
        ParsedConditionalStatement(ParsedExpression *condition, ParsedStatement *block, unsigned int start,
                                   unsigned int length)
                : ParsedStatement(start, length), _condition(condition), _block(block), _elseStatement(nullptr) {
        }

        ParsedConditionalStatement(ParsedExpression *condition, ParsedStatement *block, ParsedStatement *nextStatement,
                                   unsigned int start, unsigned int length)
                : ParsedStatement(start, length), _condition(condition), _block(block), _elseStatement(nextStatement) {
            _condition = condition;
            _block = block;
            _elseStatement = nextStatement;
        }

        ~ParsedConditionalStatement() final {
            delete _condition;
            delete _block;
            delete _elseStatement;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::Conditional;
        }

        inline const ParsedExpression *GetCondition() const {
            return _condition;
        }

        inline const ParsedStatement *GetBlock() const {
            return _block;
        }

        inline const ParsedStatement *GetElseStatement() const {
            return _elseStatement;
        }
    };

    class ParsedNumericalForStatement : public ParsedStatement {
        const HashedString _identifier;
        const ParsedExpression *_start;
        const ParsedExpression *_end;
        const ParsedExpression *_step;
        const ParsedStatement *_block;
    public:
        ParsedNumericalForStatement(const HashedString& identifier, const ParsedExpression *start,
                                    const ParsedExpression *end, const ParsedExpression *step, const ParsedStatement *block,
                                    unsigned int startPos, unsigned int length)
                : ParsedStatement(startPos, length), _identifier(identifier), _start(start), _end(end), _step(step), _block(block) {
        }

        ~ParsedNumericalForStatement() final {
            delete _start;
            delete _end;
            delete _step;
            delete _block;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::NumericalFor;
        }

        inline const HashedString GetIdentifier() const{
            return _identifier;
        }

        inline const ParsedExpression *GetStart() const{
            return _start;
        }

        inline const ParsedExpression *GetEnd() const{
            return _end;
        }

        inline const ParsedExpression *GetStep() const{
            return _step;
        }

        inline const ParsedStatement *GetBlock() const{
            return _block;
        }
    };

    class ParsedGenericForStatement : public ParsedStatement{
        const HashedString _keyIdentifier;
        const HashedString _valueIdentifier;
        const ParsedExpression* _iteratorExpression;
        const ParsedStatement* _block;
    public:
        ParsedGenericForStatement(const HashedString& keyIdentifier, const HashedString& valueIdentifier,
                                  const ParsedExpression *iteratorExpression, const ParsedStatement *block,
                                  unsigned int start, unsigned int length)
                : ParsedStatement(start, length), _keyIdentifier(keyIdentifier), _valueIdentifier(valueIdentifier),
                  _iteratorExpression(iteratorExpression), _block(block) {}

        ~ParsedGenericForStatement() final{
            delete _iteratorExpression;
            delete _block;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::GenericFor;
        }

        inline const HashedString GetKeyIdentifier() const{
            return _keyIdentifier;
        }

        inline const HashedString GetValueIdentifier() const{
            return _valueIdentifier;
        }

        inline const ParsedExpression* GetIteratorExpression() const{
            return _iteratorExpression;
        }

        inline const ParsedStatement* GetBlock() const{
            return _block;
        }
    };

    class ParsedWhileStatement : public ParsedStatement{
        const ParsedExpression* _condition;
        const ParsedStatement* _block;
    public:
        ParsedWhileStatement(const ParsedExpression *condition, const ParsedStatement *block,
                             unsigned int start, unsigned int length)
                : ParsedStatement(start, length), _condition(condition), _block(block) {}

        ~ParsedWhileStatement() final{
            delete _condition;
            delete _block;
        }

        inline const ParsedStatementKind GetKind() const final {
            return ParsedStatementKind::While;
        }

        inline const ParsedExpression* GetCondition(){
            return _condition;
        }

        inline const ParsedStatement* GetBlock(){
            return _block;
        }
    };

}
#endif //PORYGONLANG_PARSEDSTATEMENT_HPP
