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

    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_DIARYDATA_HEADER
#define LIFEOGRAPH_DIARYDATA_HEADER


#include "helpers.hpp"  // i18n headers
#include "strings.hpp"

#include <gtkmm/treemodel.h>
#include <set>
#include <map>
#include <vector>
#include <limits>

namespace LIFEO
{

using namespace HELPERS;

typedef int SortCriterion;
static const SortCriterion SoCr_DATE          = 0x1;
static const SortCriterion SoCr_SIZE_C        = 0x2;  // size (char count)
static const SortCriterion SoCr_CHANGE        = 0x3;  // last change date
static const SortCriterion SoCr_NAME          = 0x4;  // name
static const SortCriterion SoCr_FILTER_CRTR   = 0xF;
static const SortCriterion SoCr_DESCENDING    = 0x10;
static const SortCriterion SoCr_ASCENDING     = 0x20;
static const SortCriterion SoCr_FILTER_DIR    = 0xF0;
static const SortCriterion SoCr_INVERSE       = 0x100; // (<2000) inverse dir for ordinals
static const SortCriterion SoCr_DESCENDING_T  = 0x100; // temporal
static const SortCriterion SoCr_ASCENDING_T   = 0x200; // temporal
static const SortCriterion SoCr_FILTER_DIR_T  = 0xF00; // temporal
static const SortCriterion SoCr_DEFAULT       = SoCr_DATE|SoCr_ASCENDING|SoCr_DESCENDING_T;

typedef unsigned long DEID; // unique diary element id
static const DEID DEID_MIN           = 10000;   // ids have to be greater than this
static const DEID DEID_UNSET         = 404;     // :)
static const DEID HOME_CURRENT_ENTRY = 1;       // entry shown at startup
static const DEID HOME_LAST_ENTRY    = 2;       // entry shown at startup
// NOTE: when HOME is fixed element, elements ID is used

typedef unsigned long ElemStatus;
namespace ES
{
    static const ElemStatus _VOID_           = 0x0;
    static const ElemStatus EXPANDED         = 0x40;

    static const ElemStatus NOT_FAVORED      = 0x100;
    static const ElemStatus FAVORED          = 0x200;
    static const ElemStatus FILTER_FAVORED   = NOT_FAVORED|FAVORED;
    static const ElemStatus NOT_TRASHED      = 0x400;
    static const ElemStatus TRASHED          = 0x800;
    static const ElemStatus FILTER_TRASHED   = NOT_TRASHED|TRASHED;
    static const ElemStatus NOT_TODO         = 0x1000;
    // NOTE: NOT_TODO means AUTO when used together with other to do statuses
    static const ElemStatus TODO             = 0x2000;
    static const ElemStatus PROGRESSED       = 0x4000;
    static const ElemStatus DONE             = 0x8000;
    static const ElemStatus CANCELED         = 0x10000;
    static const ElemStatus FILTER_TODO      = NOT_TODO|TODO|PROGRESSED|DONE|CANCELED;
    static const ElemStatus FILTER_TODO_PURE = TODO|PROGRESSED|DONE|CANCELED;

    static const ElemStatus ENTRY_DEFAULT       = NOT_FAVORED|NOT_TRASHED|NOT_TODO;
    static const ElemStatus ENTRY_DEFAULT_FAV   = FAVORED|NOT_TRASHED|NOT_TODO;
    static const ElemStatus CHAPTER_DEFAULT     = ENTRY_DEFAULT; // same as entry now

    // FILTER RELATED CONSTANTS AND ALIASES
    static const ElemStatus SHOW_NOT_FAVORED    = NOT_FAVORED;
    static const ElemStatus SHOW_FAVORED        = FAVORED;
    static const ElemStatus SHOW_NOT_TRASHED    = NOT_TRASHED;
    static const ElemStatus SHOW_TRASHED        = TRASHED;
    static const ElemStatus SHOW_NOT_TODO       = NOT_TODO;
    static const ElemStatus SHOW_TODO           = TODO;
    static const ElemStatus SHOW_PROGRESSED     = PROGRESSED;
    static const ElemStatus SHOW_DONE           = DONE;
    static const ElemStatus SHOW_CANCELED       = CANCELED;

    static const ElemStatus FILTERED_OUT        = 0x40000000;

