/*************************************************************************
*                                                                        *
*  ICATALOG.C                                                            *
*                                                                        *
*  Copyright (C) Microsoft Corporation 1990-1994                         *
*  All Rights reserved.                                                  *
*                                                                        *
**************************************************************************
*                                                                        *
*  Module Intent                                                         *
*   Catalog generating                                                   *
*                                                                        *
**************************************************************************
*                                                                        *
*  Current Owner: BinhN                                                  *
*                                                                        *
**************************************************************************
*                                                                        *
*  Revision History:                                                     *
*                                                                        *
**************************************************************************
*                                                                        *
*  Released by Development:     (date)                                   *
*                                                                        *
*************************************************************************/

#include <mvopsys.h>
#include <mem.h>
#include <memory.h>
#include <orkin.h>
#include <mvsearch.h>
#include "common.h"
#include "index.h"

#ifdef _DEBUG
static BYTE NEAR s_aszModule[] = __FILE__;	/* Used by error return functions.*/
#endif


#define CAT_BUF_SIZE    CB_HUGE_BUF // Catalog buffer's size

/*************************************************************************
 *
 *                       API FUNCTIONS
 *  Those functions should be exported in a .DEF file
 *************************************************************************/
PUBLIC LPCAT EXPORT_API PASCAL FAR CatalogInitiate (WORD, PHRESULT);
PUBLIC HRESULT EXPORT_API PASCAL FAR CatalogAddItem (LPCAT, DWORD, LPB);
PUBLIC HRESULT EXPORT_API PASCAL FAR CatalogBuild (HFPB, LPCAT, LSZ,
    INTERRUPT_FUNC, LPV);
PUBLIC VOID EXPORT_API PASCAL FAR CatalogDispose (LPCAT lpCat);

/*************************************************************************
 *
 *                    INTERNAL PRIVATE FUNCTIONS
 *  All of them should be declared near
 *************************************************************************/
PRIVATE HRESULT PASCAL NEAR CatalogFlush (CAT_INDEX FAR *);
PRIVATE HRESULT PASCAL NEAR CatalogSort (CAT_INDEX FAR *);
PRIVATE HRESULT PASCAL NEAR CatalogMerge (CAT_INDEX FAR *);

/*************************************************************************
 *  @doc    API INDEX
 *
 *  @func   LPCAT PASCAL FAR | CatalogInitiate |
 *      Create and initialize different structures and temporary files
 *      needed for the creation of a catalog
 *
 *  @parm   WORD | wElemSize |
 *      Size of each element in a catalog
 *
 *  @parm   PHRESULT | phr |
 *      Error buffer to be used in subsequent catalog related function calls
 *
 *  @rdesc NULL if failed. The error buffer will contain descriptions about
 *      the cause of the failure
 *************************************************************************/
PUBLIC LPCAT EXPORT_API PASCAL FAR CatalogInitiate (WORD wElemSize,
    PHRESULT phr)
{
    CAT_INDEX   FAR *lpCat;
    HANDLE  handle;         // Temporary variable

    //  Initialization phase.  Allocate memory, create temporary
    //  and permanent files.
    //
    if ((handle = _GLOBALALLOC(DLLGMEM_ZEROINIT,
        sizeof(CAT_INDEX))) == NULL) {
        SetErrCode(phr, E_OUTOFMEMORY);
        return NULL;
    }

    lpCat  = (LPCAT)_GLOBALLOCK(handle);
    lpCat->hCat = handle;
    lpCat->lperrb = phr;
    lpCat->wElemSize = wElemSize;
    lpCat->FileStamp = CATALOG_STAMP;
    lpCat->version = VERCURRENT;
    lpCat->dwCurElem = lpCat->dwFirstElem = (DWORD)-1;

    /* Assume that all incoming data are already sorted
       in ascending order
    */
    lpCat->fFlags = CAT_SORTED;
    lpCat->aszTempFile = lpCat->TmpFileNames;

    /* Create temp file */

    (void)GETTEMPFILENAME((char)0, (LSZ)"cat", (WORD)0,
        (LSZ)lpCat->aszTempFile);

    if ((lpCat->hResultFile = _lcreat (lpCat->aszTempFile, 0)) == (HFILE)-1) {

exit01:
        _GLOBALUNLOCK(lpCat->hCat);
        _GLOBALFREE(lpCat->hCat);
        return NULL;
    }

    /* Allocate memory */
    if ((handle = _GLOBALALLOC(DLLGMEM, CAT_BUF_SIZE)) == NULL) {
        SetErrCode(phr, E_OUTOFMEMORY);

        /* Remove temp file */
        _lclose(lpCat->hResultFile);
        FileUnlink(NULL,lpCat->aszTempFile,REGULAR_FILE);
        goto exit01;
    }

    lpCat->lpCatBuf = _GLOBALLOCK(lpCat->hCatBuf = handle);
    lpCat->ibBufOffset = 0;
    return lpCat;
}

