/*
 * File Name: view_impl.cpp
 */

/*
 * This file is part of uds-plugin-images.
 *
 * uds-plugin-images 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-images 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 "log.h"

namespace images
{

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

PluginViewImpl::PluginViewImpl(PluginDocImpl *doc)
: document(doc)
{
    LOGPRINTF("%p", doc);

    assert(document);
    renderer = new ImagesRenderer(document->get_doc());
    assert(renderer);
    renderer->sig_page_ready.add_slot(this, 
            &PluginViewImpl::render_page_ready);

    // 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;
    get_original_rotation   = get_original_rotation_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);

    // The images plugin does not support IPluginFont.
    // If want to support IPluginFont, uncomment the following line.
    // g_instances_table.add_interface<IPluginFont>(this);

}

PluginViewImpl::~PluginViewImpl(void)
{
    LOGPRINTF("entry");

    remove_slots();

    delete renderer;
    renderer = 0;

    g_instances_table.remove(this);
}

void PluginViewImpl::stop_renderer(void)
{
    if (renderer)
    {
        renderer->stop();
    }
}

void PluginViewImpl::render_page_ready(ImagePage * page,
                                       const unsigned int refId,
                                       ImageRenderStatus status,
                                       void * user_data)
{
    LOGPRINTF("entry");
    
    // Convert user_data to be PluginRenderResultImpl.
    PluginRenderResultImpl * result = 
        static_cast<PluginRenderResultImpl *>(user_data);
    if (!result) { return; }
    
    // Set the page for PluginRenderResultImpl.
    result->set_page(page);

    // Set all fields of PluginEventAttrs.
    PluginEventAttrs attrs;
    attrs.render_end.result = static_cast<IPluginUnknown *>(result);
    if (status == IMG_RENDER_OK)
    {
        attrs.render_end.status = RENDER_DONE;
    }
    else if (status == IMG_RENDER_OOM)
    {
        attrs.render_end.status = RENDER_OUT_OF_MEMORY;
    }
    else if (status == IMG_RENDER_INVALID)
    {
        attrs.render_end.status = RENDER_INVALID_PAGE;        
    }
    else
    {
        attrs.render_end.status = RENDER_FAIL;
    }
    
    attrs.render_end.rid = refId;

    // Notify UDS by using the callback function.        
    listeners.broadcast( this, EVENT_RENDERING_END, &attrs );
}

PluginStatus 
PluginViewImpl::query_interface_impl(IPluginUnknown    *thiz,
                                     const UDSString   *id, 
                                     void              **ptr )
{
    LOGPRINTF("entry");

    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 )
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    instance->release_signal.safe_broadcast(instance);
    return 0;
}

int 
PluginViewImpl::get_page_number_impl(IPluginUnknown  *thiz,
                                     const UDSString *anchor )
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    return instance->document->get_page_number(anchor->get_buffer(anchor));
}

PluginStatus 
PluginViewImpl::get_page_name_impl(IPluginUnknown  *thiz,
                                   const UDSString *anchor,
                                   UDSString       *name)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);

    std::string temp;
    if (instance->document->get_page_name(anchor->get_buffer(anchor), temp))
    {
        name->assign(name, temp.c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

int 
PluginViewImpl::get_number_of_pages_impl(IPluginUnknown *thiz)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    return instance->document->get_page_count();
}

PluginStatus 
PluginViewImpl::get_anchor_by_page_impl(IPluginUnknown *thiz,
                                        unsigned int page,
                                        UDSString *anchor)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);

    // Use first page as the initial anchor.
    std::string temp;
    if (instance->document->get_anchor_of_page(page, temp))
    {
        anchor->assign(anchor, temp.c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_prev_page_impl(IPluginUnknown *thiz,
                                   UDSString      *anchor)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);

    std::string temp(anchor->get_buffer(anchor));
    if (instance->document->get_prev_page(temp))
    {
        anchor->assign(anchor, temp.c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_next_page_impl(IPluginUnknown *thiz,
                                   UDSString      *anchor)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);

    std::string temp(anchor->get_buffer(anchor));
    if (instance->document->get_next_page(temp))
    {
        anchor->assign(anchor, temp.c_str());
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus
PluginViewImpl::get_rendered_page_start_impl(IPluginUnknown  *thiz, 
                                             const UDSString *anchor, 
                                             UDSString       *start_anchor)
{
    if (anchor && start_anchor)
    {
        start_anchor->assign(start_anchor, anchor->get_buffer(anchor));
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

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);
}

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 )
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    instance->render_req_attributes.desired_width = width;
    instance->render_req_attributes.desired_height = height;
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::set_DPI_impl(IPluginUnknown        *thiz,
                             const unsigned int    dpi )
{
    LOGPRINTF("entry");

    // Can be ignored.
    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::set_color_depth_impl(IPluginUnknown *thiz,
                                     const unsigned int color_depth )
{
    LOGPRINTF("entry");

    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::render_impl(IPluginUnknown      *thiz,
                            const UDSString     *anchor,
                            const int           page_offset,
                            IPluginUnknown      *settings,
                            const RenderArea    *area, 
                            const unsigned int  refId )
{
    LOGPRINTF("entry");

    // Must get proper page anchor, so no page offset allowed
    if (page_offset != 0)
    {
        return PLUGIN_NOT_SUPPORTED;
    }

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    assert(instance->renderer);

    // Construct a PluginRenderResultImpl object for this render request.
    // We'll set its fields for it later when render is done.
    PluginRenderResultImpl *result = new PluginRenderResultImpl();
    assert(result);
    instance->render_results.push_back(result);
    result->release_signal.add_slot(instance, 
            &PluginViewImpl::on_render_result_released);
   
    // render image according to the settings.
    PluginRenderSettingsImpl * settings_obj = 
        PluginRenderSettingsImpl::query_instance(settings);

    images::ImagePageAttrs attrs;
    attrs.desired_width = instance->render_req_attributes.desired_width;
    attrs.desired_height = instance->render_req_attributes.desired_height;
    attrs.zoom = settings_obj->zoom();
    attrs.rotation = settings_obj->rotation();
    
    ImageRenderStatus ret = instance->renderer->render(anchor->get_buffer(anchor), 
                                                       attrs, refId, result);

    if (ret == IMG_RENDER_OK)
    {
        return PLUGIN_OK;        
    }
    else if (ret == IMG_RENDER_OOM)
    {
        return PLUGIN_OUT_OF_MEMORY;        
    }
    else /* if (ret == IMG_RENDER_INVALID) || (ret == IMG_RENDER_FAIL)) */
    {        
        return PLUGIN_FAIL;
    }
}