    static const ElemStatus FILTER_MAX          = 0x7FFFFFFF;
    // to go parallel with Java version 0x7FFFFFFF is the max
}

// COMPARISON FUNCTIONS
template< typename T >
struct FuncCmpDiaryElemByDate
{
    bool operator()( T* l, T* r ) const
    { return( l->get_date() > r->get_date() ); }
};

template< typename T >
struct FuncCmpDiaryElemById
{
    bool operator()( T* l, T* r ) const
    { return( l->get_id() < r->get_id() ); }
};

template< typename T >
struct FuncCmpDiaryElemByName
{
    bool operator()( T* l, T* r ) const
    { return( l->get_name() < r->get_name() ); }
};

// FORWARD DECLARATIONS
class DiaryElement;
class Entry;
class Diary;
class CategoryChapters;
class ListData;
class ChartData;
class Filter;
class FiltererContainer;

// TYPEDEFS
typedef std::map< Ustring, Ustring, FuncCmpStrings >
                                            MapUstringUstring;

typedef std::vector< Entry* >               VecEntries;
typedef std::vector< Entry* >::iterator     VecEntriesIter;
typedef std::vector< const Entry* >         VecConstEntries;

typedef std::set< Entry*, FuncCmpDiaryElemByDate< Entry > >
                                            SetEntries;
typedef SetEntries::iterator                SetEntriestIter;

typedef std::set< Entry*, FuncCmpDiaryElemByName< Entry > >
                                            SetEntriesByName;

typedef std::map< DEID, DiaryElement* >     PoolDEIDs;

// DIARYELEMENT ====================================================================================
class NamedElement
{
    public:
                                NamedElement() : m_name( "" ) {}
                                NamedElement( const Ustring& name ) : m_name( name ) {}
        virtual                 ~NamedElement() {}  //needed because of virtual methods

        Ustring                 get_name() const
        { return m_name; }
        std::string             get_name_std() const  // std::string version
        { return m_name; }
        virtual void            set_name( const Ustring& name )
        { m_name = name; }

    protected:
        Ustring                 m_name;
};

class DiaryElement : public NamedElement
{
    public:
        static bool             FLAG_ALLOCATE_GUI_FOR_DIARY;

        static const Icon       s_pixbuf_null;
        static const Ustring    s_type_names[];

        enum Type
        {   // CAUTION: order is significant and shouldn't be changed!
            // type_names above should follow the same order to work
            ET_NONE, ET_CHAPTER_CTG, ET_THEME, ET_FILTER, ET_CHART, ET_TABLE,
            // entry list elements (elements after ENTRY need to be its derivatives):
            ET_DIARY, ET_MULTIPLE_ENTRIES, ET_ENTRY, ET_CHAPTER
        };
                                DiaryElement(); // only for pseudo elements
                                DiaryElement( Diary* const, const Ustring&,
                                              ElemStatus = ES::_VOID_ );
                                DiaryElement( Diary* const, DEID, ElemStatus = ES::_VOID_ );
        virtual                 ~DiaryElement();

        virtual SKVVec          get_as_skvvec() const
        {
            SKVVec sv;
            sv.push_back( { SI::TYPE_NAME, get_type_name() } );
            return sv;
        }

        virtual Type            get_type() const = 0;
        const Ustring           get_type_name() const
        { return s_type_names[ get_type() ]; }
        virtual int             get_size() const = 0;

        virtual const Icon&     get_icon() const
        { return( s_pixbuf_null ); }
        virtual const Icon&     get_icon32() const
        { return( s_pixbuf_null ); }

        virtual Date            get_date() const
        { return Date( Date::NOT_APPLICABLE ); }
        virtual date_t          get_date_t() const
        { return get_date().m_date; }

        virtual Ustring         get_list_str() const
        { return Glib::Markup::escape_text( m_name ); }

        DEID                    get_id() const
        { return m_id; }

        bool                    is_equal_to( const DiaryElement* other ) const
        { return( other->m_id == this->m_id ); }

        Diary*                  get_diary() const
        { return m_p2diary; }

        bool                    get_filtered_out() const
        { return( m_status & ES::FILTERED_OUT ); }
        void                    set_filtered_out( bool filteredout = true )
        {
            if( filteredout )
                m_status |= ES::FILTERED_OUT;
            else if( m_status & ES::FILTERED_OUT )
                m_status -= ES::FILTERED_OUT;
        }

        ElemStatus              get_status() const
        { return m_status; }
        void                    set_status( ElemStatus status )
        { m_status = status; }

        void                    set_status_flag( ElemStatus flag, bool add )
        {
            if( add )
                m_status |= flag;
            else if( m_status & flag )
                m_status -= flag;
        }

        // only works for entries and chapters:
        ElemStatus              get_todo_status() const
        {
            return( m_status & ES::FILTER_TODO );
        }
        ElemStatus              get_todo_status_effective() const
        {
            const ElemStatus s{ m_status & ES::FILTER_TODO_PURE };
            return( s ? s : ES::NOT_TODO );
        }
        SI                      get_todo_status_si() const;
        void                    set_todo_status( ElemStatus );

        bool                    get_expanded() const
        { return( m_status & ES::EXPANDED ); }
        void                    set_expanded( bool flag_expanded )
        { set_status_flag( ES::EXPANDED, flag_expanded ); }

        ListData*               m_list_data{ nullptr };

