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

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

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


#ifndef LIFEOGRAPH_TEXTVIEW_DIARY_HEADER
#define LIFEOGRAPH_TEXTVIEW_DIARY_HEADER


#include <deque>

extern "C"
{
#include <enchant.h>
}

#include "../helpers.hpp"
#include "../diarydata.hpp"
#include "../entry.hpp"
#include "../parser_text.hpp"
#include "widget_entrypicker.hpp" // for completion


namespace LIFEO
{

const int       UNDO_MERGE_TIMEOUT = 3;

// LINKS ===========================================================================================
enum LinkStatus
{
    LS_OK,
    LS_ENTRY_UNAVAILABLE,
    LS_INVALID,     // separator: to check a valid entry link: linkstatus < LS_INVALID
    LS_CYCLIC,

    LS_FILE_OK,
    LS_FILE_INVALID,
    LS_FILE_UNAVAILABLE,
    LS_FILE_UNKNOWN
};

class TextbufferDiary;  // forward declaration
class TextviewDiary;    // forward declaration

class Link
{
    public:
        enum LinkType
        {
            LT_NONE, LT_ID, LT_DATE, LT_TAG, LT_URI, LT_CHECK, LT_IMAGE, LT_THEME
        };

        virtual                     ~Link();
        virtual void                go() = 0;

        // position of link in the buffer:
        Glib::RefPtr< Gtk::TextMark >
                                    m_mark_start;
        Glib::RefPtr< Gtk::TextMark >
                                    m_mark_end;

        const LinkType              type;

    protected:
                                    Link( const Glib::RefPtr< Gtk::TextMark >&,
                                          const Glib::RefPtr< Gtk::TextMark >&,
                                          LinkType );
};

class LinkID : public Link
{
    public:
        typedef sigc::signal< void, Date >
                                    Signal_void_Date;

                                    LinkID( const Glib::RefPtr< Gtk::TextMark >&,
                                            const Glib::RefPtr< Gtk::TextMark >&,
                                            DEID );
        void                        go();

        static Signal_void_Date     signal_activated()
        { return m_signal_activated; }

        DEID                        m_id;

    protected:
        static Signal_void_Date     m_signal_activated;
};

class LinkDate : public Link
{
    public:
                                    LinkDate( const Glib::RefPtr< Gtk::TextMark >&,
                                              const Glib::RefPtr< Gtk::TextMark >&,
                                              Date );
        void                        go();

        Date                        m_date;

    protected:
        static Gtk::Menu*           menu_link;
};

class LinkChart : public Link
{
    public:
                                    LinkChart( const Glib::RefPtr< Gtk::TextMark >&,
                                               const Glib::RefPtr< Gtk::TextMark >&,
                                               const std::string& );
        void                        go();

    protected:
        std::string                 m_uri;
};

class LinkUri : public Link
{
    public:
                                    LinkUri( const Glib::RefPtr< Gtk::TextMark >&,
                                             const Glib::RefPtr< Gtk::TextMark >&,
                                             const std::string&, LinkType, Diary* );
        void                        go();

        bool                        is_rel()
        { return m_uri.find( "rel:" ) == 0; }

        std::string                 m_uri;
        Diary*                      m_ptr2diary;
};

// NEW LINE PREDICATE ==============================================================================
class PredicateNL
{
    public:
        bool operator()( gunichar ch ) const
        {
            return( ch == '\n' );
        }
};

class PredicateBlank
{
    public:
        bool operator()( gunichar ch ) const
        {
            return( ch == '\n' || ch == ' ' || ch == '\t' );
        }
};

// TEXTBUFFER ======================================================================================
class TextbufferDiary : public Gtk::TextBuffer, public ParserText
{
    public:
        static const int            LEFT_MARGIN = 10;

                                    TextbufferDiary();

        Link*                       get_link( int ) const;
        Link*                       get_link( const Gtk::TextIter& ) const;

        Ustring                     get_selected_text() const;
        void                        expand_selection();

        void                        parse( int, int );
        void                        parse_damaged_region( Gtk::TextIter, Gtk::TextIter );
        void                        reparse() { parse( 0, end().get_offset() ); }

    protected:
        void                        set_static_text( const Ustring&, bool = true );
        void                        set_entry( Entry* ); // only call from TextviewDiary
        void                        unset_entry(); // on log out

