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

    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_WIDGET_TEXTVIEW_DIARY_EDIT_HEADER
#define LIFEOGRAPH_WIDGET_TEXTVIEW_DIARY_EDIT_HEADER


#include "../undo.hpp"
#include "widget_textview.hpp"


namespace LIFEO
{

class TextbufferDiaryEdit;  // forward declaration
class TextviewDiaryEdit;    // forward declaration

// UNDO ============================================================================================
class UndoEdit : public Undoable
{
    public:
                            UndoEdit( int position,
                                      const Ustring& text,
                                      UndoableType type,
                                      TextbufferDiaryEdit* buffer )
        : Undoable( "placeholder", type ), m_position( position ), m_text( text ),
          m_ptr2buffer( buffer ) { }
        virtual             ~UndoEdit() {}   //needed because of virtual methods

        unsigned int        get_position() const
        {
            return m_position;
        }

        Ustring             get_text() const
        {
            return m_text;
        }

    protected:
        virtual void        merge( Undoable* ) = 0;

        virtual void        undo() = 0;
        virtual void        redo() = 0;

        void                insert();
        void                erase();

        unsigned int        m_position;
        Ustring             m_text;

    private:
        TextbufferDiaryEdit*    m_ptr2buffer;

    friend class TextbufferDiaryEdit;
};

class UndoInsert :public UndoEdit
{
    public:
                            UndoInsert( int pos, const Ustring& text, TextbufferDiaryEdit* buffer )
        : UndoEdit( pos, text, UT_INSERT_TEXT, buffer ) { }

        bool                can_merge( const Undoable* action ) const
        {
            if( ( action->get_time_sec() - m_time.tv_sec ) < UNDO_MERGE_TIMEOUT )
                if( action->get_type() == m_type )
                    if( dynamic_cast< const UndoInsert* >( action )->get_position()
                            == m_position + m_text.length() )
                    return true;
            return false;
        }

        void                merge( Undoable* action )
        {
            m_text += dynamic_cast< UndoInsert* >( action )->get_text();
        }

    protected:
        void                undo()
        {
            erase();
        }
        void                redo()
        {
            insert();
        }
};

class UndoErase : public UndoEdit
{
    public:
                            UndoErase( int pos, const Ustring& text, TextbufferDiaryEdit* buffer )
        : UndoEdit( pos, text, UT_ERASE_TEXT, buffer ) { }

        bool                can_merge( const Undoable* action ) const
        {
            if ( ( action->get_time_sec() - m_time.tv_sec ) < UNDO_MERGE_TIMEOUT )
                if ( action->get_type() == m_type )
                {
                    const UndoErase * action_erase =
                        dynamic_cast< const UndoErase* >( action );
                    if ( action_erase->get_position() +
                            action_erase->get_text().length()
                                == m_position )
                        return true;
                }
            return false;
        }

        void                merge( Undoable* action )
        {
            UndoErase * action_erase = dynamic_cast< UndoErase* >( action );
            m_text.insert( 0, action_erase->get_text() );
            m_position = action_erase->get_position();
        }

    protected:
        void                undo()
        {
            insert();
        }
        void                redo()
        {
            erase();
        }
};

// LINKS ===========================================================================================
class LinkCheck : public Link
{
    public:
                                    LinkCheck( TextviewDiaryEdit*,
                                               const Glib::RefPtr< Gtk::TextMark >&,
                                               const Glib::RefPtr< Gtk::TextMark >&,
                                               char = ' ' );
        void                        go();

        char                        m_state;

    protected:
        TextviewDiaryEdit*          m_ptr2TvDE;
};

class TextviewDiaryEdit;    // forward declaration

// TEXTBUFFEREDIT ==================================================================================
class TextbufferDiaryEdit : public TextbufferDiary
{
    public:
        TextbufferDiaryEdit();

        Ustring                     get_text_to_save();

        void                        set_language( std::string&& ) override;

        void                        toggle_format( Glib::RefPtr< Tag >, const Ustring& );
        void                        toggle_bold();
        void                        toggle_italic();
        void                        toggle_highlight();
        void                        toggle_strikethrough();
        void                        set_check( char );
        void                        set_justification( JustificationType );

        void                        replace_itag_at_cursor( const Entry* );
        void                        replace_word_at_cursor( const Ustring& );

        void                        indent_paragraphs();
        void                        unindent_paragraphs();
        void                        set_list_item( char );
        void                        add_empty_line_above();
        void                        remove_empty_line_above();
        void                        open_paragraph_below();
        void                        move_line_up();
        void                        move_line_down();
        void                        delete_paragraphs();
        void                        duplicate_paragraphs();

        void                        insert_comment();
        void                        insert_with_spaces( Ustring&& );
        void                        insert_link( DiaryElement* );
        void                        insert_tag( Entry* );
        void                        insert_image();
        void                        insert_date_stamp( bool = false );

        void                        paste_clipboard2()
        {
            m_flag_paste_operation = true;
            paste_clipboard( Gtk::Clipboard::get() );
            // m_flag_paste_operation is unset in on_insert()
        }

        void                        go_to_link();

    protected:
        bool                        set_editable( bool );

        void                        on_insert( const Gtk::TextIter&,
                                               const Ustring&, int ) override;
        void                        on_erase( const Gtk::TextIter&,
                                              const Gtk::TextIter& ) override;

        void                        on_mark_set( const Gtk::TextIter&,
                                                 const Glib::RefPtr< TextBuffer::Mark >& ) override;

