/**
 * Copyright (C) 2000-2002 the KGhostView authors. See file AUTHORS.
 * 	
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <qfile.h>
#include <qfileinfo.h>
#include <qframe.h>
#include <qlayout.h>

#include <kaction.h>
#include <kapplication.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kdirwatch.h>
#include <kglobalsettings.h>
#include <kiconloader.h>
#include <kinstance.h>
#include <klocale.h>
#include <kstdaction.h>
#include <ktempfile.h>
#include <kio/scheduler.h>

#include "kgv_view.h"
#include "kgv_miniwidget.h"
#include "kgvpagedecorator.h"
#include "kgvpageview.h"
#include "kgvmainwidget.h"
#include "kpswidget.h"
#include "marklist.h"
#include "scrollbox.h"
#include "version.h"

#define PAGELIST_WIDTH 75

K_EXPORT_COMPONENT_FACTORY( libkghostview, KGVFactory );

KGVPart::KGVPart( QWidget* parentWidget, const char*, 
                  QObject* parent, const char* name,
                  const QStringList &args ) :  
    KParts::ReadOnlyPart( parent, name ), 
    _mimetypeScanner( 0 ),
    _isGuiInitialized( false ),
    _isDocumentOpen( false ),
    _isFileDirty( false )
{
    setInstance( KGVFactory::instance() );

    bool bBrowserView = args.contains( "Browser/View" );

    // Don't show the progress info dialog if we're embedded in Konqueror.
    setProgressInfoEnabled( !bBrowserView );

    _fileWatcher = new KDirWatch( this );
    connect( _fileWatcher, SIGNAL( dirty( const QString& ) ),
	     this, SLOT( slotFileDirty( const QString& ) ) );

    // Setup main widget
    _mainWidget = new KGVMainWidget( parentWidget );
    _mainWidget->setFocusPolicy( QWidget::StrongFocus );
    _mainWidget->installEventFilter( this );
    connect( _mainWidget, SIGNAL( spacePressed() ),
             this, SLOT( slotReadDown() ) );

    QHBoxLayout* hlay = new QHBoxLayout( _mainWidget, 0, 0 );
    QVBoxLayout* vlay = new QVBoxLayout( hlay );

    _scrollBox = new ScrollBox( _mainWidget , "scrollbox" );
    _scrollBox->setMinimumWidth( PAGELIST_WIDTH );
    _scrollBox->setMinimumHeight( PAGELIST_WIDTH );
    vlay->addWidget( _scrollBox );

    _markList = new MarkList( _mainWidget, "marklist" );
    _markList->setMinimumWidth( PAGELIST_WIDTH );
    vlay->addWidget( _markList, 1 );

    _divider = new QFrame( _mainWidget, "divider" );
    _divider->setFrameStyle( QFrame::Panel | QFrame::Raised );
    _divider->setLineWidth( 1 );
    _divider->setMinimumWidth( 3 );
    hlay->addWidget( _divider );

    _pageView = new KGVPageView( _mainWidget, "pageview" );
    _pageView->viewport()->setBackgroundMode( QWidget::PaletteMid );
    hlay->addWidget( _pageView, 1 );
    _mainWidget->setFocusProxy( _pageView );
    setWidget( _mainWidget );

    _pageDecorator = new KGVPageDecorator( _pageView->viewport() );
    _pageDecorator->hide();

    _psWidget = new KPSWidget( _pageDecorator );
    _pageView->setPage( _pageDecorator );

    _docManager = new KGVMiniWidget( this );
    _docManager->setPSWidget( _psWidget );

    // Connect up the necessary signals and slots.
    connect( _markList, SIGNAL( selected( int ) ),
	     _docManager, SLOT( goToPage( int ) ) );
    connect( _docManager, SIGNAL( newPageShown( int ) ),
	     _markList, SLOT( select( int ) ) );

    connect( _scrollBox, SIGNAL( valueChangedRelative( int, int ) ),
	     _pageView, SLOT( scrollBy( int, int ) ) );
    connect( _pageView, SIGNAL( pageSizeChanged( QSize ) ),
	     _scrollBox, SLOT( setPageSize( QSize ) ) );
    connect( _pageView, SIGNAL( viewSizeChanged( QSize ) ),
	     _scrollBox, SLOT( setViewSize( QSize ) ) );
    connect( _pageView, SIGNAL( contentsMoving( int, int ) ),
	     _scrollBox, SLOT( setViewPos( int, int ) ) );

    connect( _docManager, SIGNAL( fileChangeFailed() ),
	     this, SLOT( slotCancelWatch() ) );
    connect( _docManager, SIGNAL( completed() ),
	     this, SLOT( slotOpenFileCompleted() ) );
    connect( _docManager, SIGNAL( canceled( const QString& ) ),
	     this, SIGNAL( canceled( const QString& ) ) );
    connect( _docManager, SIGNAL( setStatusBarText( const QString& ) ),
	     this, SIGNAL( setStatusBarText( const QString& ) ) );

    //-- File Menu ----------------------------------------------------------
    KStdAction::saveAs( miniWidget(), SLOT( saveAs() ), 
                        actionCollection() );
    new KAction( i18n( "Document &Info..." ), 0,
                 miniWidget(), SLOT( info() ),
                 actionCollection(), "info" );

    //-- View Menu ----------------------------------------------------------
    _selectOrientation = new KSelectAction( i18n( "&Orientation" ), 0, 0, 0,
                                    actionCollection(), "orientation_menu" );
    _selectMedia       = new KSelectAction( i18n( "Paper &Size" ), 0, 0, 0,
                                    actionCollection(), "media_menu" );
 
    QStringList orientations;
    orientations.append( i18n( "Auto" ) );
    orientations.append( i18n( "Portrait" ) );
    orientations.append( i18n( "Landscape" ) );
    orientations.append( i18n( "Upside Down" ) );
    orientations.append( i18n( "Seascape" ) );
    _selectOrientation->setItems( orientations );
    
    connect( _selectOrientation, SIGNAL( activated( int ) ),
	     this, SLOT( slotOrientation( int ) ) );
    connect( _selectMedia, SIGNAL( activated( int ) ),
             this, SLOT( slotMedia( int ) ) );

    _zoomIn  = KStdAction::zoomIn( _docManager,  SLOT( zoomIn() ),
                                   actionCollection(), "zoomIn" );
    _zoomOut = KStdAction::zoomOut( _docManager,  SLOT( zoomOut() ),
			            actionCollection(), "zoomOut" );

    /*
    _fitWidth = new KAction( i18n( "Fit Width" ),
                             0, this, SLOT( slotFitWidth() ),
                             actionCollection(), "fitWidth" );
    */

    _prevPage  = KStdAction::prior( this, SLOT( slotPrevPage() ),
                                    actionCollection(), "prevPage" );
    _nextPage  = KStdAction::next( this, SLOT( slotNextPage() ),
                                   actionCollection(), "nextPage" );
    _firstPage = new KAction( i18n( "Go to Start" ), "start",
                              CTRL+Key_Home, this, SLOT( slotGotoStart() ),
                              actionCollection(), "goToStart" );
    _lastPage  = new KAction( i18n( "Go to End" ), "finish",
                              CTRL+Key_End, this, SLOT( slotGotoEnd() ),
                              actionCollection(), "goToEnd" );
    _readUp    = new KAction( i18n( "Read up Document" ), "previous",
                              SHIFT+Key_Space, this, SLOT( slotReadUp() ),
                              actionCollection(), "readUp" );
    // don't specify Key_Space as Accel, it breaks the lineedit in konq! (Simon)
    _readDown  = new KAction( i18n( "Read down Document" ), "next",
                              /*Key_Space*/0, this, SLOT( slotReadDown() ),
                              actionCollection(), "readDown" );

    /*
    _gotoPage = KStdAction::gotoPage( _docManager, SLOT( goToPage() ),
                                      actionCollection(), "goToPage" );
    */

    //-- PageMarks Menu -----------------------------------------------------
    new KAction( i18n( "Mark Current Page" ), "flag", CTRL+Key_M,
                 _markList, SLOT( markCurrent() ),
                 actionCollection(), "mark_current" );
    new KAction( i18n( "Mark &All Pages" ), 0,
                 _markList, SLOT( markAll() ),
                 actionCollection(), "mark_all" );
    new KAction( i18n( "Mark &Even Pages" ), 0,
                 _markList, SLOT( markEven() ),
                 actionCollection(), "mark_even" );
    new KAction( i18n( "Mark &Odd Pages" ), 0,
                 _markList, SLOT( markOdd() ),
                 actionCollection(), "mark_odd" );
    new KAction( i18n( "&Toggle Page Marks" ), 0,
                 _markList, SLOT( toggleMarks() ),
                 actionCollection(), "toggle" );
    new KAction( i18n("&Remove Page Marks"), 0,
                 _markList, SLOT( removeMarks() ),
                 actionCollection(), "remove" );

    // TODO -- disable entry if there aren't any page names

    //-- Settings Menu ------------------------------------------------------
    _showScrollBars = new KToggleAction( i18n( "Show &Scrollbars" ), 0,
                                this, SLOT( slotShowScrollBars() ),
                                actionCollection(), "show_scrollbars" );
    _watchFile	    = new KToggleAction( i18n( "&Watch File" ), 0,
                                this, SLOT( slotWatchFile() ),
                                actionCollection(), "watch_file" );
    _showPageList   = new KToggleAction( i18n( "Show &Page List" ), 0,
                                this, SLOT( slotShowMarkList() ),
                                actionCollection(), "show_page_list" );
    _showPageLabels = new KToggleAction( i18n("Show Page Labels"), 0,
                                this, SLOT( slotShowPageLabels() ),
                                actionCollection(), "show_page_labels" );
    new KAction( i18n( "&Configure KGhostView..." ), "configure", 0,
                 miniWidget(), SLOT( configureGhostscript() ),
                 actionCollection(), "configure" );

    
    _extension = new KGVBrowserExtension( this );

    setXMLFile( "kgv_part.rc" );

    connect( miniWidget(), SIGNAL( newPageShown( int ) ),
	     this, SLOT( slotNewPage( int ) ) );
    connect( _pageView, SIGNAL( contentsMoving( int, int ) ),
	     this, SLOT( slotPageMoved( int, int ) ) );

    readSettings();

    QStringList items = miniWidget()->mediaNames();
    items.prepend( i18n( "Auto ") );
    _selectMedia->setItems( items );
}

