#ifndef GOOSE_CODEGEN_MODULE_H
#define GOOSE_CODEGEN_MODULE_H

namespace llvm
{
    class TargetMachine;
}

namespace goose::codegen
{
    class Module
    {
      public:
        Module( const string& name );

        bool setupTarget();
        void dumpAsLlvmIr( const string& filename ) const;

        ptr< codegen::Function > getOrCreateFunc( const Context& c, const builtins::Func& func );
        ptr< codegen::Function > getOrCreateFunc( const Context& c, const builtins::Func& func,
            const string& name, llvm::Function::LinkageTypes linkageType );

        void runOptimizationPasses();
        bool emitToFile( const string& filename, llvm::CodeGenFileType type );

      private:
        struct State
        {
            State( const Context& c, size_t numVars, llvm::Function* f ) :
                context( c ),
                llvmFunc( f ),
                temporaries( make_shared< storage_type >( numVars ) )
            {
            }

            const Context& context;

            llvm::Function* llvmFunc = nullptr;
            llvm::BasicBlock* allocaBasicBlock = nullptr;
            llvm::AllocaInst* lastEmittedAlloca = nullptr;

            using storage_type = cir::TempStorage< ptr< codegen::Value > >;
            ptr< storage_type > temporaries;

            unordered_map< const cir::BasicBlock*, llvm::BasicBlock* > bbMap;

            Stack stack;
        };

        static llvm::BasicBlock* GetOrCreateLLVMBB( State& st, const ptr< cir::BasicBlock >& pBB );

        ptr< codegen::Function > buildFuncProto( const Context& c, const builtins::Func& func,
            const string& name, llvm::Function::LinkageTypes linkageType );

        llvm::Function* getCurrentFunction() const
        {
            return m_llvmBuilder.GetInsertBlock()->getParent();
        }

        llvm::BasicBlock* buildCFG(
            State& st, llvm::Function* llvmFunc, const ptr< cir::CFG >& pCFG );
        llvm::BasicBlock* buildBasicBlock( State& st, const ptr< cir::BasicBlock >& pBB );

        ptr< codegen::Value > buildValue( State& st, const eir::Value& val );
        ptr< codegen::Value > buildConstant( State& st, const eir::Value& val );
        ptr< codegen::Value > buildConstantPointer(
            State& st, const builtins::PointerType& ptType, const eir::Value& v );

        template< typename F > bool buildUnaryInstruction( State& st, F&& impl );

        template< typename F > bool buildBinaryInstruction( State& st, F&& impl );

        bool buildInstruction( State& st, const cir::InstrSeq& is );
        bool buildInstruction( State& st, const cir::Instruction& instr );
        bool buildInstruction( State& st, const monostate& );
        bool buildInstruction( State& st, const cir::Call& call );
        bool buildInstruction( State& st, const cir::Constant& ct );
        bool buildInstruction( State& st, const cir::VarAddr& va );
        bool buildInstruction( State& st, const cir::TempAddr& ta );
        bool buildInstruction( State& st, const cir::Select& s );
        bool buildInstruction( State& st, const cir::CreateTemporary& ct );
        bool buildInstruction( State& st, const cir::GetTemporary& gt );
        bool buildInstruction( State& st, const cir::AllocVar& av );
        bool buildInstruction( State& st, const cir::Load& load );
        bool buildInstruction( State& st, const cir::Store& store );
        bool buildInstruction( State& st, const cir::Phi& p );

        bool buildInstruction( State& st, const cir::Not& uo );
        bool buildInstruction( State& st, const cir::And& bo );
        bool buildInstruction( State& st, const cir::Or& bo );
        bool buildInstruction( State& st, const cir::Xor& bo );
        bool buildInstruction( State& st, const cir::Implies& bo );
        bool buildInstruction( State& st, const cir::IsPrefixOf& bo );

        bool buildInstruction( State& st, const cir::Shl& bo );
        bool buildInstruction( State& st, const cir::LShr& bo );
        bool buildInstruction( State& st, const cir::AShr& bo );

        bool buildInstruction( State& st, const cir::Add& bo );
        bool buildInstruction( State& st, const cir::Sub& bo );
        bool buildInstruction( State& st, const cir::Mul& bo );
        bool buildInstruction( State& st, const cir::UDiv& bo );
        bool buildInstruction( State& st, const cir::SDiv& bo );
        bool buildInstruction( State& st, const cir::URem& bo );
        bool buildInstruction( State& st, const cir::SRem& bo );

        bool buildInstruction( State& st, const cir::Eq& bo );
        bool buildInstruction( State& st, const cir::Neq& bo );
        bool buildInstruction( State& st, const cir::UGT& bo );
        bool buildInstruction( State& st, const cir::UGE& bo );
        bool buildInstruction( State& st, const cir::ULT& bo );
        bool buildInstruction( State& st, const cir::ULE& bo );
        bool buildInstruction( State& st, const cir::SGT& bo );
        bool buildInstruction( State& st, const cir::SGE& bo );
        bool buildInstruction( State& st, const cir::SLT& bo );
        bool buildInstruction( State& st, const cir::SLE& bo );

        bool buildInstruction( State& st, const cir::Assert& );
        bool buildInstruction( State& st, const cir::Placeholder& );
        bool buildInstruction( State& st, const cir::PHOverrideSet& );
        bool buildInstruction( State& st, const cir::PHOverrideClear& );
        bool buildInstruction( State& st, const cir::GhostCall& );
        bool buildInstruction( State& st, const cir::ForAllSetup& );
        bool buildInstruction( State& st, const cir::ForAll& );
        bool buildInstruction( State& st, const cir::PushType& );
        bool buildInstruction( State& st, const cir::PushStringId& );
        bool buildInstruction( State& st, const cir::CallCheck& );
        bool buildInstruction( State& st, const cir::InlineCall& );

        bool buildTerminator( State& st, const cir::Terminator& terminator );
        bool buildTerminator( State& st, const cir::RetVoid& r );
        bool buildTerminator( State& st, const cir::Ret& r );
        bool buildTerminator( State& st, const cir::Branch& b );
        bool buildTerminator( State& st, const cir::CondBranch& cb );
        bool buildTerminator( State& st, const cir::GhostBranch& gb );

        template< typename T > bool buildTerminator( State& st, const T& t ) { return false; }

        ptr< codegen::Value > buildAlloca( State& st, const codegen::Type* type );

        llvm::Module m_llvmModule;
        llvm::DataLayout m_dataLayout;
        llvm::IRBuilder<> m_llvmBuilder;
        llvm::TargetMachine* m_targetMachine = nullptr;

        unordered_map< string, llvm::GlobalVariable* > m_strings;
        uint32_t m_nextAggregateID = 0;
    };
} // namespace goose::codegen

#endif
