//===-- GoFormatterFunctions.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
#include <map>

// Other libraries and framework includes
// Project includes
#include "GoFormatterFunctions.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/StringPrinter.h"

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

namespace
{
class GoSliceSyntheticFrontEnd : public SyntheticChildrenFrontEnd
{
  public:
    GoSliceSyntheticFrontEnd(ValueObject &valobj)
        : SyntheticChildrenFrontEnd(valobj)
    {
        Update();
    }

    ~GoSliceSyntheticFrontEnd() override = default;

    size_t
    CalculateNumChildren() override
    {
        return m_len;
    }

    lldb::ValueObjectSP
    GetChildAtIndex(size_t idx) override
    {
        if (idx < m_len)
        {
            ValueObjectSP &cached = m_children[idx];
            if (!cached)
            {
                StreamString idx_name;
                idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
                lldb::addr_t object_at_idx = m_base_data_address;
                object_at_idx += idx * m_type.GetByteSize(nullptr);
                cached = CreateValueObjectFromAddress(idx_name.GetData(), object_at_idx,
                                                      m_backend.GetExecutionContextRef(), m_type);
            }
            return cached;
        }
        return ValueObjectSP();
    }

    bool
    Update() override
    {
        size_t old_count = m_len;

        ConstString array_const_str("array");
        ValueObjectSP array_sp = m_backend.GetChildMemberWithName(array_const_str, true);
        if (!array_sp)
        {
            m_children.clear();
            return old_count == 0;
        }
        m_type = array_sp->GetCompilerType().GetPointeeType();
        m_base_data_address = array_sp->GetPointerValue();

        ConstString len_const_str("len");
        ValueObjectSP len_sp = m_backend.GetChildMemberWithName(len_const_str, true);
        if (len_sp)
        {
            m_len = len_sp->GetValueAsUnsigned(0);
            m_children.clear();
        }

        return old_count == m_len;
    }

    bool
    MightHaveChildren() override
    {
        return true;
    }

    size_t
    GetIndexOfChildWithName(const ConstString &name) override
    {
        return ExtractIndexFromString(name.AsCString());
    }

  private:
    CompilerType m_type;
    lldb::addr_t m_base_data_address;
    size_t m_len;
    std::map<size_t, lldb::ValueObjectSP> m_children;
};

} // anonymous namespace

bool
lldb_private::formatters::GoStringSummaryProvider(ValueObject &valobj, Stream &stream, const TypeSummaryOptions &opts)
{
    ProcessSP process_sp = valobj.GetProcessSP();
    if (!process_sp)
        return false;

    if (valobj.IsPointerType())
    {
        Error err;
        ValueObjectSP deref = valobj.Dereference(err);
        if (!err.Success())
            return false;
        return GoStringSummaryProvider(*deref, stream, opts);
    }

    ConstString str_name("str");
    ConstString len_name("len");

    ValueObjectSP data_sp = valobj.GetChildMemberWithName(str_name, true);
    ValueObjectSP len_sp = valobj.GetChildMemberWithName(len_name, true);
    if (!data_sp || !len_sp)
        return false;
    bool success;
    lldb::addr_t valobj_addr = data_sp->GetValueAsUnsigned(0, &success);

    if (!success)
        return false;

    uint64_t length = len_sp->GetValueAsUnsigned(0);
    if (length == 0)
    {
        stream.Printf("\"\"");
        return true;
    }

    StringPrinter::ReadStringAndDumpToStreamOptions options(valobj);
    options.SetLocation(valobj_addr);
    options.SetProcessSP(process_sp);
    options.SetStream(&stream);
    options.SetSourceSize(length);
    options.SetNeedsZeroTermination(false);
    options.SetLanguage(eLanguageTypeGo);

    if (!StringPrinter::ReadStringAndDumpToStream<StringPrinter::StringElementType::UTF8>(options))
    {
        stream.Printf("Summary Unavailable");
        return true;
    }

    return true;
}

SyntheticChildrenFrontEnd *
lldb_private::formatters::GoSliceSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp)
{
    if (!valobj_sp)
        return nullptr;

    lldb::ProcessSP process_sp(valobj_sp->GetProcessSP());
    if (!process_sp)
        return nullptr;
    return new GoSliceSyntheticFrontEnd(*valobj_sp);
}
