/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-  */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef _MDB_
#include "mdb.h"
#endif

#ifndef _MORK_
#include "mork.h"
#endif

#ifndef _MORKNODE_
#include "morkNode.h"
#endif

#ifndef _MORKZONE_
#include "morkZone.h"
#endif

#ifndef _MORKENV_
#include "morkEnv.h"
#endif

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

// { ===== begin morkNode interface =====
// public: // morkNode virtual methods
void morkZone::CloseMorkNode(morkEnv* ev) // CloseZone() only if open
{
  if ( this->IsOpenNode() )
  {
    this->MarkClosing();
    this->CloseZone(ev);
    this->MarkShut();
  }
}

morkZone::~morkZone() // assert that CloseZone() executed earlier
{
  MORK_ASSERT(this->IsShutNode());
}

// public: // morkMap construction & destruction
morkZone::morkZone(morkEnv* ev, const morkUsage& inUsage,
  nsIMdbHeap* ioNodeHeap, nsIMdbHeap* ioZoneHeap)
: morkNode(ev, inUsage, ioNodeHeap)
, mZone_Heap( 0 )
, mZone_HeapVolume( 0 )
, mZone_BlockVolume( 0 )
, mZone_RunVolume( 0 )
, mZone_ChipVolume( 0 )
  
, mZone_FreeOldRunVolume( 0 )
  
, mZone_HunkCount( 0 )
, mZone_FreeOldRunCount( 0 )

, mZone_HunkList( 0 )
, mZone_FreeOldRunList( 0 )
  
, mZone_At( 0 )
, mZone_AtSize( 0 )
    
  // morkRun*     mZone_FreeRuns[ morkZone_kBuckets + 1 ];
{

  morkRun** runs = mZone_FreeRuns;
  morkRun** end = runs + (morkZone_kBuckets + 1); // one past last slot
  --runs; // prepare for preincrement
  while ( ++runs < end ) // another slot in array?
    *runs = 0; // clear all the slots
  
  if ( ev->Good() )
  {
    if ( ioZoneHeap )
    {
      nsIMdbHeap_SlotStrongHeap(ioZoneHeap, ev, &mZone_Heap);
      if ( ev->Good() )
        mNode_Derived = morkDerived_kZone;
    }
    else
      ev->NilPointerError();
  }
}

void morkZone::CloseZone(morkEnv* ev) // called by CloseMorkNode()
{
    if ( this->IsNode() )
    {
      nsIMdbHeap* heap = mZone_Heap;
      if ( heap )
      {
        morkHunk* hunk = 0;
        nsIMdbEnv* mev = ev->AsMdbEnv();
        
        morkHunk* next = mZone_HunkList;
        while ( ( hunk = next ) != 0 )
        {
#ifdef morkHunk_USE_TAG_SLOT
          if ( !hunk->HunkGoodTag()  )
            hunk->BadHunkTagWarning(ev);
#endif /* morkHunk_USE_TAG_SLOT */

          next = hunk->HunkNext();
          heap->Free(mev, hunk);
        }
      }
      nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mZone_Heap);
      this->MarkShut();
    }
    else
      this->NonNodeError(ev);
}

// } ===== end morkNode methods =====

/*static*/ void
morkZone::NonZoneTypeError(morkEnv* ev)
{
  ev->NewError("non morkZone");
}

/*static*/ void
morkZone::NilZoneHeapError(morkEnv* ev)
{
  ev->NewError("nil mZone_Heap");
}

/*static*/ void
morkHunk::BadHunkTagWarning(morkEnv* ev)
{
  ev->NewWarning("bad mHunk_Tag");
}

/*static*/ void
morkRun::BadRunTagError(morkEnv* ev)
{
  ev->NewError("bad mRun_Tag");
}

/*static*/ void
morkRun::RunSizeAlignError(morkEnv* ev)
{
  ev->NewError("bad RunSize() alignment");
}

// { ===== begin morkZone methods =====


