#include "project.h"
#include "lists.h"

static myUInt align_to_64(myUInt inp)
{
    return (   ( inp +  sizeof(myUInt) - 1)     BAND     ~(sizeof(myUInt) - 1)   ) ;
}

#define listIndexSize   256
//
// There is support for a maximum of      listIndexSize    lists.
// Each list has its own      list control block
//
// whenever a CELL is added to a LIST we check to see if that LIST has enough LIST SPACE to hold
// the existing cells  plus the new cell.
// The  LIST SPACE   is obtained using   calloc
// If there is not enough LIST SPACE  then a new   calloc  is used to get enough LIST SPACE
// the old LIST SPACE  is copied across to the new LIST SPACE   and the old LIST SPACE  is freed.
// SO  we do NOT use proper pointers within the CELLs in the LIST SPACE.
// Instead we use offsets.
// BUT BEWARE: there are different types of offset.
//    relative to the start of the LIST SPACE
//    relative to the current CELL'S base
//
// Functions exists to:
// create / delete a list
// create a new CELL within a LIST
// The user-visible structure of a cell is:
//   character string, zero-terminated  - once set  immutable
//   user defined 64 bit key
//   memory block of X (user defined) bytes - once set the size is immutable
// ---
// A specific cell can be identified by:
//    index
// or
//    character string - case sensitive
// or
//    64 bit key
// ---
// Searches for a specific character string or 64 bit key can be either
//    oldest to newest
// or
//    newest to oldest
// Such searches return the first found matching cell.
// ---
//
// ---------------------------------------------------------
// internal only. Not exposed.

typedef struct myListHeader
{
    unsigned char listOpen : 1;
    unsigned char  AllowDups : 1;
    unsigned char* listBase;

    myUInt listSize;
    LISTCELLINDEX index;    // is the number of USED CELLS in the LIST

    myUInt FirstCellOffset;
    myUInt LastCellOffset;

    myUInt freeSpaceOffset;

    myUInt lastCellSize;
}   ListHeader;

ListHeader   listControlBlock[listIndexSize];

// ---------------------------------------------------------

void lists_list_initialise(LISTINDEX useListIndex)
{
    listControlBlock[useListIndex].listOpen = 0;
    listControlBlock[useListIndex].listSize = 0;
    listControlBlock[useListIndex].index = 0;
    listControlBlock[useListIndex].listBase = 0;

    listControlBlock[useListIndex].AllowDups = 1;

    listControlBlock[useListIndex].LastCellOffset = 0;
    listControlBlock[useListIndex].FirstCellOffset = 0;
    listControlBlock[useListIndex].freeSpaceOffset = 0;

    listControlBlock[useListIndex].lastCellSize = 0;
}

myUInt lists_invalid_listIndex_check(LISTINDEX useListIndex)
{
    if (useListIndex EQU -1) return  -1;
    if (useListIndex >= listIndexSize) return  -1;
    if (listControlBlock[useListIndex].listOpen EQU 0)  return  -1;
    return 0;
}

ListCell* lists_cell_prev(ListCell* pLC)
{
    if (!pLC->prevCellSize) return NULL;
    return  (ListCell*)((unsigned char*)pLC - pLC->prevCellSize);
}
ListCell* lists_cell_next(ListCell* pLC)
{
    pLC = (ListCell *)  (    (  (unsigned char * )   pLC )   + pLC->cellSize ) ;
    if (!pLC->cellSize) return NULL ;
    return  pLC;
}

