#include "brfprv.h"

//========== Memory Management =============================================

#ifndef WIN32

#define MAX_WORD    0xffff

DECLARE_HANDLE(HHEAP);

typedef struct {                //  maps to the bottom of a 16bit DS
    WORD reserved[8];
    WORD cAlloc;
    WORD cbAllocFailed;
    HHEAP hhpFirst;
    HHEAP hhpNext;
} HEAP;

#define PHEAP(hhp)          ((HEAP *)MAKELP(hhp, 0))
#define MAKEHP(sel, off)    ((void _huge*)MAKELP((sel), (off)))

#define CBSUBALLOCMAX   0x0000f000L

HHEAP g_hhpFirst = NULL;

BOOL  DestroyHeap(HHEAP hhp);

void Mem_Terminate()
{
    while (g_hhpFirst)
        DestroyHeap(g_hhpFirst);
}

BOOL  CreateHeap(WORD cbInitial)
{
    HHEAP hhp;

    if (cbInitial < 1024)
        cbInitial = 1024;

    hhp = (HHEAP)GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_SHARE, cbInitial);

    if (!hhp)
        return FALSE;

    if (!LocalInit((WORD)hhp, sizeof(HEAP), cbInitial - 1))
    {
        GlobalFree(hhp);
        return FALSE;
    }

    PHEAP(hhp)->cAlloc = 0;
    PHEAP(hhp)->cbAllocFailed = MAX_WORD;
    PHEAP(hhp)->hhpNext = g_hhpFirst;
    g_hhpFirst = hhp;

    DebugMsg(DM_TRACE, TEXT("CreateHeap: added new local heap %x"), hhp);

    return TRUE;
}

#pragma optimize("o", off)              // linked list removals don't optimize correctly
BOOL  DestroyHeap(HHEAP hhp)
{
    ASSERT(hhp);
    ASSERT(g_hhpFirst);

    if (g_hhpFirst == hhp)
    {
        g_hhpFirst = PHEAP(hhp)->hhpNext;
    }
    else
    {
        HHEAP hhpT = g_hhpFirst;

        while (PHEAP(hhpT)->hhpNext != hhp)
        {
            hhpT = PHEAP(hhpT)->hhpNext;
            if (!hhpT)
                return FALSE;
        }

        PHEAP(hhpT)->hhpNext = PHEAP(hhp)->hhpNext;
    }
    if (GlobalFree((HGLOBAL)hhp) != NULL)
        return FALSE;

    return TRUE;
}
#pragma optimize("", on)        // back to default optimizations

#pragma optimize("lge", off) // Suppress warnings associated with use of _asm...
void *  HeapAlloc(HHEAP hhp, WORD cb)
{
    void * pb;

    _asm {
        push    ds
        mov     ds,hhp
    }

    pb = (void *)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, cb);

    if (pb)
        ((HEAP *)0)->cAlloc++;

    _asm {
        pop     ds
    }

    return pb;
}
#pragma optimize("o", off)              // linked list removals don't optimize correctly

void _huge* WINAPI SharedAlloc(long cb)
{
    void * pb;
    HHEAP hhp;
    HHEAP hhpPrev;

    // If this is a big allocation, just do a global alloc.
    //
    if (cb > CBSUBALLOCMAX)
    {
        void * lpb = MAKEHP(GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_SHARE, cb), 0);
        if (!lpb)
            DebugMsg(DM_ERROR, TEXT("Alloc: out of memory"));
        return lpb;
    }

    hhp = g_hhpFirst;

    while (TRUE)
    {
        if (hhp == NULL)
        {
            if (!CreateHeap(0))
            {
                DebugMsg(DM_ERROR, TEXT("Alloc: out of memory"));
                return NULL;
            }

            hhp = g_hhpFirst;
        }

        pb = HeapAlloc(hhp, (WORD)cb);
        if (pb)
            return MAKEHP(hhp, pb);

        // Record the size of the allocation that failed.
        // Later attempts to allocate more than this amount
        // will not succeed.  This gets reset anytime anything
        // is freed in the heap.
        //
        PHEAP(hhp)->cbAllocFailed = (WORD)cb;

        // First heap is full... see if there's room in any other heap...
        //
        for (hhpPrev = hhp; hhp = PHEAP(hhp)->hhpNext; hhpPrev = hhp)
        {
            // If the last allocation to fail in this heap
            // is not larger than cb, don't even try an allocation.
            //
            if ((WORD)cb >= PHEAP(hhp)->cbAllocFailed)
                continue;

            pb = HeapAlloc(hhp, (WORD)cb);
            if (pb)
            {
                // This heap had room: move it to the front...
                //
                PHEAP(hhpPrev)->hhpNext = PHEAP(hhp)->hhpNext;
                PHEAP(hhp)->hhpNext = g_hhpFirst;
                g_hhpFirst = hhp;

                return MAKEHP(hhp, pb);
            }
            else
            {
                // The alloc failed.  Set cbAllocFailed...
                //
                PHEAP(hhp)->cbAllocFailed = (WORD)cb;
            }
        }
    }
}
#pragma optimize("", on)        // back to default optimizations

