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

    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_FILTERING_HEADER
#define LIFEOGRAPH_FILTERING_HEADER


#include "diarydata.hpp"
#include "entry.hpp"


namespace LIFEO
{

static const ElemStatus
FILTER_STATUS_DEFAULT{ ES::SHOW_NOT_TODO|ES::SHOW_TODO|ES::SHOW_PROGRESSED };

// FORWARD DECLARATIONS
class FiltererContainer;

class Filterer
{
    public:
        Filterer( Diary* diary, FiltererContainer* ptr2container )
        : m_p2container( ptr2container ), m_p2diary( diary ) {}
        virtual ~Filterer() {}

        virtual bool            is_container() const { return false; }

        virtual bool            filter( const Entry* ) = 0;
        virtual void            get_as_string( Ustring& ) const = 0;

        FiltererContainer*      m_p2container;

    protected:
        Diary*                  m_p2diary{ nullptr };
};

class FiltererStatus : public Filterer
{
    public:
        FiltererStatus( Diary* d, FiltererContainer* ctr, ElemStatus es = FILTER_STATUS_DEFAULT )
        : Filterer( d, ctr ), m_included_statuses( es ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        ElemStatus              m_included_statuses;
};

class FiltererFavorite : public Filterer
{
    public:
        FiltererFavorite( Diary* d, FiltererContainer* ctr, bool include = true )
        : Filterer( d, ctr ), m_include_favorite( include ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        bool                    m_include_favorite;
};

class FiltererTrashed : public Filterer
{
    public:
        FiltererTrashed( Diary* d, FiltererContainer* ctr, bool include = false )
        : Filterer( d, ctr ), m_include_trashed( include ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        bool                    m_include_trashed;
};

class FiltererIs : public Filterer
{
    public:
        FiltererIs( Diary* d, FiltererContainer* ctr, DEID id, bool f_is )
        : Filterer( d, ctr ), m_id( id ), m_f_is( f_is ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        DEID                    m_id{ DEID_UNSET };
        bool                    m_f_is{ false };
};

class FiltererHasTag : public Filterer
{
    public:
        FiltererHasTag( Diary* d, FiltererContainer* ctr, Entry* tag, bool f_has )
        : Filterer( d, ctr ), m_tag( tag ), m_f_has( f_has ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        Entry*                  m_tag{ nullptr };
        bool                    m_f_has{ true };
};

class FiltererTheme : public Filterer
{
    public:
        FiltererTheme( Diary* d, FiltererContainer* ctr, Theme* theme, bool f_has )
        : Filterer( d, ctr ), m_theme( theme ), m_f_has( f_has ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        Theme*                  m_theme{ nullptr };
        bool                    m_f_has{ true };
};

class FiltererBetweenDates : public Filterer
{
    public:
        FiltererBetweenDates( Diary* d, FiltererContainer* ctr,
                              date_t date_b = Date::NOT_SET, bool f_incl_b = false,
                              date_t date_e = Date::NOT_SET, bool f_incl_e = false )
        : Filterer( d, ctr ), m_date_b( std::min( date_b, date_e ) ), m_f_incl_b( f_incl_b ),
                              m_date_e( std::max( date_b, date_e ) ), m_f_incl_e( f_incl_e ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        date_t                  m_date_b;
        bool                    m_f_incl_b; // ( or [
        date_t                  m_date_e;
        bool                    m_f_incl_e; // ) or ]
};

class FiltererBetweenEntries : public Filterer
{
    public:
        FiltererBetweenEntries( Diary* d, FiltererContainer* ctr,
                                Entry* entry_b = nullptr, bool f_incl_b = false,
                                Entry* entry_e = nullptr, bool f_incl_e = false )
        : Filterer( d, ctr ), m_entry_b( entry_b ), m_f_incl_b( f_incl_b ),
                              m_entry_e( entry_e ), m_f_incl_e( f_incl_e ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        Entry*                  m_entry_b;
        bool                    m_f_incl_b; // ( or [
        Entry*                  m_entry_e;
        bool                    m_f_incl_e; // ) or ]
};

class FiltererCompletion : public Filterer
{
    public:
        FiltererCompletion( Diary* d, FiltererContainer* ctr,
                            double compl_b = 0.0, double compl_e = 100.0 )
        : Filterer( d, ctr ), m_compl_b( std::min( compl_b, compl_e ) ),
                              m_compl_e( std::max( compl_b, compl_e ) ) {}

