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

    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_DIARY_HEADER
#define LIFEOGRAPH_DIARY_HEADER


#include "helpers.hpp" // i18n headers
#include "diarydata.hpp"
#include "chapter.hpp"
#include "filtering.hpp"


namespace LIFEO
{

const char                      DB_FILE_HEADER[] =      "LIFEOGRAPHDB";
const int                       DB_FILE_VERSION_INT     { 2000 };
const int                       DB_FILE_VERSION_INT_MIN { 1010 };
const std::string::size_type    PASSPHRASE_MIN_SIZE     { 4 };
const char                      LOCK_SUFFIX[] =         ".~LOCK~";


class Diary : public DiaryElement
{
    public:
        enum SetPathType { SPT_NORMAL, SPT_READ_ONLY, SPT_NEW };

        static constexpr int LOGGED_OUT         = 0;
        static constexpr int LOGGED_TIME_OUT    = 1;
        static constexpr int LOGGED_IN_RO       = 10;
        static constexpr int LOGGED_IN_EDIT     = 20;

                                Diary();
                                ~Diary();

        static Diary*           d;

        bool                    is_old() const
        { return( m_read_version < DB_FILE_VERSION_INT ); }
        bool                    is_encrypted() const
        { return( ! m_passphrase.empty() ); }
        bool                    is_open()
        { return( m_login_status >= LOGGED_IN_RO ); }
        bool                    is_in_edit_mode() const
        { return( m_login_status == LOGGED_IN_EDIT ); }
        bool                    can_enter_edit_mode() const
        { return( m_flag_read_only == false ); }
        bool                    is_logged_time_out() const
        { return( m_login_status == LOGGED_TIME_OUT ); }
        void                    set_timed_out()
        { m_login_status = LOGGED_TIME_OUT; }

        Result                  init_new( const std::string&, const std::string& = "" );
        virtual void            clear();

        // DISK I/O
        LIFEO::Result           set_path( const std::string&, SetPathType );
        const std::string&      get_path() const;
        bool                    is_path_set() const;
        std::string             convert_rel_uri( std::string );

        LIFEO::Result           enable_editing();

        LIFEO::Result           read_body();
        LIFEO::Result           read_header();

        LIFEO::Result           write();
        LIFEO::Result           write( const std::string& );
        LIFEO::Result           write_copy( const std::string&, const std::string&, bool );
        LIFEO::Result           write_txt( const std::string&, bool );

        bool                    remove_lock_if_necessary();

        // DIARYELEMENT INHERITED FUNCTIONS
        int                     get_size() const override
        { return m_entries.size(); }
        Type                    get_type() const override
        { return ET_DIARY; }

        Icon                    get_image( const std::string&, int );

        Ustring                 get_list_str() const override
        { return Glib::ustring::compose( "<b>%1</b>", Glib::Markup::escape_text( m_name ) ); }

        int                     get_time_span() const;

        void                    fill_up_chart_data( ChartData* ) const;

        // PASSPHRASE
        bool                    set_passphrase( const std::string& );
        void                    clear_passphrase();
        const std::string&      get_passphrase() const;
        bool                    compare_passphrase( const std::string& ) const;
        bool                    is_passphrase_set() const;

        DiaryElement*           get_element( DEID ) const;
        Entry*                  get_startup_entry() const;
        void                    set_startup_entry( const Entry* );
        Entry*                  get_most_current_entry() const;
        Entry*                  get_prev_session_entry() const;
        void                    set_last_entry( const Entry* );

        bool                    is_first( const Entry* const ) const;
        bool                    is_last( const Entry* const ) const;
        SortCriterion           get_sorting_criteria() const
        { return m_sorting_criteria; }
        void                    set_sorting_criteria( SortCriterion sc )
        { m_sorting_criteria = sc; }
        bool                    get_opt_show_all_entry_locs() const
        { return m_opt_show_all_entry_locations; }
        void                    set_opt_show_all_entry_locs( bool opt )
        { m_opt_show_all_entry_locations = opt; }
        int                     get_opt_ext_panel_cur() const
        { return m_opt_ext_panel_cur; }
        void                    set_opt_ext_panel_cur( int opt )
        { m_opt_ext_panel_cur = opt; }

        std::string             get_lang() const { return m_language; }
        void                    set_lang( const std::string& lang ) { m_language = lang; }

        DEID                    create_new_id( DiaryElement* element );
        void                    erase_id( DEID id ) { m_ids.erase( id ); }
        bool                    set_force_id( DEID id )
        {
            if( m_ids.find( id ) != m_ids.end() || id <= DEID_MIN )
                return false;
            m_force_id = id;
            return true;
        }

        // SEARCHING
        int                     set_search_text( const Ustring&, bool );
        void                    remove_entry_from_search( const Entry* );
        void                    update_search_for_entry( const Entry*, bool );
        Ustring                 get_search_text() const
        { return m_search_text; }
        bool                    is_search_active() const
        { return( m_search_text.empty() == false ); }
        void                    replace_match( Match&, const Ustring& );
        void                    replace_all_matches( const Ustring& );
        void                    replace_all_matches( const Ustring&, const Ustring& );
        ListMatches&            get_matches()
        { return m_matches; }
        Match*                  get_match_at( int );
        void                    clear_matches();

