/*
 * File Name: document_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 "log.h"
#include "document_impl.h"
#include "view_impl.h"
#include "collection_impl.h"
#include "string_impl.h"
#include "search_criteria_impl.h"
#include "text_base_types.h"

namespace text
{

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

PluginDocImpl::PluginDocImpl()
: controller(0)
{
    // 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;

    // IPluginDocSearch
    create_search_criteria  = create_search_criteria_impl;
    request_search_next     = request_search_next_impl;
    request_search_all      = request_search_all_impl;
    abort_search            = abort_search_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<IPluginDocSearch>(this);

    document.search_done_signal.add_slot(this,
        &PluginDocImpl::on_search_done);

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

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

PluginDocImpl::~PluginDocImpl()
{
    g_instances_table.remove(this);
}

PluginStatus 
PluginDocImpl::query_interface_impl(IPluginUnknown    *thiz,
                                    const UDSString   *id, 
                                    void              **ptr )
{
    // 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 )
{
    // 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)
{
    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Check document has been opened or not.
    assert(!instance->document.is_open());

    // Open it.
    return instance->document.open(path->get_buffer(path));
}

PluginBool
PluginDocImpl::is_open_impl(IPluginUnknown    *thiz)
{
    // 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)
{
    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Check document has been opened or not.
    assert(instance->document.is_open());

    // Stop working thread before clear document since pagination task may be running.
    instance->controller->stop();
    instance->document.close();
    return PLUGIN_OK;
}

IPluginUnknown *
PluginDocImpl::create_view_impl(IPluginUnknown   *thiz)
{
    // Check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    
    // Check if the controller is ready or not.

    // Create the view. 
    ViewPtr ptr = new PluginViewImpl(instance);
    assert(ptr);

    if (instance->controller == 0)
    {
        instance->controller = ptr->get_text_controller();
        instance->controller->start();
    }

    return static_cast<IPluginUnknown *>(ptr);
}

PluginStatus 
PluginDocImpl::get_initial_anchor_impl(IPluginUnknown   *thiz,
                                       UDSString        *anchor)
{
    // Use first page as the initial anchor.
    Position pos;
    std::string temp = pos.to_string();
    anchor->assign(anchor, temp.c_str());
    return PLUGIN_OK;
}

PluginStatus 
PluginDocImpl::get_object_from_anchor_impl(IPluginUnknown  *thiz,
                                           const UDSString *anchor, 
                                           PluginRange     *range)
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    Position object_start_pos, object_end_pos;
    Position anchor_pos(anchor->get_buffer(anchor));

    if (!instance->document.get_word_from_anchor(anchor_pos,
                                                 object_start_pos,
                                                 object_end_pos))
    {
        return PLUGIN_FAIL;
    }
    
    range->start_anchor->assign(range->start_anchor,
        object_start_pos.to_string().c_str());
    range->end_anchor->assign(range->end_anchor,
        object_end_pos.to_string().c_str());

    return PLUGIN_OK;
}

PluginDocObjectType 
PluginDocImpl::get_type_of_object_impl(IPluginUnknown      *thiz,
                                       const PluginRange   *range)
{
    return PLUGIN_DOC_OBJECT_TEXT;
}

PluginStatus 
PluginDocImpl::get_words_from_range_impl(IPluginUnknown      *thiz, 
                                        const PluginRange    *char_range, 
                                        PluginRange          *words_range )
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    Position char_start_pos(char_range->start_anchor->get_buffer(char_range->start_anchor));
    Position char_end_pos(char_range->end_anchor->get_buffer(char_range->end_anchor));

    if (char_start_pos > char_end_pos)
    {
        ERRORPRINTF("Invalid range, range_start = %s, range_end = %s",
            char_start_pos.to_string().c_str(),
            char_end_pos.to_string().c_str());
        return PLUGIN_FAIL;
    }

    Position words_start, words_end;
    if (!instance->document.get_words_from_range(char_start_pos, char_end_pos, words_start, words_end))
    {
        return PLUGIN_FAIL;
    }

    words_range->start_anchor->assign(words_range->start_anchor, words_start.to_string().c_str());
    words_range->end_anchor->assign(words_range->end_anchor, words_end.to_string().c_str());
    return PLUGIN_OK;
}

PluginStatus 
PluginDocImpl::get_text_from_range_impl(IPluginUnknown        *thiz, 
                                        const PluginRange     *range, 
                                        UDSString             *result)
{
    // Get instance.
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    Position text_start_pos(range->start_anchor->get_buffer(range->start_anchor));
    Position text_end_pos(range->end_anchor->get_buffer(range->end_anchor));

    if (text_start_pos > text_end_pos)
    {
        ERRORPRINTF("Invalid range, range_start = %s, range_end = %s",
            text_start_pos.to_string().c_str(),
            text_end_pos.to_string().c_str());
        return PLUGIN_FAIL;
    }

    std::string result_string;
    instance->document.get_text_from_range(result_string, text_start_pos, text_end_pos);
    result->assign(result, result_string.c_str());
    
    return PLUGIN_OK;
}

PluginBool 
PluginDocImpl::is_anchor_in_current_document_impl(IPluginUnknown    *thiz,
                                                  const UDSString   *anchor)
{
    // check object. 
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    Position pos(anchor->get_buffer(anchor));
    if (instance->document.has_anchor(pos))
    {
        return PLUGIN_TRUE;
    }
    return PLUGIN_FALSE;
}

PluginStatus
PluginDocImpl::get_file_name_from_anchor_impl(IPluginUnknown    *thiz,
                                              const UDSString   *anchor,
                                              UDSString         *file_name )
{
    // Get instance object.
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // Get absolute file path.
    const std::string& file_path = instance->document.get_path();

    // Get basename for file path.
    char* basename = g_path_get_basename(file_path.c_str());
    file_name->assign(file_name, basename);
    g_free(basename);

    return PLUGIN_OK;
}

PluginStatus
PluginDocImpl::get_file_position_from_anchor_impl(IPluginUnknown    *thiz,
                                                  const UDSString   *anchor,
                                                  signed long long  *position )
{
    Position internal_anchor(anchor->get_buffer(anchor));
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    size_t pos;
    if (instance->document.get_file_pos_from_anchor(pos, internal_anchor))
    {
        *position = pos;
        return PLUGIN_OK;
    }
    
    return PLUGIN_FAIL;
}

int 
PluginDocImpl::compare_anchor_location_impl(IPluginUnknown   *thiz,
                                            const UDSString  *anchor_a,
                                            const UDSString  *anchor_b)
{
    // check object. 
    Position a(anchor_a->get_buffer(anchor_a));
    Position b(anchor_b->get_buffer(anchor_b));

    if (a > b)
    {
        return 1;
    }
    else if (a == b)
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

// Define plain text attribute. Seems only support the encoding.
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)
{
    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 )
{
    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 )
{
    // No hyperlink for plain text.
    return PLUGIN_FALSE;
}

PluginStatus 
PluginDocImpl::get_target_from_hyperlink_impl(IPluginUnknown     *thiz,
                                              const PluginRange  *range, 
                                              UDSString          *anchor )
{
    return PLUGIN_FAIL;
}
    
PluginBool 
PluginDocImpl::is_dictionary_impl(IPluginUnknown  *thiz )
{
    return PLUGIN_FALSE;
}

// The text document does not support table of content.
IPluginUnknown* 
PluginDocImpl::get_supported_marker_types_impl(IPluginUnknown  *thiz )
{
    return 0;
}

// For text document, there is no marker tree.
PluginStatus 
PluginDocImpl::request_marker_trees_impl(IPluginUnknown     *thiz, 
                                         const unsigned int uds_private_size )
{
    return PLUGIN_FAIL;
}

IPluginUnknown *
PluginDocImpl::create_search_criteria_impl(IPluginUnknown  *thiz )
{
    return new PluginSearchCriteria;
}

// Call controller to search.
PluginStatus
PluginDocImpl::request_search_next_impl(IPluginUnknown      *thiz,
                                        IPluginUnknown      *criteria,
                                        const UDSString     *from_anchor,
                                        const unsigned int  search_id)
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // Clear the aborting search task id.
    instance->document.set_aborting_search_task_id(0);

    PluginSearchCriteria* search_criteria = 
        static_cast<PluginSearchCriteria *>(criteria);
    SearchContext& sc = search_criteria->get_search_context();
    sc.search_id = search_id;
    sc.search_type = SEARCH_NEXT;
    sc.from = Position(from_anchor->get_buffer(from_anchor));
    instance->controller->search(&sc);
    return PLUGIN_OK;
}

// Call controller to search.
PluginStatus
PluginDocImpl::request_search_all_impl(IPluginUnknown *thiz,
                                       IPluginUnknown *criteria,
                                       const unsigned int  search_id)
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);

    // Clear the aborting search task id.
    instance->document.set_aborting_search_task_id(0);

    PluginSearchCriteria* search_criteria = 
        static_cast<PluginSearchCriteria *>(criteria);
    SearchContext& sc = search_criteria->get_search_context();
    sc.search_id = search_id;
    sc.search_type = SEARCH_ALL;
    instance->controller->search(&sc);
    return PLUGIN_OK;
}

PluginStatus PluginDocImpl::abort_search_impl(
    IPluginUnknown     *thiz, 
    const unsigned int search_id)
{
    PluginDocImpl *instance = g_instances_table.get_object(thiz);
    instance->document.set_aborting_search_task_id(search_id);
    return PLUGIN_OK;
}

void PluginDocImpl::on_search_done(const std::vector<Range>& result,
                                   const SearchContext* sc)
{
    PluginCollectionImpl *coll_p = new PluginCollectionImpl();
    coll_p->create<RangeImpl *>();
    std::vector<RangeImpl *>& data = coll_p->take_data_ref<RangeImpl *>();
    
    for (unsigned int i=0; i<result.size(); i++)
    {
        data.push_back(new RangeImpl(result[i]));
    }

    PluginEventAttrs attrs;
    attrs.search_end.search_id = sc->search_id;
    attrs.search_end.result = coll_p;
    listeners.broadcast(this, EVENT_SEARCH_END, &attrs);
}

}   // namespace text