        bool                    filter( const Entry* ) override;
        void                    get_as_string( Ustring& ) const override;

    protected:
        double                  m_compl_b;
        double                  m_compl_e;
};

typedef std::vector< Filterer* > VecFilterers;

class FiltererContainer : public Filterer
{
    public:
        FiltererContainer( Diary* diary, FiltererContainer* ctr, bool flag_or = false )
        : Filterer( diary, ctr ), m_flag_or( flag_or ) {}

        virtual void            clear();

        virtual void            update_state() {}

        bool                    filter( const Entry* ) override;

        bool                    is_container() const override { return true; }
        bool                    is_or() const
        { return m_flag_or; }

        void                    get_as_string( Ustring& ) const override;
        virtual void            set_from_string( const Ustring& );

        virtual void            toggle_logic() {}
        virtual void            update_logic_label() {}

        virtual void            add_filterer_status( ElemStatus es )
        { m_pipeline.push_back( new FiltererStatus( m_p2diary, this, es ) ); }
        virtual void            add_filterer_favorite( bool f_favorite )
        { m_pipeline.push_back( new FiltererFavorite( m_p2diary, this, f_favorite ) ); }
        virtual void            add_filterer_trashed( bool f_trashed )
        { m_pipeline.push_back( new FiltererTrashed( m_p2diary, this, f_trashed ) ); }
        virtual void            add_filterer_is( DEID id, bool f_is )
        { m_pipeline.push_back( new FiltererIs( m_p2diary, this, id, f_is ) ); }
        virtual void            add_filterer_tagged_by( Entry* tag, bool f_has )
        { m_pipeline.push_back( new FiltererHasTag( m_p2diary, this, tag, f_has ) ); }
        virtual void            add_filterer_theme( Theme* theme, bool f_has )
        { m_pipeline.push_back( new FiltererTheme( m_p2diary, this, theme, f_has ) ); }
        virtual void            add_filterer_between_dates( date_t date_b, bool f_incl_b,
                                                            date_t date_e, bool f_incl_e )
        { m_pipeline.push_back( new FiltererBetweenDates(
                m_p2diary, this, date_b, f_incl_b, date_e, f_incl_e ) ); }
        virtual void            add_filterer_between_entries( Entry* entry_b, bool f_incl_b,
                                                              Entry* entry_e, bool f_incl_e )
        { m_pipeline.push_back( new FiltererBetweenEntries(
                m_p2diary, this, entry_b, f_incl_b, entry_e, f_incl_e ) ); }
        virtual void            add_filterer_completion( double compl_b, double compl_e )
        { m_pipeline.push_back( new FiltererCompletion(
                m_p2diary, this, compl_b, compl_e ) ); }

        virtual FiltererContainer*
                                add_filterer_subgroup()
        {
            FiltererContainer* container{
                    new FiltererContainer( m_p2diary, this,
                                           m_p2container && not( m_p2container->is_or() ) ) };
            m_pipeline.push_back( container );
            return container;
        }
        virtual void            remove_filterer( Filterer* );

    protected:
        VecFilterers            m_pipeline;

        bool                    m_flag_or;
};

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

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

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

            return sv;
        }

        FiltererContainer*      get_filterer_stack();
};

typedef std::map< Ustring, Filter*, FuncCmpStrings > MapUstringFilter;

} // end of namespace LIFEO

#endif
