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

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes
#include "Cocoa.h"

#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/TypeSynthetic.h"
#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Target/ObjCLanguageRuntime.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;

class NSIndexPathSyntheticFrontEnd : public SyntheticChildrenFrontEnd
{
public:
    NSIndexPathSyntheticFrontEnd (lldb::ValueObjectSP valobj_sp) :
    SyntheticChildrenFrontEnd (*valobj_sp.get()),
    m_descriptor_sp(nullptr),
    m_impl(),
    m_ptr_size(0),
    m_uint_star_type()
    {
        m_ptr_size = m_backend.GetTargetSP()->GetArchitecture().GetAddressByteSize();
    }

    ~NSIndexPathSyntheticFrontEnd() override = default;

    size_t
    CalculateNumChildren() override
    {
        return m_impl.GetNumIndexes();
    }
    
    lldb::ValueObjectSP
    GetChildAtIndex(size_t idx) override
    {
        return m_impl.GetIndexAtIndex(idx, m_uint_star_type);
    }
    
    bool
    Update() override
    {
        m_impl.Clear();
        
        TypeSystem* type_system = m_backend.GetCompilerType().GetTypeSystem();
        if (!type_system)
            return false;

        ClangASTContext *ast = m_backend.GetExecutionContextRef().GetTargetSP()->GetScratchClangASTContext();
        if (!ast)
            return false;

        m_uint_star_type = ast->GetPointerSizedIntType(false);
        
        static ConstString g__indexes("_indexes");
        static ConstString g__length("_length");

        ProcessSP process_sp = m_backend.GetProcessSP();
        if (!process_sp)
            return false;
        
        ObjCLanguageRuntime* runtime = (ObjCLanguageRuntime*)process_sp->GetLanguageRuntime(lldb::eLanguageTypeObjC);
        
        if (!runtime)
            return false;
        
        ObjCLanguageRuntime::ClassDescriptorSP descriptor(runtime->GetClassDescriptor(m_backend));
        
        if (!descriptor.get() || !descriptor->IsValid())
            return false;
        
        uint64_t info_bits(0),value_bits(0),payload(0);
        
        if (descriptor->GetTaggedPointerInfo(&info_bits, &value_bits, &payload))
        {
            m_impl.m_inlined.SetIndexes(payload, *process_sp);
            m_impl.m_mode = Mode::Inlined;
        }
        else
        {
            ObjCLanguageRuntime::ClassDescriptor::iVarDescriptor _indexes_id;
            ObjCLanguageRuntime::ClassDescriptor::iVarDescriptor _length_id;
            
            bool has_indexes(false),has_length(false);
            
            for (size_t x = 0;
                 x < descriptor->GetNumIVars();
                 x++)
            {
                const auto& ivar = descriptor->GetIVarAtIndex(x);
                if (ivar.m_name == g__indexes)
                {
                    _indexes_id = ivar;
                    has_indexes = true;
                }
                else if (ivar.m_name == g__length)
                {
                    _length_id = ivar;
                    has_length = true;
                }
                
                if (has_length && has_indexes)
                    break;
            }
            
            if (has_length && has_indexes)
            {
                m_impl.m_outsourced.m_indexes = m_backend.GetSyntheticChildAtOffset(_indexes_id.m_offset,
                                                                                    m_uint_star_type.GetPointerType(),
                                                                                    true).get();
                ValueObjectSP length_sp(m_backend.GetSyntheticChildAtOffset(_length_id.m_offset,
                                                                            m_uint_star_type,
                                                                            true));
                if (length_sp)
                {
                    m_impl.m_outsourced.m_count = length_sp->GetValueAsUnsigned(0);
                    if (m_impl.m_outsourced.m_indexes)
                        m_impl.m_mode = Mode::Outsourced;
                }
            }
        }
        return false;
    }
    
    bool
    MightHaveChildren() override
    {
        if (m_impl.m_mode == Mode::Invalid)
            return false;
        return true;
    }
    
    size_t
    GetIndexOfChildWithName(const ConstString &name) override
    {
        const char* item_name = name.GetCString();
        uint32_t idx = ExtractIndexFromString(item_name);
        if (idx < UINT32_MAX && idx >= CalculateNumChildren())
            return UINT32_MAX;
        return idx;
    }
    
    lldb::ValueObjectSP
    GetSyntheticValue() override
    {
        return nullptr;
    }

protected:
    ObjCLanguageRuntime::ClassDescriptorSP m_descriptor_sp;
    
    enum class Mode {
        Inlined,
        Outsourced,
        Invalid
    };
    
    struct Impl
    {
        size_t
        GetNumIndexes ()
        {
            switch (m_mode)
            {
                case Mode::Inlined:
                    return m_inlined.GetNumIndexes();
                case Mode::Outsourced:
                    return m_outsourced.m_count;
                default:
                    return 0;
            }
        }
        
