#include "Semantic.h"
#include "Ast.h"
#include "AstVisitor.h"

#include "CodegenVisitor.h"

#include "Util.h"

using namespace qlow::sem;

namespace qlow
{
namespace sem
{

std::unique_ptr<GlobalScope>
    createFromAst(const std::vector<std::unique_ptr<qlow::ast::AstObject>>& objects)
{
    
    Logger& logger = Logger::getInstance();

#ifdef DEBUGGING
    printf("starting building semantic representation\n");
#endif

    // create classes
    std::unique_ptr<sem::GlobalScope> globalScope = std::make_unique<sem::GlobalScope>();
    for (auto& astObject : objects) {
        if (auto* cls = dynamic_cast<ast::Class*>(astObject.get()); cls) {
            globalScope->classes[cls->name] = std::make_unique<sem::Class>(cls, *globalScope);
        }
        else if (auto* function = dynamic_cast<ast::MethodDefinition*>(astObject.get()); function) {
            globalScope->functions[function->name] = std::make_unique<sem::Method>(function, *globalScope);
        }
    }

#ifdef DEBUGGING
    printf("created symbol table entries for all classes\n");
#endif 
        
    StructureVisitor av;
    
    // create all methods and fields
    for (auto& [name, semClass] : globalScope->classes) {
        for (auto& feature : semClass->astNode->features) {
            
            if (auto* field = dynamic_cast<qlow::ast::FieldDeclaration*> (feature.get()); field) {
                if (semClass->fields.find(field->name) != semClass->fields.end()) // throw, if field already exists
                    throw SemanticError(SemanticError::DUPLICATE_FIELD_DECLARATION, field->name, field->pos);
                
                // otherwise add to the fields list
                semClass->fields[field->name] = unique_dynamic_cast<Field>(field->accept(av, semClass->scope));
            }
            else if (auto* method = dynamic_cast<qlow::ast::MethodDefinition*> (feature.get()); method) {
                if (semClass->methods.find(method->name) != semClass->methods.end()) // throw, if method already exists
                    throw SemanticError(SemanticError::DUPLICATE_METHOD_DEFINITION, method->name, method->pos);
                
                // otherwise add to the methods list
                semClass->methods[method->name] = unique_dynamic_cast<Method>(method->accept(av, semClass->scope));
            }
            else {
                // if a feature is neither a method nor a field, something went horribly wrong
                throw "internal error";
            }
        }
    }
    
    for (auto& [name, method] : globalScope->functions) {
        auto returnType = globalScope->getType(method->astNode->type);
        if (returnType) {
            method->returnType = returnType.value();
        }
        else {
            SemanticError se(SemanticError::UNKNOWN_TYPE, method->astNode->type, method->astNode->pos);
        }
    }
    
#ifdef DEBUGGING
    printf("created all methods and fields\n");
#endif
    
    for (auto& [name, semClass] : globalScope->classes) {
        for (auto& [name, method] : semClass->methods) {
            method->body = unique_dynamic_cast<sem::DoEndBlock>(av.visit(*method->astNode->body, method->scope));
        }
    }
#ifdef DEBUGGING
    printf("created all method bodies\n");
#endif
    
    return globalScope;
}

}
}


SemanticObject::~SemanticObject(void)
{
}


std::string SemanticObject::toString(void) const
{
    return "SemanticObject [" + util::toString(this) + "]";
}


std::string Class::toString(void) const
{
    std::string val = "Class[";
    
    // add fields
    for (auto& field : fields)
        val += field.second->toString() + ", ";
    if (!fields.empty())
        val = val.substr(0, val.length() - 2);
    
    // add methods
    for (auto& method : methods)
        val += method.second->toString() + ", ";
    if (!methods.empty())
        val = val.substr(0, val.length() - 2);
    
    val += " (";
    val += std::to_string(this->astNode->pos.first_line) + ", ";
    val += std::to_string(this->astNode->pos.first_column);
    val += " )";
    
    return val + "]";
}


std::string Variable::toString(void) const
{
    return "Variable[" + this->name + "]";
}


std::string Field::toString(void) const
{
    return "Field[" + this->name + "]";
}


std::string Method::toString(void) const
{
    return "Method[" + this->name + "]";
}



#define COMMA ,
#define ACCEPT_DEFINITION(ClassName, Visitor, ReturnType, Arg) \
ReturnType ClassName::accept(Visitor& v, Arg arg) \
{ \
    return v.visit(*this, arg); \
}

ACCEPT_DEFINITION(LocalVariableExpression, ExpressionVisitor, std::pair<llvm::Value* COMMA Type>, llvm::IRBuilder<>&)
ACCEPT_DEFINITION(BinaryOperation, ExpressionVisitor, std::pair<llvm::Value* COMMA Type>, llvm::IRBuilder<>&)
ACCEPT_DEFINITION(UnaryOperation, ExpressionVisitor, std::pair<llvm::Value* COMMA Type>, llvm::IRBuilder<>&)
ACCEPT_DEFINITION(FeatureCallExpression, ExpressionVisitor, std::pair<llvm::Value* COMMA Type>, llvm::IRBuilder<>&)
ACCEPT_DEFINITION(IntConst, ExpressionVisitor, std::pair<llvm::Value* COMMA Type>, llvm::IRBuilder<>&)

ACCEPT_DEFINITION(AssignmentStatement, StatementVisitor, llvm::Value*, qlow::gen::FunctionGenerator&) 
ACCEPT_DEFINITION(DoEndBlock, StatementVisitor, llvm::Value*, qlow::gen::FunctionGenerator&) 
ACCEPT_DEFINITION(IfElseBlock, StatementVisitor, llvm::Value*, qlow::gen::FunctionGenerator&) 
ACCEPT_DEFINITION(ReturnStatement, StatementVisitor, llvm::Value*, qlow::gen::FunctionGenerator&) 
ACCEPT_DEFINITION(FeatureCallStatement, StatementVisitor, llvm::Value*, qlow::gen::FunctionGenerator&) 

std::string AssignmentStatement::toString(void) const
{
    return "AssignmentStatement[" + this->target->toString() + " := " +
        this->value->toString() + "]";
}


std::string ReturnStatement::toString(void) const
{
    return "ReturnStatement[" + this->value->toString() + "]";
}


std::string LocalVariableExpression::toString(void) const
{
    return "LocalVariableExpression[" + var->name + "]";
}


std::string BinaryOperation::toString(void) const
{
    return "BinaryOperation[" + left->toString() + ", " +
        right->toString() + "]";
}


std::string UnaryOperation::toString(void) const
{
    return "UnaryOperation[" + arg->toString() + "]";
}


std::string FeatureCallExpression::toString(void) const
{
    return "FeatureCallExpression[" + callee->toString() + "]";
}


std::string FeatureCallStatement::toString(void) const
{
    return "FeatureCallStatement[" + expr->callee->toString() + "]";
}