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

    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/>.

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


#include "lifeograph.hpp"
#include "app_window.hpp"
#include "printing.hpp"
#include "diary.hpp"
#include "widgets/chart_surface.hpp"


using namespace LIFEO;

// PRINTABLES ======================================================================================
void
PrintablePageStart::render()
{
    auto cr{ PrintOpr::s_print_context->get_cairo_context() };

    const double&& w{ PrintOpr::s_print_context->get_width() };
    const double&& h{ PrintOpr::s_print_context->get_height() };
    cr->rectangle( w / -2, h / -2, w * 2, h * 2 );

    cr->set_source_rgb( m_bg_color.get_red(), m_bg_color.get_green(), m_bg_color.get_blue() );
    cr->fill();
}

void
PrintableTextLine::render()
{
    Cairo::RefPtr< Cairo::Context > cr( PrintOpr::s_print_context->get_cairo_context() );

    // Set the main color
    cr->set_source_rgb( m_color.get_red(), m_color.get_green(), m_color.get_blue() );
    cr->move_to( m_x, m_align_bottom ? m_y : m_y + get_height() );
    m_layout->show_in_cairo_context( cr );
}

void
PrintableImage::render()
{
    Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();
    Gdk::Cairo::set_source_pixbuf( cr, m_pixbuf, m_x, m_y );
    cr->paint();
}

// PRINT OPERATION =================================================================================
Glib::RefPtr< Gtk::PrintContext > PrintOpr::s_print_context;

Glib::RefPtr< PrintOpr >
PrintOpr::create()
{
    return Glib::RefPtr< PrintOpr >( new PrintOpr() );
}

PrintOpr::PrintOpr()
{
    m_refSettings = Gtk::PrintSettings::create();
    m_refPageSetup = Gtk::PageSetup::create();
    set_print_settings( m_refSettings );
    set_default_page_setup( m_refPageSetup );
    set_embed_page_setup( true );
    set_track_print_status();
}

PrintOpr::~PrintOpr()
{
}

void
PrintOpr::clear_content()
{
    for( ListPrintables::iterator iter = m_content.begin(); iter != m_content.end(); ++iter )
        delete *iter;

    m_content.clear();
}

void
PrintOpr::init_variables()
{
    // CHAPTER ITERATORS
    m_last_chapter_date = 0;

    m_chapters = Diary::d->get_chapter_ctg_cur();
    m_iter_chapter = m_chapters->rbegin();

    // ENTRY ITERATORS
    m_iter_entry = Diary::d->get_entries().rbegin();
    if( m_parser.m_opt_one_entry )
    {
        m_iter_entry_end = EntryIterConstRev( Diary::d->get_entries().find(
                AppWindow::p->UI_entry->get_cur_entry()->get_date_t() ) );
        m_iter_entry = m_iter_entry_end;
        m_iter_entry--;
    }
    else
    {
        m_iter_entry_end = Diary::d->get_entries().rend();
    }
}

void
PrintOpr::set_hide_comments( bool flag_hide )
{
    m_parser.m_opt_hide_comments = flag_hide;
}

Gtk::Widget*
PrintOpr::on_create_custom_widget()
{
    auto update_widgetry = [ this ]( Gtk::StateType )
    {
        m_CB_one_entry_per_page->set_visible( not( m_RB_current->get_active() ) );

        if( m_CB_theme_colors->get_active() )
            m_CB_one_entry_per_page->set_active();
        m_CB_one_entry_per_page->set_sensitive( not( m_CB_theme_colors->get_active() ) );

        m_FB->set_visible( not( m_CB_theme_font->get_active() ) );
    };

    if( m_builder )
        m_builder.reset();

    m_builder = Gtk::Builder::create();
    Lifeograph::load_gui( m_builder, Lifeograph::SHAREDIR + "/ui/print.ui" );

    m_builder->get_widget( "Bx_print", m_Bx_print );

    m_builder->get_widget( "RB_print_current", m_RB_current );
    m_builder->get_widget( "RB_print_filtered", m_RB_filtered );
    m_builder->get_widget( "CB_one_entry_per_page", m_CB_one_entry_per_page );

    m_builder->get_widget( "FB_print", m_FB );

    m_builder->get_widget( "CB_use_theme_font", m_CB_theme_font );
    m_builder->get_widget( "CB_use_theme_colors", m_CB_theme_colors );

    m_builder->get_widget( "RB_page_margin_off", m_RB_margin_off );
    m_builder->get_widget( "RB_page_margin_half", m_RB_margin_half );

    m_RB_current->signal_state_changed().connect( update_widgetry );
    m_CB_theme_font->signal_state_changed().connect( update_widgetry );
    m_CB_theme_colors->signal_state_changed().connect( update_widgetry );

    return m_Bx_print;
}

