/* exprfilter_evalatoms.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2014-2024 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "exprfilter_evalatoms.hh"
#include "exprfilter_eval.hh"
#include "nwcentryselectionstate.hh"
#include "wcfiletype.hh"
#include "wconfig.h"
#include <algorithm>

ExprAtomStringCmp::ExprAtomStringCmp( const std::string &val,
                                      eval_atom_cmp_t cmp_type ) : ExprAtomCmp( cmp_type ),
                                                                   m_val( val )
{
    if ( cmp_type == EAC_EQ ||
         cmp_type == EAC_NE ) {
        m_matcher.setMatchString( val );
    } else if ( cmp_type == EAC_APPROX ) {
        std::string flexible_string_filter = "*" + StringMatcherFNMatch::convertMatchStringToFlexibleMatch( val ) + "*";

        m_matcher.setMatchString( flexible_string_filter );
    }
}

bool ExprAtomStringCmp::match( const std::string &val )
{
    if ( m_cmp_type == EAC_EQ ||
         m_cmp_type == EAC_APPROX ) {
        return m_matcher.match( val );
    } else if ( m_cmp_type == EAC_NE ) {
        return ! m_matcher.match( val );
    } else {
        int res = strcmp( val.c_str(),
                          m_val.c_str() );

        switch ( m_cmp_type ) {
            case EAC_LT:
                if ( res < 0 ) return true;
                break;
            case EAC_LE:
                if ( res <= 0 ) return true;
                break;
            case EAC_GE:
                if ( res >= 0 ) return true;
                break;
            case EAC_GT:
                if ( res > 0 ) return true;
                break;
            default:
                break;
        }
    }

    return false;
}

void ExprAtomNameCmp::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    auto fse = element.getNWCEntry();

    if ( fse ) {
        vm.pushValue( match( fse->getBasename() ) );
    }
}

void ExprAtomTypeCmp::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    auto ft = element.getEffectiveFiletype();

    if ( ft ) {
        if ( ft == wconfig->getnotyettype() ) {
            // type not yet known so always match because otherwise
            // we are not able to update since hidden entries
            // are not tested
            vm.pushValue( true );
        } else {
            if ( ft->nameContainsFlags() ) {
                auto fpr = std::make_shared< NWCFlagProducer >( &element, nullptr );
                FlagReplacer rpl( fpr );

                vm.pushValue( match( rpl.replaceFlags( ft->getName(), false ) ) );
            } else {
                vm.pushValue( match( ft->getName() ) );
            }
        }
    } else {
        vm.pushValue( false );
    }
}

void ExprAtomSizeCmp::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    auto fse = element.getNWCEntry();

    if ( ! fse ) {
        vm.pushValue( false );
        return;
    }
    
    loff_t size = fse->stat_size();

    if ( m_follow ) {
        if ( fse->isLink() && ! fse->isBrokenLink() ) {
            size = fse->stat_dest_size();
        }
    }

    switch ( m_cmp_type ) {
        case EAC_LT:
            vm.pushValue( size < m_size );
            break;
        case EAC_LE:
            vm.pushValue( size <= m_size );
            break;
        case EAC_NE:
            vm.pushValue( size != m_size );
            break;
        case EAC_GE:
            vm.pushValue( size >= m_size );
            break;
        case EAC_GT:
            vm.pushValue( size > m_size );
            break;
        case EAC_APPROX:
            {
                if ( m_size == 0 ) {
                    // arbitrary chosen, less than 100 bytes is near 0
                    vm.pushValue( size < 100 );
                } else {
                    double v = (double)size / (double)m_size;

                    vm.pushValue( v >= 0.9 && v <= 1.1 );
                }
            }
            break;
        default: //EQ
            vm.pushValue( size == m_size );
            break;
    }
}

static void mergeAndList( const std::list< int > &l1,
                          const std::list< int > &l2,
                          std::list< int > &out )
{
    if ( l1.empty() && l2.empty() ) {
        // just keep result empty
        return;
    }

    if ( l1.empty() || l1.front() == -1 ) {
        for ( auto &e2 : l2 ) {
            out.push_back( e2 );
        }

        return;
    }

    if ( l2.empty() || l2.front() == -1 ) {
        for ( auto &e1 : l1 ) {
            out.push_back( e1 );
        }

        return;
    }

    std::set_intersection( l1.cbegin(), l1.cend(),
                           l2.cbegin(), l2.cend(),
                           std::back_inserter( out ) );
}

void ExprAtomAnd::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    try {
        auto v1 = vm.popValue();

        try {
            auto v2 = vm.popValue();

            if ( v1.result && v2.result ) {
                std::list< int > res;
                mergeAndList( v1.attributes,
                              v2.attributes,
                              res );
                vm.pushValue( { true, res } );
            } else {
                vm.pushValue( false );
            }
        } catch ( int ) {
            vm.pushValue( v1 );
            vm.setErrorOccured();
        }
    } catch ( int ) {
        vm.setErrorOccured();
    }
}

static void mergeOrList( const std::list< int > &l1,
                         const std::list< int > &l2,
                         std::list< int > &out )
{
    if ( l1.empty() && l2.empty() ) {
        // just keep result empty
        return;
    }

    if ( l1.empty() ) {
        if ( l2.front() >= 0 ) {
            out.push_back( -1 );
        }
        for ( auto &e2 : l2 ) {
            out.push_back( e2 );
        }

        return;
    }

    if ( l2.empty() ) {
        if ( l1.front() >= 0 ) {
            out.push_back( -1 );
        }
        for ( auto &e1 : l1 ) {
            out.push_back( e1 );
        }

        return;
    }

    // both sides are not empty, no need to add a -1
    std::set_union( l1.cbegin(), l1.cend(),
                    l2.cbegin(), l2.cend(),
                    std::back_inserter( out ) );
}

void ExprAtomOr::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    try {
        auto v1 = vm.popValue();

        try {
            auto v2 = vm.popValue();

            if ( v1.result && v2.result ) {
                std::list< int > res;
                mergeOrList( v1.attributes,
                             v2.attributes,
                             res );
                vm.pushValue( { true, res } );
            } else if ( v1.result ) {
                vm.pushValue( v1 );
            } else if ( v2.result ) {
                vm.pushValue( v2 );
            } else {
                vm.pushValue( false );
            }
        } catch ( int ) {
            vm.pushValue( v1 );
            vm.setErrorOccured();
        }
    } catch ( int ) {
        vm.setErrorOccured();
    }
}

void ExprAtomStateCmp::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    if ( m_val == "sel" ||
         m_val == "selected" ) {

        bool v = element.getSelected();
    
        switch ( m_cmp_type ) {
            case EAC_EQ:
                vm.pushValue( v );
                break;
            case EAC_NE:
                vm.pushValue( ! v );
                break;
            default:
                vm.pushValue( false );
                break;
        }
    } else {
        vm.pushValue( false );
    }
}

void ExprAtomTimeCmp::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    auto fse = element.getNWCEntry();

    if ( ! fse ) {
        vm.pushValue( false );
        return;
    }

    time_t now = time( NULL );
    time_t t = 0;

    if ( m_time_cmp_type == COMPARE_ACCESS ) {
        t = fse->stat_lastaccess();
    } else {
        t = fse->stat_lastmod();
    }

    time_t time_to_compare;

    if ( m_relative_to_now ) {
        time_to_compare = now - m_time_stamp;
    } else {
        time_to_compare = m_time_stamp;
    }

    // invert some boolean in relative mode
    switch ( m_cmp_type ) {
        case EAC_LT:
            vm.pushValue( t < time_to_compare ? ! m_relative_to_now : m_relative_to_now );
            break;
        case EAC_LE:
            vm.pushValue( t <= time_to_compare ? ! m_relative_to_now : m_relative_to_now );
            break;
        case EAC_NE:
            vm.pushValue( t != time_to_compare );
            break;
        case EAC_GE:
            vm.pushValue( t >= time_to_compare ? ! m_relative_to_now : m_relative_to_now );
            break;
        case EAC_GT:
            vm.pushValue( t > time_to_compare ? ! m_relative_to_now : m_relative_to_now );
            break;
        case EAC_APPROX:
            // handle approx like EQ
        default: //EQ
            vm.pushValue( t == time_to_compare );
            break;
    }
}

void ExprAtomContentCmp::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    auto fse = element.getNWCEntry();

    if ( ! fse ) {
        vm.pushValue( false );
        return;
    }

    if ( ! fse->isReg( m_follow ) ) {
        vm.pushValue( false );
        return;
    }

    std::list< int > matches;

    int res = m_content_checker.checkContent( *fse,
                                              [&vm]() {
                                                  return vm.checkInterrupt();
                                              },
                                              [&vm]( const std::string &feedback ) {
                                                  vm.reportFeedback( feedback );
                                              },
                                              [&matches]( const NWC::FSEntry &entry, int line_number ) {
                                                  matches.push_back( line_number );
                                              } );

    if ( m_cmp_type == EAC_EQ ||
         m_cmp_type == EAC_APPROX ) {
        if ( res != 0 ) {
            vm.pushValue( { true, matches } );
        } else {
            vm.pushValue( false );
        }
        return;
    } else if ( m_cmp_type == EAC_NE ) {
        if ( res == 0 ) {
            vm.pushValue( { true, matches } );
        } else {
            vm.pushValue( false );
        }
        return;
    }

    vm.pushValue( false );
    return;
}

void ExprAtomPseudoSkip::eval( const NWCEntrySelectionState &element, ExprFilterEval &vm )
{
    // this is a pseudo atom which does nothing
}

void ExprAtomPseudoSkip::setSkipAtoms( size_t v )
{
    m_skip_atoms = v;
}

size_t ExprAtomPseudoSkip::getSkipAtoms() const
{
    return m_skip_atoms;
}