ListCell* lists_cell_by_index(LISTINDEX useListIndex, LISTCELLINDEX useCellIndex )
{
    if (lists_invalid_listIndex_check(useListIndex))  return NULL;
    ListHeader* pLH = &listControlBlock[useListIndex];

    if (pLH->listOpen EQU 0) return NULL;
    if (pLH->index EQU 0) return NULL;

    if (useCellIndex EQU -1)    return   (ListCell*)((unsigned char*)pLH->listBase + pLH->LastCellOffset);

    if (!(useCellIndex < pLH->index))   return NULL;

    ListCell* pLC = (ListCell*)((unsigned char*)pLH->listBase + pLH->FirstCellOffset);
    if (useCellIndex EQU 0)    return   pLC ;

    while (useCellIndex--)
    {
        pLC = (ListCell*)((unsigned char*) pLC + pLC->cellSize);
    }
    return   pLC;
}
ListCell* lists_cell_find_by_value0(LISTINDEX useListIndex, myUInt wantValue0, myInt DirectionFlag, LISTCELLINDEX afterIndex)
{
    LISTCELLINDEX useCellIndex;
    if (DirectionFlag)
    {
        useCellIndex = afterIndex + 1;
        if (afterIndex EQU - 1) useCellIndex = 0;
    }
    else
    {
        useCellIndex = afterIndex - 1;
        if (afterIndex EQU - 1) useCellIndex = -1;
    }

    ListCell* pLC = lists_cell_by_index(useListIndex, useCellIndex);
    if (pLC EQU NULL)    return   NULL;

    if (DirectionFlag)
    {
        while (1)
        {
            if (pLC->Value0  EQU  wantValue0)  return pLC;
            pLC = lists_cell_next(pLC);

            if (pLC   EQU NULL)  return pLC;
        }
    }
    else
    {
        while (1)
        {
            if (pLC->Value0  EQU  wantValue0)  return pLC;
            pLC = lists_cell_prev(pLC);

            if (pLC   EQU  NULL)  return pLC;
        }
    }
    return NULL;
}
ListCell* lists_cell_find_by_value1(LISTINDEX useListIndex, myUInt wantValue1, myInt DirectionFlag, LISTCELLINDEX afterIndex)
{
    LISTCELLINDEX useCellIndex;
    if (DirectionFlag)
    {
        useCellIndex = afterIndex + 1;
        if (afterIndex EQU - 1) useCellIndex = 0;
    }
    else
    {
        useCellIndex = afterIndex - 1;
        if (afterIndex EQU - 1) useCellIndex = -1;
    }

    ListCell* pLC = lists_cell_by_index(useListIndex, useCellIndex);
    if (pLC EQU NULL)    return   NULL;

    if (DirectionFlag)
    {
        while (1)
        {
            if (pLC->Value1  EQU  wantValue1)  return pLC;
            pLC = lists_cell_next(pLC);

            if (pLC   EQU NULL)  return pLC;
        }
    }
    else
    {
        while (1)
        {
            if (pLC->Value1  EQU  wantValue1)  return pLC;
            pLC = lists_cell_prev(pLC);

            if (pLC   EQU  NULL)  return pLC;
        }
    }
    return NULL;
}

ListCell* lists_cell_find_by_key(LISTINDEX useListIndex, myUInt wantKey, myInt DirectionFlag, LISTCELLINDEX afterIndex)
{
    LISTCELLINDEX useCellIndex;
    if (DirectionFlag)
    {
        useCellIndex = afterIndex + 1;
        if (afterIndex EQU -1) useCellIndex = 0;
    }
    else
    {
        useCellIndex = afterIndex  - 1;
        if (afterIndex EQU - 1) useCellIndex = -1;
    }

    ListCell* pLC = lists_cell_by_index(useListIndex, useCellIndex);
    if (pLC EQU NULL)    return   NULL;

    if (DirectionFlag)
    {
        while (1)
        {
            if (pLC->Key  EQU  wantKey)  return pLC;
            pLC = lists_cell_next(pLC);

            if (pLC   EQU NULL)  return pLC;
        }
    }
    else
    {
        while (1)
        {
            if (pLC->Key  EQU  wantKey)  return pLC;
            pLC = lists_cell_prev(pLC);

            if (pLC   EQU  NULL)  return pLC;
        }
    }
    return NULL;
}

