/* XMLPPM: an XML compressor
Copyright (C) 2003 James Cheney

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Contacting the author:
James Cheney
Computer Science Department
Cornell University
Ithaca, NY 14850

jcheney@cs.cornell.edu
*/
/****************************************************************************
 *  This file is part of PPMd project                                       *
 *  Written and distributed to public domain by Dmitry Shkarin 1997,        *
 *  1999-2001                                                               *
 *  Contents: memory allocation routines                                    *
 ****************************************************************************/
/* Derived 1/2003 from PPMd sources by James Cheney to use with XMLPPM 
 */
#ifndef __SUBALLOC_HPP__
#define __SUBALLOC_HPP__

const unsigned int UNIT_SIZE = 12;
const unsigned int N1 = 4;
const unsigned int N2 = 4;
const unsigned int N3 = 4;
const unsigned int N4 = (128+3-1*N1-2*N2-3*N3)/4;
const unsigned int N_INDEXES = N1 + N2 + N3 + N4;

#pragma pack(1)
struct BLK_NODE
{
  DWORD Stamp;
  BLK_NODE *next;
  BOOL avail () const
  {
    return (next != NULL);
  }
  void link (BLK_NODE * p)
  {
    p->next = next;
    next = p;
  }
  void unlink ()
  {
    next = next->next;
  }
  void *remove ()
  {
    BLK_NODE *p = next;
    unlink ();
    Stamp--;
    return p;
  }
  inline void insert (void *pv, int NU);
};

struct MEM_BLK:public BLK_NODE
{
  DWORD NU;
}
#ifdef GLOBAL_NOW
_PACK_ATTR
#endif
;
#pragma pack()

inline void
BLK_NODE::insert (void *pv, int NU)
{
  MEM_BLK *p = (MEM_BLK *) pv;
  link (p);
  p->Stamp = ~0UL;
  p->NU = NU;
  Stamp++;
}

inline UINT
U2B (UINT NU)
{
  return 8 * NU + 4 * NU;
}


struct SubAlloc {
  struct BLK_NODE BList[N_INDEXES];

  BYTE Indx2Units[N_INDEXES], Units2Indx[128];	// constants
  DWORD GlueCount, SubAllocatorSize;
  BYTE *HeapStart, *pText, *UnitsStart, *LoUnit, *HiUnit;
  UINT Restarts;

  SubAlloc() {
    unsigned int i,k;
    Restarts = 0;
    SubAllocatorSize = 0;
    for (i = 0, k = 1; i < N1; i++, k += 1)
      Indx2Units[i] = k;
    for (k++; i < N1 + N2; i++, k += 2)
      Indx2Units[i] = k;
    for (k++; i < N1 + N2 + N3; i++, k += 3)
      Indx2Units[i] = k;
    for (k++; i < N1 + N2 + N3 + N4; i++, k += 4)
      Indx2Units[i] = k;
    for (k = i = 0; k < 128; k++)
      {
	i += (Indx2Units[i] < k + 1);
	Units2Indx[k] = i;
      }
  }
  inline void
  SplitBlock (void *pv, UINT OldIndx, UINT NewIndx)
  {
    UINT i, k, UDiff = Indx2Units[OldIndx] - Indx2Units[NewIndx];
    BYTE *p = ((BYTE *) pv) + U2B (Indx2Units[NewIndx]);
    if (Indx2Units[i = Units2Indx[UDiff - 1]] != UDiff)
      {
	k = Indx2Units[--i];
	BList[i].insert (p, k);
	p += U2B (k);
	UDiff -= k;
      }
    BList[Units2Indx[UDiff - 1]].insert (p, UDiff);
  }
  
  DWORD
  GetUsedMemory ()
  {
    DWORD i, RetVal =
      SubAllocatorSize - (HiUnit - LoUnit) - (UnitsStart - pText);
    for (i = 0; i < N_INDEXES; i++)
      RetVal -= UNIT_SIZE * Indx2Units[i] * BList[i].Stamp;
    return RetVal;
  }
  