mork_size morkZone::zone_grow_at(morkEnv* ev, mork_size inNeededSize)
{
  mZone_At = 0;       // remove any ref to current hunk
  mZone_AtSize = 0;   // zero available bytes in current hunk
  
  mork_size runSize = 0; // actual size of a particular run
  
  // try to find a run in old run list with at least inNeededSize bytes:
  morkRun* run = mZone_FreeOldRunList; // cursor in list scan
  morkRun* prev = 0; // the node before run in the list scan
 
  while ( run ) // another run in list to check?
  {
    morkOldRun* oldRun = (morkOldRun*) run;
    mork_size oldSize = oldRun->OldSize();
    if ( oldSize >= inNeededSize ) // found one big enough?
    {
      runSize = oldSize;
      break; // end while loop early
    }
    prev = run; // remember last position in singly linked list
    run = run->RunNext(); // advance cursor to next node in list
  }
  if ( runSize && run ) // found a usable old run?
  {
    morkRun* next = run->RunNext();
    if ( prev ) // another node in free list precedes run?
      prev->RunSetNext(next); // unlink run
    else
      mZone_FreeOldRunList = next; // unlink run from head of list
      
    morkOldRun *oldRun = (morkOldRun *) run;
    oldRun->OldSetSize(runSize);
    mZone_At = (mork_u1*) run;
    mZone_AtSize = runSize;

#ifdef morkZone_CONFIG_DEBUG
#ifdef morkZone_CONFIG_ALIGN_8
    mork_ip lowThree = ((mork_ip) mZone_At) & 7;
    if ( lowThree ) // not 8 byte aligned?
#else /*morkZone_CONFIG_ALIGN_8*/
    mork_ip lowTwo = ((mork_ip) mZone_At) & 3;
    if ( lowTwo ) // not 4 byte aligned?
#endif /*morkZone_CONFIG_ALIGN_8*/
      ev->NewWarning("mZone_At not aligned");
#endif /*morkZone_CONFIG_DEBUG*/
  }
  else // need to allocate a brand new run
  {
    inNeededSize += 7; // allow for possible alignment padding
    mork_size newSize = ( inNeededSize > morkZone_kNewHunkSize )?
      inNeededSize : morkZone_kNewHunkSize;
      
    morkHunk* hunk = this->zone_new_hunk(ev, newSize);
    if ( hunk )
    {
      morkRun* hunkRun = hunk->HunkRun();
      mork_u1* at = (mork_u1*) hunkRun->RunAsBlock();
      mork_ip lowBits = ((mork_ip) at) & 7;
      if ( lowBits ) // not 8 byte aligned?
      {
        mork_ip skip = (8 - lowBits); // skip the complement to align
        at += skip;
        newSize -= skip;
      }
      mZone_At = at;
      mZone_AtSize = newSize;
    }
  }
  
  return mZone_AtSize;
}

morkHunk* morkZone::zone_new_hunk(morkEnv* ev, mdb_size inSize) // alloc  
{
  mdb_size hunkSize = inSize + sizeof(morkHunk);
  void* outBlock = 0; // we are going straight to the heap:
  mZone_Heap->Alloc(ev->AsMdbEnv(), hunkSize, &outBlock);
  if ( outBlock )
  {
#ifdef morkZone_CONFIG_VOL_STATS
    mZone_HeapVolume += hunkSize; // track all heap allocations
#endif /* morkZone_CONFIG_VOL_STATS */
  
    morkHunk* hunk = (morkHunk*) outBlock;
#ifdef morkHunk_USE_TAG_SLOT
    hunk->HunkInitTag();
#endif /* morkHunk_USE_TAG_SLOT */
  
    hunk->HunkSetNext(mZone_HunkList);
    mZone_HunkList = hunk;
    ++mZone_HunkCount;
    
    morkRun* run = hunk->HunkRun();
    run->RunSetSize(inSize);
#ifdef morkRun_USE_TAG_SLOT
    run->RunInitTag();
#endif /* morkRun_USE_TAG_SLOT */
    
    return hunk;
  }
  if ( ev->Good() ) // got this far without any error reported yet?
    ev->OutOfMemoryError();
  return (morkHunk*) 0;
}

void* morkZone::zone_new_chip(morkEnv* ev, mdb_size inSize) // alloc  
{
#ifdef morkZone_CONFIG_VOL_STATS
  mZone_BlockVolume += inSize; // sum sizes of both chips and runs
#endif /* morkZone_CONFIG_VOL_STATS */
  
  mork_u1* at = mZone_At;
  mork_size atSize = mZone_AtSize; // available bytes in current hunk
  if ( atSize >= inSize ) // current hunk can satisfy request?
  {
    mZone_At = at + inSize;
    mZone_AtSize = atSize - inSize;
    return at;
  }
  else if ( atSize > morkZone_kMaxHunkWaste ) // over max waste allowed?
  {
    morkHunk* hunk = this->zone_new_hunk(ev, inSize);
    if ( hunk )
      return hunk->HunkRun();
      
    return (void*) 0; // show allocation has failed
  }
  else // get ourselves a new hunk for suballocation:
  {
    atSize = this->zone_grow_at(ev, inSize); // get a new hunk
  }

  if ( atSize >= inSize ) // current hunk can satisfy request?
  {
    at = mZone_At;
    mZone_At = at + inSize;
    mZone_AtSize = atSize - inSize;
    return at;
  }
  
  if ( ev->Good() ) // got this far without any error reported yet?
    ev->OutOfMemoryError();
    
  return (void*) 0; // show allocation has failed
}

