/*
 * File:   targetinterpretation.cpp
 * Author: pgess
 *
 * Created on June 29, 2016, 6:45 PM
 */

#include "compilation/targetinterpretation.h"
#include "pass/interpretationpass.h"
#include "analysis/typeinference.h"
#include "llvmlayer.h"
#include "compilation/scopedecorators.h"

#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <clang/AST/DeclBase.h>

using namespace std;
using namespace xreate::compilation;

namespace xreate{ namespace interpretation{

    const Expression EXPRESSION_FALSE = Expression(Atom<Number_t>(0));
    const Expression EXPRESSION_TRUE = Expression(Atom<Number_t>(1));

//Expression
//InterpretationScope::compile(const Expression& expression){}

CodeScope*
InterpretationScope::processOperatorIf(const Expression& expression){
    const Expression& exprCondition = process(expression.getOperands()[0]);

    if (exprCondition == EXPRESSION_TRUE){
        return expression.blocks.front();
    }

    return expression.blocks.back();
}

CodeScope*
InterpretationScope::processOperatorSwitch(const Expression& expression) {
    const Expression& exprCondition = process(expression.operands[0]);

    bool flagHasDefault = expression.operands[1].op == Operator::CASE_DEFAULT;

    //TODO check that one and only one case variant is appropriate
    for (size_t size = expression.operands.size(), i= flagHasDefault? 2: 1; i<size; ++i){
        const Expression& exprCase = process(expression.operands[i]);

        if (function->getScope(exprCase.blocks.front())->processScope() == exprCondition){
            return exprCase.blocks.back();
        }
    }

    if (flagHasDefault){
        const Expression& exprCaseDefault = expression.operands[1];
        return exprCaseDefault.blocks.front();
    }

    assert(false && "Switch has no appropriate variant");
    return nullptr;
}

CodeScope*
InterpretationScope::processOperatorSwitchVariant(const Expression& expression){
    const Expression& condition = process(expression.operands.at(0));
    assert(condition.op == Operator::VARIANT);
    const string identCondition = expression.bindings.front();

    Expression opExpected(Atom<Number_t>(condition.getValueDouble()));
    auto itFoundValue = std::find(++expression.operands.begin(), expression.operands.end(), opExpected);
    assert(itFoundValue != expression.operands.end());

    int indexBlock = itFoundValue - expression.operands.begin() -1;
    auto blockFound = expression.blocks.begin();
    std::advance(blockFound, indexBlock);

    InterpretationScope* scopeI12n = function->getScope(*blockFound);

    if (condition.operands.size()) {
        const Expression& value = condition.operands.at(0);
        scopeI12n->overrideBinding(value, identCondition);
    }

    return *blockFound;
}

llvm::Value*
InterpretationScope::compileHybrid(const InterpretationOperator& op, const Expression& expression, const Context& context){
    switch(op){
        case IF_INTERPRET_CONDITION: {
            CodeScope* scopeResult = processOperatorIf(expression);

            llvm::Value* result = context.function->getScopeUnit(scopeResult)->compile();
            return result;
        }

        case SWITCH_INTERPRET_CONDITION:{
            CodeScope* scopeResult = processOperatorSwitch(expression);

            llvm::Value* result = context.function->getScopeUnit(scopeResult)->compile();
            return result;
        }

        case SWITCH_VARIANT: {
            CodeScope* scopeResult = processOperatorSwitchVariant(expression);
            const Expression& condition = process(expression.operands.at(0));

            const string identCondition = expression.bindings.front();
            auto scopeCompilation = Decorators<CachedScopeDecoratorTag>::getInterface(context.function->getScopeUnit(scopeResult));

            if(condition.operands.size()){
                //override value
                Symbol symbCondition{ScopedSymbol{scopeResult->__identifiers.at(identCondition), versions::VERSION_NONE}, scopeResult};
                scopeCompilation->overrideDeclaration(symbCondition, Expression(condition.operands.at(0)));

                //set correct type for binding:
                TypeAnnotation typeVariant = typeinference::getType(condition, *function->man->ast);
                int conditionIndex = condition.getValueDouble();
                ScopedSymbol symbolInternal = scopeResult->getSymbol(identCondition);
                scopeResult->__declarations[symbolInternal].bindType(typeVariant.__operands.at(conditionIndex));
            }

            llvm::Value* result = context.function->getScopeUnit(scopeResult)->compile();
            return result;
        }

        case FOLD_INTERPRET_INPUT: {
            //initialization
            const Expression& exprInput = process(expression.getOperands()[0]);
            assert(exprInput.op == Operator::LIST);

            CodeScope* scopeBody = expression.blocks.front();

            const string& nameEl = expression.bindings[0];
            Symbol symbEl{ScopedSymbol{scopeBody->__identifiers.at(nameEl), versions::VERSION_NONE}, scopeBody};
            const std::string& idAccum = expression.bindings[1];
            llvm::Value* rawAccum = context.scope->process(expression.getOperands()[1]);

            InterpretationScope* intrBody = function->getScope(scopeBody);
            auto unitBody = Decorators<CachedScopeDecoratorTag>::getInterface(context.function->getScopeUnit(scopeBody));

            const std::vector<Expression> elementsInput= exprInput.getOperands();

            for (size_t i=0; i<elementsInput.size(); ++i){
                intrBody->reset();
                unitBody->reset();

                Expression exprElement  = elementsInput[i];

                intrBody->overrideBinding(exprElement, nameEl);
                unitBody->overrideDeclaration(symbEl, move(exprElement));
                unitBody->bindArg(rawAccum, string(idAccum));

                rawAccum = unitBody->compile();
            }

            return rawAccum;
        }

        /*
        case FOLD_INF_INTERPRET_INOUT{
        }
        */

        case CALL_INTERPRET_PARTIAL: {
            const std::string &calleeName = expression.getValueString();
            ICodeScopeUnit* scopeUnitSelf = context.scope;
            ManagedFnPtr callee = this->function->man->ast->findFunction(calleeName);
            const  FunctionInterpretationData& calleeData = FunctionInterpretationHelper::getSignature(callee);
            std::vector<llvm::Value *> argsActual;
            PIFSignature sig;
            sig.declaration = callee;

            for(size_t no=0, size = expression.operands.size(); no < size; ++no){
                const Expression& op =  expression.operands[no];

                if (calleeData.signature.at(no) == INTR_ONLY){
                    sig.bindings.push_back(process(op));
                    continue;
                }

                argsActual.push_back(scopeUnitSelf->process(op));
            }

            TargetInterpretation* man = dynamic_cast<TargetInterpretation*>(this->function->man);
            PIFunction* pifunction =  man->getFunction(move(sig));

            llvm::Function* raw = pifunction->compile();
            boost::scoped_ptr<CallStatementRaw> statement(new CallStatementRaw(raw, man->pass->man->llvm));
            return (*statement)(move(argsActual));
        }

        default: break;
    }

    assert(false&& "Unknown hybrid operator");
    return nullptr;
}

llvm::Value*
InterpretationScope::compile(const Expression& expression, const Context& context){
    const InterpretationData& data = Attachments::get<InterpretationData>(expression);

    if (data.op != InterpretationOperator::NONE){
        return compileHybrid(data.op, expression, context);
    }

    Expression result = process(expression);
    return context.scope->process(result);
}

Expression
InterpretationScope::process(const Expression& expression){
    switch (expression.__state){
        case Expression::INVALID:
            assert(false);

        case Expression::NUMBER:
        case Expression::STRING:
            return expression;

        case Expression::IDENT:{
            Symbol s = Attachments::get<Symbol>(expression);
            return Parent::processSymbol(s);
        }

        case Expression::COMPOUND:
            break;

        default: assert(false);
    }

    switch (expression.op) {
        case Operator::EQU: {
            const Expression& left = process(expression.operands[0]);
            const Expression& right = process(expression.operands[1]);

            if (left == right) return EXPRESSION_TRUE;
            return EXPRESSION_FALSE;
        }

        case Operator::NE: {
            const Expression& left = process(expression.operands[0]);
            const Expression& right = process(expression.operands[1]);

            if (left == right) return EXPRESSION_FALSE;
            return EXPRESSION_TRUE;
        }

        case Operator::LOGIC_AND: {
            assert(expression.operands.size() == 1);
            return process (expression.operands[0]);
        }


//        case Operator::LOGIC_OR:
        case Operator::CALL: {
            const std::string &fnName = expression.getValueString();
            ManagedFnPtr fnAst = this->function->man->ast->findFunction(fnName);
            InterpretationFunction* fnUnit = this->function->man->getFunction(fnAst);

            vector<Expression> args;
            args.reserve(expression.getOperands().size());

            for(size_t i=0, size = expression.getOperands().size(); i<size; ++i) {
                args.push_back(process(expression.getOperands()[i]));
            }

            return fnUnit->process(args);
        }

        case Operator::IF:{
            CodeScope* scopeResult = processOperatorIf(expression);
            return function->getScope(scopeResult)->processScope();
        }

        case Operator::SWITCH: {
            CodeScope* scopeResult = processOperatorSwitch(expression);
            return function->getScope(scopeResult)->processScope();
        }

        case Operator::SWITCH_VARIANT: {
            CodeScope* scopeResult = processOperatorSwitchVariant(expression);
            return function->getScope(scopeResult)->processScope();
        }

        case Operator::INDEX: {
            const Expression& exprKey = process(expression.operands[1]);
            const Expression& exprData = process(expression.operands[0]);

            if (exprKey.__state == Expression::STRING){
                const string& key = exprKey.getValueString();
                assert(exprData.__indexBindings.count(key));

                return exprData.operands[exprData.__indexBindings.at(key)];
            }

            if (exprKey.__state == Expression::NUMBER){
                int key = exprKey.getValueDouble();
                return exprData.operands[key];
            }

            assert(false);
        }

        case Operator::FOLD: {
            const Expression& exprInput = process(expression.getOperands()[0]);
            const Expression& exprInit = process(expression.getOperands()[1]);

            const std::string& argEl = expression.bindings[0];
            const std::string& argAccum = expression.bindings[1];

            InterpretationScope* body = function->getScope(expression.blocks.front());

            Expression accum = exprInit;
            for(size_t size=exprInput.getOperands().size(), i=0; i<size; ++i){
                body->overrideBinding(exprInput.getOperands()[i], argEl);
                body->overrideBinding(accum, argAccum);

                accum = body->processScope();
            }

            return accum;
        }

//        case Operator::MAP: {
//            break;
//        }

        default: break;
    }

    return expression;
}

InterpretationFunction*
TargetInterpretation::getFunction(IFunctionUnit* unit){
    if (__dictFunctionsByUnit.count(unit)) {
        return __dictFunctionsByUnit.at(unit);
    }

    InterpretationFunction* f = new InterpretationFunction(unit->function, this);
    __dictFunctionsByUnit.emplace(unit, f);
    assert(__functions.emplace(unit->function.id(), f).second);

    return f;
}

PIFunction*
TargetInterpretation::getFunction(PIFSignature&& sig){

    auto f = __pifunctions.find(sig);
    if (f != __pifunctions.end()){
        return f->second;
    }

    PIFunction* result  = new PIFunction(PIFSignature(sig), __pifunctions.size(), this);
    __pifunctions.emplace(move(sig), result);
    assert(__dictFunctionsByUnit.emplace(result->functionUnit, result).second);

    return result;
}

InterpretationScope*
TargetInterpretation::transformContext(const Context& c){
    return this->getFunction(c.function)->getScope(c.scope->scope);
}

llvm::Value*
TargetInterpretation::compile(const Expression& expression, const Context& ctx){
    return transformContext(ctx)->compile(expression, ctx);
}

InterpretationFunction::InterpretationFunction(const ManagedFnPtr& function, Target<TargetInterpretation>* target)
    : Function<TargetInterpretation>(function, target)
{}

Expression
InterpretationFunction::process(const std::vector<Expression>& args){
    InterpretationScope* body = getScope(__function->__entry);

    for(size_t i=0, size = args.size(); i<size; ++i) {
        body->overrideBinding(args.at(i), string(body->scope->__bindings.at(i)));
    }

    return body->processScope();
}

//                  Partial function interpretation

typedef BasicFunctionUnit PIFunctionUnitParent;
class PIFunctionUnit: public PIFunctionUnitParent{
public:

