/*
 *  CINDEX.C
 *  
 *  Create and extend PR_CONVERSATION_INDEX
 */

#include <windows.h>
#include <mapiwin.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapidbg.h>
#include <memory.h>

#define cbConvIndexHdr          22
#define cbConvIndexComponent    5
#define bConvIndexRes           (BYTE)1

/*
 *  ExtractLastFileTime()
 *  
 *  Purpose:
 *  
 *      Parses an existing covnersation index and extracts the last
 *      FILETIME value contained in the index.
 */
VOID
ExtractLastFileTime (LPBYTE lpb, ULONG cb, FILETIME FAR * lpft)
{
    FILETIME ft;
    FILETIME ftCur;
    LPBYTE lpbEnd;

    //  Lets do some verification on the key
    //
    Assert (!IsBadReadPtr (lpb, (UINT)cb));
    Assert (!IsBadWritePtr (lpft, sizeof(FILETIME)));
    Assert (*lpb == bConvIndexRes);
    Assert (cb >= cbConvIndexHdr);
    Assert (!((cb - cbConvIndexHdr) % cbConvIndexComponent));

    //  Rebuild the header time\date into FILETIME format
    //
    ft.dwHighDateTime = (((DWORD)(lpb[1])) << 16) |
                        (((DWORD)(lpb[2])) << 8) |
                        ((DWORD)(lpb[3]));

    ft.dwLowDateTime = (((DWORD)(lpb[4])) << 24) |
                       (((DWORD)(lpb[5])) << 16);

    //  See where the last child chunk ends
    //
    lpbEnd = lpb + cb;
    lpb += cbConvIndexHdr;

    //  Now go through the child chunks to compute
    //  for the last FILETIME using the delta
    //
    while (lpb < lpbEnd)
    {
        //  Convert the delta of the current child
        //  chunk into the FILETIME format.  Use the
        //  delta code in the first bit to get the
        //  real delta.
        //
        //  Delta code : 1 = mask 10000000 = 0x80
        //
        if ((*lpb & 0x80) == 0x80)
        {
            //  Mask out the first bit used for the delta code
            //  *lpb | 0x7F;
            //
            ftCur.dwHighDateTime = (((DWORD)(lpb[0] & 0x7F)) << 15) |
                                   (((DWORD)(lpb[1])) << 7) |
                                   (((DWORD)(lpb[2])) >> 1);

            ftCur.dwLowDateTime = (((DWORD)(lpb[2])) << 31) |
                                  (((DWORD)(lpb[3])) << 23);

            ft = FtAddFt (ft, ftCur);
        }
        else
        {
            ftCur.dwHighDateTime = (((DWORD)(lpb[0] & 0x7F)) << 10) |
                                   (((DWORD)(lpb[1])) << 2) |
                                   (((DWORD)(lpb[2])) >> 6);

            ftCur.dwLowDateTime = (((DWORD)(lpb[2])) << 26) |
                                  (((DWORD)(lpb[3])) << 18);

            ft = FtAddFt (ft, ftCur);
        }

        // Advance to next child
        //
        lpb += cbConvIndexComponent;
    }

    //  If all went well, we sould have ended up at
    //  lpbEnd
    //
    Assert (lpb == lpbEnd);
    *lpft = ft;
    return;
}

/*
 *  ScFillConvHeader()
 *  
 *  Purpose:
 *  
 *      Fills in the header of a conversation index.  This function is
 *      called when a new conversation index is created.
 *  
 *  Assumptions:
 *  
 *      The buffer passed in should be big enough to hold cbConvIndexHdr
 *      bytes (22 bytes).
 */
