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

#include <cstring>
#include <cassert>

#include <llvm/Support/Casting.h>
#include <llvm/Support/ManagedStatic.h>

#include <mcld/Fragment/Fragment.h>
#include <mcld/LD/LDSection.h>
#include <mcld/LD/SectionData.h>
#include <mcld/LD/EhFrame.h>
#include <mcld/Support/GCFactory.h>
#include <mcld/Support/MemoryRegion.h>
#include <mcld/Fragment/RegionFragment.h>
#include <mcld/Fragment/Stub.h>

using namespace mcld;

typedef GCFactory<FragmentRef, MCLD_SECTIONS_PER_INPUT> FragRefFactory;

static llvm::ManagedStatic<FragRefFactory> g_FragRefFactory;

FragmentRef FragmentRef::g_NullFragmentRef;

//===----------------------------------------------------------------------===//
// FragmentRef
//===----------------------------------------------------------------------===//
FragmentRef::FragmentRef()
  : m_pFragment(NULL), m_Offset(0) {
}

FragmentRef::FragmentRef(Fragment& pFrag,
                         FragmentRef::Offset pOffset)
  : m_pFragment(&pFrag), m_Offset(pOffset) {
}

/// Create - create a fragment reference for a given fragment.
///
/// @param pFrag - the given fragment
/// @param pOffset - the offset, can be larger than the fragment, but can not
///                  be larger than the section size.
/// @return if the offset is legal, return the fragment reference. Otherwise,
/// return NULL.
FragmentRef* FragmentRef::Create(Fragment& pFrag, uint64_t pOffset)
{
  int64_t offset = pOffset;
  Fragment* frag = &pFrag;

  while (NULL != frag) {
    offset -= frag->size();
    if (offset <= 0)
      break;
    frag = frag->getNextNode();
  }


  if (NULL == frag)
    return Null();

  FragmentRef* result = g_FragRefFactory->allocate();
  new (result) FragmentRef(*frag, offset + frag->size());

  return result;
}

FragmentRef* FragmentRef::Create(LDSection& pSection, uint64_t pOffset)
{
  SectionData* data = NULL;
  switch (pSection.kind()) {
    case LDFileFormat::Relocation:
      // No fragment reference refers to a relocation section
      break;
    case LDFileFormat::EhFrame:
      if (pSection.hasEhFrame())
        data = &pSection.getEhFrame()->getSectionData();
      break;
    default:
      data = pSection.getSectionData();
      break;
  }

  if (NULL == data || data->empty()) {
    return Null();
  }

  return Create(data->front(), pOffset);
}

void FragmentRef::Clear()
{
  g_FragRefFactory->clear();
}

FragmentRef* FragmentRef::Null()
{
  return &g_NullFragmentRef;
}

FragmentRef& FragmentRef::assign(const FragmentRef& pCopy)
{
  m_pFragment = const_cast<Fragment*>(pCopy.m_pFragment);
  m_Offset = pCopy.m_Offset;
  return *this;
}

FragmentRef& FragmentRef::assign(Fragment& pFrag, FragmentRef::Offset pOffset)
{
  m_pFragment = &pFrag;
  m_Offset = pOffset;
  return *this;
}

void FragmentRef::memcpy(void* pDest, size_t pNBytes, Offset pOffset) const
{
  // check if the offset is still in a legal range.
  if (NULL == m_pFragment)
    return;
  unsigned int total_offset = m_Offset + pOffset;
  switch(m_pFragment->getKind()) {
    case Fragment::Region: {
      RegionFragment* region_frag = static_cast<RegionFragment*>(m_pFragment);
      unsigned int total_length = region_frag->getRegion().size();
      if (total_length < (total_offset+pNBytes))
        pNBytes = total_length - total_offset;

      std::memcpy(pDest, region_frag->getRegion().getBuffer(total_offset), pNBytes);
      return;
    }
    case Fragment::Stub: {
      Stub* stub_frag = static_cast<Stub*>(m_pFragment);
      unsigned int total_length = stub_frag->size();
      if (total_length < (total_offset+pNBytes))
        pNBytes = total_length - total_offset;
      std::memcpy(pDest, stub_frag->getContent() + total_offset, pNBytes);
      return;
    }
    case Fragment::Alignment:
    case Fragment::Fillment:
    default:
      return;
  }
}

FragmentRef::Address FragmentRef::deref()
{
  if (NULL == m_pFragment)
    return NULL;
  Address base = NULL;
  switch(m_pFragment->getKind()) {
    case Fragment::Region:
      base = static_cast<RegionFragment*>(m_pFragment)->getRegion().getBuffer();
      break;
    case Fragment::Alignment:
    case Fragment::Fillment:
    default:
      return NULL;
  }
  return base + m_Offset;
}

FragmentRef::ConstAddress FragmentRef::deref() const
{
  if (NULL == m_pFragment)
    return NULL;
  ConstAddress base = NULL;
  switch(m_pFragment->getKind()) {
    case Fragment::Region:
      base = static_cast<const RegionFragment*>(m_pFragment)->getRegion().getBuffer();
      break;
    case Fragment::Alignment:
    case Fragment::Fillment:
    default:
      return NULL;
  }
  return base + m_Offset;
}

FragmentRef::Offset FragmentRef::getOutputOffset() const
{
  Offset result = 0;
  if (NULL != m_pFragment)
    result = m_pFragment->getOffset();
  return (result + m_Offset);
}