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

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

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


#include "filtering.hpp"
#include "diary.hpp"


using namespace LIFEO;

// FILTERER STATUS =================================================================================
bool
FiltererStatus::filter( const Entry* entry )
{
    return( m_included_statuses & entry->get_todo_status_effective() );
}

void
FiltererStatus::get_as_string( Ustring& string ) const
{
    string += "\nFs";
    string += ( m_included_statuses & ES::SHOW_NOT_TODO ) ? 'N' : 'n';
    string += ( m_included_statuses & ES::SHOW_TODO ) ? 'O' : 'o';
    string += ( m_included_statuses & ES::SHOW_PROGRESSED ) ? 'P' : 'p';
    string += ( m_included_statuses & ES::SHOW_DONE ) ? 'D' : 'd';
    string += ( m_included_statuses & ES::SHOW_CANCELED ) ? 'C' : 'c';
}

// FILTERER FAVORITE ===============================================================================
bool
FiltererFavorite::filter( const Entry* entry )
{
    return( entry->is_favored() == m_include_favorite );
}

void
FiltererFavorite::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFf", m_include_favorite ? 'y' : 'n' );
}

// FILTERER TRASHED ================================================================================
bool
FiltererTrashed::filter( const Entry* entry )
{
    return( entry->is_trashed() == m_include_trashed );
}

void
FiltererTrashed::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFt", m_include_trashed ? 'y' : 'n' );
}

// FILTERER IS NOT =================================================================================
bool
FiltererIs::filter( const Entry* entry )
{
    return( bool( entry->get_id() == m_id ) == m_f_is );
}

void
FiltererIs::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFi", m_f_is ? 'T' : 'F', m_id );
}

// FILTERER TAGGED BY ==============================================================================
bool
FiltererHasTag::filter( const Entry* entry )
{
    return( m_tag == nullptr || ( entry->has_tag_broad( m_tag ) == m_f_has ) );
}

void
FiltererHasTag::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFr", m_f_has ? 'T' : 'F', m_tag ? m_tag->get_id() : DEID_UNSET );
}

// FILTERER THEME ==================================================================================
bool
FiltererTheme::filter( const Entry* entry )
{
    return( m_theme == nullptr || ( entry->has_theme( m_theme->get_name() ) == m_f_has ) );
}

void
FiltererTheme::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFh", m_f_has ? 'T' : 'F', m_theme ? m_theme->get_name() : "" );
}

// FILTERER BETWEEN ================================================================================
bool
FiltererBetweenDates::filter( const Entry* entry )
{
    const date_t date{ entry->get_date_t() };
    bool         res_b{ true };
    bool         res_e{ true };

    if( Date::is_set( m_date_b ) )
    {
        if( m_f_incl_b )
        {
            if( date < m_date_b ) res_b = false;
        }
        else
        if( date <= m_date_b ) res_b = false;
    }

    if( Date::is_set( m_date_e ) )
    {
        if( m_f_incl_e )
        {
            if( date > m_date_e ) res_e = false;
        }
        else
        if( date >= m_date_e ) res_e = false;
    }

    if( m_date_b <= m_date_e )
        return( res_b && res_e );
    else
        return( res_b || res_e );
}

void
FiltererBetweenDates::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFd", m_f_incl_b ? '[' : '(', m_date_b,
                                                            m_f_incl_e ? '[' : '(', m_date_e );
}

// FILTERER BETWEEN ENTRIES ========================================================================
bool
FiltererBetweenEntries::filter( const Entry* entry )
{
    const date_t date{ entry->get_date_t() };
    const date_t date_b{ m_entry_b ? m_entry_b->get_date_t() : 0 };
    const date_t date_e{ m_entry_e ? m_entry_e->get_date_t() : Date::DATE_MAX };
    bool         res_b{ true };
    bool         res_e{ true };

    if( m_f_incl_b )
    {
        if( date < date_b ) res_b = false;
    }
    else
    if( date <= date_b ) res_b = false;

    if( m_f_incl_e )
    {
        if( date > date_e ) res_e = false;
    }
    else
    if( date >= date_e ) res_e = false;

    if( date_b <= date_e )
        return( res_b && res_e );
    else
        return( res_b || res_e );
}

void
FiltererBetweenEntries::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFe", m_f_incl_b ? '[' : '(',
                                    m_entry_b ? m_entry_b->get_id() : DEID_UNSET,
                                    m_f_incl_e ? '[' : '(',
                                    m_entry_e ? m_entry_e->get_id() : DEID_UNSET );
}

// FILTERER COMPLETION =============================================================================
bool
FiltererCompletion::filter( const Entry* entry )
{
    const double completion{ entry->get_completion() * 100.0 };
    if( m_compl_b <= m_compl_e )
        return( completion >= m_compl_b && completion <= m_compl_e );
    else
        return( completion >= m_compl_b || completion <= m_compl_e );
}

void
FiltererCompletion::get_as_string( Ustring& string ) const
{
    string += STR::compose( "\nFc", m_compl_b, '&', m_compl_e );
}