    PIFunctionUnit(ManagedFnPtr f, std::set<size_t>&& arguments, size_t id, CompilePass* p)
        : PIFunctionUnitParent(f,  p), argumentsActual(move(arguments)), __id(id)
    {}

protected:
    std::vector<llvm::Type*> prepareArguments(){
        LLVMLayer* llvm = PIFunctionUnitParent::pass->man->llvm;
        AST* ast = PIFunctionUnitParent::pass->man->root;
        CodeScope* entry = PIFunctionUnitParent::function->__entry;
        std::vector<llvm::Type*> signature;

        for(size_t no: argumentsActual){
            VNameId argId = entry->__identifiers.at(entry->__bindings.at(no));
            ScopedSymbol arg{argId, versions::VERSION_NONE};

            signature.push_back(llvm->toLLVMType(ast->expandType(entry->__declarations.at(arg).type)));
        }

        return signature;
    }

    llvm::Function::arg_iterator prepareBindings(){
        CodeScope* entry = PIFunctionUnitParent::function->__entry;
        ICodeScopeUnit* entryCompilation = PIFunctionUnitParent::getScopeUnit(entry);
        llvm::Function::arg_iterator fargsI = PIFunctionUnitParent::raw->arg_begin();

        for(size_t no: argumentsActual){
            ScopedSymbol arg{entry->__identifiers.at(entry->__bindings.at(no)), versions::VERSION_NONE};

            entryCompilation->bindArg(&*fargsI, arg);
            fargsI->setName(entry->__bindings.at(no));
            ++fargsI;
        }

        return fargsI;
    }

