/* fileviewerbg.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2010-2023 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 "fileviewerbg.hh"
#include "fileviewer.hh"
#include "worker.h"
#include "aguix/textview.h"
#include "textstoragefile.h"
#include "nwc_fsentry.hh"
#include "aguix/util.h"
#include "aguix/refcount.hh"
#include "filenameshrinker.hh"
#include "aguix/searchtextstorage.hh"
#include "aguix/backgroundmessagehandler.hh"
#include "aguix/choosebutton.h"
#include "aguix/button.h"
#include "aguix/stringgadget.h"
#include "worker_locale.h"
#include "wconfig.h"
#include "listermode.h"
#include "virtualdirmode.hh"
#include "pdatei.h"

FileViewerBG::FileViewerBG( Worker *worker ) : win( NULL ),
                                               m_wrap_mode( false ),
                                               m_showlinenumbers_mode( false ),
                                               m_use_alt_font( false ),
                                               tv( NULL ),
                                               search_sg( NULL ),
                                               okb( NULL ),
                                               wcb( NULL ),
                                               slncb( NULL ),
                                               alt_font_cb( NULL ),
                                               jumplineb( NULL ),
                                               readmoreb( NULL ),
                                               reloadb( NULL ),
                                               m_next_match_b( NULL ),
                                               m_prev_match_b( NULL ),
                                               m_restart_search_b( NULL ),
                                               m_showintab_b( NULL ),
                                               m_find_pos_text( NULL ),
                                               m_worker( worker )
{
}

FileViewerBG::~FileViewerBG()
{
    m_destroy_callback = NULL;
    if ( win != NULL ) delete win;
}

void FileViewerBG::view( const std::list<std::string> &filelist,
                         int initial_line_number,
                         bool highlight_initial_line,
                         RefCount< FileViewerDestroyCallback > destroy_callback )
{
    AGUIX *aguix = Worker::getAGUIX();
    const int cfix = AContainer::ACONT_MINH +
        AContainer::ACONT_MINW +
        AContainer::ACONT_MAXH +
        AContainer::ACONT_MAXW;
    const int cincw = AContainer::ACONT_MINH +
        AContainer::ACONT_MINW +
        AContainer::ACONT_MAXH;
    const int cincwnr = cincw +
        AContainer::ACONT_NORESIZE;

    m_filelist = filelist;
    m_destroy_callback = destroy_callback;
    
    tv = NULL;
    std::list<std::string>::const_iterator it1;
    int w, h, sw, sh;
    
    win = new AWindow( aguix, 10, 10, 10, 10, catalog.getLocale( 1290 ) );
    win->create();
    /*  if ( aguix->getTransientWindow() != NULL ) {
        win->setTransientForAWindow( aguix->getTransientWindow() );
        }*/

    AContainer *ac1 = win->setContainer( new AContainer( win, 1, 4 ), true );
    ac1->setMinSpace( 5 );
    ac1->setMaxSpace( 5 );

    AContainer *ac1_1 = ac1->add( new AContainer( win, 4, 1 ), 0, 0 );
    ac1_1->setMinSpace( 5 );
    ac1_1->setMaxSpace( 5 );
    ac1_1->setBorderWidth( 0 );
    ac1_1->add( new Text( aguix, 0, 0, catalog.getLocale( 404 ) ), 0, 0, AContainer::CO_FIXNR );
    Text *fnt = (Text*)ac1_1->add( new Text( aguix, 0, 0, "" ), 1, 0, AContainer::ACONT_MINH + AContainer::ACONT_MAXH );
    readmoreb = (Button*)ac1_1->add( new Button( aguix, 0, 0, catalog.getLocale( 763 ), 0 ),
                                     2, 0, cfix );
    readmoreb->setBubbleHelpText( catalog.getLocale( 1146 ) );

    reloadb = ac1_1->addWidget( new Button( aguix, 0, 0, catalog.getLocale( 511 ), 0 ),
                                3, 0, cfix );
    reloadb->setBubbleHelpText( catalog.getLocale( 1175 ) );

    fnt->setTextShrinker( RefCount<TextShrinker>( new FileNameShrinker() ) );

    it1 = filelist.begin();

    const std::string fontname = wconfig->getFont( m_use_alt_font ? 6 : 4 );
    RefCount<AFontWidth> lencalc( new AFontWidth( aguix, aguix->getFont( fontname.c_str() ) ) );

    if ( buildTextView( *it1, ac1, fnt, lencalc ) != 0 ) {
        delete win;
        win = NULL;
        if ( m_ts ) m_ts.reset();
        return;
    }

    ac1->readLimits();
    ac1_1->readLimits();

    AContainer *ac1_3 = ac1->add( new AContainer( win, 7, 1 ), 0, 3 );
    ac1_3->setMinSpace( 5 );
    ac1_3->setMaxSpace( -1 );
    ac1_3->setBorderWidth( 0 );
  
    /*nextb = (Button*)ac1_3->add( new Button( aguix, 0, 0, "Next", 1, 0, 2 ),
      0, 0, cfix );
      prevb = (Button*)ac1_3->add( new Button( aguix, 0, 0, "Prev", 1, 0, 2 ),
      1, 0, cfix );*/
    wcb = (ChooseButton*)ac1_3->add( new ChooseButton( aguix, 0, 0,
                                                       m_wrap_mode, catalog.getLocale( 637 ),
                                                       LABEL_RIGHT, 0 ),
                                     0, 0, cincwnr );
    wcb->setBubbleHelpText( catalog.getLocale( 1147 ) );
    slncb = (ChooseButton*)ac1_3->add( new ChooseButton( aguix, 0, 0,
                                                         m_showlinenumbers_mode, catalog.getLocale( 715 ),
                                                         LABEL_RIGHT, 0 ),
                                       1, 0, cincwnr );
    slncb->setBubbleHelpText( catalog.getLocale( 1148 ) );

    snp_cb = ac1_3->addWidget( new ChooseButton( aguix, 0, 0,
                                                 false, catalog.getLocale( 1176 ),
                                                 LABEL_RIGHT, 0 ),
                               2, 0, cincwnr );
    snp_cb->setBubbleHelpText( catalog.getLocale( 1177 ) );

    alt_font_cb = ac1_3->addWidget( new ChooseButton( aguix, 0, 0,
                                                      m_use_alt_font, catalog.getLocale( 1454 ),
                                                      LABEL_RIGHT, 0 ),
                                    3, 0, cincwnr );
    alt_font_cb->setBubbleHelpText( catalog.getLocale( 1455 ) );

    jumplineb = (Button*)ac1_3->add( new Button( aguix, 0, 0, catalog.getLocale( 716 ), 2 ),
                                     4, 0, cfix );
    jumplineb->setBubbleHelpText( catalog.getLocale( 1142 ) );

    m_showintab_b = ac1_3->addWidget( new Button( aguix, 0, 0, catalog.getLocale( 1178 ), 2 ),
                                      5, 0, cfix );
    m_showintab_b->setBubbleHelpText( catalog.getLocale( 1179 ) );

    okb = (Button*)ac1_3->add( new Button( aguix, 0, 0, catalog.getLocale( 633 ), 2 ),
                               6, 0, cfix );
    okb->setBubbleHelpText( catalog.getLocale( 1143 ) );

    AContainer *ac1_4 = ac1->add( new AContainer( win, 5, 1 ), 0, 2 );
    ac1_4->setMinSpace( 5 );
    ac1_4->setMaxSpace( 10 );
    ac1_4->setBorderWidth( 0 );
  
    AContainer *ac1_4_1 = ac1_4->add( new AContainer( win, 2, 1 ), 0, 0 );
    ac1_4_1->setMinSpace( 5 );
    ac1_4_1->setMaxSpace( 5 );
    ac1_4_1->setBorderWidth( 0 );
  
    ac1_4_1->add( new Text( aguix, 0, 0, catalog.getLocale( 790 ) ), 0, 0, AContainer::CO_FIXNR + AContainer::ACONT_CENTER );
    search_sg = (StringGadget*)ac1_4_1->add( new StringGadget( aguix, 0, 0, 60, "", 1 ),
                                             1, 0, cincw );
    search_sg->setBubbleHelpText( catalog.getLocale( 1149 ) );

    m_next_match_b = ac1_4->addWidget( new Button( aguix, 0, 0, catalog.getLocale( 1136 ), 2 ),
                                       1, 0, cfix );
    m_next_match_b->setBubbleHelpText( catalog.getLocale( 1144 ) );
    m_prev_match_b = ac1_4->addWidget( new Button( aguix, 0, 0, catalog.getLocale( 1137 ), 2 ),
                                       2, 0, cfix );
    m_prev_match_b->setBubbleHelpText( catalog.getLocale( 1145 ) );
    m_restart_search_b = ac1_4->addWidget( new Button( aguix, 0, 0, catalog.getLocale( 1138 ), 2 ),
                                           3, 0, cfix );

    AContainer *ac1_4_2 = ac1_4->add( new AContainer( win, 2, 1 ), 4, 0 );
    ac1_4_2->setMinSpace( 5 );
    ac1_4_2->setMaxSpace( 5 );
    ac1_4_2->setBorderWidth( 0 );
  
    ac1_4_2->addWidget( new Text( aguix, 0, 0, catalog.getLocale( 1139 ) ), 0, 0, AContainer::CO_FIX );
    m_find_pos_text = ac1_4_2->addWidget( new Text( aguix, 0, 0, catalog.getLocale( 1140 ) ),
                                          1, 0, AContainer::CO_INCW );
    {
        std::string info = AGUIXUtils::formatStringToString( catalog.getLocale( 1141 ), 1000000000 );
        int tw = aguix->getTextWidth( info.c_str() );

        if ( tw > m_find_pos_text->getWidth() ) {
            m_find_pos_text->resize( tw, m_find_pos_text->getHeight() );
            ac1_4_2->readLimits();
        }
    }
    
    win->setDoTabCycling( true );
    win->contMaximize( true );
  
    w = win->getWidth();
    h = win->getHeight();

    int rx, ry, rw, rh;

    aguix->getLargestDimensionOfCurrentScreen( &rx, &ry,
                                               &rw, &rh );

    sw = rw;
    sh = rh;
    sw = (int)( (double)sw * 0.8 );
    sh = (int)( (double)sh * 0.8 );
    if ( sw < 200 ) sw = 200;
    if ( sh < 100 ) sh = 100;
    if ( w < sw ) w = sw;
    if ( h < sh ) h = sh;
    win->resize( w, h );

    tv->setLineWrap( wcb->getState() );
    tv->setShowLineNumbers( slncb->getState() );
    tv->jumpToLine( initial_line_number, highlight_initial_line );

    if ( m_ts->incompleteFile() == false ) {
        readmoreb->hide();
    }

    m_find_pos_text->setText( "" );
    
    win->useStippleBackground();
    win->centerScreen();
    win->show();

    win->applyFocus( tv );
}