  void
  StopSubAllocator ()
  {
    if (SubAllocatorSize)
      {
	SubAllocatorSize = 0;
	delete[]HeapStart;
      }
  }
  BOOL
  StartSubAllocator (UINT SASize)
  {
    DWORD t = SASize << 20U;
    if (SubAllocatorSize == t)
      return TRUE;
    StopSubAllocator ();
    if ((HeapStart = new BYTE[t]) == NULL)
      return FALSE;
    SubAllocatorSize = t;
    return TRUE;
  }
  inline void
  InitSubAllocator ()
  {
    Restarts++;
    memset (BList, 0, sizeof (BList));
    HiUnit = (pText = HeapStart) + SubAllocatorSize;
    UINT Diff = UNIT_SIZE * (SubAllocatorSize / 8 / UNIT_SIZE * 7);
    LoUnit = UnitsStart = HiUnit - Diff;
    GlueCount = 0;
  }
  void
  GlueFreeBlocks ()
  {
    UINT i, k, sz;
    MEM_BLK s0, *p, *p0, *p1;
    if (LoUnit != HiUnit)
      *LoUnit = 0;
    for (i = 0, (p0 = &s0)->next = NULL; i < N_INDEXES; i++)
      while (BList[i].avail ())
	{
	  p = (MEM_BLK *) BList[i].remove ();
	  if (!p->NU)
	    continue;
	  while ((p1 = p + p->NU)->Stamp == ~0UL)
	    {
	      p->NU += p1->NU;
	      p1->NU = 0;
	    }
	  p0->link (p);
	  p0 = p;
	}
    while (s0.avail ())
      {
	p = (MEM_BLK *) s0.remove ();
	sz = p->NU;
	if (!sz)
	  continue;
	for (; sz > 128; sz -= 128, p += 128)
	  BList[N_INDEXES - 1].insert (p, 128);
	if (Indx2Units[i = Units2Indx[sz - 1]] != sz)
	  {
	    k = sz - Indx2Units[--i];
	    BList[k - 1].insert (p + (sz - k), k);
	  }
	BList[i].insert (p, Indx2Units[i]);
      }
    GlueCount = 1 << 13;
  }
  void *
  AllocUnitsRare (UINT indx)
  {
    UINT i = indx;
    if (!GlueCount)
      {
	GlueFreeBlocks ();
	if (BList[i].avail ())
	  return BList[i].remove ();
    }
    do
      {
	if (++i == N_INDEXES)
	  {
	    GlueCount--;
	    i = U2B (Indx2Units[indx]);
	    return (UnitsStart - pText > (int)i) ? (UnitsStart -= i) : (NULL);
	  }
      }
    while (!BList[i].avail ());
    void *RetVal = BList[i].remove ();
    SplitBlock (RetVal, i, indx);
    return RetVal;
  }
  inline void *
  AllocUnits (UINT NU)
  {
    UINT indx = Units2Indx[NU - 1];
    if (BList[indx].avail ())
      return BList[indx].remove ();
    void *RetVal = LoUnit;
    LoUnit += U2B (Indx2Units[indx]);
    if (LoUnit <= HiUnit)
      return RetVal;
    LoUnit -= U2B (Indx2Units[indx]);
    return AllocUnitsRare (indx);
  }
  inline void *
  AllocContext ()
  {
    if (HiUnit != LoUnit)
      return (HiUnit -= UNIT_SIZE);
    else if (BList->avail ())
      return BList->remove ();
    else
      return AllocUnitsRare (0);
  }
  inline void
  UnitsCpy (void *Dest, void *Src, UINT NU)
  {
    DWORD *p1 = (DWORD *) Dest, *p2 = (DWORD *) Src;
    do
      {
	p1[0] = p2[0];
	p1[1] = p2[1];
	p1[2] = p2[2];
	p1 += 3;
	p2 += 3;
      }
    while (--NU);
  }
  inline void *
  ExpandUnits (void *OldPtr, UINT OldNU)
  {
    UINT i0 = Units2Indx[OldNU - 1], i1 = Units2Indx[OldNU - 1 + 1];
    if (i0 == i1)
      return OldPtr;
    void *ptr = AllocUnits (OldNU + 1);
    if (ptr)
      {
	UnitsCpy (ptr, OldPtr, OldNU);
	BList[i0].insert (OldPtr, OldNU);
      }
    return ptr;
  }
  inline void *
  ShrinkUnits (void *OldPtr, UINT OldNU, UINT NewNU)
  {
    UINT i0 = Units2Indx[OldNU - 1], i1 = Units2Indx[NewNU - 1];
    if (i0 == i1)
      return OldPtr;
    if (BList[i1].avail ())
      {
	void *ptr = BList[i1].remove ();
	UnitsCpy (ptr, OldPtr, NewNU);
	BList[i0].insert (OldPtr, Indx2Units[i0]);
	return ptr;
      }
    else
      {
	SplitBlock (OldPtr, i0, i1);
	return OldPtr;
      }
  }
  inline void
  FreeUnits (void *ptr, UINT NU)
  {
    UINT indx = Units2Indx[NU - 1];
    BList[indx].insert (ptr, Indx2Units[indx]);
  }
  inline void
  SpecialFreeUnit (void *ptr)
  {
    if ((BYTE *) ptr != UnitsStart)
      BList->insert (ptr, 1);
    else
      {
	*(DWORD *) ptr = ~0UL;
	UnitsStart += UNIT_SIZE;
      }
  }
  inline void *
  MoveUnitsUp (void *OldPtr, UINT NU)
  {
    UINT indx = Units2Indx[NU - 1];
    if ((BYTE *) OldPtr > UnitsStart + 16 * 1024
	|| (BLK_NODE *) OldPtr > BList[indx].next)
      return OldPtr;
    void *ptr = BList[indx].remove ();
    UnitsCpy (ptr, OldPtr, NU);
    NU = Indx2Units[indx];
    if ((BYTE *) OldPtr != UnitsStart)
      BList[indx].insert (OldPtr, NU);
    else
      UnitsStart += U2B (NU);
    return ptr;
  }
  inline void
  ExpandTextArea ()
  {
    BLK_NODE *p;
    UINT Count[N_INDEXES];
    memset (Count, 0, sizeof (Count));
    while ((p = (BLK_NODE *) UnitsStart)->Stamp == ~0UL)
      {
	MEM_BLK *pm = (MEM_BLK *) p;
	UnitsStart = (BYTE *) (pm + pm->NU);
	Count[Units2Indx[pm->NU - 1]]++;
	pm->Stamp = 0;
      }
    for (UINT i = 0; i < N_INDEXES; i++)
      for (p = BList + i; Count[i] != 0; p = p->next)
	while (!p->next->Stamp)
	  {
	    p->unlink ();
	    BList[i].Stamp--;
	    if (!--Count[i])
	      break;
	  }
  }
};
#endif
