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

#include "AppleObjCDeclVendor.h"

#include "Plugins/ExpressionParser/Clang/ASTDumper.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/Module.h"
#include "lldb/Symbol/ClangExternalASTSourceCommon.h"
#include "lldb/Symbol/ClangUtil.h"
#include "lldb/Target/ObjCLanguageRuntime.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"

#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"

using namespace lldb_private;

class lldb_private::AppleObjCExternalASTSource : public ClangExternalASTSourceCommon
{
public:
    AppleObjCExternalASTSource (AppleObjCDeclVendor &decl_vendor) :
        m_decl_vendor(decl_vendor)
    {
    }

    bool
    FindExternalVisibleDeclsByName(const clang::DeclContext *decl_ctx, clang::DeclarationName name) override
    {
        static unsigned int invocation_id = 0;
        unsigned int current_id = invocation_id++;

        Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));  // FIXME - a more appropriate log channel?

        if (log)
        {
            log->Printf("AppleObjCExternalASTSource::FindExternalVisibleDeclsByName[%u] on (ASTContext*)%p Looking for %s in (%sDecl*)%p",
                        current_id,
                        static_cast<void*>(&decl_ctx->getParentASTContext()),
                        name.getAsString().c_str(), decl_ctx->getDeclKindName(),
                        static_cast<const void*>(decl_ctx));
        }

        do
        {
            const clang::ObjCInterfaceDecl *interface_decl = llvm::dyn_cast<clang::ObjCInterfaceDecl>(decl_ctx);

            if (!interface_decl)
                break;

            clang::ObjCInterfaceDecl *non_const_interface_decl = const_cast<clang::ObjCInterfaceDecl*>(interface_decl);

            if (!m_decl_vendor.FinishDecl(non_const_interface_decl))
                break;

            clang::DeclContext::lookup_result result = non_const_interface_decl->lookup(name);

            return (result.size() != 0);
        }
        while(0);

        SetNoExternalVisibleDeclsForName(decl_ctx, name);
        return false;
    }

    void
    CompleteType(clang::TagDecl *tag_decl) override
    {
        static unsigned int invocation_id = 0;
        unsigned int current_id = invocation_id++;

        Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));  // FIXME - a more appropriate log channel?

        if (log)
        {
            log->Printf("AppleObjCExternalASTSource::CompleteType[%u] on (ASTContext*)%p Completing (TagDecl*)%p named %s",
                        current_id,
                        static_cast<void*>(&tag_decl->getASTContext()),
                        static_cast<void*>(tag_decl),
                        tag_decl->getName().str().c_str());

            log->Printf("  AOEAS::CT[%u] Before:", current_id);
            ASTDumper dumper((clang::Decl*)tag_decl);
            dumper.ToLog(log, "    [CT] ");
        }

        if (log)
        {
            log->Printf("  AOEAS::CT[%u] After:", current_id);
            ASTDumper dumper((clang::Decl*)tag_decl);
            dumper.ToLog(log, "    [CT] ");
        }
        return;
    }

    void
    CompleteType(clang::ObjCInterfaceDecl *interface_decl) override
    {
        static unsigned int invocation_id = 0;
        unsigned int current_id = invocation_id++;

        Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));  // FIXME - a more appropriate log channel?

        if (log)
        {
            log->Printf("AppleObjCExternalASTSource::CompleteType[%u] on (ASTContext*)%p Completing (ObjCInterfaceDecl*)%p named %s",
                        current_id,
                        static_cast<void*>(&interface_decl->getASTContext()),
                        static_cast<void*>(interface_decl),
                        interface_decl->getName().str().c_str());

            log->Printf("  AOEAS::CT[%u] Before:", current_id);
            ASTDumper dumper((clang::Decl*)interface_decl);
            dumper.ToLog(log, "    [CT] ");
        }

        m_decl_vendor.FinishDecl(interface_decl);

        if (log)
        {
            log->Printf("  [CT] After:");
            ASTDumper dumper((clang::Decl*)interface_decl);
            dumper.ToLog(log, "    [CT] ");
        }
        return;
    }

    bool
    layoutRecordType(const clang::RecordDecl *Record, uint64_t &Size, uint64_t &Alignment,
                     llvm::DenseMap<const clang::FieldDecl *, uint64_t> &FieldOffsets,
                     llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> &BaseOffsets,
                     llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> &VirtualBaseOffsets) override
    {
        return false;
    }

    void
    StartTranslationUnit(clang::ASTConsumer *Consumer) override
    {
        clang::TranslationUnitDecl *translation_unit_decl = m_decl_vendor.m_ast_ctx.getASTContext()->getTranslationUnitDecl();
        translation_unit_decl->setHasExternalVisibleStorage();
        translation_unit_decl->setHasExternalLexicalStorage();
    }