SCODE
ScFillConvHeader (LPBYTE rgb, ULONG cb)
{
    SCODE sc = S_OK;
    SYSTEMTIME st;
    FILETIME ft;
    GUID guid;

    Assert (cb >= cbConvIndexHdr);
    Assert (!IsBadWritePtr (rgb, cbConvIndexHdr));

    //  (Ha). Put the reserved byte
    //
    rgb[0] = bConvIndexRes;
        
    //  (Hb). Put the current time
    //
    GetSystemTime (&st);
    SystemTimeToFileTime (&st, &ft);

    //  Construct the date\time one byte at a time
    //
    rgb[1] = (BYTE) ((ft.dwHighDateTime & 0x00FF0000) >> 16);
    rgb[2] = (BYTE) ((ft.dwHighDateTime & 0x0000FF00) >> 8);
    rgb[3] = (BYTE) (ft.dwHighDateTime & 0x000000FF);

    //  Drop the rightmost least significant 2 bytes
    //
    rgb[4] = (BYTE) ((ft.dwLowDateTime & 0xFF000000) >> 24);
    rgb[5] = (BYTE) ((ft.dwLowDateTime & 0x00FF0000) >> 16);

    //  (Hc). Now put the GUID
    //      {
    //          DWORD Data1;
    //          WORD  Data2;
    //          WORD  Data3;
    //          BYTE  Data4[8];
    //      } GUID;
    //
    sc = GetScode (CoCreateGuid (&guid));
    if (!FAILED (sc))
    {       
        //  Again, lets do it one byte at a time
        //
        rgb[6] = (BYTE) ((guid.Data1 & 0xFF000000) >> 24);
        rgb[7] = (BYTE) ((guid.Data1 & 0x00FF0000) >> 16);
        rgb[8] = (BYTE) ((guid.Data1 & 0x0000FF00) >> 8);
        rgb[9] = (BYTE) ((guid.Data1 & 0x000000FF));
        rgb[10] = (BYTE) ((guid.Data2 & 0xFF00) >> 8);
        rgb[11] = (BYTE) ((guid.Data2 & 0x00FF));
        rgb[12] = (BYTE) ((guid.Data3 & 0xFF00) >> 8);
        rgb[13] = (BYTE) ((guid.Data3 & 0x00FF));
    }

    //  Slurp the rest across
    //
    CopyMemory (&rgb[14], &guid.Data4, 8);
    DebugTraceSc (ScFillConvHeader(), sc);
    return sc;
}

/*
 *  ScAddConversationIndex()
 *  
 *  Purpose:
 *  
 *      Given the conversation index to a message, this function will
 *      create the conversation of a child message to the original.  If
 *      the no original is suplied, then an index is created that would
 *      signify the start of a new thread.
 */
