#include <mvopsys.h>
#include <mem.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


extern FDECODE DecodeTable[];
extern DWORD argdwBits[];

typedef VOID (PASCAL NEAR *ENCODEDWORD) (PNODEINFO, DWORD, int);
PRIVATE VOID PASCAL NEAR EmitBitStreamDWord (PNODEINFO, DWORD, int);
PRIVATE VOID PASCAL NEAR EmitFixedDWord (PNODEINFO, DWORD, int);
PRIVATE VOID PASCAL NEAR EmitBellDWord (PNODEINFO, DWORD, int);

static ENCODEDWORD EncodeTable[] =
{
	EmitBitStreamDWord,
	EmitFixedDWord,
	EmitBellDWord,
	NULL,
};

#define EmitDword(p,dw,key) EncodeTable[(key).cschScheme]((p), (dw), (key).ucCenter)
#define FGetDword(a,b,c) (*DecodeTable[b.cschScheme])(a, b, c)

/*************************************************************************
 *
 *                    INTERNAL PRIVATE FUNCTIONS
 *
 *  All of them should be declared near
 *
 *************************************************************************/
PRIVATE int PASCAL NEAR TraverseLeafNode (_LPIPB, PNODEINFO,
    DWORD FAR *, DWORD);
PRIVATE int PASCAL NEAR DeleteTopicFromData (_LPIPB lpipb,
    FILEOFFSET dataOffset, DWORD FAR *, DWORD,
    LPDW pTopicIdArray, DWORD dwArraySize);
VOID PRIVATE PASCAL NEAR RemapData (_LPIPB, PNODEINFO, PNODEINFO,
    DWORD, DWORD);
VOID PRIVATE PASCAL NEAR EmitBits (PNODEINFO pNode, DWORD dwVal, BYTE cBits);
PRIVATE VOID PASCAL NEAR EmitBool (PNODEINFO pNode, BOOL fVal);

PUBLIC LONG PASCAL FAR CompareDWord (DWORD, DWORD, LPV lpParm);

/*************************************************************************
 *	@doc	API
 *  @func   HRESULT FAR PASCAL | MVIndexTopicDelete |
 *      Delete topics from an index
 *  @parm   HFPB | hSysFile |
 *      Handle to an opened system file, maybe NULL
 *  @parm   _LPIPB | lpipb |
 *      Pointer to index info. This structure is obtained through
 *      IndexInitiate()
 *  @parm   SZ | szIndexName |
 *      Name of the index. If hSysFile is NULL, this is a regular DOS file
 *      else it is a subfile of hSysFile
 *  @parm   DWORD FAR * | rgTopicId |
 *      Array of topic ids to be deleted from the index
 *  @parm   DWORD | dwCount |
 *      Number of elements in the array
 *  @rdesc  S_OK, or other errors
 *************************************************************************/