void FileViewerBG::handleAGUIXMessage( AGMessage &msg )
{
    AGUIX *aguix = Worker::getAGUIX();
    int endmode = -1;
    //  bool ignore_key_release = true;

    switch ( msg.type ) {
        case AG_CLOSEWINDOW:
            if ( msg.closewindow.window == win->getWindow() ) {
                endmode = 0;
            }
            break;
        case AG_CHOOSECLICKED:
            if ( msg.choose.button == wcb ) {
                tv->setLineWrap( msg.choose.state );
            } else if ( msg.choose.button == slncb ) {
                tv->setShowLineNumbers( msg.choose.state );
            } else if ( msg.choose.button == snp_cb ) {
                if ( msg.choose.state ) {
                    m_ts->setConvertMode( TextStorageString::CONVERT_NON_PRINTABLE_HEX );
                } else {
                    m_ts->setConvertMode( TextStorageString::CONVERT_NON_PRINTABLE_SIMPLE );
                }
                tv->textStorageChanged();
            } else if ( msg.choose.button == alt_font_cb ) {
                std::string fontname = wconfig->getFont( msg.choose.state ? 6 : 4 );
                RefCount<AFontWidth> lencalc( new AFontWidth( aguix, aguix->getFont( fontname.c_str() ) ) );
                m_ts->setAWidth( lencalc );
                tv->setFont( fontname.c_str() );
                tv->redraw();
            }
            break;
        case AG_BUTTONCLICKED:
            if ( msg.button.button == okb ) {
                endmode = 0;
                /*	  } else if ( msg.button.button == nextb ) {
                          it1++;
                          if ( it1 == filelist.end() ) it1 = filelist.begin();
                          buildTextView( *it1, &ts, &tv, ac1 );
                          } else if ( msg.button.button == prevb ) {
                          if ( it1 == filelist.begin() )
                          it1 = filelist.end();
                          it1--;
                          buildTextView( *it1, &ts, &tv, ac1 );*/
            } else if ( msg.button.button == jumplineb ) {
                jumpToLine();
            } else if ( msg.button.button == readmoreb ) {
                m_ts->readMore();
                tv->textStorageChanged();
                if ( m_ts->incompleteFile() == false ) {
                    readmoreb->hide();
                }
            } else if ( msg.button.button == reloadb ) {
                m_ts->reloadFile();
                tv->textStorageChanged();
                if ( m_ts->incompleteFile() == false ) {
                    readmoreb->hide();
                }
            } else if ( msg.button.button == m_next_match_b ) {
                nextMatch();
            } else if ( msg.button.button == m_prev_match_b ) {
                nextMatch( true );
            } else if ( msg.button.button == m_restart_search_b ) {
                restartSearchFromTop();
            } else if ( msg.button.button == m_showintab_b ) {
                showInWorkerTab();
            }
            break;
            /*        case AG_KEYPRESSED:
                      ignore_key_release = false;
                      break;*/
	case AG_KEYPRESSED:
            if ( /*ignore_key_release == false && */win->isParent( msg.key.window, false ) == true ) {
                switch ( msg.key.key ) {
                    case XK_q:
                    case XK_F3:
                    case XK_F10:
                        endmode = 0;
                        break;
                    case XK_l:
                        if ( ( msg.key.keystate & ( ControlMask | Mod1Mask ) ) != 0 ) {
                            jumpToLine();
                        } else if ( KEYSTATEMASK( msg.key.keystate ) == 0 &&
                                    ! tv->getHasFocus() ) {
                            tv->setShowLineNumbers( ( tv->getShowLineNumbers() == true ) ? false : true );
                        }
                        break;
                    case XK_w:
                        if ( KEYSTATEMASK( msg.key.keystate ) == 0 &&
                             ! tv->getHasFocus() ) {
                            tv->setLineWrap( ( tv->getLineWrap() == true ) ? false : true );
                        }
                        break;
                    case XK_r:
                        if ( ( msg.key.keystate & ( ControlMask | Mod1Mask ) ) != 0 ) {
                            m_ts->reloadFile();
                            tv->textStorageChanged();
                            if ( m_ts->incompleteFile() == false ) {
                                readmoreb->hide();
                            }
                        } else {
                            if ( m_ts->incompleteFile() == true ) {
                                m_ts->readMore();
                                tv->textStorageChanged();
                                if ( m_ts->incompleteFile() == false ) {
                                    readmoreb->hide();
                                }
                            }
                        }
                        break;
                    case XK_slash:
                        if ( search_sg->isActive() == true ) {
                            nextMatch();
                        } else {
                            std::pair<int, int> rl = m_ts->getRealLinePair( tv->getYOffset() );
                            ts_search->setSearchStartLine( rl.first );
                            search_sg->setText( "" );
                            search_sg->activate();
                        }
                        break;
                    case XK_s:
                    case XK_f:
                    case XK_i:
                        if ( ( msg.key.keystate & ( ControlMask | Mod1Mask ) ) != 0 ) {
                            if ( search_sg->isActive() == true ) {
                                nextMatch();
                            } else {
                                std::pair<int, int> rl = m_ts->getRealLinePair( tv->getYOffset() );
                                ts_search->setSearchStartLine( rl.first );
                                search_sg->setText( "" );
                                search_sg->activate();
                            }
                        } else {
                            if ( msg.key.key == XK_f ) {
                                alt_font_cb->setState( ! alt_font_cb->getState() );
                                std::string fontname = wconfig->getFont( alt_font_cb->getState() ? 6 : 4 );
                                RefCount<AFontWidth> lencalc( new AFontWidth( aguix, aguix->getFont( fontname.c_str() ) ) );
                                m_ts->setAWidth( lencalc );
                                tv->setFont( fontname.c_str() );
                                tv->redraw();
                            }
                        }
                        break;
                    case XK_n:
                        if ( KEYSTATEMASK( msg.key.keystate ) == 0 ) {
                            nextMatch();
                        }
                        break;
                    case XK_p:
                        if ( KEYSTATEMASK( msg.key.keystate ) == 0 ) {
                            nextMatch( true );
                        }
                        break;
                    case XK_Return:
                        nextMatch();
                        break;
                }
                wcb->setState( tv->getLineWrap() );
                slncb->setState( tv->getShowLineNumbers() );
            }
            break;
        case AG_STRINGGADGET_OK:
            if ( msg.stringgadget.sg == search_sg ) {
                search_sg->activate();
            }
        case AG_STRINGGADGET_CONTENTCHANGE:
            if ( msg.stringgadget.sg == search_sg ) {
                nextMatch();
            }
            break;
        case AG_TEXTVIEW_END_REACHED:
            if ( msg.textview.textview == tv &&
                 m_ts->incompleteFile() ) {
                m_ts->readMore();
                tv->textStorageChanged();
                if ( m_ts->incompleteFile() == false ) {
                    readmoreb->hide();
                }
            }
            break;
    }

    if ( endmode == 0 ) {
        aguix->unregisterBGHandler( win );
        FileViewer::setGlobalLineWrap( tv->getLineWrap() );
        FileViewer::setGlobalShowLineNumbers( tv->getShowLineNumbers() );
        FileViewer::setGlobalUseAltFont( alt_font_cb->getState() );
    }
}

