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

// Project includes
#include "MinidumpParser.h"

// Other libraries and framework includes
// C includes
// C++ includes

using namespace lldb_private;
using namespace minidump;

llvm::Optional<MinidumpParser>
MinidumpParser::Create(const lldb::DataBufferSP &data_buf_sp) {
  if (data_buf_sp->GetByteSize() < sizeof(MinidumpHeader)) {
    return llvm::None;
  }

  llvm::ArrayRef<uint8_t> header_data(data_buf_sp->GetBytes(),
                                      sizeof(MinidumpHeader));
  const MinidumpHeader *header = MinidumpHeader::Parse(header_data);

  if (header == nullptr) {
    return llvm::None;
  }

  lldb::offset_t directory_list_offset = header->stream_directory_rva;
  // check if there is enough data for the parsing of the directory list
  if ((directory_list_offset +
       sizeof(MinidumpDirectory) * header->streams_count) >
      data_buf_sp->GetByteSize()) {
    return llvm::None;
  }

  const MinidumpDirectory *directory = nullptr;
  Error error;
  llvm::ArrayRef<uint8_t> directory_data(
      data_buf_sp->GetBytes() + directory_list_offset,
      sizeof(MinidumpDirectory) * header->streams_count);
  llvm::DenseMap<uint32_t, MinidumpLocationDescriptor> directory_map;

  for (uint32_t i = 0; i < header->streams_count; ++i) {
    error = consumeObject(directory_data, directory);
    if (error.Fail()) {
      return llvm::None;
    }
    directory_map[static_cast<const uint32_t>(directory->stream_type)] =
        directory->location;
  }

  MinidumpParser parser(data_buf_sp, header, directory_map);
  return llvm::Optional<MinidumpParser>(parser);
}

MinidumpParser::MinidumpParser(
    const lldb::DataBufferSP &data_buf_sp, const MinidumpHeader *header,
    const llvm::DenseMap<uint32_t, MinidumpLocationDescriptor> &directory_map)
    : m_data_sp(data_buf_sp), m_header(header), m_directory_map(directory_map) {
}

lldb::offset_t MinidumpParser::GetByteSize() {
  return m_data_sp->GetByteSize();
}

llvm::Optional<llvm::ArrayRef<uint8_t>>
MinidumpParser::GetStream(MinidumpStreamType stream_type) {
  auto iter = m_directory_map.find(static_cast<uint32_t>(stream_type));
  if (iter == m_directory_map.end())
    return llvm::None;

  // check if there is enough data
  if (iter->second.rva + iter->second.data_size > m_data_sp->GetByteSize())
    return llvm::None;

  llvm::ArrayRef<uint8_t> arr_ref(m_data_sp->GetBytes() + iter->second.rva,
                                  iter->second.data_size);
  return llvm::Optional<llvm::ArrayRef<uint8_t>>(arr_ref);
}

llvm::Optional<std::vector<const MinidumpThread *>>
MinidumpParser::GetThreads() {
  llvm::Optional<llvm::ArrayRef<uint8_t>> data =
      GetStream(MinidumpStreamType::ThreadList);

  if (!data)
    return llvm::None;

  return MinidumpThread::ParseThreadList(data.getValue());
}

const MinidumpSystemInfo *MinidumpParser::GetSystemInfo() {
  llvm::Optional<llvm::ArrayRef<uint8_t>> data =
      GetStream(MinidumpStreamType::SystemInfo);

  if (!data)
    return nullptr;

  return MinidumpSystemInfo::Parse(data.getValue());
}

ArchSpec MinidumpParser::GetArchitecture() {
  ArchSpec arch_spec;
  arch_spec.GetTriple().setOS(llvm::Triple::OSType::UnknownOS);
  arch_spec.GetTriple().setVendor(llvm::Triple::VendorType::UnknownVendor);
  arch_spec.GetTriple().setArch(llvm::Triple::ArchType::UnknownArch);

  // TODO should we add the OS type here, or somewhere else ?

  const MinidumpSystemInfo *system_info = GetSystemInfo();

  if (!system_info)
    return arch_spec;

  // TODO what to do about big endiand flavors of arm ?
  // TODO set the arm subarch stuff if the minidump has info about it

  const MinidumpCPUArchitecture arch =
      static_cast<const MinidumpCPUArchitecture>(
          static_cast<const uint32_t>(system_info->processor_arch));
  switch (arch) {
  case MinidumpCPUArchitecture::X86:
    arch_spec.GetTriple().setArch(llvm::Triple::ArchType::x86);
    break;
  case MinidumpCPUArchitecture::AMD64:
    arch_spec.GetTriple().setArch(llvm::Triple::ArchType::x86_64);
    break;
  case MinidumpCPUArchitecture::ARM:
    arch_spec.GetTriple().setArch(llvm::Triple::ArchType::arm);
    break;
  case MinidumpCPUArchitecture::ARM64:
    arch_spec.GetTriple().setArch(llvm::Triple::ArchType::aarch64);
    break;
  }

  return arch_spec;
}

const MinidumpMiscInfo *MinidumpParser::GetMiscInfo() {
  llvm::Optional<llvm::ArrayRef<uint8_t>> data =
      GetStream(MinidumpStreamType::MiscInfo);

  if (!data)
    return nullptr;

  return MinidumpMiscInfo::Parse(data.getValue());
}