    protected:
        void                    set_id( DEID id )
        { m_id = id; }

        Diary* const            m_p2diary{ nullptr };

        ElemStatus              m_status = ES::_VOID_;

    private:
        DEID                    m_id = DEID_UNSET;
};

class ListData
{
    public:
        class Colrec : public Gtk::TreeModel::ColumnRecord
        {
            public:
                Colrec()
                {
                    add( ptr );
                    add( icon );
                    add( info );
                    add( icon2 );
                }
                // HIDDEN COLUMNS
                Gtk::TreeModelColumn< Entry* >                      ptr;
                // COLUMNS
                Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > icon;
                Gtk::TreeModelColumn< Glib::ustring >               info;
                Gtk::TreeModelColumn< Glib::RefPtr< Gdk::Pixbuf > > icon2;
        };
        static Colrec*          colrec;

        Gtk::TreePath           treepath;
};

template< class T >
Ustring
create_unique_name_for_map( const std::map< Ustring, T, FuncCmpStrings >& map,
                            const Ustring& name0 )
{
    Ustring name = name0;
    for( int i = 1; map.find( name ) != map.end(); i++ )
    {
        name = Glib::ustring::compose( "%1 %2", name0, i );
    }

    return name;
}

class StringDefElem : public DiaryElement
{
    public:
        StringDefElem( Diary* const diary, const Ustring& name, const Ustring& definition )
        : DiaryElement( diary, name ), m_definition( definition ) {}

        int                     get_size() const override
        { return 0; }
        const Ustring&          get_definition() const
        { return m_definition; }
        void                    set_definition( const Ustring& definition )
        { m_definition = definition; }

        void                    add_definition_line( const Ustring& line )
        {
            if( not( m_definition.empty() ) ) m_definition += '\n';
            m_definition += line;
        }

    protected:
        Ustring                 m_definition;

};

// TAGGING =========================================================================================
struct NameAndValue
{
    static constexpr int HAS_NAME = 0x1;
    static constexpr int HAS_VALUE = 0x2;
    static constexpr int HAS_UNIT = 0x4;
    static constexpr int HAS_EQUAL = 0x10;

    NameAndValue() {}
    NameAndValue( const Ustring& n, const Value& v ) : name( n ), value( v ) {}

    Ustring name{ "" };
    Value value{ 0.0 };
    Ustring unit{ "" };
    int status{ 0 };

    static NameAndValue parse( const Ustring& );
};

class MapTags : public std::map< const Ustring, Value >
{
    public:
        Value                   get_value_for_tag( const Ustring& tag_name ) const
        {
            auto&& kv{ find( tag_name ) };
            if( kv == end() )
                return 0;
            else
                return kv->second;
        }

    protected:

};

// PARAGRAPH =======================================================================================
enum JustificationType { JT_LEFT = '<', JT_CENTER = '|', JT_RIGHT = '>' };

class Paragraph
{
    public:
        Paragraph( const Ustring& text, const Entry* host, int index )
        : m_host( host ), m_para_no( index ), m_text( text )
        {
            update_per_text();
        }
        ~Paragraph()
        {
            clear_references();
        }

        Paragraph*                  get_prev() const;
        Paragraph*                  get_next() const;
        Paragraph*                  get_parent() const;

        bool                        is_empty() const
        { return m_text.empty(); }
        gunichar                    get_char( UstringSize i ) const
        { return m_text[ i ]; }
        const Ustring&              get_text() const
        { return m_text; }
        const std::string           get_text_std() const // for file output
        { return m_text; }
        Ustring                     get_substr_after( UstringSize i ) const
        { return m_text.substr( i ); }

        UstringSize                 get_size() const
        { return m_text.length(); }
        void                        set_text( const Ustring& text )
        { m_text = text; update_per_text(); }
        void                        append( const Ustring& text )
        { m_text += text; update_per_text(); }
        void                        insert_text( UstringSize pos, const Ustring& text )
        { m_text.insert( pos, text ); update_per_text(); }
        void                        erase_text( UstringSize pos, UstringSize size )
        { m_text.erase( pos, size ); update_per_text(); }
        void                        replace_text( UstringSize pos, UstringSize size,
                                                  const Ustring& text )
        { m_text.erase( pos, size ); m_text.insert( pos, text ); update_per_text(); }

        ElemStatus                  get_todo_status() const
        {
            if( m_indentation_level < 1 || m_text.length() < m_indentation_level + 4 ||
                m_text[ m_indentation_level ] != '[' || m_text[ m_indentation_level + 2 ] != ']' )
                return ES::NOT_TODO;

            switch( m_text[ m_indentation_level + 1 ] )
            {
                case ' ': return ES::TODO;
                case '~': return ES::PROGRESSED;
                case '+': return ES::DONE;
                case 'x':
                case 'X':
                case '>':
                default:  return ES::CANCELED;
            }
        }