private:
    AppleObjCDeclVendor                                    &m_decl_vendor;
};

AppleObjCDeclVendor::AppleObjCDeclVendor(ObjCLanguageRuntime &runtime) :
    DeclVendor(),
    m_runtime(runtime),
    m_ast_ctx(runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple().getTriple().c_str()),
    m_type_realizer_sp(m_runtime.GetEncodingToType())
{
    m_external_source = new AppleObjCExternalASTSource (*this);
    llvm::IntrusiveRefCntPtr<clang::ExternalASTSource> external_source_owning_ptr (m_external_source);
    m_ast_ctx.getASTContext()->setExternalSource(external_source_owning_ptr);
}

clang::ObjCInterfaceDecl*
AppleObjCDeclVendor::GetDeclForISA(ObjCLanguageRuntime::ObjCISA isa)
{
    ISAToInterfaceMap::const_iterator iter = m_isa_to_interface.find(isa);
    
    if (iter != m_isa_to_interface.end())
        return iter->second;
    
    clang::ASTContext *ast_ctx = m_ast_ctx.getASTContext();
    
    ObjCLanguageRuntime::ClassDescriptorSP descriptor = m_runtime.GetClassDescriptorFromISA(isa);
    
    if (!descriptor)
        return NULL;
    
    const ConstString &name(descriptor->GetClassName());
    
    clang::IdentifierInfo &identifier_info = ast_ctx->Idents.get(name.GetStringRef());

    clang::ObjCInterfaceDecl *new_iface_decl = clang::ObjCInterfaceDecl::Create(*ast_ctx,
                                                                                ast_ctx->getTranslationUnitDecl(),
                                                                                clang::SourceLocation(),
                                                                                &identifier_info,
                                                                                nullptr,
                                                                                nullptr);
    
    ClangASTMetadata meta_data;
    meta_data.SetISAPtr(isa);
    m_external_source->SetMetadata(new_iface_decl, meta_data);
    
    new_iface_decl->setHasExternalVisibleStorage();
    new_iface_decl->setHasExternalLexicalStorage();
    
    ast_ctx->getTranslationUnitDecl()->addDecl(new_iface_decl);
    
    m_isa_to_interface[isa] = new_iface_decl;
    
    return new_iface_decl;
}

class ObjCRuntimeMethodType
{
public:
    ObjCRuntimeMethodType (const char *types) : m_is_valid(false)
    {
        const char *cursor = types;
        enum ParserState {
            Start = 0,
            InType,
            InPos
        } state = Start;
        const char *type = NULL;
        int brace_depth = 0;
        
        uint32_t stepsLeft = 256;
        
        while (1)
        {
            if (--stepsLeft == 0)
            {
                m_is_valid = false;
                return;
            }
            
            switch (state)
            {
            case Start:
                {
                    switch (*cursor)
                    {
                    default:
                        state = InType;
                        type = cursor;
                        break;
                    case '\0':
                        m_is_valid = true;
                        return;
                    case '0': case '1': case '2': case '3': case '4':
                    case '5': case '6': case '7': case '8': case '9':
                        m_is_valid = false;
                        return;
                    }
                }
                break;
            case InType:
                {
                    switch (*cursor)
                    {
                    default:
                        ++cursor;
                        break;
                    case '0': case '1': case '2': case '3': case '4':
                    case '5': case '6': case '7': case '8': case '9':
                        if (!brace_depth)
                        {
                            state = InPos;
                            if (type)
                            {
                                m_type_vector.push_back(std::string(type, (cursor - type)));
                            }
                            else
                            {
                                m_is_valid = false;
                                return;
                            }
                            type = NULL;
                        }
                        else
                        {
                            ++cursor;
                        }
                        break;
                    case '[': case '{': case '(':
                        ++brace_depth;
                        ++cursor;
                        break;
                    case ']': case '}': case ')':
                        if (!brace_depth)
                        {
                            m_is_valid = false;
                            return;
                        }
                        --brace_depth;
                        ++cursor;
                        break;
                    case '\0':
                        m_is_valid = false;
                        return;
                    }
                }
                break;
            case InPos:
                {
                    switch (*cursor)
                    {
                    default:
                        state = InType;
                        type = cursor;
                        break;
                    case '0': case '1': case '2': case '3': case '4':
                    case '5': case '6': case '7': case '8': case '9':
                        ++cursor;
                        break;
                    case '\0':
                        m_is_valid = true;
                        return;
                    }
                }
                break;
            }
        }
    }
    
