#include <ast.h>
#include <iostream>
#include "query/containers.h"
#include "instructions/instr-containers.h"

using namespace xreate;
using namespace std;

    llvm::Value *
    CodeScope::compileExpression(const Expression &expr, LLVMLayer &l, std::string  hintRetVar) {
    #define VARNAME(x) (hintRetVar.empty()? 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->dump();
                right->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;

                ManagedFnPtr calleeFunc = l.ast->findFunction(fname);
                assert(calleeFunc.isValid());

                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 containers::Instructions(this, &l).compileConstantArray(expr, VARNAME("list"));
            };

            case Operator::LIST_RANGE:
            {
                assert(false); //no compilation phase for a range list
              //  return InstructionList(this).compileConstantArray(expr, l, hintRetVar);
            };



            case Operator::MAP:
            {
                assert(expr.blocks.size());
                return containers::Instructions(this, &l).compileMapArray(expr, VARNAME("map"));
            };

            case Operator::FOLD:
            {
                return containers::Instructions(this, &l).compileFold(expr, VARNAME("fold"));
            };

            case Operator::INDEX:
            {
                assert(expr.operands.size());
                const std::string &ident = expr.operands.begin()->getValueString();
                Symbol s = findSymbol(ident, l, true);

                std::vector<llvm::Value*> indexes;
                std::transform(++expr.operands.begin(), expr.operands.end(), std::inserter(indexes, indexes.end()),
                    [this, &l] (const Expression& op){return compileExpression(op, l);}
                );

                return containers::Instructions(this, &l).compileIndex(s, indexes, VARNAME(string("el_") + ident));
            };


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

                switch (expr.__state) {
                    case Expression::IDENT: {
                        const std::string &vname = expr.__valueS;
                        const Symbol& var = findSymbol(vname, l, true);
                        return var.scope->__rawVars.at(var.identifier);
                    }

                    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& hintBlockName) {
        if (!hintBlockName.empty()) {
            llvm::BasicBlock *block = llvm::BasicBlock::Create(llvm::getGlobalContext(), hintBlockName, l.context.function);
            l.builder.SetInsertPoint(block);
        }

        return compileExpression(__body, l);
    }

    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(__entry->__definitions[0].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.context.function = __raw;
        const std::string blockName =  "entry";
        l.builder.CreateRet(__entry->compile(l, blockName));
        l.context.function = nullptr;
        l.moveToGarbage(ft);

        return __raw;
    };

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

        for (Function* f: __functions) {
            llvm::Function *rawf = f->compile(layer);
        }
    }