//===- MemoryArea.h -------------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef MCLD_SUPPORT_MEMORY_AREA_H
#define MCLD_SUPPORT_MEMORY_AREA_H
#ifdef ENABLE_UNITTEST
#include <gtest.h>
#endif

#include <mcld/ADT/Uncopyable.h>
#include <cstddef>
#include <map>

#if defined(ENABLE_UNITTEST)
namespace mcldtest {
  class MemoryAreaTest;
} // namespace of mcldtest
#endif

namespace mcld {

class Space;
class FileHandle;
class MemoryRegion;

/** \class MemoryArea
 *  \brief MemoryArea is used to manage distinct MemoryRegions of address space.
 *
 *  Good linkers must well manipulate memory mapped I/O and dynamic memory.
 *  In MCLinker, MemoryArea is the decision-maker to use memory mapped I/O or
 *  dynamic memory. When a client requests MemoryArea for a piece of memory
 *  to hold a part of a file, MemoryArea is going to see whether the requested
 *  part of the file is already in any existing memory which is requested
 *  before. If it is, MemoryArea creates a new MemoryRegion within the memory
 *  requested before. Otherwise, MemoryArea uses memory mapped I/O or dynamic
 *  memory to load the file.
 *
 *  If the part a file being loaded is larger than 3/4 pages, MemoryArea uses
 *  memory mapped I/O to load the file. Otherwise, MemoryArea uses dynamic
 *  memory to read the content of file into the memory space.
 */
class MemoryArea : private Uncopyable
{
  friend class MemoryAreaFactory;
public:
  // constructor by file handler.
  // If the given file handler is read-only, client can not request a region
  // that out of the file size.
  // @param pFileHandle - file handler
  explicit MemoryArea(FileHandle& pFileHandle);

  // constructor by set universal space.
  // Client can not request a region that out of the universal space.
  // @param pUniverse - file handler
  explicit MemoryArea(Space& pUniverse);

  // destructor
  ~MemoryArea();

  // request - create a MemoryRegion within a sufficient space
  // find an existing space to hold the MemoryRegion.
  // if MemoryArea does not find such space, then it creates a new space and
  // assign a MemoryRegion into the space.
  MemoryRegion* request(size_t pOffset, size_t pLength);

  // release - release a MemoryRegion.
  // release a MemoryRegion does not cause
  void release(MemoryRegion* pRegion);

  // clear - release all memory regions.
  void clear();

  const FileHandle* handler() const { return m_pFileHandle; }
  FileHandle*       handler()       { return m_pFileHandle; }

  bool hasHandler() const { return (NULL != m_pFileHandle); }

  // -----  space list methods  ----- //
  Space* find(size_t pOffset, size_t pLength);

  const Space* find(size_t pOffset, size_t pLength) const;

private:
  class Key {
  public:
    Key(size_t pOffset, size_t pLength)
    : m_Offset(pOffset), m_Length(pLength)
    { }

    size_t offset() const { return m_Offset; }

    size_t length() const { return m_Length; }

    struct Compare {
      bool operator()(const Key& KEY1, const Key& KEY2) const
      {
        return KEY1.offset() + KEY1.length() < KEY2.offset() ||
               (KEY1.offset() < KEY2.offset() &&
                (KEY1.offset() + KEY1.length() < KEY2.offset() + KEY2.length()));
      }
    };

  private:
    size_t m_Offset;
    size_t m_Length;
  };

  typedef std::multimap<Key, Space*, Key::Compare> SpaceMapType;

private:
  SpaceMapType m_SpaceMap;
  FileHandle* m_pFileHandle;
};

} // namespace of mcld

#endif