/*
 * File      : textview.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2011, 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
 * 2011-03-05     Bernard      first version
 */
#include <rtgui/dc.h>
#include <rtgui/rtgui_system.h>
#include <rtgui/widgets/textview.h>

rt_inline char *_get_line_text(rtgui_textview_t *textview, rt_uint16_t index)
{
    char *line;
    if (index < textview->line_count)
    {
        line = textview->lines + (index * textview->line_width);
        return line;
    }

    return RT_NULL;
}

static void _calc_line(rtgui_textview_t *textview, const char *text)
{
    char *line;
    const unsigned char *ptr;
    rt_ubase_t line_index, line_position;

    if (textview->lines != RT_NULL)
    {
        rtgui_free(textview->lines);
        textview->lines = RT_NULL;
        textview->line_count = 0;
    }

    /* get line count */
    line_index = 0;
    line_position = 0;
    ptr = (const unsigned char *)text;
    if (*ptr == 0) return;

    while (*ptr != '\0')
    {
        if (*ptr == '\n')
        {
            line_index ++;
            line_position = 0;
        }
        else if (*ptr == '\r')
        {
            ptr ++;
            continue;
        }
        else if (*ptr == '\t')
        {
            line_position += 4;
            if (line_position >= textview->line_width - 1)
            {
                line_index ++;
                line_position = 0;
            }
        }
        else
        {
            if ((*ptr) >= 0x80)
            {
                /* fill cjk character */
                if (line_position + 1 >= (textview->line_width - 1))
                {
                    /* split to next line */
                    line_index ++;
                    line_position = 0;
                }

                line_position ++;
                line_position ++;
            }
            else
            {
                line_position ++;
            }

            if (line_position >= textview->line_width - 1)
            {
                line_index ++;
                line_position = 0;
            }
        }

        ptr ++;
    }

    /* set line count */
    textview->line_count = line_index + 1;

    /* allocate lines */
    textview->lines = rtgui_malloc(textview->line_count * textview->line_width);
    rt_memset(textview->lines, 0, (textview->line_count * textview->line_width));

    /* fill lines */
    line_index = 0;
    line_position = 0;
    ptr = (const unsigned char *)text;
    line = _get_line_text(textview, line_index);
    while (*ptr)
    {
        if (*ptr == '\n')
        {
            line_index ++;
            line_position = 0;
            line = _get_line_text(textview, line_index);
        }
        else if (*ptr == '\r')
        {
            /* ignore '\r' */
            ptr ++;
            continue;
        }
        else if (*ptr == '\t')
        {
            line[line_position++] = ' ';
            line[line_position++] = ' ';
            line[line_position++] = ' ';
            line[line_position++] = ' ';
            if (line_position >= textview->line_width - 1)
            {
                line_index ++;
                line_position = 0;
                line = _get_line_text(textview, line_index);
            }
        }
        else
        {
            if ((*ptr) >= 0x80)
            {
                /* fill cjk character */
                if (line_position + 1 >= (textview->line_width - 1))
                {
                    /* split to next line */
                    line_index ++;
                    line_position = 0;
                    line = _get_line_text(textview, line_index);
                }

                line[line_position ++] = *ptr ++;
                line[line_position ++] = *ptr;
            }
            else
            {
                line[line_position ++] = *ptr;
            }

            if (line_position >= textview->line_width - 1)
            {
                line_index ++;
                line_position = 0;
                line = _get_line_text(textview, line_index);
            }
        }

        ptr ++;
    }

    textview->line_current = 0;
}

static void _calc_width(rtgui_textview_t *textview)
{
    rtgui_rect_t rect;
    rt_uint16_t width, height;

    width = rtgui_rect_width(RTGUI_WIDGET(textview)->extent) - 6;
    height = rtgui_rect_height(RTGUI_WIDGET(textview)->extent);

    rtgui_font_get_metrics(RTGUI_WIDGET_FONT(textview), "W", &rect);
    textview->line_width = width / rtgui_rect_width(rect) + 1;
    textview->line_page_count = height / (rtgui_rect_height(rect) + 3);

    /* set minimal value */
    if (textview->line_page_count == 0) textview->line_page_count = 1;
}

