/* OCaml-Win32
 * win32_treeview.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: Tree-View Controls */

#define Htreeitem_val(v) (*((HTREEITEM*)(v)))

value alloc_htreeitem(HTREEITEM h)
{
    value val;

    val = alloc_small(sizeof(HTREEITEM) / sizeof(value), Abstract_tag);
    Htreeitem_val(val) = h;
    return val;
}

CAMLprim value eq_htreeitem(value item1, value item2)
{
    return Val_bool(Htreeitem_val(item1) == Htreeitem_val(item2));
}

CAMLprim value is_null_htreeitem(value item)
{
    return Val_bool(Htreeitem_val(item) == NULL);
}

CAMLprim value get_null_htreeitem(value unit)
{
    return alloc_htreeitem(NULL);
}

CAMLprim value get_root_htreeitem(value unit)
{
    return alloc_htreeitem(TVI_ROOT);
}

CAMLprim value int32_of_htreeitem(value h)
{
    return copy_int32((int)Htreeitem_val(h));
}

CAMLprim value htreeitem_of_int32(value i)
{
    return alloc_htreeitem((HTREEITEM)Int32_val(i));
}

static void tvitemstate_val(value val, DWORD* state, DWORD* mask)
{
    *state = 0;
    *mask = 0;
    if (Is_some(Field(val, 0))) {
        *mask |= TVIS_BOLD;
        if (Bool_val(Value_of(Field(val, 0))))
            *state |= TVIS_BOLD;
    }
    if (Is_some(Field(val, 1))) {
        *mask |= TVIS_CUT;
        if (Bool_val(Value_of(Field(val, 1))))
            *state |= TVIS_CUT;
    }
    if (Is_some(Field(val, 2))) {
        *mask |= TVIS_DROPHILITED;
        if (Bool_val(Value_of(Field(val, 2))))
            *state |= TVIS_DROPHILITED;
    }
    if (Is_some(Field(val, 3))) {
        *mask |= TVIS_EXPANDED;
        if (Bool_val(Value_of(Field(val, 3))))
            *state |= TVIS_EXPANDED;
    }
    if (Is_some(Field(val, 4))) {
        *mask |= TVIS_EXPANDEDONCE;
        if (Bool_val(Value_of(Field(val, 4))))
            *state |= TVIS_EXPANDEDONCE;
    }
    if (Is_some(Field(val, 5))) {
        *mask |= TVIS_EXPANDPARTIAL;
        if (Bool_val(Value_of(Field(val, 5))))
            *state |= TVIS_EXPANDPARTIAL;
    }
    if (Is_some(Field(val, 6))) {
        *mask |= TVIS_SELECTED;
        if (Bool_val(Value_of(Field(val, 6))))
            *state |= TVIS_SELECTED;
    }
    if (Is_some(Field(val, 7))) {
        *mask |= TVIS_OVERLAYMASK;
        *state |= INDEXTOOVERLAYMASK(Int_val(Value_of(Field(val, 7))));
    }
    if (Is_some(Field(val, 8))) {
        *mask |= TVIS_STATEIMAGEMASK;
        *state |= INDEXTOSTATEIMAGEMASK(Int_val(Value_of(Field(val, 8))));
    }
}

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

    val = alloc_tuple(9);
    if (mask & TVIS_BOLD) {
        v = alloc_some(Val_bool((state & TVIS_BOLD) != 0));
        Store_field(val, 0, v);
    } else
        Store_field(val, 0, Val_none);
    if (mask & TVIS_CUT) {
        v = alloc_some(Val_bool((state & TVIS_CUT) != 0));
        Store_field(val, 1, v);
    } else
        Store_field(val, 1, Val_none);
    if (mask & TVIS_DROPHILITED) {
        v = alloc_some(Val_bool((state & TVIS_DROPHILITED) != 0));
        Store_field(val, 2, v);
    } else
        Store_field(val, 2, Val_none);
    if (mask & TVIS_EXPANDED) {
        v = alloc_some(Val_bool((state & TVIS_EXPANDED) != 0));
        Store_field(val, 3, v);
    } else
        Store_field(val, 3, Val_none);
    if (mask & TVIS_EXPANDEDONCE) {
        v = alloc_some(Val_bool((state & TVIS_EXPANDEDONCE) != 0));
        Store_field(val, 4, v);
    } else
        Store_field(val, 4, Val_none);
    if (mask & TVIS_EXPANDPARTIAL) {
        v = alloc_some(Val_bool((state & TVIS_EXPANDPARTIAL) != 0));
        Store_field(val, 5, v);
    } else
        Store_field(val, 5, Val_none);
    if (mask & TVIS_SELECTED) {
        v = alloc_some(Val_bool((state & TVIS_SELECTED) != 0));
        Store_field(val, 6, v);
    } else
        Store_field(val, 6, Val_none);
    if (mask & TVIS_OVERLAYMASK) {
        v = alloc_some(Val_int((state & TVIS_OVERLAYMASK) >> 8));
        Store_field(val, 7, v);
    } else
        Store_field(val, 7, Val_none);
    if (mask & TVIS_STATEIMAGEMASK) {
        v = alloc_some(Val_int((state & TVIS_STATEIMAGEMASK) >> 12));
        Store_field(val, 8, v);
    } else
        Store_field(val, 8, Val_none);
    CAMLreturn(val);
}