void
PrintOpr::on_custom_widget_apply( Gtk::Widget* )
{
    m_parser.m_opt_one_entry = m_RB_current->get_active();
    m_parser.m_opt_filtered_only = m_RB_filtered->get_active();

    m_parser.m_opt_entries_at_page_start = m_CB_one_entry_per_page->get_active();

    m_parser.m_opt_use_theme_font = m_CB_theme_font->get_active();
    if( m_parser.m_opt_use_theme_font == false )
        m_parser.m_font = Pango::FontDescription( m_FB->get_font_name() );

    m_parser.m_opt_use_theme_colors = m_CB_theme_colors->get_active();

    m_parser.m_opt_margin = m_RB_margin_off->get_active() ? 0.0 :
                                ( m_RB_margin_half->get_active() ? 0.25 : 0.5 );
}

void
PrintOpr::show_page_setup()
{
    // Show the page setup dialog, asking it to start with the existing settings:
    Glib::RefPtr< Gtk::PageSetup > new_page_setup =
            Gtk::run_page_setup_dialog( *AppWindow::p, m_refPageSetup, m_refSettings );

    //Save the chosen page setup dialog for use when printing, previewing, or
    //showing the page setup dialog again:
    m_refPageSetup = new_page_setup;
}

void
PrintOpr::parse()
{
    m_parser.init();

    // diary title:
    if( m_parser.m_opt_one_entry == false )
    {
        m_parser.add_diary_cover();
        m_content.splice( m_content.end(), m_parser.m_content );
    }

    while( m_iter_entry != m_iter_entry_end )
    {
        auto entry = m_iter_entry->second;

        if( m_parser.m_opt_one_entry == false && m_iter_chapter != m_chapters->rend() )
        {
            auto chapter = m_iter_chapter->second;
            if( entry->get_date() >= chapter->get_date() &&
                // do not print the previous chapter again:
                chapter->get_date_t() > m_last_chapter_date )
            {
                m_parser.add_entry( chapter );
                m_content.splice( m_content.end(), m_parser.m_content );

                m_last_chapter_date = chapter->get_date_t();
                m_iter_chapter++;
                continue;
            }
        }

        if( not( m_parser.m_opt_filtered_only && entry->get_filtered_out() ) )
        {
            m_parser.add_entry( entry );
            m_content.splice( m_content.end(), m_parser.m_content );
        }

        m_iter_entry++;
    }

    // TODO print remaining chapters if any

    set_n_pages( m_parser.m_n_pages );
}

void
PrintOpr::on_begin_print( const Glib::RefPtr< Gtk::PrintContext >& print_context )
{
    s_print_context = print_context;

    clear_content();
    init_variables();

    parse();
}

void
PrintOpr::on_draw_page( const Glib::RefPtr< Gtk::PrintContext >& print_context, int page_no )
{
    s_print_context = print_context; // does it change after on_begin_print?

    int i_page{ -1 };

    for( Printable* printable : m_content )
    {
        if( printable->get_type() == Printable::Type::PT_PAGE_START )
            i_page++;

        if( i_page == page_no )
            printable->render();
        else if( i_page > page_no )
            break;
    }
}

void
PrintOpr::on_status_changed()
{
    if( is_finished() )
    {
        print_info( "Print job completed" );
    }
    else
    {
        print_info( get_status_string() );
    }
}

