/*
 * Unit tests for DPA functions
 *
 * Copyright 2003 Uwe Bonnes
 * Copyright 2005 Felix Nawothnig
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "commctrl.h"
#include "objidl.h"

#include "wine/test.h"

#define expect(expected, got) ok(got == expected, "Expected %d, got %d\n", expected, got)

typedef struct _STREAMDATA
{
    DWORD dwSize;
    DWORD dwData2;
    DWORD dwItems;
} STREAMDATA, *PSTREAMDATA;

static HDPA    (WINAPI *pDPA_Clone)(const HDPA,HDPA);
static HDPA    (WINAPI *pDPA_Create)(INT);
static HDPA    (WINAPI *pDPA_CreateEx)(INT,HANDLE);
static PVOID   (WINAPI *pDPA_DeleteAllPtrs)(HDPA);
static PVOID   (WINAPI *pDPA_DeletePtr)(HDPA,INT);
static BOOL    (WINAPI *pDPA_Destroy)(HDPA);
static VOID    (WINAPI *pDPA_DestroyCallback)(HDPA,PFNDPAENUMCALLBACK,PVOID);
static VOID    (WINAPI *pDPA_EnumCallback)(HDPA,PFNDPAENUMCALLBACK,PVOID); 
static INT     (WINAPI *pDPA_GetPtr)(HDPA,INT);
static INT     (WINAPI *pDPA_GetPtrIndex)(HDPA,PVOID);
static BOOL    (WINAPI *pDPA_Grow)(HDPA,INT);
static INT     (WINAPI *pDPA_InsertPtr)(HDPA,INT,PVOID);
static HRESULT (WINAPI *pDPA_LoadStream)(HDPA*,PFNDPASTREAM,IStream*,LPVOID);
static BOOL    (WINAPI *pDPA_Merge)(HDPA,HDPA,DWORD,PFNDPACOMPARE,PFNDPAMERGE,LPARAM);
static HRESULT (WINAPI *pDPA_SaveStream)(HDPA,PFNDPASTREAM,IStream*,LPVOID);
static INT     (WINAPI *pDPA_Search)(HDPA,PVOID,INT,PFNDPACOMPARE,LPARAM,UINT);
static BOOL    (WINAPI *pDPA_SetPtr)(HDPA,INT,PVOID);
static BOOL    (WINAPI *pDPA_Sort)(HDPA,PFNDPACOMPARE,LPARAM);

#define COMCTL32_GET_PROC(func, ord) \
  ((p ## func = (PVOID)GetProcAddress(hcomctl32,(LPCSTR)ord)) ? 1 \
   : (trace( #func " not exported\n"), 0)) 

static BOOL InitFunctionPtrs(HMODULE hcomctl32)
{
    /* 4.00+ */
    if(COMCTL32_GET_PROC(DPA_Clone, 331) &&
       COMCTL32_GET_PROC(DPA_Create, 328) &&
       COMCTL32_GET_PROC(DPA_CreateEx, 340) &&
       COMCTL32_GET_PROC(DPA_DeleteAllPtrs, 337) &&
       COMCTL32_GET_PROC(DPA_DeletePtr, 336) &&
       COMCTL32_GET_PROC(DPA_Destroy, 329) &&
       COMCTL32_GET_PROC(DPA_GetPtr, 332) &&
       COMCTL32_GET_PROC(DPA_GetPtrIndex, 333) &&
       COMCTL32_GET_PROC(DPA_Grow, 330) &&
       COMCTL32_GET_PROC(DPA_InsertPtr, 334) &&
       COMCTL32_GET_PROC(DPA_Search, 339) &&
       COMCTL32_GET_PROC(DPA_SetPtr, 335) &&
       COMCTL32_GET_PROC(DPA_Sort, 338))
    {
        /* 4.71+ */
        COMCTL32_GET_PROC(DPA_DestroyCallback, 386) &&
        COMCTL32_GET_PROC(DPA_EnumCallback, 385) &&
        COMCTL32_GET_PROC(DPA_LoadStream, 9) &&
        COMCTL32_GET_PROC(DPA_Merge, 11) &&
        COMCTL32_GET_PROC(DPA_SaveStream, 10);

        return TRUE;
    }

    return FALSE;
}

