/* filterstash.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2022 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 "filterstash.hh"
#include "ajson.hh"
#include "filelock.hh"
#include "nmfilter.hh"
#include "nwc_fsentry.hh"
#include "virtualdirmode.hh"

FilterStash::FilterStash( const std::string &file_name ) :
    m_file_name( file_name )
{
    m_lock_file = m_file_name;
    m_lock_file += ".lck";
}

static bool is_known_set( const std::list< std::list< NM_Filter > > &stash,
                          const std::list< NM_Filter > &filters )
{
    // m_stash and filters must be sorted

    for ( auto &l : stash ) {
        auto it1 = l.begin();
        auto it2 = filters.begin();

        for (;; it1++, it2++ ) {
            if ( it1 == l.end() && it2 == filters.end() ) return true;

            if ( it1 == l.end() ) break;
            if ( it2 == filters.end() ) break;

            if ( *it1 != *it2 ) {
                break;
            }
        }
    }

    return false;
}

int FilterStash::stash( ListerMode *lm )
{
    if ( ! lm ) return -EINVAL;

    VirtualDirMode *vdm = dynamic_cast< VirtualDirMode * >( lm );

    if ( ! vdm ) return -ENOTSUP;

    FileLock d( m_lock_file );
    if ( ! d.lock() ) return -EAGAIN;

    std::list< NM_Filter > filters =  vdm->getFilters();
    filters.sort();

    filters.remove_if( []( const NM_Filter &f ){
        return f.getCheck() == NM_Filter::INACTIVE;
    } );

    read();

    if ( ! filters.empty() &&
         ! is_known_set( m_stash, filters ) ) {
        m_stash.push_front( filters );

        m_dirty = true;

        write();
    }

    return 0;
}

int FilterStash::applyTop( ListerMode *lm )
{
    if ( ! lm ) return -EINVAL;

    VirtualDirMode *vdm = dynamic_cast< VirtualDirMode * >( lm );

    if ( ! vdm ) {
        lm->not_supported();
        return -ENOTSUP;
    }

    FileLock d( m_lock_file );
    if ( ! d.lock() ) return -EAGAIN;

    read();

    if ( m_stash.empty() ) return -ENOENT;

    auto filters = m_stash.front();

    vdm->setFilters( filters );

    return 0;
}

int FilterStash::applyPop( ListerMode *lm )
{
    if ( ! lm ) return -EINVAL;

    VirtualDirMode *vdm = dynamic_cast< VirtualDirMode * >( lm );

    if ( ! vdm ) {
        lm->not_supported();
        return -ENOTSUP;
    }

    FileLock d( m_lock_file );
    if ( ! d.lock() ) return -EAGAIN;

    read();

    if ( m_stash.empty() ) return -ENOENT;

    auto filters = m_stash.front();

    m_stash.pop_front();

    vdm->setFilters( filters );

    m_dirty = true;

    write();

    return 0;
}

int FilterStash::applyEntry( ListerMode *lm, size_t pos )
{
    if ( ! lm ) return -EINVAL;

    VirtualDirMode *vdm = dynamic_cast< VirtualDirMode * >( lm );

    if ( ! vdm ) {
        lm->not_supported();
        return -ENOTSUP;
    }

    FileLock d( m_lock_file );
    if ( ! d.lock() ) return -EAGAIN;

    read();

    if ( m_stash.size() <= pos ) return -ENOENT;

    auto it = m_stash.begin();

    std::advance( it, pos );

    auto filters = *it;

    vdm->setFilters( filters );

    return 0;
}

int FilterStash::deleteEntry( size_t pos )
{
    FileLock d( m_lock_file );
    if ( ! d.lock() ) return -EAGAIN;

    read();

    if ( m_stash.size() <= pos ) return -ENOENT;

    auto it = m_stash.begin();

    std::advance( it, pos );

    m_stash.erase( it );

    m_dirty = true;

    write();

    return 0;
}

std::list< std::list< NM_Filter > > FilterStash::getStash()
{
    read();

    return m_stash;
}

int FilterStash::write()
{
    if ( ! m_dirty ) return 0;

    auto jobj = AJSON::make_object();
    auto jobj_stashes = AJSON::make_array();

    for ( auto &e : m_stash ) {
        auto jstash_obj = AJSON::make_object();

        auto jarr = AJSON::make_array();

        for ( auto &filter : e ) {
            auto jfilter = AJSON::make_object();

            jfilter->set( "filter", AJSON::make_string( filter.getPattern() ) );

            switch ( filter.getCheck() ) {
                case NM_Filter::EXCLUDE:
                    jfilter->set( "type", AJSON::make_string( "exclude" ) );
                    break;
                case NM_Filter::INCLUDE:
                    jfilter->set( "type", AJSON::make_string( "include" ) );
                    break;
                default:
                    jfilter->set( "type", AJSON::make_string( "inactive" ) );
                    break;
            }

            jarr->append( jfilter );
        }

        jstash_obj->set( "filters", jarr );

        jobj_stashes->append( jstash_obj );
    }

    jobj->set( "stashes" , jobj_stashes );

    std::string temp_target_file = m_file_name;
    temp_target_file += ".new";

    if ( AJSON::dump( jobj, temp_target_file ) == 0 ) {
        worker_rename( temp_target_file.c_str(), m_file_name.c_str() );
    } else {
        worker_unlink( temp_target_file.c_str() );
    }

    NWC::FSEntry t( m_file_name );
    m_lastsize = t.stat_size();
    m_lastmod = t.stat_lastmod();

    m_dirty = false;

    return 0;
}

int FilterStash::read()
{
    NWC::FSEntry fe( m_file_name );

    if ( ! fe.entryExists() ) return 0;

    bool changed = true;

    if ( fe.isLink() ) {
        if ( fe.stat_dest_lastmod() == m_lastmod &&
             fe.stat_dest_size() == m_lastsize ) {
            changed = false;
        }
    } else {
        if ( fe.stat_lastmod() == m_lastmod &&
             fe.stat_size() == m_lastsize ) {
            changed = false;
        }
    }

    if ( ! changed ) return 0;

    if ( fe.isLink() ) {
        m_lastmod = fe.stat_dest_lastmod();
        m_lastsize = fe.stat_dest_size();
    } else {
        m_lastmod = fe.stat_lastmod();
        m_lastsize = fe.stat_size();
    }

    m_stash.clear();

    auto jobj = AJSON::as_object( AJSON::load( m_file_name ) );

    if ( ! jobj ) return -EINVAL;

    auto jstashes = AJSON::as_array( jobj->get( "stashes" ) );

    if ( jstashes ) {
        for ( size_t pos = 0; pos < jstashes->size(); pos++ ) {
            auto jstash_obj = AJSON::as_object( jstashes->get( pos ) );

            if ( ! jstash_obj ) continue;

            auto jfilters = AJSON::as_array( jstash_obj->get( "filters" ) );

            if ( ! jfilters ) continue;

            std::list< NM_Filter > filters;

            for ( size_t filter_pos = 0; filter_pos < jfilters->size(); filter_pos++ ) {
                auto jfilter = AJSON::as_object( jfilters->get( filter_pos ) );

                if ( ! jfilter ) continue;

                NM_Filter filter;

                auto jtype = AJSON::as_string( jfilter->get( "type" ) );
                auto jfilter_str = AJSON::as_string( jfilter->get( "filter" ) );

                if ( ! jtype || ! jfilter_str ) continue;

                filter.setPattern( jfilter_str->get_value().c_str() );

                if ( jtype->get_value() == "include" ) {
                    filter.setCheck( NM_Filter::INCLUDE);
                } else if ( jtype->get_value() == "exclude" ) {
                    filter.setCheck( NM_Filter::EXCLUDE );
                } else {
                    filter.setCheck( NM_Filter::INACTIVE );
                }

                filters.push_back( filter );
            }

            filters.sort();

            m_stash.push_back( filters );
        }
    }

    return 0;
}
