//
// Copyright (C) 2000-2002 Andrey Slepuhin <pooh@msu.ru>
//
// libp++ is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// libp++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libp++; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// $Source$
// $Revision$
// $Date$
// Author: Andrey Slepuhin <pooh@msu.ru>

#include "pxx_heap.ih"
#include "pxx_common.ih"
#include "pxx_sys_error.ih"
#include <errno.h>

#ifdef WINDOWS
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif

namespace pxx
{

Heap::Heap (
  size_t _init_size /* = 0 */,      // Initial heap size, page size by default
  size_t const _max_size /* = 0 */, // Maximum size, not limited by default
  void* const _start /* = null */,
  int _fd /* = -1 */
) throw (SysError) :
  start_addr (_start),
  fd (_fd)
{
  cur_size_log = get_order(_init_size == 0 ? page_size : _init_size);
  current_size = 1UL << cur_size_log;
  if (_max_size != 0) {
    max_size_log = get_order(_max_size);
    max_size = 1UL << max_size_log;
  } else {
    max_size = max_size_log = 0;
  }
  if ((max_size != 0) && (current_size > max_size))
    FATAL("Initial heap size greater than maximal size");
#ifndef WINDOWS
  int mmap_flags = 0;
  if (start_addr != null) mmap_flags |= MAP_FIXED;
  if (fd != -1) {
    mmap_flags |= MAP_SHARED;
    int res = ftruncate(fd, current_size);
    if (res != 0) throw_sys_error(errno);
  } else {
    mmap_flags |= MAP_PRIVATE | MAP_ANON;
  }
#ifdef USE_MREMAP
  void* res =
    mmap(start_addr, current_size, PROT_READ | PROT_WRITE, mmap_flags, fd, 0);
#else
  void* res =
    mmap(start_addr, max_size, PROT_READ | PROT_WRITE, mmap_flags, fd, 0);
#endif
  if (res == MAP_FAILED) throw_sys_error(errno);
  start_addr = res;
#else // WINDOWS
  LPVOID res = VirtualAlloc(start_addr, max_size, MEM_RESERVE, PAGE_NOACCESS);
  if (res == NULL) throw_sys_error(errno);
  start_addr = res;
  res = VirtualAlloc(start_addr, current_size, MEM_COMMIT, PAGE_READWRITE);
  if (res == NULL) throw_sys_error(errno);
#endif // WINDOWS
}

Heap::~Heap () throw (SysError)
{
#ifndef WINDOWS
#ifdef USE_MREMAP
  max_size = current_size;
#endif
  int res = munmap(start_addr, max_size);
  if (res != 0) throw_sys_error(errno);
#else // WINDOWS
  bool res = VirtualFree(start_addr, 0, MEM_RELEASE);
  if (res != 0) throw_sys_error(errno);
#endif // WINDOWS
}

void Heap::expand (unsigned _order /* = 1 */) throw (SysError)
{
  size_t new_size = current_size << _order;
  if ((max_size != 0) && ((new_size > max_size) || (new_size == 0)))
    FATAL("Heap exhausted");
#ifdef WINDOWS
  LPVOID res = VirtualAlloc(start_addr, new_size, MEM_COMMIT, PAGE_READWRITE);
  if (res == NULL) throw_sys_error(errno);
#elif defined USE_MREMAP
  void* res = mremap(start_addr, current_size, new_size, 0);
  if (res == MAP_FAILED) throw_sys_error(errno);
  if (res != start_addr)
    FATAL("mremap() changes a heap start address");
  if (fd != -1) {
    int res = ftruncate(fd, new_size);
    if (res != 0) throw_sys_error(errno);
  }
#endif
  cur_size_log += _order;
  current_size = new_size;
}

}