/* Callbacks */
static INT CALLBACK CB_CmpLT(PVOID p1, PVOID p2, LPARAM lp)
{
    ok(lp == 0x1abe11ed, "lp=%ld\n", lp);
    return p1 < p2 ? -1 : p1 > p2 ? 1 : 0;
}

static INT CALLBACK CB_CmpGT(PVOID p1, PVOID p2, LPARAM lp)
{
    ok(lp == 0x1abe11ed, "lp=%ld\n", lp);
    return p1 > p2 ? -1 : p1 < p2 ? 1 : 0;
}

/* merge callback messages counter
   DPAMM_MERGE     1
   DPAMM_DELETE    2
   DPAMM_INSERT    3  */
static INT nMessages[4];

static PVOID CALLBACK CB_MergeInsertSrc(UINT op, PVOID p1, PVOID p2, LPARAM lp)
{
    nMessages[op]++;
    ok(lp == 0x1abe11ed, "lp=%ld\n", lp);
    return p1;
}        

static PVOID CALLBACK CB_MergeDeleteOddSrc(UINT op, PVOID p1, PVOID p2, LPARAM lp)
{
    nMessages[op]++;
    ok(lp == 0x1abe11ed, "lp=%ld\n", lp);
    return ((PCHAR)p2)+1;
}

static INT nEnum;

static INT CALLBACK CB_EnumFirstThree(PVOID pItem, PVOID lp)
{   
    INT i;

    i = pDPA_GetPtrIndex(lp, pItem);
    ok(i == nEnum, "i=%d nEnum=%d\n", i, nEnum);
    nEnum++;
    pDPA_SetPtr(lp, i, (PVOID)7);
    return pItem != (PVOID)3;
}

static HRESULT CALLBACK CB_Save(DPASTREAMINFO *pInfo, IStream *pStm, LPVOID lp)
{
    HRESULT hRes;

    ok(lp == (LPVOID)0xdeadbeef, "lp=%p\n", lp);
    hRes = IStream_Write(pStm, &pInfo->iPos, sizeof(INT), NULL);
    expect(S_OK, hRes);
    hRes = IStream_Write(pStm, &pInfo->pvItem, sizeof(PVOID), NULL);
    expect(S_OK, hRes);
    return S_OK;
}

static HRESULT CALLBACK CB_Load(DPASTREAMINFO *pInfo, IStream *pStm, LPVOID lp)
{
    HRESULT hRes;
    INT iOldPos;
    
    iOldPos = pInfo->iPos;
    ok(lp == (LPVOID)0xdeadbeef, "lp=%p\n", lp);
    hRes = IStream_Read(pStm, &pInfo->iPos, sizeof(INT), NULL);
    expect(S_OK, hRes);
    ok(pInfo->iPos == iOldPos, "iPos=%d iOldPos=%d\n", pInfo->iPos, iOldPos);
    hRes = IStream_Read(pStm, &pInfo->pvItem, sizeof(PVOID), NULL);
    expect(S_OK, hRes);
    return S_OK;
}

static BOOL CheckDPA(HDPA dpa, DWORD dwIn, PDWORD pdwOut)
{
    DWORD dwOut = 0;
    INT i;

    for(i = 0; i < 8;)
    {
        ULONG_PTR ulItem = (ULONG_PTR)pDPA_GetPtr(dpa, i++);
        if(!ulItem) break;
        dwOut = dwOut << 4 | (ulItem & 0xf);
    }
    
    *pdwOut = dwOut;

    if(dwOut != dwIn)
    {
        pDPA_DeleteAllPtrs(dpa);
        
        do
        {
            pDPA_InsertPtr(dpa, 0, (PVOID)(ULONG_PTR)(dwIn & 0xf));
            dwIn >>= 4;
        }
        while(dwIn);
        
        return FALSE;
    }
    
    return TRUE;
}