HRESULT PUBLIC EXPORT_API FAR PASCAL MVIndexTopicDelete (HFPB hSysFile,
    _LPIPB lpipb, SZ szIndexName, DWORD FAR * rgTopicId, DWORD dwCount)
{
    PNODEINFO pNodeInfo;
    int fRet;
    int cLevel;
    int cMaxLevel;
    WORD wLen;
    LPB pCur;
    
    if (lpipb == NULL || rgTopicId == NULL || dwCount == 0)
        return(E_INVALIDARG);
        
    // Set the bState
    lpipb->bState = DELETING_STATE;
        
    // Open the index file
    if ((fRet = IndexOpenRW(lpipb, hSysFile, szIndexName)) != S_OK)
    {
exit00:
        if (lpipb->idxf & IDXF_NORMALIZE)
        {
            FreeHandle (lpipb->wi.hSigma);
            FreeHandle (lpipb->wi.hLog);
            lpipb->wi.hSigma = lpipb->wi.hLog = NULL;
        }

        return(fRet);
    }
    
    // Allocate buffer
    if ((pNodeInfo = AllocBTreeNode (lpipb)) == NULL)
    {
        fRet = E_OUTOFMEMORY;
exit0:
        FileClose(lpipb->hfpbIdxFile);
        FreeBTreeNode (pNodeInfo);

        goto exit00;
    }
        
    if ((lpipb->hTmpBuf = _GLOBALALLOC (DLLGMEM_ZEROINIT,
        lpipb->BTreeData.Header.dwMaxWLen * 2)) == NULL)
        goto exit0;
    
    lpipb->pTmpBuf = (LPB)_GLOBALLOCK (lpipb->hTmpBuf);
    
    if (((lpipb->pIndexDataNode = 
        AllocBTreeNode (lpipb))) == NULL)    
    {
        fRet = E_OUTOFMEMORY;
exit1:
        _GLOBALUNLOCK(lpipb->hTmpBuf);
        _GLOBALFREE(lpipb->hTmpBuf);
        lpipb->hTmpBuf = NULL;
        goto exit0;
    }
        
    pNodeInfo->nodeOffset = lpipb->BTreeData.Header.foIdxRoot;
    cMaxLevel = lpipb->BTreeData.Header.cIdxLevels - 1;
    
    // Sort the incoming array
    if ((fRet = HugeDataSort((LPV HUGE*)rgTopicId, dwCount,
    	(FCOMPARE)CompareDWord, NULL, NULL, NULL)) != S_OK)
        goto exit1;
        
    // Move down the tree, based on the first offset of the block
    for (cLevel = 0; cLevel < cMaxLevel; cLevel++)
    {
        if ((fRet = ReadNewNode(lpipb->hfpbIdxFile, pNodeInfo,
            FALSE)) != S_OK)
        {
            _GLOBALUNLOCK(lpipb->hData);
            _GLOBALFREE(lpipb->hData);
            lpipb->hData = NULL;
exit2:
            FreeBTreeNode (lpipb->pIndexDataNode);
            lpipb->pIndexDataNode = NULL;
            goto exit1;
        }
        pCur = pNodeInfo->pBuffer + sizeof(WORD); // Skip cbLeft
        pCur = ExtractWord (lpipb->pTmpBuf, pCur, &wLen);
        pCur += ReadFileOffset (&pNodeInfo->nodeOffset, pCur);
    }
    
    // Handle leaf node
    while (!FoEquals (pNodeInfo->nodeOffset, foNil))
    {
        if ((fRet = ReadNewNode(lpipb->hfpbIdxFile, pNodeInfo,
            TRUE)) != S_OK)
			return fRet;
        if ((fRet = TraverseLeafNode (lpipb, pNodeInfo, rgTopicId, dwCount)) !=
            S_OK)
        {
            goto exit2;
        }
                
        ReadFileOffset (&pNodeInfo->nodeOffset, pNodeInfo->pBuffer);
    }
    fRet = S_OK;
    goto exit2;   
}