// FILTERER CONTAINER ==============================================================================
// this is re-implemented in WidgetFilter:
void
FiltererContainer::remove_filterer( Filterer* filterer )
{
    for( auto&& iter = m_pipeline.begin(); iter != m_pipeline.end(); iter++ )
    {
        if( *iter == filterer )
        {
            m_pipeline.erase( iter );
            delete filterer;
            update_state();
            break;
        }
    }
}

// this is re-implemented in WidgetFilter:
void
FiltererContainer::clear()
{
    for( auto& filterer : m_pipeline )
        delete filterer;

    m_pipeline.clear();
}

bool
FiltererContainer::filter( const Entry* entry )
{
    if( m_pipeline.empty() )
        return true;

    for( auto& filterer : m_pipeline )
    {
        if( filterer->filter( entry ) )
        {
            if( m_flag_or )
                return true;
        }
        else
        if( ! m_flag_or )
            return false;
    }

    return( ! m_flag_or );
}

void
FiltererContainer::get_as_string( Ustring& string ) const
{
    string += m_p2container ? "\nF(" : ( m_flag_or ? "F|" : "F&" );

    for( auto& filterer : m_pipeline )
        filterer->get_as_string( string );

    if( m_p2container != nullptr )
        string += "\nF)";
}

void
FiltererContainer::set_from_string( const Ustring& string )
{
    if( m_p2container ) // only top level can do this
        return;

    std::string         line;
    UstringSize         line_offset{ 0 };
    FiltererContainer*  container{ this };

    clear();

    while( STR::get_line( string, line_offset, line ) )
    {
        if( line.size() < 2 )   // should never occur
            continue;

        switch( line[ 1 ] )
        {
            case 't':   // trashed (y/n)
                container->add_filterer_trashed( line[ 2 ] == 'y' );
                break;
            case 'f':   // favorite (y/n)
                container->add_filterer_favorite( line[ 2 ] == 'y' );
                break;
            case 's':   // status (ElemStatus)
            {
                ElemStatus status{ 0 };
                if( line.size() > 6 )
                {
                    if( line[ 2 ] == 'N' ) status |= ES::SHOW_NOT_TODO;
                    if( line[ 3 ] == 'O' ) status |= ES::SHOW_TODO;
                    if( line[ 4 ] == 'P' ) status |= ES::SHOW_PROGRESSED;
                    if( line[ 5 ] == 'D' ) status |= ES::SHOW_DONE;
                    if( line[ 6 ] == 'C' ) status |= ES::SHOW_CANCELED;
                }
                container->add_filterer_status( status );
                break;
            }
            case 'i':   // is not (DEID)
            {
                const DEID id{ std::stoul( line.substr( 3 ) ) };
                container->add_filterer_is( id, line[ 2 ] == 'T' );
                break;
            }
            case 'r':   // tagged/referenced by (DEID)
            {
                const DEID id{ std::stoul( line.substr( 3 ) ) };
                container->add_filterer_tagged_by( m_p2diary->get_entry_by_id( id ),
                                                   line[ 2 ] == 'T' );
                break;
            }
            case 'h':   // theme (Ustring)
            {
                const Ustring&& name{ line.substr( 3 ) };
                container->add_filterer_theme( m_p2diary->get_theme( name ), line[ 2 ] == 'T' );
                break;
            }
            case 'd':   // between dates
            {
                int          i_date{ 3 };
                const date_t date_b{ STR::get_ul( line, i_date ) };
                const int    i_f_icl_e = i_date;
                i_date++;
                const date_t date_e{ STR::get_ul( line, i_date ) };
                container->add_filterer_between_dates(
                        date_b, line[ 2 ] == '[', date_e, line[ i_f_icl_e ] == '[' );
                break;
            }
            case 'e':   // between entries
            {
                int        i_id{ 3 };
                const DEID id_b{ STR::get_ul( line, i_id ) };
                const int  i_f_icl_e = i_id;
                i_id++;
                const DEID id_e{ STR::get_ul( line, i_id ) };
                container->add_filterer_between_entries(
                        m_p2diary->get_entry_by_id( id_b ), line[ 2 ] == '[',
                        m_p2diary->get_entry_by_id( id_e ), line[ i_f_icl_e ] == '[' );
                break;
            }
            case 'c':   // completion
            {
                int i_double{ 2 };
                const double double_b{ STR::get_d( line, i_double ) };
                i_double++;
                const double double_e{ STR::get_d( line, i_double ) };
                container->add_filterer_completion( double_b, double_e );
                break;
            }
            case '(':   // sub group
                container = container->add_filterer_subgroup();
                break;
            case ')':   // end of sub group
                container = container->m_p2container;
                break;
            case '|':
                m_flag_or = true;
                break;
            case '&':
                m_flag_or = false;
                break;
            default:
                PRINT_DEBUG( "Unrecognized filter string: ", line );
                break;
        }
    }

    update_logic_label();
}

const Ustring Filter::DEFINITION_DEFAULT{ "F&\nFtn\nFsNOPdc" };

FiltererContainer*
Filter::get_filterer_stack()
{
    if( m_definition.empty() )
        return nullptr;

    FiltererContainer* fc{ new FiltererContainer( m_p2diary, nullptr ) };

    fc->set_from_string( m_definition );

    return fc;
}