        void                        handle_space();
        bool                        handle_minus();
        bool                        handle_new_line();
        bool                        increment_numbered_line( Gtk::TextIter&, int );

        void                        update_todo_status() override;
        void                        add_link_check( Gtk::TextIter&, Gtk::TextIter&, char ) override;
        void                        set_list_item_internal( Gtk::TextIter&, Gtk::TextIter&, char );

        void                        copy_image_file_to_rel();

        // SPELL CHECKING
        void                        check_word() override;
        void                        handle_spellcheck_toggled();
        void                        add_spell_suggestions( const Ustring&, Gtk::Box* );
        void                        ignore_misspelled_word( const Ustring& );
        void                        replace_misspelled_word( const Ustring& );
        void                        add_word_to_dictionary( const Ustring& );

        EnchantDict*                m_enchant_dict{ nullptr };
        int                         m_spell_suggest_offset{ 0 };
        int                         m_insert_offset{ 0 };

        // OTHER VARIABLES
        bool                        m_flag_paste_operation{ false };
        bool                        m_flag_undo_operation{ false };
        TextviewDiaryEdit*          m_ptr2TvDE{ nullptr };

        SignalVoid                  m_Sg_changed;

    friend class UIEntry;
    friend class TextviewDiaryEdit;
    friend class UndoEdit;
};

// TEXTVIEWEDIT ====================================================================================
class TextviewDiaryEdit : public TextviewDiary
{
    public:
        //TextviewDiaryEdit(); not used at the moment
        TextviewDiaryEdit( BaseObjectType*, const Glib::RefPtr<Gtk::Builder>& );
        ~TextviewDiaryEdit();

        TextbufferDiaryEdit*        get_buffer()
        { return m_buffer_edit; }

        bool                        is_editor() const override { return true; }
        bool                        is_derived() const override { return true; }

        void                        set_entry( Entry* );
        void                        unset_entry()
        { set_editable( false ); m_buffer_edit->unset_entry(); clear_undo_data(); }

        void                        set_editable( bool );

        void                        show_Po_context( int, int = 0 );
        void                        show_Po_completion();
        void                        show_Po_check();
        int                         get_Po_check_pos() const
        { return m_Po_check_pos; }

        void                        update_highlight_button();

        void                        replace_match( Match&, const Ustring& );

        bool                        can_undo()
        { return( m_p2undo_manager && m_p2undo_manager->can_undo() ); }
        bool                        can_redo()
        { return( m_p2undo_manager && m_p2undo_manager->can_redo() ); }
        void                        undo();
        void                        redo();
        void                        clear_undo_data();

        SignalVoid                  signal_changed()
        { return m_buffer_edit->m_Sg_changed; }

        Gtk::Popover*               m_Po_context{ nullptr };
        EntryPickerCompletion*      m_completion{ nullptr };
        Gtk::Popover*               m_Po_checklist{ nullptr };

    protected:
        void                        on_size_allocate( Gtk::Allocation& ) override;
        bool                        on_button_press_event( GdkEventButton* ) override;
        //bool                        on_button_release_event( GdkEventButton* ) override;
        bool                        on_key_press_event( GdkEventKey* ) override;
        bool                        on_key_release_event( GdkEventKey* ) override;
        void                        on_drag_data_received( const Glib::RefPtr< Gdk::DragContext >&,
                                                           int, int, const Gtk::SelectionData&,
                                                           guint, guint ) override;

        TextbufferDiaryEdit*        m_buffer_edit;

        // TODO separate stack from UndoManager. There should be only one manager but many stacks.
        UndoManager*                m_p2undo_manager{ nullptr };
        std::map< DEID, UndoManager* >  m_undo_managers;

        Gtk::Box*                   m_Bx_context_edit_1;
        Gtk::Box*                   m_Bx_context_edit_2;
        Gtk::Button*                m_B_expand_sel;
        Gtk::Button*                m_B_cut;
        Gtk::Button*                m_B_copy;
        Gtk::Button*                m_B_paste;
        Gtk::Button*                m_B_insert_emoji;
        Gtk::Button*                m_B_insert_image;
        Gtk::ModelButton*           m_MoB_copy_img_to_rel;
        Gtk::Box*                   m_Bx_spell_separator;
        Gtk::Box*                   m_Bx_spell;

        Gtk::Button*                m_B_undo;
        Gtk::Button*                m_B_redo;
        Gtk::Button*                m_B_bold;
        Gtk::Button*                m_B_italic;
        Gtk::Button*                m_B_strkthru;
        Gtk::Button*                m_B_highlight;
        Gtk::Label*                 m_L_highlight;
        Gtk::EventBox*              m_EB_highlight;      // necessary to change bg color!!
        Gtk::RadioButton*           m_RB_justify_left;
        Gtk::RadioButton*           m_RB_justify_center;
        Gtk::RadioButton*           m_RB_justify_right;
        Gtk::RadioButton*           m_RB_list_none;
        Gtk::RadioButton*           m_RB_list_bllt;
        Gtk::RadioButton*           m_RB_list_todo;
        Gtk::RadioButton*           m_RB_list_prog;
        Gtk::RadioButton*           m_RB_list_done;
        Gtk::RadioButton*           m_RB_list_cncl;

        Gtk::RadioButton*           m_RB_check_open;
        Gtk::RadioButton*           m_RB_check_prog;
        Gtk::RadioButton*           m_RB_check_done;
        Gtk::RadioButton*           m_RB_check_cncl;
        int                         m_Po_check_pos;

    friend class TextbufferDiaryEdit;
};

} // end of namespace LIFEO

#endif
