#ifndef COMPILEPASS_H
#define COMPILEPASS_H

#include "abstractpass.h"

#include "llvm/IR/Function.h"

namespace xreate {
    class ClaspLayer;
    class CompilePass;
    class LLVMLayer;
    
    namespace adhoc{
        class AdhocScheme;
    }
    
    namespace context{
        class ContextQuery;
        class LateContextCompiler2;
    }
    
    namespace interpretation{
        class TargetInterpretation;
    }
}

namespace xreate { namespace compilation {

class ICodeScopeUnit;
class IFunctionUnit;
class TransformationsManager;

struct Context{
    ICodeScopeUnit* scope;
    IFunctionUnit* function;
    CompilePass* pass;
};

class CallStatement {
public:
    virtual llvm::Value* operator() (std::vector<llvm::Value *>&& args, const std::string& hintDecl="") = 0;
};

class CallStatementRaw: public CallStatement{
public:
    CallStatementRaw(llvm::Function* callee, LLVMLayer* l)
        : __callee(callee), __calleeTy(callee->getFunctionType()), llvm(l) {}
    CallStatementRaw(llvm::Value* callee, llvm::FunctionType* ty, LLVMLayer* l)
        : __callee(callee), __calleeTy(ty), llvm(l) {}
    llvm::Value* operator() (std::vector<llvm::Value *>&& args, const std::string& hintDecl="");
        
private:
    llvm::Value* __callee;        
    llvm::FunctionType* __calleeTy;
    
    LLVMLayer* llvm;
};

class ICodeScopeUnit{
public:
    CompilePass* const pass;
    IFunctionUnit* const function;
    CodeScope* const scope;
    
    ICodeScopeUnit(CodeScope* codeScope, IFunctionUnit* f, CompilePass* compilePass);
    virtual ~ICodeScopeUnit();

    virtual llvm::Value* compile(const std::string& hintBlockDecl="")=0;
    virtual llvm::Value* processSymbol(const Symbol& s, std::string hintRetVar="")=0;
    virtual llvm::Value* process(const Expression& expr, const std::string& hintVarDecl="")=0;
    
    virtual void bindArg(llvm::Value* value, std::string&& alias)=0;
    virtual void bindArg(llvm::Value* value, const ScopedSymbol& s)=0;

protected:
    virtual CallStatement* findFunction(const std::string& callee)=0;
};

class BasicCodeScopeUnit: public ICodeScopeUnit{
public:
    BasicCodeScopeUnit(CodeScope* codeScope, IFunctionUnit* f, CompilePass* compilePass);

    llvm::Value* processSymbol(const Symbol& s, std::string hintRetVar="") override;
    llvm::Value* process(const Expression& expr, const std::string& hintVarDecl="") override;
    llvm::Value* compile(const std::string& hintBlockDecl="") override;
    
protected:
    CallStatement* findFunction(const std::string& callee) override;
};

class IFunctionUnit{
public:
   IFunctionUnit(ManagedFnPtr f, CompilePass* p): function(f), pass(p) {}
   virtual ~IFunctionUnit();

    llvm::Function* compile();

    ICodeScopeUnit* getEntry();
    ICodeScopeUnit* getScopeUnit(CodeScope* scope);
    ICodeScopeUnit* getScopeUnit(ManagedScpPtr scope);

    ManagedFnPtr function;
    llvm::Function* raw = nullptr;
    
protected:
    CompilePass* pass=nullptr;
    
    virtual std::string prepareName() = 0;
    virtual std::vector<llvm::Type*>  prepareArguments() = 0;
    virtual llvm::Type* prepareResult() = 0;
    virtual llvm::Function::arg_iterator prepareBindings() = 0;
    
private:
    std::map<CodeScope*, std::weak_ptr<ICodeScopeUnit>> __scopes;
    std::list<std::shared_ptr<ICodeScopeUnit>> __orphanedScopes;
};

class BasicFunctionUnit: public IFunctionUnit{
public:
    BasicFunctionUnit(ManagedFnPtr f, CompilePass* p)
    : IFunctionUnit(f, p) {}

protected:
    std::string prepareName() override;
    virtual std::vector<llvm::Type*>  prepareArguments() override;
    virtual llvm::Type* prepareResult() override;
    virtual llvm::Function::arg_iterator prepareBindings() override;
};
} // end of namespace compilation

class CompilePass : public AbstractPass<void> {
    friend class context::LateContextCompiler2;
    friend class compilation::BasicCodeScopeUnit;
    friend class compilation::IFunctionUnit;

public:
    compilation::TransformationsManager* managerTransformations;
    interpretation::TargetInterpretation* targetInterpretation;
    
    CompilePass(PassManager* manager): AbstractPass<void>(manager) {}
    compilation::IFunctionUnit* getFunctionUnit(const ManagedFnPtr& function);
    void run() override;
    llvm::Function* getEntryFunction();
    static void prepareQueries(ClaspLayer* clasp);
    
public:
    virtual compilation::IFunctionUnit* buildFunctionUnit(const ManagedFnPtr& function)=0;
    virtual compilation::ICodeScopeUnit* buildCodeScopeUnit(CodeScope* scope, compilation::IFunctionUnit* function)=0;
    
private:
    //TODO free `functions` in destructor
    std::map<unsigned int, compilation::IFunctionUnit*> functions;
    llvm::Function* entry = 0;

    context::ContextQuery* queryContext;    
};

namespace compilation{
template<class FUNCTION_DECORATOR=void, class SCOPE_DECORATOR=void>
class CompilePassCustomDecorators: public ::xreate::CompilePass{
public:
    CompilePassCustomDecorators(PassManager* manager): ::xreate::CompilePass(manager) {}
    
    virtual compilation::IFunctionUnit* buildFunctionUnit(const ManagedFnPtr& function) override{
        return new FUNCTION_DECORATOR(function, this);
    }
    
    virtual compilation::ICodeScopeUnit* buildCodeScopeUnit(CodeScope* scope, IFunctionUnit* function) override{
        return new SCOPE_DECORATOR(scope, function, this);
    }        
};

template<>
compilation::IFunctionUnit* 
CompilePassCustomDecorators<void, void>::buildFunctionUnit(const ManagedFnPtr& function);

template<>
compilation::ICodeScopeUnit*
CompilePassCustomDecorators<void, void>::buildCodeScopeUnit(CodeScope* scope, IFunctionUnit* function);

}} //end of namespace xreate::compilation

#endif // COMPILEPASS_H
