/* OCaml-Win32
 * win32_listview.c
 * Copyright (c) 2002-2004 by Harry Chomsky
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "win32.h"

/********************************************************************/
/* User Interface: Windows User Experience: Windows Shell: SDK: Windows Controls: Individual Control Information: List-View Controls */

static DWORD listview_extended_styles[] = {
    LVS_EX_CHECKBOXES,
    LVS_EX_FLATSB,
    LVS_EX_FULLROWSELECT,
    LVS_EX_GRIDLINES,
    LVS_EX_HEADERDRAGDROP,
    LVS_EX_INFOTIP,
    LVS_EX_MULTIWORKAREAS,
    LVS_EX_ONECLICKACTIVATE,
    LVS_EX_REGIONAL,
    LVS_EX_SUBITEMIMAGES,
    LVS_EX_TRACKSELECT,
    LVS_EX_TWOCLICKACTIVATE,
    LVS_EX_UNDERLINECOLD,
    LVS_EX_UNDERLINEHOT
};
Define_static_constants(listview_extended_styles);

static void lvitemstate_val(value val, DWORD* state, DWORD* mask)
{
    *state = 0;
    *mask = 0;
    if (Is_some(Field(val, 0))) {
        *mask |= LVIS_ACTIVATING;
        if (Bool_val(Value_of(Field(val, 0))))
            *state |= LVIS_ACTIVATING;
    }
    if (Is_some(Field(val, 1))) {
        *mask |= LVIS_CUT;
        if (Bool_val(Value_of(Field(val, 1))))
            *state |= LVIS_CUT;
    }
    if (Is_some(Field(val, 2))) {
        *mask |= LVIS_DROPHILITED;
        if (Bool_val(Value_of(Field(val, 2))))
            *state |= LVIS_DROPHILITED;
    }
    if (Is_some(Field(val, 3))) {
        *mask |= LVIS_FOCUSED;
        if (Bool_val(Value_of(Field(val, 3))))
            *state |= LVIS_FOCUSED;
    }
    if (Is_some(Field(val, 4))) {
        *mask |= LVIS_SELECTED;
        if (Bool_val(Value_of(Field(val, 4))))
            *state |= LVIS_SELECTED;
    }
    if (Is_some(Field(val, 5))) {
        *mask |= LVIS_OVERLAYMASK;
        *state |= INDEXTOOVERLAYMASK(Int_val(Value_of(Field(val, 5))));
    }
    if (Is_some(Field(val, 6))) {
        *mask |= LVIS_STATEIMAGEMASK;
        *state |= INDEXTOSTATEIMAGEMASK(Int_val(Value_of(Field(val, 6))));
    }
}

static value alloc_lvitemstate(DWORD state, DWORD mask)
{
    CAMLparam0();
    CAMLlocal2(val, v);

    val = alloc_tuple(7);
    if (mask & LVIS_ACTIVATING) {
        v = alloc_some(Val_bool((state & LVIS_ACTIVATING) != 0));
        Store_field(val, 0, v);
    } else
        Store_field(val, 0, Val_none);
    if (mask & LVIS_CUT) {
        v = alloc_some(Val_bool((state & LVIS_CUT) != 0));
        Store_field(val, 1, v);
    } else
        Store_field(val, 1, Val_none);
    if (mask & LVIS_DROPHILITED) {
        v = alloc_some(Val_bool((state & LVIS_DROPHILITED) != 0));
        Store_field(val, 2, v);
    } else
        Store_field(val, 2, Val_none);
    if (mask & LVIS_FOCUSED) {
        v = alloc_some(Val_bool((state & LVIS_FOCUSED) != 0));
        Store_field(val, 3, v);
    } else
        Store_field(val, 3, Val_none);
    if (mask & LVIS_SELECTED) {
        v = alloc_some(Val_bool((state & LVIS_SELECTED) != 0));
        Store_field(val, 4, v);
    } else
        Store_field(val, 4, Val_none);
    if (mask & LVIS_OVERLAYMASK) {
        v = alloc_some(Val_int((state & LVIS_OVERLAYMASK) >> 8));
        Store_field(val, 5, v);
    } else
        Store_field(val, 5, Val_none);
    if (mask & LVIS_STATEIMAGEMASK) {
        v = alloc_some(Val_int((state & LVIS_STATEIMAGEMASK) >> 12));
        Store_field(val, 6, v);
    } else
        Store_field(val, 6, Val_none);
    CAMLreturn(val);
}

static LVITEM* new_lvitem(value val)
{
    LVITEM* lvi;

    lvi = malloc(sizeof(LVITEM));
    memset(lvi, 0, sizeof(LVITEM));
    lvi->iItem = Int_val(Field(val, 0));
    lvi->iSubItem = Int_val(Field(val, 1));
    if (Is_some(Field(val, 2))) {
        lvi->mask |= LVIF_STATE;
        lvitemstate_val(Value_of(Field(val, 2)), &lvi->state, &lvi->stateMask);
    }
    if (Is_some(Field(val, 3))) {
        lvi->mask |= LVIF_TEXT;
        lvi->pszText = new_commoncontroltext(Value_of(Field(val, 3)));
    }
    if (Is_some(Field(val, 4))) {
        lvi->mask |= LVIF_IMAGE;
        lvi->iImage = commoncontrolimageindex_val(Value_of(Field(val, 4)));
    }
    if (Is_some(Field(val, 5))) {
        lvi->mask |= LVIF_PARAM;
        lvi->lParam = Int32_val(Value_of(Field(val, 5)));
    }
    if (Is_some(Field(val, 6))) {
        lvi->mask |= LVIF_INDENT;
        lvi->iIndent = Int_val(Value_of(Field(val, 6)));
    }
    if (Bool_val(Field(val, 7)))
        lvi->mask |= LVIF_NORECOMPUTE;
    return lvi;
}

static void free_lvitem(LVITEM* lvi)
{
    if (lvi->mask & LVIF_TEXT)
        free_commoncontroltext(lvi->pszText);
    free(lvi);
}

static value alloc_lvitem(LVITEM* lvi)
{
    CAMLparam0();
    CAMLlocal2(val, v);

    val = alloc_tuple(8);
    Store_field(val, 0, Val_int(lvi->iItem));
    Store_field(val, 1, Val_int(lvi->iSubItem));
    if (lvi->mask & LVIF_STATE) {
        v = alloc_some(alloc_lvitemstate(lvi->state, lvi->stateMask));
        Store_field(val, 2, v);
    } else
        Store_field(val, 2, Val_none);
    if (lvi->mask & LVIF_TEXT) {
        v = alloc_some(alloc_commoncontroltext(lvi->pszText));
        Store_field(val, 3, v);
    } else
        Store_field(val, 3, Val_none);
    if (lvi->mask & LVIF_IMAGE) {
        v = alloc_some(alloc_commoncontrolimageindex(lvi->iImage));
        Store_field(val, 4, v);
    } else
        Store_field(val, 4, Val_none);
    if (lvi->mask & LVIF_PARAM) {
        v = alloc_some(copy_int32(lvi->lParam));
        Store_field(val, 5, v);
    } else
        Store_field(val, 5, Val_none);
    if (lvi->mask & LVIF_INDENT) {
        v = alloc_some(Val_int(lvi->iIndent));
        Store_field(val, 6, v);
    } else
        Store_field(val, 6, Val_none);
    Store_field(val, 7, Val_bool((lvi->mask & LVIF_NORECOMPUTE) != 0));
    CAMLreturn(val);
}

