/*
 * File      : list_view.c
 * This file is part of RTGUI in RT-Thread RTOS
 * COPYRIGHT (C) 2010, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2010-01-06     Bernard      first version
 */

#include <rtgui/rtgui_theme.h>
#include <rtgui/widgets/list_view.h>

#define LIST_MARGIN     5

static void _rtgui_list_view_constructor(struct rtgui_list_view *view)
{
    /* default rect */
    struct rtgui_rect rect = {0, 0, 200, 200};

    /* set default widget rect and set event handler */
    rtgui_object_set_event_handler(RTGUI_OBJECT(view), rtgui_list_view_event_handler);
    rtgui_widget_set_rect(RTGUI_WIDGET(view), &rect);

    RTGUI_WIDGET(view)->flag |= RTGUI_WIDGET_FLAG_FOCUSABLE;

    view->flag = RTGUI_LIST_VIEW_LIST;
    view->current_item = 0;
    view->items_count = 0;
    view->page_items = 0;

    RTGUI_WIDGET_BACKGROUND(view) = white;
    RTGUI_WIDGET_TEXTALIGN(view) = RTGUI_ALIGN_CENTER_VERTICAL;
}

DEFINE_CLASS_TYPE(listview, "listview",
                  RTGUI_CONTAINER_TYPE,
                  _rtgui_list_view_constructor,
                  RT_NULL,
                  sizeof(struct rtgui_list_view));

static void rtgui_list_view_onicondraw(struct rtgui_list_view *view, struct rtgui_dc *dc)
{
    struct rtgui_rect rect, item_rect, drawing_rect;
    rt_ubase_t c, r, item_index; /* col and row index */
    rt_ubase_t item_width, item_height;
    rtgui_image_t *image;

    if (view->items_count == 0) return;

    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);
    item_index = (view->current_item / view->page_items) * view->page_items;

    item_width = (rtgui_rect_width(rect) - 2 * LIST_MARGIN) / view->col_items;
    item_height = (rtgui_rect_height(rect) - 4) / view->row_items;
    image = view->items[0].image;

    for (r = 0; r < view->row_items; r ++)
    {
        for (c = 0; c < view->col_items; c ++)
        {
            if (item_index < view->items_count)
            {
                item_rect.y1 = rect.y1 + LIST_MARGIN + r * item_height;
                item_rect.x1 = rect.x1 + LIST_MARGIN + c * item_width;
                item_rect.x2 = item_rect.x1 + item_width;
                item_rect.y2 = item_rect.y1 + item_height;

                if (item_index == view->current_item)
                {
                    rtgui_theme_draw_selected(dc, &item_rect);
                }

                drawing_rect.x1 = drawing_rect.y1 = 0;
                drawing_rect.x2 = image->w;
                drawing_rect.y2 = image->h;
                rtgui_rect_moveto_align(&item_rect, &drawing_rect, RTGUI_ALIGN_CENTER_HORIZONTAL);
                drawing_rect.y1 += 5;
                drawing_rect.y2 += 5;
                rtgui_image_blit(view->items[item_index].image, dc, &drawing_rect);

                item_rect.y1 = drawing_rect.y2 + LIST_MARGIN;
                item_rect.x1 += 3;
                item_rect.x2 -= 3;
                rtgui_font_get_metrics(RTGUI_WIDGET_FONT(view), view->items[item_index].name,
                                       &drawing_rect);
                rtgui_rect_moveto_align(&item_rect, &drawing_rect, RTGUI_ALIGN_CENTER_HORIZONTAL);
                rtgui_dc_draw_text(dc, view->items[item_index].name, &drawing_rect);

                item_index ++;
            }
            else break;
        }
    }
}

