/* Copyright (c) Microsoft Corporation. All rights reserved. */
#define OPTIMIZE        /* enable Base.h optimizations, since we're the "base" */

#include <mmlite.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
//#include <machdep.h>

// #include "xxx.h"    /* for CurrentHeapDelayedFree */

// #include "optims.h"


#include "heap.h"
#include <s_NullHeap_v.c>
#include <s_NullHeapFactory_v.c>

#define DPRINT(x)
#define EPRINT(x)

/* Minimal heap.
 *
 */

#if _UINTSIZE == 16
#define HB_ALIGN    4            /* Allocation alignment                 */
#else
#define HB_ALIGN    8            /* Allocation alignment                 */
#endif
#define HB_ROUNDOFF (HB_ALIGN-1) /* Used to round up allocation size     */

typedef struct {
    ADDR_SIZE Reserve;  /* amount of address space reserved for the heap */
    ADDR_SIZE Used;     /* of that, how much is actually allocated       */
    ADDR_SIZE Avail;    /* ..and how much is left free.                  */
} HEAP_STATUS;


/* Heap header
 */
typedef struct _HEAPINFO {  /* Per-heap information                      */
    const struct IHeapVtbl *Vtbl; /* Reserved for IHEAP vtbl             */
    UINT Refs;              /* Number of references                      */
    UINT Flags;             /* Passed to CreatHeap                       */
    MUTEX Mx;               /* Serialize access to the heap.             */
    HEAP_STATUS Status;     /* Reserve, Commit, etc fields               */
} HEAPINFO, *PHEAPINFO;

#define pHP(pHeap) ((PHEAPINFO)(pHeap))

#ifdef _MSC_VER
#pragma warning(disable:4127)  /* conditional expression is constant */
#pragma warning(disable:4702)  /* unreachable code */
#endif

#define IncUsedCnt(_phi_,_size_) \
{ \
      (_phi_)->Status.Used += (_size_); \
      (_phi_)->Status.Avail -= (_size_); \
}

/* Lock/Unlock functions
 */
#define LockHeap(_phi_,_Flags_) \
{ \
    if (!((_Flags_) & HEAP_NO_SERIALIZE)) \
        Mutex_Lock(&(_phi_)->Mx); \
}

#define UnlockHeap(_phi_,_Flags_) \
{ \
    if (!((_Flags_) & HEAP_NO_SERIALIZE)) \
        Mutex_Unlock(&(_phi_)->Mx); \
}

/* *** HPAlloc
 *
 * Allocates a heap block of at least Size bytes from the heap pointed
 * by pHeap.  If Flags contains HEAP_ZERO_MEMORY also zero fills the new block.
 * Returns pointer to the block of memory, NULL if error.
 */
PRIVATE PTR MCT HPAlloc(IHeap *pHeap, UINT Flags, UINT Size, UINT Alignment)
{
    PHEAPINFO phi;
    BYTE *pMem = NULL;
    SCODE Error;
    UINT Frag;

    if (Flags & HEAP_CACHE_ALIGN)
      Alignment = _DCACHELINESIZE;

    if (Alignment > 1) {
        /* Check that it is a power of two */
        if ((Alignment-1) & Alignment) {
            Error = E_INVALID_PARAMETER;
            goto ErrorExit;
        }
    }
    if (Alignment < HB_ALIGN)
        Alignment = HB_ALIGN;

    DPRINT(("HPAlloc: pHeap %x, Flags %x, Size %x\n",
            (UINT) pHeap, Flags, Size));

    phi = (PHEAPINFO) pHeap;
    Flags |= phi->Flags;

    /* Might need cache-aligned buffer.  Calculate minimum amount of
     * memory to allocate, assuming ideal alignment of free block.
     */
    Size = ((Size + (Alignment-1)) & ~(Alignment-1));

    /* Lock heap if necessary
     */
    LockHeap(phi, Flags);

    /* See if the proposed pointer is suitably aligned
     * NB: This knows the heap is contig with descriptor.
     */
    pMem = ((BYTE*)phi) + phi->Status.Used;
    Frag = (((ADDRESS)pMem) & (Alignment-1));
    if (Frag) {
        /* Nope, roundup.
         */
        Frag = Alignment - Frag;
        Size += Frag;
        pMem += Frag;
    }

    /* Do we have room for it.
     */
    if (Size <= phi->Status.Avail) {
        /* Yes, record this allocation;
         */
        IncUsedCnt(phi,Size);
        Error = NO_ERROR;

    } else {
        Error = E_NOT_ENOUGH_MEMORY;
        pMem = NULL;
    }

    /* Let lock go iff.
     */
    UnlockHeap(phi, Flags);

    /* Done, one way or the other.
     */
ErrorExit:
    return (PTR) pMem;
}

