/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * versions.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on January 21, 2017, 1:24 PM
 */

/**
 * \file    
 * \brief   CodeScope's Decorator to support Versions
 */

#include "pass/versionspass.h"
#include "pass/compilepass.h"
#include "llvmlayer.h"

namespace xreate {
    class CompilePass;

namespace compilation {
    class ICodeScopeUnit;
    class IFunctionUnit;
}

namespace versions{
    
/**\brief Enables compilation of code with versioned variables
 * \details Dictates order of computation determined by VersionsPass
 * \extends xreate::compilation::ICodeScopeUnit
 * \sa VersionsPass, VersionsGraph
 */    
template<class Parent>
class VersionsScopeDecorator: public Parent{
    typedef VersionsScopeDecorator<Parent> SELF;
    
public:
    VersionsScopeDecorator(const CodeScope* const codeScope, compilation::IFunctionUnit* f, CompilePass* compilePass): Parent(codeScope, f, compilePass){}

    virtual llvm::Value* processSymbol(const Symbol& s, std::string hintSymbol=""){
        if (Attachments::exists<VersionImposedDependency>(s)){
            const std::list<Symbol> dependencies = Attachments::get<VersionImposedDependency>(s);

            for(const Symbol& symbolDependent: dependencies){
                processSymbol(symbolDependent);
            }
        }

        llvm::Value* result = Parent::processSymbol(s, hintSymbol);
        
        if (s.identifier.version == VERSION_INIT){
            llvm::Value* storage = SELF::processIntrinsicInit(result->getType(), hintSymbol);
            setSymbolStorage(s, storage);
            
            processIntrinsicCopy(result, storage);
            return compilation::ICodeScopeUnit::pass->man->llvm->builder.CreateLoad(storage);
            
        } else if (s.identifier.version != VERSION_NONE){
            Symbol symbolInitVersion = getSymbolInitVersion(s);
            
           llvm::Value* storage = getSymbolStorage(symbolInitVersion);
           processIntrinsicCopy(result, storage);
           
           return compilation::ICodeScopeUnit::pass->man->llvm->builder.CreateLoad(storage);
        }
        
        return result;
    }
    
    llvm::Value* 
    processIntrinsicInit(llvm::Type* typeStorage, const std::string& hintVarDecl=""){
        LLVMLayer* llvm = compilation::ICodeScopeUnit::pass->man->llvm;
        
        llvm::IntegerType* tyInt = llvm::Type::getInt32Ty(llvm->llvmContext);
        llvm::ConstantInt* constOne = llvm::ConstantInt::get(tyInt, 1, false);
        
        return llvm->builder.CreateAlloca(typeStorage, constOne, hintVarDecl);
    }
    
    void
    processIntrinsicCopy(llvm::Value* value, llvm::Value* storage){
        compilation::ICodeScopeUnit::pass->man->llvm->builder.CreateStore(value, storage);
    }
    
private:
    std::map<Symbol, llvm::Value*> __symbolStorage;
    
        static Symbol
    getSymbolInitVersion(const Symbol& s){
        return Symbol{ScopedSymbol{s.identifier.id, VERSION_INIT}, s.scope};
    }
    
    llvm::Value*
    getSymbolStorage(const Symbol& s){
        return __symbolStorage.at(s);
    }
    
    void setSymbolStorage(const Symbol& s, llvm::Value* storage){
        __symbolStorage[s] = storage;
    }

};

template<class Parent>
class VersionedFunctionDecorator : public Parent {
public:
    VersionedFunctionDecorator(ManagedFnPtr f, CompilePass* p)
    : Parent(f, p){}

protected:
    std::vector<llvm::Type*> prepareArguments() {
        std::vector<llvm::Type*>&& arguments = Parent::prepareArguments();

        return arguments;
    }
};
} }  //end of namespace xreate::versions

//    llvm::Value*
//    processIntrinsicInitAndCopy(){
//         
//    }

//llvm::Value*
//process(const Expression& expr, const std::string& hintVarDecl){
//    case Operator::CALL_INTRINSIC: {
//        enum INRINSIC{INIT, COPY};
//        
//        const ExpandedType& typSymbol = pass->man->root->expandType(expr.type);
//
//        INTRINSIC op = (INTRINSIC) expr.getValueDouble();
//
//        switch (op){
//            case INIT: {
//                llvm::Type* typSymbolRaw = l.toLLVMType(typSymbol);
//                
//
//                return storage;
//            }
//
//            case COPY: {
//                llvm::Type* typSymbolRaw = l.toLLVMType(typSymbol);
//                llvm::value* valueOriginal = process(expr.getOperands()[0], hintVarDecl);
//                llvm::Value* storage = l.builder.CreateAlloca(typSymbolRaw, constOne, hintVarDecl);
//                llvm::Value* valueCopy = l.builder.CreateStore(valueOriginal, storage);
//
//                return valueCopy;
//            }
//        }
//        return;
//    }
//}
//};