static void rtgui_list_view_update_icon(struct rtgui_list_view *view, rt_int16_t old_item)
{
    struct rtgui_rect rect, item_rect, drawing_rect;
    rt_ubase_t c, r; /* col and row index */
    rt_ubase_t item_width, item_height;
    rtgui_image_t *image;
    struct rtgui_dc *dc;

    if ((view->items_count == 0) ||
            (old_item == view->current_item))
        return;

    if (old_item / view->page_items != view->current_item / view->page_items)
    {
        /* it's not a same page, update all */
        rtgui_widget_update(RTGUI_WIDGET(view));
        return;
    }

    dc = rtgui_dc_begin_drawing(RTGUI_WIDGET(view));
    if (dc == RT_NULL) return;

    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);

    item_width = (rtgui_rect_width(rect) - 2 * LIST_MARGIN) / view->col_items;
    item_height = (rtgui_rect_height(rect) - 4) / view->row_items;
    image = view->items[0].image;

    /* update old item */
    r = (old_item % view->page_items) / view->col_items;
    c = (old_item % view->page_items) % view->col_items;
    item_rect.y1 = rect.y1 + LIST_MARGIN + r * item_height;
    item_rect.x1 = rect.x1 + LIST_MARGIN + c * item_width;
    item_rect.x2 = item_rect.x1 + item_width;
    item_rect.y2 = item_rect.y1 + item_height;
    rtgui_dc_fill_rect(dc, &item_rect);

    /* draw image */
    drawing_rect.x1 = drawing_rect.y1 = 0;
    drawing_rect.x2 = image->w;
    drawing_rect.y2 = image->h;
    rtgui_rect_moveto_align(&item_rect, &drawing_rect, RTGUI_ALIGN_CENTER_HORIZONTAL);
    drawing_rect.y1 += 5;
    drawing_rect.y2 += 5;
    rtgui_image_blit(view->items[old_item].image, dc, &drawing_rect);

    /* draw text */
    item_rect.y1 = drawing_rect.y2 + LIST_MARGIN;
    item_rect.x1 += 3;
    item_rect.x2 -= 3;
    rtgui_font_get_metrics(RTGUI_WIDGET_FONT(view), view->items[old_item].name,
                           &drawing_rect);
    rtgui_rect_moveto_align(&item_rect, &drawing_rect, RTGUI_ALIGN_CENTER_HORIZONTAL);
    rtgui_dc_draw_text(dc, view->items[old_item].name, &drawing_rect);

    /* update new item as selected */
    r = (view->current_item % view->page_items) / view->col_items;
    c = (view->current_item % view->page_items) % view->col_items;
    item_rect.y1 = rect.y1 + LIST_MARGIN + r * item_height;
    item_rect.x1 = rect.x1 + LIST_MARGIN + c * item_width;
    item_rect.x2 = item_rect.x1 + item_width;
    item_rect.y2 = item_rect.y1 + item_height;
    rtgui_theme_draw_selected(dc, &item_rect);

    /* draw image */
    drawing_rect.x1 = 0;
    drawing_rect.y1 = 3;
    drawing_rect.x2 = image->w;
    drawing_rect.y2 = 3 + image->h;

    rtgui_rect_moveto_align(&item_rect, &drawing_rect, RTGUI_ALIGN_CENTER_HORIZONTAL);
    rtgui_image_blit(view->items[view->current_item].image, dc, &drawing_rect);

    /* draw text */
    item_rect.y1 = drawing_rect.y2 + LIST_MARGIN;
    item_rect.x1 += 3;
    item_rect.x2 -= 3;
    rtgui_font_get_metrics(RTGUI_WIDGET_FONT(view),
                           view->items[view->current_item].name,
                           &drawing_rect);
    rtgui_rect_moveto_align(&item_rect, &drawing_rect, RTGUI_ALIGN_CENTER_HORIZONTAL);
    rtgui_dc_draw_text(dc, view->items[view->current_item].name, &drawing_rect);

    rtgui_dc_end_drawing(dc);
}

static void rtgui_list_view_onlistdraw(struct rtgui_list_view *view, struct rtgui_dc *dc)
{
    rt_ubase_t index, page_index;
    rtgui_rect_t rect, item_rect, image_rect;
    const struct rtgui_list_item *item;

    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);

    /* get item base rect */
    item_rect = rect;
    item_rect.y1 += 2;
    item_rect.y2 = item_rect.y1 + (2 + rtgui_theme_get_selected_height());

    /* get current page */
    page_index = (view->current_item / view->page_items) * view->page_items;
    for (index = 0; index < view->page_items; index ++)
    {
        if (page_index + index >= view->items_count) break;

        item = &(view->items[page_index + index]);

        if (page_index + index == view->current_item)
        {
            rtgui_theme_draw_selected(dc, &item_rect);
        }
        item_rect.x1 += LIST_MARGIN;

        if (item->image != RT_NULL)
        {
            /* blit on center */
            image_rect.x1 = 0;
            image_rect.y1 = 0;
            image_rect.x2 = item->image->w;
            image_rect.y2 = item->image->h;

            rtgui_rect_moveto_align(&item_rect, &image_rect, RTGUI_ALIGN_CENTER_VERTICAL);
            rtgui_image_blit(item->image, dc, &image_rect);
            item_rect.x1 += item->image->w + 2;
        }
        /* draw text */
        rtgui_dc_draw_text(dc, item->name, &item_rect);

        if (item->image != RT_NULL)
            item_rect.x1 -= (item->image->w + 2);
        item_rect.x1 -= LIST_MARGIN;

        /* move to next item position */
        item_rect.y1 += (rtgui_theme_get_selected_height() + 2);
        item_rect.y2 += (rtgui_theme_get_selected_height() + 2);
    }
}