void
PrintOpr::on_done( Gtk::PrintOperationResult result )
{
    //Printing is "done" when the print data is spooled.

    if( result == Gtk::PRINT_OPERATION_RESULT_ERROR )
    {
        Gtk::MessageDialog err_dialog( *AppWindow::p, "Printing failed", false,
                Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true );
        err_dialog.run();
    }
    else
    if( result == Gtk::PRINT_OPERATION_RESULT_APPLY )
    {
        // Update PrintSettings with the ones used in this PrintOperation:
        m_refSettings = get_print_settings();
    }

   /* if( ! is_finished())
    {
    // We will connect to the status-changed signal to track status
    // and update a status bar. In addition, you can, for example,
    // keep a list of active print operations, or provide a progress dialog.
        signal_status_changed().connect( sigc::bind( sigc::mem_fun( this,
                    &PrintOpr::handle_print_status_changed ),
                operation ) );
    }*/
}

// PANGO ENTRY PARSER ==============================================================================
void
EntryParserPango::init()
{
    // margins are 1/2" on each side when full
    m_margin_x = PrintOpr::s_print_context->get_dpi_x() * m_opt_margin;
    m_margin_y = PrintOpr::s_print_context->get_dpi_y() * m_opt_margin;
    m_page_w = PrintOpr::s_print_context->get_width() - ( 2 * m_margin_x );
    m_page_h = PrintOpr::s_print_context->get_height() - ( 2 * m_margin_y );
    m_max_thumbnail_w = ( PrintOpr::s_print_context->get_width() / 2.5 );

    auto&& layout{ PrintOpr::s_print_context->create_pango_layout() };
    layout->set_width( m_page_w * Pango::SCALE );
    layout->set_font_description( m_font );
    layout->set_markup( "XXX" );
    m_todo_icon_h = layout->get_iter().get_line()->get_pixel_logical_extents().get_height();
    m_todo_icon_h *= 0.8;

    m_total_h_cur = m_page_h;   // to force add_page() at the beginning
    m_entry_break_h = PrintOpr::s_print_context->get_dpi_y() * 2.0; // 2" break
    m_unit_indent_w = PrintOpr::s_print_context->get_dpi_x() * 0.5; // 1/2" indent
    m_n_pages = 0;

    // when multiple entries will be printed set theme to default for the cover
    if( m_opt_use_theme_colors == false || m_opt_one_entry == false )
        m_theme = ThemeSystem::get();
}

void
EntryParserPango::add_diary_cover()
{
    add_page();

    m_alignment = Pango::ALIGN_CENTER;
    m_total_h_cur = m_page_h / 1.9;

    if( m_opt_use_theme_font )
        m_font = ThemeSystem::get()->font;

    add_printable( new PrintableImage( Diary::d->get_icon32(),
                                       m_margin_x + m_page_w / 2 - 16, m_total_h_cur ));

    m_marked_up_text = STR::compose( "<span size='xx-large'>",
                                     Glib::Markup::escape_text( Diary::d->get_name() ),
                                     "</span>" );
    add_text( 0.0 );

    m_marked_up_text = Date::format_string( Date::get_today() );
    add_text( 0.0 );

    m_total_h_cur = m_page_h;   // to force add_page() at the beginning
}