AWindow *FileViewerBG::getAWindow()
{
    return win;
}

int FileViewerBG::buildTextView( const std::string &filename,
                                 AContainer *ac1,
                                 class Text *fnt,
                                 const RefCount<AWidth> &lencalc )
{
    AGUIX *aguix = Worker::getAGUIX();
    const int cmin = AContainer::ACONT_MINH +
        AContainer::ACONT_MINW;
  
    if ( ac1 == NULL ) return 1;

    NWC::FSEntry fse( filename );
    loff_t file_length = 0;
    int initial_size = ( m_wrap_mode == true ) ? ( 512 * 1024 ) : ( 2 * 1024 * 1024 );
    bool continue_viewing = true;

    file_length = PDatei::getFileSize( filename );

    if ( file_length > initial_size ) {
        std::string l1, l2;
        int cancel_button;

        l1 = AGUIXUtils::bytes_to_human_readable_f( file_length );
        l2 = AGUIXUtils::bytes_to_human_readable_f( (double)initial_size );

        char *textstr = (char*)_allocsafe( strlen( catalog.getLocale( 764 ) ) +
                                           l1.length() + l2.length() + 1 );
        sprintf( textstr, catalog.getLocale( 764 ), l1.c_str(), l2.c_str() );

        std::string buttontext = catalog.getLocale( 765 );
        buttontext += "|";
        buttontext += catalog.getLocale( 766 );

        if ( m_wrap_mode == true ) {
            buttontext += "|";
            buttontext += catalog.getLocale( 767 );

            cancel_button = 3;
        } else {
            cancel_button = 2;
        }

        buttontext += "|";
        buttontext += catalog.getLocale( 8 );

        int erg = Worker::getRequester()->request( catalog.getLocale( 123 ),
                                                   textstr,
                                                   buttontext.c_str(),
                                                   win,
                                                   Requester::REQUEST_NONE );
        if ( erg == 1 ) {
            if ( file_length > 1 * 1024 * 1024 * 1024 ) {
                initial_size = 1 * 1024 * 1024 * 1024;
            } else {
                initial_size = file_length;
            }
        } else if ( erg == cancel_button ) {
            continue_viewing = false;
        } else if ( erg == 2 ) {
            if ( file_length > 1 * 1024 * 1024 * 1024 ) {
                initial_size = 1 * 1024 * 1024 * 1024;
            } else {
                initial_size = file_length;
            }
            m_wrap_mode = false;
        }
        _freesafe( textstr );
    }

    if ( continue_viewing == false ) {
        return 2;
    }
  
    if ( tv != NULL ) {
        delete tv;
    }
    m_ts = std::make_shared< TextStorageFile >( filename, lencalc, initial_size, TextStorageString::CONVERT_NON_PRINTABLE_SIMPLE );
    tv = new TextView( aguix, 0, 0, 100, 100, "", m_ts );

    ts_search.reset( new SearchTextStorage( *m_ts ) );

    tv->setDisplayFocus( true );

    const std::string fontname = wconfig->getFont( m_use_alt_font ? 6 : 4 );

    tv->setFont( fontname.c_str() );
    ac1->add( tv, 0, 1, cmin );
    tv->create();
    if ( fnt != NULL ) fnt->setText( filename.c_str() );
    ac1->rearrange();
    tv->show();

    std::string title = AGUIXUtils::formatStringToString( catalog.getLocale( 1079 ), filename.c_str() );
    
    win->setTitle( title.c_str() );

    return 0;
}