PRIVATE int PASCAL NEAR TraverseLeafNode (_LPIPB lpipb, PNODEINFO pNodeInfo,
    DWORD FAR *rgTopicId, DWORD dwCount)
{
    LPB pCur;
    LPB pMaxAddress;
    OCCF occf = lpipb->occf;
    WORD wLen;
    FILEOFFSET dataOffset;
    DWORD dataSize;
    BYTE  TopicCnt[20];
    BYTE  cbOldCount;
    BYTE  cbNewCount;
    ERRB  errb;
    BYTE  fChange = FALSE;
    HRESULT   fRet;
    
    pCur = pNodeInfo->pCurPtr;
    pMaxAddress = pNodeInfo->pMaxAddress;
    
    while (pCur < pMaxAddress)
    {
        DWORD dwTemp;
        DWORD dwTopicCount;
        DWORD dwOldTopicCount;
        LPB   pSaved;
        LPB   pTemp;
        
        pCur = ExtractWord (lpipb->pTmpBuf, pCur, &wLen);
        
        // Skip field id, topic count. fileoffset, datasize
        if (occf & OCCF_FIELDID)
            pCur += CbByteUnpack (&dwTemp, pCur); // FieldId
            
        pTemp = pSaved = pCur;  // Save the pointer to the topic count offset
        cbOldCount = (BYTE)CbByteUnpack (&dwTopicCount, pCur);
        pCur += cbOldCount;
        pCur += ReadFileOffset (&dataOffset, pCur);
        pCur += CbByteUnpack (&dataSize, pCur);
        
        if (dwTopicCount == 0)
            continue;
            
        dwOldTopicCount = dwTopicCount;
        if ((fRet = DeleteTopicFromData (lpipb, dataOffset, &dwTopicCount,
            dataSize, rgTopicId, dwCount)) != S_OK)
            return(fRet);
        
        if (dwOldTopicCount == dwTopicCount)
            continue;
        
        cbNewCount = (BYTE)CbBytePack (TopicCnt, dwTopicCount);

        // Update the topic count
        if (cbOldCount > cbNewCount)
		{
			TopicCnt[cbNewCount - 1] |= 0x80;	// Set the high bit
		}
        MEMCPY(pSaved, TopicCnt, cbNewCount);
		pSaved += cbNewCount;
        switch (cbOldCount - cbNewCount)
        {
            // Do we need 16 bytes to compress 4-bytes. YES!
            // Sometimes. we index/compress based on insufficient data
            // If subsequent updates contain value way larger than the
            // original data, then we may end up using 16 bytes to compress
            // 4 bytes!!
            case 16:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 15:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 14:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 13:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 12:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 11:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 10:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 9:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 7:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 6:
                *pSaved++ = 0x80; // Set the high bit
                break;
            case 5:
                *pSaved++ = 0x80; // Set the high bit
            case 4:
                *pSaved++ = 0x80; // Set the high bit
            case 3:
                *pSaved++ = 0x80; // Set the high bit
            case 2:
                *pSaved++ = 0x80; // Set the high bit
            case 1:
                *pSaved = 0x00; 
            case 0:
                break;
        }
#ifdef _DEBUG
        CbByteUnpack (&dwOldTopicCount, pTemp); // FieldId
        assert (dwOldTopicCount == dwTopicCount);
#endif
        
        fChange = TRUE; // The node have been changed
        
    }
    
    if (fChange == FALSE)
        return(S_OK);
        
    // Update the node
    if ((FileSeekWrite(lpipb->hfpbIdxFile,
        pNodeInfo->pBuffer, pNodeInfo->nodeOffset,
        lpipb->BTreeData.Header.dwBlockSize, &errb)) !=
        (LONG)lpipb->BTreeData.Header.dwBlockSize)
    {
        return(errb);
    }
    return(S_OK);
}

