//===- AArch64GOT.cpp -----------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "AArch64GOT.h"

#include <llvm/Support/Casting.h>

#include <mcld/LD/LDSection.h>
#include <mcld/LD/LDFileFormat.h>
#include <mcld/Support/MsgHandling.h>

namespace {
  const unsigned int AArch64GOT0Num = 3;
} // end of anonymous namespace

using namespace mcld;

//===----------------------------------------------------------------------===//
// AArch64GOT
AArch64GOT::AArch64GOT(LDSection& pSection)
  : GOT(pSection), m_pGOTPLTFront(NULL), m_pGOTFront(NULL)
{
}

AArch64GOT::~AArch64GOT()
{
}

void AArch64GOT::createGOT0()
{
  // create GOT0, and put them into m_SectionData immediately
  for (unsigned int i = 0; i < AArch64GOT0Num; ++i)
    new AArch64GOTEntry(0, m_SectionData);
}

bool AArch64GOT::hasGOT1() const
{
  return ((!m_GOT.empty()) || (!m_GOTPLT.empty()));
}

AArch64GOTEntry* AArch64GOT::createGOT()
{
  AArch64GOTEntry* entry = new AArch64GOTEntry(0, NULL);
  m_GOT.push_back(entry);
  return entry;
}

AArch64GOTEntry* AArch64GOT::createGOTPLT()
{
  AArch64GOTEntry* entry = new AArch64GOTEntry(0, NULL);
  m_GOTPLT.push_back(entry);
  return entry;
}

void AArch64GOT::finalizeSectionSize()
{
  uint32_t offset = 0;
  SectionData::FragmentListType& frag_list = m_SectionData->getFragmentList();
  // setup GOT0 offset
  SectionData::iterator frag, fragEnd = m_SectionData->end();
  for (frag = m_SectionData->begin(); frag != fragEnd; ++frag) {
    frag->setOffset(offset);
    offset += frag->size();
  }

  // push GOTPLT into the SectionData and setup the offset
  if (!m_GOTPLT.empty()) {
    m_pGOTPLTFront = m_GOTPLT.front();
    entry_iterator it, end = m_GOTPLT.end();
    for (it = m_GOTPLT.begin(); it != end; ++it) {
      AArch64GOTEntry* entry = *it;
      frag_list.push_back(entry);
      entry->setParent(m_SectionData);
      entry->setOffset(offset);
      offset += entry->size();

    }
  }
  m_GOTPLT.clear();

  // push GOT into the SectionData and setup the offset
  if (!m_GOT.empty()) {
    m_pGOTFront = m_GOT.front();
    entry_iterator it, end = m_GOT.end();
    for (it = m_GOT.begin(); it != end; ++it) {
      AArch64GOTEntry* entry = *it;
      frag_list.push_back(entry);
      entry->setParent(m_SectionData);
      entry->setOffset(offset);
      offset += entry->size();
    }
  }
  m_GOT.clear();

  // set section size
  m_Section.setSize(offset);
}

void AArch64GOT::applyGOT0(uint64_t pAddress)
{
  llvm::cast<AArch64GOTEntry>
    (*(m_SectionData->getFragmentList().begin())).setValue(pAddress);
}

void AArch64GOT::applyGOTPLT(uint64_t pPLTBase)
{
  if (NULL == m_pGOTPLTFront)
    return;

  SectionData::iterator entry(m_pGOTPLTFront);
  SectionData::iterator e_end;
  if (NULL == m_pGOTFront)
    e_end = m_SectionData->end();
  else
    e_end = SectionData::iterator(m_pGOTFront);

  while (entry != e_end) {
    llvm::cast<AArch64GOTEntry>(entry)->setValue(pPLTBase);
    ++entry;
  }
}

uint64_t AArch64GOT::emit(MemoryRegion& pRegion)
{
  uint64_t* buffer = reinterpret_cast<uint64_t*>(pRegion.begin());

  AArch64GOTEntry* got = NULL;
  uint64_t result = 0x0;
  for (iterator it = begin(), ie = end(); it != ie; ++it, ++buffer) {
      got = &(llvm::cast<AArch64GOTEntry>((*it)));
      *buffer = static_cast<uint64_t>(got->getValue());
      result += AArch64GOTEntry::EntrySize;
  }
  return result;
}