static void test_dpa(void)
{
    SYSTEM_INFO si;
    HANDLE hHeap;
    HDPA dpa, dpa2, dpa3;
    INT ret, i;
    PVOID p;
    DWORD dw, dw2, dw3;
    BOOL rc;
    
    GetSystemInfo(&si);
    hHeap = HeapCreate(0, 1, 2);
    ok(hHeap != NULL, "error=%d\n", GetLastError());
    dpa3 = pDPA_CreateEx(0, hHeap);
    ok(dpa3 != NULL, "\n");
    ret = pDPA_Grow(dpa3, si.dwPageSize + 1);
    ok(!ret && GetLastError() == ERROR_NOT_ENOUGH_MEMORY,
       "ret=%d error=%d\n", ret, GetLastError());

    dpa = pDPA_Create(0);
    ok(dpa != NULL, "\n");

    /* Set item with out of bound index */
    ok(pDPA_SetPtr(dpa, 1, (PVOID)6), "\n");
    /* Fill the created gap */
    ok(pDPA_SetPtr(dpa, 0, (PVOID)5), "\n");
    rc=CheckDPA(dpa, 0x56, &dw);
    ok(rc, "dw=0x%x\n", dw);
    
    /* Prepend item */
    ret = pDPA_InsertPtr(dpa, 1, (PVOID)1);
    ok(ret == 1, "ret=%d\n", ret);
    /* Append item using correct index */
    ret = pDPA_InsertPtr(dpa, 3, (PVOID)3);
    ok(ret == 3, "ret=%d\n", ret);
    /* Append item using out of bound index */
    ret = pDPA_InsertPtr(dpa, 5, (PVOID)2);
    ok(ret == 4, "ret=%d\n", ret);
    /* Append item using DPA_APPEND */ 
    ret = pDPA_InsertPtr(dpa, DPA_APPEND, (PVOID)4);
    ok(ret == 5, "ret=%d\n", ret);

    rc=CheckDPA(dpa, 0x516324, &dw);
    ok(rc, "dw=0x%x\n", dw);

    for(i = 1; i <= 6; i++)
    {
        INT j, k;
        k = pDPA_GetPtrIndex(dpa, (PVOID)(INT_PTR)i);
        /* Linear searches should work on unsorted DPAs */
        j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, 0, CB_CmpLT, 0x1abe11ed, 0);
        ok(j == k, "j=%d k=%d\n", j, k);
    }

    /* Sort DPA */
    ok(pDPA_Sort(dpa, CB_CmpGT, 0x1abe11ed), "\n");
    rc=CheckDPA(dpa, 0x654321, &dw);
    ok(rc, "dw=0x%x\n", dw);
    
    /* Clone into a new DPA */
    dpa2 = pDPA_Clone(dpa, NULL);
    ok(dpa2 != NULL, "\n");
    /* The old data should have been preserved */
    rc=CheckDPA(dpa2, 0x654321, &dw2);
    ok(rc, "dw=0x%x\n", dw2);
    ok(pDPA_Sort(dpa, CB_CmpLT, 0x1abe11ed), "\n");
    
    /* Test if the DPA itself was really copied */
    rc=CheckDPA(dpa,  0x123456, &dw);
    ok(rc, "dw=0x%x\n",  dw );
    rc=CheckDPA(dpa2, 0x654321, &dw2);
    ok(rc, "dw2=0x%x\n", dw2);

    /* Clone into an old DPA */
    SetLastError(ERROR_SUCCESS);
    p = pDPA_Clone(dpa, dpa3);
    ok(p == dpa3, "p=%p\n", p);
    rc=CheckDPA(dpa3, 0x123456, &dw3);
    ok(rc, "dw3=0x%x\n", dw3);

    for(i = 1; i <= 6; i++)
    {
        INT j;

        /* The array is in order so ptr == index+1 */
        j = pDPA_GetPtrIndex(dpa, (PVOID)(INT_PTR)i);
        ok(j+1 == i, "j=%d i=%d\n", j, i);
        j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, 0, CB_CmpLT, 0x1abe11ed, DPAS_SORTED);
        ok(j+1 == i, "j=%d i=%d\n", j, i);

        /* Linear searches respect iStart ... */
        j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, i+1, CB_CmpLT, 0x1abe11ed, 0);
        ok(j == DPA_ERR, "j=%d\n", j);
        /* ... but for a binary search it's ignored */
        j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, i+1, CB_CmpLT, 0x1abe11ed, DPAS_SORTED);
        ok(j+1 == i, "j=%d i=%d\n", j, i);
    }

    /* Try to get the index of a nonexistent item */
    i = pDPA_GetPtrIndex(dpa, (PVOID)7);
    ok(i == DPA_ERR, "i=%d\n", i);
    
    /* Try to delete out of bound indexes */
    p = pDPA_DeletePtr(dpa, -1);
    ok(p == NULL, "p=%p\n", p);
    p = pDPA_DeletePtr(dpa, 6);
    ok(p == NULL, "p=%p\n", p);

    /* Delete the third item */
    p = pDPA_DeletePtr(dpa, 2);
    ok(p == (PVOID)3, "p=%p\n", p);
    rc=CheckDPA(dpa, 0x12456, &dw);
    ok(rc, "dw=0x%x\n", dw);

    /* Check where to re-insert the deleted item */
    i = pDPA_Search(dpa, (PVOID)3, 0, 
                    CB_CmpLT, 0x1abe11ed, DPAS_SORTED|DPAS_INSERTAFTER);
    ok(i == 2, "i=%d\n", i);
    /* DPAS_INSERTBEFORE works just like DPAS_INSERTAFTER */
    i = pDPA_Search(dpa, (PVOID)3, 0,
                    CB_CmpLT, 0x1abe11ed, DPAS_SORTED|DPAS_INSERTBEFORE);
    ok(i == 2, "i=%d\n", i);
    /* without DPAS_INSERTBEFORE/AFTER */
    i = pDPA_Search(dpa, (PVOID)3, 0,
                    CB_CmpLT, 0x1abe11ed, DPAS_SORTED);
    ok(i == -1, "i=%d\n", i);

    /* Re-insert the item */
    ret = pDPA_InsertPtr(dpa, 2, (PVOID)3);
    ok(ret == 2, "ret=%d i=%d\n", ret, 2);
    rc=CheckDPA(dpa, 0x123456, &dw);
    ok(rc, "dw=0x%x\n", dw);
    
    /* When doing a binary search while claiming reverse order all indexes
     * should be bogus */
    for(i = 0; i < 6; i++)
    {
        INT j = pDPA_Search(dpa, (PVOID)(INT_PTR)i, 0, CB_CmpGT, 0x1abe11ed,
                            DPAS_SORTED|DPAS_INSERTBEFORE);
        ok(j != i, "i=%d\n", i);
    }

    /* Setting item with huge index should work */
    ok(pDPA_SetPtr(dpa2, 0x12345, (PVOID)0xdeadbeef), "\n");
    ret = pDPA_GetPtrIndex(dpa2, (PVOID)0xdeadbeef);
    ok(ret == 0x12345, "ret=%d\n", ret);
          
    pDPA_DeleteAllPtrs(dpa2);
    rc=CheckDPA(dpa2, 0, &dw2);
    ok(rc, "dw2=0x%x\n", dw2);

    pDPA_Destroy(dpa);
    pDPA_Destroy(dpa2);
    pDPA_Destroy(dpa3);
}

