/*
 * File Name: view_impl.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 <cassert>
#include "view_impl.h"
#include "document_impl.h"
#include "collection_impl.h"
#include "log.h"
namespace text
{

utils::ObjectTable<PluginViewImpl> PluginViewImpl::g_instances_table;

PluginViewImpl::PluginViewImpl(PluginDocImpl *doc)
: document(doc)
, view(&document->get_model())
, controller(&document->get_model(), &view)
{
    assert(document);

    // IPluginUnknown
    query_interface = query_interface_impl;
    release         = release_impl;

    // IPluginView
    get_page_number         = get_page_number_impl;
    get_page_name           = get_page_name_impl;
    get_number_of_pages     = get_number_of_pages_impl;
    get_anchor_by_page      = get_anchor_by_page_impl;
    get_prev_page           = get_prev_page_impl;
    get_next_page           = get_next_page_impl;
    get_rendered_page_start = get_rendered_page_start_impl;
    get_physical_page_start = get_physical_page_start_impl;
    get_cover_page          = get_cover_page_impl;

    // IPluginViewSettings
    set_display_size    = set_display_size_impl;
    set_DPI             = set_DPI_impl;
    set_color_depth     = set_color_depth_impl;

    // IPluginRender
    render                  = render_impl;
    create_render_settings  = create_render_settings_impl;
    set_memory_limit        = set_memory_limit_impl;
    get_original_size       = get_original_size_impl;
    get_page_content_area   = get_page_content_area_impl;

    // IPluginFont
    get_font_size   = get_font_size_impl;
    set_font_size   = set_font_size_impl;
    get_font_family = get_font_family_impl;
    set_font_family = set_font_family_impl;

    // IPluginEventBroadcaster
    add_event_receiver      = add_event_receiver_impl;
    remove_event_receiver   = remove_event_receiver_impl;

    g_instances_table.add_interface<IPluginUnknown>(this);
    g_instances_table.add_interface<IPluginView>(this);
    g_instances_table.add_interface<IPluginViewSettings>(this);
    g_instances_table.add_interface<IPluginRender>(this);
    g_instances_table.add_interface<IPluginEventBroadcaster>(this);
    g_instances_table.add_interface<IPluginFont>(this);

    // Initialize signals.
    view.render_done_signal.add_slot(this,
        &PluginViewImpl::on_render_done);
    view.pagination_start_signal.add_slot(this,
        &PluginViewImpl::on_pagination_start);
    view.pagination_end_signal.add_slot(this,
        &PluginViewImpl::on_pagination_done);
}

PluginViewImpl::~PluginViewImpl(void)
{
    g_instances_table.remove(this);
}

PluginStatus 
PluginViewImpl::query_interface_impl(IPluginUnknown    *thiz,
                                     const UDSString   *id, 
                                     void              **ptr )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    if (g_instances_table.query_interface(instance, id->get_buffer(id), ptr))
    {
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

int 
PluginViewImpl::release_impl(IPluginUnknown  *thiz )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    delete instance;
    return 0;
}

int 
PluginViewImpl::get_page_number_impl(IPluginUnknown  *thiz,
                                     const UDSString *anchor )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    return instance->view.get_page_index_by_anchor(Position(
        anchor->get_buffer(anchor))) + 1;
}

PluginStatus
PluginViewImpl::get_page_name_impl(IPluginUnknown  *thiz,
                                   const UDSString *page_start_anchor,
                                   UDSString       *name)
{
    return PLUGIN_FAIL;
}

PluginStatus
PluginViewImpl::get_rendered_page_start_impl(IPluginUnknown  *thiz, 
                                             const UDSString *anchor, 
                                             UDSString       *start_anchor)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    Position page_anchor = instance->view.get_page_anchor_by_anchor(
        Position(anchor->get_buffer(anchor)));
    start_anchor->assign(start_anchor, page_anchor.to_string().c_str());
    return PLUGIN_OK;
}

PluginStatus
PluginViewImpl::get_physical_page_start_impl(IPluginUnknown  *thiz, 
                                             const UDSString *anchor, 
                                             UDSString       *start_anchor)
{
    return get_rendered_page_start_impl(thiz, anchor, start_anchor);
}

int 
PluginViewImpl::get_number_of_pages_impl(IPluginUnknown *thiz)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    return MAX(instance->view.get_page_count(), 1);
}

PluginStatus 
PluginViewImpl::get_anchor_by_page_impl(IPluginUnknown *thiz,
                                        unsigned int page,
                                        UDSString *anchor)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    Position pos;
    if (instance->view.get_anchor_by_page(page-1, pos))
    {
        std::string str = pos.to_string();
        anchor->assign(anchor, str.c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_prev_page_impl(IPluginUnknown *thiz,
                                   UDSString      *anchor)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    Position pos(anchor->get_buffer(anchor));
    Position new_pos;
    if (instance->view.calculate_prev_page_pos(pos, new_pos))
    {
        anchor->assign(anchor, new_pos.to_string().c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_next_page_impl(IPluginUnknown *thiz,
                                   UDSString      *anchor)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    Position pos(anchor->get_buffer(anchor));
    Position new_pos;
    if (instance->view.calculate_next_page_pos(pos, new_pos))
    {
        anchor->assign(anchor, new_pos.to_string().c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus
PluginViewImpl::get_cover_page_impl( IPluginUnknown *thiz,
                                    const int      width,
                                    const int      height,
                                    PluginBitmapAttributes *cover_page)
{
    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::set_display_size_impl(IPluginUnknown *thiz,
                                      const int       width,
                                      const int       height )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    instance->view.set_display_size(width, height);
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::set_DPI_impl(IPluginUnknown        *thiz,
                             const unsigned int    dpi )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    instance->view.set_DPI(dpi);
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::set_color_depth_impl(IPluginUnknown *thiz,
                                     const unsigned int color_depth )
{
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::render_impl(IPluginUnknown      *thiz,
                            const UDSString     *anchor,
                            const int           page_offset,
                            IPluginUnknown      *settings,
                            const RenderArea    *area, 
                            const unsigned int  id )
{
    // Must get proper page anchor, so no page offset allowed
    if (page_offset != 0)
    {
        return PLUGIN_NOT_SUPPORTED;
    }

    // No multiple thread support.
    // Render image here and invoke callback function immeditately.
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    
    // Use controller to render.
    Position render_pos(anchor->get_buffer(anchor));

    // Pre-allocate render result object first, since in the worker thread we
    // don't create any render result objects. This is for thread safe purpose.
    PluginBitmapAttributes ba;
    instance->view.get_display_size(ba.width, ba.height);
    ba.row_stride = (ba.width + 3) / 4 * 4;

    unsigned char* bmp = new unsigned char[ba.row_stride * ba.height];
    memset(bmp, 0xFF, ba.row_stride * ba.height);
    ba.data = bmp;

    PluginRenderResultImpl * result
        = new PluginRenderResultImpl(instance, ba, id, 0);

    result->release_signal.add_slot(instance,
        &PluginViewImpl::on_render_result_released);

    instance->render_results.push_back(result);

    instance->view.get_controller()->render(id, render_pos, result, bmp);

    return PLUGIN_OK;
}

IPluginUnknown * 
PluginViewImpl::create_render_settings_impl(IPluginUnknown  *thiz)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    RenderSettingsPtr ptr = new PluginRenderSettingsImpl;
    assert(ptr);
    instance->render_settings.push_back(ptr);
    ptr->release_signal.add_slot(instance, 
        &PluginViewImpl::on_render_settings_released);
    return static_cast<IPluginUnknown *>(ptr);
}

PluginStatus 
PluginViewImpl::set_memory_limit_impl(IPluginUnknown       *thiz,
                                      const unsigned int   bytes)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    
    if ( bytes == 0 )
    {
        instance->view.get_controller()->pause_pagination();
    }
    else
    {
        instance->view.get_controller()->restart_pagination();
    }
    return PLUGIN_OK;
}

// For plain text plugin, all pages' size are the same.
PluginStatus 
PluginViewImpl::get_original_size_impl(IPluginUnknown       *thiz,
                                       const UDSString      *anchor,
                                       unsigned int         *width,
                                       unsigned int         *height )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    int tmp_width, tmp_height;
    instance->view.get_display_size(tmp_width, tmp_height);
    *width = tmp_width;
    *height = tmp_height;
    return PLUGIN_OK;
}

PluginStatus
PluginViewImpl::get_page_content_area_impl(IPluginUnknown       *thiz,
                                           const UDSString      *start_of_page_anchor,
                                           RenderArea           *area )
{
    return PLUGIN_FAIL;
}

int 
PluginViewImpl::get_font_size_impl(IPluginUnknown   *thiz)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    return instance->view.get_font_size();
}

PluginStatus 
PluginViewImpl::set_font_size_impl(IPluginUnknown      *thiz,
                                   const unsigned int  font_size)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    return instance->view.set_font_size(font_size) ? PLUGIN_OK : PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_font_family_impl(IPluginUnknown    *thiz,
                                     UDSString         *font_family)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    font_family->assign(font_family, instance->view.get_font_family().c_str());
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::set_font_family_impl(IPluginUnknown    *thiz,
                                     const UDSString   *font_family)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    instance->view.set_font_family(font_family->get_buffer(font_family));
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::add_event_receiver_impl(IPluginUnknown     *thiz,
                                        const PluginEvent  plugin_event,
                                        EventFunc          callback,
                                        void               *user_data,
                                        unsigned long      *handler_id )
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    assert(handler_id);
    *handler_id = 
        instance->listeners.add_listener(plugin_event, callback, user_data);
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::remove_event_receiver_impl(IPluginUnknown  *thiz,
                                           unsigned long   handler_id)
{
    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    if (instance->listeners.remove_listener(handler_id))
    {
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

void 
PluginViewImpl::on_render_settings_released(PluginRenderSettingsImpl *settings)
{
    RenderSettingsIter iter = std::find(render_settings.begin(),
                                        render_settings.end(),
                                        settings);
    if (iter != render_settings.end())
    {
        delete *iter;
        render_settings.erase(iter);
    }
}

void 
PluginViewImpl::on_render_result_released(PluginRenderResultImpl * result)
{
    RenderResultIter iter = std::find(render_results.begin(),
                                      render_results.end(),
                                      result);
    if (iter != render_results.end())
    {
        delete *iter;
        render_results.erase(iter);
    }
}

void PluginViewImpl::on_render_done(unsigned int id,
                                    const Position& start_position,
                                    const Position& end_position,
                                    void  *data)
{
    // TODO. Add render settings to render result.
    // PluginRenderSettingsImpl * settings_obj = 
    //    PluginRenderSettingsImpl::query_instance(settings);
    PluginEventAttrs attrs;
    attrs.render_end.result = static_cast<IPluginUnknown *>(data);

    PluginRenderResultImpl* render_result = static_cast<PluginRenderResultImpl *>(data);
    render_result->set_render_result_range(start_position, end_position);
    attrs.render_end.rid = id;
    attrs.render_end.status = RENDER_DONE;
    listeners.broadcast(this, EVENT_RENDERING_END, &attrs);
}

void PluginViewImpl::on_pagination_start(unsigned int total)
{
    PluginEventAttrs attrs;
    attrs.paginate.current = 1;
    attrs.paginate.total = total;
    listeners.broadcast(this, EVENT_PAGINATE_START, &attrs);
}

void PluginViewImpl::on_pagination_done(unsigned int current, unsigned int total)
{
    //
    // Sending paginate done event to UDS
    //
    PluginEventAttrs attrs;
    attrs.paginate.current = current + 1;
    attrs.paginate.total = total;
    listeners.broadcast(this, EVENT_PAGINATE_END, &attrs);
}

PluginStatus PluginViewImpl::get_anchor_from_coordinates_impl(const Position& page_start_pos,
                                                              const int       x,
                                                              const int       y,
                                                              UDSString*      anchor)
{
    Position doc_pos;

    ViewPosition view_pos;
    view_pos.x = x;
    view_pos.y = y;

    if (!view.map_view_pos_to_doc_pos(page_start_pos, view_pos, doc_pos))
    {
        return PLUGIN_FAIL;
    }

    anchor->assign(anchor, doc_pos.to_string().c_str());
    return PLUGIN_OK;
}

IPluginUnknown* PluginViewImpl::get_bounding_rectangles_from_range_impl(const Position&    page_start_pos,
                                                                        const PluginRange* range)
{
    std::vector<Rect> rects;
    Range range_internal(Position(range->start_anchor->get_buffer(range->start_anchor)),
                         Position(range->end_anchor->get_buffer(range->end_anchor)));
    view.get_bounding_rectangles(page_start_pos, rects, range_internal);

    PluginCollectionImpl *coll_p = new PluginCollectionImpl();
    coll_p->create<Rect>();
    std::vector<Rect>& data = coll_p->take_data_ref<Rect>();
    data.swap(rects);

    return coll_p;
}

}