KGVPart::~KGVPart()
{
    delete _mimetypeScanner;
    writeSettings();
}

KAboutData* KGVPart::createAboutData()
{
    KAboutData* about = new KAboutData( "kghostview", I18N_NOOP( "KGhostView"), 
            KGHOSTVIEW_VERSION, 
            I18N_NOOP( "Viewer for Postscript (.ps, .eps) and Portable Document Format (.pdf) files."), 
            KAboutData::License_GPL,
            "(C) 1998 Mark Donohoe, (C) 1999-2000 David Sweet, "
            "(C) 2000-2001 Wilco Greven",
            I18N_NOOP( "KGhostView displays, prints, and saves "
                       "Postscript and PDF files.\n"
                       "Based on original work by Tim Theisen." ) );
    about->addAuthor( "Wilco Greven",
                      I18N_NOOP( "Current maintainer" ),
                      "greven@kde.org" );
    about->addAuthor( "David Sweet", 
                      I18N_NOOP( "Maintainer 1999-2000" ),
                      "dsweet@kde.org",
                      "http://www.andamooka.org/~dsweet" );
    about->addAuthor( "Mark Donohoe",
	 	      I18N_NOOP( "Original author" ),
                      "donohoe@kde.org" );
    about->addAuthor( "David Faure",
		      I18N_NOOP( "Basis for shell"),
                      "faure@kde.org" );
    about->addAuthor( "Daniel Duley",
		      I18N_NOOP( "Port to KParts" ),
                      "mosfet@kde.org" );
    about->addAuthor( "Espen Sand",
		      I18N_NOOP( "Dialog boxes" ),
                      "espen@kde.org" );
    about->addCredit( "Russell Lang of Ghostgum Software Pty Ltd",
		      I18N_NOOP( "for contributing GSView's DSC parser." ),
                      0, 
                      "http://www.ghostgum.com.au/" );
    about->addCredit( "The Ghostscript authors",
                      0, 0,
		      "http://www.cs.wisc.edu/~ghost/" );
    return about;
}

