#include <ast.h>
#include "codeinstructions.h"

using namespace xreate;

    llvm::Value *
    CodeScope::compileExpression(const Expression &expr, LLVMLayer &l, const std::string * const hintRetVar) {
    #define VARNAME(x) (hintRetVar==0 ? x: *hintRetVar)
        llvm::Value *left;
        llvm::Value *right;


        switch (expr.__op) {
            case Operator::ADD:
            case Operator::SUB:
            case Operator::MUL:
            case Operator::DIV:
            case Operator::EQU:
            case Operator::LSS:
            case Operator::GTR:
                assert(expr.__state == Expression::COMPOUND);
                assert(expr.operands.size() == 2);

                left = compileExpression(expr.operands[0], l);
                right = compileExpression(expr.operands[1], l);
                break;

            default:;
        }

        switch (expr.__op) {
            case Operator::ADD:
                return l.builder.CreateAdd(left, right, VARNAME("tmp_add"));
                break;

            case Operator::SUB:
                return l.builder.CreateSub(left, right, VARNAME("tmp_sub"));
                break;

            case Operator::MUL:
                return l.builder.CreateMul(left, right, VARNAME("tmp_mul"));
                break;

            case Operator::DIV:
                return l.builder.CreateSDiv(left, right, VARNAME("tmp_div"));
                break;

            case Operator::EQU:
                left->getType()->dump();
                right->getType()->dump();
                return l.builder.CreateICmpEQ(left, right, VARNAME("tmp_equ"));
                break;

            case Operator::LSS:
                return l.builder.CreateICmpSLT(left, right, VARNAME("tmp_lss"));
                break;

            case Operator::GTR:
                return l.builder.CreateICmpSGT(left, right, VARNAME("tmp_gtr"));
                break;

            case Operator::NEG:
                left = compileExpression(expr.operands[0], l);
                return l.builder.CreateNeg(left, VARNAME("tmp_neg"));
                break;

            case Operator::CALL: {
                assert(expr.__state == Expression::COMPOUND);

                const std::string &fname = expr.__valueS;
                assert(l.ast->__indexFunctions.count(fname));

                const Function &calleeFunc = l.ast->getFunctionById(l.ast->__indexFunctions[fname]);
                llvm::Function *callee = calleeFunc.__raw;

                std::vector<llvm::Value *> args;
                args.reserve(expr.operands.size() - 1);

                std::transform(expr.operands.begin(), expr.operands.end(), std::inserter(args, args.end()),
                        [&l, this](const Expression &operand) {
                            return compileExpression(operand, l);
                        }
                );

                return l.builder.CreateCall(callee, args, VARNAME("tmp_call"));
            }

            case Operator::LIST:
            {
                return CodeInstruction<Operator::LIST>(expr, l).compile(hintRetVar);
            };

            case Operator::INDEX:
            {
                assert(expr.operands.size());
                const std::string &name = expr.__valueS;
                llvm::Value* heap = findSymbol(name, l);
                std::vector<llvm::Value*> indexes;

                indexes.push_back(llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvm::getGlobalContext()), 0));
                std::transform(expr.operands.begin(), expr.operands.end(), std::inserter(indexes, indexes.end()),
                        [this, &l] (const Expression& op){return compileExpression(op, l);}
                );

                llvm::Value* result = l.builder.CreateGEP(heap, llvm::ArrayRef<llvm::Value*>(indexes), VARNAME("tmp_el_of_arr"));
                result->getType()->dump();
                return result;
            };


            case Operator::NONE:
                assert(expr.__state != Expression::COMPOUND);

                switch (expr.__state) {
                    case Expression::IDENT: {
                        const std::string &vname = expr.__valueS;
                        return findSymbol(vname, l);
                    }

                    case Expression::NUMBER:
                        int literal = expr.__valueD;
                        return llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvm::getGlobalContext()), literal);
                };

                break;

        }

        assert(false);
        return 0;
    }

    llvm::Value *
    CodeScope::compile(LLVMLayer &l, const std::string * const hintBlockName) {
        if (hintBlockName) {
            llvm::BasicBlock *block = llvm::BasicBlock::Create(llvm::getGlobalContext(), *hintBlockName, l.function);
            l.builder.SetInsertPoint(block);
        }

        return compileExpression(__body, l, 0);
    }

    llvm::Function *
    Function::compile(LLVMLayer &l) {

        std::vector<llvm::Type *> types;
        std::transform(__entry.__args.begin(), __entry.__args.end(), std::inserter(types, types.end()),
                [this](const std::string &arg)->llvm::Type* {
                    assert(__entry.__vartable.count(arg));
                    VID argid = __entry.__vartable.at(arg);
                    assert(__entry.__definitions.count(argid));
                    return __entry.__definitions.at(argid).toLLVMType();
                });

        llvm::FunctionType *ft = llvm::FunctionType::get(__retType.toLLVMType(), types, false);
        __raw = llvm::cast<llvm::Function>(l.module->getOrInsertFunction(__name, ft));

        llvm::Function::arg_iterator fargsI = __raw->arg_begin();
        for (std::string &arg : __entry.__args) {
            VID argid = __entry.__vartable[arg];

            __entry.__rawVars[argid] = fargsI;
            fargsI->setName(arg);
            ++fargsI;
        }

        l.function = __raw;
        const std::string blockName =  "entry";
        l.builder.CreateRet(__entry.compile(l, &blockName));
        l.function = nullptr;
        l.moveToGarbage(ft);

        return __raw;
    };

    void
    AST::compile(LLVMLayer &layer) {
        layer.ast = this;
        layer.module = new llvm::Module(getModuleName(), llvm::getGlobalContext());

        int __debug_fcount = __functions.size();
        for (Function &f: __functions) {
            llvm::Function *rawf = f.compile(layer);
        }
    }