void FileViewerBG::jumpToLine()
{
    int erg;
    std::string buttons;
    char *return_str = NULL;

    buttons = catalog.getLocale( 716 );
    buttons += "|";
    buttons += catalog.getLocale( 8 );

    //TODO use current line number as default but I need the interface in tv for this first
    erg = win->string_request( catalog.getLocale( 123 ),
                               catalog.getLocale( 717 ),
                               "",
                               buttons.c_str(),
                               &return_str );
    if ( erg == 0 && return_str != NULL ) {
        int line_nr = 0;
        line_nr = atoi( return_str ) - 1;
        tv->jumpToLine( line_nr, true );
    }
    if ( return_str != NULL )
        _freesafe( return_str );
}

void FileViewerBG::nextMatch( bool backwards )
{
    if ( backwards ) {
        ts_search->searchBackwards( search_sg->getText() );
    } else {
        ts_search->search( search_sg->getText() );
    }
    ts_search->highlightCurMatch( *tv );
    tv->makeSelectionVisible();

    if ( strlen( search_sg->getText() ) < 1 ) {
        m_find_pos_text->setText( "" );
    } else {
        int match_line = ts_search->getCurMatchLine();

        if ( match_line < 0 ) {
            m_find_pos_text->setText( catalog.getLocale( 1140 ) );
        } else {
            std::string info = AGUIXUtils::formatStringToString( catalog.getLocale( 1141 ), match_line + 1 );
        
            m_find_pos_text->setText( info.c_str() );
        }
    }
}