#pragma optimize("lge", off) // Suppress warnings associated with use of _asm...

void _huge* WINAPI SharedReAlloc(void _huge* pb, long cb)
{
    void * pbNew;
    void _huge* lpbNew;
    UINT cbOld;

    // FEATURE, does not work with cb > 64k
    if (!pb)
        return SharedAlloc(cb);

    if (OFFSETOF(pb) == 0)
        return MAKEHP(GlobalReAlloc((HGLOBAL)SELECTOROF(pb), cb, GMEM_MOVEABLE | GMEM_ZEROINIT), 0);

    _asm {
        push    ds
        mov     ds,word ptr [pb+2]
    }

    pbNew = (void *)LocalReAlloc((HLOCAL)OFFSETOF(pb), (int)cb, LMEM_MOVEABLE | LMEM_ZEROINIT);
    if (!pbNew)
        cbOld = LocalSize((HLOCAL)OFFSETOF(pb));

    _asm {
        pop     ds
    }

    if (pbNew)
        return MAKEHP(SELECTOROF(pb), pbNew);

    lpbNew = SharedAlloc(cb);
    if (lpbNew)
    {
        hmemcpy((void *)lpbNew, (void *)pb, cbOld);
        Free(pb);
    }
    else
    {
        DebugMsg(DM_ERROR, TEXT("ReAlloc: out of memory"));
    }
    return lpbNew;
}

BOOL WINAPI SharedFree(void _huge*  * ppb)
{
    BOOL fSuccess;
    UINT cAlloc;
    void _huge * pb = *ppb;

    if (!pb)
        return FALSE;

    *ppb = 0;

    if (OFFSETOF(pb) == 0)
        return (GlobalFree((HGLOBAL)SELECTOROF(pb)) == NULL);

    _asm {
        push    ds
        mov     ds,word ptr [pb+2]
    }

    fSuccess = (LocalFree((HLOCAL)OFFSETOF(pb)) ? FALSE : TRUE);

    cAlloc = 1;
    if (fSuccess)
    {
        cAlloc = --((HEAP *)0)->cAlloc;
        ((HEAP *)0)->cbAllocFailed = MAX_WORD;
    }

    _asm {
        pop     ds
    }

    if (cAlloc == 0)
        DestroyHeap((HHEAP)SELECTOROF(pb));

    return fSuccess;
}


DWORD WINAPI SharedGetSize(void _huge* pb)
{
    WORD wSize;

    if (OFFSETOF(pb) == 0)
        return GlobalSize((HGLOBAL)SELECTOROF(pb));

    _asm {
        push    ds
        mov     ds,word ptr [pb+2]
    }

    wSize = LocalSize((HLOCAL)OFFSETOF(pb));

    _asm {
        pop     ds
    }

    return (DWORD)wSize;
}


#if 0
// hmemcpy() is faster (says davidds)

void WINAPI MemCopy(void * pTo, const void * pFrom, UINT cb)
{
    _asm {
        mov     cx,cb
        jcxz    mcexit

;;      push    si
;;      push    di
        mov     dx,ds

        lds     si,pFrom
        les     di,pTo

        cmp     si,di
        jae     mccopyup

        mov     ax,cx
        dec     ax
        dec     ax
        add     si,ax
        add     di,ax

        std
        shr     cx,1
        rep     movsw
        jnc     mc100
        inc     si
        inc     di
        movsb
mc100:
        cld
        jmp     short mcexit

mccopyup:
        cld
        shr     cx,1
        rep     movsw
        jnc     mc200
        movsb
mc200:

mcexit:
        mov     ds,dx
;;      pop     di
;;      pop     si
    }
}
#endif

#pragma optimize("", on)

#else // WIN32

