/*
 * File Name: multi_thread_test.cpp
 */

/*
 * This file is part of uds-plugin-plaintext.
 *
 * uds-plugin-plaintext 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.
 *
 * uds-plugin-plaintext 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Copyright (C) 2008 iRex Technologies B.V.
 * All rights reserved.
 */

#include <stdio.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "text_config.h"
#include "text_model.h"
#include "text_view.h"

using namespace text;

// Global variables.
GdkPixbuf *pix_buf = NULL;
GtkWidget *top_level_window = NULL;

static int page_index = 0;
static int total_pages = 0;

struct RenderData
{
    RenderData(const PluginBitmapAttributes& _ba, const Position& _pos)
    : ba(_ba), pos(_pos)
    {
    }

public:
    PluginBitmapAttributes ba;
    Position pos;
};

struct SearchResult
{
    int    matches;
    Range* ranges;
};

struct Event
{
public:
    Event()
    {
    }
    Event(TextEvent e, void *_data)
    : event(e), data(_data)
    {
    }

public:
    TextEvent event;
    void      *data;
};

class EventQueue
{
public:
    EventQueue()
    {
        mutex = g_mutex_new();
    }
    ~EventQueue()
    {
        g_mutex_free(mutex);
    }

    void append_event(const Event& ev)
    {
        g_mutex_lock(mutex);
        event_queue.push_back(ev);
        g_mutex_unlock(mutex);
    }

    bool get_event(Event& ev)
    {
        bool ret = false;
        g_mutex_lock(mutex);
        if (!event_queue.empty())
        {
            ev = event_queue.front();
            event_queue.pop_front();
            ret = true;
        }
        g_mutex_unlock(mutex);
        return ret;
    }

private:
    std::list<Event> event_queue;
    GMutex* mutex;
};

class EventHandler
{
public:
    EventHandler(EventQueue *_eq)
    : eq(_eq)
    {
    }
    ~EventHandler()
    {
    }
public:
    void handle_paginate_start(unsigned int page_count)
    {
        Event e(PAGINATE_STARTED, (void *)1);
        eq->append_event(e);
    }

    void handle_paginate_end(unsigned int page_count)
    {
        Event e(PAGINATE_ENDED, (void *)page_count);
        eq->append_event(e);
    }

    void handle_render_done(const PluginBitmapAttributes& ba, const Position& start_pos)
    {
        RenderData *rd = new RenderData(ba, start_pos);
        Event e(RENDER_DONE, (void *)rd);
        eq->append_event(e);
    }

    void handle_search_done(const std::vector<Range>& result_ranges, const SearchContext& search_context)
    {
        SearchResult *p = new SearchResult;
        p->matches = result_ranges.size();
        p->ranges  = new Range[p->matches];

        for (unsigned int i=0; i<result_ranges.size(); i++)
        {
            p->ranges[i] = result_ranges[i];
        }

        Event e(SEARCH_DONE, (void *)p);
        eq->append_event(e);
    }

private:
    EventQueue *eq;
};

void fill_pix_buf(const unsigned char *bmp, const int width, const int height)
{
    if (pix_buf != NULL)
    {
        g_object_unref(pix_buf);
        pix_buf = NULL;
    }

    pix_buf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
        FALSE,
        8,
        width,
        height);

    guchar *pixels = gdk_pixbuf_get_pixels(pix_buf);
    int rowstride = gdk_pixbuf_get_rowstride(pix_buf);
    int channels = gdk_pixbuf_get_n_channels(pix_buf);

    guchar *p = NULL;
    for (int y=0; y<height; y++)
    {
        for (int x=0; x<width; x++)
        {
            p = pixels + y*rowstride + x*channels;
            p[0] = p[1] = p[2] = bmp[y*width+x];
        }
    }
}