SCODE
ScAddConversationIndex (ULONG cbParent,
    LPBYTE lpbParent,
    ULONG FAR * lpcb,
    LPBYTE FAR * lppb)
{
    SCODE sc;
    DWORD dwTemp;
    SYSTEMTIME st;
    FILETIME ft;
    FILETIME ftLast;
    FILETIME ftDelta;
    HMODULE hMAPIDll = NULL;
    typedef SCODE (STDAPICALLTYPE FAR *MAPICONVIDX)(ULONG, LPBYTE, ULONG FAR *, LPBYTE FAR *);
    MAPICONVIDX lpfnMAPIConvIdx = NULL;

#if defined (WIN16)
#define szScCreateConversationIndex "ScCreateConversationIndex"
#elif defined (_WIN32) && defined (_X86_)
#define szScCreateConversationIndex "ScCreateConversationIndex@16"
#elif defined (_ALPHA_) || defined (_MIPS_) || defined (_PPC_)
#define szScCreateConversationIndex "ScCreateConversationIndex"
#endif

#ifdef _WIN32
    #define szMAPIDll "mapi32.dll"
#else
    #define szMAPIDll "mapi.dll"
#endif

    /*
     * MAPI is going to export a function that is doing the same thing as this one.
     * So if the function is present we'll use it.
     */
    hMAPIDll = GetModuleHandle(szMAPIDll);
    if(hMAPIDll)
    {
        lpfnMAPIConvIdx = (MAPICONVIDX)GetProcAddress(hMAPIDll,
                                            szScCreateConversationIndex);
        if(lpfnMAPIConvIdx)
        {
            return (*lpfnMAPIConvIdx)(cbParent, lpbParent, lpcb, lppb);
        }
    }
    //  Ensure that the parent is what we think
    //  it should be
    //
    if ((cbParent < cbConvIndexHdr) ||
        ((cbParent - cbConvIndexHdr) % cbConvIndexComponent) ||
        (lpbParent[0] != bConvIndexRes))
    {
        cbParent = 0;
        *lpcb = cbConvIndexHdr;
    }
    else
        *lpcb = cbParent + cbConvIndexComponent;

    sc = MAPIAllocateBuffer (*lpcb, (LPVOID FAR *)lppb);
    if (!FAILED (sc))
    {
        if (cbParent == 0)
        {
            //  This is a new key, so all it ever contains
            //  is a header.  Fill it in and we are done
            //
            sc = ScFillConvHeader (*lppb, *lpcb);
            if (FAILED (sc))
            {
                MAPIFreeBuffer (*lppb);
                *lppb = NULL;
            }
        }
        else
        {
            //  First copy the old key across
            //
            CopyMemory (*lppb, lpbParent, (UINT)cbParent);

            //  (Cb).  First get the current time (we'll then get
            //  the absolute distance between the current time and
            //  the time in the last chunk)
            //
            GetSystemTime (&st);
            SystemTimeToFileTime (&st, &ft);

            //  Now get the time of the last chunk
            //  
            ExtractLastFileTime (lpbParent, cbParent, &ftLast);

            //  Now mask out the bits we don't want from the
            //  current time
            //
            ft.dwHighDateTime &= 0x00FFFFFF;
            ft.dwLowDateTime &= 0xFFFF0000;

            //  This assert is here to catch how often the
            //  5-byte time can collide and under what scenario,
            //  to see if 5 bytes + the next byte suffices to
            //  make this child chunk unique.
            //  
            Assert (!((ftLast.dwHighDateTime == ft.dwHighDateTime) &&
                (ftLast.dwLowDateTime == ft.dwLowDateTime)));

            //  Get the change in time
            //
            if ((ft.dwHighDateTime > ftLast.dwHighDateTime) ||
                ((ft.dwHighDateTime == ftLast.dwHighDateTime) &&
                 (ft.dwLowDateTime > ftLast.dwLowDateTime)))
            {
                ftDelta = FtSubFt (ft, ftLast);
            }
            else
                ftDelta = FtSubFt (ftLast, ft);

            //  If the delta is less than 1.7 yrs, use 0
            //
            if (!(ftDelta.dwHighDateTime & 0x00FE0000))
            {
                //  Just mask out the 31 bits that we
                //  want from the ftDelta
                //
                dwTemp = ((DWORD)(ftDelta.dwHighDateTime & 0x0001FFFF)) << 14 |
                         ((DWORD)(ftDelta.dwLowDateTime & 0xFFFC0000)) >> 18;

                //  Only the first byte is different
                //
                (*lppb)[cbParent] = (BYTE)((dwTemp & 0xFF000000) >> 24 );
            }
            else
            {
                //  Just mask out the 31 bits that we
                //  want from the ftDelta
                //
                dwTemp = ((DWORD)(ftDelta.dwHighDateTime & 0x003FFFFF)) << 9 |
                         ((DWORD)(ftDelta.dwLowDateTime & 0xFF800000)) >> 23;

                // Only the first byte is different
                //
                (*lppb)[cbParent] = (BYTE)(HIBYTE(HIWORD(dwTemp)) | 0x080);
            }

            //  The remaining delta bytes are the same
            //
            (*lppb)[cbParent + 1] = (BYTE) ((dwTemp & 0x00FF0000) >> 16);
            (*lppb)[cbParent + 2] = (BYTE) ((dwTemp & 0x0000FF00) >> 8);
            (*lppb)[cbParent + 3] = (BYTE) ((dwTemp & 0x000000FF) );

            //  (Cc). Next get the random number
            //  (Cd). Next get the sequence count
            //  -- we are going to use part of the tick count
            //
            (*lppb)[cbParent + 4] = (BYTE) (GetTickCount() & 0x000000FF);
        }
    }

    DebugTraceSc (ScAddConversationIndex(), sc);
    return sc;
}