/*************************************************************************
 *  @doc    API INDEX
 *  
 *  @func   HRESULT PASCAL FAR | CatalogAddItem |
 *      Add an item into the catalog
 *
 *  @parm   LPCAT | lpCat |
 *      Pointer to catalog structure (returned by CatalogInitiate())
 *
 *  @parm   DWORD | dwItemN |
 *      Item's id
 *  
 *  @parm   LPB | lpCatElement |
 *      Buffer contained the value of the catalog item. The size of this
 *      buffer is pre-determined in CatalogInitiate()
 *  
 *  @rdesc S_OK if succeeded. The error buffer provided in CatalogInitiate()
 *      will contain descriptions about the cause of the failure
 *  
 *  @xref   CatalogInitiate()
 *************************************************************************/
PUBLIC HRESULT EXPORT_API PASCAL FAR CatalogAddItem (CAT_INDEX FAR *lpCat,
    DWORD dwItemN, LPB lpCatElement)
{
    WORD wNewOffset;
    LPB lpBuf;

    /* Sanity check */
    if (lpCat == NULL || lpCatElement == NULL)
        return E_INVALIDARG;

    if (lpCat->dwFirstElem == (DWORD)-1) {
        lpCat->dwFirstElem = dwItemN;
    }
    else {
        if (dwItemN <= lpCat->dwCurElem) {
            /* The elements are not sorted */
            lpCat->fFlags &= ~CAT_SORTED;
            if (lpCat->dwFirstElem > dwItemN)
                lpCat->dwFirstElem = dwItemN;
        }
    }

    /* Check to see if we have enough room in the buffer. If not
     * just flush the buffer to make room for new data
     */
    if (lpCat->ibBufOffset + (wNewOffset  = lpCat->wElemSize +
        sizeof(DWORD)) >= CAT_BUF_SIZE) {
        if (CatalogFlush(lpCat) != S_OK)
            return E_FAIL;
    }

    /* Have enough room, just copy data */

    lpBuf = lpCat->lpCatBuf + lpCat->ibBufOffset;
    *(LPDW)lpBuf = dwItemN;
    lpBuf += sizeof (DWORD);
    MEMCPY(lpBuf, lpCatElement, lpCat->wElemSize);

    /* Only update buffer pointer if the last datum is not the
       same as this one
    */

    if (lpCat->dwCurElem != dwItemN) {
        lpCat->dwCurElem = dwItemN;
        lpCat->ibBufOffset += wNewOffset;
        lpCat->cElement ++;
    }
    return S_OK;
}

/*************************************************************************
 *  @doc    API INDEX
 *
 *  @func   int PASCAL FAR | CatalogBuild |
 *      Write the catalog as a subfile of the system file
 *
 *  @parm   HFPB | hfpbSysFile |
 *      If non-zero, this is the handle of an already opened system file
 *
 *  @parm LPCAT | lpCat |
 *      Pointer to catalog structure (returned by CatalogInitiate())
 *
 *  @parm   LSZ | lszFilename |
 *      If hpfbSysFile is non-zero, this is the name of the catalog's subfile
 *      else this is a regular DOS file
 *
 *  @rdesc S_OK if succeeded, other errors otherwise
 *      the failure
 *
 *  @xref   CatalogInitiate()
 *************************************************************************/