bool KGVPart::closeURL()
{
    _psWidget->disableInterpreter(); 
    _isDocumentOpen = false;
    _isFileDirty = false;
    if( _mimetypeScanner != 0 )
	_mimetypeScanner->abort();
    if( !m_file.isEmpty() )
        _fileWatcher->removeFile( m_file );
    _mimetype = QString::null;
    stateChanged( "initState" );
    return KParts::ReadOnlyPart::closeURL();
}

void KGVPart::writeSettings()
{
    KConfigGroup general( KGVFactory::instance()->config(), "General" );
    general.writeEntry( "ShowScrollBars", _showScrollBars->isChecked() );
    general.writeEntry( "WatchFile", _watchFile->isChecked() );
    general.writeEntry( "ShowPageList", _showPageList->isChecked() );
    general.writeEntry( "ShowPageNames", _showPageLabels->isChecked() );
    general.sync();
}

void KGVPart::readSettings()
{
    KConfigGroup general( KGVFactory::instance()->config(), "General" );
    _showScrollBars->setChecked( general.readBoolEntry( "ShowScrollBars", true ) );
    slotShowScrollBars();
    _watchFile->setChecked( general.readBoolEntry( "WatchFile", false ) );
    slotWatchFile();
    _showPageList->setChecked( general.readBoolEntry( "ShowPageList", true ) );
    slotShowMarkList();
    _showPageLabels->setChecked( general.readBoolEntry( "ShowPageNames", false ) );
    slotShowPageLabels();
}