        // FILTERING
        Filter*                 create_filter( const Ustring&, const Ustring& );
        Filter*                 get_filter( const Ustring& ) const;
        Filter*                 get_filter_active() const
        { return m_filter_active; }
        bool                    set_filter_active( const Ustring& );
        Filter* const *         get_p2filter_active() const
        { return &m_filter_active; }
        Ustring                 get_filter_active_name() const
        { return m_filter_active->get_name(); }
        const MapUstringFilter&
                                get_filters() const
        { return m_filters; }
        MapUstringFilter*       get_p2filters()
        { return &m_filters; }

        bool                    rename_filter( const Ustring&, const Ustring& );
        bool                    dismiss_filter( const Ustring& );
        bool                    dismiss_filter_active();
        void                    remove_entry_from_filters( Entry* );

        // CHARTS
        ChartElem*              create_chart( const Ustring&, const Ustring& );
        ChartElem*              get_chart( const Ustring& ) const;
        ChartElem*              get_chart_active() const
        { return m_chart_active; }
        bool                    set_chart_active( const Ustring& );
        ChartElem* const *      get_p2chart_active() const
        { return &m_chart_active; }
        Ustring                 get_chart_active_name() const
        { return m_chart_active->get_name(); }        const MapUstringChartElem&
                                get_charts() const
        { return m_charts; }
        MapUstringChartElem*    get_p2charts()
        { return &m_charts; }

        bool                    rename_chart( const Ustring&, const Ustring& );
        bool                    dismiss_chart( const Ustring& );

        // TABLES
        TableElem*              create_table( const Ustring&, const Ustring& );
        TableElem*              get_table( const Ustring& ) const;
        TableElem*              get_table_active() const
        { return m_table_active; }
        bool                    set_table_active( const Ustring& );
        TableElem* const *      get_p2table_active() const
        { return &m_table_active; }
        Ustring                 get_table_active_name() const
        { return( m_table_active ? m_table_active->get_name() : "" ); }
        const MapUstringTableElem&
                                get_tables() const
        { return m_tables; }
        MapUstringTableElem*    get_p2tables()
        { return &m_tables; }

        bool                    rename_table( const Ustring&, const Ustring& );
        bool                    dismiss_table( const Ustring& );

        // ENTRIES
        const PoolEntries&      get_entries() const
        { return m_entries; }
        PoolEntryNames&         get_entry_names()
        { return m_entry_names; }
        Entry*                  get_entry_today() const;
        Entry*                  get_entry_by_id( const DEID ) const;
        Entry*                  get_entry_by_date( const date_t, bool=false ) const;
        VecEntries              get_entries_by_date( date_t, bool=false ) const; // takes pure date
        Entry*                  get_entry_by_name( const Ustring& ) const;
        VecEntries              get_entries_by_name( const Ustring& ) const;
        VecEntries              get_entries_by_filter( const Ustring& );
        unsigned int            get_entry_count_on_day( const Date& ) const;
        Entry*                  get_entry_next_in_day( const Date& ) const;
        Entry*                  get_entry_first_untrashed() const;
        Entry*                  get_entry_latest() const; // get last temporal entry
        Entry*                  get_entry_closest_to( const date_t, bool ) const;
        Entry*                  get_first_descendant( const date_t ) const;
        void                    set_entry_date( Entry*, date_t );
        void                    update_entry_name( Entry* );
        Entry*                  create_entry( date_t, const Ustring& = "", bool = false );
        Entry*                  create_entry( date_t, bool, bool, bool );
        // adds a new entry to today even if there is already one or more:
        Entry*                  add_today();
        virtual bool            dismiss_entry( Entry*, bool = true );
        void                    shift_entry_orders_after( date_t, int );
        void                    shift_dated_entry_orders_after( date_t, int );
        date_t                  get_available_order_1st( bool ) const;
        date_t                  get_available_order_sub( date_t ) const;
        date_t                  get_available_order_next( date_t ) const;

        bool                    is_trash_empty() const;

        Entry*                  get_completion_tag() const
        { return get_entry_by_id( m_completion_tag_id ); }
        void                    set_completion_tag( DEID id )
        { m_completion_tag_id = id; }

        // THEMES
        Theme*                  create_theme( const Ustring& );
        Theme*                  create_theme( const Ustring&,
                                              const Ustring&,
                                              const std::string&,
                                              const std::string&,
                                              const std::string&,
                                              const std::string&,
                                              const std::string& );
        Theme*                  get_theme( const Ustring& name )
        {
            auto iter( m_themes.find( name ) );
            if( iter != m_themes.end() )
                return iter->second;

            return nullptr;
        }
        PoolThemes*             get_p2themes()
        { return &m_themes; }
        const PoolThemes*       get_p2themes() const
        { return &m_themes; }
        Theme*                  get_theme_default()
        { return m_theme_default; }
        void                    set_theme_default( Theme* theme )
        { m_theme_default = theme; }
        void                    clear_themes();
        void                    dismiss_theme( Theme* );
        void                    rename_theme( Theme*, const Ustring& );