static TVITEMEX* new_tvitem(value val)
{
    TVITEMEX* tvi;

    tvi = malloc(sizeof(TVITEMEX));
    memset(tvi, 0, sizeof(TVITEMEX));
    if (Is_some(Field(val, 0))) {
        tvi->mask |= TVIF_HANDLE;
        tvi->hItem = Htreeitem_val(Value_of(Field(val, 0)));
    }
    if (Is_some(Field(val, 1))) {
        tvi->mask |= TVIF_STATE;
        tvitemstate_val(Value_of(Field(val, 1)), &tvi->state, &tvi->stateMask);
    }
    if (Is_some(Field(val, 2))) {
        tvi->mask |= TVIF_TEXT;
        tvi->pszText = new_commoncontroltext(Value_of(Field(val, 2)));
    }
    if (Is_some(Field(val, 3))) {
        tvi->mask |= TVIF_IMAGE;
        tvi->iImage = commoncontrolimageindex_val(Value_of(Field(val, 3)));
    }
    if (Is_some(Field(val, 4))) {
        tvi->mask |= TVIF_SELECTEDIMAGE;
        tvi->iSelectedImage = commoncontrolimageindex_val(Value_of(Field(val, 4)));
    }
    if (Is_some(Field(val, 5))) {
        tvi->mask |= TVIF_CHILDREN;
        switch (Int_val(Value_of(Field(val, 5)))) {
            case 0:
                tvi->cChildren = 0;
                break;
            case 1:
                tvi->cChildren = 1;
                break;
            default:
                tvi->cChildren = I_CHILDRENCALLBACK;
                break;
        }
    }
    if (Is_some(Field(val, 6))) {
        tvi->mask |= TVIF_PARAM;
        tvi->lParam = Int32_val(Value_of(Field(val, 6)));
    }
    if (Is_some(Field(val, 7))) {
        tvi->mask |= TVIF_INTEGRAL;
        tvi->iIntegral = Int_val(Value_of(Field(val, 7)));
    }
    return tvi;
}

static void free_tvitem(TVITEMEX* tvi)
{
    if (tvi->mask & TVIF_TEXT)
        free_commoncontroltext(tvi->pszText);
    free(tvi);
}

static value alloc_tvitem(TVITEMEX* tvi)
{
    CAMLparam0();
    CAMLlocal3(val, v, vv);

    val = alloc_tuple(8);
    if (tvi->mask & TVIF_HANDLE) {
        v = alloc_some(alloc_htreeitem(tvi->hItem));
        Store_field(val, 0, v);
    } else
        Store_field(val, 0, Val_none);
    if (tvi->mask & TVIF_STATE) {
        v = alloc_some(alloc_tvitemstate(tvi->state, tvi->stateMask));
        Store_field(val, 1, v);
    } else
        Store_field(val, 1, Val_none);
    if (tvi->mask & TVIF_TEXT) {
        v = alloc_some(alloc_commoncontroltext(tvi->pszText));
        Store_field(val, 2, v);
    } else
        Store_field(val, 2, Val_none);
    if (tvi->mask & TVIF_IMAGE) {
        v = alloc_some(alloc_commoncontrolimageindex(tvi->iImage));
        Store_field(val, 3, v);
    } else
        Store_field(val, 3, Val_none);
    if (tvi->mask & TVIF_SELECTEDIMAGE) {
        v = alloc_some(alloc_commoncontrolimageindex(tvi->iSelectedImage));
        Store_field(val, 4, v);
    } else
        Store_field(val, 4, Val_none);
    if (tvi->mask & TVIF_CHILDREN) {
        switch (tvi->cChildren) {
            case 0:
                vv = Val_int(0);
                break;
            case 1:
                vv = Val_int(1);
                break;
            default:
                vv = Val_int(2);
                break;
        }
        v = alloc_some(vv);
        Store_field(val, 5, v);
    } else
        Store_field(val, 5, Val_none);
    if (tvi->mask & TVIF_PARAM) {
        v = alloc_some(copy_int32(tvi->lParam));
        Store_field(val, 6, v);
    } else
        Store_field(val, 6, Val_none);
    if (tvi->mask & TVIF_INTEGRAL) {
        v = alloc_some(Val_int(tvi->iIntegral));
        Store_field(val, 7, v);
    } else
        Store_field(val, 7, Val_none);
    CAMLreturn(val);
}