static void _draw_textview(rtgui_textview_t *textview)
{
    struct rtgui_dc *dc;
    struct rtgui_rect rect, font_rect;
    char *line;
    rt_ubase_t line_index, item_height;

    rtgui_font_get_metrics(RTGUI_WIDGET_FONT(textview), "W", &font_rect);
    item_height = rtgui_rect_height(font_rect) + 3;

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

    /* fill rect */
    rtgui_widget_get_rect(RTGUI_WIDGET(textview), &rect);
    rtgui_dc_fill_rect(dc, &rect);

    rect.x1 += 3;
    rect.x2 -= 3;

    for (line_index = textview->line_current;
            (line_index < textview->line_current + textview->line_page_count) &&
            (line_index < textview->line_count);
            line_index ++)
    {
        line = (char *)_get_line_text(textview, line_index);
        rtgui_dc_draw_text(dc, line, &rect);

        rect.y1 += item_height;
    }

    rtgui_dc_end_drawing(dc);
}

static void _rtgui_textview_constructor(rtgui_textview_t *textview)
{
    /* init widget and set event handler */
    rtgui_object_set_event_handler(RTGUI_OBJECT(textview), rtgui_textview_event_handler);
    RTGUI_WIDGET(textview)->flag |= RTGUI_WIDGET_FLAG_FOCUSABLE;

    /* set field */
    textview->line_count = 0;
    textview->lines = RT_NULL;

    textview->line_current = -1;
    textview->line_page_count = 1;
}

static void _rtgui_textview_destructor(rtgui_textview_t *textview)
{
    /* release line memory */
    rtgui_free(textview->lines);
    textview->lines = RT_NULL;
}

DEFINE_CLASS_TYPE(textview, "textview",
                  RTGUI_WIDGET_TYPE,
                  _rtgui_textview_constructor,
                  _rtgui_textview_destructor,
                  sizeof(struct rtgui_textview));

rt_bool_t rtgui_textview_event_handler(struct rtgui_object *object, struct rtgui_event *event)
{
    struct rtgui_textview *textview;
    RTGUI_WIDGET_EVENT_HANDLER_PREPARE

    textview = RTGUI_TEXTVIEW(object);
    switch (event->type)
    {
    case RTGUI_EVENT_PAINT:
        _draw_textview(textview);
        break;

    case RTGUI_EVENT_KBD:
    {
        struct rtgui_event_kbd *ekbd = (struct rtgui_event_kbd *)event;
        if (ekbd->type == RTGUI_KEYDOWN)
        {
            rt_int16_t line_current_update;
            line_current_update = textview->line_current;
            if (ekbd->key == RTGUIK_LEFT)
            {
                if (textview->line_current > textview->line_page_count)
                {
                    line_current_update -= textview->line_page_count;
                }
                else if (textview->line_current > 0)
                {
                    line_current_update = 0;
                }
            }
            else if (ekbd->key == RTGUIK_RIGHT)
            {
                if (textview->line_current + textview->line_page_count < textview->line_count - 1)
                {
                    line_current_update += textview->line_page_count;
                }
            }
            else if (ekbd->key == RTGUIK_UP)
            {
                if (textview->line_current > 0)
                {
                    line_current_update --;
                }
            }
            else if (ekbd->key == RTGUIK_DOWN)
            {
                if (textview->line_current + textview->line_page_count < textview->line_count - 1)
                {
                    line_current_update ++;
                }
            }

            if (textview->line_current != line_current_update)
            {
                textview->line_current = line_current_update;
                rtgui_widget_update(widget);
                return RT_TRUE;
            }
        }
        break;
    }
    default:
        return rtgui_widget_event_handler(RTGUI_OBJECT(widget), event);
    }

    return RT_FALSE;
}

rtgui_textview_t *rtgui_textview_create(const char *text, const rtgui_rect_t *rect)
{
    struct rtgui_textview *textview;

    textview = (struct rtgui_textview *) rtgui_widget_create(RTGUI_TEXTVIEW_TYPE);
    if (textview != RT_NULL)
    {
        rtgui_widget_set_rect(RTGUI_WIDGET(textview), rect);

        /* calculate line width and line page count */
        _calc_width(textview);

        /* set text */
        _calc_line(textview, text);
    }

    return textview;
}

void rtgui_textview_destroy(rtgui_textview_t *textview)
{
    rtgui_widget_destroy(RTGUI_WIDGET(textview));
}

void rtgui_textview_set_text(rtgui_textview_t *textview, const char *text)
{
    RT_ASSERT(textview != RT_NULL);

    /* calculate line width and line page count */
    _calc_width(textview);

    /* set text */
    _calc_line(textview, text);

    /* update widget */
    rtgui_widget_update(RTGUI_WIDGET(textview));
}