void rtgui_list_view_update_list(struct rtgui_list_view *view, rt_int16_t old_item)
{
    struct rtgui_dc *dc;
    const struct rtgui_list_item *item;
    rtgui_rect_t rect, item_rect;

    if (old_item / view->page_items != view->current_item / view->page_items)
    {
        /* it's not a same page, update all */
        rtgui_widget_update(RTGUI_WIDGET(view));
        return;
    }

    dc = rtgui_dc_begin_drawing(RTGUI_WIDGET(view));
    if (dc == RT_NULL) return;

    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);

    /* get old item's rect and draw old item */
    item_rect.x1 = rect.x1;
    item_rect.x2 = rect.x2;
    item_rect.y1 = rect.y1 + 2;
    item_rect.y1 += (old_item % view->page_items) * (2 + rtgui_theme_get_selected_height());
    item_rect.y2 = item_rect.y1 + (2 + rtgui_theme_get_selected_height());
    rtgui_dc_fill_rect(dc, &item_rect);

    item_rect.x1 += LIST_MARGIN;

    item = &(view->items[old_item]);
    if (item->image != RT_NULL)
    {
        struct rtgui_rect image_rect;

        /* blit on center */
        image_rect.x1 = 0;
        image_rect.y1 = 0;
        image_rect.x2 = item->image->w;
        image_rect.y2 = item->image->h;

        rtgui_rect_moveto_align(&item_rect, &image_rect, RTGUI_ALIGN_CENTER_VERTICAL);
        rtgui_image_blit(item->image, dc, &image_rect);
        item_rect.x1 += item->image->w + 2;
    }
    rtgui_dc_draw_text(dc, item->name, &item_rect);

    /* draw current item */
    item_rect = rect;
    /* get current item's rect */
    item_rect.y1 += 2;
    item_rect.y1 += (view->current_item % view->page_items) * (2 + rtgui_theme_get_selected_height());
    item_rect.y2 = item_rect.y1 + (2 + rtgui_theme_get_selected_height());

    /* draw current item */
    rtgui_theme_draw_selected(dc, &item_rect);

    item_rect.x1 += LIST_MARGIN;

    item = &(view->items[view->current_item]);
    if (item->image != RT_NULL)
    {
        struct rtgui_rect image_rect;

        /* blit on center */
        image_rect.x1 = 0;
        image_rect.y1 = 0;
        image_rect.x2 = item->image->w;
        image_rect.y2 = item->image->h;

        rtgui_rect_moveto_align(&item_rect, &image_rect, RTGUI_ALIGN_CENTER_VERTICAL);
        rtgui_image_blit(item->image, dc, &image_rect);
        item_rect.x1 += (item->image->w + 2);
    }
    rtgui_dc_draw_text(dc, item->name, &item_rect);

    rtgui_dc_end_drawing(dc);
}
RTM_EXPORT(rtgui_list_view_update_list);

void rtgui_list_view_ondraw(struct rtgui_list_view *view)
{
    struct rtgui_rect rect;
    struct rtgui_dc *dc;

    dc = rtgui_dc_begin_drawing(RTGUI_WIDGET(view));
    if (dc == RT_NULL) return;

    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);
    rtgui_dc_fill_rect(dc, &rect);

    switch (view->flag)
    {
    case RTGUI_LIST_VIEW_LIST:
        rtgui_list_view_onlistdraw(view, dc);
        break;

    case RTGUI_LIST_VIEW_ICON:
        rtgui_list_view_onicondraw(view, dc);
        break;
    }

    rtgui_dc_end_drawing(dc);
}
RTM_EXPORT(rtgui_list_view_ondraw);