void KGVPart::slotScrollLeft()
{
    _pageView->scrollLeft();
}

void KGVPart::slotScrollRight()
{
    _pageView->scrollRight();
}

void KGVPart::slotScrollUp()
{
    _pageView->scrollUp();
}

void KGVPart::slotScrollDown()
{
    _pageView->scrollDown();
}

void KGVPart::slotReadUp()
{
    if( !_isDocumentOpen )
	return;
    
    if( !_pageView->readUp() ) {
	_docManager->prevPage();
	_pageView->scrollBottom();
    }
}

void KGVPart::slotReadDown()
{
    if( !_isDocumentOpen )
	return;
    
    if( !_pageView->readDown() )
	slotNextPage();
}

void KGVPart::slotPrevPage()
{
    _docManager->prevPage();
    _pageView->scrollTop();
}

void KGVPart::slotNextPage()
{
    if( _docManager->nextPage() ) {
	_pageView->scrollTop();
    }
    else
	_pageView->scrollBottom();
}

void KGVPart::slotGotoStart()
{
    _docManager->firstPage();
    _pageView->scrollTop();
}

void KGVPart::slotGotoEnd()
{
    _docManager->lastPage();
    _pageView->scrollTop();
}

void KGVPart::slotWatchFile()
{
    if( _watchFile->isChecked() )
	_fileWatcher->startScan();
    else
	_fileWatcher->stopScan();
}

void KGVPart::slotCancelWatch()
{
    _fileWatcher->stopScan();
    _watchFile->setChecked( false );
}

/*
void KGVPart::slotFitWidth()
{
    _docManager->fitWidth( pageView()->viewport()->width() - 
         2*( pageDecorator()->margin() + pageDecorator()->borderWidth() ) );
}
*/