static DWORD lvitem_state_components[] = {
    LVIS_ACTIVATING,
    LVIS_CUT,
    LVIS_DROPHILITED,
    LVIS_FOCUSED,
    LVIS_SELECTED,
    LVIS_OVERLAYMASK,
    LVIS_STATEIMAGEMASK
};
Define_static_constants(lvitem_state_components);

static DWORD lvitem_components_noparam[] = {
    LVIF_IMAGE,
    LVIF_PARAM,
    LVIF_INDENT
};
Define_static_constants(lvitem_components_noparam);

static DWORD lvcolumn_justifications[] = {
    LVCFMT_CENTER,
    LVCFMT_LEFT,
    LVCFMT_RIGHT
};
Define_static_constants(lvcolumn_justifications);

static DWORD lvcolumn_format_options[] = {
    LVCFMT_BITMAP_ON_RIGHT,
    LVCFMT_COL_HAS_IMAGES,
    LVCFMT_IMAGE
};
Define_static_constants(lvcolumn_format_options);

static LVCOLUMN* new_lvcolumn(value val)
{
    LVCOLUMN* lvc;
    value v;

    lvc = malloc(sizeof(LVCOLUMN));
    memset(lvc, 0, sizeof(LVCOLUMN));
    if (Is_some(Field(val, 0))) {
        lvc->mask |= LVCF_FMT;
        v = Value_of(Field(val, 0));
        lvc->fmt =
            Constant_val(Field(v, 0), lvcolumn_justifications) |
            flags_val(Field(v, 1), lvcolumn_format_options);
    }
    if (Is_some(Field(val, 1))) {
        lvc->mask |= LVCF_WIDTH;
        lvc->cx = Int_val(Value_of(Field(val, 1)));
    }
    if (Is_some(Field(val, 2))) {
        lvc->mask |= LVCF_TEXT;
        lvc->pszText = new_string(Value_of(Field(val, 2)));
    }
    if (Is_some(Field(val, 3))) {
        lvc->mask |= LVCF_SUBITEM;
        lvc->iSubItem = Int_val(Value_of(Field(val, 3)));
    }
    if (Is_some(Field(val, 4))) {
        lvc->mask |= LVCF_IMAGE;
        lvc->iImage = Int_val(Value_of(Field(val, 4)));
    }
    if (Is_some(Field(val, 5))) {
        lvc->mask |= LVCF_ORDER;
        lvc->iOrder = Int_val(Value_of(Field(val, 5)));
    }
    return lvc;
}

static void free_lvcolumn(LVCOLUMN* lvc)
{
    if (lvc->mask & LVCF_TEXT)
        free_string(lvc->pszText);
    free(lvc);
}

static value alloc_lvcolumn(LVCOLUMN* lvc)
{
    CAMLparam0();
    CAMLlocal4(val, v, vv, vvv);

    val = alloc_tuple(6);
    if (lvc->mask & LVCF_FMT) {
        vv = alloc_tuple(2);
        Store_field(vv, 0, Val_constant(lvc->fmt & LVCFMT_JUSTIFYMASK, lvcolumn_justifications));
        vvv = Alloc_flags(lvc->fmt, lvcolumn_format_options);
        Store_field(vv, 2, vvv);
        v = alloc_some(vv);
        Store_field(val, 0, v);
    } else
        Store_field(val, 0, Val_none);
    if (lvc->mask & LVCF_WIDTH) {
        v = alloc_some(Val_int(lvc->cx));
        Store_field(val, 1, v);
    } else
        Store_field(val, 1, Val_none);
    if (lvc->mask & LVCF_TEXT) {
        v = alloc_some(copy_string(lvc->pszText));
        Store_field(val, 2, v);
    } else
        Store_field(val, 2, Val_none);
    if (lvc->mask & LVCF_SUBITEM) {
        v = alloc_some(Val_int(lvc->iSubItem));
        Store_field(val, 3, v);
    } else
        Store_field(val, 3, Val_none);
    if (lvc->mask & LVCF_IMAGE) {
        v = alloc_some(Val_int(lvc->iImage));
        Store_field(val, 4, v);
    } else
        Store_field(val, 4, Val_none);
    if (lvc->mask & LVCF_ORDER) {
        v = alloc_some(Val_int(lvc->iOrder));
        Store_field(val, 5, v);
    } else
        Store_field(val, 5, Val_none);
    CAMLreturn(val);
}

static DWORD lvcolumn_components_noparam[] = {
    LVCF_FMT,
    LVCF_WIDTH,
    LVCF_SUBITEM,
    LVCF_IMAGE,
    LVCF_ORDER
};
Define_static_constants(lvcolumn_components_noparam);

static DWORD lvbkimage_styles[] = {
    LVBKIF_STYLE_NORMAL,
    LVBKIF_STYLE_TILE
};
Define_static_constants(lvbkimage_styles);

static DWORD listview_which_image_lists[] = {
    LVSIL_NORMAL,
    LVSIL_SMALL,
    LVSIL_STATE
};
Define_static_constants(listview_which_image_lists);

static DWORD listview_item_portions[] = {
    LVIR_BOUNDS,
    LVIR_ICON,
    LVIR_LABEL,
    LVIR_SELECTBOUNDS
};
Define_static_constants(listview_item_portions);

static DWORD listview_hit_test_directions[] = {
    LVHT_ABOVE,
    LVHT_BELOW,
    LVHT_TOLEFT,
    LVHT_TORIGHT
};
Define_static_constants(listview_hit_test_directions);

static DWORD listview_hit_test_parts[] = {
    LVHT_ONITEMICON,
    LVHT_ONITEMLABEL,
    LVHT_ONITEMSTATEICON
};
Define_static_constants(listview_hit_test_parts);

CAMLprim value listview_get_check_state(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    BOOL result;

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = ListView_GetCheckState(hwnd, Int_val(item));
    leave_blocking_section();
    CAMLreturn(Val_bool(result != 0));
}

#define ListView_SetCheckState(hwndLV, i, fCheck) \
    ListView_SetItemState(hwndLV, i, \
    INDEXTOSTATEIMAGEMASK((fCheck)+1), LVIS_STATEIMAGEMASK)

CAMLprim value listview_set_check_state(value wnd, value item, value check)
{
    CAMLparam3(wnd, item, check);
    HWND hwnd;

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    ListView_SetCheckState(hwnd, Int_val(item), Bool_val(check));
    leave_blocking_section();
    CAMLreturn(Val_unit);
}

/* Styles */

static DWORD listview_style_options[] = {
    LVS_ALIGNLEFT,
    LVS_ALIGNTOP,
    LVS_AUTOARRANGE,
    LVS_EDITLABELS,
    LVS_ICON,
    LVS_LIST,
    LVS_NOCOLUMNHEADER,
    LVS_NOLABELWRAP,
    LVS_NOSCROLL,
    LVS_NOSORTHEADER,
    LVS_OWNERDATA,
    LVS_OWNERDRAWFIXED,
    LVS_REPORT,
    LVS_SHAREIMAGELISTS,
    LVS_SHOWSELALWAYS,
    LVS_SINGLESEL,
    LVS_SMALLICON,
    LVS_SORTASCENDING,
    LVS_SORTDESCENDING
};
Define_static_constants(listview_style_options);

CAMLprim value listview_style_of_option(value option)
{
    return copy_int64((int64)Constant_val(option, listview_style_options));
}