static rt_bool_t rtgui_list_view_onmouse(struct rtgui_list_view *view, struct rtgui_event_mouse *emouse)
{
    rtgui_rect_t rect;

    /* calculate selected item */

    /* get physical extent information */
    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);
    rtgui_widget_rect_to_device(RTGUI_WIDGET(view), &rect);

    if (rtgui_rect_contains_point(&rect, emouse->x, emouse->y) == RT_EOK)
    {
        rt_uint16_t index;
        rt_uint16_t old_item;

        /* get old item */
        old_item = view->current_item;

        switch (view->flag)
        {
        case RTGUI_LIST_VIEW_LIST:
            index = (emouse->y - rect.y1) / (2 + rtgui_theme_get_selected_height());

            if ((index < view->items_count) && (index < view->page_items))
            {
                /* set selected item */
                view->current_item = (view->current_item / view->page_items) * view->page_items + index;
                if (emouse->button & RTGUI_MOUSE_BUTTON_DOWN)
                {
                    /* down event */
                    rtgui_list_view_update_list(view, old_item);
                }
                else
                {
                    /* up event */
                    if (view->items[view->current_item].action != RT_NULL)
                    {
                        view->items[view->current_item].action(RTGUI_WIDGET(view),
                                                               view->items[view->current_item].parameter);
                    }
                }
            }
            break;

        case RTGUI_LIST_VIEW_ICON:
        {
            rt_uint16_t x, y;
            rt_uint16_t item_height, item_width;
            rt_ubase_t current_page;

            item_width = (rtgui_rect_width(rect) - 2 * LIST_MARGIN) / view->col_items;
            item_height = (rtgui_rect_height(rect) - 4) / view->row_items;
            x = emouse->x - rect.x1;
            y = emouse->y - rect.y1;

            index = (y / item_height * view->col_items) + x / item_width;
            current_page = view->current_item / view->page_items;

            if ((index + (current_page * view->page_items) < view->items_count))
            {
                if (emouse->button & RTGUI_MOUSE_BUTTON_DOWN)
                {
                    view->current_item = index + (current_page * view->page_items);

                    /* down event */
                    rtgui_list_view_update_icon(view, old_item);
                }
                else
                {
                    /* up event */
                    if (view->items[view->current_item].action != RT_NULL)
                    {
                        view->items[view->current_item].action(RTGUI_WIDGET(view),
                                                               view->items[view->current_item].parameter);
                    }
                }
            }
        }
        break;

        case RTGUI_LIST_VIEW_REPORT:
            break;
        }

        return RT_TRUE;
    }

    return RT_FALSE;
}

rt_bool_t rtgui_list_view_event_handler(struct rtgui_object *widget, struct rtgui_event *event)
{
    struct rtgui_list_view *view = RT_NULL;

    view = RTGUI_LIST_VIEW(widget);
    switch (event->type)
    {
    case RTGUI_EVENT_PAINT:
        rtgui_list_view_ondraw(view);
        return RT_FALSE;

    case RTGUI_EVENT_RESIZE:
    {
        struct rtgui_event_resize *resize;

        resize = (struct rtgui_event_resize *)event;

        /* recalculate page items */
        view->page_items = resize->h  / (2 + rtgui_theme_get_selected_height());
    }
    break;

    case RTGUI_EVENT_MOUSE_BUTTON:
    {
        struct rtgui_event_mouse *emouse;

        emouse = (struct rtgui_event_mouse *)event;
        return rtgui_list_view_onmouse(view, emouse);
    }

    case RTGUI_EVENT_KBD:
    {
        struct rtgui_event_kbd *ekbd = (struct rtgui_event_kbd *)event;
        if (ekbd->type == RTGUI_KEYDOWN)
        {
            rt_uint16_t old_item;

            if (view->current_item == -1)
            {
                /* set a initial item */
                if ((view->items_count > 0) &&
                        (ekbd->key == RTGUIK_UP || ekbd->key == RTGUIK_DOWN))
                {
                    view->current_item = 0;
                    if (view->flag == RTGUI_LIST_VIEW_LIST)
                        rtgui_list_view_update_list(view, -1);
                    else
                        rtgui_list_view_update_icon(view, -1);
                    break;
                }
                else return RT_FALSE;
            }

            old_item = view->current_item;
            switch (ekbd->key)
            {
            case RTGUIK_LEFT:
                if (view->flag == RTGUI_LIST_VIEW_LIST)
                {
                    if (view->current_item - view->page_items >= 0)
                        view->current_item -= view->page_items;

                    rtgui_list_view_update_list(view, old_item);
                }
                else if (view->flag == RTGUI_LIST_VIEW_ICON)
                {
                    if (view->current_item > 0)
                        view->current_item --;
                    rtgui_list_view_update_icon(view, old_item);
                }
                return RT_FALSE;

            case RTGUIK_UP:
                if (view->flag == RTGUI_LIST_VIEW_LIST)
                {
                    if (view->current_item > 0)
                        view->current_item --;
                    rtgui_list_view_update_list(view, old_item);
                }
                else if (view->flag == RTGUI_LIST_VIEW_ICON)
                {
                    if (view->current_item >= view->col_items)
                        view->current_item -= view->col_items;
                    else
                        view->current_item = 0;

                    rtgui_list_view_update_icon(view, old_item);
                }
                return RT_FALSE;

            case RTGUIK_RIGHT:
                if (view->flag == RTGUI_LIST_VIEW_LIST)
                {
                    if (view->current_item + view->page_items < view->items_count - 1)
                        view->current_item += view->page_items;

                    rtgui_list_view_update_list(view, old_item);
                }
                else if (view->flag == RTGUI_LIST_VIEW_ICON)
                {
                    if (view->current_item < view->items_count - 1)
                        view->current_item ++;

                    rtgui_list_view_update_icon(view, old_item);
                }
                return RT_FALSE;

            case RTGUIK_DOWN:
                if (view->flag == RTGUI_LIST_VIEW_LIST)
                {
                    if (view->current_item < view->items_count - 1)
                        view->current_item ++;

                    rtgui_list_view_update_list(view, old_item);
                }
                else if (view->flag == RTGUI_LIST_VIEW_ICON)
                {
                    if (view->current_item + view->col_items <= (view->items_count - 1))
                        view->current_item += view->col_items;
                    else
                        view->current_item = view->items_count - 1;

                    rtgui_list_view_update_icon(view, old_item);
                }
                return RT_FALSE;

            case RTGUIK_RETURN:
                if (view->items[view->current_item].action != RT_NULL)
                {
                    view->items[view->current_item].action(RTGUI_WIDGET(view),
                                                           view->items[view->current_item].parameter);
                }
                return RT_FALSE;

            default:
                break;
            }
        }
    }
    return RT_FALSE;

    default:

    /* use view event handler */
    return rtgui_container_event_handler(widget, event);
    }

    return RT_FALSE;
}
RTM_EXPORT(rtgui_list_view_event_handler);

