//===-- ClangASTImporter.h --------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_ClangASTImporter_h_
#define liblldb_ClangASTImporter_h_

// C Includes
// C++ Includes
#include <map>
#include <memory>
#include <set>
#include <vector>

// Other libraries and framework includes
#include "clang/AST/ASTImporter.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/FileSystemOptions.h"

// Project includes
#include "lldb/lldb-types.h"
#include "lldb/Symbol/CompilerDeclContext.h"

namespace lldb_private {
    
class ClangASTMetrics
{
public:
    static void DumpCounters (Log *log);
    static void ClearLocalCounters ()
    {
        local_counters = { 0, 0, 0, 0, 0, 0 };
    }
    
    static void RegisterVisibleQuery ()
    {
        ++global_counters.m_visible_query_count;
        ++local_counters.m_visible_query_count;
    }
    
    static void RegisterLexicalQuery ()
    {
        ++global_counters.m_lexical_query_count;
        ++local_counters.m_lexical_query_count;
    }
    
    static void RegisterLLDBImport ()
    {
        ++global_counters.m_lldb_import_count;
        ++local_counters.m_lldb_import_count;
    }
    
    static void RegisterClangImport ()
    {
        ++global_counters.m_clang_import_count;
        ++local_counters.m_clang_import_count;
    }
    
    static void RegisterDeclCompletion ()
    {
        ++global_counters.m_decls_completed_count;
        ++local_counters.m_decls_completed_count;
    }
    
    static void RegisterRecordLayout ()
    {
        ++global_counters.m_record_layout_count;
        ++local_counters.m_record_layout_count;
    }
    
private:
    struct Counters
    {
        uint64_t    m_visible_query_count;
        uint64_t    m_lexical_query_count;
        uint64_t    m_lldb_import_count;
        uint64_t    m_clang_import_count;
        uint64_t    m_decls_completed_count;
        uint64_t    m_record_layout_count;
    };
    
    static Counters global_counters;
    static Counters local_counters;
    
    static void DumpCounters (Log *log, Counters &counters);
};

class ClangASTImporter 
{
public:
    ClangASTImporter () :
        m_file_manager(clang::FileSystemOptions())
    {
    }
    
    clang::QualType
    CopyType (clang::ASTContext *dst_ctx,
              clang::ASTContext *src_ctx,
              clang::QualType type);
    
    lldb::opaque_compiler_type_t
    CopyType (clang::ASTContext *dst_ctx,
              clang::ASTContext *src_ctx,
              lldb::opaque_compiler_type_t type);

    CompilerType
    CopyType (ClangASTContext &dst,
              const CompilerType &src_type);

    clang::Decl *
    CopyDecl (clang::ASTContext *dst_ctx,
              clang::ASTContext *src_ctx,
              clang::Decl *decl);
    
    lldb::opaque_compiler_type_t
    DeportType (clang::ASTContext *dst_ctx,
                clang::ASTContext *src_ctx,
                lldb::opaque_compiler_type_t type);
    
    clang::Decl *
    DeportDecl (clang::ASTContext *dst_ctx,
                clang::ASTContext *src_ctx,
                clang::Decl *decl);
    
    void
    CompleteDecl (clang::Decl *decl);
        
    bool
    CompleteTagDecl (clang::TagDecl *decl);
    
    bool
    CompleteTagDeclWithOrigin (clang::TagDecl *decl, clang::TagDecl *origin);
    
    bool
    CompleteObjCInterfaceDecl (clang::ObjCInterfaceDecl *interface_decl);

    bool
    CompleteAndFetchChildren (clang::QualType type);

    bool
    RequireCompleteType (clang::QualType type);
    
    bool
    ResolveDeclOrigin (const clang::Decl *decl, clang::Decl **original_decl, clang::ASTContext **original_ctx)
    {
        DeclOrigin origin = GetDeclOrigin(decl);
        
        if (original_decl)
            *original_decl = origin.decl;
        
        if (original_ctx)
            *original_ctx = origin.ctx;
        
        return origin.Valid();
    }
    
    void
    SetDeclOrigin (const clang::Decl *decl, clang::Decl *original_decl);
    
    ClangASTMetadata *
    GetDeclMetadata (const clang::Decl *decl);
    
    //
    // Namespace maps
    //
    
    typedef std::vector < std::pair<lldb::ModuleSP, CompilerDeclContext> > NamespaceMap;
    typedef std::shared_ptr<NamespaceMap> NamespaceMapSP;
    
    void RegisterNamespaceMap (const clang::NamespaceDecl *decl, 
                               NamespaceMapSP &namespace_map);
                           
    NamespaceMapSP GetNamespaceMap (const clang::NamespaceDecl *decl);
    
    void BuildNamespaceMap (const clang::NamespaceDecl *decl);
    
    //
    // Completers for maps
    //
    
    class MapCompleter 
    {
    public:
        virtual ~MapCompleter ();
        
        virtual void CompleteNamespaceMap (NamespaceMapSP &namespace_map,
                                           const ConstString &name,
                                           NamespaceMapSP &parent_map) const = 0;
    };
    
    void InstallMapCompleter (clang::ASTContext *dst_ctx, MapCompleter &completer)
    {
        ASTContextMetadataSP context_md;
        ContextMetadataMap::iterator context_md_iter = m_metadata_map.find(dst_ctx);
        
        if (context_md_iter == m_metadata_map.end())
        {
            context_md = ASTContextMetadataSP(new ASTContextMetadata(dst_ctx));
            m_metadata_map[dst_ctx] = context_md;
        }
        else
        {
            context_md = context_md_iter->second;
        }
                
        context_md->m_map_completer = &completer;
    }
    