    clang::ObjCMethodDecl *BuildMethod (clang::ObjCInterfaceDecl *interface_decl, const char *name, bool instance, ObjCLanguageRuntime::EncodingToTypeSP type_realizer_sp)
    {
        if (!m_is_valid || m_type_vector.size() < 3)
            return NULL;
        
        clang::ASTContext &ast_ctx(interface_decl->getASTContext());
        
        clang::QualType return_qual_type;
        
        const bool isInstance = instance;
        const bool isVariadic = false;
        const bool isSynthesized = false;
        const bool isImplicitlyDeclared = true;
        const bool isDefined = false;
        const clang::ObjCMethodDecl::ImplementationControl impControl = clang::ObjCMethodDecl::None;
        const bool HasRelatedResultType = false;
        const bool for_expression = true;
        
        std::vector <clang::IdentifierInfo *> selector_components;
        
        const char *name_cursor = name;
        bool is_zero_argument = true;
        
        
        while (*name_cursor != '\0')
        {
            const char *colon_loc = strchr(name_cursor, ':');
            if (!colon_loc)
            {
                selector_components.push_back(&ast_ctx.Idents.get(llvm::StringRef(name_cursor)));
                break;
            }
            else
            {
                is_zero_argument = false;
                selector_components.push_back(&ast_ctx.Idents.get(llvm::StringRef(name_cursor, colon_loc - name_cursor)));
                name_cursor = colon_loc + 1;
            }
        }
        
        clang::Selector sel = ast_ctx.Selectors.getSelector(is_zero_argument ? 0 : selector_components.size(), selector_components.data());

        clang::QualType ret_type = ClangUtil::GetQualType(
            type_realizer_sp->RealizeType(interface_decl->getASTContext(), m_type_vector[0].c_str(), for_expression));

        if (ret_type.isNull())
            return NULL;
        
        clang::ObjCMethodDecl *ret = clang::ObjCMethodDecl::Create(ast_ctx,
                                                                   clang::SourceLocation(),
                                                                   clang::SourceLocation(),
                                                                   sel,
                                                                   ret_type,
                                                                   NULL,
                                                                   interface_decl,
                                                                   isInstance,
                                                                   isVariadic,
                                                                   isSynthesized,
                                                                   isImplicitlyDeclared,
                                                                   isDefined,
                                                                   impControl,
                                                                   HasRelatedResultType);
        
        std::vector <clang::ParmVarDecl*> parm_vars;
        
        for (size_t ai = 3, ae = m_type_vector.size();
             ai != ae;
             ++ai)
        {
            const bool for_expression = true;
            clang::QualType arg_type = ClangUtil::GetQualType(
                type_realizer_sp->RealizeType(ast_ctx, m_type_vector[ai].c_str(), for_expression));

            if (arg_type.isNull())
                return NULL; // well, we just wasted a bunch of time.  Wish we could delete the stuff we'd just made!

            parm_vars.push_back(clang::ParmVarDecl::Create(ast_ctx,
                                                           ret,
                                                           clang::SourceLocation(),
                                                           clang::SourceLocation(),
                                                           NULL,
                                                           arg_type,
                                                           NULL,
                                                           clang::SC_None,
                                                           NULL));
        }
        
        ret->setMethodParams(ast_ctx, llvm::ArrayRef<clang::ParmVarDecl*>(parm_vars), llvm::ArrayRef<clang::SourceLocation>());
        
        return ret;
    }
    