PRIVATE int PASCAL NEAR DeleteTopicFromData (_LPIPB lpipb,
    FILEOFFSET dataOffset, DWORD FAR * pTopicCount, DWORD dataSize,
    LPDW pTopicIdArray, DWORD dwArraySize)
{
    HRESULT fRet;
    ERRB errb;
    DWORD dwOldTopicCount;
    DWORD dwTopicId;
    DWORD dwTopicIdDelta;
    DWORD dwIndex;
    PNODEINFO pIndexDataNode = lpipb->pIndexDataNode;
    NODEINFO  CopyNode;
    PNODEINFO pCopyNode = &CopyNode;
    PIH20 pHeader = &lpipb->BTreeData.Header;
    OCCF occf = lpipb->occf;
    LPB  pStart;
    DWORD dwOldTopicId = 0;
    BYTE fetchOldData;
    BYTE fChanged;
    BYTE fNormalize = (lpipb->idxf & IDXF_NORMALIZE);
    
    // Make sure that we have enough memory to hold the data
    if (dataSize > pIndexDataNode->dwBlockSize)
    {
        _GLOBALUNLOCK (pIndexDataNode->hMem);
        if ((pIndexDataNode->hMem = _GLOBALREALLOC (pIndexDataNode->hMem,
            pIndexDataNode->dwBlockSize = dataSize, DLLGMEM_ZEROINIT)) == NULL)
            return(E_OUTOFMEMORY);
        
        pIndexDataNode->pBuffer = _GLOBALLOCK (pIndexDataNode->hMem);
    }
    
    // Read in the data
    if (FileSeekRead (lpipb->hfpbIdxFile, pIndexDataNode->pCurPtr =
        pIndexDataNode->pBuffer, dataOffset,
        dataSize, &errb) != (long)dataSize)
        return E_BADFILE;
        
    pIndexDataNode->pMaxAddress = pIndexDataNode->pBuffer + dataSize;    
	pIndexDataNode->ibit = cbitBYTE - 1;
    
    // Copy the prelimary node info
    CopyNode = *pIndexDataNode;
    
    dwOldTopicCount = *pTopicCount;
    dwTopicId = dwIndex = 0;
    fetchOldData = TRUE;
    fChanged = FALSE;
    
    while (dwOldTopicCount > 0)
    {
        DWORD dwTmp;
        
        if (fetchOldData)
        {
            // Byte align
            if (pIndexDataNode->ibit != cbitBYTE - 1)
            {
                pIndexDataNode->ibit = cbitBYTE - 1;
                pIndexDataNode->pCurPtr ++;
            }

            // Keep track of the starting position            
			pStart = pIndexDataNode->pCurPtr;
            if (fChanged == FALSE)
    			pCopyNode->pCurPtr = pIndexDataNode->pCurPtr;
            
            // Get the topicId from the index file
            if ((fRet = FGetDword(pIndexDataNode, pHeader->ckeyTopicId,
                &dwTopicIdDelta)) != S_OK)
                return fRet;
            dwTopicId += dwTopicIdDelta;
            fetchOldData = FALSE;
        }
        
        if (dwTopicId < pTopicIdArray[dwIndex])
        {
            if (fChanged == FALSE)
            {
    			if (fNormalize)
    			{
    				if ((fRet = FGetBits(pIndexDataNode, &dwTmp,
    					sizeof (USHORT) * cbitBYTE)) != S_OK)
    					return fRet;
    			}

                SkipOldData (lpipb, pIndexDataNode);
            }
            else
            {
                pIndexDataNode->pCurPtr = pStart;
                RemapData (lpipb, pCopyNode, pIndexDataNode,
                    dwTopicId, dwOldTopicId);
            }
            fetchOldData = TRUE;    
            dwOldTopicId = dwTopicId;
            dwOldTopicCount --;
            continue;
        }
        
        if (dwTopicId > pTopicIdArray[dwIndex])
        {
            if (dwIndex < dwArraySize - 1)
            {
                dwIndex++;
                continue;
            }
            
            if (fChanged == FALSE)
                return(S_OK);
                
            pIndexDataNode->pCurPtr = pStart;
            RemapData (lpipb, pCopyNode, pIndexDataNode,
                dwTopicId, dwOldTopicId);
            fetchOldData =TRUE;
            dwOldTopicId = dwTopicId;
            dwOldTopicCount --;
            continue;
        }
        
        // Both TopicId are equal. Ignore the current data
		fChanged = TRUE;	// We have changes
        if (fNormalize)
        {
            if ((fRet = FGetBits(pIndexDataNode, &dwTmp,
                sizeof (USHORT) * cbitBYTE)) != S_OK)
                return fRet;
        }

        if (occf & OCCF_HAVE_OCCURRENCE) 
        {
            if ((fRet = SkipOldData (lpipb, pIndexDataNode)) != S_OK)
                return(fRet);
        }
        
        (*pTopicCount)--;
        fetchOldData = TRUE;
		dwOldTopicCount--;
    }
    
    if (fChanged)
    {
        MEMSET(pCopyNode->pCurPtr, 0,
            (size_t) (pCopyNode->pMaxAddress - pCopyNode->pCurPtr));

        // Write out the new data
        if (FileSeekWrite (lpipb->hfpbIdxFile, pIndexDataNode->pBuffer, dataOffset,
            dataSize, &errb) != (long)dataSize)
            return errb;
    }
    return(S_OK);
}    