static void rtgui_list_view_calc(struct rtgui_list_view *view)
{
    rtgui_rect_t rect;
    rt_uint32_t image_width, image_height;
    rt_ubase_t text_width, text_height;
    rt_ubase_t item_width, item_height;

    if (view->items_count == 0) return;

    /* get image width and height */
    if (view->items[0].image != RT_NULL)
    {
        image_width  = view->items[0].image->w;
        image_height = view->items[0].image->h;
    }
    else
    {
        image_width  = 0;
        image_height = 0;
    }

    rtgui_font_get_metrics(RTGUI_WIDGET_FONT(view), "HHHHHH", &rect);

    text_height = rtgui_rect_height(rect);
    text_width = rtgui_rect_width(rect);

    rtgui_widget_get_rect(RTGUI_WIDGET(view), &rect);

    item_width = (image_width + LIST_MARGIN);
    if (item_width < (text_width + LIST_MARGIN)) item_width = text_width + LIST_MARGIN;
    item_height = image_height + 8 + text_height + LIST_MARGIN;
    if (item_width > item_height) item_height = item_width;
    else item_width = item_height;

    view->row_items = (rtgui_rect_height(rect) - 2 * LIST_MARGIN) / item_height;
    view->col_items = (rtgui_rect_width(rect) - 2 * LIST_MARGIN) / item_width;
    view->page_items = view->row_items * view->col_items;
}

rtgui_list_view_t *rtgui_list_view_create(const struct rtgui_list_item *items, rt_uint16_t count,
        rtgui_rect_t *rect, rt_uint16_t flag)
{
    struct rtgui_list_view *view = RT_NULL;

    view = (struct rtgui_list_view *) rtgui_widget_create(RTGUI_LIST_VIEW_TYPE);
    if (view != RT_NULL)
    {
        view->items = items;
        view->items_count = count;

        view->flag = flag;
        rtgui_widget_set_rect(RTGUI_WIDGET(view), rect);

        if (flag == RTGUI_LIST_VIEW_LIST)
            view->page_items = rtgui_rect_height(*rect) / (2 + rtgui_theme_get_selected_height());
        else if ((flag == RTGUI_LIST_VIEW_ICON) && (count > 0))
        {
            rtgui_list_view_calc(view);
        }
    }

    return view;
}
RTM_EXPORT(rtgui_list_view_create);

void rtgui_list_view_destroy(rtgui_list_view_t *view)
{
    /* destroy view */
    rtgui_widget_destroy(RTGUI_WIDGET(view));
}
RTM_EXPORT(rtgui_list_view_destroy);