ListCell* lists_cell_new_of_size(LISTINDEX useListIndex, myUInt nameSizeInTCHAR, myUInt blobSize )
{
    if (lists_invalid_listIndex_check (useListIndex) ) return (ListCell*)-1;

    // nameSize  is size of the user Name   IN BYTES

    ListCell* pCElast, * pCEnew;
    ListHeader* pLH;
    myUInt haveSize, wantSize, i, sizeofNewCell, requestSize;
    void* newList;
    unsigned char* dst, * src;

    pLH = &listControlBlock[useListIndex];

    //    debugn((TCHAR*)_TEXT("Blob size in ")); debugInt(blobSize);

    sizeofNewCell = align_to_64(sizeof(ListCell));

    if (nameSizeInTCHAR)   sizeofNewCell += align_to_64(nameSizeInTCHAR * sizeof(TCHAR)   + 2);
    if (blobSize)   sizeofNewCell += align_to_64(blobSize + 2);

    haveSize = pLH->listSize;
    wantSize = pLH->freeSpaceOffset + sizeofNewCell+ align_to_64(sizeof(ListCell));

    if (wantSize < haveSize)
    {
    }
    else
    {
        // need to allocate more data space for the new  cell
        // Get enough space for OLD LIST + extra
        // copy old LIST contents to new
        // free up old LIST space

        wantSize = haveSize + sizeofNewCell +  1024;  // 1024 as a fiddle factor
        requestSize = (wantSize + 2 * (myUInt)CELL_SIZE_IN_BYTES) BAND  ~(CELL_SIZE_IN_BYTES - 1);
        newList = calloc(requestSize,1);
        if (newList EQU NULL)return (ListCell*)-1;

        src = (unsigned char* ) pLH->listBase;
        dst = (unsigned char* ) newList;

        if (haveSize)
        {
            if ( dst  LAND  src )   for (i = 0; i < haveSize; i++) *dst++ = *src++;
            free(pLH->listBase);
        }
        pLH->listBase = (unsigned char * ) newList;
        pLH->listSize = wantSize;
    }
    // ----------------------------------------------------------------------------------
    // update pointers to the last (old) and new    cells

    if (pLH->listBase EQU NULL )return (ListCell*)-1;

    pCElast = (ListCell*)((unsigned char*)pLH->listBase + pLH->LastCellOffset);
    pCEnew  = (ListCell*)((unsigned char*)pLH->listBase + pLH->freeSpaceOffset);

    if ( (pCElast EQU NULL) LOR (pCEnew EQU NULL) ) return (ListCell*)-1;

    // ----------------------------------------------------------------------------------
    // update the previous LAST cell
    //    pLH->index++;                               // increment count of cells in list

    pCEnew->prevCellSize = pLH->lastCellSize;
    pLH->lastCellSize = sizeofNewCell;
    // ----------------------------------------------------------------------------------
    // initialise the new LAST cell

    pCEnew->Value0 = 0;
    pCEnew->Value1 = 0;

    pCEnew->index = pLH->index++;
    pCEnew->cellSize = sizeofNewCell;

    pCEnew->nameOffset = 0;
    if (nameSizeInTCHAR)   pCEnew->nameOffset = align_to_64(sizeof(ListCell)); // data comes after the CELL HEADER
    pCEnew->nameSizeInTCHAR = nameSizeInTCHAR;

    pCEnew->blobOffset = align_to_64(sizeof(ListCell));

    if (nameSizeInTCHAR) pCEnew->blobOffset += align_to_64(nameSizeInTCHAR*sizeof(TCHAR)  + 2);
    if (!blobSize) pCEnew->blobOffset = 0;

    pCEnew->blobSizeInBytes = blobSize ;
    //   debugn((TCHAR*)_TEXT("Blob size out ")); debugInt(blobSize);

    // ----------------------------------------------------------------------------------
    // update the header

    pLH->LastCellOffset = pLH->freeSpaceOffset;                           // point at the "new"  last cell

    pLH->freeSpaceOffset = pLH->freeSpaceOffset + sizeofNewCell;         // point at the next free byte

    // initialise the "next" cell to be empty
    ListCell * pCEnext = (ListCell*)((unsigned char*)pLH->listBase + pLH->freeSpaceOffset);
    pCEnext->cellSize = 0;

    return pCEnew;
}