static void test_DPA_Merge(void)
{
    HDPA dpa, dpa2, dpa3;
    INT ret, i;
    DWORD dw;
    BOOL rc;

    if(!pDPA_Merge)
    {
        win_skip("DPA_Merge() not available\n");
        return;
    }

    dpa  = pDPA_Create(0);
    dpa2 = pDPA_Create(0);
    dpa3 = pDPA_Create(0);

    ret = pDPA_InsertPtr(dpa, 0, (PVOID)1);
    ok(ret == 0, "ret=%d\n", ret);
    ret = pDPA_InsertPtr(dpa, 1, (PVOID)3);
    ok(ret == 1, "ret=%d\n", ret);
    ret = pDPA_InsertPtr(dpa, 2, (PVOID)5);
    ok(ret == 2, "ret=%d\n", ret);

    rc = CheckDPA(dpa, 0x135, &dw);
    ok(rc, "dw=0x%x\n", dw);

    for (i = 0; i < 6; i++)
    {
        ret = pDPA_InsertPtr(dpa2, i, (PVOID)(INT_PTR)(6-i));
        ok(ret == i, "ret=%d\n", ret);
        ret = pDPA_InsertPtr(dpa3, i, (PVOID)(INT_PTR)(i+1));
        ok(ret == i, "ret=%d\n", ret);
    }

    rc = CheckDPA(dpa2, 0x654321, &dw);
    ok(rc, "dw=0x%x\n", dw);
    rc = CheckDPA(dpa3, 0x123456, &dw);
    ok(rc, "dw=0x%x\n", dw);

    /* Delete all odd entries from dpa2 */
    memset(nMessages, 0, sizeof(nMessages));
    pDPA_Merge(dpa2, dpa, DPAM_INTERSECT,
               CB_CmpLT, CB_MergeDeleteOddSrc, 0x1abe11ed);
    rc = CheckDPA(dpa2, 0x246, &dw);
    ok(rc, "dw=0x%x\n", dw);

    expect(3, nMessages[DPAMM_MERGE]);
    expect(3, nMessages[DPAMM_DELETE]);
    expect(0, nMessages[DPAMM_INSERT]);

    for (i = 0; i < 6; i++)
    {
        ret = pDPA_InsertPtr(dpa2, i, (PVOID)(INT_PTR)(6-i));
        ok(ret == i, "ret=%d\n", ret);
    }

    /* DPAM_INTERSECT - returning source while merging */
    memset(nMessages, 0, sizeof(nMessages));
    pDPA_Merge(dpa2, dpa, DPAM_INTERSECT,
               CB_CmpLT, CB_MergeInsertSrc, 0x1abe11ed);
    rc = CheckDPA(dpa2, 0x135, &dw);
    ok(rc, "dw=0x%x\n", dw);

    expect(3, nMessages[DPAMM_MERGE]);
    expect(6, nMessages[DPAMM_DELETE]);
    expect(0, nMessages[DPAMM_INSERT]);

    /* DPAM_UNION */
    pDPA_DeleteAllPtrs(dpa);
    pDPA_InsertPtr(dpa, 0, (PVOID)1);
    pDPA_InsertPtr(dpa, 1, (PVOID)3);
    pDPA_InsertPtr(dpa, 2, (PVOID)5);
    pDPA_DeleteAllPtrs(dpa2);
    pDPA_InsertPtr(dpa2, 0, (PVOID)2);
    pDPA_InsertPtr(dpa2, 1, (PVOID)4);
    pDPA_InsertPtr(dpa2, 2, (PVOID)6);

    memset(nMessages, 0, sizeof(nMessages));
    pDPA_Merge(dpa2, dpa, DPAM_UNION,
               CB_CmpLT, CB_MergeInsertSrc, 0x1abe11ed);
    rc = CheckDPA(dpa2, 0x123456, &dw);
    ok(rc ||
       broken(!rc && dw == 0x23456), /* 4.7x */
       "dw=0x%x\n", dw);

    expect(0, nMessages[DPAMM_MERGE]);
    expect(0, nMessages[DPAMM_DELETE]);
    ok(nMessages[DPAMM_INSERT] == 3 ||
       broken(nMessages[DPAMM_INSERT] == 2), /* 4.7x */
       "Expected 3, got %d\n", nMessages[DPAMM_INSERT]);

    /* Merge dpa3 into dpa2 and dpa */
    memset(nMessages, 0, sizeof(nMessages));
    pDPA_Merge(dpa, dpa3, DPAM_UNION|DPAM_SORTED,
               CB_CmpLT, CB_MergeInsertSrc, 0x1abe11ed);
    expect(3, nMessages[DPAMM_MERGE]);
    expect(0, nMessages[DPAMM_DELETE]);
    expect(3, nMessages[DPAMM_INSERT]);


    pDPA_DeleteAllPtrs(dpa2);
    pDPA_InsertPtr(dpa2, 0, (PVOID)2);
    pDPA_InsertPtr(dpa2, 1, (PVOID)4);
    pDPA_InsertPtr(dpa2, 2, (PVOID)6);

    memset(nMessages, 0, sizeof(nMessages));
    pDPA_Merge(dpa2, dpa3, DPAM_UNION|DPAM_SORTED,
               CB_CmpLT, CB_MergeInsertSrc, 0x1abe11ed);
    expect(3, nMessages[DPAMM_MERGE]);
    expect(0, nMessages[DPAMM_DELETE]);
    ok(nMessages[DPAMM_INSERT] == 3 ||
       broken(nMessages[DPAMM_INSERT] == 2), /* 4.7x */
       "Expected 3, got %d\n", nMessages[DPAMM_INSERT]);

    rc = CheckDPA(dpa,  0x123456, &dw);
    ok(rc, "dw=0x%x\n",  dw);
    rc = CheckDPA(dpa2, 0x123456, &dw);
    ok(rc ||
       broken(!rc), /* win98 */
       "dw=0x%x\n", dw);
    rc = CheckDPA(dpa3, 0x123456, &dw);
    ok(rc, "dw=0x%x\n", dw);

    pDPA_Destroy(dpa);
    pDPA_Destroy(dpa2);
    pDPA_Destroy(dpa3);
}

