#ifndef PORYGONLANG_BOUNDSTATEMENT_HPP
#define PORYGONLANG_BOUNDSTATEMENT_HPP
#include <utility>
#include <vector>
#include "../BoundExpressions/BoundExpression.hpp"
#include "../BoundVariables/BoundVariableKey.hpp"


using namespace std;

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

    class BoundStatement {
    public:
        [[nodiscard]]
        virtual BoundStatementKind GetKind() const = 0;

        virtual ~BoundStatement() = default;

        virtual void GetTreeString(std::stringstream& stream, size_t indents) const = 0;
    };

    class BoundBadStatement : public BoundStatement {
    public:
        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Bad;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const final{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "BadStatement";
        }
    };

    class BoundBreakStatement : public BoundStatement {
    public:
        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Break;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const final{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "BreakStatement";
        }
    };

    class BoundBlockStatement : public BoundStatement {
        const vector<const BoundStatement *> _statements;
    public:
        explicit BoundBlockStatement(vector<const BoundStatement *> statements)
                : _statements(std::move(statements)) {
        }

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

        [[nodiscard]]
        inline BoundStatementKind GetKind() const override {
            return BoundStatementKind::Block;
        }

        [[nodiscard]]
        inline const vector<const BoundStatement *> *GetStatements() const {
            return &_statements;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "BlockStatement";
            for (auto s : _statements){
                stream << endl;
                s->GetTreeString(stream, indents + 1);
            }
        }
    };

    class BoundScriptStatement : public BoundBlockStatement {
        const int _localVariableCount;
    public:
        explicit BoundScriptStatement(vector<const BoundStatement *> statements, int localVariableCount)
                : BoundBlockStatement(std::move(statements)),
                  _localVariableCount(localVariableCount) {
        }

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Script;
        }

        [[nodiscard]]
        inline int GetLocalVariableCount() const {
            return _localVariableCount;
        }
    };

    class BoundExpressionStatement : public BoundStatement {
        const BoundExpression *_expression;
    public:
        explicit BoundExpressionStatement(BoundExpression *expression)
                : _expression(expression) {
            _expression = expression;
        }

        ~BoundExpressionStatement() final {
            delete _expression;
        }

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Expression;
        }

        [[nodiscard]]
        inline const BoundExpression *GetExpression() const {
            return _expression;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "ExpressionStatement" << endl;
            _expression->GetTreeString(stream, indents + 1);
        }
    };

    class BoundAssignmentStatement : public BoundStatement {
        const BoundVariableKey *_key;
        const BoundExpression *_expression;
    public:
        BoundAssignmentStatement(const BoundVariableKey *key, BoundExpression *expression)
                : _key(key), _expression(expression) {
        }

        ~BoundAssignmentStatement() final {
            delete _key;
            delete _expression;
        }

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Assignment;
        }

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

        [[nodiscard]]
        inline const BoundExpression *GetExpression() const {
            return _expression;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Assignment -> " << _key->GetIdentifier()->GetString() << endl;
            _expression->GetTreeString(stream, indents + 1);
        }
    };

    class BoundIndexAssignmentStatement : public BoundStatement {
        const BoundExpression *_indexExpression;
        const BoundExpression *_valueExpression;
    public:
        BoundIndexAssignmentStatement(const BoundExpression *index, BoundExpression *value)
                : _indexExpression(index), _valueExpression(value) {
        }

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

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::IndexAssignment;
        }

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

        [[nodiscard]]
        inline const BoundExpression *GetValueExpression() const {
            return _valueExpression;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "IndexAssignment" << endl;
            _indexExpression->GetTreeString(stream, indents + 1);
            stream << endl;
            _valueExpression->GetTreeString(stream, indents + 1);
        }
    };

    class BoundReturnStatement : public BoundStatement {
        const BoundExpression *_expression;
    public:
        explicit BoundReturnStatement(BoundExpression *expression)
                : _expression(expression) {
        }

        ~BoundReturnStatement() final {
            delete _expression;
        }

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Return;
        }

        [[nodiscard]]
        inline const BoundExpression *GetExpression() const {
            return _expression;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "ReturnStatement" << endl;
            if (_expression != nullptr){
                _expression->GetTreeString(stream, indents + 1);
            }
        }

    };

    class BoundConditionalStatement : public BoundStatement {
        const BoundExpression *_condition;
        const BoundStatement *_block;
        const BoundStatement *_elseStatement;
    public:
        explicit BoundConditionalStatement(BoundExpression *condition, BoundStatement *block, BoundStatement *next)
                : _condition(condition), _block(block), _elseStatement(next) {
        }

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

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::Conditional;
        }

        [[nodiscard]]
        inline const BoundExpression *GetCondition() const {
            return _condition;
        }

        [[nodiscard]]
        inline const BoundStatement *GetBlock() const {
            return _block;
        }

        [[nodiscard]]
        inline const BoundStatement *GetElseStatement() const {
            return _elseStatement;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "ConditionalStatement" << endl;
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Condition:" << endl;
            _condition->GetTreeString(stream, indents + 1);
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "If True:" << endl;
            _block->GetTreeString(stream, indents + 1);
            if (_elseStatement != nullptr){
                for (size_t i = 0; i < indents; i++)
                    stream << "\t";
                stream << "Else:" << endl;
                _elseStatement->GetTreeString(stream, indents + 1);
            }
        }
    };

    class BoundNumericalForStatement : public BoundStatement {
        const BoundVariableKey* _identifier;
        const BoundExpression *_start;
        const BoundExpression *_end;
        const BoundExpression *_step;

        const BoundStatement *_block;
    public:
        explicit BoundNumericalForStatement(const BoundVariableKey* identifier, const BoundExpression *start,
                                            const BoundExpression *end, const BoundExpression *step,
                                            const BoundStatement *block)
                : _identifier(identifier), _start(start), _end(end), _step(step), _block(block) {
        }

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

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::NumericalFor;
        }

        [[nodiscard]]
        inline const BoundVariableKey* GetIdentifier() const{
            return _identifier;
        }

        [[nodiscard]]
        inline const BoundExpression* GetStart() const{
            return _start;
        }

        [[nodiscard]]
        inline const BoundExpression* GetEnd() const{
            return _end;
        }

        [[nodiscard]]
        inline const BoundExpression* GetStep() const{
            return _step;
        }

        [[nodiscard]]
        inline const BoundStatement* GetBlock() const{
            return _block;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "NumericForLoopStatement" << endl;
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Start:" << endl;
            _start->GetTreeString(stream, indents + 1);
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "End:" << endl;
            _end->GetTreeString(stream, indents + 1);
            if (_step != nullptr){
                for (size_t i = 0; i < indents; i++)
                    stream << "\t";
                stream << "Step:" << endl;
                _step->GetTreeString(stream, indents + 1);
            }
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Do:" << endl;
            _block->GetTreeString(stream, indents + 1);
        }
    };

    class BoundGenericForStatement : public BoundStatement {
        const BoundVariableKey* _keyIdentifier;
        const BoundVariableKey* _valueIdentifier;
        const BoundExpression* _iterator;

        const BoundStatement *_block;
    public:
        explicit BoundGenericForStatement(const BoundVariableKey *keyIdentifier,
                                          const BoundVariableKey *valueIdentifier,
                                          const BoundExpression *iterator, const BoundStatement *block)
                : _keyIdentifier(keyIdentifier), _valueIdentifier(valueIdentifier), _iterator(iterator), _block(block) {
        }

        ~BoundGenericForStatement() final {
            delete _keyIdentifier;
            delete _valueIdentifier;
            delete _iterator;
            delete _block;
        }

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::GenericFor;
        }

        [[nodiscard]]
        inline const BoundVariableKey* GetKeyIdentifier() const{
            return _keyIdentifier;
        }

        [[nodiscard]]
        inline const BoundVariableKey* GetValueIdentifier() const{
            return _valueIdentifier;
        }

        [[nodiscard]]
        inline const BoundExpression* GetIterator() const{
            return _iterator;
        }

        [[nodiscard]]
        inline const BoundStatement* GetBlock() const{
            return _block;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "GenericForLoopStatement" << endl;
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Key: " << _keyIdentifier->GetIdentifier()->GetString().get() << endl;
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Value: " << _valueIdentifier->GetIdentifier()->GetString().get() << endl;
            stream << "Iterator: " << endl;
            _iterator->GetTreeString(stream, indents + 1);
            stream << endl;
            _block->GetTreeString(stream, indents + 1);
        }
    };

    class BoundWhileStatement : public BoundStatement {
        const BoundExpression* _condition;
        const BoundStatement *_block;
    public:
        explicit BoundWhileStatement(const BoundExpression *condition, const BoundStatement *block)
                : _condition(condition), _block(block) {
        }

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

        [[nodiscard]]
        inline BoundStatementKind GetKind() const final {
            return BoundStatementKind::While;
        }

        [[nodiscard]]
        inline const BoundExpression* GetCondition() const{
            return _condition;
        }

        [[nodiscard]]
        inline const BoundStatement* GetBlock() const{
            return _block;
        }

        void GetTreeString(std::stringstream& stream, size_t indents) const override{
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "ConditionalStatement" << endl;
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "Condition:" << endl;
            _condition->GetTreeString(stream, indents + 1);
            for (size_t i = 0; i < indents; i++)
                stream << "\t";
            stream << "While True:" << endl;
            _block->GetTreeString(stream, indents + 1);
        }
    };
}


#include "BoundFunctionDeclarationStatement.hpp"

#endif //PORYGONLANG_BOUNDSTATEMENT_HPP