void handle_event(const Event& ev)
{
    const unsigned char *bmp = 0;
    RenderData *rd = 0;
    Position pos;
    SearchResult *sr = 0;

    switch (ev.event)
    {
    case PAGINATE_STARTED:
        printf("Pagination starts...\n");
        break;

    case PAGINATE_ENDED:
        total_pages = (unsigned int)ev.data;
        printf("Pagination end, total_pages = %d.\n", total_pages);
        break;

    case RENDER_DONE:
        rd = (RenderData *)ev.data;
        pos = rd->pos;
        bmp = rd->ba.data;
        fill_pix_buf(bmp, rd->ba.width, rd->ba.height);
        delete[] bmp;
        delete rd;
        rd = 0;
        printf("Render done for pos (%d, %d).\n", pos.paragraph, pos.offset);
        gdk_window_invalidate_rect(top_level_window->window, NULL, TRUE);
        break;

    case SEARCH_DONE:
        printf("Search done.\n");
        sr = (SearchResult *)ev.data;
        for (int i=0; i<sr->matches; i++)
        {
            printf("Match %3d: (%d, %d)~(%d, %d)\n",
                i+1,
                sr->ranges[i].start.paragraph,
                sr->ranges[i].start.offset,
                sr->ranges[i].end.paragraph,
                sr->ranges[i].end.offset);
        }
        delete[] sr->ranges;
        delete sr;
        break;

    default:
        break;
    }
}

static gboolean widget_event_handler(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
    guint key_code;
    TextController *ctrl = (TextController *)user_data;

    switch (event->type)
    {
        case GDK_KEY_PRESS:
            key_code = ((GdkEventKey*)event)->keyval;
            if (key_code == GDK_Page_Up)
            {
                if (page_index == 0)
                {
                    return FALSE;
                }
                --page_index;
            }
            else if (key_code == GDK_Page_Down)
            {
                if (page_index == total_pages-1)
                {
                    return FALSE;
                }
                ++page_index;
            }
            ctrl->render(page_index);
            break;

        default:
            break;
    }

    return TRUE;
}
gboolean timeout_handler(gpointer data)
{
    Event ev;
    EventQueue *eq = (EventQueue *)data;
    while (eq->get_event(ev))
    {
        handle_event(ev);
    }
    
    return TRUE;
}

static gboolean my_expose(GtkWidget *da, GdkEventExpose *event, gpointer data)
{
    GdkGC *gc = gdk_gc_new(da->window);
    GdkPixbuf** pix_buf = (GdkPixbuf**)data;
    if (*pix_buf != NULL)
    {
        gdk_draw_pixbuf(da->window,
            gc,
            *pix_buf,
            0,
            0,
            0,
            0,
            -1,
            -1,
            GDK_RGB_DITHER_NORMAL,
            0,
            0);
    }

    return TRUE;
}

static void destroy(GtkWidget *widget, gpointer data)
{
    gtk_main_quit ();
}

int main(int argc, char* argv[])
{
    gtk_init(&argc, &argv);

    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s <file_name>.\n", argv[0]);
        return -1;
    }

    // Read content from disk file.
    TextModel model(argv[1]);
    model.open();

    // Create view and controller.
    TextView view(&model);
    g_thread_init(NULL);
    TextController ctrl(&model, &view);

    // Add signal handler.
    EventQueue eq;
    EventHandler event_handler(&eq);
    model.search_done_signal.add_slot<EventHandler>(&event_handler, &EventHandler::handle_search_done);
    view.pagination_start_signal.add_slot<EventHandler>(&event_handler, &EventHandler::handle_paginate_start);
    view.pagination_end_signal.add_slot<EventHandler>(&event_handler, &EventHandler::handle_paginate_end);
    view.render_done_signal.add_slot<EventHandler>(&event_handler, &EventHandler::handle_render_done);

    // GTK stuff
    top_level_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request(top_level_window, DEFAULT_SURFACE_WIDTH, DEFAULT_SURFACE_HEIGHT);

    // Register signal handler
    g_signal_connect(G_OBJECT(top_level_window), "destroy", G_CALLBACK(destroy), NULL);
    g_signal_connect(G_OBJECT(top_level_window), "expose_event", G_CALLBACK(my_expose), &pix_buf);
    gtk_signal_connect(GTK_OBJECT(top_level_window), "key_press_event", GTK_SIGNAL_FUNC(widget_event_handler), &ctrl);
    g_timeout_add(10, timeout_handler, &eq);

    // Show all widget
    gtk_widget_show_all(top_level_window);

    // Render page 0
    ctrl.render(page_index);
    ctrl.search(SearchContext(SEARCH_ALL, Position(0, 0), "render", true, true, false));
    gtk_main();

    g_object_unref(pix_buf);
    return 0;
}