void FileViewerBG::setLineWrap( bool nv )
{
    m_wrap_mode = nv;
}

bool FileViewerBG::getLineWrap() const
{
    return m_wrap_mode;
}

void FileViewerBG::setShowLineNumbers( bool nv )
{
    m_showlinenumbers_mode = nv;
}

bool FileViewerBG::getShowLineNumbers() const
{
    return m_showlinenumbers_mode;
}

void FileViewerBG::setUseAltFont( bool nv )
{
    m_use_alt_font = nv;
}

bool FileViewerBG::getUseAltFont() const
{
    return m_use_alt_font;
}

void FileViewerBG::restartSearchFromTop()
{
    ts_search->setSearchStartLine( 0 );
    nextMatch();
}

void FileViewerBG::showInWorkerTab()
{
    if ( ! m_worker ) return;

    auto it1 = m_filelist.begin();

    if ( it1 == m_filelist.end() ) return;

    Lister *l1;

    l1 = m_worker->getActiveLister();
    if ( l1 != NULL ) {
        l1->switch2Mode( 0 );

        VirtualDirMode *vdm = dynamic_cast< VirtualDirMode* >( l1->getActiveMode() );
        if ( vdm != NULL ) {
            vdm->newTab();

            vdm->showDir( *it1 );
        }
    }
}