static DWORD listview_modes[] = {
    LVS_ICON,
    LVS_SMALLICON,
    LVS_LIST,
    LVS_REPORT
};
Define_static_constants(listview_modes);

CAMLprim value get_listview_mode(value style)
{
    return Val_constant((DWORD)(Int64_val(style) & LVS_TYPEMASK), listview_modes);
}

CAMLprim value set_listview_mode(value style, value mode)
{
    return copy_int64(
        (Int64_val(style) & ~(int64)LVS_TYPEMASK) |
        Val_constant(mode, listview_modes));
}

/* Messages */

CAMLprim value pack_lvm_approximateviewrect(value count, value size)
{
    return pack_message(LVM_APPROXIMATEVIEWRECT, Int_val(count), MAKELPARAM(Cx(size), Cy(size)));
}

CAMLprim value unpack_lvm_approximateviewrect(value result)
{
    DWORD dw;
    SIZE size;

    dw = Int32_val(result);
    size.cx = LOWORD(dw);
    size.cy = HIWORD(dw);
    return alloc_size(size);
}

static DWORD listview_arrangements[] = {
    LVA_ALIGNLEFT,
    LVA_ALIGNTOP,
    LVA_DEFAULT,
    LVA_SNAPTOGRID
};
Define_static_constants(listview_arrangements);

CAMLprim value pack_lvm_arrange(value arrangement)
{
    return pack_message(LVM_ARRANGE, Constant_val(arrangement, listview_arrangements), 0);
}