    void ForgetDestination (clang::ASTContext *dst_ctx);
    void ForgetSource (clang::ASTContext *dst_ctx, clang::ASTContext *src_ctx);

private:
    struct DeclOrigin 
    {
        DeclOrigin () :
            ctx(nullptr),
            decl(nullptr)
        {
        }
        
        DeclOrigin (clang::ASTContext *_ctx,
                    clang::Decl *_decl) :
            ctx(_ctx),
            decl(_decl)
        {
        }
        
        DeclOrigin (const DeclOrigin &rhs)
        {
            ctx = rhs.ctx;
            decl = rhs.decl;
        }
        
        void operator= (const DeclOrigin &rhs)
        {
            ctx = rhs.ctx;
            decl = rhs.decl;
        }
        
        bool 
        Valid ()
        {
            return (ctx != nullptr || decl != nullptr);
        }
        
        clang::ASTContext  *ctx;
        clang::Decl        *decl;
    };
    
    typedef std::map<const clang::Decl *, DeclOrigin>   OriginMap;
    
    class Minion : public clang::ASTImporter
    {
    public:
        Minion (ClangASTImporter &master,
                clang::ASTContext *target_ctx,
                clang::ASTContext *source_ctx) :
            clang::ASTImporter(*target_ctx,
                               master.m_file_manager,
                               *source_ctx,
                               master.m_file_manager,
                               true /*minimal*/),
            m_decls_to_deport(nullptr),
            m_decls_already_deported(nullptr),
            m_master(master),
            m_source_ctx(source_ctx)
        {
        }
        
        // A call to "InitDeportWorkQueues" puts the minion into deport mode.
        // In deport mode, every copied Decl that could require completion is
        // recorded and placed into the decls_to_deport set.
        //
        // A call to "ExecuteDeportWorkQueues" completes all the Decls that
        // are in decls_to_deport, adding any Decls it sees along the way that
        // it hasn't already deported.  It proceeds until decls_to_deport is
        // empty.
        //
        // These calls must be paired.  Leaving a minion in deport mode or
        // trying to start deport minion with a new pair of queues will result
        // in an assertion failure.
        
        void InitDeportWorkQueues (std::set<clang::NamedDecl *> *decls_to_deport,
                                   std::set<clang::NamedDecl *> *decls_already_deported);
        void ExecuteDeportWorkQueues ();
        
        void ImportDefinitionTo (clang::Decl *to, clang::Decl *from);
        
        clang::Decl *Imported(clang::Decl *from, clang::Decl *to) override;
        
        clang::Decl *GetOriginalDecl(clang::Decl *To) override;
        
        std::set<clang::NamedDecl *>   *m_decls_to_deport;
        std::set<clang::NamedDecl *>   *m_decls_already_deported;
        ClangASTImporter               &m_master;
        clang::ASTContext              *m_source_ctx;
    };
    
    typedef std::shared_ptr<Minion> MinionSP;
    typedef std::map<clang::ASTContext *, MinionSP> MinionMap;
    typedef std::map<const clang::NamespaceDecl *, NamespaceMapSP> NamespaceMetaMap;
    
    struct ASTContextMetadata
    {
        ASTContextMetadata(clang::ASTContext *dst_ctx) :
            m_dst_ctx (dst_ctx),
            m_minions (),
            m_origins (),
            m_namespace_maps (),
            m_map_completer (nullptr)
        {
        }
        
        clang::ASTContext      *m_dst_ctx;
        MinionMap               m_minions;
        OriginMap               m_origins;
        
        NamespaceMetaMap        m_namespace_maps;
        MapCompleter           *m_map_completer;
    };
    
    typedef std::shared_ptr<ASTContextMetadata> ASTContextMetadataSP;    
    typedef std::map<const clang::ASTContext *, ASTContextMetadataSP> ContextMetadataMap;
    
    ContextMetadataMap m_metadata_map;
    
    ASTContextMetadataSP
    GetContextMetadata (clang::ASTContext *dst_ctx)
    {
        ContextMetadataMap::iterator context_md_iter = m_metadata_map.find(dst_ctx);
        
        if (context_md_iter == m_metadata_map.end())
        {
            ASTContextMetadataSP context_md = ASTContextMetadataSP(new ASTContextMetadata(dst_ctx));
            m_metadata_map[dst_ctx] = context_md;
            return context_md;
        }
        else
        {
            return context_md_iter->second;
        }
    }
    
    ASTContextMetadataSP
    MaybeGetContextMetadata (clang::ASTContext *dst_ctx)
    {
        ContextMetadataMap::iterator context_md_iter = m_metadata_map.find(dst_ctx);

        if (context_md_iter != m_metadata_map.end())
            return context_md_iter->second;
        else
            return ASTContextMetadataSP();
    }
    
    MinionSP
    GetMinion (clang::ASTContext *dst_ctx, clang::ASTContext *src_ctx)
    {
        ASTContextMetadataSP context_md = GetContextMetadata(dst_ctx);
        
        MinionMap &minions = context_md->m_minions;
        MinionMap::iterator minion_iter = minions.find(src_ctx);
        
        if (minion_iter == minions.end())
        {
            MinionSP minion = MinionSP(new Minion(*this, dst_ctx, src_ctx));
            minions[src_ctx] = minion;
            return minion;
        }
        else
        {
            return minion_iter->second;
        }       
    }
    
    DeclOrigin
    GetDeclOrigin (const clang::Decl *decl);
        
    clang::FileManager      m_file_manager;
};
    
} // namespace lldb_private

#endif // liblldb_ClangASTImporter_h_