        lldb::ValueObjectSP
        GetIndexAtIndex (size_t idx, const CompilerType& desired_type)
        {
            if (idx >= GetNumIndexes())
                return nullptr;
            switch (m_mode)
            {
                default: return nullptr;
                case Mode::Inlined:
                    return m_inlined.GetIndexAtIndex (idx, desired_type);
                case Mode::Outsourced:
                    return m_outsourced.GetIndexAtIndex (idx);
            }
        }
        
        struct InlinedIndexes
        {
        public:
            void SetIndexes(uint64_t value, Process& p)
            {
                m_indexes = value;
                _lengthForInlinePayload(p.GetAddressByteSize());
                m_process = &p;
            }
            
            size_t
            GetNumIndexes ()
            {
                return m_count;
            }
            
            lldb::ValueObjectSP
            GetIndexAtIndex (size_t idx, const CompilerType& desired_type)
            {
                if (!m_process)
                    return nullptr;

                std::pair<uint64_t, bool> value(_indexAtPositionForInlinePayload(idx));
                if (!value.second)
                    return nullptr;
                
                Value v;
                if (m_ptr_size == 8)
                {
                    Scalar scalar( (unsigned long long)value.first );
                    v = Value(scalar);
                }
                else
                {
                    Scalar scalar( (unsigned int)value.first );
                    v = Value(scalar);
                }
                
                v.SetCompilerType(desired_type);
                
                StreamString idx_name;
                idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
                
                return ValueObjectConstResult::Create(m_process, v, ConstString(idx_name.GetData()));
            }
            
            void
            Clear ()
            {
                m_indexes = 0;
                m_count = 0;
                m_ptr_size = 0;
                m_process = nullptr;
            }
            
            InlinedIndexes () :
            m_indexes(0),
            m_count(0),
            m_ptr_size(0),
            m_process(nullptr)
            {
            }
            
        private:
            uint64_t m_indexes;
            size_t m_count;
            uint32_t m_ptr_size;
            Process *m_process;
            
            // cfr. Foundation for the details of this code
            size_t _lengthForInlinePayload(uint32_t ptr_size) {
                m_ptr_size = ptr_size;
                if (m_ptr_size == 8)
                    m_count = ((m_indexes >> 3) & 0x7);
                else
                    m_count = ((m_indexes >> 3) & 0x3);
                return m_count;
            }
            
            std::pair<uint64_t, bool>
            _indexAtPositionForInlinePayload(size_t pos)
            {
                if (m_ptr_size == 8)
                {
                    switch (pos) {
                        case 5: return {((m_indexes >> 51) & 0x1ff),true};
                        case 4: return {((m_indexes >> 42) & 0x1ff),true};
                        case 3: return {((m_indexes >> 33) & 0x1ff),true};
                        case 2: return {((m_indexes >> 24) & 0x1ff),true};
                        case 1: return {((m_indexes >> 15) & 0x1ff),true};
                        case 0: return {((m_indexes >>  6) & 0x1ff),true};
                    }
                }
                else
                {
                    switch (pos) {
                        case 2: return {((m_indexes >> 23) & 0x1ff),true};
                        case 1: return {((m_indexes >> 14) & 0x1ff),true};
                        case 0: return {((m_indexes >>  5) & 0x1ff),true};
                    }
                }
                return {0,false};
            }
            
        };

        struct OutsourcedIndexes
        {
            lldb::ValueObjectSP
            GetIndexAtIndex (size_t idx)
            {
                if (m_indexes)
                {
                    ValueObjectSP index_sp(m_indexes->GetSyntheticArrayMember(idx, true));
                    return index_sp;
                }
                return nullptr;
            }
            
            void
            Clear ()
            {
                m_indexes = nullptr;
                m_count = 0;
            }
            
            OutsourcedIndexes () :
            m_indexes(nullptr),
            m_count(0)
            {
            }
            
            ValueObject *m_indexes;
            size_t m_count;
        };
        
        union
        {
            struct InlinedIndexes m_inlined;
            struct OutsourcedIndexes m_outsourced;
        };
        
        void
        Clear ()
        {
            m_mode = Mode::Invalid;
            m_inlined.Clear();
            m_outsourced.Clear();
        }
        
        Impl() :
        m_mode(Mode::Invalid)
        {
        }
        
        Mode m_mode;
    } m_impl;
    
    uint32_t m_ptr_size;
    CompilerType m_uint_star_type;
};

namespace lldb_private {
    namespace formatters {
        
        SyntheticChildrenFrontEnd* NSIndexPathSyntheticFrontEndCreator (CXXSyntheticChildren*, lldb::ValueObjectSP valobj_sp)
        {
            if (valobj_sp)
                return new NSIndexPathSyntheticFrontEnd(valobj_sp);
            return nullptr;
        }

    } // namespace formatters
} // namespace lldb_private