    explicit operator bool ()
    {
        return m_is_valid;
    }
    
    size_t
    GetNumTypes ()
    {
        return m_type_vector.size();
    }
    
    const char*
    GetTypeAtIndex (size_t idx)
    {
        return m_type_vector[idx].c_str();
    }
    
private:
    typedef std::vector <std::string> TypeVector;
    
    TypeVector  m_type_vector;
    bool        m_is_valid;
};

bool
AppleObjCDeclVendor::FinishDecl(clang::ObjCInterfaceDecl *interface_decl)
{
    Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));  // FIXME - a more appropriate log channel?
    
    ClangASTMetadata *metadata = m_external_source->GetMetadata(interface_decl);
    ObjCLanguageRuntime::ObjCISA objc_isa = 0;
    if (metadata)
     objc_isa = metadata->GetISAPtr();
    
    if (!objc_isa)
        return false;
    
    if (!interface_decl->hasExternalVisibleStorage())
        return true;
    
    interface_decl->startDefinition();
    
    interface_decl->setHasExternalVisibleStorage(false);
    interface_decl->setHasExternalLexicalStorage(false);
    
    ObjCLanguageRuntime::ClassDescriptorSP descriptor = m_runtime.GetClassDescriptorFromISA(objc_isa);
    
    if (!descriptor)
        return false;
    
    auto superclass_func = [interface_decl, this](ObjCLanguageRuntime::ObjCISA isa)
    {
        clang::ObjCInterfaceDecl *superclass_decl = GetDeclForISA(isa);
        
        if (!superclass_decl)
            return;
        
        FinishDecl(superclass_decl);
        clang::ASTContext *context = m_ast_ctx.getASTContext();
        interface_decl->setSuperClass(
                context->getTrivialTypeSourceInfo(context->getObjCInterfaceType(superclass_decl)));
    };
    
    auto instance_method_func = [log, interface_decl, this](const char *name, const char *types) -> bool
    {        
        if (!name || !types)
            return false; // skip this one

        ObjCRuntimeMethodType method_type(types);
        
        clang::ObjCMethodDecl *method_decl = method_type.BuildMethod (interface_decl, name, true, m_type_realizer_sp);
        
        if (log)
            log->Printf("[  AOTV::FD] Instance method [%s] [%s]", name, types);
        
        if (method_decl)
            interface_decl->addDecl(method_decl);
        
        return false;
    };
    
    auto class_method_func = [log, interface_decl, this](const char *name, const char *types) -> bool
    {
        if (!name || !types)
            return false; // skip this one
        
        ObjCRuntimeMethodType method_type(types);
        
        clang::ObjCMethodDecl *method_decl = method_type.BuildMethod (interface_decl, name, false, m_type_realizer_sp);
        
        if (log)
            log->Printf("[  AOTV::FD] Class method [%s] [%s]", name, types);
        
        if (method_decl)
            interface_decl->addDecl(method_decl);
        
        return false;
    };
    
    auto ivar_func = [log, interface_decl, this](const char *name, const char *type, lldb::addr_t offset_ptr, uint64_t size) -> bool
    {
        if (!name || !type)
            return false;
        
        const bool for_expression = false;
        
        if (log)
            log->Printf("[  AOTV::FD] Instance variable [%s] [%s], offset at %" PRIx64, name, type, offset_ptr);
        
        CompilerType ivar_type = m_runtime.GetEncodingToType()->RealizeType(m_ast_ctx, type, for_expression);
        
        if (ivar_type.IsValid())
        {
            clang::TypeSourceInfo * const type_source_info = nullptr;
            const bool is_synthesized = false;
            clang::ObjCIvarDecl *ivar_decl = clang::ObjCIvarDecl::Create(
                *m_ast_ctx.getASTContext(), interface_decl, clang::SourceLocation(), clang::SourceLocation(),
                &m_ast_ctx.getASTContext()->Idents.get(name), ClangUtil::GetQualType(ivar_type),
                type_source_info, // TypeSourceInfo *
                clang::ObjCIvarDecl::Public, 0, is_synthesized);

            if (ivar_decl)
            {
                interface_decl->addDecl(ivar_decl);
            }
        }
        
        return false;
    };
    
    if (log)
    {
        ASTDumper method_dumper ((clang::Decl*)interface_decl);
        
        log->Printf("[AppleObjCDeclVendor::FinishDecl] Finishing Objective-C interface for %s", descriptor->GetClassName().AsCString());
    }
    
    
    if (!descriptor->Describe(superclass_func,
                              instance_method_func,
                              class_method_func,
                              ivar_func))
        return false;
    
    if (log)
    {
        ASTDumper method_dumper ((clang::Decl*)interface_decl);
        
        log->Printf("[AppleObjCDeclVendor::FinishDecl] Finished Objective-C interface");
        
        method_dumper.ToLog(log, "  [AOTV::FD] ");
    }
    
    return true;
}