        void                        clear_tags()
        { m_tags.clear(); m_tags_planned.clear(); m_tags_in_order.clear(); }
        const MapTags&              get_tags() const
        { return m_tags; }
        void                        set_tag( const Ustring& tag, Value value )
        { m_tags[ tag ] = value; m_tags_in_order.push_back( tag ); }
        void                        set_tag( const Ustring& tag, Value v_real, Value v_planned )
        {
            m_tags[ tag ] = v_real;
            m_tags_planned[ tag ] = v_planned;
            m_tags_in_order.push_back( tag );
        }
        bool                        has_tag( const Entry* ) const;
        bool                        has_tag_planned( const Entry* ) const;
        bool                        has_tag_broad( const Entry* ) const; // in broad sense
        Value                       get_value_for_tag( const Entry*, int& ) const;
        Value                       get_value_planned_for_tag( const Entry*, int& ) const;
        Value                       get_value_remaining_for_tag( const Entry*, int& ) const;
        Value                       get_value_for_tag( const ChartData*, int& ) const;
        Value                       get_value_planned_for_tag( const ChartData*, int& ) const;
        // return which sub tag of a parent tag is present in the map
        Entry*                      get_sub_tag_first( const Entry* ) const;
        Entry*                      get_sub_tag_last( const Entry* ) const;
        Entry*                      get_sub_tag_lowest( const Entry* ) const;
        Entry*                      get_sub_tag_highest( const Entry* ) const;

        double                      get_completion() const;
        double                      get_completed() const;
        double                      get_workload() const;

        bool                        has_date() const
        { return( m_date != Date::NOT_SET ); }
        date_t                      get_date() const
        { return m_date; }
        date_t                      get_date_broad() const; // in broad sense
        void                        set_date( date_t date )
        { m_date = date; }

        void                        set_as_image_para( bool flag_image_para )
        { m_flag_image_para = flag_image_para; }
        bool                        is_image_para() const
        { return m_flag_image_para; }

        //int                         get_indentation_level();

        Pango::Alignment            get_pango_alignment() const
        {
            switch( m_justification )
            {
                case JT_LEFT:   return Pango::ALIGN_LEFT;
                case JT_CENTER: return Pango::ALIGN_CENTER;
                case JT_RIGHT:
                default:        return Pango::ALIGN_RIGHT;
            }
        }

        void                        clear_references()
        {
            for( auto& ref : m_references )
                *ref = nullptr;
            m_references.empty();
        }
        void                        add_reference( Paragraph** ref )
        {
            m_references.insert( ref );
        }
        void                        remove_reference( Paragraph** ref2remove )
        {
            m_references.erase( ref2remove );
            *ref2remove = nullptr;
        }

        const Entry*                m_host;
        int                         m_para_no;  // at host
        ElemStatus                  m_status{ ES::NOT_TODO };
        UstringSize                 m_indentation_level{ 0 };
        int                         m_heading_level{ 0 };
        JustificationType           m_justification{ JT_LEFT };

    protected:
        void                        update_per_text();

        bool                        m_flag_image_para{ false };

        Ustring                     m_text;
        MapTags                     m_tags;
        MapTags                     m_tags_planned;
        VecUstrings                 m_tags_in_order;
        date_t                      m_date{ Date::NOT_SET };
        std::set< Paragraph** >     m_references;
};

struct FuncCmpParagraphs
{
    bool operator()( const Paragraph* l, const Paragraph* r ) const;
};

typedef std::set< Paragraph*, FuncCmpParagraphs >   SetParagraphs;
typedef std::vector< Paragraph* >                   VecParagraphs;
typedef std::list< Paragraph* >                     ListParagraphs;

// SEARCH MATCH ====================================================================================
struct Match
{
    Match( Paragraph* p, UstringSize p_entry, UstringSize p_para )
    : para( p ), pos_entry( p_entry ), pos_para( p_para )
    {
        p->add_reference( &this->para );
    }
    ~Match()
    {
        if( para )
            para->remove_reference( &this->para );
    }

    bool operator==( Match& other )
    { return( other.para == para && other.pos_para == pos_para ); }

    Paragraph*          para;
    UstringSize         pos_entry;
    UstringSize         pos_para;
    bool                valid{ true };
};

typedef std::vector< Match > VecMatches;
typedef std::list< Match* > ListMatches;

// COORDS ==========================================================================================
struct Coords
{
    Coords() : latitude( -0.1 ), longitude( -0.1 ) {}
    Coords( double lat, double lon ) : latitude( lat ), longitude( lon ) {}
    double latitude;
    double longitude;
    Ustring to_string() const { return STR::compose( latitude, ", ", longitude ); }
    bool is_set() const { return( latitude != -0.1 ); }
    void unset(){ latitude = -0.1; longitude = -0.1; }
    bool is_equal_to( const Coords& pt2 ) const
    { return( pt2.latitude == latitude && pt2.longitude == longitude ); }