VOID PRIVATE PASCAL NEAR RemapData (_LPIPB lpipb, PNODEINFO pCopyNode,
    PNODEINFO pIndexDataNode, DWORD dwTopicId, DWORD dwOldTopicId)
{
    DWORD dwTmp;
    DWORD dwOccs;
    PIH20 pHeader = &lpipb->BTreeData.Header;
    OCCF  occf = lpipb->occf;
    
	 pIndexDataNode->ibit = cbitBYTE - 1;

    // Skip TopicIdDelta, since we already have TopicId
    FGetDword(pIndexDataNode, pHeader->ckeyTopicId, &dwTmp);
    
    EmitDword (pCopyNode, dwTopicId - dwOldTopicId, pHeader->ckeyTopicId);
    
    // EmitDword (pCopyNode, dwTopicDelta, pHeader->ckeyTopicId);
    if (lpipb->idxf & IDXF_NORMALIZE)
    {
        FGetBits(pIndexDataNode, &dwTmp, sizeof (USHORT) * cbitBYTE);
        EmitBits(pCopyNode, dwTmp, (BYTE)(sizeof (WORD) * cbitBYTE));
    }

    if ((occf & OCCF_HAVE_OCCURRENCE) == 0)
        return;
        
    // Get the number of occurrences
    FGetDword(pIndexDataNode, pHeader->ckeyOccCount, &dwOccs);
    EmitDword (pCopyNode, dwOccs, pHeader->ckeyOccCount);
    
    //
    //  One pass through here for each occurence in the
    //  current sub-list.
    //
    for (; dwOccs; dwOccs--)
    {
        //
        //  Keeping word-counts?  If so, get it.
        //
        if (occf & OCCF_COUNT) 
        {
            FGetDword(pIndexDataNode, pHeader->ckeyWordCount, &dwTmp);
            EmitDword(pCopyNode, dwTmp, pHeader->ckeyWordCount);
        }
        //
        //  Keeping byte-offsets?  If so, get it.
        //
        if (occf & OCCF_OFFSET) 
        {
            FGetDword(pIndexDataNode, pHeader->ckeyOffset, &dwTmp);
            EmitDword(pCopyNode, dwTmp, pHeader->ckeyOffset);
        }
    }
    if (pCopyNode->ibit != cbitBYTE - 1)
    {
        pCopyNode->ibit = cbitBYTE - 1;
        pCopyNode->pCurPtr ++;
    }
}    

PRIVATE VOID PASCAL NEAR EmitBitStreamDWord (PNODEINFO pNode, DWORD dw,
    int ckeyCenter)
{
    BYTE    ucBits;

    // Bitstream scheme.
    //
    //      This writes "dw" one-bits followed by a zero-bit.
    //
    for (; dw;)
    {
        if (dw < cbitBYTE * sizeof(DWORD))
        {
            ucBits = (BYTE)dw;
            dw = 0;
        }
        else
        {
            ucBits = cbitBYTE * sizeof(DWORD);
            dw -= cbitBYTE * sizeof(DWORD);
        }
        EmitBits(pNode, argdwBits[ucBits], (BYTE)ucBits);
    }
    EmitBool(pNode, 0);
}
    
PRIVATE VOID PASCAL NEAR EmitFixedDWord (PNODEINFO pNode, DWORD dw,
    int ckeyCenter)
{
	// This just writes "ckey.ucCenter" bits of data.
    EmitBits (pNode, dw, (BYTE)(ckeyCenter + 1));
}