void* morkZone::ZoneNewChip(morkEnv* ev, mdb_size inSize) // alloc  
{
#ifdef morkZone_CONFIG_ARENA

#ifdef morkZone_CONFIG_DEBUG
  if ( !this->IsZone() )
    this->NonZoneTypeError(ev);
  else if ( !mZone_Heap )
    this->NilZoneHeapError(ev);
#endif /*morkZone_CONFIG_DEBUG*/

#ifdef morkZone_CONFIG_ALIGN_8
  inSize += 7;
  inSize &= ~((mork_ip) 7); // force to multiple of 8 bytes
#else /*morkZone_CONFIG_ALIGN_8*/
  inSize += 3;
  inSize &= ~((mork_ip) 3); // force to multiple of 4 bytes
#endif /*morkZone_CONFIG_ALIGN_8*/

#ifdef morkZone_CONFIG_VOL_STATS
  mZone_ChipVolume += inSize; // sum sizes of chips only
#endif /* morkZone_CONFIG_VOL_STATS */

  return this->zone_new_chip(ev, inSize);

#else /*morkZone_CONFIG_ARENA*/
  void* outBlock = 0;
  mZone_Heap->Alloc(ev->AsMdbEnv(), inSize, &outBlock);
  return outBlock;
#endif /*morkZone_CONFIG_ARENA*/
  
}
  
// public: // ...but runs do indeed know how big they are
void* morkZone::ZoneNewRun(morkEnv* ev, mdb_size inSize) // alloc  
{
#ifdef morkZone_CONFIG_ARENA

#ifdef morkZone_CONFIG_DEBUG
  if ( !this->IsZone() )
    this->NonZoneTypeError(ev);
  else if ( !mZone_Heap )
    this->NilZoneHeapError(ev);
#endif /*morkZone_CONFIG_DEBUG*/

  inSize += morkZone_kRoundAdd;
  inSize &= morkZone_kRoundMask;
  if ( inSize <= morkZone_kMaxCachedRun )
  {
    morkRun** bucket = mZone_FreeRuns + (inSize >> morkZone_kRoundBits);
    morkRun* hit = *bucket;
    if ( hit ) // cache hit?
    {
      *bucket = hit->RunNext();
      hit->RunSetSize(inSize);
      return hit->RunAsBlock();
    }
  }
  mdb_size blockSize = inSize + sizeof(morkRun); // plus run overhead
#ifdef morkZone_CONFIG_VOL_STATS
  mZone_RunVolume += blockSize; // sum sizes of runs only
#endif /* morkZone_CONFIG_VOL_STATS */
  morkRun* run = (morkRun*) this->zone_new_chip(ev, blockSize);
  if ( run )
  {
    run->RunSetSize(inSize);
#ifdef morkRun_USE_TAG_SLOT
    run->RunInitTag();
#endif /* morkRun_USE_TAG_SLOT */
    return run->RunAsBlock();
  }
  
  if ( ev->Good() ) // got this far without any error reported yet?
    ev->OutOfMemoryError();
  
  return (void*) 0; // indicate failed allocation

#else /*morkZone_CONFIG_ARENA*/
  void* outBlock = 0;
  mZone_Heap->Alloc(ev->AsMdbEnv(), inSize, &outBlock);
  return outBlock;
#endif /*morkZone_CONFIG_ARENA*/
}