void
EntryParserPango::add_entry( const Entry* entry )
{
    m_format_map_offset = 0;
    m_content.clear();
    m_source_text = entry->get_text();
    m_format_map.clear();
    m_format_map.assign( m_source_text.length() + 1, 0 ); // +1 is for the file end
    m_ptr2entry = entry;
    if( m_opt_use_theme_font )
        m_font = entry->get_theme()->font;
    if( m_opt_use_theme_colors )
        m_theme = m_ptr2entry->get_theme();

    // Do the parsing
    ParserText::parse( 0, m_source_text.length() );

    // ENTRY ICON
    Icon&& pixbuf{ m_ptr2entry->get_icon32()->scale_simple(
            m_todo_icon_h * 2, m_todo_icon_h * 2, Gdk::INTERP_BILINEAR ) };
    add_printable( new PrintableImage( pixbuf, m_margin_x, m_total_h_cur ) );

    // ENTRY DATE
    if( m_ptr2entry->get_date().is_hidden() == false )
    {
        const auto t_total_h = m_total_h_cur;
        m_total_h_cur -= 2; // some margin
        m_alignment = Pango::ALIGN_LEFT;
        m_indent_level_cur = 0;
        m_marked_up_text = Ustring::compose( "<b>%1</b>", entry->get_date().format_string() );
        add_text( m_todo_icon_h * 2.4, true ); // to the right of the icon
        m_total_h_cur = t_total_h;
    }

    // ENTRY PARAGRAPHS
    for( auto& para : *( entry->get_paragraphs() ) )
    {
        // Set params
        UstringSize i{ para->m_indentation_level };
        const UstringSize para_size{ para->get_size() };
        m_format_prev = 0;
        m_alignment = para->get_pango_alignment();
        m_indent_level_cur = para->m_indentation_level;

        if( add_todo_icon( para->get_todo_status() ) )
            i += 4;

        // <= is for handling the file end:
        for( ; i <= para_size; i++ )
        {
            m_format_cur = m_format_map[ i + m_format_map_offset ];

            if( m_format_cur & CS_SKIP )
                continue;

            if( m_format_cur != m_format_prev )
            {
                append_markups();

                switch( m_format_cur & CF_FILTER )
                {
                    case CF_IMAGE:
                        m_alignment = Pango::ALIGN_CENTER;
                        add_image( get_format_substr( i + m_format_map_offset, CF_IMAGE ) );
                        i = para_size; // terminate the loop
                        break;
                    case CF_CHART:
                        add_chart( get_format_substr( i + m_format_map_offset, CF_CHART ) );
                        i = para_size; // terminate the loop
                        break;
                    case CF_TAG:
                        // TODO: draw tag form around it (this is not so easy)
                        break;
                }
            } // end of if( format_curr != format_prev )

            // do not print the last char which is usually the \n
            if( i < para_size )
            {
                m_marked_up_text += Glib::Markup::escape_text(
                        m_source_text.substr( i + m_format_map_offset, 1 ) );
                m_format_prev = m_format_cur;
            }
        } // end of char loop

        add_text( 0.0 );    // add paragraph

        m_format_map_offset += ( para_size + 1 ); // +1 is to account for the \n
    } // end of para loop

    add_entry_break();
}

inline void
EntryParserPango::add_page()
{
    m_content.push_back( new PrintablePageStart( m_theme->color_base ) );
    m_n_pages++;
    m_total_h_cur = m_margin_y;
}

inline void
EntryParserPango::add_printable( Printable* printable, bool flag_adds_to_h )
{
    if( m_total_h_cur + printable->get_height() > m_page_h )
    {
        const auto prev_offset{ printable->m_y - m_total_h_cur};
        add_page();
        printable->m_y = m_total_h_cur + prev_offset;
    }

    // do not add empty lines to the top of pages
    if( printable->m_y == m_margin_y && printable->is_empty() )
        delete printable;
    else
    {
        m_content.push_back( printable );

        if( flag_adds_to_h )
            m_total_h_cur += printable->get_height();
    }
}

inline void
EntryParserPango::add_entry_break()
{
    if( m_opt_entries_at_page_start || m_total_h_cur + m_entry_break_h > m_page_h )
        m_total_h_cur = m_page_h;   // so any new printable will cause an add_page()
    else
        m_total_h_cur += m_entry_break_h;
}

void
EntryParserPango::add_text( double px_indent, bool align_bottom )
{
    px_indent += ( m_indent_level_cur * m_unit_indent_w );
    auto&& layout{ PrintOpr::s_print_context->create_pango_layout() };
    layout->set_width( ( m_page_w - px_indent ) * Pango::SCALE );
    layout->set_font_description( m_font );
    layout->set_markup( m_marked_up_text );
    layout->set_alignment( m_alignment );

#if( ( PANGOMM_MAJOR_VERSION > 2 ) || \
     ( ( PANGOMM_MAJOR_VERSION == 2 ) && ( PANGOMM_MINOR_VERSION >= 28 ) ) )
    Pango::LayoutIter iter( layout->get_iter() );
#else
    Pango::LayoutIter iter;
    layout->get_iter( iter );