LISTINDEX lists_list_new()
{
/*
\ help_pos list=open

\\ (-- index)
\\ Returns the index of a  free list
\\ It does NOT automatically select that list !
\\ No input parameters
\\ --
\\ INDEX     0..63
\\           = -1 = no free list
\
*/
    LISTINDEX   useListIndex;
    for (useListIndex = 0; useListIndex < listIndexSize; useListIndex++)
    {
        if (listControlBlock[useListIndex].listOpen EQU 0)
        {
            lists_list_initialise(useListIndex);
            listControlBlock[useListIndex].listOpen = 1;
            return useListIndex;
        }
    }
    return -1;
}

void lists_list_delete(LISTINDEX useListIndex )
{
/*
\ help_pos lists:delete
\\ (LISTINDEX --)
\\ Set the specified LIST to have no CELLS.
\\ Deletes and resources associated with the LISTINDEX
\\ and returns the index to the free pool
\\ LISTINDEX    zero based of target LIST
\\ --
\\ No output parameters
\
*/
    if (lists_invalid_listIndex_check(useListIndex)) return;

    if ((listControlBlock[useListIndex].listOpen) LAND(listControlBlock[useListIndex].listBase))
    {
        free(listControlBlock[useListIndex].listBase);
    }
    lists_list_initialise(useListIndex);
}

void lists_list_allow_duplicates( LISTINDEX useListIndex , myInt uniqueFlag)
{
/*
\ help_pos list=unique
\\ ( bool --)
\\ defines whether the current LIST allows duplicate byte/char arrays
\\ BOOL   = 0  duplicate lines are permitted (default)
\\        else duplicate lines are dropped
\\ --
\\ No output parameters
\
*/
    if (lists_invalid_listIndex_check(useListIndex)) return;

    if (uniqueFlag EQU 0)
    listControlBlock[useListIndex].AllowDups = 0;
    else
    listControlBlock[useListIndex].AllowDups = 1;
}

myUInt lists_cell_count_get(LISTINDEX useListIndex)
{
/*
\ help_pos list@size
\\ ( -- u)
\\ Returns the number of cells in the current list.
\\ No input parameters
\\ --
\\ U         The number of cells in the current list.
\\           -1 if LIST not OPENED
\
*/
    if (lists_invalid_listIndex_check(useListIndex)) return -1;

    if (listControlBlock[useListIndex].listOpen)
    {
        return (listControlBlock[useListIndex].index);
    }

    return -1;
}

LISTCELLINDEX lists_cell_create(LISTINDEX useListIndex, myUInt Key , TCHAR* Caddr, myUInt cCount ,  myUInt value0, myUInt value1, unsigned char * pBlobSrc , myUInt blobSize)
{
/*
\ help_pos list.cell=add
\\ ( blobAdd , blobSize , xA , xB , c-addr , count , key , ListId -- u)
\\ If the   KEY is ???
\\ Return U the zero based index of the array/string.
\\
\\ ValueA    data value associated with CHARACTER string
\\ Valueb    data value associated with CHARACTER string
\\ C-ADDR    Address of CHARACTER    array/string
\\ COUNT     Size of  string  in CHARACTERs
\\ LISTID    Identity of list to be added to

\\ --
\\ U         The zero based index of this string
\\           -1 if not found
\
*/

    LISTCELLINDEX  atIndex;
    TCHAR* src, * dst;
    ListCell* lcP;
    unsigned char  * pBlobDst = NULL;

    // ----------------------------------------------------------------------------------------
    // validate input parameters

    if (lists_invalid_listIndex_check(useListIndex)) return -1;

    // ----------------------------------------------------------------------------------------
    // if DUPLICATES are allowed, then there is no need to search
    // else search the LIST for the supplied characater string

    atIndex = -1;

    if (  ! listControlBlock[useListIndex].AllowDups   )
    {
        // ----------------------------------------------------------------------------------------
        // see if this entry exists
        if ( lists_cell_find_by_key(useListIndex,   Key, 1 , -1)  NEQU NULL )
        {
            return -1;
        }
    }

    // ----------------------------------------------------------------------------------------
    //  create a new CELL

    lcP = lists_cell_new_of_size(useListIndex, cCount  , blobSize   );

    // copy characters

    dst = (TCHAR*)((unsigned char*)lcP + lcP->nameOffset);
    if ( (cCount) LAND (Caddr NEQU NULL) )
    {
        src = Caddr;

        while (cCount--) *dst++ = *src++;
        *dst++ = 0;
    }
    else  if (cCount)
    {
        while (cCount--) *dst++ = 0;
        *dst++ = 0;
    }

    // copy blob
    pBlobDst = (unsigned char*)((unsigned char*)lcP +lcP->blobOffset);

    if ( (blobSize)  LAND (pBlobSrc NEQU NULL) )
    {
        while (blobSize--) *pBlobDst++ = *pBlobSrc++;
    }
    else if  (blobSize)
    {
        while (blobSize--) *pBlobDst++ = 0 ;
    }

    // copy values
    lcP->Key = Key;
    lcP->Value0 = value0;
    lcP->Value1 = value1;

    return (listControlBlock[useListIndex].index - 1);
}