        virtual void                set_language( std::string&& ) { }

        virtual void                set_theme( const Theme* );
        const Theme*                get_theme() const { return m_p2theme; }

        void                        set_search_str( const Ustring& );

        void                        set_comments_hidden( bool );
        bool                        are_comments_hidden() const
        { return m_tag_comment->property_invisible(); }

        int                         get_text_width( const Pango::FontDescription&,
                                                    const Ustring& );
        int                         get_first_nl_offset()
        {
            auto firstnl = begin();
            if( ! firstnl.forward_find_char( s_predicate_nl ) )
                return end().get_offset();
            return firstnl.get_offset();
        }

        // HELPERS
        void                        calculate_para_bounds( Gtk::TextIter&, Gtk::TextIter& );
        bool                        calculate_sel_word_bounds( Gtk::TextIter&, Gtk::TextIter& );
        bool                        calculate_sel_para_bounds( Gtk::TextIter&, Gtk::TextIter& );
        Ustring                     calculate_word_bounds( Gtk::TextIter&, Gtk::TextIter& );
        bool                        calculate_itag_bounds( Gtk::TextIter&, Gtk::TextIter& );

        // PARSING
        virtual void                reset( UstringSize, UstringSize ) override;
        void                        process_paragraph() override;
        void                        apply_hidden_link_tags( Gtk::TextIter&,
                                                            const Glib::RefPtr< Tag >& );
        virtual void                apply_heading() override;
        void                        apply_subheading() override;
        void                        apply_subsubheading() override;
        void                        apply_bold() override;
        void                        apply_italic() override;
        void                        apply_strikethrough() override;
        void                        apply_highlight() override;
        void                        apply_comment() override;
        void                        apply_ignore() override;
        virtual void                apply_link_hidden() override;
        virtual void                apply_link() override;
        virtual void                apply_date();
        void                        apply_check( Glib::RefPtr< Gtk::TextTag >*,
                                                 Glib::RefPtr< Gtk::TextTag >*,
                                                 char );
        virtual void                add_link_check( Gtk::TextIter&, Gtk::TextIter&, char ) {}
        void                        apply_check_ccl() override;
        void                        apply_check_unf() override;
        void                        apply_check_prg() override;
        void                        apply_check_fin() override;
        virtual void                apply_chart() override;

        virtual void                apply_match() override;
        void                        apply_indent() override;

        virtual void                apply_inline_tag() override;

        void                        apply_markup( const Glib::RefPtr< Tag >& );

        Wchar                       get_char_at( int ) override;
        Wchar                       check_and_get_char_at( int );
        bool                        check_cursor_is_in_para();
        virtual void                update_todo_status() {}

        UstringSize                 m_parser_pos_cursor_para_bgn{ 0 };
        UstringSize                 m_parser_pos_cursor_para_end{ 0 };
        UstringSize                 m_pos_cursor{ 0 };

        static PredicateNL          s_predicate_nl;
        static PredicateBlank       s_predicate_blank;

        // LINKS
        void                        clear_links( int, int );
        void                        clear_links();

        bool                        update_thumbnail_width( int );
        void                        reset_markup_visibilities();