static void test_DPA_EnumCallback(void)
{
    HDPA dpa;
    BOOL rc;
    DWORD dw;
    INT i, ret;

    if(!pDPA_EnumCallback)
    {
        win_skip("DPA_EnumCallback() not available\n");
        return;
    }

    dpa = pDPA_Create(0);

    for (i = 0; i < 6; i++)
    {
        ret = pDPA_InsertPtr(dpa, i, (PVOID)(INT_PTR)(i+1));
        ok(ret == i, "ret=%d\n", ret);
    }

    rc = CheckDPA(dpa, 0x123456, &dw);
    ok(rc, "dw=0x%x\n", dw);

    nEnum = 0;
    /* test callback sets first 3 items to 7 */
    pDPA_EnumCallback(dpa, CB_EnumFirstThree, dpa);
    rc = CheckDPA(dpa, 0x777456, &dw);
    ok(rc, "dw=0x%x\n", dw);
    ok(nEnum == 3, "nEnum=%d\n", nEnum);

    pDPA_Destroy(dpa);
}

static void test_DPA_DestroyCallback(void)
{
    HDPA dpa;
    INT i, ret;

    if(!pDPA_DestroyCallback)
    {
        win_skip("DPA_DestroyCallback() not available\n");
        return;
    }

    dpa = pDPA_Create(0);

    for (i = 0; i < 3; i++)
    {
        ret = pDPA_InsertPtr(dpa, i, (PVOID)(INT_PTR)(i+1));
        ok(ret == i, "ret=%d\n", ret);
    }

    nEnum = 0;
    pDPA_DestroyCallback(dpa, CB_EnumFirstThree, dpa);
    ok(nEnum == 3, "nEnum=%d\n", nEnum);
}