PRIVATE VOID PASCAL NEAR EmitBellDWord (PNODEINFO pNode, DWORD dw,
    int ckeyCenter)
{
    BYTE ucBits;
    
    // The "BELL" scheme is more complicated.
    ucBits = (BYTE)CbitBitsDw(dw);
    
    if (ucBits <= ckeyCenter) 
    {
        //  
        //  Encoding a small value.  Write a zero, then write 
        // "ckey.ucCenter" bits of the value, which
        //  is guaranteed to be enough.
        //
        EmitBool(pNode, 0);
        EmitBits(pNode, dw, (BYTE)(ckeyCenter));
		return;
    }
    
    //
    //  Encoding a value that won't fit in "ckey.ucCenter" bits.
    //  "ucBits" is how many bits it will really take.
    //
    //  First, write out "ucBits - ckey.ucCenter"  one-bits.
    //
    EmitBits(pNode, argdwBits[ucBits - ckeyCenter],
        (BYTE)(ucBits - ckeyCenter));
    //
    // Now, write out the value in "ucBits" bits,
    // but zero the high-bit first.
    //
    EmitBits(pNode, dw & argdwBits[ucBits - 1], ucBits);
}


/*************************************************************************
 *
 * @doc  PRIVATE INDEXING
 *       
 * @func VOID | EmitBits |
 *    Writes a bunch of bits into the output buffer.
 *
 * @parm PNODEINFO | pNode |
 *    Pointer to the output data structure
 *
 * @parm DWORD | dwVal |
 *    DWORD value to write
 *
 * @parm BYTE | cbits |
 *    Number of bits to write from dwVal
 *************************************************************************/

PRIVATE VOID PASCAL NEAR EmitBits (PNODEINFO pNode, DWORD dwVal, BYTE cBits)
{
    BYTE    cbitThisPassBits;
    BYTE    bThis;

    // Loop until no bits left
    for (; cBits;) 
    {

        if (pNode->ibit < 0) 
        {
            pNode->pCurPtr++;
            pNode->ibit = cbitBYTE - 1;
        }
        cbitThisPassBits = (pNode->ibit + 1 < cBits) ?
            pNode->ibit + 1 : cBits;
        bThis = (pNode->ibit == cbitBYTE - 1) ?
            0 : *pNode->pCurPtr;
        bThis |= ((dwVal >> (cBits - cbitThisPassBits)) <<
            (pNode->ibit - cbitThisPassBits + 1));
        *pNode->pCurPtr = (BYTE)bThis;
        pNode->ibit -= cbitThisPassBits;
        cBits -= (BYTE)cbitThisPassBits;
    }
}


/*************************************************************************
 *
 * @doc  PRIVATE INDEXING
 *       
 * @func VOID | EmitBool |
 *    Writes a single bit into the output buffer.
 *
 * @parm PNODEINFO | pNode |
 *    Pointer to the output data structure
 *
 * @parm BOOL | dwVal |
 *    BOOL value to write
 *************************************************************************/

PRIVATE VOID PASCAL NEAR EmitBool (PNODEINFO pNode, BOOL fVal)
{

    if (pNode->ibit < 0) 
    {   // This byte is full, point to a new byte
        pNode->pCurPtr++;
        pNode->ibit = cbitBYTE - 1;
    }
    if (pNode->ibit == cbitBYTE - 1) // Zero out a brand-new byte.
        *pNode->pCurPtr = (BYTE)0;
    if (fVal)                               // Write my boolean.
        *pNode->pCurPtr |= 1 << pNode->ibit;
    pNode->ibit--;
}


PUBLIC LONG PASCAL FAR CompareDWord (DWORD dw1, DWORD dw2, LPV lpParm)
{
   return (dw1 - dw2);
}