/* textviewmode.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2011-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 "textviewmode.hh"
#include "aguix/acontainer.h"
#include "aguix/textview.h"
#include "worker_locale.h"
#include "wconfig.h"
#include "worker.h"
#include "fileentry.hh"
#include "nmspecialsourceext.hh"
#include "textstoragefile.h"
#include "execlass.h"
#include "wpucontext.h"
#include "pathname_watcher.hh"
#include "run_custom_action.hh"
#include "wcfiletype.hh"

const char *TextViewMode::type = "TextViewMode";

#define TVM_DEFAULT_SIZE ( 16 * 1024 )

TextViewMode::TextViewMode( Lister *parent ) : ListerMode( parent )
{
    m_lastactivefe = NULL;
    m_tv = NULL;
    m_cont = NULL;
    m_info_text = nullptr;
    m_file_name_text = nullptr;
    m_file_percentage_text = nullptr;

    registerCommand( "up", [this]{ command_up(); } );
    registerCommand( "down", [this]{ command_down(); } );
    registerCommand( "first", [this]{ command_first(); } );
    registerCommand( "last", [this]{ command_last(); } );
    registerCommand( "pageup", [this]{ command_pageup(); } );
    registerCommand( "pagedown", [this]{ command_pagedown(); } );
}

TextViewMode::~TextViewMode()
{
    clearLastActiveFE();
}

void TextViewMode::messageHandler( AGMessage *msg )
{
    bool ma = false;
    switch ( msg->type ) {
        case AG_KEYPRESSED:
            if ( parentlister->isActive() == true ) {
                m_tv->handleKeyMessage( msg->key.key,
                                        msg->key.keystate );
            }
            break;
        case AG_MOUSECLICKED:
            if ( m_tv->isParent( msg->mouse.window, false ) ) {
                ma = true;
            }
            break;
        case AG_TEXTVIEW_END_REACHED:
            if ( msg->textview.textview == m_tv &&
                 m_source_mode == ACTIVE_FILE_OTHER_SIDE &&
                 m_ts_file->incompleteFile() ) {
                m_ts_file->readMore();
                m_tv->textStorageChanged();
                setFilePercentage();
            }
    }
    if ( ma == true ) parentlister->makeActive();
}

void TextViewMode::on()
{
    on( false );
}

void TextViewMode::on( bool keep_state )
{
    if ( ! keep_state ) {
        m_source_mode = ACTIVE_FILE_OTHER_SIDE;
        m_options = FOLLOW_ACTIVE;
    }

    m_lencalc.reset( new AFontWidth( aguix, aguix->getFont( wconfig->getFont( 4 ).c_str() ) ) );
    m_ts_file = std::make_shared< TextStorageFile >( "", m_lencalc, TVM_DEFAULT_SIZE, TextStorageString::CONVERT_NON_PRINTABLE_SIMPLE );

    m_cont = new AContainer( parentawindow, 1, 2 );
    m_cont->setBorderWidth( 0 );
    m_cont->setMinSpace( 0 );
    m_cont->setMaxSpace( 0 );
    parentlister->setContainer( m_cont );

    m_ac1 = (AContainer*)m_cont->add( new AContainer( parentawindow, 3, 1 ),
                                      0, 0 );
    m_ac1->setBorderWidth( 2 );
    m_ac1->setMinSpace( 5 );
    m_ac1->setMaxSpace( 5 );

    m_info_text = m_ac1->addWidget( new Text( aguix, 0, 0, catalog.getLocale( 404 ) ), 0, 0, AContainer::CO_FIX );
    m_file_name_text = (Text*)m_ac1->add( new Text( aguix, 0, 0, "" ),
                                          1, 0, AContainer::ACONT_MINH + AContainer::ACONT_MAXH );
    m_file_percentage_text = (Text*)m_ac1->add( new Text( aguix, 0, 0, "" ),
                                                2, 0, AContainer::CO_FIX );

    m_tv = new TextView( aguix, 0, 0, 50, 50, "", m_ts_file );
    m_tv->setFont( wconfig->getFont( 4 ).c_str() );
    m_tv->redraw();
    m_tv->setAcceptFocus( false );

    m_tv = (TextView*)m_cont->add( m_tv, 0, 1 );
    m_tv->show();

    parentlister->setActiveMode( this );
    setName();

    parentawindow->updateCont();

    update( true );
}

void TextViewMode::off()
{
    delete m_tv;
    m_tv = NULL;

    delete m_info_text;
    m_info_text = nullptr;

    delete m_file_name_text;
    m_file_name_text = nullptr;

    delete m_file_percentage_text;
    m_file_percentage_text = nullptr;

    parentlister->setContainer( NULL );
    delete m_cont;
    m_cont = NULL;

    parentlister->setActiveMode( NULL );
    parentlister->setName( "" );
    clearLastActiveFE();

    m_ts_file = NULL;
    m_ts_str = NULL;
    m_lencalc = NULL;
}

void TextViewMode::activate()
{
}

void TextViewMode::deactivate()
{
}

bool TextViewMode::isType( const char *str )
{
    if ( strcmp( str, type ) == 0 ) return true; else return false;
}

const char *TextViewMode::getType()
{
    return type;
}

const char *TextViewMode::getStaticType()
{
    return type;
}


int TextViewMode::configure()
{
    return 0;
}

void TextViewMode::cyclicfunc( cyclicfunc_mode_t mode )
{
    update( false );
}

const char* TextViewMode::getLocaleName()
{
    return getStaticLocaleName();
}

const char* TextViewMode::getStaticLocaleName()
{
    return catalog.getLocale( 939 );
}

int TextViewMode::load()
{
    return 0;
}

bool TextViewMode::save( Datei *fh )
{
    return false;
}

void TextViewMode::setName()
{
    parentlister->setName( catalog.getLocale( 939 ) );
}

void TextViewMode::relayout()
{
    if ( m_tv != NULL ) {
        off();
        on( true );
    }
}

void TextViewMode::update( bool force )
{
    ListerMode *lm1 = NULL;
    Lister *ol = NULL;
    const FileEntry *fe;
  
    ol = parentlister->getWorker()->getOtherLister( parentlister );
    if ( ol != NULL ) {
        lm1 = ol->getActiveMode();
    }
  
    if ( force == true )
        clearLastActiveFE();

    bool update_content = false;
  
    if ( lm1 != NULL ) {
        std::list< NM_specialsourceExt > files;
    
        lm1->getSelFiles( files, ListerMode::LM_GETFILES_ONLYACTIVE );
        if ( ! files.empty() ) {
            bool needs_update = force;
            fe = files.begin()->entry();
            if ( fe ) {
                if ( m_options & FOLLOW_ACTIVE ) {
                    if ( ! fe->isSamePath( m_lastactivefe ) ) {
                        needs_update = true;
                    } else if ( ( m_options & WATCH_FILE ) &&
                                ! fe->equals( m_lastactivefe ) ) {
                        needs_update = true;
                    }
                } else if ( m_options & WATCH_FILE ) {
                    if ( fe->isSamePath( m_lastactivefe ) &&
                         ! fe->equals( m_lastactivefe ) ) {
                        needs_update = true;
                    }
                }

                if ( m_source_mode == FILE_TYPE_ACTION &&
                     m_lastactivefe != nullptr &&
                     fe->filetype != m_lastactivefe->filetype ) {
                    needs_update = true;
                }

                if ( needs_update ) {
                    clearLastActiveFE();
                    m_lastactivefe = new FileEntry( *fe );
                    update_content = true;
                }
            }
        } else {
            if ( ol->isActive() == true ) {
                // only clear LV when other side is active and has no active fe
                if ( m_lastactivefe != NULL ) {
                    clearLastActiveFE();
                    update_content = true;
                }
            }
        }
    }

    if ( update_content ) {
        if ( m_source_mode == ACTIVE_FILE_OTHER_SIDE ) {
            showFile();
        } else if ( m_source_mode == COMMAND_STR &&
                    ( ( m_options & FOLLOW_ACTIVE ) ||
                      force ) ) {
            processCommand();
        } else if ( m_source_mode == FILE_TYPE_ACTION &&
                    ( ( m_options & FOLLOW_ACTIVE ) ||
                      force ) ) {
            processFileTypeAction();
        }
    }
}

void TextViewMode::clearLastActiveFE() {
    if ( m_lastactivefe != NULL ) {
        delete m_lastactivefe;
        m_lastactivefe = NULL;
    }
}

void TextViewMode::showFile()
{
    if ( m_lastactivefe != NULL ) {
        bool is_at_end = m_tv->isAtEnd() || m_start_over;
        m_ts_file->setFileName( m_lastactivefe->fullname, TVM_DEFAULT_SIZE );
        m_tv->replaceTextStorage( m_ts_file );
        m_tv->textStorageChanged();
        m_info_text->setText( catalog.getLocale( 404 ) );
        m_file_name_text->setText( m_lastactivefe->name );
        setFilePercentage();

        if ( is_at_end &&
             ( m_options & WATCH_FILE ) ) {
            m_tv->scrollToEnd();
        }

        m_start_over = false;
    }
}

void TextViewMode::command_up()
{
    m_tv->handleKeyMessage( XK_Up, 0 );
}

void TextViewMode::command_down()
{
    m_tv->handleKeyMessage( XK_Down, 0 );
}

void TextViewMode::command_first()
{
    m_tv->handleKeyMessage( XK_Home, 0 );
}

void TextViewMode::command_last()
{
    m_tv->handleKeyMessage( XK_End, 0 );
}

void TextViewMode::command_pageup()
{
    m_tv->handleKeyMessage( XK_Prior, 0 );
}

void TextViewMode::command_pagedown()
{
    m_tv->handleKeyMessage( XK_Next, 0 );
}

void TextViewMode::setFilePercentage()
{
    if ( m_lastactivefe == NULL ) return;
    double v = 0.0;

    if ( m_lastactivefe->isLink && ! m_lastactivefe->isCorrupt ) {
        v = m_lastactivefe->dsize();
    } else {
        v = m_lastactivefe->size();
    }

    if ( v > 0.0 ) {
        v = m_ts_file->getCurrentSize() / v;
    }

    if ( v < 0.0 ) v = 0.0;
    else if ( v > 1.0 ) v = 1.0;

    std::string p = AGUIXUtils::formatStringToString( "%3.0f %%", v * 100.0 );

    m_file_percentage_text->setText( p.c_str() );
    m_ac1->readLimits();
    parentawindow->updateCont();
}

void TextViewMode::setCommandString( const std::string &cmd,
                                     const std::string &basedir,
                                     uint32_t options )
{
    m_command_str_unparsed = cmd;
    m_command_str_parsed.clear();
    m_command_base_dir = basedir;

    m_source_mode = COMMAND_STR;
    m_options = options;

    m_start_over = true;

    update( true );

    m_source_mode_change_count++;
}

void TextViewMode::setBuffer( const std::string &buffer,
                              uint32_t options )
{
    m_source_mode = BUFFER;
    m_ts_str = std::make_shared< TextStorageString >( buffer,
                                                      m_lencalc );
    m_options = options;

    m_start_over = true;

    update( true );

    m_source_mode_change_count++;
}

void TextViewMode::setFileTypeAction( const std::string &file_type_action,
                                      uint32_t options )
{
    m_source_mode = FILE_TYPE_ACTION;
    m_file_type_action = file_type_action;
    m_options = options;

    m_start_over = true;

    update( true );

    m_source_mode_change_count++;
}

void TextViewMode::setViewActiveFile( uint32_t options )
{
    m_source_mode = ACTIVE_FILE_OTHER_SIDE;
    m_options = options;

    m_start_over = true;

    update( true );

    m_source_mode_change_count++;
}

void TextViewMode::processCommand()
{
    if ( m_source_mode != COMMAND_STR ) return;

    std::unique_ptr< ExeClass > ec( new ExeClass() );

    ActionMessage msg( parentlister->getWorker() );
    msg.mode = ActionMessage::AM_MODE_ON_DEMAND;

    std::string parsed_str;

    // declared in outer scope even though it is used in in scope
    // only. But the instance may hold temporary files which should
    // exists until the command execution is done.
    WPUContext wpu( parentlister->getWorker(),
                    {},
                    &msg );

    if ( ( m_options & FOLLOW_ACTIVE ) || m_command_str_parsed.empty() ) {
        auto res = wpu.parse( m_command_str_unparsed.c_str(),
                              parsed_str,
                              a_max( EXE_STRING_LEN - 1024, 256 ),
                              true,
                              WPUContext::PERSIST_NOTHING );

        if ( res != WPUContext::PARSE_SUCCESS ) {
            m_ts_str = std::make_shared< TextStorageString >( catalog.getLocale( 1509 ),
                                                              m_lencalc );

            m_tv->replaceTextStorage( m_ts_str );
            m_tv->redraw();
            return;
        }

        m_command_str_parsed = parsed_str;

        const char *new_base_dir = wpu.getBaseDir();
        if ( new_base_dir ) {
            m_command_base_dir = new_base_dir;
        }

        if ( m_options & WATCH_FILE ) {
            std::string fullname = wpu.getPathDBFilename();

            if ( ! fullname.empty() ) {
                NWC::FSEntry f( fullname );
                    
                if ( f.isReg( true ) ) {
                    if ( ! m_filewatch_data.path_watch_enabled ||
                         fullname != m_filewatch_data.watched_path ) {
                        parentlister->getWorker()->getPathnameWatcher()->watchPath( fullname );
                        m_filewatch_data.path_watch_enabled = true;
                        m_filewatch_data.watched_path = fullname;
                    }
                }
            }
        } else {
            m_filewatch_data.path_watch_enabled = false;
        }
    } else {
        parsed_str = m_command_str_parsed;
    }

    if ( parsed_str.empty() ) {
        m_ts_str = std::make_shared< TextStorageString >( catalog.getLocale( 1510 ),
                                                          m_lencalc );

        m_tv->replaceTextStorage( m_ts_str );
        m_tv->redraw();
        return;
    }

    ec->cdDir( m_command_base_dir.c_str() );
    ec->addCommand( "%s", parsed_str.c_str() );

    setInfoLine( catalog.getLocale( 1514 ),
                 parsed_str );

    int ret_error;
    parentlister->getWorker()->setWaitCursor();
    int exit_code = ec->getReturnCode( &ret_error );
    parentlister->getWorker()->unsetWaitCursor();
    std::string output;

    if ( exit_code == 0 &&
         ret_error == 0 &&
         ec->readOutput( output ) == 0 ) {
        m_ts_str = std::make_shared< TextStorageString >( output,
                                                          m_lencalc );

        bool is_at_end = m_tv->isAtEnd() || m_start_over;
        m_tv->replaceTextStorage( m_ts_str );
        m_tv->redraw();
        if ( is_at_end &&
             ( m_options & WATCH_FILE ) ) {
            m_tv->scrollToEnd();
        }
        m_start_over = false;
    } else {
        m_ts_str = std::make_shared< TextStorageString >( "failed",
                                                          m_lencalc );
        m_tv->replaceTextStorage( m_ts_str );
        m_tv->redraw();
    }
}

bool TextViewMode::pathsChanged( const std::set< std::string > changed_paths )
{
    if ( ! m_filewatch_data.path_watch_enabled ) return false;

    if ( m_source_mode != COMMAND_STR &&
         m_source_mode != FILE_TYPE_ACTION ) return false;
    if ( ( m_options & WATCH_FILE ) == 0 ) return false;

    for ( auto &f : changed_paths ) {
        if ( f == m_filewatch_data.watched_path ) {
            parentlister->getWorker()->getPathnameWatcher()->watchPath( f );
            update( true );
            return true;
        }
    }
    return false;
}

void TextViewMode::processFileTypeAction()
{
    bool clear = false;

    if ( m_source_mode != FILE_TYPE_ACTION ) clear = true;
    if ( m_file_type_action.empty() ) clear = true;
    if ( m_lastactivefe == NULL ) clear = true;

    if ( clear ) {
        m_ts_str = std::make_shared< TextStorageString >( catalog.getLocale( 1508 ),
                                                          m_lencalc );
        m_tv->replaceTextStorage( m_ts_str );
        m_tv->redraw();
        return;
    }

    auto action = std::make_unique< RunCustomAction::RunCustomActionDescr >( m_file_type_action );
    ActionMessage amsg( parentlister->getWorker() );
    amsg.mode = amsg.AM_MODE_SPECIAL;

    WCFiletype *ft = m_lastactivefe->filetype;
    if ( ft == NULL ) {
        if ( m_lastactivefe->isDir() == true ) {
            ft = wconfig->getdirtype();
        } else {
            ft = wconfig->getnotyettype();
        }
    }
    if ( ft != NULL ) {
        amsg.setFE( m_lastactivefe );
        amsg.filetype = ft;
        amsg.m_action_descr = RefCount<ActionDescr>( new RunCustomAction::RunCustomActionDescr( m_file_type_action ) );

        std::string file_type_action = m_file_type_action;
        uint32_t options = m_options;
        uint32_t current_source_mode_change_count = m_source_mode_change_count;

        m_skip_info_update = true;

        parentlister->getWorker()->interpret( ft->getActionList( true, *( amsg.m_action_descr ) ), &amsg );

        if ( current_source_mode_change_count == m_source_mode_change_count ) {
            m_ts_str = std::make_shared< TextStorageString >( catalog.getLocale( 1511 ),
                                                              m_lencalc );
            m_tv->replaceTextStorage( m_ts_str );
            m_tv->redraw();
        }

        // restore settings which probably have been changed by the
        // file type action
        m_source_mode = FILE_TYPE_ACTION;
        m_file_type_action = file_type_action;
        m_options = options;

        m_skip_info_update = false;

        setInfoLine( catalog.getLocale( 1513 ),
                     m_file_type_action );
    }
}

void TextViewMode::setInfoLine( const std::string &info,
                                const std::string &file_info )
{
    if ( m_skip_info_update ) return;

    if ( strcmp( m_info_text->getText(),
                 info.c_str() ) != 0 ||
         strcmp( m_file_name_text->getText(),
                 file_info.c_str() ) != 0 ) {
        m_info_text->setText( info.c_str() );
        m_file_name_text->setText( file_info.c_str() );

        if ( m_source_mode != ACTIVE_FILE_OTHER_SIDE ) {
            m_file_percentage_text->setText( "" );
        }

        m_ac1->readLimits();
        parentawindow->updateCont();
    }
}