    virtual std::string prepareName(){
        return PIFunctionUnitParent::prepareName() + "_" + std::to_string(__id);
    }

private:
    std::set<size_t> argumentsActual;
    size_t __id;
};

PIFunction::PIFunction(PIFSignature&& sig, size_t id, TargetInterpretation* target)
    : InterpretationFunction(sig.declaration, target), signatureInstance(move(sig))
{
    const FunctionInterpretationData&  functionData = FunctionInterpretationHelper::getSignature(signatureInstance.declaration);

    std::set<size_t> argumentsActual;
    for (size_t no=0, size=functionData.signature.size(); no < size; ++no){
        if (functionData.signature.at(no) != INTR_ONLY){
            argumentsActual.insert(no);
        }
    }

    functionUnit = new PIFunctionUnit(signatureInstance.declaration, move(argumentsActual), id, target->pass);
    CodeScope* entry = signatureInstance.declaration->__entry;
    auto entryUnit = Decorators<CachedScopeDecoratorTag>::getInterface<>(functionUnit->getEntry());
    InterpretationScope* entryIntrp = InterpretationFunction::getScope(entry);

    for(size_t no=0, sigNo=0, size = entry->__bindings.size(); no < size; ++no){
        if (functionData.signature.at(no) == INTR_ONLY){
            entryIntrp->overrideBinding(signatureInstance.bindings[sigNo], entry->__bindings[no]);

            VNameId argId = entry->__identifiers.at(entry->__bindings[no]);
            Symbol argSymbol{ScopedSymbol{argId, versions::VERSION_NONE}, entry};
            entryUnit->overrideDeclaration(argSymbol, Expression(signatureInstance.bindings[sigNo]));
            ++sigNo;
        }
    }
}

llvm::Function*
PIFunction::compile(){
    llvm::Function* raw = functionUnit->compile();

    return raw;
}

bool operator<(const PIFSignature& lhs, const PIFSignature& rhs){
    if (lhs.declaration.id() != rhs.declaration.id()) {
        return lhs.declaration.id() < rhs.declaration.id();
    }

    return lhs.bindings < rhs.bindings;
}

bool operator<(const PIFSignature& lhs, PIFunction* const rhs){
    return lhs < rhs->signatureInstance;
}

bool operator<(PIFunction* const lhs, const PIFSignature& rhs){
    return lhs->signatureInstance < rhs;
}

}}