        // CHAPTERS
        PoolCategoriesChapters* get_p2chapter_ctgs()
        { return &m_chapter_categories; }
        CategoryChapters*       get_chapter_ctg_cur() const
        { return m_p2chapter_ctg_cur; }
        CategoryChapters* const *   get_p2chapter_ctg_cur() const
        { return &m_p2chapter_ctg_cur; }
        void                    set_current_chapter_ctg( CategoryChapters* ctg )
        {
            m_p2chapter_ctg_cur = ctg;
            update_entries_in_chapters();
        }
        void                    set_current_chapter_ctg( const Ustring& ctg_name )
        {
            m_p2chapter_ctg_cur = get_chapter_ctg( ctg_name );
            update_entries_in_chapters();
        }
        CategoryChapters*       get_chapter_ctg( const Ustring& name )
        {
            auto ctg = m_chapter_categories.find( name );
            return( ctg == m_chapter_categories.end() ? nullptr : ctg->second );
        }
        CategoryChapters*       create_chapter_ctg();
        CategoryChapters*       create_chapter_ctg( const Ustring& );
        bool                    dismiss_chapter_ctg( CategoryChapters* );
        void                    rename_chapter_ctg( CategoryChapters*, const Ustring& );

        void                    dismiss_chapter( Chapter*, bool = false );
        int                     get_chapter_count() const;
        void                    update_entries_in_chapters();
        void                    add_entry_to_related_chapter( Entry* );
        void                    remove_entry_from_chapters( Entry* );

        // IMPORTING
        bool                    import_entry( const Entry*, Entry*, bool );
        void                    import_chapter_ctg( const CategoryChapters*, bool );
        bool                    import_chapter( const Chapter*, bool );
        bool                    import_theme( const Theme*, bool );
        bool                    import_filter( const Filter*, bool );
        bool                    import_chart( const ChartElem*, bool );
        bool                    import_table( const TableElem*, bool );

        DiaryElement*           get_corresponding_elem( const DiaryElement* ) const;
        SI                      compare_foreign_elem( const DiaryElement*,
                                                      const DiaryElement*& ) const;

        static bool             s_flag_ignore_locks;

    protected:
        // IDS (must be first)
        PoolDEIDs               m_ids;
        DEID                    m_force_id{ DEID_UNSET };
        DEID                    m_startup_entry_id = HOME_CURRENT_ENTRY;
        DEID                    m_last_entry_id{ DEID_UNSET };
        DEID                    m_completion_tag_id{ DEID_UNSET };
        // PROPERTIES
        std::string             m_path;
        std::string             m_passphrase;
        std::string             m_language;
        // CONTENT
        PoolEntries             m_entries;
        PoolEntryNames          m_entry_names;  // this is a multimap
        Theme*                  m_theme_default{ nullptr };
        PoolThemes              m_themes;
        PoolCategoriesChapters  m_chapter_categories;
        CategoryChapters*       m_p2chapter_ctg_cur{ nullptr };
        // FLAGS
        int                     m_read_version{ 0 };
        bool                    m_flag_only_save_filtered{ false };
        bool                    m_flag_read_only{ false };
        // OPTIONS
        SortCriterion           m_sorting_criteria{ SoCr_DEFAULT   };
        bool                    m_opt_show_all_entry_locations{ false };
        int                     m_opt_ext_panel_cur{ 1 };
        // FILTERING
        Ustring                 m_search_text;
        ListMatches             m_matches;

        MapUstringFilter        m_filters;
        Filter*                 m_filter_active{ nullptr };

        MapUstringChartElem     m_charts;
        ChartElem*              m_chart_active{ nullptr };

        MapUstringTableElem     m_tables;
        TableElem*              m_table_active{ nullptr };

        LIFEO::Result           parse_db_body_text( std::istream& );
        LIFEO::Result           parse_db_body_text_2000( std::istream& );
        LIFEO::Result           parse_db_body_text_1050( std::istream& );
        LIFEO::Result           parse_db_body_text_1010( std::istream& );

        void                    upgrade_to_1030();
        void                    upgrade_to_1050();
        //void                    upgrade_to_2000();

        void                    do_standard_checks_after_parse();
        void                    add_entries_to_name_map();

        void                    create_db_entry_text( const Entry*, std::stringstream& ); // helper
        bool                    create_db_body_text( std::stringstream& );

        void                    close_file();
        LIFEO::Result           read_plain();
        LIFEO::Result           read_encrypted();
        LIFEO::Result           write_plain( const std::string&, bool = false );
        LIFEO::Result           write_encrypted( const std::string& );

        // RUN_TIME POOL OF IMAGES FOR EMBEDS INTO ENTRIES
        MapPathsIcons           m_map_images;

    private:
        std::ifstream*          m_ifstream{ nullptr };

        int                     m_login_status{ LOGGED_OUT };

    friend class UIDiary;
    friend class DialogSync;
};

} // end of namespace LIFEO

#endif