void KGVPart::updatePageDepActions()
{
    if( !_isDocumentOpen ) 
	return;
    
    _prevPage->setEnabled( !_docManager->atFirstPage() );
    _firstPage->setEnabled( !_docManager->atFirstPage() );
    _nextPage->setEnabled( !_docManager->atLastPage() );
    _lastPage->setEnabled( !_docManager->atLastPage() );
    
    updateReadUpDownActions();
}

void KGVPart::updateReadUpDownActions()
{
    if( !_isDocumentOpen ) 
	return;
    
    if( _docManager->atFirstPage() && _pageView->atTop() )
	_readUp->setEnabled( false );
    else
	_readUp->setEnabled( true );

    if( _docManager->atLastPage() && _pageView->atBottom() )
	_readDown->setEnabled( false );
    else
	_readDown->setEnabled( true );
}

bool KGVPart::openURL( const KURL& url )
{
    if( url.isMalformed() )
	return false;
    if( !closeURL() )
	return false;

    m_url = url;

    emit setWindowCaption( m_url.prettyURL() );

    _mimetypeScanner = new KGVRun( m_url, m_url.isLocalFile(), false );
    connect( _mimetypeScanner, SIGNAL( finished( const QString& ) ),
             SLOT( slotMimetypeFinished( const QString& ) ) );
    connect( _mimetypeScanner, SIGNAL( error() ),
             SLOT( slotMimetypeError() ) );
   
    return true;
}