uint32_t
AppleObjCDeclVendor::FindDecls (const ConstString &name,
                                bool append,
                                uint32_t max_matches,
                                std::vector <clang::NamedDecl *> &decls)
{
    static unsigned int invocation_id = 0;
    unsigned int current_id = invocation_id++;
    
    Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));  // FIXME - a more appropriate log channel?
    
    if (log)
        log->Printf("AppleObjCDeclVendor::FindTypes [%u] ('%s', %s, %u, )",
                    current_id,
                    (const char*)name.AsCString(),
                    append ? "true" : "false",
                    max_matches);
    
    if (!append)
        decls.clear();
    
    uint32_t ret = 0;
    
    do
    {
        // See if the type is already in our ASTContext.
        
        clang::ASTContext *ast_ctx = m_ast_ctx.getASTContext();
        
        clang::IdentifierInfo &identifier_info = ast_ctx->Idents.get(name.GetStringRef());
        clang::DeclarationName decl_name = ast_ctx->DeclarationNames.getIdentifier(&identifier_info);
        
        clang::DeclContext::lookup_result lookup_result = ast_ctx->getTranslationUnitDecl()->lookup(decl_name);
        
        if (!lookup_result.empty())
        {
            if (clang::ObjCInterfaceDecl *result_iface_decl = llvm::dyn_cast<clang::ObjCInterfaceDecl>(lookup_result[0]))
            {
                if (log)
                {
                    clang::QualType result_iface_type = ast_ctx->getObjCInterfaceType(result_iface_decl);
                    ASTDumper dumper(result_iface_type);
                    
                    uint64_t isa_value = LLDB_INVALID_ADDRESS;
                    ClangASTMetadata *metadata = m_external_source->GetMetadata(result_iface_decl);
                    if (metadata)
                        isa_value = metadata->GetISAPtr();
                    
                    log->Printf("AOCTV::FT [%u] Found %s (isa 0x%" PRIx64 ") in the ASTContext",
                                current_id,
                                dumper.GetCString(),
                                isa_value);
                }
                    
                decls.push_back(result_iface_decl);
                ret++;
                break;
            }
            else
            {
                if (log)
                    log->Printf("AOCTV::FT [%u] There's something in the ASTContext, but it's not something we know about",
                                current_id);
                break;
            }
        }
        else if(log)
        {
            log->Printf("AOCTV::FT [%u] Couldn't find %s in the ASTContext",
                        current_id,
                        name.AsCString());
        }
        
        // It's not.  If it exists, we have to put it into our ASTContext.
                
        ObjCLanguageRuntime::ObjCISA isa = m_runtime.GetISA(name);
    
        if (!isa)
        {
            if (log)
                log->Printf("AOCTV::FT [%u] Couldn't find the isa",
                            current_id);
            
            break;
        }
        
        clang::ObjCInterfaceDecl *iface_decl = GetDeclForISA(isa);
        
        if (!iface_decl)
        {
            if (log)
                log->Printf("AOCTV::FT [%u] Couldn't get the Objective-C interface for isa 0x%" PRIx64,
                            current_id,
                            (uint64_t)isa);
            
            break;
        }
        
        if (log)
        {
            clang::QualType new_iface_type = ast_ctx->getObjCInterfaceType(iface_decl);
            ASTDumper dumper(new_iface_type);
            log->Printf("AOCTV::FT [%u] Created %s (isa 0x%" PRIx64 ")",
                        current_id,
                        dumper.GetCString(),
                        (uint64_t)isa);
        }
        
        decls.push_back(iface_decl);
        ret++;
        break;
    } while (0);
    
    return ret;
}
