/* Copyright (c) 2015  Gerald Knizia
 * 
 * This file is part of the IboView program (see: http://www.iboview.org)
 * 
 * IboView 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, version 3.
 * 
 * IboView 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 details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
 * 
 * Please see IboView documentation in README.txt for:
 * -- A list of included external software and their licenses. The included
 *    external software's copyright is not touched by this agreement.
 * -- Notes on re-distribution and contributions to/further development of
 *    the IboView software
 */

#include <stdexcept>
#include <sstream>
#include <new>

#ifdef _DEBUG
    #include <stdio.h> // for printf and fflush
#endif

#include "CxMemoryStack.h"
#include "CxDefs.h"
#include "CxOpenMpProxy.h"


namespace ct {

#define CX_DEFAULT_MEM_ALIGN 64   // align to 64 bytes.

// note: boundary must be size-of-2
inline size_t AlignSizeT(size_t iPos, size_t Boundary) {
   size_t
      iNew = ((iPos - 1) | (Boundary - 1)) + 1;
   return iNew;
}

// note: boundary must be size-of-2
inline char *AlignCharPtr(char *p, size_t Boundary) {
//    size_t
//       iPos = reinterpret_cast<size_t>(p),
//       iNew = ((iPos - 1) | (Boundary - 1)) + 1;
//    return reinterpret_cast<char*>(iNew);
   return reinterpret_cast<char*>(AlignSizeT(reinterpret_cast<size_t>(p), Boundary));
}


inline void FMemoryStack2::PushTopMark() {
#ifdef _DEBUG
   assert(m_Pos < m_Size - 8);
   *reinterpret_cast<size_t*>(&m_pDataAligned[m_Pos]) = 0xbadc0de;
   m_Pos += sizeof(size_t);
#endif
}
inline void FMemoryStack2::PopTopMark() {
#ifdef _DEBUG
   m_Pos -= sizeof(size_t);
   assert(m_Pos < m_Size - 8);
   if ( *reinterpret_cast<size_t*>(&m_pDataAligned[m_Pos]) != 0xbadc0de ) {
      printf("\n\n\n===================\nstack inconsistent!\n===================\n");
      assert(!"stack error");
   }
#endif
}

void* FMemoryStack2::Alloc(size_t nSizeInBytes)
{
   PopTopMark();
   size_t OldPos = m_Pos;
   m_Pos += AlignSizeT(nSizeInBytes, CX_DEFAULT_MEM_ALIGN);
   PushTopMark();
   if ( m_Pos >= m_Size )
      throw std::runtime_error("FMemoryStack2: Stack size exceeded.");
   return &m_pDataAligned[OldPos];
}

void FMemoryStack2::Free(void *p)
{
   PopTopMark();
   ptrdiff_t
      n = &m_pDataAligned[m_Pos] - (char*)p;
   if ( n < 0 )
      throw std::runtime_error("FMemoryStack2: Release address too low!");
   m_Pos -= n;
   // note: new start of heap should be aligned automagically, since p is required to come out of a aligned Alloc() call.
   PushTopMark();
}

size_t FMemoryStack2::MemoryLeft() {
   return m_Size - m_Pos;
}


FMemoryStack2::FMemoryStack2(char *pBase, size_t nActualSize, size_t nNeededSize)
   : m_pDataStart(0), m_pDataAligned(0)
{
   if (pBase && nActualSize >= nNeededSize)
      AssignMemory(pBase, nActualSize);
   else
      Create((nNeededSize != 0)? nNeededSize : nActualSize);
}

void FMemoryStack2::Create(size_t nSize)
{
   assert_rt(m_pDataStart == 0);
   m_Size = nSize;
   m_Pos = 0;
   m_pDataStart = 0;
   m_bOwnMemory = false;
   if ( nSize != 0 ) {
      m_pDataStart = new(std::nothrow) char[nSize];
      m_bOwnMemory = true;
      if ( m_pDataStart == 0 ) {
         std::stringstream str; str.precision(2); str.setf(std::ios::fixed);
         str << "FMemoryStack2: Sorry, failed to allocate " << (static_cast<double>(nSize)/static_cast<double>(1ul<<20)) << " MB of memory.";
         throw std::runtime_error(str.str());
      }

    if (nSize < 2*CX_DEFAULT_MEM_ALIGN)
        throw std::runtime_error("FMemoryStack2: assigned workspace is too small.");
    m_pDataAligned = AlignCharPtr(m_pDataStart, CX_DEFAULT_MEM_ALIGN);
    m_Size -= m_pDataAligned - m_pDataStart;
    PushTopMark();
   }
}

void FMemoryStack2::AssignMemory(char *pBase_, size_t nSize)
{
   assert(m_pDataStart == 0 && pBase_ != 0);
   Create(0);
   m_bOwnMemory = false;
   m_pDataStart = pBase_;
   m_Size = nSize;
   m_Pos = 0;
   if (nSize < 2*CX_DEFAULT_MEM_ALIGN)
      throw std::runtime_error("FMemoryStack2: assigned workspace is too small.");
   m_pDataAligned = AlignCharPtr(m_pDataStart, CX_DEFAULT_MEM_ALIGN);
   m_Size -= m_pDataAligned - m_pDataStart;
   PushTopMark();
}

void FMemoryStack2::Destroy()
{
#ifdef _DEBUG
   if ( m_Size != 0 )
      PopTopMark();
   else
      assert(m_pDataStart == 0);
   assert(m_Pos == 0);
#endif // _DEBUG
   if ( m_bOwnMemory )
      delete []m_pDataStart;
   m_pDataStart = 0;
   m_pDataAligned = 0;
}


FMemoryStack::~FMemoryStack()
{
}

FMemoryStack2::~FMemoryStack2()
{
   if ( m_bOwnMemory )
      delete []m_pDataStart;
   m_pDataStart = (char*)0xbadc0de;
   m_pDataAligned = (char*)0xbadc0de;
}

void FMemoryStack2::Align(uint Boundary)
{
   assert(m_pDataAligned == AlignCharPtr(m_pDataAligned, CX_DEFAULT_MEM_ALIGN));
   PopTopMark();
   if (Boundary <= CX_DEFAULT_MEM_ALIGN) {
       m_Pos = AlignSizeT(m_Pos, Boundary);
   } else {
       m_Pos = AlignCharPtr(&m_pDataAligned[m_Pos], Boundary) - m_pDataAligned;
   }
   PushTopMark();
}



void FMemoryStack::Align(uint Boundary)
{
   size_t
      iPos = reinterpret_cast<size_t>(Alloc(0)),
      iNew = ((iPos - 1) | (Boundary - 1)) + 1;
   Alloc(iNew - iPos);
}










FMemoryStackArray::FMemoryStackArray( FMemoryStack &BaseStack )
    : pBaseStack(&BaseStack)
{
    nThreads = omp_get_max_threads();
    pSubStacks = new FMemoryStack2[nThreads];
    size_t
        nSize = static_cast<size_t>(0.98 * static_cast<double>(pBaseStack->MemoryLeft() / nThreads));

    pBaseStack->Alloc(pStackBase, nThreads * nSize);
    for (size_t i = 0; i < nThreads; ++ i)
        pSubStacks[i].AssignMemory(pStackBase + i * nSize, nSize);
}

void FMemoryStackArray::Release()
{
    assert(pBaseStack != 0);
    delete []pSubStacks;
    pBaseStack->Free(pStackBase);
    pStackBase = 0;
    pBaseStack = 0;
}

FMemoryStackArray::~FMemoryStackArray()
{
    if (pBaseStack)
        Release();
}

FMemoryStack2 &FMemoryStackArray::GetStackOfThread()
{
    int iThread = omp_get_thread_num();
    assert(iThread < (int)nThreads);
    return pSubStacks[iThread];
}






} // namespace ct

// kate: indent-width 4