    static double get_distance( const Coords& p1, const Coords& p2 );
};

struct FuncCmpCoords
{
    bool operator()( const Coords& l, const Coords& r ) const
    { return( ( l.latitude * 1000.0 + l.longitude ) < ( r.latitude * 1000.0 + r.longitude ) ); }
};

typedef std::list< Coords >                         ListLocations;
typedef std::map< Coords, Entry*, FuncCmpCoords >   MapCoordsEntries;

// THEME ===========================================================================================
class Theme : public DiaryElement
{
    public:
                                    Theme( Diary* const, const Ustring& );
                                    Theme( Diary* const, const Ustring&,
                                           const Ustring&,
                                           const std::string&,
                                           const std::string&,
                                           const std::string&,
                                           const std::string&,
                                           const std::string& );
        // duplicates an existing theme, works like a copy constructor
                                    Theme( Diary* const, const Ustring&, const Theme* );
        virtual                     ~Theme() {}

        DiaryElement::Type          get_type() const override
        { return ET_THEME; }
        int                         get_size() const override
        { return 0; }   // redundant

        virtual bool                is_system() const
        { return false; }
        bool                        is_default() const;

        SKVVec                      get_as_skvvec() const override
        {
            SKVVec sv;
            sv.push_back( { SI::TYPE_NAME, get_type_name() } );
            sv.push_back( { SI::THEME_FONT, font.to_string() } );
            sv.push_back( { SI::THEME_BASE,
                            convert_gdkrgba_to_string( color_base ) } );
            sv.push_back( { SI::THEME_TEXT,
                            convert_gdkrgba_to_string( color_text ) } );
            sv.push_back( { SI::THEME_HEADING,
                            convert_gdkrgba_to_string( color_heading ) } );
            sv.push_back( { SI::THEME_SUBHEADING,
                            convert_gdkrgba_to_string( color_subheading ) } );
            sv.push_back( { SI::THEME_HIGHLIGHT,
                            convert_gdkrgba_to_string( color_highlight ) } );

            return sv;
        }

        void                        copy_to( Theme* ) const;

        const Color&                get_color_subsubheading() const
        { return m_color_subsubheading; }
        const Color&                get_color_inline_tag() const
        { return m_color_inline_tag; }
        const Color&                get_color_mid() const
        { return m_color_mid; }
        const Color&                get_color_region_bg() const
        { return m_color_region_bg; }
        const Color&                get_color_match_bg() const
        { return m_color_match_bg; }
        const Color&                get_color_link() const
        { return m_color_link; }
        const Color&                get_color_link_broken() const
        { return m_color_link_broken; }

        const Color&                get_color_open() const
        { return m_color_open; }
        const Color&                get_color_open_bg() const
        { return m_color_open_bg; }
        const Color&                get_color_progressed() const
        { return m_color_progressed; }
        const Color&                get_color_progressed_bg() const
        { return m_color_progressed_bg; }
        const Color&                get_color_done() const
        { return m_color_done; }
        const Color&                get_color_done_text() const
        { return m_color_done_text; }
        const Color&                get_color_done_bg() const
        { return m_color_done_bg; }
        const Color&                get_color_canceled() const
        { return m_color_canceled; }
        const Color&                get_color_canceled_bg() const
        { return m_color_canceled_bg; }

        void                        calculate_derived_colors();

        Pango::FontDescription      font;
        std::string                 image_bg;

        // USER DEFINED COLORS
        Color                       color_base;
        Color                       color_text;
        Color                       color_heading;
        Color                       color_subheading;
        Color                       color_highlight;

    protected:
        // DERIVED COLORS
        Color                       m_color_subsubheading;
        Color                       m_color_inline_tag;
        Color                       m_color_mid;
        Color                       m_color_region_bg;
        Color                       m_color_match_bg;
        Color                       m_color_link;
        Color                       m_color_link_broken;

        Color                       m_color_open;
        Color                       m_color_open_bg;
        Color                       m_color_progressed;
        Color                       m_color_progressed_bg;
        Color                       m_color_done;
        Color                       m_color_done_text;   // for the text rather than checkbox
        Color                       m_color_done_bg;
        Color                       m_color_canceled;
        Color                       m_color_canceled_bg;

    public:
        // CONSTANT COLORS
        static const Color          s_color_match1;
        static const Color          s_color_match2;
        static const Color          s_color_link1;
        static const Color          s_color_link2;
        static const Color          s_color_broken1;
        static const Color          s_color_broken2;