myUInt lists_cell_get_by_index (LISTINDEX useListIndex, LISTCELLINDEX useCellIndex, myUInt* pKey ,   TCHAR** pChar  , myUInt*  pCharSize ,  myUInt* pVal0, myUInt* pVal1, unsigned char** pBlob,  myUInt * pBlobSize)
{
/*
\ help_pos list@bytes
\\ ( u -- value1 , value0, c-addr , count)
\\ Return the  array (it might originally be chars or bytes), and data values  VALUE0 and VALUE1, associated with the zero based index
\\ of the currently active LIST as a byte array!
\\ U         The zero based index of this array/string
\\ --
\\ C-ADDR    Address of BYTE    array
\\ COUNT     Size of array in BYTES
\\           If  U  is out of range, then return   COUNT = -1
\\           If U in range, but there is no BYTE array  COUNT = 0
*/
    ListCell* myCell;

    if (lists_invalid_listIndex_check(useListIndex)) return  -1;

    myCell = lists_cell_by_index(useListIndex, useCellIndex);

    if (myCell EQU  NULL )   return(-1);

    if (pCharSize)    * pCharSize = 0;
    if (pChar)
    {
        *pChar = (TCHAR *)  ((unsigned char*)myCell  + myCell->nameOffset  ) ;
        if (pCharSize) *pCharSize = myCell->nameSizeInTCHAR  ;
    }

    if ( pVal0 ) *pVal0 = (myCell->Value0);
    if ( pVal1 ) *pVal1 = (myCell->Value1);
    if (pKey) *pKey = (myCell->Key);

    if (pBlobSize) *pBlobSize = 0;
    if (pBlob)
    {
        *pBlob = (unsigned char*)myCell + myCell->blobOffset ;
        if (pBlobSize) *pBlobSize = myCell->blobSizeInBytes;
    }

    return (myCell->blobSizeInBytes);
}

myUInt lists_cell_set_values_by_index(LISTINDEX useListIndex , LISTCELLINDEX useCellIndex,  myUInt Val0 , myUInt Val1)
{
/*
\ help_pos list=value
\\ ( val0 , val1 , u --  -1 | 0)
\\ Set the user defined values val0, val1  associated with  U the zero based index of the currently active LIST
\\ VAL0      User data
\\ VAL1      User data
\\ U         The zero based index of this array/string
\\ --
\\ -1 | 0    Returns 0 if okay, else ERROR
*/
    ListCell* myCell;
    if (lists_invalid_listIndex_check(useListIndex)) return  -1;

    myCell = lists_cell_by_index(useListIndex , useCellIndex);

    if ( myCell EQU NULL )return -1;

    myCell->Value0 = Val0;
    myCell->Value1 = Val1;

    return 0;
}

// ------------------------------------------------------------------------------------------------------

void codecontrol_lists(SYS_STATE state )
{
    LISTINDEX useListIndex = 0;

    switch (state)
    {
        case SS_POWER_UP:

            for (useListIndex = 0; useListIndex < listIndexSize; useListIndex++)
            {
                lists_list_initialise(useListIndex);
            }
            break;

        case SS_POWER_DOWN:
            for (useListIndex = 0; useListIndex < listIndexSize; useListIndex++)
            {
                lists_list_delete(useListIndex );
            }
            break;

        case SS_RESTART:
            break;
    }
}
// ------------------------------------------------------------------------------------------------------