void KGVPart::openURLContinue()
{
    if( m_url.isLocalFile() ) 
    {
	emit started( 0 );
	m_file = m_url.path();
	_docManager->openFile( m_file, _mimetype );
    }
    else 
    {
	m_bTemp = true;
	// Use same extension as remote file. This is important for
	// mimetype-determination (e.g. koffice)
	QString extension;
	QString fileName = m_url.fileName();
	int extensionPos = fileName.findRev( '.' );
	if( extensionPos != -1 )
	    extension = fileName.mid( extensionPos ); // keep the '.'
	KTempFile tempFile( QString::null, extension );
	m_file = tempFile.name();
	_tmpFile.setName( m_file );
	_tmpFile.open( IO_ReadWrite );

	/*
	d->m_job = KIO::file_copy( m_url, m_file, 0600, true, false, d->m_showProgressInfo );
	emit started( d->m_job );
	connect( d->m_job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotJobFinished ( KIO::Job * ) ) );
	*/

	_job = KIO::get( m_url, false, isProgressInfoEnabled() );

	connect( _job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
		 SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
	connect( _job, SIGNAL( result( KIO::Job* ) ),
		 SLOT( slotJobFinished( KIO::Job* ) ) );

	emit started( _job );
    }
}

bool KGVPart::openFile()
{
    return false;
}

void KGVPart::slotOpenFileCompleted()
{
    if( _isFileDirty )
    {
	_docManager->goToPage( _currentPage );
	_docManager->redisplay();
	_isFileDirty = false;
    }
    else	
    {
	_isDocumentOpen = true;
	_docManager->firstPage();
	stateChanged( "documentState" );
	connect( _pageView, SIGNAL( nextPage() ), SLOT( slotNextPage() ));
	connect( _pageView, SIGNAL( prevPage() ), SLOT( slotPrevPage() ));
	_fileWatcher->addFile( m_file );
	slotWatchFile();
	emit completed();
    }
}

void KGVPart::guiActivateEvent( KParts::GUIActivateEvent* event )
{
    if( event->activated() && !_isGuiInitialized )
    {
	stateChanged( "initState" );
	_isGuiInitialized = true;
    }
    KParts::ReadOnlyPart::guiActivateEvent( event );
}

void KGVPart::slotData( KIO::Job* job, const QByteArray& data )
{
    Q_ASSERT( _job == job );

    kdDebug(4500) << "KGVPart::slotData: received " << data.size() << " bytes." << endl;

    _tmpFile.writeBlock( data );
}

void KGVPart::slotMimetypeFinished( const QString& type )
{
    kdDebug(4500) << "KGVPart::slotMimetype: type=" << type << endl;
    _mimetype = type;
    if( _mimetypeScanner->hasError() )
	emit canceled( QString::null );
    else
	openURLContinue();
    _mimetypeScanner = 0;
}

void KGVPart::slotMimetypeError()
{
    /*
    _mimetypeScanner = 0;
    emit started( 0 );
    kapp->processEvents();
    emit canceled( QString::null );
    */
}

void KGVPart::slotJobFinished( KIO::Job* job )
{
    Q_ASSERT( _job == job );

    kdDebug(4500) << "KGVPart::slotJobFinished" << endl;

    _job = 0;

    _tmpFile.close();

    if( job->error() )
	emit canceled( job->errorString() );
    else 
	_docManager->openFile( m_file, _mimetype );
}

void KGVPart::slotFileDirty( const QString& fileName )
{
    if( fileName == m_file )
    {
	kdDebug(4500) << "KGVPart::File changed" << endl;
	_isFileDirty = true;
	_currentPage = _docManager->currentPage();
	_docManager->openFile( m_file, _mimetype );
    }
}

void KGVPart::slotNewPage( int )
{
    updatePageDepActions();
    //media->setCurrentItem (miniWidget()->getSize()-1);
    //orientation->setCurrentItem (miniWidget()->getOrientation()-1);
    //TODO -- zoom
}

void KGVPart::slotPageMoved( int, int )
{
    updateReadUpDownActions();
}

void KGVPart::slotOrientation( int id )
{
    switch( id ) {
    case 0: miniWidget()->restoreOverrideOrientation();              break;
    case 1: miniWidget()->setOverrideOrientation( CDSC_PORTRAIT );   break;
    case 2: miniWidget()->setOverrideOrientation( CDSC_LANDSCAPE );  break;
    case 3: miniWidget()->setOverrideOrientation( CDSC_UPSIDEDOWN ); break;
    case 4: miniWidget()->setOverrideOrientation( CDSC_SEASCAPE );   break;
    default: ;
    }
}

void KGVPart::slotMedia( int id )
{
    if( id == 0 )
	miniWidget()->restoreOverridePageMedia();
    else
	miniWidget()->setOverridePageMedia( miniWidget()->mediaNames()[id-1] );
}

void KGVPart::slotShowScrollBars()
{
    _pageView->enableScrollBars( _showScrollBars->isChecked() );
}

void KGVPart::slotShowMarkList()
{
    if( _showPageList->isChecked() ) 
    {
	_markList->show();
	_scrollBox->show();
	_divider->show();
    }
    else 
    {
	_markList->hide();
	_scrollBox->hide();
	_divider->hide();
    }
}

void KGVPart::slotShowPageLabels()
{
    _docManager->enablePageLabels( _showPageLabels->isChecked() );
}


KGVBrowserExtension::KGVBrowserExtension( KGVPart *parent ) :
    KParts::BrowserExtension( parent, "KGVBrowserExtension" )
{
    emit enableAction( "print", true );
    setURLDropHandlingEnabled( true );
}

void KGVBrowserExtension::print()
{
    ((KGVPart *)parent())->miniWidget()->print();
}


KGVRun::KGVRun( const KURL& url, mode_t mode, bool isLocalFile, 
                bool showProgressInfo ) :
    KRun( url, mode, isLocalFile, showProgressInfo )
{
    connect( this, SIGNAL( finished() ), SLOT( emitFinishedWithMimetype() ) );
}

KGVRun::~KGVRun()
{}

void KGVRun::foundMimeType( const QString& mimetype )
{
    kdDebug(4500) << "KGVRun::foundMimeType( " << mimetype << " )" << endl;

    if( m_job && m_job->inherits( "KIO::TransferJob" ) )
    {
	KIO::TransferJob *job = static_cast< KIO::TransferJob* >( m_job );
	job->putOnHold();
	KIO::Scheduler::publishSlaveOnHold();
	m_job = 0;
    }

    _mimetype = mimetype;

    m_bFinished = true;
    m_timer.start( 0, true );
}

#include "kgv_view.moc"