        static const Color          s_color_todo;
        static const Color          s_color_progressed;
        static const Color          s_color_done;
        static const Color          s_color_canceled;
};

class ThemeSystem : public Theme
{
    public:
        static ThemeSystem*         get();
        bool                        is_system() const
        { return true; }

    protected:
                                    ThemeSystem( const Ustring&,
                                                 const std::string&,
                                                 const std::string&,
                                                 const std::string&,
                                                 const std::string&,
                                                 const std::string& );
};

typedef std::map< Ustring, Theme*, FuncCmpStrings > PoolThemes;
typedef PoolThemes::iterator                        PoolThemesIter;

// CHARTPOINTS =====================================================================================
class ChartData
{
    public:
        typedef std::pair< float, Color > PairChapter;
        // NOTE: change in these values will break compatibility with older diary versions
        static constexpr int    MONTHLY                 = 0x1;
        static constexpr int    YEARLY                  = 0x2;
        static constexpr int    WEEKLY                  = 0x3;
        static constexpr int    PERIOD_MASK             = 0xF;

        static constexpr int    BOOLEAN                 = 0x10; // not used any more
        static constexpr int    CUMULATIVE_PERIODIC     = 0x20; // corresponds to the old cumulative
        static constexpr int    AVERAGE                 = 0x30;
        static constexpr int    CUMULATIVE_CONTINUOUS   = 0x40;
        static constexpr int    VALUE_TYPE_MASK         = 0xF0;

        static constexpr int    UNDERLAY_PREV_YEAR      = 0x100;
        static constexpr int    UNDERLAY_PLANNED        = 0x200;
        static constexpr int    UNDERLAY_MASK           = 0x300;
        static constexpr int    UNDERLAY_NONE           = 0x300; // same as mask to save bits

        static constexpr int    TAGGED_ONLY             = 0x800;

        static constexpr int    COUNT                   = 0x1000;
        static constexpr int    TEXT_LENGTH             = 0x2000;
        static constexpr int    MAP_PATH_LENGTH         = 0x3000;
        static constexpr int    TAG_VALUE_ENTRY         = 0x4000;
        static constexpr int    TAG_VALUE_PARA          = 0x5000;
        static constexpr int    Y_AXIS_MASK             = 0xF000;

        static constexpr int    DEFAULT                 = MONTHLY|CUMULATIVE_CONTINUOUS;

        ChartData( Diary* d, int t = DEFAULT ) : type( t ), m_p2diary( d ) { }

        void                        clear();

        void                        set_diary( Diary* diary )
        { m_p2diary = diary; }

        std::string                 get_as_string() const;
        void                        set_from_string( const Ustring& );

        void                        set_type_sub( int );

        void                        refresh_unit();

        unsigned int                calculate_distance( const date_t, const date_t ) const;

        void                        forward_date( date_t&, unsigned int ) const;

        void                        clear_points();

        void                        add_value( const date_t, const Value, const Value = 0.0 );

        void                        update_min_max();

        unsigned int                get_span() const
        { return values.size(); }

        int                         get_period() const
        { return( type & PERIOD_MASK ); }
        bool                        is_monthly() const
        { return( ( type & PERIOD_MASK ) == MONTHLY ); }
        int                         get_type() const
        { return( type & VALUE_TYPE_MASK ); }
        bool                        is_average() const
        { return( ( type & VALUE_TYPE_MASK ) == AVERAGE ); }
        bool                        is_tagged_only() const
        { return( ( type & TAGGED_ONLY ) &&
                  ( ( type & Y_AXIS_MASK ) == TAG_VALUE_ENTRY ||
                    ( type & Y_AXIS_MASK ) == TAG_VALUE_PARA ) ); }
        int                         get_y_axis() const
        { return( type & Y_AXIS_MASK ); }
        date_t                      get_start_date() const
        { return( dates.empty() ? Date::NOT_SET : dates[ 0 ] ); }
        bool                        is_underlay_prev_year() const
        { return( ( type & UNDERLAY_MASK ) == UNDERLAY_PREV_YEAR &&
                  ( type & PERIOD_MASK ) != YEARLY ); }
        bool                        is_underlay_planned() const
        { return( ( type & UNDERLAY_MASK ) == UNDERLAY_PLANNED ); }

        Value                       v_min{ std::numeric_limits< double >::max() };
        Value                       v_max{ -std::numeric_limits< double >::max() };
        Value                       v_plan_min{ std::numeric_limits< double >::max() };
        Value                       v_plan_max{ -std::numeric_limits< double >::max() };

        std::vector< Value >        values;
        std::vector< Value >        values_plan;
        std::vector< int >          counts;
        std::vector< date_t >       dates;
        std::vector< PairChapter >  chapters;
        Ustring                     unit;
        int                         type;
        const Entry*                tag{ nullptr };
        const Entry*                para_filter_tag{ nullptr };
        Filter*                     filter{ nullptr };

