/* Copyright 2017 - 2024 R. Thomas
 * Copyright 2017 - 2024 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "spdlog/fmt/fmt.h"
#include "LIEF/Visitor.hpp"
#include "LIEF/MachO/Symbol.hpp"
#include "LIEF/MachO/Section.hpp"
#include "LIEF/MachO/SegmentCommand.hpp"
#include "LIEF/MachO/DyldInfo.hpp"
#include "LIEF/MachO/Relocation.hpp"
#include "LIEF/MachO/EnumToString.hpp"
#include "frozen.hpp"

namespace LIEF {
namespace MachO {

Relocation::Relocation(const Relocation& other) :
  LIEF::Relocation{other},
  type_{other.type_},
  architecture_{other.architecture_}
{}

Relocation::Relocation(uint64_t address, uint8_t type) {
  address_ = address;
  type_    = type;
}

Relocation& Relocation::operator=(const Relocation& other) {
  if (&other != this) {
    /* Do not copy pointer as they could be not bind to the same Binary */
    address_      = other.address_;
    size_         = other.size_;
    type_         = other.type_;
    architecture_ = other.architecture_;
  }
  return *this;
}
void Relocation::swap(Relocation& other) noexcept {
  LIEF::Relocation::swap(other);

  std::swap(symbol_,       other.symbol_);
  std::swap(type_,         other.type_);
  std::swap(architecture_, other.architecture_);
  std::swap(section_,      other.section_);
  std::swap(segment_,      other.segment_);
}

void Relocation::type(uint8_t type) {
  type_ = type;
}

void Relocation::accept(Visitor& visitor) const {
  visitor.visit(*this);
}

std::ostream& Relocation::print(std::ostream& os) const {
  std::string symbol_name;
  if (const Symbol* sym = symbol()) {
    symbol_name = sym->name();
  }

  std::string section_name;
  if (const Section* sec = section()) {
    section_name = sec->name();
  }

  std::string segment_name;
  if (const SegmentCommand* seg = segment()) {
    segment_name = seg->name();
  }

  std::string segment_section_name;
  if (!section_name.empty() && !segment_name.empty()) {
    segment_section_name = segment_name + '.' + section_name;
  }
  else if (!segment_name.empty()) {
    segment_section_name = segment_name;
  }
  else if (!section_name.empty()) {
    segment_section_name = section_name;
  }

  std::string relocation_type;
  if (origin() == Relocation::ORIGIN::RELOC_TABLE) {
    switch (architecture()) {
      case Header::CPU_TYPE::X86:
        relocation_type = to_string(X86_RELOCATION(type()));
        break;

      case Header::CPU_TYPE::X86_64:
        relocation_type = to_string(X86_64_RELOCATION(type()));
        break;

      case Header::CPU_TYPE::ARM:
        relocation_type = to_string(ARM_RELOCATION(type()));
        break;

      case Header::CPU_TYPE::ARM64:
        relocation_type = to_string(ARM64_RELOCATION(type()));
        break;

      case Header::CPU_TYPE::POWERPC:
        relocation_type = to_string(PPC_RELOCATION(type()));
        break;

      default:
        relocation_type = std::to_string(type());
    }
  }

  if (origin() == Relocation::ORIGIN::DYLDINFO) {
    relocation_type = to_string(DyldInfo::REBASE_TYPE(type()));
  }

  os << fmt::format(
    "address=0x{:x}, type={}, size={}, origin={} ",
    address(), relocation_type, size(), to_string(origin())
  );
  if (!segment_section_name.empty()) {
      os << segment_section_name;
  } else {
    if (!section_name.empty()) {
      os << section_name;
    }

    if (!segment_name.empty()) {
      os << section_name;
    }
  }
  os << ' ' << symbol_name;
  return os;
}


std::ostream& operator<<(std::ostream& os, const Relocation& reloc) {
  return reloc.print(os);
}
const char* to_string(Relocation::ORIGIN e) {
  #define ENTRY(X) std::pair(Relocation::ORIGIN::X, #X)
  STRING_MAP enums2str {
    ENTRY(UNKNOWN),
    ENTRY(DYLDINFO),
    ENTRY(RELOC_TABLE),
    ENTRY(CHAINED_FIXUPS),
  };
  #undef ENTRY

  if (auto it = enums2str.find(e); it != enums2str.end()) {
    return it->second;
  }
  return "UNKNOWN";
}

}
}
