/* pathjumpui.hh
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2012-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
 */

#ifndef PATHJUMPUI_HH
#define PATHJUMPUI_HH

#include "wdefines.h"
#include <memory>
#include <string>
#include <list>
#include <vector>
#include <set>
#include "nwc_virtualdir.hh"
#include "stringmatcher_flexiblematch.hh"
#include "stringmatcher_fnmatch.hh"
#include "aguix/fieldlistview.h"
#include <future>
#include "existence_test.hh"

class DeepPathStore;
class Button;
class FieldListView;
class Text;
class AGUIX;
class SolidButton;
class AContainer;
class Worker;
class ChooseButton;
class CycleButton;
class Worker;
class Kartei;

class PathJumpUI
{
public:
    PathJumpUI( AGUIX &aguix, DeepPathStore &path_store, Worker &worker );
    ~PathJumpUI();

    typedef enum {
        SHOW_BY_TIME,
        SHOW_BY_FILTER,
        SHOW_BY_PROGRAM
    } initial_tab_t;

    typedef enum {
        SHOW_ALL,
        SHOW_ONLY_SUBDIRS,
        SHOW_ONLY_DIRECT_SUBDIRS
    } entry_filter_mode_t;

    int mainLoop( initial_tab_t show_tab );
    std::string getSelectedPath( bool &stored );

    void setCurrentDirname( const std::string &dirname );
    void setCurrentBasename( const std::string &basename );

    void setEntryFilterMode( entry_filter_mode_t m );
    void setShowAllData( bool v );
    void setHideNonExisting( bool v );
    void setStartLockDirectory( const std::string &dir );

    std::unique_ptr< NWC::Dir > getResultsAsDir();
private:
    AGUIX &m_aguix;
    Worker &m_worker;
    DeepPathStore &m_path_store;

    std::string m_dirname, m_basename;
    std::string m_filter;
    std::string m_byfilter_filter;
    std::string m_byprog_filter;
    std::string m_selected_path;
    bool m_selected_path_stored = false;
    
    std::unique_ptr<class AWindow> m_win;
    AWindow *m_subwin1;
    AWindow *m_subwin2;
    FieldListView *m_lv;
    FieldListView *m_class_counts_lv;
    FieldListView *m_lv_progs;
    FieldListView *m_lv_prog_files;
    Text *m_infixtext;
    Button *m_hold_status_b;
    Button *m_lock_dir_status_b = nullptr;
    Text *m_hold_filter_text;
    Text *m_lock_dir_text = nullptr;
    Button *m_okb;
    Button *m_cancelb;
    Button *m_cleanupb;
    Button *m_cleanup_archiveb;
    Button *m_prefixcleanupb;
    Button *m_panelize_b;
    AContainer *m_co1;
    AContainer *m_mainco;
    AContainer *m_hold_co;
    AContainer *m_lock_dir_co = nullptr;
    ChooseButton *m_show_all_cb;
    ChooseButton *m_ignore_date_cb;
    CycleButton *m_filter_mode_cyb;
    Button *m_plus1h_b;
    Button *m_plus1d_b;
    Button *m_plus1w_b;
    Button *m_age_reset_b;
    Text *m_age_text;
    FieldListView *m_byfilter_lv;
    Text *m_byfilter_filter_t;
    Button *m_byfilter_jump_b;
    Button* m_byfilter_close_b;
    Kartei *m_k1;
    Text *m_byprog_filter_t;
    Button *m_byprog_jump_b;
    Button* m_byprog_close_b;
    Button* m_byprog_clean_b;
    ChooseButton *m_show_relative_paths_cb;
    ChooseButton *m_hide_non_existing_cb;
    ChooseButton *m_byprog_hide_non_existing_cb;
    ChooseButton *m_include_archive_cb = nullptr;

    time_t m_entry_filter_age;

    typedef enum {
        PATH_HIDE,
        PATH_SHOW,
        BOOKMARK_HIDE,
        BOOKMARK_SHOW,
    } entry_visibility_t;

    struct pathjump_entry {
        entry_visibility_t m_type;
        int m_blockcount;
        std::string m_path;
        bool m_post_filtering;
        bool m_always_ignore;
        time_t m_last_access;
        ExistenceTest::existence_state_t m_exists{ .exists = ExistenceTest::EXISTS_UNKNOWN,
            .path_length = 0 };

        pathjump_entry() = delete;
        pathjump_entry( entry_visibility_t type,
                        int blockcount,
                        const std::string &path,
                        time_t last_access ) : m_type( type ),
                                               m_blockcount( blockcount ),
                                               m_path( path ),
                                               m_post_filtering( true ),
                                               m_always_ignore( false ),
                                               m_last_access( last_access )
        {}

        static bool compare( const pathjump_entry &lhs,
                             const pathjump_entry &rhs,
                             bool ignore_time )
        {
            if ( lhs.m_blockcount < rhs.m_blockcount ) return true;
            else if ( lhs.m_blockcount == rhs.m_blockcount ) {
                if ( ignore_time ) {
                    if ( lhs.m_path < rhs.m_path ) return true;
                } else {
                    if ( lhs.m_last_access > rhs.m_last_access ) return true;
                    else if ( lhs.m_last_access == rhs.m_last_access ) {
                        if ( lhs.m_path < rhs.m_path ) return true;
                    }
                }
            }

            return false;
        }
        
        bool operator<( const pathjump_entry &rhs ) const
        {
            return compare( *this, rhs, true );
        }
    };

