/*
 * File Name: document_impl.cpp
 */

/*
 * This file is part of uds-plugin-comics.
 *
 * 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 "document_impl.h"
#include "view_impl.h"
#include "collection_impl.h"
#include "string_impl.h"
#include "marker_entry_impl.h"
#include "log.h"

namespace comics
{

utils::ObjectTable<PluginDocImpl> PluginDocImpl::g_instances_table;

PluginDocImpl::PluginDocImpl()
{
    LOGPRINTF("entry");

    // IPluginUnknown
    query_interface = query_interface_impl;
    release         = release_impl;

    // IPluginDocument
    open        = open_impl;
    is_open     = is_open_impl;
    close       = close_impl;
    create_view = create_view_impl;

    // IPluginDocNavigator
    get_initial_anchor      = get_initial_anchor_impl;
    get_object_from_anchor  = get_object_from_anchor_impl;
    get_type_of_object      = get_type_of_object_impl;
    get_words_from_range    = get_words_from_range_impl;
    get_text_from_range     = get_text_from_range_impl;
    is_anchor_in_current_document = is_anchor_in_current_document_impl;
    get_file_name_from_anchor = get_file_name_from_anchor_impl;
    get_file_position_from_anchor = get_file_position_from_anchor_impl;
    compare_anchor_location = compare_anchor_location_impl;

    // IPluginDocAttributes
    get_attribute = get_attribute_impl;
    set_attribute = set_attribute_impl;

    // IPluginEventBroadcaster
    add_event_receiver      = add_event_receiver_impl;
    remove_event_receiver   = remove_event_receiver_impl;

    // IPluginDocHyperlink
    is_hyperlink                = is_hyperlink_impl;
    get_target_from_hyperlink   = get_target_from_hyperlink_impl;

    // IPluginDocDictionary
    is_dictionary = is_dictionary_impl;

    // IPluginDocMarker
    get_supported_marker_types  = get_supported_marker_types_impl;
    request_marker_trees        = request_marker_trees_impl;
    has_toc                     = has_toc_impl;

    // Initialize interface and object table.
    g_instances_table.add_interface<IPluginUnknown>(this);
    g_instances_table.add_interface<IPluginDocument>(this);
    g_instances_table.add_interface<IPluginDocNavigator>(this);
    g_instances_table.add_interface<IPluginDocAttributes>(this);
    g_instances_table.add_interface<IPluginEventBroadcaster>(this);
    g_instances_table.add_interface<IPluginDocHyperlink>(this);
    g_instances_table.add_interface<IPluginDocDictionary>(this);
    g_instances_table.add_interface<IPluginDocMarker>(this);

    // Initialize document attributes map.
    init_doc_attributes();
}

void PluginDocImpl::init_doc_attributes()
{
    doc_attr_map.insert(std::make_pair("fixed-page", "yes"));
}

PluginDocImpl::~PluginDocImpl()
{
    LOGPRINTF("entry");

    remove_slots();
    g_instances_table.remove(this);
}

unsigned int PluginDocImpl::get_page_count()
{
    LOGPRINTF("%d", document.page_count());

    return document.page_count();
}

unsigned int PluginDocImpl::get_page_number(const std::string & anchor)
{
    LOGPRINTF("%s", anchor.c_str());

    return document.get_position(anchor);
}

bool PluginDocImpl::get_page_name(const std::string & anchor,
                                  std::string & name)
{
    return document.get_page_name(anchor, name);
}

int PluginDocImpl::get_page(const std::string & anchor)
{
    unsigned int page_number = get_page_number(anchor);

    LOGPRINTF("%s %d", anchor.c_str(), page_number);

    return page_number;
}

bool PluginDocImpl::get_anchor_of_page(const unsigned int page_number,
                                       std::string & anchor)
{
    LOGPRINTF("%d", page_number);

    return document.get_anchor_of_page(page_number, anchor);
}

bool PluginDocImpl::get_prev_page(std::string & anchor)
{
    LOGPRINTF("%s", anchor.c_str());

    return document.get_prev_page(anchor);
}

bool PluginDocImpl::get_next_page(std::string & anchor)
{
    LOGPRINTF("%s", anchor.c_str());

    return document.get_next_page(anchor);
}

ImagesDocument * PluginDocImpl::get_doc(void)
{
    LOGPRINTF("entry");

    return &document;
}

bool PluginDocImpl::get_original_size(const std::string &anchor,
                                      unsigned int &width,
                                      unsigned int &height)
{
    return document.get_original_size(anchor, width, height);
}

bool PluginDocImpl::get_original_rotation(const std::string &anchor,
                                          PluginRotationDegree &rotation)
{
    return document.get_original_rotation(anchor, rotation);
}

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

    // check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // check interface.
    if (g_instances_table.query_interface(instance, 
                                          id->get_buffer(id), 
                                          ptr))
    {
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

int 
PluginDocImpl::release_impl(IPluginUnknown  *thiz )
{
    LOGPRINTF("entry");

    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    instance->release_signal.safe_broadcast(instance);
    return 0;
}


PluginStatus
PluginDocImpl::open_impl(IPluginUnknown    *thiz, 
                         const UDSString   *path)
{
    LOGPRINTF("%s", path->get_buffer(path));

    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Check document has been opened or not.
    assert(!instance->document.is_open());
    
    if (instance->document.open_document(path->get_buffer(path), instance->doc_attr_map))
    {	
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;    
}

PluginBool
PluginDocImpl::is_open_impl(IPluginUnknown    *thiz)
{
    LOGPRINTF("entry");

    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Check document has been opened or not.
    if (instance->document.is_open())
    {
        return PLUGIN_TRUE;
    }
    return PLUGIN_FALSE;
}

PluginStatus
PluginDocImpl::close_impl(IPluginUnknown   *thiz)
{
    LOGPRINTF("entry");

    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Stop the render/pre-render thread firstly.
    ViewsIter begin = instance->views.begin();
    ViewsIter end   = instance->views.end();
    for(ViewsIter iter = begin; iter != end; ++iter)
    {
        (*iter)->stop_renderer();
    }
    
    // Check document has been opened or not.
    assert(instance->document.is_open());
    if (instance->document.close_document())
    {
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

IPluginUnknown *
PluginDocImpl::create_view_impl(IPluginUnknown   *thiz)
{
    LOGPRINTF("entry");

    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Create the view.
    ViewPtr ptr = new PluginViewImpl(instance);
    assert(ptr);
    instance->views.push_back(ptr);
    ptr->release_signal.add_slot(instance, 
                                 &PluginDocImpl::on_view_released);
    return static_cast<IPluginUnknown *>(ptr);
}

PluginStatus 
PluginDocImpl::get_initial_anchor_impl(IPluginUnknown   *thiz,
                                       UDSString        *anchor)
{
    LOGPRINTF("entry");

    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // Use first page as the initial anchor.
    int page_num = instance->document.get_initial_page_num();

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

PluginStatus 
PluginDocImpl::get_object_from_anchor_impl(IPluginUnknown  *thiz,
                                           const UDSString *anchor, 
                                           PluginRange     *range)
{
    LOGPRINTF("entry");

	range->start_anchor->assign(range->start_anchor, 
                                anchor->get_buffer(anchor));
    range->end_anchor->assign(range->end_anchor, 
                              anchor->get_buffer(anchor));
    return PLUGIN_OK;
}

PluginDocObjectType 
PluginDocImpl::get_type_of_object_impl(IPluginUnknown      *thiz,
                                       const PluginRange   *range)
{
    LOGPRINTF("entry");

    return PLUGIN_DOC_OBJECT_IMAGE;
}
    
PluginStatus 
PluginDocImpl::get_words_from_range_impl(IPluginUnknown      *thiz, 
                                        const PluginRange    *chars_range, 
                                        PluginRange          *words_range )
{
    LOGPRINTF("entry");
    return PLUGIN_NOT_SUPPORTED;
}

PluginStatus 
PluginDocImpl::get_text_from_range_impl(IPluginUnknown        *thiz, 
                                        const PluginRange     *range, 
                                        UDSString             *result)
{
    LOGPRINTF("entry");

	result->assign(result, range->start_anchor->get_buffer(range->start_anchor));
	return PLUGIN_OK;
	//    return PLUGIN_FAIL;
}

PluginBool 
PluginDocImpl::is_anchor_in_current_document_impl(IPluginUnknown    *thiz,
                                                  const UDSString   *anchor)
{
    LOGPRINTF("entry");

    // check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    if (instance->document.has_anchor(anchor->get_buffer(anchor)))
    {
        return PLUGIN_TRUE;
    }
    return PLUGIN_FALSE;
}

PluginStatus 
PluginDocImpl::get_file_name_from_anchor_impl(IPluginUnknown    *thiz,
                                              const UDSString   *anchor,
                                              UDSString         *file_name)
{
    if (!thiz || !anchor || !file_name)
    {
        return PLUGIN_FAIL;
    }
    
    if (is_anchor_in_current_document_impl(thiz, anchor) == PLUGIN_FALSE)
    {
        return PLUGIN_FAIL;
    }
    
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    std::string temp;
    if (instance->get_page_name(anchor->get_buffer(anchor), temp))
    {
        file_name->assign(file_name, temp.c_str());
        return PLUGIN_OK;
    }
    
    return PLUGIN_FAIL;    
}

PluginStatus 
PluginDocImpl::get_file_position_from_anchor_impl(IPluginUnknown    *thiz,
                                                  const UDSString   *anchor,
                                                  signed long long  *position)
{
    if (!thiz || !anchor || !position)
    {
        return PLUGIN_FAIL;
    }
    
    if (is_anchor_in_current_document_impl(thiz, anchor) == PLUGIN_FALSE)
    {
        return PLUGIN_FAIL;
    }

    // check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    unsigned int page_number = instance->get_page_number(anchor->get_buffer(anchor));
    *position = page_number;
    return PLUGIN_OK;
}

int 
PluginDocImpl::compare_anchor_location_impl(IPluginUnknown   *thiz,
                                            const UDSString  *anchor_a,
                                            const UDSString  *anchor_b)
{
    LOGPRINTF("entry");

    // check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    return instance->document.compare_anchor(anchor_a->get_buffer(anchor_a), 
                                          anchor_a->get_buffer(anchor_b));
}

// Define images attribute.
PluginStatus
PluginDocImpl::get_attribute_impl(IPluginUnknown     *thiz,
                                  const UDSString    *key, 
                                  UDSString          *value )
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // Check whether the key exists or not.
    if (instance->doc_attr_map.find(key->get_buffer(key)) 
            == instance->doc_attr_map.end())
    {
        return PLUGIN_NOT_SUPPORTED;
    }
    value->assign(value, instance->doc_attr_map[key->get_buffer(key)].c_str());
    return PLUGIN_OK;
}

PluginStatus 
PluginDocImpl::set_attribute_impl(IPluginUnknown     *thiz,
                                  const UDSString    *key,
                                  const UDSString    *value )
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    const char* key_cstr = key->get_buffer(key);
    DocAttrMapIter iter = instance->doc_attr_map.find(key_cstr);
    if (iter == instance->doc_attr_map.end())
    {
        return PLUGIN_NOT_SUPPORTED;
    }

    iter->second = value->get_buffer(value);
    return PLUGIN_OK;
}
    
PluginStatus 
PluginDocImpl::add_event_receiver_impl(IPluginUnknown     *thiz,
                                       const PluginEvent  plugin_event,
                                       EventFunc          callback,
                                       void               *user_data,
                                       unsigned long      *handler_id)
{
    LOGPRINTF("entry");

    PluginDocImpl *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 
PluginDocImpl::remove_event_receiver_impl(IPluginUnknown  *thiz,
                                          unsigned long   handler_id )
{
    LOGPRINTF("entry");

    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    if (instance->listeners.remove_listener(handler_id))
    {
        return PLUGIN_OK;
    }
    return PLUGIN_FAIL;
}

PluginBool 
PluginDocImpl::is_hyperlink_impl(IPluginUnknown    *thiz,
                                 const PluginRange *range )
{
    LOGPRINTF("entry");

    // Needed for TOC support, we don't care about the range, just the start
	return is_anchor_in_current_document_impl(thiz, range->start_anchor);
}

PluginStatus 
PluginDocImpl::get_target_from_hyperlink_impl(IPluginUnknown     *thiz,
                                              const PluginRange  *range, 
                                              UDSString          *anchor )
{
    LOGPRINTF("entry");

	anchor->assign(anchor, range->start_anchor->get_buffer(range->start_anchor));
    return PLUGIN_OK;
}
    
PluginBool 
PluginDocImpl::is_dictionary_impl(IPluginUnknown  *thiz )
{
    LOGPRINTF("entry");

    return PLUGIN_FALSE;
}

// The images document supports table of content.
IPluginUnknown* 
PluginDocImpl::get_supported_marker_types_impl(IPluginUnknown  *thiz )
{
    LOGPRINTF("entry");

    // Not necessary to check thiz in this kind of document.
    // But if the document has different marker type it should
    // make necessary check.

    PluginCollectionImpl *collection = 
        new PluginCollectionImpl;
    collection->create<PluginMarkerType>();
    collection->take_data_ref<PluginMarkerType>().push_back(MARKER_TOC);

    // It's not necessary to connect the release signal, because
    // the collection can remove the memory itself. Document object
    // does not store any pointer data in the collection here.
    return static_cast<IPluginUnknown *>(collection);
}

// For images document, the table of content is the file list.
// Steps to generate marker tree.
// 1. Generate the collection object.
// 2. Add marker tree nodes into the collection.
// 3. Invoke the callback function registered in listeners list.
PluginStatus 
PluginDocImpl::request_marker_trees_impl(IPluginUnknown     *thiz, 
                                         const unsigned int uds_private_size )
{
    LOGPRINTF("entry");

    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // Allocate collection object.
    PluginCollectionImpl *collection = new PluginCollectionImpl;
    collection->create<MarkerEntry *>();

    // Retrieve every page anchor.
    MarkerEntry * prev_entry = 0;
    unsigned int count = instance->document.page_count();
    for(unsigned int i = 1; i <= count; ++i)
    {
        std::string anchor;
        if (instance->document.get_anchor_of_page(i, anchor))
        {
            MarkerEntry * entry = marker_entry_new(uds_private_size);
            entry->type = MARKER_TOC;
            entry->anchor = new StringImpl(anchor);
            entry->text = new StringImpl(anchor);
		    entry->sibling      = 0;
		    entry->first_child  = 0;
            entry->uds_private_size = uds_private_size;

            if (i == 1)
            {
                collection->take_data_ref<MarkerEntry *>().push_back(entry);
            }
            
            // Update entry.
            if (prev_entry)
            {
                prev_entry->sibling = entry;
            }
            prev_entry = entry;
        }
    }

    // Notify all listeners.
    PluginEventAttrs event_data;
    event_data.marker_ready.result = static_cast<IPluginUnknown *>(collection);
    instance->listeners.broadcast(thiz, EVENT_MARKER_READY, &event_data);
    return PLUGIN_OK;
}

PluginBool 
PluginDocImpl::has_toc_impl(IPluginUnknown *thiz)
{
	return PLUGIN_TRUE;
}

void PluginDocImpl::on_view_released(ViewPtr view)
{
    LOGPRINTF("entry");

    ViewsIter iter = std::find(views.begin(), views.end(), view);
    if (iter != views.end())
    {
        delete *iter;
        views.erase(iter);
    }
}

void PluginDocImpl::remove_slots(void)
{
    for (ViewsIter iter = views.begin(); iter != views.end(); ++iter)
    {
        if (*iter)
        {
            LOGPRINTF("Removing the slot of release signal of PluginViewImpl");
            (*iter)->release_signal.remove_slot(this, 
                    &PluginDocImpl::on_view_released);
        }
    }
}

}   // namespace comics