        // TAGS
        // TODO: move these program-wide objects out of this class as...
        // ...there may be other instances of TextbufferDiary in the future
        Glib::RefPtr< Tag >         m_tag_heading;
        Glib::RefPtr< Tag >         m_tag_subheading;
        Glib::RefPtr< Tag >         m_tag_subsubheading;
        Glib::RefPtr< Tag >         m_tag_match;
        Glib::RefPtr< Tag >         m_tag_markup;
        Glib::RefPtr< Tag >         m_tag_hidable;
        Glib::RefPtr< Tag >         m_tag_hidden;
        Glib::RefPtr< Tag >         m_tag_hidden_virt; // not invisible but same color as bg
        Glib::RefPtr< Tag >         m_tag_bold;
        Glib::RefPtr< Tag >         m_tag_italic;
        Glib::RefPtr< Tag >         m_tag_strikethrough;
        Glib::RefPtr< Tag >         m_tag_highlight;
        Glib::RefPtr< Tag >         m_tag_comment;
        Glib::RefPtr< Tag >         m_tag_region;
        Glib::RefPtr< Tag >         m_tag_link;
        Glib::RefPtr< Tag >         m_tag_link_broken;
        Glib::RefPtr< Tag >         m_tag_link_hidden;
        Glib::RefPtr< Tag >         m_tag_done;
        Glib::RefPtr< Tag >         m_tag_checkbox_todo;
        Glib::RefPtr< Tag >         m_tag_checkbox_progressed;
        Glib::RefPtr< Tag >         m_tag_checkbox_done;
        Glib::RefPtr< Tag >         m_tag_checkbox_canceled;
        Glib::RefPtr< Tag >         m_tag_inline_tag;
        Glib::RefPtr< Tag >         m_tag_inline_tag_bg;    // background
        Glib::RefPtr< Tag >         m_tag_inline_value;
        Glib::RefPtr< Tag >         m_tag_pixbuf;
        Glib::RefPtr< Tag >         m_tag_misspelled;
        Glib::RefPtr< Tag >         m_tag_justify_left;
        Glib::RefPtr< Tag >         m_tag_justify_center;
        Glib::RefPtr< Tag >         m_tag_justify_right;

        // OTHER VARIABLES
        bool                        m_flag_settext_operation{ false };
        int                         m_ongoing_operation_depth{ 0 }; // Incremental to allow nesting
        bool                        m_flag_parsing{ false };
        bool                        m_flag_apply_heading{ true };
        Entry*                      m_p2entry{ nullptr };
        Diary*                      m_ptr2diary{ nullptr };
        const Theme*                m_p2theme{ nullptr };
        TextviewDiary*              m_ptr2TvD{ nullptr };

        typedef std::list< Link* >  ListLinks;
        ListLinks                   m_list_links;

        int                         m_max_thumbnail_w{ 0 };
        int                         m_text_width_tab{ 0 };
        int                         m_text_width_checkbox{ 0 };
        int                         m_text_width_dash{ 0 };
        int                         m_text_width_dot{ 0 };

    friend class TextviewDiary;
};

// TEXTVIEW ========================================================================================
class TextviewDiary : public Gtk::TextView
{
    public:
        TextviewDiary();
        TextviewDiary( BaseObjectType*, const Glib::RefPtr< Gtk::Builder >& );
        ~TextviewDiary() {}

        TextbufferDiary*            get_buffer()
        { return m_buffer; }

        virtual bool                is_editor() const { return false; }
        virtual bool                is_derived() const { return false; }

        void                        set_static_text( const Ustring& text,
                                                     bool F_apply_heading = true )
        { m_buffer->set_static_text( text, F_apply_heading ); }

        void                        set_theme( const Theme* theme )
        { m_buffer->set_theme( theme ); }
        const Theme*                get_theme() const
        { return m_buffer->get_theme(); }

        void                        set_search_str( const Ustring& str )
        { m_buffer->set_search_str( str ); }

        void                        set_comments_hidden( bool flag )
        { return m_buffer->set_comments_hidden( flag ); }
        bool                        are_comments_hidden() const
        { return m_buffer->are_comments_hidden(); }

        unsigned int                get_word_count() const
        { return m_buffer->m_word_count; }

        bool                        get_selection_rect( Gdk::Rectangle& );

    protected:
        virtual void                init();

        void                        update_link();
        void                        update_link_at_insert();

        virtual bool                on_motion_notify_event( GdkEventMotion* ) override;
        virtual bool                on_button_release_event( GdkEventButton* ) override;
        virtual bool                on_draw( const Cairo::RefPtr< Cairo::Context >& ) override;

        virtual bool                handle_query_tooltip( int, int, bool,
                                                          const Glib::RefPtr< Gtk::Tooltip >& );

        void                        get_string_coordinates( const Gtk::TextIter&,
                                                            const Gtk::TextIter&,
                                                            int&, int&, int&, int& );

        TextbufferDiary*            m_buffer{ nullptr };

        Gdk::CursorType             m_cursor_default{ Gdk::XTERM };
        const Glib::RefPtr< Gdk::Cursor >*
                                    m_ptr2cursor_last{ nullptr };
        Link*                       m_link_hovered{ nullptr };

        bool                        m_flag_set_text_queued{ false };

    friend class TextbufferDiary;
};

} // end of namespace LIFEO

#endif