#endif

    do
    {
        auto&& layout_line{ iter.get_line() };
        const auto layout_w{ layout_line->get_pixel_logical_extents().get_width() };
        double x{ m_margin_x + px_indent };

        switch( m_alignment )
        {
            case Pango::ALIGN_LEFT:     break;
            case Pango::ALIGN_CENTER:   x += ( ( m_page_w - px_indent - layout_w ) / 2 ); break;
            case Pango::ALIGN_RIGHT:    x += ( m_page_w - px_indent - layout_w ); break;
        }

        add_printable( new PrintableTextLine( layout_line, m_marked_up_text.empty(),
                                              x, m_total_h_cur, align_bottom,
                                              m_theme->color_text ) );
    }
    while( iter.next_line() );

    m_marked_up_text.clear();
}

inline void
EntryParserPango::add_image( const std::string&& uri )
{
    if( not( m_marked_up_text.empty() ) )
        add_text( 0.0 );

    auto&& pixbuf { Diary::d->get_image( uri, m_max_thumbnail_w ) };
    double x      { m_margin_x + m_page_w / 2 };

    switch( m_alignment )
    {
        case Pango::ALIGN_LEFT:     break;
        case Pango::ALIGN_CENTER:   x -= ( pixbuf->get_width() / 2 ); break;
        case Pango::ALIGN_RIGHT:    x -= pixbuf->get_width(); break;
    }

    add_printable( new PrintableImage( pixbuf, x, m_total_h_cur ) );
}

inline void
EntryParserPango::add_chart( const std::string&& chart_name )
{
    if( not( m_marked_up_text.empty() ) )
        add_text( 0.0 );

    ChartElem* chart{ Diary::d->get_chart( chart_name ) };
    if( chart )
    {
        try
        {
            auto surface{ new ChartSurface( chart, m_max_thumbnail_w * 1.5 ) };
            auto&& pixbuf{ surface->get_pixbuf() };
            const double x{ m_margin_x + m_page_w / 2 - ( pixbuf->get_width() / 2 ) };
            add_printable( new PrintableImage( pixbuf, x, m_total_h_cur ) );
            delete surface;
        }
        catch( ... )
        {
            print_error( "Could not add chart to the page!" );
        }
    }
}

inline bool
EntryParserPango::add_todo_icon( const int&& todo_status )
{
    if( todo_status == ES::NOT_TODO )
        return false;

    Icon&& pixbuf{ Lifeograph::get_todo_icon( todo_status )->scale_simple(
            m_todo_icon_h, m_todo_icon_h, Gdk::INTERP_BILINEAR ) };

    add_printable( new PrintableImage(
            pixbuf,
            m_margin_x + m_indent_level_cur * m_unit_indent_w - m_todo_icon_h * 1.2,
            m_total_h_cur + ( m_todo_icon_h * 0.2 ) ),
        false ); // do not increment total_height

    return true;
}