/* *** HPStatus
 *
 * Returns status information about this heap.
 */
PRIVATE SCODE MCT HPStatus(IHeap *pHeap, ADDR_SIZE *pReserve, ADDR_SIZE *pCommit, ADDR_SIZE *pUsed, ADDR_SIZE *pMaxUsed)
{
    PHEAPINFO phi;

    phi = (PHEAPINFO) pHeap;

    if (pReserve)
        *pReserve = phi->Status.Reserve;
    if (pCommit)
        *pCommit = phi->Status.Reserve;
    if (pUsed)
        *pUsed = phi->Status.Used;
    if (pMaxUsed) {
        *pMaxUsed = phi->Status.Used;
    }

    return NO_ERROR;
}

/* *** Constructor: HeapCreate
 *
 * Creates heap from a block of memory.  pmem points to a block of memory
 * in the appropriate address space to be used.  InitSize is the number of
 * bytes already commited at the beginning of memory block.  MaxSize is
 * total number of bytes in memory block.  Flags passes HEAP_ZERO_MEMORY etc.
 * Returns pointer to the heap, NULL if failed.
 *
 */
SCODE NHCCreate(
    PIHEAPFACTORY iThis,
    ADDRESS Mem,
    UINT Flags,
    UINT InitSize,
    UINT MaxSize,
    /*out*/ PIHEAP *ppHeap)
{
    PHEAPINFO phi;
    SCODE Error = E_INVALID_PARAMETER;

    DPRINT(("Enter CreatHeap Mem %08x, InitSize %08x, MaxSize %08x, Flags %08x\n",Mem,InitSize,MaxSize,Flags));

    assert(Mem != 0);
    /* On some machines this might be ok..but slow */
    assert((Mem & (sizeof(void *) - 1)) == 0);

    /* Calculate overhead, no point creating a useless heap.
     */
#define MinHeapSize (sizeof(HEAPINFO) + HB_ALIGN)

    /* Make sure the block passed in is big enough to hold a heap,
     * committed size less or equal reserved size.
     */
    if ((MaxSize < MinHeapSize) || (MaxSize < InitSize)) {

        /* Reserved block too small or
         * commit size greater then reserved size.
         */
        goto ErrorExit;
    }
#undef MinHeapSize

    /* Initialize HEAPINFO structure (per-heap information).
     */
    phi = (PHEAPINFO) Mem;
    phi->Vtbl = &NullHeapVtbl;
    phi->Refs = 1;
    phi->Flags = Flags;

    phi->Status.Reserve = MaxSize;
    phi->Status.Used    = sizeof(HEAPINFO);
    phi->Status.Avail   = MaxSize - sizeof(HEAPINFO);

    Mutex_Init(&phi->Mx);

    DPRINT(("Exit CreatHeap\n"));
    *ppHeap = (PIHEAP) phi;
    return S_OK;

ErrorExit:
    EPRINT(("Error exit CreatHeap\n"));
    return E_FAIL;
}

/*
 * Exported methods
 */
PRIVATE UINT MCT HPAddRef(PIHEAP pThis)
{
    PHEAPINFO phi = (PHEAPINFO) pThis;

    return AtomicInc(&phi->Refs);
}

PRIVATE UINT MCT HPRelease(PIHEAP pThis)
{
    PHEAPINFO phi = (PHEAPINFO) pThis;
    UINT newrefs;
    PIHEAP cur = CurrentHeap();

    newrefs = AtomicDec(&phi->Refs);

    if (newrefs == 0 && pThis != cur) {
        cur->v->Free(cur,0,pThis);
    }

    return newrefs;
}

PRIVATE SCODE MCT HPQueryInterface(PIHEAP pThis, REFIID pIid, void* *ppUnk)
{
    return GenericQueryInterface((IUnknown *)pThis,pIid,ppUnk,&IID_IHeap);
}

PRIVATE struct HEAPCOB NullHeapCobObject = {
    &NullHeapFactoryVtbl,
    1
};

PIUNKNOWN NullHeapCobMain(void)
{
    return (PIUNKNOWN) &NullHeapCobObject;
}