//===- MemoryArea.cpp -----------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <mcld/Support/MemoryArea.h>
#include <mcld/Support/Space.h>
#include <mcld/Support/MemoryRegion.h>
#include <mcld/Support/FileHandle.h>
#include <mcld/Support/MsgHandling.h>

using namespace mcld;

//===--------------------------------------------------------------------===//
// MemoryArea
//===--------------------------------------------------------------------===//
// MemoryArea - special constructor
// This constructor is used for *SPECIAL* situation. I'm sorry I can not
// reveal what is the special situation.
MemoryArea::MemoryArea(Space& pUniverse)
  : m_pFileHandle(NULL) {
  m_SpaceMap.insert(std::make_pair(Key(pUniverse.start(), pUniverse.size()),
                                   &pUniverse));
}

MemoryArea::MemoryArea(FileHandle& pFileHandle)
  : m_pFileHandle(&pFileHandle) {
}

MemoryArea::~MemoryArea()
{
}

// The layout of MemorySpace in the virtual memory space
//
// |  : page boundary
// [,]: MemoryRegion
// -  : fillment
// =  : data
//
// |---[=|====|====|==]--|
// ^   ^              ^  ^
// |   |              |  |
// | r_start      +r_len |
// space.data      +space.size
//
// space.file_offset is the offset of the mapped file segment from the start of
// the file. if the MemorySpace's type is ALLOCATED_ARRAY, the distances of
// (space.data, r_start) and (r_len, space.size) are zero.
//
MemoryRegion* MemoryArea::request(size_t pOffset, size_t pLength)
{
  Space* space = find(pOffset, pLength);
  if (NULL == space) {
    // not found
    if (NULL == m_pFileHandle) {
      // if m_pFileHandle is NULL, clients delegate us an universal Space and
      // we never remove it. In that way, space can not be NULL.
      unreachable(diag::err_out_of_range_region) << pOffset << pLength;
    }

    space = Space::Create(*m_pFileHandle, pOffset, pLength);
    m_SpaceMap.insert(std::make_pair(Key(space->start(), space->size()), space));
  }

  // adjust r_start
  off_t distance = pOffset - space->start();
  void* r_start = space->memory() + distance;

  // now, we have a legal space to hold the new MemoryRegion
  return MemoryRegion::Create(r_start, pLength, *space);
}

// release - release a MemoryRegion
void MemoryArea::release(MemoryRegion* pRegion)
{
  if (NULL == pRegion)
    return;

  Space *space = pRegion->parent();
  MemoryRegion::Destroy(pRegion);

  if (0 == space->numOfRegions()) {

    if (NULL != m_pFileHandle) {
      // if m_pFileHandle is NULL, clients delegate us an universal Space and
      // we never remove it. Otherwise, we have to synchronize and release
      // Space.
      if (m_pFileHandle->isWritable()) {
        // synchronize writable space before we release it.
        Space::Sync(space, *m_pFileHandle);
      }

      std::pair<SpaceMapType::iterator, SpaceMapType::iterator> range =
        m_SpaceMap.equal_range(Key(space->start(), space->size()));
      SpaceMapType::iterator it;
      for (it = range.first; it != range.second; ++it) {
        if (space == it->second)
          break;
      }
      m_SpaceMap.erase(it);

      Space::Release(space, *m_pFileHandle);
      assert(NULL != space);
      Space::Destroy(space);
    }
  }
}

// clear - release all MemoryRegions
void MemoryArea::clear()
{
  if (NULL == m_pFileHandle)
    return;

  SpaceMapType::iterator space, sEnd = m_SpaceMap.end();
  if (m_pFileHandle->isWritable()) {
    for (space = m_SpaceMap.begin(); space != sEnd; ++space) {
      Space::Sync(space->second, *m_pFileHandle);
      Space::Release(space->second, *m_pFileHandle);
      assert(NULL != space->second);
      Space::Destroy(space->second);
    }
  }
  else {
    for (space = m_SpaceMap.begin(); space != sEnd; ++space) {
      Space::Release(space->second, *m_pFileHandle);
      assert(NULL != space->second);
      Space::Destroy(space->second);
    }
  }

  m_SpaceMap.clear();
}

//===--------------------------------------------------------------------===//
// SpaceList methods
//===--------------------------------------------------------------------===//
Space* MemoryArea::find(size_t pOffset, size_t pLength)
{
  SpaceMapType::iterator it = m_SpaceMap.find(Key(pOffset, pLength));
  if (it != m_SpaceMap.end())
    return it->second;

  return NULL;
}

const Space* MemoryArea::find(size_t pOffset, size_t pLength) const
{
  SpaceMapType::const_iterator it = m_SpaceMap.find(Key(pOffset, pLength));
  if (it != m_SpaceMap.end())
    return it->second;

  return NULL;
}