/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 Gilles Roux
 *               2003 Garrett Derner
 *               2010 Yoshihisa Uchida
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "plugin.h"
#include "tv_bookmark.h"
#include "tv_pager.h"
#include "tv_preferences.h"

/* text viewer bookmark functions */

enum {
    TV_BOOKMARK_SYSTEM = 1,
    TV_BOOKMARK_USER   = 2,
};


struct tv_bookmark_info {
    struct tv_screen_pos pos;
    unsigned char flag;
};

/* bookmark stored array */
struct tv_bookmark_info bookmarks[TV_MAX_BOOKMARKS + 1];
static unsigned char bookmark_count;

static int tv_compare_screen_pos(const struct tv_screen_pos *p1, const struct tv_screen_pos *p2)
{
    if (p1->page != p2->page)
        return p1->page - p2->page;

    return p1->line - p2->line;
}

static int bm_comp(const void *a, const void *b)
{
    struct tv_bookmark_info *pa;
    struct tv_bookmark_info *pb;

    pa = (struct tv_bookmark_info*)a;
    pb = (struct tv_bookmark_info*)b;

    return tv_compare_screen_pos(&pa->pos, &pb->pos);
}

static int tv_add_bookmark(const struct tv_screen_pos *pos)
{
    if (bookmark_count >= TV_MAX_BOOKMARKS)
        return -1;

    bookmarks[bookmark_count].pos  = *pos;
    bookmarks[bookmark_count].flag = TV_BOOKMARK_USER;

    return bookmark_count++;
}

static void tv_remove_bookmark(int idx)
{
    int k;

    if (idx >= 0 && idx < bookmark_count)
    {
        for (k = idx + 1; k < bookmark_count; k++)
            bookmarks[k-1] = bookmarks[k];

        bookmark_count--;
    }
}

static int tv_find_bookmark(const struct tv_screen_pos *pos)
{
    int i;

    for (i = 0; i < bookmark_count; i++)
    {
        if (tv_compare_screen_pos(&bookmarks[i].pos, pos) == 0)
            return i;
    }
    return -1;
}

static int tv_change_preferences(const struct tv_preferences *oldp)
{
    int i;

    if (oldp)
    {
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
        rb->cpu_boost(true);
#endif

        for (i = 0; i < bookmark_count; i++)
            tv_convert_fpos(bookmarks[i].pos.file_pos, &bookmarks[i].pos);

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
        rb->cpu_boost(false);
#endif
    }
    return TV_CALLBACK_OK;
}

bool tv_init_bookmark(unsigned char **buf, size_t *size)
{
    (void)buf;
    (void)size;

    tv_add_preferences_change_listner(tv_change_preferences);
    return true;
}

void tv_finalize_bookmark(void)
{
    /* no-operation function */
}

int tv_get_bookmark_positions(struct tv_screen_pos *pos_array)
{
    int i;

    for(i = 0; i < bookmark_count; i++)
        *pos_array++ = bookmarks[i].pos;

    return bookmark_count;
}

void tv_toggle_bookmark(void)
{
    const struct tv_screen_pos *pos = tv_get_screen_pos();
    int idx = tv_find_bookmark(pos);

    if (idx < 0)
    {
        if (tv_add_bookmark(pos) >= 0)
            rb->splash(HZ/2, "Bookmark add");
        else
            rb->splash(HZ/2, "No more add bookmark");
        return;
    }
    tv_remove_bookmark(idx);
    rb->splash(HZ/2, "Bookmark remove");
}

void tv_create_system_bookmark(void)
{
    const struct tv_screen_pos *pos = tv_get_screen_pos();
    int idx = tv_find_bookmark(pos);

    if (idx >= 0)
        bookmarks[idx].flag |= TV_BOOKMARK_SYSTEM;
    else
    {
        /* we can't add a bookmark if there is no room left! */
        if (bookmark_count == TV_MAX_BOOKMARKS)
            return;
        bookmarks[bookmark_count].pos  = *pos;
        bookmarks[bookmark_count].flag = TV_BOOKMARK_SYSTEM;
        bookmark_count++;
    }
}