void
EntryParserPango::append_markups()
{
    // close last
    if( ( m_format_prev & CS_MONOSPACE ) == CS_MONOSPACE )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_LINK ) == CS_LINK )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & ( CS_STRIKETHROUGH|CS_COMMENT ) ) == CS_STRIKETHROUGH )
        m_marked_up_text += "</s>";

    if( ( m_format_prev & CS_TODO_FILTER ) == CS_TODO_DONE )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_INLINE_TAG ) == CS_INLINE_TAG )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_HIGHLIGHT ) == CS_HIGHLIGHT )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_ITALIC ) == CS_ITALIC )
        m_marked_up_text += "</i>";

    if( ( m_format_prev & CS_BOLD ) == CS_BOLD )
        m_marked_up_text += "</b>";

    if( ( m_format_prev & CS_COMMENT ) == CS_COMMENT )
        m_marked_up_text += "</span></sup>";

    if( m_format_prev & CS_HEADING ) // works for any heading type
        m_marked_up_text += "</span></b>";

    // open new (in reverse order)
    if( ( m_format_cur & CS_HEADING_FILTER ) == CS_HEADING )
        m_marked_up_text += STR::compose(
                "<b><span size='xx-large' color='",
                convert_gdkcolor_to_html( m_theme->color_heading ), "'>" );

    if( ( m_format_cur & CS_HEADING_FILTER ) == CS_SUBHEADING )
        m_marked_up_text += STR::compose(
                "<b><span size='x-large' color='",
                convert_gdkcolor_to_html( m_theme->color_subheading ), "'>" );

    if( ( m_format_cur & CS_HEADING_FILTER ) == CS_SUBSUBHEADING )
        m_marked_up_text += STR::compose(
                "<b><span size='larger' color='",
                convert_gdkcolor_to_html( m_theme->get_color_subsubheading() ), "'>" );

    if( ( m_format_cur & CS_COMMENT ) == CS_COMMENT )
        m_marked_up_text += STR::compose(
                "<sup><span size='smaller' color='",
                convert_gdkcolor_to_html( m_theme->get_color_mid() ), "'>" );

    if( ( m_format_cur & CS_BOLD ) == CS_BOLD )
        m_marked_up_text += "<b>";

    if( ( m_format_cur & CS_ITALIC ) == CS_ITALIC )
        m_marked_up_text += "<i>";

    if( ( m_format_cur & CS_HIGHLIGHT ) == CS_HIGHLIGHT )
        m_marked_up_text += STR::compose(
                "<span bgcolor='", convert_gdkcolor_to_html( m_theme->color_highlight ), "'>" );

    if( ( m_format_cur & CS_INLINE_TAG ) == CS_INLINE_TAG )
        m_marked_up_text += STR::compose(
                "<span bgcolor='", convert_gdkcolor_to_html( m_theme->get_color_inline_tag() ),
                "' font='Monospace'>" );

    if( ( m_format_cur & CS_TODO_FILTER ) == CS_TODO_DONE )
        m_marked_up_text += STR::compose(
                "<span color='", convert_gdkcolor_to_html( m_theme->get_color_done() ),
                "' bgcolor='", convert_gdkcolor_to_html( m_theme->get_color_done_bg() ), "'>" );

    // disable strikethrough in comments
    if( ( m_format_cur & ( CS_STRIKETHROUGH|CS_COMMENT ) ) == CS_STRIKETHROUGH )
        m_marked_up_text += "<s>";

    if( ( m_format_cur & CS_LINK ) == CS_LINK )
        m_marked_up_text += STR::compose(
                "<span color='", convert_gdkcolor_to_html( m_theme->get_color_link() ),
                "' underline='single'>" );

    if( ( m_format_cur & CS_MONOSPACE ) == CS_MONOSPACE )
        m_marked_up_text += "<span font='Monospace'>";
}

// PARSING METHODS
void
EntryParserPango::apply_markup( int format )
{
    m_format_map[ m_recipe_cur->m_pos_bgn ] |= CS_SKIP;
    m_format_map[ m_pos_cur ] |= CS_SKIP;

    apply_format( format, m_recipe_cur->m_pos_bgn, m_pos_cur );
}

void
EntryParserPango::apply_heading()
{
    apply_format( ( m_ptr2entry->get_status() & ES::CANCELED ) ?
                        CS_HEADING|CS_STRIKETHROUGH : CS_HEADING, 0, get_para_end( 0 ) );
}

void
EntryParserPango::apply_subheading()
{
    apply_format( CS_SUBHEADING, m_recipe_cur->m_pos_bgn, get_para_end( m_recipe_cur->m_pos_bgn ) );
}

void
EntryParserPango::apply_subsubheading()
{
    apply_format( CS_SUBSUBHEADING,
                  m_recipe_cur->m_pos_bgn, get_para_end( m_recipe_cur->m_pos_bgn ) );
}

void
EntryParserPango::apply_bold()
{
    apply_markup( CS_BOLD );
}

void
EntryParserPango::apply_italic()
{
    apply_markup( CS_ITALIC );
}

void
EntryParserPango::apply_strikethrough()
{
    apply_markup( CS_STRIKETHROUGH );
}

void
EntryParserPango::apply_highlight()
{
    apply_markup( CS_HIGHLIGHT );
}