    protected:
        void                        push_back_value( const Value v_real, const Value v_plan = 0.0 )
        {
            values.push_back( v_real );
            values_plan.push_back( v_plan );
        }

        Diary*                      m_p2diary;
};

class ChartElem : public StringDefElem
{
    public:
        static const Ustring DEFINITION_DEFAULT;
        static const Ustring DEFINITION_DEFAULT_Y; // temporary: needed for diary upgrades

        ChartElem( Diary* const diary, const Ustring& name, const Ustring& definition )
        : StringDefElem( diary, name, definition ) {}

        DiaryElement::Type      get_type() const override
        { return ET_CHART; }

        SKVVec                  get_as_skvvec() const override
        {
            SKVVec sv;
            sv.push_back( { SI::TYPE_NAME,  get_type_name() } );
            sv.push_back( { SI::DEFINITION, m_definition } );

            return sv;
        }
};

typedef std::map< Ustring, ChartElem*, FuncCmpStrings > MapUstringChartElem;

// TABLES ==========================================================================================
class TableColumn : public NamedElement
{
    public:
        enum Type{ TCT_NAME = 0, TCT_DATE = 1, TCT_COUNT = 2, TCT_TAG_V = 3, TCT_SUB = 4,
                   TCT_COMPLETION = 5, TCT_PATH_LENGTH = 6 };
        enum ValueType{ VT_FIRST = 2, VT_LAST = 3, VT_LOWEST = 4, VT_HIGHEST = 5,
                        VT_TOTAL = 8, VT_AVERAGE = 16,
                        VT_REALIZED = 32,       VT_PLANNED = 64,       VT_REMAINING = 128,
                        VT_TOTAL_REALIZED = 40, VT_TOTAL_PLANNED = 72, VT_TOTAL_REMAINING = 136,
                        VT_AVG_REALIZED = 48,   VT_AVG_PLANNED = 80,   VT_AVG_REMAINING = 144 };
        enum Comparison{ C_IGNORE = 0, C_LESS = 1, C_LESS_OR_EQUAL = 2, C_EQUAL = 3,
                         C_MORE_OR_EQUAL = 4, C_MORE = 5 };

                                    TableColumn() : NamedElement( _( "New Column" ) ) {}

        void                        set_type( Type type )
        {
            m_type = type;

            if( m_type == TCT_TAG_V )
                m_value_type = TableColumn::VT_TOTAL_REALIZED;
            else
            if( m_type == TableColumn::TCT_SUB )
                m_value_type = TableColumn::VT_FIRST;
        }
        Type                        get_type() const
        { return m_type; }

        void                        set_value_type( ValueType type )
        { m_value_type = type; }
        ValueType                   get_value_type() const
        { return m_value_type; }

        void                        set_tag( Entry* tag )
        { m_p2tag = tag; }
        Entry*                      get_tag() const
        { return m_p2tag; }

        void                        set_conditional( bool flag_conditional )
        { m_flag_conditional = flag_conditional; }
        bool                        get_conditional() const
        { return m_flag_conditional; }

        void                        set_condition_rel_lo( Comparison comparison )
        { m_condition_comparison_lo = comparison; }
        Comparison                  get_condition_rel_lo() const
        { return m_condition_comparison_lo; }
        void                        set_condition_rel_hi( Comparison comparison )
        { m_condition_comparison_hi = comparison; }
        Comparison                  get_condition_rel_hi() const
        { return m_condition_comparison_hi; }

        void                        set_condition_num_lo( Value value )
        { m_condition_num_lo = value; }
        Value                       get_condition_num_lo() const
        { return m_condition_num_lo; }
        void                        set_condition_num_hi( Value value )
        { m_condition_num_hi = value; }
        Value                       get_condition_num_hi() const
        { return m_condition_num_hi; }

        void                        set_condition_sub_filter( Filter* filter )
        { m_condition_sub_filter = filter; }
        Filter*                     get_condition_sub_filter() const
        { return m_condition_sub_filter; }
        Filter* const *             get_p2condition_sub_filter() const
        { return &m_condition_sub_filter; }

        bool                        is_numeric() const;
//        bool                        is_fraction() const;
        bool                        is_percentage() const;
        bool                        is_name() const
        { return( m_type == TCT_NAME ); }

        Value                       get_entry_v_num( const Entry* ) const;
        Value                       get_entry_weight( const Entry* ) const;
        Ustring                     get_entry_v_txt( const Entry* ) const;

        Value                       get_para_v_num( const Paragraph* ) const;
        Value                       get_para_weight( const Paragraph* ) const;
        Ustring                     get_para_v_txt( const Paragraph* ) const;

