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

#include "mcld/Fragment/AlignFragment.h"
#include "mcld/Fragment/Stub.h"
#include "mcld/LD/LDSection.h"
#include "mcld/LD/ResolveInfo.h"

#include <sstream>

namespace mcld {

//============================================================================//
// BranchIsland
//============================================================================//
BranchIsland::BranchIsland(Fragment& pEntryFrag, size_t pMaxSize, size_t pIndex)
    : m_Entry(pEntryFrag),
      m_pExit(pEntryFrag.getNextNode()),
      m_pRear(NULL),
      m_MaxSize(pMaxSize),
      m_Name("island-") {
  // island name
  std::ostringstream index;
  index << pIndex;
  m_Name.append(index.str());
}

BranchIsland::~BranchIsland() {
}

/// fragment iterators of the island
SectionData::iterator BranchIsland::begin() {
  return ++iterator(&m_Entry);
}

SectionData::const_iterator BranchIsland::begin() const {
  return ++iterator(&m_Entry);
}

SectionData::iterator BranchIsland::end() {
  if (m_pExit != NULL)
    return iterator(m_pExit);
  return m_Entry.getParent()->end();
}

SectionData::const_iterator BranchIsland::end() const {
  if (m_pExit != NULL)
    return iterator(m_pExit);
  return m_Entry.getParent()->end();
}

uint64_t BranchIsland::offset() const {
  return m_Entry.getOffset() + m_Entry.size();
}

size_t BranchIsland::size() const {
  size_t size = 0x0;
  if (numOfStubs() != 0x0) {
    size = m_pRear->getOffset() + m_pRear->size() -
           m_Entry.getNextNode()->getOffset();
  }
  return size;
}

size_t BranchIsland::maxSize() const {
  return m_MaxSize;
}

const std::string& BranchIsland::name() const {
  return m_Name;
}

size_t BranchIsland::numOfStubs() const {
  return m_StubMap.numOfEntries();
}

/// findStub - return true if there is a stub built from the given prototype
///            for the given relocation
Stub* BranchIsland::findStub(const Stub* pPrototype, const Relocation& pReloc) {
  Key key(pPrototype, pReloc.symInfo()->outSymbol(), pReloc.addend());
  StubMapType::iterator it = m_StubMap.find(key);
  if (it != m_StubMap.end()) {
    assert(it.getEntry()->value() != NULL);
    return it.getEntry()->value();
  }
  return NULL;
}

/// addStub - add a stub into the island
bool BranchIsland::addStub(const Stub* pPrototype,
                           const Relocation& pReloc,
                           Stub& pStub) {
  bool exist = false;
  Key key(pPrototype, pReloc.symInfo()->outSymbol(), pReloc.addend());
  StubEntryType* entry = m_StubMap.insert(key, exist);
  if (!exist) {
    entry->setValue(&pStub);
    m_pRear = &pStub;
    SectionData* sd = m_Entry.getParent();

    // insert alignment fragment
    // TODO: check if we can reduce this alignment fragment for some cases
    AlignFragment* align_frag =
        new AlignFragment(pStub.alignment(), 0x0, 1u, pStub.alignment() - 1);
    align_frag->setParent(sd);
    sd->getFragmentList().insert(end(), align_frag);
    align_frag->setOffset(align_frag->getPrevNode()->getOffset() +
                          align_frag->getPrevNode()->size());

    // insert stub fragment
    pStub.setParent(sd);
    sd->getFragmentList().insert(end(), &pStub);
    pStub.setOffset(pStub.getPrevNode()->getOffset() +
                    pStub.getPrevNode()->size());
  }
  return !exist;
}

void BranchIsland::addStub(Stub& pStub) {
  bool exist = false;
  Key key(&pStub, pStub.symInfo()->outSymbol(), 0);
  m_StubMap.insert(key, exist);

  m_pRear = &pStub;
  SectionData* sd = m_Entry.getParent();

  // insert alignment fragment
  // TODO: check if we can reduce this alignment fragment for some cases
  AlignFragment* align_frag =
      new AlignFragment(pStub.alignment(), 0x0, 1u, pStub.alignment() - 1);
  align_frag->setParent(sd);
  sd->getFragmentList().insert(end(), align_frag);
  align_frag->setOffset(align_frag->getPrevNode()->getOffset() +
                        align_frag->getPrevNode()->size());

  // insert stub fragment
  pStub.setParent(sd);
  sd->getFragmentList().insert(end(), &pStub);
  pStub.setOffset(pStub.getPrevNode()->getOffset() +
                  pStub.getPrevNode()->size());
}

/// addRelocation - add a relocation into island
bool BranchIsland::addRelocation(Relocation& pReloc) {
  m_Relocations.push_back(&pReloc);
  return true;
}

}  // namespace mcld