PUBLIC HRESULT EXPORT_API PASCAL FAR CatalogBuild (HFPB hfpbSysFile,
    CAT_INDEX FAR *lpCat, LSZ lszCatName, INTERRUPT_FUNC fnInterrupt,
    LPV lpParm)
{
    WORD cbBufSize;         // Buffer size
    WORD cbBytePerItem;     // Number of bytes per item
    WORD cItemRead;         // Number of items read at once
    int  cbRead;            // Number of bytes read at once
    HANDLE hResultBuf;      // Handle of temporary buffer
    LPB  lpbResultBuf;      // Temporary result buffer
    LPB  lpbCurItem;        // Pointer at current item
    LPB  lpbInBuf;          // Pointer for input buffer
    DWORD lfoFileOffset;    // Current offset in catalog subfile
    WORD cbTotalInBuf;      // Total number of bytes in tmpbuf
    PHRESULT phr;          // Error buffer
    HRESULT fRet;              // Return value
    BYTE Dummy[CATALOG_HDR_SIZE]; // Dummy buffer to write 0
    long LastItem;          // Last item
    long CurItem;           // Current item
    WORD wElemSize;         // Element size

    if (lpCat == NULL || lszCatName == NULL)
        return E_INVALIDARG;

    phr = lpCat->lperrb;
    fRet = E_FAIL;  // Default fRet

    /* Flush the catalog buffer */

    if ((fRet = CatalogFlush(lpCat)) != S_OK)
    {
        SetErrCode(phr, fRet);
        goto exit00;
    }

    /* Rewind the temp file */
    if (_llseek(lpCat->hResultFile, 0L, 0) == (LONG)-1)
    {
        SetErrCode(phr, E_FILESEEK);
        goto exit00;
    }
    
    /* Open system file & catalog subfile */
    if ((lpCat->hfpbCatalog = FileCreate(hfpbSysFile, lszCatName,
        hfpbSysFile ? FS_SUBFILE : REGULAR_FILE, phr)) == NULL)
    {
        goto exit00;
    }

    /* Inititalize various variables */
    cbBytePerItem = sizeof (DWORD) + (wElemSize = lpCat->wElemSize);
    cItemRead = CAT_BUF_SIZE / cbBytePerItem;
    cbBufSize = cItemRead * cbBytePerItem;
    LastItem = lpCat->dwFirstElem - 1;
    lpCat->cElement = 0;

    /* Allocate a temporary buffer */
    if ((hResultBuf = _GLOBALALLOC(DLLGMEM, CAT_BUF_SIZE)) == NULL)
    {
        SetErrCode(phr, E_OUTOFMEMORY);
        goto exit01;
    }
    lpbResultBuf = _GLOBALLOCK(hResultBuf);
    lfoFileOffset = 0;

    /* Reserve space for catalog information */
    lpbCurItem = lpbResultBuf +
        (cbTotalInBuf = CATALOG_HDR_SIZE);

    for (;;) {
        if (fnInterrupt != NULL) {

            /* Call user's interrupt functions */

            if ((fRet = (*fnInterrupt)(lpParm)) != S_OK)
            {
                SetErrCode(phr, fRet);
                goto FreeAll;
            }
        }

        /* Read in a block */
        if ((cbRead = _lread(lpCat->hResultFile, lpCat->lpCatBuf,
            cbBufSize)) == (LONG)-1)
        {
            SetErrCode(phr, E_FILEREAD);
            goto FreeAll;
        }

        if (cbRead == 0)    // EOF check
            break;

        for (lpbInBuf = lpCat->lpCatBuf; cbRead > 0;)
        {

            if ((LastItem > *(long far *)lpbInBuf))
            {
                SetErrCode(phr, E_ASSERT);
                goto FreeAll;
            }

            CurItem = *(LPDW)lpbInBuf;
            lpbInBuf += sizeof(DWORD);

            /* Fill up the "holes", ie. missing indices. For random access
               retrieval to work, all items need to be present. So if we
               only have keys 1 and 5, we have to fill in "holes" of key
               2, 3, 4.
            */

            while (LastItem < CurItem - 1)
            {

                /* If buffer is full, flush it to subfile */
                if (cbTotalInBuf + cbBytePerItem > cbBufSize)
                {
                    if (fnInterrupt != NULL)
                    {

                        /* Call user's interrupt functions */

                        if ((fRet = (*fnInterrupt)(lpParm)) != S_OK)
                        {
                            SetErrCode(phr, fRet);
                            goto FreeAll;
                        }
                    }

                    if (FileWrite(lpCat->hfpbCatalog, lpbResultBuf,
                        cbTotalInBuf,NULL) != cbTotalInBuf)
                    {
                        goto FreeAll;
                    }

                    /* Reset the variables */

                    lfoFileOffset += cbTotalInBuf;
                    lpbCurItem = lpbResultBuf;
                    cbTotalInBuf = 0;
                }

                /* Fill the missing element with -1 */
                MEMSET(lpbCurItem, (BYTE)-1, wElemSize);
                lpbCurItem += wElemSize;
                cbTotalInBuf += wElemSize;
                lpCat->cElement++;
                LastItem++;
            }

            /* If buffer is full, flush it to subfile */

            if (cbTotalInBuf + cbBytePerItem > cbBufSize)
            {
                if (FileWrite(lpCat->hfpbCatalog, lpbResultBuf,
                    cbTotalInBuf,NULL) != cbTotalInBuf)
                {
                    goto FreeAll;
                }

                /* Reset the variables */

                lfoFileOffset += cbTotalInBuf;
                lpbCurItem = lpbResultBuf;
                cbTotalInBuf = 0;
            }

            /* Copy the data */
            MEMCPY(lpbCurItem, lpbInBuf, wElemSize);

            /* Only update pointer if data indices are different */
            if (LastItem != CurItem)
            {
                lpbCurItem += wElemSize;
                cbTotalInBuf += wElemSize;
                LastItem = CurItem;
                lpCat->cElement++;
            }

            lpbInBuf += wElemSize;
            cbRead -= wElemSize + sizeof(DWORD);
        }
    }

    /* Flush the buffer */

    if (cbTotalInBuf > 0)
    {
        if (FileWrite(lpCat->hfpbCatalog, lpbResultBuf,
            cbTotalInBuf, phr) != cbTotalInBuf)
        {
            fRet = *phr;
            goto FreeAll;
        }
    }

    /* Write catalog header information. */

    MEMSET(Dummy, (BYTE)0, CATALOG_HDR_SIZE);

    /* Write all zero to the header */
    if (FileSeekWrite(lpCat->hfpbCatalog, Dummy, foNil,
        CATALOG_HDR_SIZE, phr) != CATALOG_HDR_SIZE)
    {
        fRet = *phr;
        goto FreeAll;
    }

    if (FileSeekWrite(lpCat->hfpbCatalog, lpCat, foNil,
        sizeof(CAT_HEADER),phr)!=sizeof (CAT_HEADER))
        fRet = *phr;

FreeAll:
    _GLOBALUNLOCK(hResultBuf);
    _GLOBALFREE(hResultBuf);

exit01:
    FileClose(lpCat->hfpbCatalog);
    
exit00:
    return fRet;
}