CAMLprim value unpack_lvm_arrange(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value issue_lvm_createdragimage(value sender, value item)
{
    CAMLparam2(sender, item);
    POINT pt;
    CAMLlocal4(contents, result, val, v);

    contents = pack_message(LVM_CREATEDRAGIMAGE, Int_val(item), (LPARAM)&pt);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_tuple(2);
    v = alloc_handle((HIMAGELIST)Int32_val(result));
    Store_field(val, 0, v);
    v = alloc_point(pt);
    Store_field(val, 1, v);
    CAMLreturn(val);
}

CAMLprim value unpack_lvm_deleteallitems(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_deletecolumn(value col)
{
    return pack_message(LVM_DELETECOLUMN, Int_val(col), 0);
}

CAMLprim value unpack_lvm_deletecolumn(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_deleteitem(value item)
{
    return pack_message(LVM_DELETEITEM, Int_val(item), 0);
}

CAMLprim value unpack_lvm_deleteitem(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_editlabel(value item)
{
    return pack_message(LVM_EDITLABEL, Int_val(item), 0);
}

CAMLprim value unpack_lvm_editlabel(value result)
{
    return alloc_handle((HWND)Int32_val(result));
}

CAMLprim value pack_lvm_ensurevisible(value item, value partial_ok)
{
    return pack_message(LVM_ENSUREVISIBLE, Int_val(item), Bool_val(partial_ok));
}

CAMLprim value unpack_lvm_ensurevisible(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value issue_lvm_finditem(value sender, value info)
{
    CAMLparam2(sender, info);
    int start;
    LVFINDINFO lvfi;
    CAMLlocal5(target, order, contents, result, val);

    start = 0;
    memset(&lvfi, 0, sizeof(LVFINDINFO));
    target = Field(info, 0);
    order = Field(info, 1);
    switch (Tag_val(target)) {
        case 0:
            lvfi.flags |= LVFI_STRING;
            lvfi.psz = new_string(Field(target, 0));
            break;
        case 1:
            lvfi.flags |= LVFI_STRING | LVFI_PARTIAL;
            lvfi.psz = new_string(Field(target, 0));
            break;
        case 2:
            lvfi.flags |= LVFI_PARAM;
            lvfi.lParam = Int32_val(Field(target, 0));
            break;
    }
    if (Is_long(order))
        start = -1;
    else {
        switch (Tag_val(order)) {
            case 0:
                start = Int_val(Field(order, 0));
                break;
            case 1:
                start = Int_val(Field(order, 0));
                lvfi.flags |= LVFI_WRAP;
                break;
            case 2:
                lvfi.flags |= LVFI_NEARESTXY;
                lvfi.pt = point_val(Field(order, 0));
                lvfi.vkDirection = virtualkeycode_val(Field(order, 1));
                break;
        }
    }
    contents = pack_message(LVM_FINDITEM, start, (LPARAM)&lvfi);
    result = callback(sender, contents);
    if (lvfi.flags & LVFI_STRING)
        free_string((char*)lvfi.psz);
    if (result == -1)
        val = Val_none;
    else
        val = alloc_some(Val_int(result));
    CAMLreturn(val);
}

CAMLprim value unpack_lvm_getbkcolor(value result)
{
    return alloc_color((COLORREF)Int32_val(result));
}

CAMLprim value issue_lvm_getbkimage(value sender, value max_length)
{
    CAMLparam2(sender, max_length);
    LVBKIMAGE lvbki;
    CAMLlocal5(contents, result, val, v, vv);

    memset(&lvbki, 0, sizeof(LVBKIMAGE));
    lvbki.cchImageMax = Int_val(max_length) + 1;
    lvbki.pszImage = malloc(lvbki.cchImageMax);
    contents = pack_message(LVM_GETBKIMAGE, 0, (LPARAM)&lvbki);
    result = callback(sender, contents);
    if (Int32_val(result) == 0) {
        free(lvbki.pszImage);
        raise_win32_error(0);
    }
    val = alloc_tuple(4);
    switch (lvbki.ulFlags & LVBKIF_SOURCE_MASK) {
        case LVBKIF_SOURCE_NONE:
            v = Val_int(0);
            break;
        case LVBKIF_SOURCE_HBITMAP:
            v = alloc(1, 0);
            vv = alloc_handle(lvbki.hbm);
            Store_field(v, 0, vv);
            break;
        case LVBKIF_SOURCE_URL:
            v = alloc(1, 1);
            vv = copy_string(lvbki.pszImage);
            Store_field(v, 0, vv);
            break;
    }
    Store_field(val, 0, v);
    Store_field(val, 1, Val_constant(lvbki.ulFlags & LVBKIF_STYLE_MASK, lvbkimage_styles));
    Store_field(val, 2, Val_int(lvbki.xOffsetPercent));
    Store_field(val, 3, Val_int(lvbki.yOffsetPercent));
    free(lvbki.pszImage);
    CAMLreturn(val);
}

CAMLprim value unpack_lvm_getcallbackmask(value result)
{
    return Alloc_flags(Int32_val(result), lvitem_state_components);
}

CAMLprim value issue_lvm_getcolumn(value sender, value col, value mask)
{
    CAMLparam3(sender, col, mask);
    LVCOLUMN lvc;
    CAMLlocal4(comp, contents, result, val);

    memset(&lvc, 0, sizeof(LVCOLUMN));
    for (; !Is_nil(mask); mask = Tail(mask)) {
        comp = Head(mask);
        if (Is_long(comp))
            lvc.mask |= Constant_val(comp, lvcolumn_components_noparam);
        else {
            lvc.mask |= LVCF_TEXT;
            lvc.cchTextMax = Int_val(Field(comp, 0)) + 1;
        }
    }
    if (lvc.mask & LVCF_TEXT)
        lvc.pszText = malloc(lvc.cchTextMax);
    contents = pack_message(LVM_GETCOLUMN, Int_val(col), (LPARAM)&lvc);
    result = callback(sender, contents);
    if (Int32_val(result) == 0) {
        if (lvc.mask & LVCF_TEXT)
            free(lvc.pszText);
        raise_win32_error(0);
    }
    val = alloc_lvcolumn(&lvc);
    if (lvc.mask & LVCF_TEXT)
        free(lvc.pszText);
    CAMLreturn(val);
}

CAMLprim value issue_lvm_getcolumnorderarray(value sender, value count)
{
    CAMLparam2(sender, count);
    int cnt, i;
    int* p;
    CAMLlocal3(contents, result, val);

    cnt = Int_val(count);
    if (cnt > 0)
        p = malloc(cnt * sizeof(int));
    else
        p = NULL;
    memset(p, 0, cnt * sizeof(int));
    contents = pack_message(LVM_GETCOLUMNORDERARRAY, cnt, (LPARAM)p);
    result = callback(sender, contents);
    if (Int32_val(result) == 0) {
        if (cnt > 0)
            free(p);
        raise_win32_error(0);
    }
    val = cnt > 0 ? alloc_tuple(cnt) : Atom(0);
    for (i = 0; i < cnt; ++i)
        Store_field(val, i, Val_int(p[i]));
    if (cnt > 0)
        free(p);
    CAMLreturn(val);
}

CAMLprim value pack_lvm_getcolumnwidth(value col)
{
    return pack_message(LVM_GETCOLUMNWIDTH, Int_val(col), 0);
}

CAMLprim value unpack_lvm_getcolumnwidth(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value unpack_lvm_getcountperpage(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value unpack_lvm_geteditcontrol(value result)
{
    return alloc_handle((HWND)Int32_val(result));
}

CAMLprim value unpack_lvm_getextendedlistviewstyle(value result)
{
    return Alloc_flags(Int32_val(result), listview_extended_styles);
}

CAMLprim value unpack_lvm_getheader(value result)
{
    return alloc_handle((HWND)Int32_val(result));
}

CAMLprim value unpack_lvm_gethotcursor(value result)
{
    return alloc_handle((HCURSOR)Int32_val(result));
}

CAMLprim value unpack_lvm_gethotitem(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value unpack_lvm_gethovertime(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value pack_lvm_getimagelist(value which)
{
    return pack_message(LVM_GETIMAGELIST, Constant_val(which, listview_which_image_lists), 0);
}

CAMLprim value unpack_lvm_getimagelist(value result)
{
    return alloc_handle((HIMAGELIST)Int32_val(result));
}

CAMLprim value issue_lvm_getisearchstring(value sender)
{
    CAMLparam1(sender);
    char buf[256];
    CAMLlocal3(contents, result, val);
    int len;

    contents = pack_message(LVM_GETISEARCHSTRING, 0, (LPARAM)buf);
    result = callback(sender, contents);
    len = Int32_val(result);
    val = alloc_string(len);
    if (len > 0)
        memcpy(String_val(val), buf, len);
    CAMLreturn(val);
}

CAMLprim value issue_lvm_getitem(value sender, value item, value sub_item, value mask)
{
    CAMLparam4(sender, item, sub_item, mask);
    LVITEM lvi;
    CAMLlocal4(comp, contents, result, val);

    memset(&lvi, 0, sizeof(LVITEM));
    lvi.iItem = Int_val(item);
    lvi.iSubItem = Int_val(sub_item);
    for (; !Is_nil(mask); mask = Tail(mask)) {
        comp = Head(mask);
        if (Is_long(comp))
            lvi.mask |= Constant_val(comp, lvitem_components_noparam);
        else if (Tag_val(comp) == 0) {
            lvi.mask |= LVIF_STATE;
            lvi.stateMask = flags_val(Field(comp, 0), lvitem_state_components);
        } else {
            lvi.mask |= LVIF_TEXT;
            lvi.cchTextMax = Int_val(Field(comp, 0)) + 1;
        }
    }
    if (lvi.mask & LVIF_TEXT)
        lvi.pszText = malloc(lvi.cchTextMax);
    contents = pack_message(LVM_GETITEM, 0, (LPARAM)&lvi);
    result = callback(sender, contents);
    if (Int32_val(result) == 0) {
        if (lvi.mask & LVIF_TEXT)
            free(lvi.pszText);
        raise_win32_error(0);
    }
    val = alloc_lvitem(&lvi);
    if (lvi.mask & LVIF_TEXT)
        free(lvi.pszText);
    CAMLreturn(val);
}

CAMLprim value unpack_lvm_getitemcount(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value issue_lvm_getitemposition(value sender, value item)
{
    CAMLparam2(sender, item);
    POINT pt;
    CAMLlocal3(contents, result, val);

    contents = pack_message(LVM_GETITEMPOSITION, Int_val(item), (LPARAM)&pt);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_point(pt);
    CAMLreturn(val);
}

CAMLprim value issue_lvm_getitemrect(value sender, value item, value portion)
{
    CAMLparam3(sender, item, portion);
    RECT rect;
    CAMLlocal3(contents, result, val);

    memset(&rect, 0, sizeof(RECT));
    rect.left = Constant_val(portion, listview_item_portions);
    contents = pack_message(LVM_GETITEMRECT, Int_val(item), (LPARAM)&rect);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_rect(&rect);
    CAMLreturn(val);
}

CAMLprim value pack_lvm_getitemspacing(value smal)
{
    return pack_message(LVM_GETITEMSPACING, Bool_val(smal), 0);
}

CAMLprim value unpack_lvm_getitemspacing(value result)
{
    POINT pt;

    pt.x = GET_X_LPARAM(Int32_val(result));
    pt.y = GET_Y_LPARAM(Int32_val(result));
    return alloc_point(pt);
}

CAMLprim value issue_lvm_getitemstate(value sender, value item, value mask)
{
    CAMLparam3(sender, item, mask);
    DWORD m;
    CAMLlocal3(contents, result, val);

    m = flags_val(mask, lvitem_state_components);
    contents = pack_message(LVM_GETITEMSTATE, Int_val(item), m);
    result = callback(sender, contents);
    val = alloc_lvitemstate(Int32_val(result), m);
    CAMLreturn(val);
}

CAMLprim value issue_lvm_getitemtext(value sender, value item, value sub_item, value max_length)
{
    CAMLparam4(sender, item, sub_item, max_length);
    LVITEM lvi;
    CAMLlocal2(contents, val);

    memset(&lvi, 0, sizeof(LVITEM));
    lvi.iSubItem = Int_val(sub_item);
    lvi.cchTextMax = Int_val(max_length) + 1;
    lvi.pszText = malloc(lvi.cchTextMax);
    contents = pack_message(LVM_GETITEMTEXT, 0, (LPARAM)&lvi);
    callback(sender, contents);
    val = copy_string(lvi.pszText);
    free(lvi.pszText);
    CAMLreturn(val);
}

static DWORD listview_next_item_directions[] = {
    LVNI_ALL,
    LVNI_ABOVE,
    LVNI_BELOW,
    LVNI_TOLEFT,
    LVNI_TORIGHT
};
Define_static_constants(listview_next_item_directions);

static DWORD listview_next_item_states[] = {
    LVNI_CUT,
    LVNI_DROPHILITED,
    LVNI_FOCUSED,
    LVNI_SELECTED
};
Define_static_constants(listview_next_item_states);

CAMLprim value pack_lvm_getnextitem(value start, value direction, value state)
{
    UINT flags;

    flags =
        Constant_val(direction, listview_next_item_directions) |
        flags_val(state, listview_next_item_states);
    return pack_message(LVM_GETNEXTITEM, Int_val(start), flags);
}

CAMLprim value unpack_lvm_getnextitem(value result)
{
    if (Int32_val(result) == -1)
        return Val_none;
    else
        return alloc_some(Val_int(Int32_val(result)));
}

CAMLprim value issue_lvm_getnumberofworkareas(value sender)
{
    CAMLparam1(sender);
    UINT n;
    CAMLlocal1(contents);

    contents = pack_message(LVM_GETNUMBEROFWORKAREAS, 0, (LPARAM)&n);
    callback(sender, contents);
    CAMLreturn(Val_int(n));
}

CAMLprim value issue_lvm_getorigin(value sender)
{
    CAMLparam1(sender);
    POINT pt;
    CAMLlocal3(contents, result, val);

    contents = pack_message(LVM_GETORIGIN, 0, (LPARAM)&pt);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_point(pt);
    CAMLreturn(val);
}

CAMLprim value unpack_lvm_getselectedcount(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value unpack_lvm_getselectionmark(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value issue_lvm_getstringwidth(value sender, value str)
{
    CAMLparam2(sender, str);
    char* s;
    CAMLlocal2(contents, result);

    s = malloc(string_length(str) + 1);
    strcpy(s, String_val(str));
    contents = pack_message(LVM_GETSTRINGWIDTH, 0, (LPARAM)s);
    result = callback(sender, contents);
    free(s);
    CAMLreturn(Val_int(result));
}

CAMLprim value issue_lvm_getsubitemrect(value sender, value item, value sub_item, value portion)
{
    CAMLparam4(sender, item, sub_item, portion);
    RECT rect;
    CAMLlocal3(contents, result, val);

    memset(&rect, 0, sizeof(RECT));
    rect.top = Int_val(sub_item);
    rect.left = Constant_val(portion, listview_item_portions);
    contents = pack_message(LVM_GETSUBITEMRECT, Int_val(item), (LPARAM)&rect);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_rect(&rect);
    CAMLreturn(val);
}

CAMLprim value unpack_lvm_gettextbkcolor(value result)
{
    return alloc_color((COLORREF)Int32_val(result));
}

CAMLprim value unpack_lvm_gettextcolor(value result)
{
    return alloc_color((COLORREF)Int32_val(result));
}

CAMLprim value unpack_lvm_gettooltips(value result)
{
    return alloc_handle((HWND)Int32_val(result));
}

CAMLprim value unpack_lvm_gettopindex(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value issue_lvm_getviewrect(value sender)
{
    CAMLparam1(sender);
    RECT r;
    CAMLlocal3(contents, result, val);

    contents = pack_message(LVM_GETVIEWRECT, 0, (LPARAM)&r);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_rect(&r);
    CAMLreturn(val);
}

CAMLprim value issue_lvm_getworkareas(value sender, value count)
{
    CAMLparam2(sender, count);
    int n, i;
    RECT* rects;
    CAMLlocal3(contents, val, v);

    n = Int_val(count);
    rects = malloc(n * sizeof(RECT));
    memset(rects, 0, n * sizeof(RECT));
    contents = pack_message(LVM_GETWORKAREAS, n, (LPARAM)rects);
    callback(sender, contents);
    val = Val_nil;
    for (i = n - 1; i >= 0; --i) {
        v = alloc_rect(&rects[i]);
        val = alloc_cons(v, val);
    }
    free(rects);
    CAMLreturn(val);
}

CAMLprim value issue_lvm_hittest(value sender, value pt)
{
    CAMLparam2(sender, pt);
    LVHITTESTINFO lvht;
    CAMLlocal4(contents, result, val, v);

    memset(&lvht, 0, sizeof(LVHITTESTINFO));
    lvht.pt = point_val(pt);
    contents = pack_message(LVM_HITTEST, 0, (LPARAM)&lvht);
    result = callback(sender, contents);
    if (Int32_val(result) == -1) {
        if (lvht.flags == LVHT_NOWHERE)
            val = Val_int(0);
        else {
            val = alloc(1, 0);
            v = Alloc_flags(lvht.flags, listview_hit_test_directions);
            Store_field(val, 0, v);
        }
    } else {
        val = alloc(3, 1);
        Store_field(val, 0, Val_int(lvht.iItem));
        Store_field(val, 1, Val_int(0));
        Store_field(val, 2, Val_constant(lvht.flags, listview_hit_test_parts));
    }
    CAMLreturn(val);
}

CAMLprim value issue_lvm_insertcolumn(value sender, value col, value lvcolumn)
{
    CAMLparam3(sender, col, lvcolumn);
    LVCOLUMN* lvc;
    CAMLlocal2(contents, result);

    lvc = new_lvcolumn(lvcolumn);
    contents = pack_message(LVM_INSERTCOLUMN, Int_val(col), (LPARAM)lvc);
    result = callback(sender, contents);
    free_lvcolumn(lvc);
    if (Int32_val(result) == -1)
        raise_win32_error(0);
    CAMLreturn(Val_int(Int32_val(result)));
}

CAMLprim value issue_lvm_insertitem(value sender, value lvitem)
{
    CAMLparam2(sender, lvitem);
    LVITEM* lvi;
    CAMLlocal2(contents, result);

    lvi = new_lvitem(lvitem);
    contents = pack_message(LVM_INSERTITEM, 0, (LPARAM)lvi);
    result = callback(sender, contents);
    free_lvitem(lvi);
    if (Int32_val(result) == -1)
        raise_win32_error(0);
    CAMLreturn(Val_int(Int32_val(result)));
}

CAMLprim value pack_lvm_redrawitems(value first, value last)
{
    return pack_message(LVM_REDRAWITEMS, Int_val(first), Int_val(last));
}

CAMLprim value unpack_lvm_redrawitems(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_scroll(value offset)
{
    return pack_message(LVM_SCROLL, X(offset), Y(offset));
}

CAMLprim value unpack_lvm_scroll(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_setbkcolor(value color)
{
    return pack_message(LVM_SETBKCOLOR, 0, (LPARAM)color_val(color));
}

CAMLprim value unpack_lvm_setbkcolor(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value issue_lvm_setbkimage(value sender, value image)
{
    CAMLparam2(sender, image);
    LVBKIMAGE lvbki;
    CAMLlocal3(contents, result, val);

    memset(&lvbki, 0, sizeof(LVBKIMAGE));
    val = Field(image, 0);
    if (Is_long(val))
        lvbki.ulFlags |= LVBKIF_SOURCE_NONE;
    else if (Tag_val(val) == 0) {
        lvbki.ulFlags |= LVBKIF_SOURCE_HBITMAP;
        lvbki.hbm = Handle_val(Field(val, 0));
    } else {
        lvbki.ulFlags |= LVBKIF_SOURCE_URL;
        lvbki.pszImage = new_string(Field(val, 0));
    }
    lvbki.ulFlags |= Constant_val(Field(val, 1), lvbkimage_styles);
    lvbki.xOffsetPercent = Int_val(Field(val, 2));
    lvbki.yOffsetPercent = Int_val(Field(val, 3));
    contents = pack_message(LVM_SETBKIMAGE, 0, (LPARAM)&lvbki);
    result = callback(sender, contents);
    if ((lvbki.ulFlags & LVBKIF_SOURCE_MASK) == LVBKIF_SOURCE_URL)
        free_string(lvbki.pszImage);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value pack_lvm_setcallbackmask(value mask)
{
    return pack_message(LVM_SETCALLBACKMASK, flags_val(mask, lvitem_state_components), 0);
}

CAMLprim value unpack_lvm_setcallbackmask(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value issue_lvm_setcolumn(value sender, value col, value lvcolumn)
{
    CAMLparam3(sender, col, lvcolumn);
    LVCOLUMN* lvc;
    CAMLlocal2(contents, result);

    lvc = new_lvcolumn(lvcolumn);
    contents = pack_message(LVM_SETCOLUMN, Int_val(col), (LPARAM)lvc);
    result = callback(sender, contents);
    free_lvcolumn(lvc);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value issue_lvm_setcolumnorderarray(value sender, value order)
{
    CAMLparam2(sender, order);
    int cnt, i;
    int* p;
    CAMLlocal3(contents, result, val);

    cnt = Wosize_val(order);
    p = malloc(cnt * sizeof(int));
    for (i = 0; i < cnt; ++i)
        p[i] = Int_val(Field(order, i));
    contents = pack_message(LVM_SETCOLUMNORDERARRAY, cnt, (LPARAM)p);
    result = callback(sender, contents);
    free(p);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value pack_lvm_setcolumnwidth(value col, value width)
{
    int cx;

    if (Is_block(width))
        cx = Int_val(Field(width, 0));
    else if (Int_val(width) == 0)
        cx = LVSCW_AUTOSIZE;
    else
        cx = LVSCW_AUTOSIZE_USEHEADER;
    return pack_message(LVM_SETCOLUMNWIDTH, Int_val(col), cx);
}

CAMLprim value unpack_lvm_setcolumnwidth(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_setextendedlistviewstyle(value mask, value style)
{
    return pack_message(
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        flags_val(mask, listview_extended_styles),
        flags_val(style, listview_extended_styles));
}

CAMLprim value unpack_lvm_setextendedlistviewstyle(value result)
{
    return Alloc_flags(Int32_val(result), listview_extended_styles);
}

CAMLprim value pack_lvm_sethotcursor(value cursor)
{
    return pack_message(LVM_SETHOTCURSOR, 0, (LPARAM)Handle_val(cursor));
}

CAMLprim value unpack_lvm_sethotcursor(value result)
{
    return alloc_handle((HCURSOR)Int32_val(result));
}

CAMLprim value pack_lvm_sethotitem(value item)
{
    return pack_message(LVM_SETHOTITEM, Int_val(item), 0);
}

CAMLprim value unpack_lvm_sethotitem(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value pack_lvm_sethovertime(value time)
{
    return pack_message(LVM_SETHOVERTIME, 0, Int_val(time));
}

CAMLprim value unpack_lvm_sethovertime(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value pack_lvm_seticonspacing(value offset)
{
    return pack_message(LVM_SETICONSPACING, 0, MAKELONG(X(offset), Y(offset)));
}

CAMLprim value unpack_lvm_seticonspacing(value result)
{
    POINT pt;

    pt.x = GET_X_LPARAM(Int32_val(result));
    pt.y = GET_Y_LPARAM(Int32_val(result));
    return alloc_point(pt);
}

CAMLprim value pack_lvm_setimagelist(value which, value iml)
{
    return pack_message(LVM_SETIMAGELIST, Constant_val(which, listview_which_image_lists), (LPARAM)Handle_val(iml));
}

CAMLprim value unpack_lvm_setimagelist(value result)
{
    return alloc_handle((HIMAGELIST)Int32_val(result));
}

CAMLprim value issue_lvm_setitem(value sender, value lvitem)
{
    CAMLparam2(sender, lvitem);
    LVITEM* lvi;
    CAMLlocal2(contents, result);

    lvi = new_lvitem(lvitem);
    contents = pack_message(LVM_SETITEM, 0, (LPARAM)lvi);
    result = callback(sender, contents);
    free_lvitem(lvi);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

static DWORD listview_set_item_count_options[] = {
    LVSICF_NOINVALIDATEALL,
    LVSICF_NOSCROLL
};

CAMLprim value pack_lvm_setitemcount(value count, value options)
{
    return pack_message(LVM_SETITEMCOUNT, Int_val(count), flags_val(options, listview_set_item_count_options));
}

CAMLprim value unpack_lvm_setitemcount(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_setitemposition(value item, value pos)
{
    return pack_message(LVM_SETITEMPOSITION, Int_val(item), MAKELPARAM(X(pos), Y(pos)));
}

CAMLprim value unpack_lvm_setitemposition(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value issue_lvm_setitemposition32(value sender, value item, value pos)
{
    CAMLparam3(sender, item, pos);
    POINT pt;
    CAMLlocal1(contents);

    pt = point_val(pos);
    contents = pack_message(LVM_SETITEMPOSITION32, Int_val(item), (LPARAM)&pt);
    callback(sender, contents);
    CAMLreturn(Val_unit);
}

CAMLprim value issue_lvm_setitemstate(value sender, value item, value state)
{
    CAMLparam3(sender, item, state);
    LVITEM lvi;
    CAMLlocal2(contents, result);

    memset(&lvi, 0, sizeof(LVITEM));
    lvitemstate_val(state, &lvi.state, &lvi.stateMask);
    contents = pack_message(LVM_SETITEMSTATE, Int_val(item), (LPARAM)&lvi);
    result = callback(sender, contents);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value issue_lvm_setitemtext(value sender, value item, value sub_item, value text)
{
    CAMLparam4(sender, item, sub_item, text);
    LVITEM lvi;
    CAMLlocal2(contents, result);

    memset(&lvi, 0, sizeof(LVITEM));
    lvi.iSubItem = Int_val(sub_item);
    lvi.pszText = new_commoncontroltext(text);
    contents = pack_message(LVM_SETITEMTEXT, Int_val(item), (LPARAM)&lvi);
    result = callback(sender, contents);
    free_commoncontroltext(lvi.pszText);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value pack_lvm_setselectionmark(value mark)
{
    return pack_message(LVM_SETSELECTIONMARK, 0, Int_val(mark));
}

CAMLprim value unpack_lvm_setselectionmark(value result)
{
    return Val_int(Int32_val(result));
}

CAMLprim value pack_lvm_settextbkcolor(value color)
{
    return pack_message(LVM_SETTEXTBKCOLOR, 0, (LPARAM)color_val(color));
}

CAMLprim value unpack_lvm_settextbkcolor(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_settextcolor(value color)
{
    return pack_message(LVM_SETTEXTCOLOR, 0, (LPARAM)color_val(color));
}

CAMLprim value unpack_lvm_settextcolor(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

CAMLprim value pack_lvm_settooltips(value tooltips)
{
    return pack_message(LVM_SETTOOLTIPS, 0, (LPARAM)Handle_val(tooltips));
}

CAMLprim value unpack_lvm_settooltips(value result)
{
    return alloc_handle((HWND)Int32_val(result));
}

CAMLprim value issue_lvm_setworkareas(value sender, value areas)
{
    CAMLparam2(sender, areas);
    int n;
    RECT* rects;
    CAMLlocal3(contents, val, v);

    n = list_length(areas);
    if (n > 0)
        rects = malloc(list_length(areas) * sizeof(RECT));
    else
        rects = NULL;
    n = 0;
    for (; !Is_nil(areas); areas = Tail(areas))
        rect_val(Head(areas), &rects[n++]);
    contents = pack_message(LVM_SETWORKAREAS, n, (LPARAM)rects);
    callback(sender, contents);
    if (rects)
        free(rects);
    CAMLreturn(Val_unit);
}

static int listview_compare_func_impl(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    CAMLparam0();
    CAMLlocal4(func, p1, p2, result);

    func = *(value*)lParamSort;
    p1 = copy_int32(lParam1);
    p2 = copy_int32(lParam2);
    result = callback2(func, p1, p2);
    CAMLreturn(Int_val(result));
}

static int CALLBACK listview_compare_func(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    int result;

    leave_blocking_section();
    result = listview_compare_func_impl(lParam1, lParam2, lParamSort);
    enter_blocking_section();
    return result;
}

CAMLprim value issue_lvm_sortitems(value sender, value compare)
{
    CAMLparam2(sender, compare);
    value* p;
    CAMLlocal2(contents, result);

    p = malloc(sizeof(value));
    register_global_root(p);
    *p = compare;
    contents = pack_message(LVM_SORTITEMS, (WPARAM)p, (LPARAM)listview_compare_func);
    result = callback(sender, contents);
    remove_global_root(p);
    free(p);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value issue_lvm_subitemhittest(value sender, value pt)
{
    CAMLparam2(sender, pt);
    LVHITTESTINFO lvht;
    CAMLlocal4(contents, result, val, v);

    memset(&lvht, 0, sizeof(LVHITTESTINFO));
    lvht.pt = point_val(pt);
    contents = pack_message(LVM_SUBITEMHITTEST, 0, (LPARAM)&lvht);
    result = callback(sender, contents);
    if (Int32_val(result) == -1) {
        if (lvht.flags == LVHT_NOWHERE)
            val = Val_int(0);
        else {
            val = alloc(1, 0);
            v = Alloc_flags(lvht.flags, listview_hit_test_directions);
            Store_field(val, 0, v);
        }
    } else {
        val = alloc(3, 1);
        Store_field(val, 0, Val_int(lvht.iItem));
        Store_field(val, 1, Val_int(lvht.iSubItem));
        Store_field(val, 2, Val_constant(lvht.flags, listview_hit_test_parts));
    }
    CAMLreturn(val);
}

CAMLprim value pack_lvm_update(value item)
{
    return pack_message(LVM_UPDATE, Int_val(item), 0);
}

CAMLprim value unpack_lvm_update(value result)
{
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    return Val_unit;
}

/* Notifications */

CAMLprim value unpack_nmlvcustomdraw(value msg)
{
    CAMLparam1(msg);
    NMLVCUSTOMDRAW* nmlvcd;
    CAMLlocal2(val, v);

    nmlvcd = (NMLVCUSTOMDRAW*)Lparam(msg);
    val = alloc_tuple(4);
    Store_field(val, 0, Val_int(nmlvcd->nmcd.dwItemSpec));
    v = alloc_color(nmlvcd->clrText);
    Store_field(val, 1, v);
    v = alloc_color(nmlvcd->clrTextBk);
    Store_field(val, 2, v);
    Store_field(val, 3, Val_int(nmlvcd->iSubItem));
    CAMLreturn(val);
}

CAMLprim value unpack_nmlistview(value msg)
{
    CAMLparam1(msg);
    NMLISTVIEW* nmlv;
    CAMLlocal4(val, v, vv, vvv);

    nmlv = (NMLISTVIEW*)Lparam(msg);
    val = alloc_tuple(7);
    Store_field(val, 0, Val_int(nmlv->iItem));
    Store_field(val, 1, Val_int(nmlv->iSubItem));
    v = alloc_lvitemstate(nmlv->uNewState, 0xFFFFFFFF);
    Store_field(val, 2, v);
    v = alloc_lvitemstate(nmlv->uOldState, 0xFFFFFFFF);
    Store_field(val, 3, v);
    v = Alloc_flags(nmlv->uChanged, lvitem_components_noparam);
    if (nmlv->uChanged & LVIF_TEXT) {
        vv = alloc(1, 1);
        Store_field(vv, 0, Val_int(256));
        v = alloc_cons(vv, v);
    }
    if (nmlv->uChanged & LVIF_STATE) {
        vv = alloc(1, 0);
        vvv = Alloc_flags(0xFFFFFFFF, lvitem_state_components);
        Store_field(vv, 0, vvv);
        v = alloc_cons(vv, v);
    }
    Store_field(val, 4, v);
    v = alloc_point(nmlv->ptAction);
    Store_field(val, 5, v);
    v = copy_int32(nmlv->lParam);
    Store_field(val, 6, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmlvdispinfo(value msg)
{
    CAMLparam1(msg);
    NMLVDISPINFO* nmlvdi;
    CAMLlocal2(val, v);

    nmlvdi = (NMLVDISPINFO*)Lparam(msg);
    val = alloc_tuple(1);
    v = alloc_lvitem(&nmlvdi->item);
    Store_field(val, 0, v);
    CAMLreturn(val);
}

static DWORD listview_modifier_keys[] = {
    LVKF_ALT,
    LVKF_CONTROL,
    LVKF_SHIFT
};
Define_static_constants(listview_modifier_keys);

CAMLprim value unpack_nmitemactivate(value msg)
{
    CAMLparam1(msg);
    NMITEMACTIVATE* nmia;
    CAMLlocal4(val, v, vv, vvv);

    nmia = (NMITEMACTIVATE*)Lparam(msg);
    val = alloc_tuple(8);
    Store_field(val, 0, Val_int(nmia->iItem));
    Store_field(val, 1, Val_int(nmia->iSubItem));
    v = alloc_lvitemstate(nmia->uNewState, 0xFFFFFFFF);
    Store_field(val, 2, v);
    v = alloc_lvitemstate(nmia->uOldState, 0xFFFFFFFF);
    Store_field(val, 3, v);
    v = Alloc_flags(nmia->uChanged, lvitem_components_noparam);
    if (nmia->uChanged & LVIF_TEXT) {
        vv = alloc(1, 1);
        Store_field(vv, 0, Val_int(256));
        v = alloc_cons(vv, v);
    }
    if (nmia->uChanged & LVIF_STATE) {
        vv = alloc(1, 0);
        vvv = Alloc_flags(0xFFFFFFFF, lvitem_state_components);
        Store_field(vv, 0, vvv);
        v = alloc_cons(vv, v);
    }
    Store_field(val, 4, v);
    v = alloc_point(nmia->ptAction);
    Store_field(val, 5, v);
    v = copy_int32(nmia->lParam);
    Store_field(val, 6, v);
    v = Alloc_flags(nmia->uKeyFlags, listview_modifier_keys);
    Store_field(val, 7, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmlvkeydown(value msg)
{
    CAMLparam1(msg);
    NMLVKEYDOWN* nmlvkd;
    CAMLlocal2(val, v);

    nmlvkd = (NMLVKEYDOWN*)Lparam(msg);
    val = alloc_tuple(1);
    v = alloc_virtualkeycode((BYTE)nmlvkd->wVKey);
    Store_field(val, 0, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmlvcachehint(value msg)
{
    CAMLparam1(msg);
    NMLVCACHEHINT* nmlvch;
    CAMLlocal1(val);

    nmlvch = (NMLVCACHEHINT*)Lparam(msg);
    val = alloc_tuple(2);
    Store_field(val, 0, Val_int(nmlvch->iFrom));
    Store_field(val, 1, Val_int(nmlvch->iTo));
    CAMLreturn(val);
}

CAMLprim value unpack_nmlvfinditem(value msg)
{
    CAMLparam1(msg);
    NMLVFINDITEM* nmlvfi;
    CAMLlocal4(val, target, order, v);

    nmlvfi = (NMLVFINDITEM*)Lparam(msg);
    if (nmlvfi->lvfi.flags & LVFI_PARAM) {
        target = alloc(1, 2);
        v = copy_int32(nmlvfi->lvfi.lParam);
        Store_field(target, 0, v);
    } else if (nmlvfi->lvfi.flags & LVFI_PARTIAL) {
        target = alloc(1, 1);
        v = copy_string(nmlvfi->lvfi.psz);
        Store_field(target, 0, v);
    } else {
        target = alloc(1, 0);
        v = copy_string(nmlvfi->lvfi.psz);
        Store_field(target, 0, v);
    }
    if (nmlvfi->lvfi.flags & LVFI_NEARESTXY) {
        order = alloc(2, 2);
        v = alloc_point(nmlvfi->lvfi.pt);
        Store_field(order, 0, v);
        v = alloc_virtualkeycode((BYTE)nmlvfi->lvfi.vkDirection);
        Store_field(order, 1, v);
    } else if (nmlvfi->iStart >= 0) {
        if (nmlvfi->lvfi.flags & LVFI_WRAP) {
            order = alloc(1, 1);
            Store_field(order, 0, Val_int(nmlvfi->iStart));
        } else {
            order = alloc(1, 0);
            Store_field(order, 0, Val_int(nmlvfi->iStart));
        }
    } else
        order = Val_int(0);
    val = alloc_tuple(2);
    Store_field(val, 0, target);
    Store_field(val, 0, order);
    CAMLreturn(val);
}

CAMLprim value unpack_nmlvodstatechange(value msg)
{
    CAMLparam1(msg);
    NMLVODSTATECHANGE* nmlvsc;
    CAMLlocal2(val, v);

    nmlvsc = (NMLVODSTATECHANGE*)Lparam(msg);
    val = alloc_tuple(4);
    Store_field(val, 0, Val_int(nmlvsc->iFrom));
    Store_field(val, 1, Val_int(nmlvsc->iTo));
    v = alloc_lvitemstate(nmlvsc->uNewState, 0xFFFFFFFF);
    Store_field(val, 2, v);
    v = alloc_lvitemstate(nmlvsc->uOldState, 0xFFFFFFFF);
    Store_field(val, 3, v);
    CAMLreturn(val);
}

CAMLprim value return_lvn_endlabeledit(value result)
{
    return copy_int32(Bool_val(result));
}

CAMLprim value on_lvn_getdispinfo(value handler, value wnd, value msg)
{
    CAMLparam3(handler, wnd, msg);
    NMHDR* nmh;
    NMLVDISPINFO* nmlvdi;
    BOOL has_text;
    CAMLlocalN(args, 7);
    CAMLlocal5(mask, result, pair, lvi, ret);
    CAMLlocal2(val, v);
    DWORD state, stateMask;

    ret = Val_none;
    if (Int_val(Field(msg, 0)) == WM_NOTIFY) {
        nmh = (NMHDR*)Lparam(msg);
        if (nmh->code == LVN_GETDISPINFO) {
            nmlvdi = (NMLVDISPINFO*)nmh;
            has_text =
                (nmlvdi->item.mask & LVIF_TEXT) &&
                nmlvdi->item.pszText != NULL &&
                nmlvdi->item.cchTextMax > 0;
            mask = Alloc_flags(nmlvdi->item.mask, lvitem_components_noparam);
            if (has_text) {
                val = alloc(1, 1);
                Store_field(val, 0, Val_int(nmlvdi->item.cchTextMax - 1));
                mask = alloc_cons(val, mask);
            }
            if (nmlvdi->item.mask & LVIF_STATE) {
                val = alloc(1, 0);
                v = Alloc_flags(nmlvdi->item.stateMask, lvitem_state_components);
                Store_field(val, 0, v);
                mask = alloc_cons(val, mask);
            }
            args[0] = wnd;
            args[1] = msg;
            args[2] = Val_int(Wparam(msg));
            args[3] = alloc_nmhdr(nmh);
            args[4] = Val_int(nmlvdi->item.iItem);
            args[5] = Val_int(nmlvdi->item.iSubItem);
            args[6] = mask;
            result = callbackN(handler, 7, args);
            if (Is_block(result)) {
                if (Tag_val(result) == 0)
                    ret = alloc_some(Field(result, 0));
                else {
                    pair = Field(result, 0);
                    lvi = Field(pair, 0);
                    if (Is_some(Field(lvi, 2))) {
                        lvitemstate_val(Value_of(Field(lvi, 2)), &state, &stateMask);
                        stateMask &= nmlvdi->item.stateMask;
                        nmlvdi->item.state =
                            (nmlvdi->item.state & ~stateMask) | (state & stateMask);
                    }
                    if (has_text && Is_some(Field(lvi, 3))) {
                        val = Value_of(Field(lvi, 3));
                        if (Is_block(val)) {
                            strncpy(
                                nmlvdi->item.pszText,
                                String_val(Field(val, 0)),
                                nmlvdi->item.cchTextMax - 1);
                            nmlvdi->item.pszText[nmlvdi->item.cchTextMax-1] = '\0';
                        } else if (Int_val(val) == 0)
                            nmlvdi->item.pszText = NULL;
                        else
                            nmlvdi->item.pszText[0] = '\0';
                    }
                    if (Is_some(Field(lvi, 4)))
                        nmlvdi->item.iImage = commoncontrolimageindex_val(Value_of(Field(lvi, 4)));
                    if (Bool_val(Field(pair, 1)))
                        nmlvdi->item.mask |= LVIF_DI_SETITEM;
                    ret = alloc_some(copy_int32(0));
                }
            }
        }
    }
    CAMLreturn(ret);
}

CAMLprim value on_lvn_hottrack(value handler, value wnd, value msg)
{
    CAMLparam3(handler, wnd, msg);
    NMHDR* nmh;
    NMLISTVIEW* nmlv;
    CAMLlocalN(args, 5);
    CAMLlocal3(result, val, ret);

    ret = Val_none;
    if (Int_val(Field(msg, 0)) == WM_NOTIFY) {
        nmh = (NMHDR*)Lparam(msg);
        if (nmh->code == LVN_HOTTRACK) {
            nmlv = (NMLISTVIEW*)nmh;
            args[0] = wnd;
            args[1] = msg;
            args[2] = Val_int(Wparam(msg));
            args[3] = alloc_nmhdr(nmh);
            args[4] = unpack_nmlistview(msg);
            result = callbackN(handler, 5, args);
            if (Is_block(result)) {
                if (Tag_val(result) == 0)
                    ret = alloc_some(Field(result, 0));
                else {
                    val = Field(result, 0);
                    if (Is_block(val)) {
                        nmlv->iItem = Int_val(Field(val, 0));
                        ret = alloc_some(copy_int32(0));
                    } else if (Int_val(val) == 0)
                        ret = alloc_some(copy_int32(0));
                    else
                        ret = alloc_some(copy_int32(1));
                }
            }
        }
    }
    CAMLreturn(ret);
}

CAMLprim value return_lvn_odfinditem(value result)
{
    if (Is_some(result))
        return copy_int32(Int_val(Value_of(result)));
    else
        return copy_int32(-1);
}