static void test_DPA_LoadStream(void)
{
    static const WCHAR szStg[] = { 'S','t','g',0 };
    IStorage* pStg = NULL;
    IStream* pStm = NULL;
    LARGE_INTEGER li;
    ULARGE_INTEGER uli;
    DWORD dwMode;
    HRESULT hRes;
    STREAMDATA header;
    ULONG written, ret;
    HDPA dpa;

    if(!pDPA_LoadStream)
    {
        win_skip("DPA_LoadStream() not available. Skipping stream tests.\n");
        return;
    }

    hRes = CoInitialize(NULL);
    if (hRes != S_OK)
    {
        ok(0, "hResult: %d\n", hRes);
        return;
    }

    dwMode = STGM_DIRECT|STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE;
    hRes = StgCreateDocfile(NULL, dwMode|STGM_DELETEONRELEASE, 0, &pStg);
    expect(S_OK, hRes);

    hRes = IStorage_CreateStream(pStg, szStg, dwMode, 0, 0, &pStm);
    expect(S_OK, hRes);

    /* write less than header size */
    li.QuadPart = 0;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);

    memset(&header, 0, sizeof(header));
    written = 0;
    uli.QuadPart = sizeof(header)-1;
    hRes = IStream_SetSize(pStm, uli);
    expect(S_OK, hRes);
    hRes = IStream_Write(pStm, &header, sizeof(header)-1, &written);
    expect(S_OK, hRes);
    written -= sizeof(header)-1;
    expect(0, written);

    li.QuadPart = 0;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);

    hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, NULL);
    expect(E_FAIL, hRes);

    /* check stream position after header read failed */
    li.QuadPart = 0;
    uli.QuadPart = 1;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_CUR, &uli);
    expect(S_OK, hRes);
    ok(uli.QuadPart == 0, "Expected to position reset\n");

    /* write valid header for empty DPA */
    header.dwSize = sizeof(header);
    header.dwData2 = 1;
    header.dwItems = 0;
    written = 0;

    li.QuadPart = 0;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);

    uli.QuadPart = sizeof(header);
    hRes = IStream_SetSize(pStm, uli);
    expect(S_OK, hRes);

    hRes = IStream_Write(pStm, &header, sizeof(header), &written);
    expect(S_OK, hRes);
    written -= sizeof(header);
    expect(0, written);

    li.QuadPart = 0;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);

    dpa = NULL;
    hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, NULL);
    expect(S_OK, hRes);
    DPA_Destroy(dpa);

    /* try with altered dwData2 field */
    header.dwSize = sizeof(header);
    header.dwData2 = 2;
    header.dwItems = 0;

    li.QuadPart = 0;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);
    hRes = IStream_Write(pStm, &header, sizeof(header), &written);
    expect(S_OK, hRes);
    written -= sizeof(header);
    expect(0, written);

    li.QuadPart = 0;
    hRes = IStream_Seek(pStm, li, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);

    hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, (void*)0xdeadbeef);
    expect(E_FAIL, hRes);

    ret = IStream_Release(pStm);
    ok(!ret, "ret=%d\n", ret);

    ret = IStorage_Release(pStg);
    ok(!ret, "ret=%d\n", ret);

    CoUninitialize();
}