void show_CellEntry(ListCell* pLC)
{
    TCHAR* namePtr;
    unsigned char * blobPtr;
    myUInt  cnt;

    if (pLC EQU (ListCell *) - 1)
    {
        debug((TCHAR*)_TEXT("ListCell: invalid"));
        return;
    }
    debug((TCHAR*)_TEXT(" "));
    debug((TCHAR*)_TEXT("ListCell"));

    debugn((TCHAR*)_TEXT("prevCellSize      ")); debugInt((myUInt)pLC->prevCellSize);

    debugn((TCHAR*)_TEXT("index             ")); debugInt((myUInt)pLC->index);
    debugn((TCHAR*)_TEXT("Key               ")); debugInt((myUInt)pLC->Key);
    debugn((TCHAR*)_TEXT("Value0            ")); debugInt((myUInt)pLC->Value0);
    debugn((TCHAR*)_TEXT("Value1            ")); debugInt((myUInt)pLC->Value1);

    debugn((TCHAR*)_TEXT("cellSize      ")); debugInt((myUInt)pLC->cellSize);
    debugn((TCHAR*)_TEXT("nameOffset      ")); debugInt((myUInt)pLC->nameOffset);
    debugn((TCHAR*)_TEXT("nameSizeInTCHAR      ")); debugInt((myUInt)pLC->nameSizeInTCHAR);
    namePtr = (TCHAR*)((unsigned char*)pLC + pLC->nameOffset);
    cnt = pLC->nameSizeInTCHAR  ;
    if (cnt)  debug(namePtr);

    debugn((TCHAR*)_TEXT("blobOffset      ")); debugInt((myUInt)pLC->blobOffset);
    debugn((TCHAR*)_TEXT("blobSizeInBytes      ")); debugInt((myUInt)pLC->blobSizeInBytes);
    blobPtr =   (unsigned char*)pLC + pLC->blobOffset  ;
    cnt = pLC->blobSizeInBytes;
    cnt = min(cnt, 0x10);
    while (cnt--)
    {
        swprintf_s(txtBuffer, szTxtBuffer, L" %02x", *blobPtr++); addText(txtBuffer);
    }
    debug((TCHAR*)_TEXT("\n"));
}

void show_ListHeader (LISTINDEX useListIndex)
{
    if (lists_invalid_listIndex_check(useListIndex))
    {
        debug((TCHAR*)_TEXT("ListHeader: invalid index"));
        return;
    }

    ListHeader  *pLH =  & listControlBlock[useListIndex];

    debug((TCHAR*)_TEXT(" "));
    debug((TCHAR*)_TEXT("ListHeader"));

    debugn((TCHAR*)_TEXT("List index       ")); debugInt((myUInt)useListIndex);
    debugn((TCHAR*)_TEXT("ListHeader ")); debugInt((myUInt)pLH);
    debugn((TCHAR*)_TEXT("listOpen        ")); debugInt((myUInt)pLH->listOpen);
    debugn((TCHAR*)_TEXT("listSize        ")); debugInt((myUInt)pLH->listSize);
    debugn((TCHAR*)_TEXT("Entries index           ")); debugInt((myUInt)pLH->index);
    debugn((TCHAR*)_TEXT("AllowDups       ")); debugInt((myUInt)pLH->AllowDups);
    debugn((TCHAR*)_TEXT("FirstCellOffset   ")); debugInt((myUInt)pLH->FirstCellOffset);
    debugn((TCHAR*)_TEXT("LastCellOffset  ")); debugInt((myUInt)pLH->LastCellOffset);
    debugn((TCHAR*)_TEXT("freeSpaceOffset   ")); debugInt((myUInt)pLH->freeSpaceOffset);
    debugn((TCHAR*)_TEXT("lastCellSize  ")); debugInt((myUInt)pLH->lastCellSize);
}