    protected:
        Type                        m_type{ TCT_NAME };
        Entry*                      m_p2tag{ nullptr };
        ValueType                   m_value_type{ VT_TOTAL };
        bool                        m_flag_conditional{ false };
        Comparison                  m_condition_comparison_lo{ C_IGNORE };
        Comparison                  m_condition_comparison_hi{ C_EQUAL };
        Value                       m_condition_num_lo{ 0.0 };
        Value                       m_condition_num_hi{ 0.0 };
        Filter*                     m_condition_sub_filter{ nullptr };
};

typedef std::list< TableColumn* > ListTableColumns;

class TableLine
{
    public:
        void                        add_col_v_num( const int, Value, Value = 0.0 );
        void                        add_col_v_txt( int, const Ustring& );

        Value                       get_col_v_num( int ) const;
        Ustring                     get_col_v_txt( int ) const;

        std::vector< Ustring >      m_values_txt;
        std::vector< Value >        m_values_num;
        std::vector< Value >        m_weights;

        VecEntries                  m_entries;
};

struct
TableLineComparator
{
    TableLineComparator( int i_col, const TableColumn* col, bool f_desc )
    : m_i_col( i_col ), m_f_numeric( col->is_numeric() ), m_f_percentage( col->is_percentage() ),
      m_f_descending( f_desc ) {}

    bool operator()( TableLine* l, TableLine* r ) const
    {
        if( m_f_percentage )
        {
            if( m_f_descending )
                return( ( l->m_values_num[ m_i_col ] / l->m_weights[ m_i_col ] )
                        >
                        ( r->m_values_num[ m_i_col ] / r->m_weights[ m_i_col ] ) );
            else
                return( ( l->m_values_num[ m_i_col ] / l->m_weights[ m_i_col ] )
                        <
                        ( r->m_values_num[ m_i_col ] / r->m_weights[ m_i_col ] ) );
        }
        else
        if( m_f_numeric )
        {
            if( m_f_descending )
                return( l->m_values_num[ m_i_col ] > r->m_values_num[ m_i_col ] );
            else
                return( l->m_values_num[ m_i_col ] < r->m_values_num[ m_i_col ] );
        }
        else
        {
            if( m_f_descending )
                return( l->m_values_txt[ m_i_col ] > r->m_values_txt[ m_i_col ] );
            else
                return( l->m_values_txt[ m_i_col ] < r->m_values_txt[ m_i_col ] );
        }
    }

    const int  m_i_col;
    const bool m_f_numeric;
    const bool m_f_percentage;
    const bool m_f_descending;
};

typedef std::multimap< Ustring, TableLine* > MapTableLines;
typedef std::multiset< TableLine*, TableLineComparator > SetTableLines;

class TableData
{
    public:
                                    ~TableData()
        { clear(); }

        void                        set_diary( Diary* diary )
        { m_p2diary = diary; }
        Diary*                      get_diary() const
        { return m_p2diary; }

        void                        clear();
        void                        clear_lines();

        std::string                 get_as_string() const;
        void                        set_from_string( const Ustring& );

        TableColumn*                add_column();
        void                        dismiss_column( int );
        void                        move_column( int, int );

        void                        enable_grouping( bool F_enable )
        { m_flag_group_by_first = F_enable; }

        void                        populate_lines();
        void                        sort_lines( TableColumn* = nullptr );

        Ustring                     get_value_str( int, int ) const;
        Ustring                     get_total_value_str( int ) const;

    protected:
        TableLine*                  add_line( const Ustring& );
        void                        add_entry( Entry* );
        void                        add_para( Paragraph* );
        void                        add_entry_to_line( Entry*, TableLine* );
        void                        add_para_to_line( Paragraph*, TableLine* );

        Filter*                     m_filter{ nullptr };
        Entry*                      m_tag_filter{ nullptr };
        ListTableColumns            m_columns;
        int                         m_i_col_sort{ -1 };
        bool                        m_f_sort_desc{ false };
        MapTableLines               m_lines_unsorted;
        SetTableLines*              m_lines_sorted{ nullptr };
        TableLine*                  m_line_total{ nullptr };
        bool                        m_flag_group_by_first{ false };
        bool                        m_flag_para_based{ false };
        Diary*                      m_p2diary;

    friend class Table;
    friend class WidgetTable;
};

class TableElem : public StringDefElem
{
    public:
        static const Ustring DEFINITION_DEFAULT;

        TableElem( Diary* const diary, const Ustring& name, const Ustring& definition )
        : StringDefElem( diary, name, definition ) {}

        DiaryElement::Type      get_type() const override
        { return ET_TABLE; }

        SKVVec                  get_as_skvvec() const override
        {
            SKVVec sv;
            sv.push_back( { SI::TYPE_NAME,  get_type_name() } );
            sv.push_back( { SI::DEFINITION, m_definition } );

            return sv;
        }
};

typedef std::map< Ustring, TableElem*, FuncCmpStrings > MapUstringTableElem;

} // end of namespace LIFEO

#endif