static void test_DPA_SaveStream(void)
{
    HDPA dpa;
    static const WCHAR szStg[] = { 'S','t','g',0 };
    IStorage* pStg = NULL;
    IStream* pStm = NULL;
    DWORD dwMode, dw;
    HRESULT hRes;
    INT ret;
    INT i;
    BOOL rc;
    LARGE_INTEGER liZero;

    if(!pDPA_SaveStream)
    {
        win_skip("DPA_SaveStream() not available. Skipping stream tests.\n");
        return;
    }

    hRes = CoInitialize(NULL);
    if (hRes != S_OK)
    {
        ok(0, "hResult: %d\n", hRes);
        return;
    }

    dwMode = STGM_DIRECT|STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE;
    hRes = StgCreateDocfile(NULL, dwMode|STGM_DELETEONRELEASE, 0, &pStg);
    expect(S_OK, hRes);

    hRes = IStorage_CreateStream(pStg, szStg, dwMode, 0, 0, &pStm);
    expect(S_OK, hRes);

    dpa = pDPA_Create(0);

    /* simple parameter check */
    hRes = pDPA_SaveStream(dpa, NULL, pStm, NULL);
    ok(hRes == E_INVALIDARG ||
       broken(hRes == S_OK) /* XP and below */, "Wrong result, %d\n", hRes);
if (0) {
    /* crashes on XP */
    hRes = pDPA_SaveStream(NULL, CB_Save, pStm, NULL);
    expect(E_INVALIDARG, hRes);

    hRes = pDPA_SaveStream(dpa, CB_Save, NULL, NULL);
    expect(E_INVALIDARG, hRes);
}

    /* saving/loading */
    for (i = 0; i < 6; i++)
    {
        ret = pDPA_InsertPtr(dpa, i, (PVOID)(INT_PTR)(i+1));
        ok(ret == i, "ret=%d\n", ret);
    }

    liZero.QuadPart = 0;
    hRes = IStream_Seek(pStm, liZero, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);

    hRes = pDPA_SaveStream(dpa, CB_Save, pStm, (void*)0xdeadbeef);
    expect(S_OK, hRes);
    pDPA_Destroy(dpa);

    liZero.QuadPart = 0;
    hRes = IStream_Seek(pStm, liZero, STREAM_SEEK_SET, NULL);
    expect(S_OK, hRes);
    hRes = pDPA_LoadStream(&dpa, CB_Load, pStm, (void*)0xdeadbeef);
    expect(S_OK, hRes);
    rc = CheckDPA(dpa, 0x123456, &dw);
    ok(rc, "dw=0x%x\n", dw);
    pDPA_Destroy(dpa);

    ret = IStream_Release(pStm);
    ok(!ret, "ret=%d\n", ret);

    ret = IStorage_Release(pStg);
    ok(!ret, "ret=%d\n", ret);

    CoUninitialize();
}

START_TEST(dpa)
{
    HMODULE hcomctl32;

    hcomctl32 = GetModuleHandleA("comctl32.dll");

    if(!InitFunctionPtrs(hcomctl32))
    {
        win_skip("Needed functions are not available\n");
        return;
    }

    test_dpa();
    test_DPA_Merge();
    test_DPA_EnumCallback();
    test_DPA_DestroyCallback();
    test_DPA_LoadStream();
    test_DPA_SaveStream();
}