void
EntryParserPango::apply_comment()
{
    apply_markup( m_opt_hide_comments ? CS_SKIP : CS_COMMENT );
}

void
EntryParserPango::apply_check_ccl()
{
    apply_format( CS_STRIKETHROUGH, m_pos_cur, get_para_end( m_pos_cur ) );
}

void
EntryParserPango::apply_check_unf()
{
    apply_format( CS_BOLD, m_pos_cur, get_para_end( m_pos_cur ) );
}

void
EntryParserPango::apply_check_fin()
{
    apply_format( CS_TODO_DONE, m_pos_cur, get_para_end( m_pos_cur ) );
}

void
EntryParserPango::apply_link()
{
    if( m_recipe_cur->m_id == RID_DATE )
        return;

    auto begin{ m_recipe_cur->m_pos_bgn };
    Glib::RefPtr< Gdk::Pixbuf > pixbuf;
    bool flag_ends_para{ m_pos_cur == m_source_text.length() };
    if( !flag_ends_para )
        flag_ends_para = ( get_char_at( m_pos_cur ) == '\n' );

    // there can be no text on the same line as the image:
    if( begin > 0 && get_char_at( begin - 1 ) == '\n' && flag_ends_para )
    {
        const std::string&& uri{ m_source_text.substr( begin, m_pos_cur - begin ) };

        if( uri.find( "file://" ) == 0 || uri.find( "rel://" ) == 0 )
        {
            try
            {
                pixbuf = Diary::d->get_image( uri, m_max_thumbnail_w );
                PRINT_DEBUG( "Image loaded to thumbnail map" );
            }
            catch( ... )
            {
                PRINT_DEBUG( "Could not load image" );
            }
        }
    }

    if( pixbuf )
        apply_format( CF_IMAGE, begin, m_pos_cur );
    else
        apply_format( CS_LINK, begin, m_pos_cur );
}

void
EntryParserPango::apply_link_hidden( )
{
    auto pos_label( m_recipe_cur->m_pos_mid + 1 );

    apply_format( CS_SKIP, m_recipe_cur->m_pos_bgn, pos_label );
    apply_format( CS_SKIP, m_pos_cur, m_pos_cur + 1 );
    apply_format( CS_LINK, pos_label, m_pos_cur );
}

void
EntryParserPango::apply_chart()
{
    apply_format( CS_SKIP, m_recipe_cur->m_pos_bgn, m_recipe_cur->m_pos_bgn + 6 );
    apply_format( CF_CHART, m_recipe_cur->m_pos_bgn + 6, m_pos_cur );
    // +6 is to eliminate the chart: part
}

void
EntryParserPango::apply_inline_tag()
{
    // m_pos_middle is used to determine if a value is assigned to the tag
    const auto pos_b{ m_recipe_cur->m_pos_bgn + 1 };
    const auto pos_e{ m_recipe_cur->m_pos_mid > 0 ? m_recipe_cur->m_pos_mid - 1 : m_pos_cur - 1 };

    if( ! Diary::d->get_entries_by_name( m_source_text.substr( pos_b, pos_e - pos_b ) ).empty() )
    {
        if( m_recipe_cur->m_pos_mid == 0 )
        {
            m_format_map[ m_recipe_cur->m_pos_bgn ] |= CS_SKIP;
            m_format_map[ pos_e ] |= CS_SKIP;

            apply_format( CF_TAG|CS_INLINE_TAG, pos_b, pos_e );
        }
        else  // the value part
            apply_format( CS_ITALIC, m_recipe_cur->m_pos_mid + 1, m_pos_cur );
    }
}

inline void
EntryParserPango::apply_format( int format, UstringSize begin, UstringSize end )
{
    for( auto i = begin; i< end; i++ )
        m_format_map[ i ] |= format;
}

inline Ustring
EntryParserPango::get_format_substr( UstringSize begin, int format )
{
    Ustring subtext;
    for( UstringSize size = 0; ( begin + size ) < m_format_map.size(); size++ )
        if( ( m_format_map[ begin + size ] & ( format|0xF ) ) != format )
            break;
        else
            subtext += m_source_text[ begin + size ];

    return subtext;
}