void morkZone::ZoneZapRun(morkEnv* ev, void* ioRunBlock) // free
{
#ifdef morkZone_CONFIG_ARENA

  morkRun* run = morkRun::BlockAsRun(ioRunBlock);
  mdb_size runSize = run->RunSize();
#ifdef morkZone_CONFIG_VOL_STATS
  mZone_BlockVolume -= runSize; // tracking sizes of both chips and runs
#endif /* morkZone_CONFIG_VOL_STATS */

#ifdef morkZone_CONFIG_DEBUG
  if ( !this->IsZone() )
    this->NonZoneTypeError(ev);
  else if ( !mZone_Heap )
    this->NilZoneHeapError(ev);
  else if ( !ioRunBlock )
    ev->NilPointerError();
  else if ( runSize & morkZone_kRoundAdd )
    run->RunSizeAlignError(ev);
#ifdef morkRun_USE_TAG_SLOT
  else if ( !run->RunGoodTag() )
    run->BadRunTagError(ev);
#endif /* morkRun_USE_TAG_SLOT */
#endif /*morkZone_CONFIG_DEBUG*/

  if ( runSize <= morkZone_kMaxCachedRun ) // goes into free run list?
  {
    morkRun** bucket = mZone_FreeRuns + (runSize >> morkZone_kRoundBits);
    run->RunSetNext(*bucket); // push onto free run list
    *bucket = run;
  }
  else // free old run list
  {
    run->RunSetNext(mZone_FreeOldRunList); // push onto free old run list
    mZone_FreeOldRunList = run;
    ++mZone_FreeOldRunCount;
#ifdef morkZone_CONFIG_VOL_STATS
    mZone_FreeOldRunVolume += runSize;
#endif /* morkZone_CONFIG_VOL_STATS */

    morkOldRun* oldRun = (morkOldRun*) run; // to access extra size slot
    oldRun->OldSetSize(runSize); // so we know how big this is later
  }

#else /*morkZone_CONFIG_ARENA*/
  mZone_Heap->Free(ev->AsMdbEnv(), ioRunBlock);
#endif /*morkZone_CONFIG_ARENA*/
}

void* morkZone::ZoneGrowRun(morkEnv* ev, void* ioRunBlock, mdb_size inSize)
{
#ifdef morkZone_CONFIG_ARENA

  morkRun* run = morkRun::BlockAsRun(ioRunBlock);
  mdb_size runSize = run->RunSize();

#ifdef morkZone_CONFIG_DEBUG
  if ( !this->IsZone() )
    this->NonZoneTypeError(ev);
  else if ( !mZone_Heap )
    this->NilZoneHeapError(ev);
#endif /*morkZone_CONFIG_DEBUG*/

#ifdef morkZone_CONFIG_ALIGN_8
  inSize += 7;
  inSize &= ~((mork_ip) 7); // force to multiple of 8 bytes
#else /*morkZone_CONFIG_ALIGN_8*/
  inSize += 3;
  inSize &= ~((mork_ip) 3); // force to multiple of 4 bytes
#endif /*morkZone_CONFIG_ALIGN_8*/

  if ( inSize > runSize )
  {
    void* newBuf = this->ZoneNewRun(ev, inSize);
    if ( newBuf )
    {
      MORK_MEMCPY(newBuf, ioRunBlock, runSize);
      this->ZoneZapRun(ev, ioRunBlock);
      
      return newBuf;
    }
  }
  else
    return ioRunBlock; // old size is big enough
  
  if ( ev->Good() ) // got this far without any error reported yet?
    ev->OutOfMemoryError();
  
  return (void*) 0; // indicate failed allocation

#else /*morkZone_CONFIG_ARENA*/
  void* outBlock = 0;
  mZone_Heap->Free(ev->AsMdbEnv(), ioRunBlock);
  return outBlock;
#endif /*morkZone_CONFIG_ARENA*/
}

//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789

// { ===== begin nsIMdbHeap methods =====
/*virtual*/ nsresult
morkZone::Alloc(nsIMdbEnv* mev, // allocate a piece of memory
  mdb_size inSize,   // requested size of new memory block 
  void** outBlock)  // memory block of inSize bytes, or nil
{
  nsresult outErr = NS_OK;
  void* block = 0;
  morkEnv* ev = morkEnv::FromMdbEnv(mev);
  if ( ev )
  {
    block = this->ZoneNewRun(ev, inSize);
    outErr = ev->AsErr();
  }
  else
    outErr = morkEnv_kOutOfMemoryError;

  if ( outBlock )
    *outBlock = block;
    
  return outErr;
}
  
/*virtual*/ nsresult
morkZone::Free(nsIMdbEnv* mev, // free block allocated earlier by Alloc()
  void* inBlock)
{
  nsresult outErr = NS_OK;
  if ( inBlock )
  {
    morkEnv* ev = morkEnv::FromMdbEnv(mev);
    if ( ev )
    {
      this->ZoneZapRun(ev, inBlock);
      outErr = ev->AsErr();
    }
    else
      // XXX 1 is not a valid nsresult
      outErr = static_cast<nsresult>(1);
  }
    
  return outErr;
}

// } ===== end nsIMdbHeap methods =====