static DWORD tvitem_state_components[] = {
    TVIS_BOLD,
    TVIS_CUT,
    TVIS_DROPHILITED,
    TVIS_EXPANDED,
    TVIS_EXPANDEDONCE,
    TVIS_EXPANDPARTIAL,
    TVIS_SELECTED,
    TVIS_OVERLAYMASK,
    TVIS_STATEIMAGEMASK
};
Define_static_constants(tvitem_state_components);

static DWORD tvitem_components_noparam[] = {
    TVIF_IMAGE,
    TVIF_SELECTEDIMAGE,
    TVIF_CHILDREN,
    TVIF_PARAM,
    TVIF_INTEGRAL
};
Define_static_constants(tvitem_components_noparam);

static DWORD treeview_which_image_lists[] = {
    TVSIL_NORMAL,
    TVSIL_STATE
};
Define_static_constants(treeview_which_image_lists);

static DWORD treeview_expand_actions[] = {
    TVE_COLLAPSE,
    TVE_EXPAND,
    TVE_TOGGLE
};
Define_static_constants(treeview_expand_actions);

static DWORD treeview_expand_options[] = {
    TVE_COLLAPSERESET,
    TVE_EXPANDPARTIAL
};
Define_static_constants(treeview_expand_options);

CAMLprim value treeview_delete_all_items(value wnd)
{
    CAMLparam1(wnd);
    HWND hwnd;
    BOOL result;

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = TreeView_DeleteAllItems(hwnd);
    leave_blocking_section();
    if (!result)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value treeview_get_child(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem, result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_GetChild(hwnd, hitem);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_drop_hilight(value wnd)
{
    CAMLparam1(wnd);
    HWND hwnd;
    HTREEITEM result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = TreeView_GetDropHilight(hwnd);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_first_visible(value wnd)
{
    CAMLparam1(wnd);
    HWND hwnd;
    HTREEITEM result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = TreeView_GetFirstVisible(hwnd);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_last_visible(value wnd)
{
    CAMLparam1(wnd);
    HWND hwnd;
    HTREEITEM result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = TreeView_GetLastVisible(hwnd);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_next_sibling(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem, result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_GetNextSibling(hwnd, hitem);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_next_visible(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem, result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_GetNextVisible(hwnd, hitem);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_parent(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem, result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_GetParent(hwnd, hitem);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_prev_sibling(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem, result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_GetPrevSibling(hwnd, hitem);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_prev_visible(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem, result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_GetPrevVisible(hwnd, hitem);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_root(value wnd)
{
    CAMLparam1(wnd);
    HWND hwnd;
    HTREEITEM result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = TreeView_GetRoot(hwnd);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_get_selection(value wnd)
{
    CAMLparam1(wnd);
    HWND hwnd;
    HTREEITEM result;
    CAMLlocal1(val);

    hwnd = Handle_val(wnd);
    enter_blocking_section();
    result = TreeView_GetSelection(hwnd);
    leave_blocking_section();
    val = alloc_htreeitem(result);
    CAMLreturn(val);
}

CAMLprim value treeview_select_drop_target(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem;
    BOOL result;

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_SelectDropTarget(hwnd, hitem);
    leave_blocking_section();
    if (!result)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value treeview_select_item(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem;
    BOOL result;

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_SelectItem(hwnd, hitem);
    leave_blocking_section();
    if (!result)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value treeview_select_set_first_visible(value wnd, value item)
{
    CAMLparam2(wnd, item);
    HWND hwnd;
    HTREEITEM hitem;
    BOOL result;

    hwnd = Handle_val(wnd);
    hitem = Htreeitem_val(item);
    enter_blocking_section();
    result = TreeView_SelectSetFirstVisible(hwnd, hitem);
    leave_blocking_section();
    if (!result)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

/* Styles */

static DWORD treeview_style_options[] = {
    TVS_CHECKBOXES,
    TVS_DISABLEDRAGDROP,
    TVS_EDITLABELS,
    TVS_FULLROWSELECT,
    TVS_HASBUTTONS,
    TVS_HASLINES,
    TVS_INFOTIP,
    TVS_LINESATROOT,
    TVS_NONEVENHEIGHT,
    TVS_NOSCROLL,
    TVS_NOTOOLTIPS,
    TVS_RTLREADING,
    TVS_SHOWSELALWAYS,
    TVS_SINGLEEXPAND,
    TVS_TRACKSELECT
};
Define_static_constants(treeview_style_options);

CAMLprim value treeview_style_of_option(value option)
{
    return copy_int64((int64)Constant_val(option, treeview_style_options));
}

/* Messages */

CAMLprim value pack_tvm_createdragimage(value item)
{
    return pack_message(TVM_CREATEDRAGIMAGE, 0, (LPARAM)Htreeitem_val(item));
}

CAMLprim value unpack_tvm_createdragimage(value result)
{
    HIMAGELIST h;

    h = (HIMAGELIST)Int32_val(result);
    if (h == NULL)
        raise_win32_error(0);
    return alloc_handle(h);
}

CAMLprim value pack_tvm_deleteitem(value item)
{
    return pack_message(TVM_DELETEITEM, 0, (LPARAM)Htreeitem_val(item));
}

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

CAMLprim value pack_tvm_editlabel(value item)
{
    return pack_message(TVM_EDITLABEL, 0, (LPARAM)Htreeitem_val(item));
}

CAMLprim value unpack_tvm_editlabel(value result)
{
    HWND h;

    h = (HWND)Int32_val(result);
    if (h == NULL)
        raise_win32_error(0);
    return alloc_handle(h);
}

CAMLprim value pack_tvm_endeditlabelnow(value cancel)
{
    return pack_message(TVM_ENDEDITLABELNOW, Bool_val(cancel), 0);
}

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

CAMLprim value pack_tvm_ensurevisible(value item)
{
    return pack_message(TVM_ENSUREVISIBLE, 0, (LPARAM)Htreeitem_val(item));
}

CAMLprim value unpack_tvm_ensurevisible(value result)
{
    return Val_bool(Int32_val(result) != 0);
}

CAMLprim value pack_tvm_expand(value action, value options, value item)
{
    return pack_message(TVM_EXPAND,
        Constant_val(action, treeview_expand_actions) | flags_val(options, treeview_expand_options),
        (LPARAM)Htreeitem_val(item));
}

CAMLprim value unpack_tvm_expand(value result)
{
    return Val_bool(Int32_val(result) != 0);
}

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

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

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

CAMLprim value pack_tvm_getimagelist(value which)
{
    return pack_message(TVM_GETIMAGELIST, Constant_val(which, treeview_which_image_lists), 0);
}

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

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

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

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

    contents = pack_message(TVM_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_tvm_getitem(value sender, value item, value mask)
{
    CAMLparam3(sender, item, mask);
    TVITEMEX tvi;
    CAMLlocal4(comp, contents, result, val);

    memset(&tvi, 0, sizeof(TVITEMEX));
    tvi.hItem = Htreeitem_val(item);
    for (; !Is_nil(mask); mask = Tail(mask)) {
        comp = Head(mask);
        if (Is_long(comp))
            tvi.mask |= Constant_val(comp, tvitem_components_noparam);
        else if (Tag_val(comp) == 0) {
            tvi.mask |= TVIF_STATE;
            tvi.stateMask = flags_val(Field(comp, 0), tvitem_state_components);
        } else {
            tvi.mask |= TVIF_TEXT;
            tvi.cchTextMax = Int_val(Field(comp, 0)) + 1;
        }
    }
    if (tvi.mask & TVIF_TEXT)
        tvi.pszText = malloc(tvi.cchTextMax);
    contents = pack_message(TVM_GETITEM, 0, (LPARAM)&tvi);
    result = callback(sender, contents);
    if (Int32_val(result) == 0) {
        if (tvi.mask & TVIF_TEXT)
            free(tvi.pszText);
        raise_win32_error(0);
    }
    val = alloc_tvitem(&tvi);
    if (tvi.mask & TVIF_TEXT)
        free(tvi.pszText);
    CAMLreturn(val);
}

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

CAMLprim value issue_tvm_getitemrect(value sender, value item, value text_only)
{
    CAMLparam3(sender, item, text_only);
    RECT r;
    CAMLlocal3(contents, result, val);

    *(HTREEITEM*)&r = Htreeitem_val(item);
    contents = pack_message(TVM_GETITEMRECT, Bool_val(text_only), (LPARAM)&r);
    result = callback(sender, contents);
    if (Int32_val(result) != 0) {
        val = alloc_some(alloc_rect(&r));
    } else
        val = Val_none;
    CAMLreturn(val);
}

static DWORD treeview_next_item_directions[] = {
    TVGN_CARET,
    TVGN_CHILD,
    TVGN_DROPHILITE,
    TVGN_FIRSTVISIBLE,
    TVGN_LASTVISIBLE,
    TVGN_NEXT,
    TVGN_NEXTVISIBLE,
    TVGN_PARENT,
    TVGN_PREVIOUS,
    TVGN_PREVIOUSVISIBLE,
    TVGN_ROOT
};

CAMLprim value pack_tvm_getnextitem(value item, value direction)
{
    return pack_message(TVM_GETNEXTITEM,
        Constant_val(direction, treeview_next_item_directions),
        (LPARAM)Htreeitem_val(item));
}

CAMLprim value unpack_tvm_getnextitem(value result)
{
    return alloc_htreeitem((HTREEITEM)Int32_val(result));
}

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

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

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

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

static DWORD treeview_hit_test_directions[] = {
    TVHT_ABOVE,
    TVHT_BELOW,
    TVHT_TOLEFT,
    TVHT_TORIGHT
};
Define_static_constants(treeview_hit_test_directions);

static DWORD treeview_hit_test_parts[] = {
    TVHT_ONITEMBUTTON,
    TVHT_ONITEMICON,
    TVHT_ONITEMINDENT,
    TVHT_ONITEMLABEL,
    TVHT_ONITEMRIGHT,
    TVHT_ONITEMSTATEICON
};
Define_static_constants(treeview_hit_test_parts);

CAMLprim value issue_tvm_hittest(value sender, value pt)
{
    CAMLparam2(sender, pt);
    TVHITTESTINFO tvht;
    CAMLlocal4(contents, result, val, v);

    memset(&tvht, 0, sizeof(TVHITTESTINFO));
    tvht.pt = point_val(pt);
    contents = pack_message(TVM_HITTEST, 0, (LPARAM)&tvht);
    result = callback(sender, contents);
    if ((HTREEITEM)Int32_val(result) == NULL) {
        if (tvht.flags == TVHT_NOWHERE)
            val = Val_int(0);
        else {
            val = alloc(1, 0);
            v = Alloc_flags(tvht.flags, treeview_hit_test_directions);
            Store_field(val, 0, v);
        }
    } else {
        val = alloc(2, 1);
        v = alloc_htreeitem(tvht.hItem);
        Store_field(val, 0, v);
        Store_field(val, 1, Val_constant(tvht.flags, treeview_hit_test_parts));
    }
    CAMLreturn(val);
}

CAMLprim value issue_tvm_insertitem(value sender, value parent, value location, value tvitem)
{
    CAMLparam4(sender, parent, location, tvitem);
    TVINSERTSTRUCT tvis;
    TVITEMEX* tvi;
    CAMLlocal3(contents, result, val);

    memset(&tvis, 0, sizeof(TVINSERTSTRUCT));
    tvis.hParent = Htreeitem_val(parent);
    if (Is_block(location))
        tvis.hInsertAfter = Htreeitem_val(Field(location, 0));
    else {
        switch (Int_val(location)) {
            case 0:
                tvis.hInsertAfter = TVI_FIRST;
                break;
            case 1:
                tvis.hInsertAfter = TVI_LAST;
                break;
            case 2:
                tvis.hInsertAfter = TVI_SORT;
                break;
        }
    }
    tvi = new_tvitem(tvitem);
    memcpy(&tvis.itemex, tvi, sizeof(TVITEMEX));
    contents = pack_message(TVM_INSERTITEM, 0, (LPARAM)&tvis);
    result = callback(sender, contents);
    free_tvitem(tvi);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    val = alloc_htreeitem((HTREEITEM)Int32_val(result));
    CAMLreturn(val);
}

CAMLprim value pack_tvm_selectitem(value item, value action)
{
    DWORD act;

    switch (Int_val(action)) {
        case 0:
            act = TVGN_CARET;
            break;
        case 1:
            act = TVGN_DROPHILITE;
            break;
        case 2:
            act = TVGN_FIRSTVISIBLE;
            break;
    }
    return pack_message(TVM_SELECTITEM, act, (LPARAM)Htreeitem_val(item));
}

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

CAMLprim value pack_tvm_setbkcolor(value color)
{
    return pack_message(TVM_SETBKCOLOR, 0, (LPARAM)color_val(color));
}

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

CAMLprim value pack_tvm_setimagelist(value which, value iml)
{
    return pack_message(TVM_SETIMAGELIST, Constant_val(which, treeview_which_image_lists), (LPARAM)Handle_val(iml));
}

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

CAMLprim value pack_tvm_setindent(value indent)
{
    return pack_message(TVM_SETINDENT, Int_val(indent), 0);
}

CAMLprim value pack_tvm_setinsertmark(value item, value after)
{
    return pack_message(TVM_SETINSERTMARK, Bool_val(after), (LPARAM)Htreeitem_val(item));
}

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

CAMLprim value pack_tvm_setinsertmarkcolor(value color)
{
    return pack_message(TVM_SETINSERTMARKCOLOR, 0, (LPARAM)color_val(color));
}

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

CAMLprim value issue_tvm_setitem(value sender, value tvitem)
{
    CAMLparam2(sender, tvitem);
    TVITEMEX* tvi;
    CAMLlocal2(contents, result);

    tvi = new_tvitem(tvitem);
    contents = pack_message(TVM_SETITEM, 0, (LPARAM)tvi);
    result = callback(sender, contents);
    free_tvitem(tvi);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

CAMLprim value pack_tvm_setitemheight(value height)
{
    return pack_message(TVM_SETITEMHEIGHT, Int_val(height), 0);
}

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

CAMLprim value pack_tvm_setscrolltime(value time)
{
    return pack_message(TVM_SETSCROLLTIME, Int_val(time), 0);
}

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

CAMLprim value pack_tvm_settextcolor(value color)
{
    return pack_message(TVM_SETTEXTCOLOR, 0, (LPARAM)color_val(color));
}

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

CAMLprim value pack_tvm_settooltips(value tooltips)
{
    return pack_message(TVM_SETTOOLTIPS, (WPARAM)Handle_val(tooltips), 0);
}

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

CAMLprim value pack_tvm_sortchildren(value item)
{
    return pack_message(TVM_SORTCHILDREN, 0, (LPARAM)Htreeitem_val(item));
}

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

static int treeview_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 treeview_compare_func(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    int result;

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

CAMLprim value issue_tvm_sortchildrencb(value sender, value item, value compare)
{
    CAMLparam3(sender, item, compare);
    TVSORTCB tvscb;
    value* p;
    CAMLlocal2(contents, result);

    memset(&tvscb, 0, sizeof(TVSORTCB));
    tvscb.hParent = Htreeitem_val(item);
    tvscb.lpfnCompare = treeview_compare_func;
    p = malloc(sizeof(value));
    register_global_root(p);
    *p = compare;
    tvscb.lParam = (LPARAM)p;
    contents = pack_message(TVM_SORTCHILDRENCB, 0, (LPARAM)&tvscb);
    result = callback(sender, contents);
    remove_global_root(p);
    free(p);
    if (Int32_val(result) == 0)
        raise_win32_error(0);
    CAMLreturn(Val_unit);
}

/* Notifications */

CAMLprim value unpack_nmtvcustomdraw(value msg)
{
    CAMLparam1(msg);
    NMTVCUSTOMDRAW* nmtvcd;
    CAMLlocal2(val, v);

    nmtvcd = (NMTVCUSTOMDRAW*)Lparam(msg);
    val = alloc_tuple(4);
    v = alloc_handle((HTREEITEM)nmtvcd->nmcd.dwItemSpec);
    Store_field(val, 0, v);
    v = alloc_color(nmtvcd->clrText);
    Store_field(val, 1, v);
    v = alloc_color(nmtvcd->clrTextBk);
    Store_field(val, 2, v);
    Store_field(val, 3, Val_int(nmtvcd->iLevel));
    CAMLreturn(val);
}

CAMLprim value unpack_nmtreeview_delete(value msg)
{
    CAMLparam1(msg);
    NMTREEVIEW* nmtv;
    CAMLlocal2(val, v);

    nmtv = (NMTREEVIEW*)Lparam(msg);
    val = alloc_tuple(1);
    v = alloc_tvitem((TVITEMEX*)&nmtv->itemOld);
    Store_field(val, 0, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmtreeview_drag(value msg)
{
    CAMLparam1(msg);
    NMTREEVIEW* nmtv;
    CAMLlocal2(val, v);

    nmtv = (NMTREEVIEW*)Lparam(msg);
    val = alloc_tuple(2);
    v = alloc_tvitem((TVITEMEX*)&nmtv->itemNew);
    Store_field(val, 0, v);
    v = alloc_point(nmtv->ptDrag);
    Store_field(val, 1, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmtreeview_expand(value msg)
{
    CAMLparam1(msg);
    NMTREEVIEW* nmtv;
    CAMLlocal2(val, v);

    nmtv = (NMTREEVIEW*)Lparam(msg);
    val = alloc_tuple(2);
    v = alloc_tvitem((TVITEMEX*)&nmtv->itemNew);
    Store_field(val, 0, v);
    Store_field(val, 1, Val_constant(nmtv->action, treeview_expand_actions));
    CAMLreturn(val);
}

static DWORD nmtreeview_selchange_actions[] = {
    TVC_BYKEYBOARD,
    TVC_BYMOUSE,
    TVC_UNKNOWN
};
Define_static_constants(nmtreeview_selchange_actions);

CAMLprim value unpack_nmtreeview_selchange(value msg)
{
    CAMLparam1(msg);
    NMTREEVIEW* nmtv;
    CAMLlocal2(val, v);

    nmtv = (NMTREEVIEW*)Lparam(msg);
    val = alloc_tuple(3);
    v = alloc_tvitem((TVITEMEX*)&nmtv->itemOld);
    Store_field(val, 0, v);
    v = alloc_tvitem((TVITEMEX*)&nmtv->itemNew);
    Store_field(val, 1, v);
    Store_field(val, 2, Val_constant(nmtv->action, nmtreeview_selchange_actions));
    CAMLreturn(val);
}

CAMLprim value unpack_nmtreeview_singleexpand(value msg)
{
    CAMLparam1(msg);
    NMTREEVIEW* nmtv;
    CAMLlocal2(val, v);

    nmtv = (NMTREEVIEW*)Lparam(msg);
    val = alloc_tuple(1);
    v = alloc_tvitem((TVITEMEX*)&nmtv->itemNew);
    Store_field(val, 0, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmtvdispinfo(value msg)
{
    CAMLparam1(msg);
    NMTVDISPINFO* nmtvdi;
    CAMLlocal2(val, v);

    nmtvdi = (NMTVDISPINFO*)Lparam(msg);
    val = alloc_tuple(1);
    v = alloc_tvitem((TVITEMEX*)&nmtvdi->item);
    Store_field(val, 0, v);
    CAMLreturn(val);
}

CAMLprim value unpack_nmtvkeydown(value msg)
{
    CAMLparam1(msg);
    NMTVKEYDOWN* nmtvkd;
    CAMLlocal2(val, v);

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

CAMLprim value on_tvn_getdispinfo(value handler, value wnd, value msg)
{
    CAMLparam3(handler, wnd, msg);
    NMHDR* nmh;
    NMTVDISPINFO* nmtvdi;
    BOOL has_text;
    CAMLlocalN(args, 8);
    CAMLlocal5(mask, result, pair, tvi, ret);
    CAMLlocal2(val, v);

    ret = Val_none;
    if (Int_val(Field(msg, 0)) == WM_NOTIFY) {
        nmh = (NMHDR*)Lparam(msg);
        if (nmh->code == TVN_GETDISPINFO) {
            nmtvdi = (NMTVDISPINFO*)nmh;
            has_text =
                (nmtvdi->item.mask & TVIF_TEXT) &&
                nmtvdi->item.pszText != NULL &&
                nmtvdi->item.cchTextMax > 0;
            mask = Alloc_flags(nmtvdi->item.mask, tvitem_components_noparam);
            if (has_text) {
                val = alloc(1, 1);
                Store_field(val, 0, Val_int(nmtvdi->item.cchTextMax - 1));
                mask = alloc_cons(val, mask);
            }
            args[0] = wnd;
            args[1] = msg;
            args[2] = Val_int(Wparam(msg));
            args[3] = alloc_nmhdr(nmh);
            args[4] = alloc_htreeitem(nmtvdi->item.hItem);
            args[5] = alloc_tvitemstate(nmtvdi->item.state, 0xFFFFFFFF);
            args[6] = copy_int32(nmtvdi->item.lParam);
            args[7] = mask;
            result = callbackN(handler, 8, args);
            if (Is_block(result)) {
                if (Tag_val(result) == 0)
                    ret = alloc_some(Field(result, 0));
                else {
                    pair = Field(result, 0);
                    tvi = Field(pair, 0);
                    if (has_text && Is_some(Field(tvi, 2))) {
                        val = Value_of(Field(tvi, 2));
                        if (Is_block(val)) {
                            strncpy(
                                nmtvdi->item.pszText,
                                String_val(Field(val, 0)),
                                nmtvdi->item.cchTextMax - 1);
                            nmtvdi->item.pszText[nmtvdi->item.cchTextMax-1] = '\0';
                        } else if (Int_val(val) == 0)
                            nmtvdi->item.pszText = NULL;
                        else
                            nmtvdi->item.pszText[0] = '\0';
                    }
                    if (Is_some(Field(tvi, 3)))
                        nmtvdi->item.iImage = commoncontrolimageindex_val(Value_of(Field(tvi, 3)));
                    if (Is_some(Field(tvi, 4)))
                        nmtvdi->item.iSelectedImage = commoncontrolimageindex_val(Value_of(Field(tvi, 4)));
                    if (Is_some(Field(tvi, 5))) {
                        switch (Int_val(Value_of(Field(tvi, 5)))) {
                            case 0:
                                nmtvdi->item.cChildren = 0;
                                break;
                            case 1:
                                nmtvdi->item.cChildren = 1;
                                break;
                        }
                    }
                    if (Bool_val(Field(pair, 1)))
                        nmtvdi->item.mask |= TVIF_DI_SETITEM;
                    ret = alloc_some(copy_int32(0));
                }
            }
        }
    }
    CAMLreturn(ret);
}

CAMLprim value on_tvn_getinfotip(value handler, value wnd, value msg)
{
    CAMLparam3(handler, wnd, msg);
    NMHDR* nmh;
    NMTVGETINFOTIP* nmtvgit;
    CAMLlocalN(args, 8);
    CAMLlocal3(result, ret, val);

    ret = Val_none;
    if (Int_val(Field(msg, 0)) == WM_NOTIFY) {
        nmh = (NMHDR*)Lparam(msg);
        if (nmh->code == TVN_GETINFOTIP) {
            nmtvgit = (NMTVGETINFOTIP*)nmh;
            args[0] = wnd;
            args[1] = msg;
            args[2] = Val_int(Wparam(msg));
            args[3] = alloc_nmhdr(nmh);
            args[4] = alloc_htreeitem(nmtvgit->hItem);
            args[5] = copy_int32(nmtvgit->lParam);
            args[6] = copy_string(nmtvgit->pszText);
            args[7] = Val_int(nmtvgit->cchTextMax - 1);
            result = callbackN(handler, 8, args);
            if (Is_block(result)) {
                if (Tag_val(result) == 0)
                    ret = alloc_some(Field(result, 0));
                else {
                    val = Field(result, 0);
                    if (Is_some(val)) {
                        strncpy(
                            nmtvgit->pszText,
                            String_val(Value_of(val)),
                            nmtvgit->cchTextMax - 1);
                        nmtvgit->pszText[nmtvgit->cchTextMax-1] = '\0';
                    }
                    ret = alloc_some(copy_int32(0));
                }
            }
        }
    }
    CAMLreturn(ret);
}