    struct apply_filter_state {
        apply_filter_state() : matches( 0 ),
                               use_flexible( true ),
                               exact_path_matches( 0 ),
                               exact_bookmark_matches( 0 ),
                               path_matches( 0 ),
                               bookmark_matches( 0 ),
                               prefer_exact_matches( false ),
                               done ( false )
        {}
    
        int matches;

        StringMatcherFlexibleMatch flmatch;
        StringMatcherFNMatch match;
        bool use_flexible;

        std::string flexible_string_filter;
        StringMatcherFNMatch flprematcher;

        int exact_path_matches, exact_bookmark_matches,
            path_matches, bookmark_matches;
        bool prefer_exact_matches;

        std::vector< PathJumpUI::pathjump_entry >::iterator it1;

        bool done;

        std::string filter;

        std::function< void( int ) > finalize;
    };

    struct best_hit {
        std::string filter;
        std::string path;
        float value;
        bool visible;
        time_t last_access;
        ExistenceTest::existence_state_t exists;
    };

    struct program_entry {
        std::string program;
        time_t last_access;
        bool visible;
    };

    struct program_path_entry {
        std::string path;
        time_t last_access;
        ExistenceTest::existence_state_t exists;
    };

    apply_filter_state m_apply_state;

    std::vector< pathjump_entry > m_entries;
    AContainer *m_breadcrumb_co;
    std::vector< Button * > m_breadcrumb_buttons;
    int m_current_depth;
    std::vector< std::pair< std::string, bool > > m_current_components;
    std::list< std::string > m_bookmarks;
    std::vector< best_hit > m_best_hits;

    std::list< program_entry > m_used_programs;
    std::map< std::string, std::vector< program_path_entry > > m_program_paths;

    bool m_show_hidden_entries;

    entry_filter_mode_t m_entry_filter_mode;

    std::string m_current_entry_byfilter;
    size_t m_current_entry_pos = 0;

    bool m_lv_dirty;
    bool m_byfilter_lv_dirty;
    bool m_byprog_lv_dirty;

    std::vector< std::tuple< std::string, time_t, ExistenceTest::existence_state_t, size_t > > m_entry_fullnames;
    std::vector< int > m_byfilter_row_to_element;
    std::list< std::tuple< std::future< ExistenceTest::existence_state_t >, int, bool > > m_existence_tests;
    std::list< std::tuple< std::future< ExistenceTest::existence_state_t >, int, bool > > m_existence_tests_filter_path;
    std::list< std::tuple< std::future< ExistenceTest::existence_state_t >, int, bool > > m_existence_tests_prog_path;

    struct hold_status {
        hold_status() : active( false ),
                        base_show_hidden_state( false )
        {}

        bool active;
        std::string current_filter;
        bool base_show_hidden_state;
    } m_hold_status;

    std::list< std::pair< int, int > > m_match_classes;

    static int s_pathjump_number;

    void showData();
    void showProgramData();
    void showPathsForProgram( int row );
    void maximizeWin();

    void apply_filter( const std::string filter,
                       std::function< void( int ) > finalize );
    void apply_filter_next();

    int update_breadcrumb();

    void highlight_best_hit( const std::string &filter );

    int find_best_matching_depth( const std::string &filter );
    int find_best_matching_depth( const std::string &path,
                                  const std::string &filter );
    int build_current_path_components();

    int post_filtering();

    int find_entry( const std::string &name, const std::string &filter );
    int set_entry( const std::string &name );

    int cleanup( AWindow *win, const std::string &base, time_t unused_since );
    int cleanupPrefixDB( AWindow *win, int unused_since_days );
    int cleanupProgramDB( AWindow *win, int unused_since_days );
    int cleanupArchive( AWindow *win, const std::string &base, time_t unused_since_days, bool also_existing );

    int resetHeldEntries();
    void resetHeldEntriesAndUpdateUI();
    int holdCurrentEntries( int top_x );
    int holdCurrentEntriesAndUpdateUI( int top_x );
    int calculateMatchClasses();

    int handleMatchClassRow( int row );
    int handleMainLVRowChange( int row );
    int handleByFilterLVRowChange( int row );

    void showHoldHelp();

    void updateLVData( int row, int field,
                       struct FieldListView::on_demand_data &data );
    void updateFilterLVData( int row, int field,
                             struct FieldListView::on_demand_data &data );
    void updateProgFileLVData( int row, int field,
                               struct FieldListView::on_demand_data &data );

    void updateAgeText();
    
    int storeCleanUp();
    int storeCleanUpArchive();

    int prefixDBCleanUp();
    int programDBCleanUp();

    std::string getCurrentPath();

    void checkExistResults();
    void moveTests();

    void buildMatchClassesLV();

    void prepare_entries( bool include_archive,
                          int &max_path_components );
    void rebuild_breadcrumb( int components );
    void update_entries();

    std::string get_current_entry() const;

    void lock_to_current_path();
    void unlock_directory();
    bool is_locked_on_directory() const;
    void set_lock_dir( const std::string &path );

    struct {
        StringMatcherFlexibleMatch matcher;
        size_t prefix_filter_length;
    } m_ondemand_info;

    time_t m_current_time;
    bool m_ignore_date_for_sorting;
    time_t m_oldest_time;

    std::string m_previous_apply_filter;

    bool m_relative_view_mode = true;

    bool m_hide_non_existing = false;

    bool m_include_archive = false;

    std::string m_lock_base_dir;
};

#endif
