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

#include "compilation/targetinterpretation.h"
#include "pass/interpretationpass.h"

using namespace std;

namespace xreate{ namespace compilation {

    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;
}

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 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];
            const Symbol& symbolEl = scopeBody->findSymbol(nameEl);
            const std::string& idAccum = expression.bindings[1];
            llvm::Value* rawAccum = context.scope->process(expression.getOperands()[1]);
            compilation::CodeScopeUnit* unitBody = context.function->getScopeUnit(scopeBody);
            InterpretationScope* intrBody = function->getScope(scopeBody);
            const std::vector<Expression> elementsInput= exprInput.getOperands();

            for (size_t i=0; i<elementsInput.size(); ++i){
                Expression exprElement  = elementsInput[i];

                unitBody->reset();

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

                rawAccum = unitBody->compile();
            }

            return rawAccum;
        }

        default: break;
    }

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

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

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

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

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

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

        case Expression::IDENT:{
            const std::string &ident = expression.getValueString();

            Symbol s = scope->findSymbol(ident);
            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);
            InterpretatonFunction* 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::INDEX: {
            const Expression& exprKey = process(expression.operands[0]);
            const Expression& exprData = processSymbol(scope->findSymbol(expression.getValueString()));


            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->bindArg(exprInput.getOperands()[i], argEl);
                body->bindArg(accum, argAccum);

                accum = body->processScope();
            }

            return accum;
        }

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

        default: break;
    }

    return expression;
}

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

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

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

    return body->processScope();
}

InterpretatonFunction*
TargetInterpretation::getFunction(const PIFSignature& sig){
    return __functions.find(sig);
}

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

llvm::Value*
TargetInterpretation::transform(const Expression& expression, llvm::Value* raw, const Context& ctx){
    return raw;
}

Expression
TargetInterpretation::transform(const Expression& expression, const Context& ctx){
    return transformContext(ctx)->process(expression);
}

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

bool
TargetInterpretation::isAcceptable(const Expression& expression){
    const InterpretationData& data = Attachments::get<Expression, InterpretationData>(expression, {BOTH, NONE});

    return (data.resolution == INTR_ONLY || data.op != InterpretationOperator::NONE);
}

}}

        //Partial function interpretation
llvm::Value*
InterpretationScope::compilePartialFnCall(const Expression& expression, const Context& context){
    const std::string &fnName = expression.getValueString();
    ManagedFnPtr fnAst = this->function->man->ast->findFunction(fnName);
    context.pass->getFunctionUnit(fnAst)

    intrBody->bindArg(exprElement, nameEl);
    unitBody->overrideDeclaration(symbolEl, move(exprElement));
}

class PartialInterpretationScopeDecorator(){
    void compile(){

    }

    void findFunction(){

    }
};

template<class Parent>
class PartialInterpretationFunctionDecorator: public Parent{

protected:
    void recognizeArguments(){
        argsReal.reserve(entry->__bindings.size());

        for(size_t no=0, size=entry->__bindings.size(); no < size; ++no){
            const std::string& argName = entry->__bindings[no];
            const Expression& arg = entry->findDeclaration(entry->findSymbol(argName));

            InterpretationResolution res = recognizeTags(arg.tags);
            if (res != INTR_ONLY){
                argsReal.push_back(arg);
            }
        }
    }

    std::vector<llvm::Type*> prepareArguments(){
        std::vector<llvm::Type*> signature;

        for(size_t no=0, size=argsReal.size(); no < size; ++no){
                signature.push_back(llvm->toLLVMType(ast->expandType(argsReal[no].type)));
        }
    }

    void prepareBindings(){
        // bindings from argsReal
    }

private:
    std::vector<Expression> argsReal;
};

template<class Parent>
class PartialInterpretationCallStatement: public Parent {
    PartialInterpretationCallStatement(){

    }
}