IPluginUnknown * 
PluginViewImpl::create_render_settings_impl(IPluginUnknown  *thiz)
{
    LOGPRINTF("entry");

    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)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    if (instance->renderer)
    {
        if (instance->renderer->set_memory_limit(bytes))
        {
            return PLUGIN_OK;
        }
    }

    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_original_size_impl(IPluginUnknown   *thiz,
                                   const UDSString      *anchor,
                                   unsigned int         *width,
                                   unsigned int         *height )
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    // 3079 When opening an image it doesn't autorotate to the best fit.
    // Showing the image as large as possible.
    // This is a quick way to fix it in images plugin only.
    // Note, this pocily is the same as ImagePage.
    unsigned int w, h;
    if (instance->document->get_original_size(anchor->get_buffer(anchor), w, h))
    {
        unsigned int display_w = instance->render_req_attributes.desired_width;
        unsigned int display_h = instance->render_req_attributes.desired_height;

        PluginRotationDegree autorotate = ImagePage::check_autorotate(w, h, display_w, display_h);
        if (   autorotate == Clockwise_Degrees_90
            || autorotate == Clockwise_Degrees_270)
        {
            *width = h;
            *height = w;
        }
        else
        {
            *width = w;
            *height = h;
        }
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginStatus
PluginViewImpl::get_page_content_area_impl(IPluginUnknown       *thiz,
                                           const UDSString      *start_of_page_anchor,
                                           RenderArea           *area )
{
    area->x_offset = area->y_offset = 0.0f;
    area->width    = area->height   = 1.0f;
    return PLUGIN_OK;
}

PluginStatus 
PluginViewImpl::get_original_rotation_impl(IPluginUnknown   *thiz,
                                   const UDSString          *anchor,
                                   PluginRotationDegree     *rotation)
{
    LOGPRINTF("entry");

    PluginViewImpl *instance = g_instances_table.get_object(thiz);
    if (instance->document->get_original_rotation(anchor->get_buffer(anchor),
                                              *rotation))
    {
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

int 
PluginViewImpl::get_font_size_impl(IPluginUnknown   *thiz)
{
    LOGPRINTF("entry");

    return -1;
}

PluginStatus 
PluginViewImpl::set_font_size_impl(IPluginUnknown      *thiz,
                                   const unsigned int  font_size)
{
    LOGPRINTF("entry");

    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::get_font_family_impl(IPluginUnknown    *thiz,
                                     UDSString         *font_family)
{
    LOGPRINTF("entry");

    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::set_font_family_impl(IPluginUnknown    *thiz,
                                     const UDSString   *font_family)
{
    LOGPRINTF("entry");

    return PLUGIN_FAIL;
}

PluginStatus 
PluginViewImpl::add_event_receiver_impl(IPluginUnknown     *thiz,
                                        const PluginEvent  plugin_event,
                                        EventFunc          callback,
                                        void               *user_data,
                                        unsigned long      *handler_id )
{
    LOGPRINTF("entry");

    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)
{
    LOGPRINTF("entry");

    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)
{
    LOGPRINTF("entry");

    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)
{
    LOGPRINTF("entry");

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

void PluginViewImpl::remove_slots(void)
{
    //
    if (renderer)
    {
        renderer->sig_page_ready.remove_slot(this, 
                &PluginViewImpl::render_page_ready);
    }

    // 
    for (RenderSettingsIter iter = render_settings.begin();
            iter != render_settings.end();
            ++iter)
    {
        if (*iter)
        {
            LOGPRINTF("Removing the slot of release signal of RenderSettings.");
            (*iter)->release_signal.remove_slot(this,
                    &PluginViewImpl::on_render_settings_released);
        }
    }
 
    //
    for (RenderResultIter iter = render_results.begin();
            iter != render_results.end();
            ++iter)
    {
        if (*iter)
        {
            LOGPRINTF("Removing the slot of release signal of RenderResult");
            (*iter)->release_signal.remove_slot(this,
                    &PluginViewImpl::on_render_result_released);
        }
    }
}

}