static const char* get_bookmark_name(int selected, void * data,
                                     char * buffer, size_t buffer_len)
{
    (void)data;
    struct tv_bookmark_info *bookmark = &bookmarks[selected];
    rb->snprintf(buffer, buffer_len,
#ifdef HAVE_LCD_BITMAP
                 "%cPage: %d  Line: %d",
#else
                 "%cP:%d  L:%d",
#endif
                 (bookmark->flag & TV_BOOKMARK_SYSTEM)? '*' : ' ',
                 bookmark->pos.page + 1, bookmark->pos.line + 1);
    return buffer;
}

static int list_action_callback(int action, struct gui_synclist *lists)
{
    (void) lists;
    if (action == ACTION_STD_OK)
        return ACTION_STD_CANCEL;
    return action;
}

void tv_select_bookmark(void)
{
    int i;
    struct tv_screen_pos select_pos;

    for (i = 0; i < bookmark_count; i++)
    {
        if (bookmarks[i].flag & TV_BOOKMARK_SYSTEM)
            break;
    }

    /* if does not find the system bookmark, add the system bookmark. */
    if (i >= bookmark_count)
        tv_create_system_bookmark();

    if (bookmark_count == 1)
        select_pos = bookmarks[0].pos;
    else
    {
        struct simplelist_info info;

        rb->qsort(bookmarks, bookmark_count, sizeof(struct tv_bookmark_info), bm_comp);

        rb->simplelist_info_init(&info, "Select bookmark",
                                 bookmark_count, bookmarks);
        info.get_name = get_bookmark_name;
        info.action_callback = list_action_callback;
        rb->simplelist_show_list(&info);

        if (info.selection >= 0 && info.selection < bookmark_count)
            select_pos = bookmarks[info.selection].pos;
        else
        {
            /* when does not select any bookmarks, move to the current page */
            tv_copy_screen_pos(&select_pos);

            if (select_pos.file_pos == 0)
                rb->splash(HZ, "Start the first page");
            else
                rb->splash(HZ, "Return to the current page");
        }
    }

    /* deletes the system bookmark */
    for (i = 0; i < bookmark_count; i++)
    {
        if ((bookmarks[i].flag &= TV_BOOKMARK_USER) == 0)
        {
            tv_remove_bookmark(i);
            break;
        }
    }

    /* move to the select position */
    if (preferences->vertical_scroll_mode == VS_PAGE)
        select_pos.line = 0;

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(true);
#endif

    tv_move_screen(select_pos.page, select_pos.line, SEEK_SET);

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(false);
#endif
}

/* serialize or deserialize of the bookmark array */
static bool tv_read_bookmark_info(int fd, struct tv_bookmark_info *b)
{
    unsigned char buf[SERIALIZE_BOOKMARK_SIZE];

    if (rb->read(fd, buf, sizeof(buf)) < 0)
        return false;

    b->pos.file_pos = (buf[0] << 24)|(buf[1] << 16)|(buf[2] << 8)|buf[3];
    b->pos.page     = (buf[4] << 8)|buf[5];
    b->pos.line     = buf[6];
    b->flag         = buf[7];

    return true;
}

bool tv_deserialize_bookmarks(int fd)
{
    int i;
    bool res = true;

    if (rb->read(fd, &bookmark_count, 1) < 0)
        return false;

    for (i = 0; i < bookmark_count; i++)
    {
        if (!tv_read_bookmark_info(fd, &bookmarks[i]))
        {
            res = false;
            break;
        }
    }

    bookmark_count = i;
    return res;
}

static void tv_write_bookmark_info(unsigned char *p, const struct tv_bookmark_info *b)
{
    *p++ = b->pos.file_pos >> 24;
    *p++ = b->pos.file_pos >> 16;
    *p++ = b->pos.file_pos >> 8;
    *p++ = b->pos.file_pos;

    *p++ = b->pos.page >> 8;
    *p++ = b->pos.page;

    *p++ = b->pos.line;
    *p   = b->flag;
}

int tv_serialize_bookmarks(unsigned char *buf)
{
    int i;

    buf[0] = bookmark_count;

    for (i = 0; i < bookmark_count; i++)
    {
        tv_write_bookmark_info(buf + i * SERIALIZE_BOOKMARK_SIZE + 1, &bookmarks[i]);
    }
    return i * SERIALIZE_BOOKMARK_SIZE + 1;
}
