/* exprfilter_parser.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_parser.hh"
#include "exprfilter_eval.hh"
#include "exprfilter_evalatoms.hh"
#include "aguix/util.h"
#include <set>

ExprToken ExprFilterParser::getCurrentToken() const
{
    if ( m_tokens.empty() ) return ExprToken( ExprToken::NONE );

    return m_tokens.front();
}

void ExprFilterParser::advanceToken()
{
    if ( m_tokens.empty() ) abort();

    ExprToken t = m_tokens.front();

    m_reconstructed_filter += t.getValue();

    m_tokens.pop_front();
}

bool ExprFilterParser::hasNextToken() const
{
    return ! m_tokens.empty();
}

void ExprFilterParser::parse( ExprFilterEval *ee )
{
    m_ee = ee;
    
    expr();

    if ( hasNextToken() ) {
    }

    m_ee = nullptr;
}

void ExprFilterParser::expr()
{
    if ( ! m_valid ) return;

    fexpr();

    if ( getCurrentToken() == ExprToken::AND ) {
        advanceToken();

        auto skip_atom = std::make_shared< ExprAtomPseudoSkip >();
        m_ee->pushAtom( skip_atom );
        
        // get old length of atom list
        size_t current_atom_length = m_ee->getAtomsLength();

        and_expr();

        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >( new ExprAtomAnd() ) );

        skip_atom->setSkipAtoms( m_ee->getAtomsLength() - current_atom_length );
    } else if ( getCurrentToken() == ExprToken::OR ) {
        advanceToken();

        or_expr();

        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >( new ExprAtomOr() ) );
    } else {
        pushExpectedStrings( { "&&", "||" } );
    }

    return;
}

bool ExprFilterParser::is_known_attribute( const std::string &name ) const
{
    if ( m_options.disabled_tokens.count( name ) > 0 ) {
        return false;
    }

    if ( name == "name" ||
         name == "size" ||
         name == "type" ||
         name == "state" ||
         name == "timeaccess" ||
         name == "timemod" ||
         name == "n" ||
         name == "s" ||
         name == "t" ||
         name == "st" ||
         name == "ta" ||
         name == "tm" ||
         name == "content" ||
         name == "c" ) {
        return true;
    }

    return false;
}

void ExprFilterParser::fexpr()
{
    if ( ! m_valid ) return;

    if ( getCurrentToken() == ExprToken::OPEN_PAR ) {
        advanceToken();

        expr();

        if ( ! ( getCurrentToken() == ExprToken::CLOSE_PAR ) ) {
            pushExpectedStrings( { ")" } );

            m_valid = false;
        } else {
            advanceToken();
        }
    } else if ( getCurrentToken() == ExprToken::WORD ||
                getCurrentToken() == ExprToken::NUMBER ) {
        ExprToken v1 = getCurrentToken();
        
        advanceToken();

        ExprToken v2 = getCurrentToken();
        ExprToken suffix( ExprToken::NONE );

        if ( v1 == ExprToken::NUMBER && v2 == ExprToken::WORD ) {
            suffix = v2;

            advanceToken();

            v2 = getCurrentToken();
        }

        if ( v1 == ExprToken::WORD ) {
            v1.convertOptions();
        }

        if ( v2 == ExprToken::LT ||
             v2 == ExprToken::LE ||
             v2 == ExprToken::EQ ||
             v2 == ExprToken::NE ||
             v2 == ExprToken::GE ||
             v2 == ExprToken::GT ||
             v2 == ExprToken::APPROX ) {

            advanceToken();

            ExprToken v3 = getCurrentToken();

            if ( v3 == ExprToken::WORD ||
                 v3 == ExprToken::NUMBER ||
                 v3 == ExprToken::DATE ) {

                advanceToken();

                if ( v3 == ExprToken::NUMBER && getCurrentToken() == ExprToken::WORD ) {
                    suffix = getCurrentToken();

                    advanceToken();
                }

                // valid expression, generate cmp atom
                bool valid = false;

                if ( v1 == ExprToken::WORD && is_known_attribute( v1.getValue() ) ) {
                    valid = true;
                } else if ( v3 == ExprToken::WORD && is_known_attribute( v3.getValue() ) ) {
                    // switch sides
                    ExprToken t = v1;
                    v1 = v3;
                    v3 = t;

                    if ( v2 == ExprToken::LT ) {
                        v2 = ExprToken( ExprToken::GT );
                    } else if ( v2 == ExprToken::LE ) {
                        v2 = ExprToken( ExprToken::GE );
                    } else if ( v2 == ExprToken::GE ) {
                        v2 = ExprToken( ExprToken::LE );
                    } else if ( v2 == ExprToken::GT ) {
                        v2 = ExprToken( ExprToken::LT );
                    }

                    valid = true;
                } else {
                    m_valid = false;
                }

                if ( valid ) {
                    ExprAtomCmp::eval_atom_cmp_t cmp_type = ExprAtomCmp::EAC_EQ;

                    if ( v2 == ExprToken::LT ) {
                        cmp_type = ExprAtomCmp::EAC_LT;
                    } else if ( v2 == ExprToken::LE ) {
                        cmp_type = ExprAtomCmp::EAC_LE;
                    } else if ( v2 == ExprToken::EQ ) {
                        cmp_type = ExprAtomCmp::EAC_EQ;
                    } else if ( v2 == ExprToken::NE ) {
                        cmp_type = ExprAtomCmp::EAC_NE;
                    } else if ( v2 == ExprToken::GE ) {
                        cmp_type = ExprAtomCmp::EAC_GE;
                    } else if ( v2 == ExprToken::GT ) {
                        cmp_type = ExprAtomCmp::EAC_GT;
                    } else if ( v2 == ExprToken::APPROX ) {
                        cmp_type = ExprAtomCmp::EAC_APPROX;
                    }

                    if ( v1.getValue() == "name" ||
                         v1.getValue() == "n" ) {
                        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >
                                        ( new ExprAtomNameCmp( v3.getValue(),
                                                               cmp_type ) ) );
                    } else if ( v1.getValue() == "type" ||
                                v1.getValue() == "t" ) {
                        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >
                                        ( new ExprAtomTypeCmp( v3.getValue(),
                                                               cmp_type ) ) );
                    } else if ( v1.getValue() == "state" ||
                                v1.getValue() == "st" ) {
                        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >
                                        ( new ExprAtomStateCmp( v3.getValue(),
                                                                cmp_type ) ) );
                    } else if ( v1.getValue() == "timeaccess" ||
                                v1.getValue() == "ta" ||
                                v1.getValue() == "timemod" ||
                                v1.getValue() == "tm" ) {

                        time_t t;
                        bool rel_time = false;

                        if ( v3 == ExprToken::DATE ) {
                            if ( AGUIXUtils::parseStringToTime( v3.getValue(), &t ) ) {
                                rel_time = false;
                            } else {
                                m_valid = valid = false;
                            }
                        } else if ( v3 == ExprToken::NUMBER ) {
                            loff_t v = 0;

                            AGUIXUtils::convertFromString( v3.getValue(), v );

                            if ( suffix == ExprToken::WORD ) {
                                std::string s = AGUIXUtils::tolower( suffix.getValue() );
                            
                                if ( s == "s" || s == "sec" ) {
                                } else if ( s == "m" || s == "min" || s == "mins" ) {
                                    v *= 60;
                                } else if ( s == "h" || s == "hour" || s == "hours" ) {
                                    v *= 60 * 60;
                                } else if ( s == "d" || s == "day" || s == "days" ) {
                                    v *= 60 * 60 * 24;
                                } else if ( s == "w" || s == "week" || s == "weeks" ) {
                                    v *= 7 * 60 * 60 * 24;
                                } else {
                                    m_valid = valid = false;
                                }
                            }

                            if ( v <= 1LL * 1000 * 1000 * 1000 ) {
                                t = (time_t)v;
                                rel_time = true;
                            } else {
                                m_valid = valid = false;
                            }
                        } else {
                            m_valid = valid = false;
                        }

                        if ( valid ) {
                            ExprAtomTimeCmp::time_cmp_t time_cmp_type;

                            if ( v1.getValue() == "timeaccess" ||
                                 v1.getValue() == "ta" ) {
                                time_cmp_type = ExprAtomTimeCmp::COMPARE_ACCESS;
                            } else {
                                time_cmp_type = ExprAtomTimeCmp::COMPARE_MOD;
                            }

                            m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >
                                            ( new ExprAtomTimeCmp( t,
                                                                   rel_time,
                                                                   time_cmp_type,
                                                                   cmp_type ) ) );
                        }
                    } else if ( v1.getValue() == "size" ||
                                v1.getValue() == "s" ) {
                        loff_t v = 0;

                        AGUIXUtils::convertFromString( v3.getValue(), v );

                        if ( suffix == ExprToken::WORD ) {
                            std::string s = AGUIXUtils::tolower( suffix.getValue() );
                            
                            if ( s == "kb" || s == "k" ) {
                                v *= 1000;
                            } else if ( s == "kib" ) {
                                v *= 1024;
                            } else if ( s == "mb" || s == "m" ) {
                                v *= 1000000;
                            } else if ( s == "mib" ) {
                                v *= 1024 * 1024;
                            } else if ( s == "gb" || s == "g" ) {
                                v *= 1000000000;
                            } else if ( s == "gib" ) {
                                v *= 1024 * 1024 * 1024;
                            } else {
                                m_valid = valid = false;
                            }
                        }

                        if ( valid ) {
                            bool follow = m_options.follow;

                            for ( auto &o : v1.getOptions() ) {
                                if ( o == "follow" ||
                                     o == "f" ) {
                                    follow = true;
                                }
                            }

                            m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >
                                            ( new ExprAtomSizeCmp( v,
                                                                   follow,
                                                                   cmp_type ) ) );
                        }
                    } else if ( v1.getValue() == "content" ||
                                v1.getValue() == "c" ) {
                        bool case_sensitive = false;
                        loff_t size_limit = m_options.content_check_size_limit;
                        bool follow = m_options.follow;

                        for ( auto &o : v1.getOptions() ) {
                            if ( o == "case" ||
                                 o == "c" ) {
                                case_sensitive = true;
                            } else if ( o == "follow" ||
                                        o == "f" ) {
                                follow = true;
                            } else if ( ! o.empty() && std::isdigit( o[0] ) ) {
                                size_limit = AGUIXUtils::convertHumanStringToNumber( o );
                            }
                        }

                        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >
                                        ( new ExprAtomContentCmp( v3.getValue(),
                                                                  case_sensitive,
                                                                  follow,
                                                                  size_limit,
                                                                  cmp_type ) ) );
                    }
                }
            } else {
                if ( v1.getValue() == "name" ||
                     v1.getValue() == "n" ) {
                    pushExpectedTokens( { { "*foo*", "file name pattern" } } );
                } else if ( v1.getValue() == "type" ||
                            v1.getValue() == "t" ) {
                    pushExpectedTokens( { { "*bar*", "type pattern" } } );
                } else if ( v1.getValue() == "state" ||
                            v1.getValue() == "st" ) {
                    pushExpectedStrings( { "selected" } );
                } else if ( v1.getValue() == "size" ||
                            v1.getValue() == "s" ) {
                    pushExpectedTokens( { { "123", "number" } } );
                } else if ( v1.getValue() == "timeaccess" ||
                            v1.getValue() == "ta" ||
                            v1.getValue() == "timemod" ||
                            v1.getValue() == "tm") {
                    pushExpectedTokens( { { "1m", "time (YYYY-MM-DD or 5s, 3m, 4h, ...)" } } );
                } else if ( v1.getValue() == "content" ||
                            v1.getValue() == "c" ) {
                    pushExpectedTokens( { { ".*", "regular expression" } } );
                }

                m_valid = false;
            }
        } else {
            if ( v1.getValue() == "content" ||
                 v1.getValue() == "c" ) {
                pushExpectedStrings( { "==", "!=" } );

                if ( v1.getOptions().empty() ) {
                    pushExpectedTokens( { { ",case,follow,123", ",case[,follow][,<size limit>]" } } );
                }
            } else if ( v1.getValue() == "size" ||
                        v1.getValue() == "s" ) {
                pushExpectedStrings( { "<", "<=", "==", "!=", ">=", ">", "~" } );
                if ( v1.getOptions().empty() ) {
                    pushExpectedStrings( { ",follow" } );
                }
            } else {
                pushExpectedStrings( { "<", "<=", "==", "!=", ">=", ">", "~" } );
            }

            m_valid = false;
        }
    } else {
        pushExpectedStrings( { "name", "size", "type", "state",
                    "timemod", "timeaccess",
                    "content",
                    "(" } );

        m_valid = false;
    }

    if ( m_valid ) {
        valid_filter();
    }

    return;
}

void ExprFilterParser::and_expr()
{
    if ( ! m_valid ) return;

    fexpr();

    if ( getCurrentToken() == ExprToken::AND ) {
        advanceToken();

        auto skip_atom = std::make_shared< ExprAtomPseudoSkip >();
        m_ee->pushAtom( skip_atom );
        
        // get old length of atom list
        size_t current_atom_length = m_ee->getAtomsLength();

        and_expr();

        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >( new ExprAtomAnd() ) );

        skip_atom->setSkipAtoms( m_ee->getAtomsLength() - current_atom_length );
    } else {
        pushExpectedStrings( { "&&" } );
    }

    return;
}

void ExprFilterParser::or_expr()
{
    if ( ! m_valid ) return;

    fexpr();

    if ( getCurrentToken() == ExprToken::OR ) {
        advanceToken();

        or_expr();

        m_ee->pushAtom( std::shared_ptr< ExprFilterEvalAtom >( new ExprAtomOr() ) );
    } else {
        pushExpectedStrings( { "||" } );
    }

    return;
}

void ExprFilterParser::valid_filter()
{
    m_valid_reconstructed_filter = m_reconstructed_filter;
}

std::string ExprFilterParser::getValidReconstructedFilter()
{
    return m_valid_reconstructed_filter;
}

void ExprFilterParser::pushExpectedStrings( const std::list< std::string > &l )
{
    if ( m_valid && ! hasNextToken() ) {
        for ( auto &s : l ) {
            if ( m_options.disabled_tokens.count( s ) > 0 ) {
                continue;
            }
            m_expected_tokens.push_back( { s, s } );
        }
    }
}

void ExprFilterParser::pushExpectedTokens( const std::list< token_help > &l )
{
    if ( m_valid && ! hasNextToken() ) {
        for ( auto &s : l ) {
            m_expected_tokens.push_back( s );
        }
    }
}

std::list< ExprFilterParser::token_help > ExprFilterParser::getListOfExpectedTokens() const
{
    return m_expected_tokens;
}

bool ExprFilterParser::valid() const
{
    return m_valid;
}
