//===-- MessageObjects.cpp --------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "MessageObjects.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/StringExtractor.h"
#include "llvm/ADT/StringExtras.h"
#include "gtest/gtest.h"

using namespace lldb_private;
using namespace lldb;
using namespace llvm;
namespace llgs_tests {

Expected<ProcessInfo> ProcessInfo::create(StringRef response) {
  ProcessInfo process_info;
  auto elements_or_error = SplitUniquePairList("ProcessInfo", response);
  if (!elements_or_error)
    return elements_or_error.takeError();

  auto &elements = *elements_or_error;
  if (elements["pid"].getAsInteger(16, process_info.m_pid))
    return make_parsing_error("ProcessInfo: pid");
  if (elements["parent-pid"].getAsInteger(16, process_info.m_parent_pid))
    return make_parsing_error("ProcessInfo: parent-pid");
  if (elements["real-uid"].getAsInteger(16, process_info.m_real_uid))
    return make_parsing_error("ProcessInfo: real-uid");
  if (elements["real-gid"].getAsInteger(16, process_info.m_real_gid))
    return make_parsing_error("ProcessInfo: real-uid");
  if (elements["effective-uid"].getAsInteger(16, process_info.m_effective_uid))
    return make_parsing_error("ProcessInfo: effective-uid");
  if (elements["effective-gid"].getAsInteger(16, process_info.m_effective_gid))
    return make_parsing_error("ProcessInfo: effective-gid");
  if (elements["ptrsize"].getAsInteger(10, process_info.m_ptrsize))
    return make_parsing_error("ProcessInfo: ptrsize");

  process_info.m_triple = fromHex(elements["triple"]);
  StringRef endian_str = elements["endian"];
  if (endian_str == "little")
    process_info.m_endian = support::little;
  else if (endian_str == "big")
    process_info.m_endian = support::big;
  else
    return make_parsing_error("ProcessInfo: endian");

  return process_info;
}

lldb::pid_t ProcessInfo::GetPid() const { return m_pid; }

support::endianness ProcessInfo::GetEndian() const { return m_endian; }

//====== ThreadInfo ============================================================
ThreadInfo::ThreadInfo(StringRef name, StringRef reason, RegisterMap registers,
                       unsigned int)
    : m_name(name.str()), m_reason(reason.str()),
      m_registers(std::move(registers)) {}

const RegisterValue *ThreadInfo::ReadRegister(unsigned int Id) const {
  auto Iter = m_registers.find(Id);
  return Iter == m_registers.end() ? nullptr : &Iter->getSecond();
}

//====== JThreadsInfo ==========================================================

Expected<RegisterMap>
JThreadsInfo::parseRegisters(const StructuredData::Dictionary &Dict,
                             ArrayRef<RegisterInfo> RegInfos) {
  RegisterMap Result;

  auto KeysObj = Dict.GetKeys();
  auto Keys = KeysObj->GetAsArray();
  for (size_t i = 0; i < Keys->GetSize(); i++) {
    StringRef KeyStr, ValueStr;
    Keys->GetItemAtIndexAsString(i, KeyStr);
    Dict.GetValueForKeyAsString(KeyStr, ValueStr);
    unsigned int Register;
    if (!llvm::to_integer(KeyStr, Register, 10))
      return make_parsing_error("JThreadsInfo: register key[{0}]", i);

    auto RegValOr =
        parseRegisterValue(RegInfos[Register], ValueStr, support::big);
    if (!RegValOr)
      return RegValOr.takeError();
    Result[Register] = std::move(*RegValOr);
  }
  return std::move(Result);
}

Expected<JThreadsInfo> JThreadsInfo::create(StringRef Response,
                                            ArrayRef<RegisterInfo> RegInfos) {
  JThreadsInfo jthreads_info;

  StructuredData::ObjectSP json = StructuredData::ParseJSON(Response);
  StructuredData::Array *array = json->GetAsArray();
  if (!array)
    return make_parsing_error("JThreadsInfo: JSON array");

  for (size_t i = 0; i < array->GetSize(); i++) {
    StructuredData::Dictionary *thread_info;
    array->GetItemAtIndexAsDictionary(i, thread_info);
    if (!thread_info)
      return make_parsing_error("JThreadsInfo: JSON obj at {0}", i);

    StringRef name, reason;
    thread_info->GetValueForKeyAsString("name", name);
    thread_info->GetValueForKeyAsString("reason", reason);
    uint64_t signal;
    thread_info->GetValueForKeyAsInteger("signal", signal);
    uint64_t tid;
    thread_info->GetValueForKeyAsInteger("tid", tid);

    StructuredData::Dictionary *register_dict;
    thread_info->GetValueForKeyAsDictionary("registers", register_dict);
    if (!register_dict)
      return make_parsing_error("JThreadsInfo: registers JSON obj");

    auto RegsOr = parseRegisters(*register_dict, RegInfos);
    if (!RegsOr)
      return RegsOr.takeError();
    jthreads_info.m_thread_infos[tid] =
        ThreadInfo(name, reason, std::move(*RegsOr), signal);
  }

  return jthreads_info;
}

const ThreadInfoMap &JThreadsInfo::GetThreadInfos() const {
  return m_thread_infos;
}

Expected<RegisterInfo> RegisterInfoParser::create(StringRef Response) {
  auto ElementsOr = SplitUniquePairList("RegisterInfoParser", Response);
  if (!ElementsOr)
    return ElementsOr.takeError();
  auto &Elements = *ElementsOr;

  RegisterInfo Info = {
      nullptr,       // Name
      nullptr,       // Alt name
      0,             // byte size
      0,             // offset
      eEncodingUint, // encoding
      eFormatHex,    // format
      {
          LLDB_INVALID_REGNUM, // eh_frame reg num
          LLDB_INVALID_REGNUM, // DWARF reg num
          LLDB_INVALID_REGNUM, // generic reg num
          LLDB_INVALID_REGNUM, // process plugin reg num
          LLDB_INVALID_REGNUM  // native register number
      },
      NULL,
      NULL,
      NULL, // Dwarf expression opcode bytes pointer
      0     // Dwarf expression opcode bytes length
  };
  Info.name = ConstString(Elements["name"]).GetCString();
  if (!Info.name)
    return make_parsing_error("qRegisterInfo: name");

  Info.alt_name = ConstString(Elements["alt-name"]).GetCString();

  if (!to_integer(Elements["bitsize"], Info.byte_size, 10))
    return make_parsing_error("qRegisterInfo: bit-size");
  Info.byte_size /= CHAR_BIT;

  if (!to_integer(Elements["offset"], Info.byte_offset, 10))
    return make_parsing_error("qRegisterInfo: offset");

  Info.encoding = Args::StringToEncoding(Elements["encoding"]);
  if (Info.encoding == eEncodingInvalid)
    return make_parsing_error("qRegisterInfo: encoding");

  Info.format = StringSwitch<Format>(Elements["format"])
                    .Case("binary", eFormatBinary)
                    .Case("decimal", eFormatDecimal)
                    .Case("hex", eFormatHex)
                    .Case("float", eFormatFloat)
                    .Case("vector-sint8", eFormatVectorOfSInt8)
                    .Case("vector-uint8", eFormatVectorOfUInt8)
                    .Case("vector-sint16", eFormatVectorOfSInt16)
                    .Case("vector-uint16", eFormatVectorOfUInt16)
                    .Case("vector-sint32", eFormatVectorOfSInt32)
                    .Case("vector-uint32", eFormatVectorOfUInt32)
                    .Case("vector-float32", eFormatVectorOfFloat32)
                    .Case("vector-uint64", eFormatVectorOfUInt64)
                    .Case("vector-uint128", eFormatVectorOfUInt128)
                    .Default(eFormatInvalid);
  if (Info.format == eFormatInvalid)
    return make_parsing_error("qRegisterInfo: format");

  Info.kinds[eRegisterKindGeneric] =
      Args::StringToGenericRegister(Elements["generic"]);

  return std::move(Info);
}

Expected<RegisterValue> parseRegisterValue(const RegisterInfo &Info,
                                           StringRef HexValue,
                                           llvm::support::endianness Endian,
                                           bool ZeroPad) {
  SmallString<128> Storage;
  if (ZeroPad && HexValue.size() < Info.byte_size * 2) {
    Storage.insert(Storage.begin(), Info.byte_size * 2 - HexValue.size(), '0');
    Storage += HexValue;
    HexValue = Storage;
  }

  SmallVector<uint8_t, 64> Bytes(HexValue.size() / 2);
  StringExtractor(HexValue).GetHexBytes(Bytes, '\xcc');
  RegisterValue Value;
  Status ST;
  Value.SetFromMemoryData(
      &Info, Bytes.data(), Bytes.size(),
      Endian == support::little ? eByteOrderLittle : eByteOrderBig, ST);
  if (ST.Fail())
    return ST.ToError();
  return Value;
}

//====== StopReply =============================================================
Expected<std::unique_ptr<StopReply>>
StopReply::create(StringRef Response, llvm::support::endianness Endian,
                  ArrayRef<RegisterInfo> RegInfos) {
  if (Response.size() < 3)
    return make_parsing_error("StopReply: Invalid packet");
  if (Response.consume_front("T"))
    return StopReplyStop::create(Response, Endian, RegInfos);
  if (Response.consume_front("W"))
    return StopReplyExit::create(Response);
  return make_parsing_error("StopReply: Invalid packet");
}

Expected<RegisterMap> StopReplyStop::parseRegisters(
    const StringMap<SmallVector<StringRef, 2>> &Elements,
    support::endianness Endian, ArrayRef<lldb_private::RegisterInfo> RegInfos) {

  RegisterMap Result;
  for (const auto &E : Elements) {
    StringRef Key = E.getKey();
    const auto &Val = E.getValue();
    if (Key.size() != 2)
      continue;

    unsigned int Reg;
    if (!to_integer(Key, Reg, 16))
      continue;

    if (Val.size() != 1)
      return make_parsing_error(
          "StopReplyStop: multiple entries for register field [{0:x}]", Reg);

    auto RegValOr = parseRegisterValue(RegInfos[Reg], Val[0], Endian);
    if (!RegValOr)
      return RegValOr.takeError();
    Result[Reg] = std::move(*RegValOr);
  }
  return std::move(Result);
}

Expected<std::unique_ptr<StopReplyStop>>
StopReplyStop::create(StringRef Response, support::endianness Endian,
                      ArrayRef<RegisterInfo> RegInfos) {
  unsigned int Signal;
  StringRef SignalStr = Response.take_front(2);
  Response = Response.drop_front(2);
  if (!to_integer(SignalStr, Signal, 16))
    return make_parsing_error("StopReply: stop signal");

  auto Elements = SplitPairList(Response);
  for (StringRef Field :
       {"name", "reason", "thread", "threads", "thread-pcs"}) {
    // This will insert an empty field if there is none. In the future, we
    // should probably differentiate between these fields not being present and
    // them being empty, but right now no tests depends on this.
    if (Elements.insert({Field, {""}}).first->second.size() != 1)
      return make_parsing_error(
          "StopReply: got multiple responses for the {0} field", Field);
  }
  StringRef Name = Elements["name"][0];
  StringRef Reason = Elements["reason"][0];

  lldb::tid_t Thread;
  if (!to_integer(Elements["thread"][0], Thread, 16))
    return make_parsing_error("StopReply: thread");

  SmallVector<StringRef, 20> Threads;
  SmallVector<StringRef, 20> Pcs;
  Elements["threads"][0].split(Threads, ',');
  Elements["thread-pcs"][0].split(Pcs, ',');
  if (Threads.size() != Pcs.size())
    return make_parsing_error("StopReply: thread/PC count mismatch");

  RegisterMap ThreadPcs;
  const RegisterInfo *PcInfo = find_if(RegInfos, [](const RegisterInfo &Info) {
    return Info.kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC;
  });
  assert(PcInfo);

  for (auto ThreadPc : zip(Threads, Pcs)) {
    lldb::tid_t Id;
    if (!to_integer(std::get<0>(ThreadPc), Id, 16))
      return make_parsing_error("StopReply: Thread id '{0}'",
                                std::get<0>(ThreadPc));

    auto PcOr = parseRegisterValue(*PcInfo, std::get<1>(ThreadPc), Endian,
                                   /*ZeroPad*/ true);
    if (!PcOr)
      return PcOr.takeError();
    ThreadPcs[Id] = std::move(*PcOr);
  }

  auto RegistersOr = parseRegisters(Elements, Endian, RegInfos);
  if (!RegistersOr)
    return RegistersOr.takeError();

  return llvm::make_unique<StopReplyStop>(Signal, Thread, Name,
                                          std::move(ThreadPcs),
                                          std::move(*RegistersOr), Reason);
}

Expected<std::unique_ptr<StopReplyExit>>
StopReplyExit::create(StringRef Response) {
  uint8_t Status;
  if (!to_integer(Response, Status, 16))
    return make_parsing_error("StopReply: exit status");
  return llvm::make_unique<StopReplyExit>(Status);
}

//====== Globals ===============================================================
Expected<StringMap<StringRef>> SplitUniquePairList(StringRef caller,
                                                   StringRef str) {
  SmallVector<StringRef, 20> elements;
  str.split(elements, ';');

  StringMap<StringRef> pairs;
  for (StringRef s : elements) {
    std::pair<StringRef, StringRef> pair = s.split(':');
    if (pairs.count(pair.first))
      return make_parsing_error("{0}: Duplicate Key: {1}", caller, pair.first);

    pairs.insert(pair);
  }

  return pairs;
}

StringMap<SmallVector<StringRef, 2>> SplitPairList(StringRef str) {
  SmallVector<StringRef, 20> elements;
  str.split(elements, ';');

  StringMap<SmallVector<StringRef, 2>> pairs;
  for (StringRef s : elements) {
    std::pair<StringRef, StringRef> pair = s.split(':');
    pairs[pair.first].push_back(pair.second);
  }

  return pairs;
}
} // namespace llgs_tests

std::ostream &lldb_private::operator<<(std::ostream &OS,
                                       const RegisterValue &RegVal) {
  ArrayRef<uint8_t> Bytes(static_cast<const uint8_t *>(RegVal.GetBytes()),
                          RegVal.GetByteSize());
  return OS << formatv("RegisterValue[{0}]: {1:@[x-2]}", RegVal.GetByteSize(),
                       make_range(Bytes.begin(), Bytes.end()))
                   .str();
}
