/* 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    versions.h
 * \brief   Versions-aware compilation support
 */

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

namespace xreate {
    class CompilePass;

namespace compilation {
    class ICodeScopeUnit;
    class IFunctionUnit;
}

namespace versions{
    
/**\brief Versioned variables code scope level compilation support
 * 
 * Decorates \ref xreate::compilation::ICodeScopeUnit to enable versioned variables compilation.
 * An order of variables computation is determined by \ref 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;
    }

};

/**
 * \brief A Versions-aware function level decorator. Not used currently. 
 */
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