/*************************************************************************
 *  @doc    API INDEX
 *
 *  @func   VOID PASCAL FAR | CatalogDispose |
 *      Release all memories and temporary files associated with
 *      the catalog
 *
 *  @parm LPCAT | lpCat |
 *      Pointer to catalog structure (returned by CatalogInitiate())
 *************************************************************************/
PUBLIC VOID EXPORT_API PASCAL FAR CatalogDispose (CAT_INDEX FAR *lpCat)
{
    if (lpCat == NULL)
        return;
    if (lpCat->hResultFile) {
        _lclose(lpCat->hResultFile);
        FileUnlink(NULL,lpCat->aszTempFile,REGULAR_FILE);
    }

    /* Free catalog buffer */
    _GLOBALUNLOCK(lpCat->hCatBuf);
    _GLOBALFREE(lpCat->hCatBuf);

    /* Free temporary index array */
    if (lpCat->hIndexArray) {
        _GLOBALUNLOCK(lpCat->hIndexArray);
        _GLOBALFREE(lpCat->hIndexArray);
    }

    /* Free catalog structure */
    _GLOBALUNLOCK(lpCat->hCat);
    _GLOBALFREE(lpCat->hCat);
}

/*************************************************************************
 *  @doc    INTERNAL
 *
 *  @func   PRIVATE HRESULT PASCAL NEAR | CatalogFlush |
 *      Flush out the catalog buffer. The function will perform sort
 *      if necessary
 *
 *  @parm   CAT_INDEX FAR * | lpCat |
 *      Pointer to catalog buffer
 *************************************************************************/
