#ifdef TESTS_BUILD
#include <catch.hpp>
#include "../../src/Parser/Parser.hpp"
using namespace Porygon::Parser;

TEST_CASE( "Parse single true keyword", "[parser]" ) {
    vector<const Token*> v {new SimpleToken(TokenKind::TrueKeyword,0,0), new SimpleToken(TokenKind::EndOfFile,0,0)};
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::LiteralBool);
    auto boolean = ((LiteralBoolExpression*)expression);
    REQUIRE(boolean->GetValue());

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse single false keyword", "[parser]" ) {
    vector<const Token*> v {new SimpleToken(TokenKind::FalseKeyword,0,0), new SimpleToken(TokenKind::EndOfFile,0,0)};
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::LiteralBool);
    auto boolean = ((LiteralBoolExpression*)expression);
    REQUIRE_FALSE(boolean->GetValue());

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse simple addition", "[parser]" ) {
    vector<const Token*> v {
            new IntegerToken(5, 0, 0),
            new SimpleToken(TokenKind::PlusToken,0,0),
            new IntegerToken(10, 0, 0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::Binary);
    auto binary = ((BinaryExpression*)expression);
    CHECK(binary -> GetOperatorKind() == BinaryOperatorKind::Addition);
    auto left = binary->GetLeft();
    auto right = binary->GetRight();
    REQUIRE(left->GetKind() == ParsedExpressionKind::LiteralInteger);
    REQUIRE(right->GetKind() == ParsedExpressionKind::LiteralInteger);
    CHECK(((LiteralIntegerExpression*)left)->GetValue() == 5);
    CHECK(((LiteralIntegerExpression*)right)->GetValue() == 10);

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse simple negation", "[parser]" ) {
    vector<const Token*> v {
            new SimpleToken(TokenKind::MinusToken,0,0),
            new IntegerToken(10, 0, 0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::Unary);
    auto unary = ((UnaryExpression*)expression);
    CHECK(unary -> GetOperatorKind() == UnaryOperatorKind::Negation);
    auto operand = unary->GetOperand();
    REQUIRE(operand->GetKind() == ParsedExpressionKind::LiteralInteger);
    CHECK(((LiteralIntegerExpression*)operand)->GetValue() == 10);
    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse logical negation", "[parser]" ) {
    vector<const Token*> v {
            new SimpleToken(TokenKind::NotKeyword,0,0),
            new SimpleToken(TokenKind::FalseKeyword,0,0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::Unary);
    auto unary = ((UnaryExpression*)expression);
    CHECK(unary -> GetOperatorKind() == UnaryOperatorKind::LogicalNegation);
    auto operand = unary->GetOperand();
    REQUIRE(operand->GetKind() == ParsedExpressionKind::LiteralBool);
    CHECK_FALSE(((LiteralBoolExpression*)operand)->GetValue());
    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Are parenthesized expressions valid", "[parser]" ) {
    vector<const Token*> v {
            new IntegerToken(5, 0, 0),
            new SimpleToken(TokenKind::PlusToken,0,0),
            new IntegerToken(10, 0, 0),
            new SimpleToken(TokenKind::StarToken,0,0),
            new IntegerToken(6, 0, 0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::Binary);
    auto binary = ((BinaryExpression*)expression);
    CHECK(binary -> GetOperatorKind() == BinaryOperatorKind::Addition);
    auto left = binary->GetLeft();
    auto right = binary->GetRight();
    REQUIRE(left->GetKind() == ParsedExpressionKind::LiteralInteger);
    REQUIRE(right->GetKind() == ParsedExpressionKind::Binary);
    CHECK(((LiteralIntegerExpression*)left)->GetValue() == 5);
    left = ((BinaryExpression*)right)->GetLeft();
    right = ((BinaryExpression*)right)->GetRight();
    CHECK(((LiteralIntegerExpression*)left)->GetValue() == 10);
    CHECK(((LiteralIntegerExpression*)right)->GetValue() == 6);

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Assert binary precedence", "[parser]" ) {
    vector<const Token*> v {
            new SimpleToken(TokenKind::OpenParenthesis,0,0),
            new IntegerToken(10, 0, 0),
            new SimpleToken(TokenKind::CloseParenthesis,0,0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::Parenthesized);
    auto innerExpression = ((ParenthesizedExpression*)expression) -> GetInnerExpression();
    REQUIRE(innerExpression -> GetKind() == ParsedExpressionKind::LiteralInteger);

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse String Tokens", "[parser]" ) {
    vector<const Token*> v {new StringToken(u"foo bar", 0,0), new SimpleToken(TokenKind::EndOfFile,0,0)};
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Expression);
    auto expression = ((ParsedExpressionStatement*)firstStatement)->GetExpression();
    REQUIRE(expression -> GetKind() == ParsedExpressionKind::LiteralString);
    auto boolean = ((LiteralStringExpression*)expression);
    REQUIRE(boolean->GetValue() == u"foo bar");

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse Global Assignment", "[parser]" ) {
    vector<const Token*> v {
            new IdentifierToken(HashedString::CreateLookup(u"foo"),0,0),
            new SimpleToken(TokenKind::AssignmentToken,0,0),
            new SimpleToken(TokenKind::TrueKeyword,0,0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
        };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Assignment);
    auto assignment = (ParsedAssignmentStatement*)firstStatement;
    REQUIRE(!assignment -> IsLocal());
    REQUIRE(assignment->GetIdentifier().GetHash() == HashedString::CreateLookup(u"foo").GetHash());
    REQUIRE(((LiteralBoolExpression*)assignment->GetExpression()) -> GetValue());

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}

TEST_CASE( "Parse local Assignment", "[parser]" ) {
    vector<const Token*> v {
            new SimpleToken(TokenKind::LocalKeyword,0,0),
            new IdentifierToken(HashedString(new u16string(u"foo")),0,0),
            new SimpleToken(TokenKind::AssignmentToken,0,0),
            new SimpleToken(TokenKind::TrueKeyword,0,0),
            new SimpleToken(TokenKind::EndOfFile,0,0)
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::Assignment);
    auto assignment = (ParsedAssignmentStatement*)firstStatement;
    REQUIRE(assignment -> IsLocal());
    REQUIRE(assignment->GetIdentifier().GetHash() == HashedString::CreateLookup(u"foo").GetHash());
    REQUIRE(((LiteralBoolExpression*)assignment->GetExpression()) -> GetValue());

    for (auto t : v){
        delete t;
    }
    delete parsedScript;

}

TEST_CASE( "Parse function declaration", "[parser]" ){
    vector<const Token*> v {
            new SimpleToken(TokenKind::FunctionKeyword,0,0),
            new IdentifierToken(HashedString(new u16string(u"foo")),0,0),
            new SimpleToken(TokenKind::OpenParenthesis,0,0),
            new IdentifierToken(HashedString(new u16string(u"number")),0,0),
            new IdentifierToken(HashedString(new u16string(u"bar")),0,0),
            new SimpleToken(TokenKind::CommaToken,0,0),
            new IdentifierToken(HashedString(new u16string(u"number")),0,0),
            new IdentifierToken(HashedString(new u16string(u"par")),0,0),
            new SimpleToken(TokenKind::CloseParenthesis,0,0),
            new IdentifierToken(HashedString(new u16string(u"bar")),0,0),
            new SimpleToken(TokenKind::PlusToken,0,0),
            new IdentifierToken(HashedString(new u16string(u"par")),0,0),
            new SimpleToken(TokenKind::EndKeyword,0,0),
            new SimpleToken(TokenKind::EndOfFile,0,0),
    };
    Parser parser = Parser(v, nullptr);
    auto parsedScript = parser.Parse();
    auto parsedStatements = parsedScript -> GetStatements();
    REQUIRE(parsedStatements->size() == 1);
    auto firstStatement = parsedStatements -> at(0);
    REQUIRE(firstStatement -> GetKind() == ParsedStatementKind::FunctionDeclaration);
    auto functionDeclaration = (ParsedFunctionDeclarationStatement*)firstStatement;
    REQUIRE(functionDeclaration->GetIdentifier() == HashedString::CreateLookup(u"foo"));
    auto parameters = functionDeclaration->GetParameters();
    CHECK(*parameters -> at(0) ->GetType() == HashedString::CreateLookup(u"number"));
    CHECK(*parameters -> at(0) ->GetIdentifier() == HashedString::CreateLookup(u"bar"));
    CHECK(*parameters -> at(1) ->GetType() == HashedString::CreateLookup(u"number"));
    CHECK(*parameters -> at(1) ->GetIdentifier() == HashedString::CreateLookup(u"par"));

    for (auto t : v){
        delete t;
    }
    delete parsedScript;
}


#endif