// Define a Global Shared Heap that we use allocate memory out of that we
// Need to share between multiple instances.
HANDLE g_hSharedHeap = NULL;
#define MAXHEAPSIZE 2097152
#define HEAP_SHARED     0x04000000              /* put heap in shared memory */

//----------------------------------------------------------------------------
void PUBLIC Mem_Terminate()
{
    // Assuming that everything else has exited
    //
    if (g_hSharedHeap != NULL)
        HeapDestroy(g_hSharedHeap);
    g_hSharedHeap = NULL;
}

//----------------------------------------------------------------------------
void * WINAPI SharedAlloc(long cb)
{
    // I will assume that this is the only one that needs the checks to
    // see if the heap has been previously created or not

    if (g_hSharedHeap == NULL)
    {
        ENTEREXCLUSIVE();
        if (g_hSharedHeap == NULL)
        {
              g_hSharedHeap = HeapCreate(0, 1, MAXHEAPSIZE);
        }
        LEAVEEXCLUSIVE();

        // If still NULL we have problems!
        if (g_hSharedHeap == NULL)
            return(NULL);
    }

    return HeapAlloc(g_hSharedHeap, HEAP_ZERO_MEMORY, cb);
}

//----------------------------------------------------------------------------
void * WINAPI SharedReAlloc(void * pb, long cb)
{
    if (pb==NULL)
    {
        return SharedAlloc(cb);
    }
    return HeapReAlloc(g_hSharedHeap, HEAP_ZERO_MEMORY, pb, cb);
}

//----------------------------------------------------------------------------
BOOL WINAPI SharedFree(void ** ppb)
{
    void * pb = *ppb;

    if (!pb)
        return FALSE;

    *ppb = 0;

    return HeapFree(g_hSharedHeap, 0, pb);
}

//----------------------------------------------------------------------------
DWORD WINAPI SharedGetSize(void * pb)
{
    return (DWORD)HeapSize(g_hSharedHeap, 0, pb);
}

//----------------------------------------------------------------------------
// The following functions are for debug only and are used to try to
// calculate memory usage.
//
#ifdef DEBUG
typedef struct _HEAPTRACE
{
    DWORD   cAlloc;
    DWORD   cFailure;
    DWORD   cReAlloc;
    DWORD   cbMaxTotal;
    DWORD   cCurAlloc;
    DWORD   cbCurTotal;
} HEAPTRACE;

HEAPTRACE g_htSync = {0};      // Start of zero...

LPVOID WINAPI MemAlloc(HANDLE hheap, DWORD cb)
{
    LPVOID lp;

    lp = HeapAlloc(hheap, HEAP_ZERO_MEMORY, cb);
    if (lp == NULL)
    {
        g_htSync.cFailure++;
        return NULL;
    }

    // Update counts.
    g_htSync.cAlloc++;
    g_htSync.cCurAlloc++;
    g_htSync.cbCurTotal += cb;
    if (g_htSync.cbCurTotal > g_htSync.cbMaxTotal)
        g_htSync.cbMaxTotal = g_htSync.cbCurTotal;

    return lp;
}

LPVOID WINAPI MemReAlloc(HANDLE hheap, LPVOID pb, DWORD cb)
{
    LPVOID lp;
    DWORD cbOld;

    cbOld = HeapSize(hheap, 0, pb);

    lp = HeapReAlloc(hheap, HEAP_ZERO_MEMORY, pb,cb);
    if (lp == NULL)
    {
        g_htSync.cFailure++;
        return NULL;
    }

    // Update counts.
    g_htSync.cReAlloc++;
    g_htSync.cbCurTotal += cb - cbOld;
    if (g_htSync.cbCurTotal > g_htSync.cbMaxTotal)
        g_htSync.cbMaxTotal = g_htSync.cbCurTotal;

    return lp;
}

BOOL  WINAPI MemFree(HANDLE hheap, LPVOID pb)
{
    BOOL fRet;

    DWORD cbOld;

    cbOld = HeapSize(hheap, 0, pb);

    fRet = HeapFree(hheap, 0, pb);
    if (fRet)
    {
        // Update counts.
        g_htSync.cCurAlloc--;
        g_htSync.cbCurTotal -= cbOld;
    }

    return(fRet);
}

DWORD WINAPI MemSize(HANDLE hheap, LPVOID pb)
{
    return HeapSize(hheap, 0, pb);
}
#endif


#endif // WIN32