PRIVATE HRESULT PASCAL NEAR CatalogFlush (CAT_INDEX FAR *lpCat)
{
    HRESULT fRet;  // Returned value

    fRet = S_OK;
    if (lpCat->ibBufOffset == 0) {
        /* There is nothing to flush */
        return S_OK;
    }

    /* Sort the temporary buffer if it is not already sorted */
    if ((lpCat->fFlags & CAT_SORTED) == 0) {
        if ((fRet = CatalogSort (lpCat)) != S_OK)
            return fRet;
    }

    /* Write the sorted result to the temp file */
    if ((fRet = CatalogMerge (lpCat)) != S_OK)
        return fRet;

    /* Reset various variables */
    lpCat->fFlags |= CAT_SORTED;
    lpCat->cElement = lpCat->ibBufOffset = 0;
    return S_OK;
}

/*************************************************************************
 *  @doc    INTERNAL
 *
 *  @func   PRIVATE HRESULT PASCAL NEAR | CatalogMerge |
 *      Flush out the catalog buffer by merging the catalog buffer
 *      with the already flushed data in the temp file
 *
 *  @parm   CAT_INDEX FAR * | lpCat |
 *      Pointer to catalog buffer
 *
 *  @rdesc  The function returns S_OK if succeeded
 *************************************************************************/
PRIVATE HRESULT PASCAL NEAR CatalogMerge (CAT_INDEX FAR *lpCat)
{
    HRESULT fRet;

    if (lpCat->fFlags & CAT_SORTED) {
    /* everything is still sorted. Just add the buffer to the
       end of the file
    */
        if (_lwrite(lpCat->hResultFile, lpCat->lpCatBuf,
            lpCat->ibBufOffset) != lpCat->ibBufOffset) {
            return E_DISKFULL;
        }
    }
    else {
        if ((fRet = IndexMergeSort ((HFILE FAR *)&lpCat->hResultFile,
            (LSZ)lpCat->aszTempFile, lpCat->IndexArray,
            lpCat->lpCatBuf, lpCat->wElemSize + sizeof(DWORD),
            (WORD)lpCat->cElement)) != S_OK)
            return fRet;
    }
    return S_OK;
}

/*************************************************************************
 *  @doc    INTERNAL
 *
 *  @func   PRIVATE HRESULT PASCAL NEAR | CatalogSort |
 *      Given the array  ofdata in lpCat->lpbCatBuf, the function will
 *      perform an "indirect" sort. First, an index array containing
 *      the offsets of the elements in the buffer will be created.
 *      The function then call IndexSort() to do the sorting
 *
 *  @parm   CAT_INDEX FAR * | lpCat |
 *      Pointer to catalog buffer
 *
 *  @rdesc  The function returns S_OK if succeeded
 *************************************************************************/
PRIVATE HRESULT PASCAL NEAR CatalogSort (CAT_INDEX FAR *lpCat)
{
    LPW lpIndexArray;
    int cbElement;
    register int i, j;
    WORD wElemSize;
    
    if (lpCat->hIndexArray == 0) {
        /* Allocate the index buffer */
        if ((lpCat->hIndexArray= _GLOBALALLOC(DLLGMEM,
            (cbElement = (CAT_BUF_SIZE / lpCat->wElemSize)*sizeof(WORD))))
            == NULL) {
            return E_OUTOFMEMORY;
        }
        lpCat->IndexArray = (LPW)_GLOBALLOCK(lpCat->hIndexArray);
    }

    /* Initialize the index array */
    cbElement = (WORD)lpCat->cElement;
    wElemSize = lpCat->wElemSize + sizeof(DWORD);

    for (j = 0, i = cbElement, lpIndexArray = lpCat->IndexArray;
        i > 0; i--, j += wElemSize) {
        *lpIndexArray++ = (WORD) j;
    }

    /* Do the sorting */
    if (IndexSort (lpCat->IndexArray, lpCat->lpCatBuf,
        cbElement) != S_OK)
        return E_FAIL;

    /* Update the current element to be the largest element.
       This will avoiding future sorting if this is the only time
       things get out of order (lpCat->fFlags CAT_SORTED will be
       set)
     */
    lpCat->dwCurElem = *(LPDW)(lpCat->lpCatBuf +
        lpCat->IndexArray[cbElement - 1]);
    return S_OK;
}