/***********************************************************************************

    Copyright (C) 2007-2020 Ahmet Öztürk (aoz_2@yahoo.com)

    This file is part of Lifeograph.

    Lifeograph 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 3 of the License, or
    (at your option) any later version.

    Lifeograph 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 Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cmath>

#include "../lifeograph.hpp"
#include "../app_window.hpp"
#include "widget_textviewsearch.hpp"


using namespace LIFEO;


// ADDITIONAL RECIPES
const ParserText::Recipe::Contents
    TextbufferDiarySearch::s_rc_separator =
    {
        Ch_NEWLINE,
        { Ch_TAB, &ParserText::set_start },
        Ch_TAB, Ch_TAB, Ch_TAB, Ch_TAB, Ch_TAB, Ch_TAB, Ch_TAB, // x7
        { Ch_NEWLINE, &ParserText::apply_custom1 }
    };

// LINKS ===========================================================================================
LinkMatch::LinkMatch( const Glib::RefPtr< Gtk::TextMark >& start,
                      const Glib::RefPtr< Gtk::TextMark >& end,
                      Match& match, TextbufferDiarySearch* container, int para_no )
:   Link( start, end, LT_TAG ), m_match( match ), m_container( container ), m_para_no( para_no )
{
}

void
LinkMatch::go()
{
    m_container->set_para_sel( m_para_no );
    AppWindow::p->UI_entry->show( m_match );
}

// TEXTBUFFERDIARYSEARCH ===========================================================================
TextbufferDiarySearch::TextbufferDiarySearch()
{
    // TAGS
    // NOTE: order is significant. the later a tag is added the more dominant it is.
    Glib::RefPtr< TagTable > tag_table = get_tag_table();

    m_tag_para_sel = Tag::create( "para.sel" );
    tag_table->add( m_tag_para_sel );

    m_tag_region->property_scale() = 0.3;

    m_tag_hidden->property_invisible() = false;

    m_all_recipes.insert( new Recipe{ RID_CUSTOM, this, &s_rc_separator, 0 } );
}

void
TextbufferDiarySearch::set_para_sel( int para_no )
{
    if( para_no == m_para_no_sel ) return;

    auto&& it_bgn{ get_iter_at_line( m_para_no_sel ) };
    auto   it_end = it_bgn;
    it_end.forward_to_line_end();
    remove_tag( m_tag_para_sel, it_bgn, it_end );

    it_end = it_bgn = get_iter_at_line( para_no );
    it_end.forward_to_line_end();
    apply_tag( m_tag_para_sel, it_bgn, it_end );

    m_para_no_sel = para_no;
}

void
TextbufferDiarySearch::set_text_from_matches( ListMatches* matches )
{
    m_flag_settext_operation = true;
    clear_links();
    m_parser_pos_cursor_para_bgn = -1;
    m_parser_pos_cursor_para_end = -1;
    m_ptr2matches = matches;
    m_index_match = 0;
    m_para_no_sel = -1;
    m_word_count = 0;
    if( matches && !matches->empty() )
    {
        Paragraph* prev_para   { nullptr };
        Ustring    result_text { "\n" };
        int        i           { 0 };

        for( auto& match : *m_ptr2matches )
        {
            if( ++i > 200 ) break;
            if( match->para == prev_para ) continue;
            //add_match_item( match );
            result_text += ( match->para->get_text() + "\n\t\t\t\t\t\t\t\t\n" );
            prev_para = match->para;
        }
        Gtk::TextBuffer::set_text( result_text );
    }
    else
    {
        Gtk::TextBuffer::set_text( "" );
    }

    reparse();
    m_flag_settext_operation = false;
}

void
TextbufferDiarySearch::set_theme( const Theme* theme )
{
    m_p2theme = theme;

    static Glib::RefPtr< Gtk::CssProvider > css_provider;

    int size{ m_p2theme->font.get_size() };
    if( ! m_p2theme->font.get_size_is_absolute() )
        size /= PANGO_SCALE;

    const auto font =
            Pango::FontDescription( STR::compose( m_p2theme->font.get_family(), " ", size ) );
    m_text_width_tab = get_text_width( font, "\t" );
    m_text_width_checkbox =
            get_text_width(
                    Pango::FontDescription( STR::compose( "monospace ", size ) ), "[*]" ) +
            get_text_width( font, " " );
    m_text_width_dash = get_text_width( font, "- " );
    m_text_width_dot = get_text_width( font, "• " );

    Ustring data{
            STR::compose(
                "textview { "
                    "font-family: ", m_p2theme->font.get_family(), "; "
                    "font-size: ", size, "pt; "
                    "caret-color: ", m_p2theme->color_text.to_string(), "; }\n"
                "text:selected, text selection { "
                    "color: ", m_p2theme->color_base.to_string(), "; "
                    "background: ", m_p2theme->color_heading.to_string(), " }\n"
                "text { "
                    "color: ", m_p2theme->color_text.to_string(), "; "
                    "background-color: ", m_p2theme->color_base.to_string(), " }" ) };

    if( css_provider )
        m_ptr2TvD->get_style_context()->remove_provider( css_provider );
    css_provider = Gtk::CssProvider::create();
    if( css_provider->load_from_data( data ) )
        m_ptr2TvD->get_style_context()->add_provider(
                css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION );

    m_tag_highlight->property_background_rgba() = m_p2theme->get_color_region_bg();

    m_tag_inline_tag->property_background_rgba() = m_p2theme->get_color_mid();

    m_tag_comment->property_foreground_rgba() = m_p2theme->get_color_mid();

    m_tag_hidable->property_foreground_rgba() = m_p2theme->get_color_mid();

    // separator:
    m_tag_region->property_paragraph_background_rgba() = m_p2theme->get_color_region_bg();

    m_tag_para_sel->property_paragraph_background_rgba() = m_p2theme->color_highlight;

    m_tag_match->property_foreground_rgba() = m_p2theme->color_base;
    m_tag_match->property_background_rgba() = m_p2theme->get_color_match_bg();
}

// PARSING
void
TextbufferDiarySearch::reset( UstringSize bgn, UstringSize end )
{
    TextbufferDiary::reset( bgn, end );

    m_index_match = 0;
    m_para_no_sel = 0;
}

void
TextbufferDiarySearch::apply_heading()
{
    apply_tag( m_tag_region, get_iter_at_offset( 0 ), get_iter_at_offset( 1 ) );
}

void
TextbufferDiarySearch::apply_link_hidden()
{
    bool   flag_broken { false };
    auto&& it_end      { get_iter_at_offset( m_pos_cur + 1 ) };

    switch( m_recipe_cur->m_id )
    {
        case RID_URI:
            break;
        case RID_ID:
        {
            DiaryElement* element{ Diary::d->get_element( m_recipe_cur->m_int_value ) };
            if( element == nullptr || element->get_type() != DiaryElement::ET_ENTRY )
                flag_broken = true;
            break;
        }
    }

    apply_hidden_link_tags( it_end, flag_broken ? m_tag_link_broken : m_tag_link );
}

void
TextbufferDiarySearch::apply_link()
{
    switch( m_recipe_cur->m_id )
    {
        case RID_DATE:
            apply_date();
            break;
        case RID_LINK_AT:
        case RID_URI:
        case RID_ID:
            apply_tag( m_tag_link,
                       get_iter_at_offset( m_recipe_cur->m_pos_bgn ),
                       get_iter_at_offset( m_pos_cur ) );
            break;
    }
}

void
TextbufferDiarySearch::apply_date()
{
    auto&& it_bgn    { get_iter_at_offset( m_recipe_cur->m_pos_bgn ) };
    auto&& it_end    { get_iter_at_offset( m_pos_cur + 1 ) };

    apply_tag( m_tag_link, it_bgn, it_end );
}

void
TextbufferDiarySearch::apply_chart()
{
    auto&&     it_bgn { get_iter_at_offset( m_recipe_cur->m_pos_bgn ) };
    auto&&     it_cur { get_iter_at_offset( m_pos_cur ) };
    ChartElem* chart  { Diary::d->get_chart( get_slice( it_bgn, it_cur ) ) };

    apply_tag( chart ? m_tag_link : m_tag_link_broken, it_bgn, it_cur );
}

void
TextbufferDiarySearch::apply_match()
{
    auto&& it_bgn{ get_iter_at_offset( m_pos_search ) };
    auto&& it_end{ get_iter_at_offset( m_pos_cur + 1 ) };

    m_list_links.push_back( new LinkMatch( create_mark( it_bgn ),
                                           create_mark( it_end ),
                                           *m_ptr2diary->get_match_at( m_index_match++ ),
                                           this, it_bgn.get_line() ) );

    apply_tag( m_tag_match, it_bgn, it_end );
}

void
TextbufferDiarySearch::apply_inline_tag()
{
    // m_pos_mid is used to determine if the name part or the value type is being applied
    if( m_recipe_cur->m_pos_mid == 0 )
    {
        auto&& it_bgn{ get_iter_at_offset( m_recipe_cur->m_pos_bgn ) };
        auto&& it_end{ get_iter_at_offset( m_pos_cur ) };
        apply_tag( m_tag_inline_tag, it_bgn, it_end );
    }
}

void
TextbufferDiarySearch::apply_custom1()
{
    auto&& it_bgn { get_iter_at_offset( m_recipe_cur->m_pos_bgn ) };
    auto   it_end = it_bgn;
    it_end.forward_to_line_end();
    apply_tag( m_tag_region, it_bgn, it_end );
}

// TEXTVIEW ========================================================================================
TextviewDiarySearch::TextviewDiarySearch()
{
    init();
}

TextviewDiarySearch::TextviewDiarySearch( BaseObjectType* obj,
                                          const Glib::RefPtr< Gtk::Builder >& builder )
:   TextviewDiary( obj, builder )
{
    init();
}

inline void
TextviewDiarySearch::init()
{
    m_buffer = m_buffer_search = new TextbufferDiarySearch;
    set_buffer( static_cast< Glib::RefPtr< TextbufferDiary > >( m_buffer ) );
    m_buffer_search->m_ptr2TvD = this;

    auto builder{ Gtk::Builder::create() };
    Lifeograph::load_gui( builder, Lifeograph::SHAREDIR + "/ui/tv_diary.ui" );

    builder->get_widget( "Po_find", m_Po_find );
    builder->get_widget( "MoB_replace_match", m_MoB_replace );

    m_Po_find->set_relative_to( *this );

    set_editable( false );

    m_buffer_search->set_theme( ThemeSystem::get() );

    m_MoB_replace->signal_clicked().connect(
            [ this ]()
            {
                if( m_link_hovered == nullptr ) return;

                m_Sg_replace.emit( dynamic_cast< LinkMatch* >( m_link_hovered )->m_match );
            } );

    m_cursor_default = Gdk::ARROW;
}

void
TextviewDiarySearch::show_Po_find()
{
    if( m_link_hovered == nullptr ) return;

    bool show_replace{ m_buffer_search->m_ptr2diary->is_in_edit_mode() };
    //LinkMatch* link{ dynamic_cast< LinkMatch* >( m_link_hovered ) };
    m_MoB_replace->set_visible( show_replace );

    Gdk::Rectangle rect;
    auto&&         it_bgn{ m_link_hovered->m_mark_start->get_iter() };
    auto&&         it_end{ m_link_hovered->m_mark_end->get_iter() };
    int            w_x1, w_y1, w_x2, w_y2;

    get_iter_location( it_bgn, rect );
    buffer_to_window_coords( Gtk::TEXT_WINDOW_TEXT, rect.get_x(), rect.get_y(), w_x1, w_y1 );
    get_iter_location( it_end, rect );
    buffer_to_window_coords( Gtk::TEXT_WINDOW_TEXT,
                             rect.get_x() + rect.get_width(), rect.get_y() + rect.get_height(),
                             w_x2, w_y2 );

    rect.set_x( w_x1 );
    rect.set_y( w_y1 );
    rect.set_width( w_x2 - w_x1 );
    rect.set_height( w_y2 - w_y1 );

    m_Po_find->set_pointing_to( rect );
    m_Po_find->show();
}

void
TextviewDiarySearch::replace_match( Match& match, const Ustring& new_str )
{
    for( Link* link : m_buffer_search->m_list_links )
    {
        LinkMatch* lm{ dynamic_cast< LinkMatch* >( link ) };
        if( lm->m_match == match )
        {
            auto&& it_bgn{ lm->m_mark_start->get_iter() };
            auto&& it_end{ lm->m_mark_end->get_iter() };

            m_buffer_search->m_flag_settext_operation = true;

            it_bgn = m_buffer_search->erase( it_bgn, it_end );
            m_buffer_search->insert( it_bgn, new_str );

            m_buffer_search->m_flag_settext_operation = false;
            return;
        }
    }
}

void
TextviewDiarySearch::set_para_sel( Match& match )
{
    for( Link* link : m_buffer_search->m_list_links )
    {
        LinkMatch* lm{ dynamic_cast< LinkMatch* >( link ) };
        if( lm->m_match == match )
        {
            m_buffer_search->set_para_sel( lm->m_para_no );

            scroll_to( lm->m_mark_start, 0.05 );

            return;
        }
    }
}

bool
TextviewDiarySearch::on_button_release_event( GdkEventButton* event )
{
    if( m_link_hovered != nullptr )
    {
        if( event->button == 1 )
        {
            m_link_hovered->go();
            return true;
        }
        else
        if( event->button == 3 && m_buffer_search->m_ptr2diary->is_in_edit_mode() )
        {
            show_Po_find();
            return true;
        }
    }

    return false;
}

bool
TextviewDiarySearch::on_draw( const Cairo::RefPtr< Cairo::Context >& cr )
{
    return Gtk::TextView::on_draw( cr );
}

bool
TextviewDiarySearch::handle_query_tooltip( int x, int y, bool keyboard_mode,
                                           const Glib::RefPtr< Gtk::Tooltip >& tooltip )
{
    if( m_link_hovered != nullptr )
    {
        auto   host_entry{ dynamic_cast< LinkMatch* >( m_link_hovered )->m_match.para->m_host };
        auto&& tooltip_text{ "<b>" + host_entry->get_list_str() + "</b>\n" +
                             _( "Click to jump to" ) };

        if( m_buffer_search->m_ptr2diary->is_in_edit_mode() )
        {
            tooltip_text += "  |  ";
            tooltip_text += _( "Right click to replace" );
        }

        tooltip->set_markup( tooltip_text );
    }
    else
        return false;

    return true;
}
