/***************************************************************************
 *   Copyright (C) 2005 by James Thorniley                                 *
 *   james@uncommonlygood.co.uk                                            *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "burndevicewidget.h"
#include "k3bemptydiscwaiter.h"
#include "burnsidebarplugin.h"
#include "burninfomessagedialog.h"
#include <k3bjob.h>
#include <k3bdevice.h>
#include <k3bdevicemanager.h>
#include <k3bdoc.h>
#include <qbitmap.h>
#include <k3bcore.h>
#include <kstdguiitem.h>
#include <qlabel.h>
#include <qlayout.h>
#include <kprogress.h>
#include <qcombobox.h>
#include <qwidgetstack.h>
#include <kconfig.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <qstring.h>
#include <qmutex.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <qtimer.h>
#include <klocale.h>
#include <qvbox.h>
#include <kpushbutton.h>
#include <qstringlist.h>
#include <qtextstream.h>
#include <k3bdvddoc.h>
#include <k3bglobals.h>
#include <kstddirs.h>
#include <qpainter.h>
#include <dcopclient.h>
#include <qtooltip.h>
#include <qapplication.h>
#include <qeventloop.h>
#include <qstyle.h>

BurnDeviceWidget::BurnDeviceWidget(K3bDevice::Device *device, const QWidget *dragSource, BurnSidebarPlugin *plugin, QWidget *parent, const char *name)
 : QFrame(parent, name),
   DCOPObject( QCString("burnsidebar" + device->vendor() + device->description() ) ),
   m_isDVD(device->isDVD()),
   m_dndBurn( 0 ),
   m_dragSource( dragSource ),
   m_device( device ),
   m_burnJob(0),
   m_plugin( plugin ),
   m_inLoop( false )
{
  setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
  setLineWidth( style().pixelMetric(QStyle::PM_DefaultFrameWidth, this) );
    
  setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );

  setAcceptDrops( true );
  //initialise all the GUI stuff
  m_deviceRescanButton = new QPushButton( this );
  m_deviceRescanButton->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum );
  QToolTip::add( m_deviceRescanButton, i18n( "Check media" ) );
  setRescanPixmap( "cdwriter_unmount" );
  connect( m_deviceRescanButton, SIGNAL( clicked() ),
           this, SLOT( configureSpeedCombo() ) );

  m_widgetStack = new QWidgetStack( this );
  
  m_hLayout = new QHBoxLayout( this );

  m_hLayout->addWidget( m_deviceRescanButton );
  m_hLayout->addWidget( m_widgetStack );
  m_hLayout->setSpacing( 5 );
  m_hLayout->setMargin( 5 );

  m_mainVBox = new QVBox( this );
  m_mainVBox->setSpacing(2);

  m_mainHBox = new QHBox( m_mainVBox );
  m_mainHBox->setSpacing(3);

  m_speedLabel = new QLabel( m_mainHBox );
  m_speedLabel->setText( i18n( "Speed:" ) );
  m_speedCombo = new QComboBox( m_mainHBox );
  configureSpeedCombo();
  
  m_startBurnButton = new QPushButton( m_mainVBox );
  m_startBurnButton->hide();

  connect( m_startBurnButton, SIGNAL( clicked() ),
           this, SLOT( slotStartBurnClicked() ) );

  m_progressBox = new QWidget( this );
  QVBoxLayout *progressLayout = new QVBoxLayout( m_progressBox );
  m_progress = new KProgress( m_progressBox );
  m_progress->setFormat( i18n("Burning... %p%") );
  m_progress->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
  progressLayout->addStretch(1);
  progressLayout->add( m_progress );
  progressLayout->addStretch(1);

  m_errorBox = new QWidget( this );
  m_errorBox->setBackgroundColor( QColor( 0xff, 0 ,0 ) );
  m_errorLabel = new QLabel( m_errorBox );
  m_errorLabel->setBackgroundColor( QColor( 0xff, 0, 0 ) );
  m_errorLabel->setTextFormat( Qt::RichText );
  m_errorLabel->setMargin(0);
  m_errorLabel->setPaletteForegroundColor( QColor( 0xff, 0xff, 0xff ) );
  m_errorLabel->setText( i18n( "Burning failed" ) );
  QFont errorFont = m_errorLabel->font();
  errorFont.setBold( true );
  m_errorLabel->setFont( errorFont );


  m_errorOkButton = new KPushButton( KStdGuiItem::ok(), m_errorBox );
  m_errorOkButton->setBackgroundColor( QColor( 0xff, 0 ,0 ) );
  connect( m_errorOkButton, SIGNAL( clicked() ),
           this, SLOT( slotErrorTimeout() ) );
  m_errorDetailsButton = new QPushButton( m_errorBox );
  m_errorDetailsButton->setText( i18n( "Details..." ) );
  m_errorDetailsButton->setBackgroundColor( QColor( 0xff, 0 ,0 ) );
  connect( m_errorDetailsButton, SIGNAL( clicked() ),
           this, SLOT( displayInfoMessages() ) );

  QVBoxLayout *errorLayout = new QVBoxLayout( m_errorBox );
  errorLayout->addStretch(1);
  errorLayout->add( m_errorLabel );
  errorLayout->addStretch(1);
  QHBoxLayout *errorButtonLayout = new QHBoxLayout( errorLayout );
  errorButtonLayout->setSpacing( 10 );
  errorButtonLayout->add( m_errorOkButton );
  errorButtonLayout->add( m_errorDetailsButton );
  errorLayout->addStretch(1);

  m_burnVBox = new QWidget( this );
  QVBoxLayout *burnVLayout = new QVBoxLayout( m_burnVBox );
  burnVLayout->addStretch( 1 );
  m_burnLabel = new QLabel( i18n("Ready to begin burning" ), m_burnVBox );
  burnVLayout->add( m_burnLabel );
  burnVLayout->addStretch( 1 );
  m_burnHBox = new QHBox( m_burnVBox );
  burnVLayout->add( m_burnHBox );
  m_burnHBox->setSpacing(10);
  m_burnButton = new QPushButton( i18n( "Burn" ), m_burnHBox );
  m_burnButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
  m_burnCancelButton = new QPushButton( i18n( "Cancel" ), m_burnHBox );
  m_burnCancelButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
  burnVLayout->addStretch( 1 );

  connect( m_burnButton, SIGNAL( clicked() ),
           this, SLOT( startBurning() ) );
  connect( m_burnCancelButton, SIGNAL( clicked() ),
           this, SLOT( cancelClicked() ) );

  m_widgetStack->addWidget( m_progressBox );
  m_widgetStack->addWidget( m_mainVBox );
  m_widgetStack->addWidget( m_errorBox );
  m_widgetStack->addWidget( m_burnVBox );
  m_widgetStack->raiseWidget( m_mainVBox );

  m_infoMessages = new QStringList();

  connectDCOPSignal("kded", "mediamanager", "mediumAdded(QString)", "mediumChanged(QString)", false);
  connectDCOPSignal("kded", "mediamanager", "mediumChanged(QString)", "mediumChanged(QString)", false);
  connectDCOPSignal("kded", "mediamanager", "mediumRemoved(QString)", "mediumChanged(QString)", false);
  connectDCOPSignal("kded", "mediamanager", "filesAdded(KURL)", "fileAdded(KURL)", false);
}

bool BurnDeviceWidget::process( const QCString &fun, const QByteArray &data,
		QCString &replyType, QByteArray &replyData)
{
  kdDebug() << "BurnDeviceWidget::process " << fun << endl;

  if( fun == "mediumChanged(QString)" )
  {
    QDataStream arg( data, IO_ReadOnly );
    QString name;
    arg >> name;
    if( m_device->blockDeviceName().contains( name ) )
      configureSpeedCombo(); //looks like the media changed in this device, rescan speeds
  }

  return true;
}
void BurnDeviceWidget::setRescanPixmap( const QString &iconName )
{
  QPixmap pixmap = KGlobal::iconLoader()->loadIcon( iconName, KIcon::Desktop, KIcon::SizeMedium );
  //QPainter painter( &pixmap );
  //QPixmap reload = KGlobal::iconLoader()->loadIcon( "reload", KIcon::Desktop, KIcon::SizeSmall );
  //painter.drawPixmap( 26,22, reload );
  m_deviceRescanButton->setPixmap( pixmap );
}

void BurnDeviceWidget::configureSpeedCombo()
{

  m_isDVD = m_device->isDVD();
  int speedDiv = m_isDVD ? 1385 : 175;

  QString speedString = m_isDVD ? "DVD %1x" : "CD %1x";
  
  m_speedCombo->clear();
  m_speedCombo->insertItem( i18n( "Auto" ) );
  
  int mediaType = m_device->diskInfo().mediaType();
  if( !( mediaType & (K3bDevice::MEDIA_WRITABLE_CD | K3bDevice::MEDIA_WRITABLE_DVD )) )
  {
    kdDebug() << "BurnDeviceWidget: New media is not writable" << endl;
    setRescanPixmap( "cdrom_unmount" );

    return; 
    //don't bother listing speeds if the media is not writable,
    //they'll be irrelevant anyway
  }

  QValueList<int> speeds = m_device->determineSupportedWriteSpeeds();
  QValueList<int>::iterator it;
  for( it = speeds.begin(); it != speeds.end(); it++ )
    m_speedCombo->insertItem( speedString.arg( QString::number( *it / speedDiv ) ) );

  std::string icon;
  if( mediaType & K3bDevice::MEDIA_WRITABLE_CD )
    icon = "cdwriter_unmount";
  else if( mediaType & K3bDevice::MEDIA_WRITABLE_DVD )
    icon = "dvd_unmount";

  setRescanPixmap( icon );

}

int BurnDeviceWidget::waitForMedia( K3bDevice::Device* dev,
                                    int mediaState,
                                    int mediaType,
                                    const QString& message)
{
  kdDebug() << "BurnDeviceWidget::waitForMedia" << endl;
  int ret = K3bEmptyDiscWaiter::wait( dev, mediaState, mediaType, message, this );
  kdDebug() << "...returning ... " << ret << endl;
  return ret;
}

void BurnDeviceWidget::blockingInformation( const QString& text,
                                                const QString& caption )
{
  //kdDebug() << "BurnDeviceWidget::blockingInformation" << endl;
  KMessageBox::information( this, text, caption );
}

void BurnDeviceWidget::displayInfoMessages()
{
  BurnInfoMessageDialog( *m_infoMessages, this ).exec();
}

bool BurnDeviceWidget::questionYesNo( const QString& text,
                                      const QString& caption )
{
  return ( KMessageBox::questionYesNo( this, text, caption ) == KMessageBox::Yes );
}

BurnDeviceWidget::~BurnDeviceWidget()
{
  BurnSidebarPlugin::config()->sync();
  delete m_infoMessages;
  delete m_dndBurn;
  if( m_inLoop )
  {
    QApplication::eventLoop()->exitLoop();
  }
}

void BurnDeviceWidget::dragEnterEvent( QDragEnterEvent *e )
{
  e->accept();
}

void BurnDeviceWidget::dropEvent( QDropEvent *de )
{
  if( de->source() == m_dragSource )
  {
    m_dndBurn = 0;

    m_widgetStack->raiseWidget( m_burnVBox );
    setAcceptDrops( false );
  }
  else if( de->provides( "text/uri-list" ) )
  {
    
    m_dndBurn = m_isDVD ? new K3bDvdDoc( this ) : new K3bDataDoc( this );
    m_dndBurn->newDocument();
    m_dndBurn->setWritingMode( K3b::WRITING_MODE_AUTO );
    m_dndBurn->setOnTheFly( true );
    m_dndBurn->setSpeed(0);
    m_dndBurn->setRemoveImages(true);
    m_dndBurn->setWritingApp( K3b::DEFAULT );
    QString tempDir = locateLocal( "tmp", "konqburn-" + QString::number( getpid() ) );
    m_dndBurn->setTempDir( tempDir );
    m_dndBurn->setOnlyCreateImages(false);
    m_dndBurn->setMultiSessionMode( K3bDataDoc::NONE );
  
    QTextStream stream( de->encodedData("text/uri-list"), IO_ReadOnly );
    while( !stream.atEnd() )
    {
      KURL url( stream.readLine() );
      if( url.isValid() )
       m_dndBurn->addUrl( url );
    }
    
    m_widgetStack->raiseWidget( m_burnVBox );
    setAcceptDrops( false );
  }
}

void BurnDeviceWidget::cancelClicked()
{
  m_widgetStack->raiseWidget( m_mainVBox );			
  setAcceptDrops(true);
  delete m_dndBurn;
  m_dndBurn = 0;
}

void BurnDeviceWidget::startBurning()
{

    K3bDoc *document = m_dndBurn ? m_dndBurn : m_plugin->k3bDoc();
    if( !document )
      return;

    document->setBurner( m_device );
    QValueList<int> speeds = m_device->determineSupportedWriteSpeeds();
    if( (m_speedCombo->currentItem() != 0 )
        && ( (m_speedCombo->count()-1) == (int)speeds.count() ))
    { //if "auto" selected, don't call setSpeed
      //also, don't call setSpeed if it appears device has changed its mind
      //about supported write speeds since the speedCombo was updated

      int speed = speeds[m_speedCombo->currentItem()-1];
      kdDebug() << "BurnDeviceWidget Setting burn speed to " << speed << endl;
      document->setSpeed( speed );
    }

//#ifndef BURN_FINAL
    //document->setDummy( true );
//#endif
    Q_ASSERT( m_burnJob == 0 ); //we don't allow creating a new burnjob 
                               //if one already exists
    m_burnJob = document->newBurnJob( this );

    connect( m_burnJob, SIGNAL( started() ), this, SLOT( slotStarted() ) );
    connect( m_burnJob, SIGNAL( percent(int) ), m_progress, SLOT( setValue(int ) ) );
    connect( m_burnJob, SIGNAL( finished(bool) ), this, SLOT( slotFinished(bool) ) );
    connect( m_burnJob, SIGNAL( infoMessage( const QString&, int) ), this, SLOT( slotInfoMessage( const QString&, int ) ) );

    m_burnJob->start();
}

void BurnDeviceWidget::slotInfoMessage(const QString &message, int type )
{
  kdDebug() << "Info message: " << message << endl;
  
  m_infoMessages->append( message );

  if( type == K3bJob::ERROR )
  {
    m_widgetStack->raiseWidget( m_errorBox );
    setBackgroundColor( QColor( 0xff, 0, 0 ) );
  }
}

void BurnDeviceWidget::slotErrorTimeout()
{
  m_widgetStack->raiseWidget( m_mainVBox );
  setAcceptDrops( true );
  setBackgroundColor( m_speedLabel->backgroundColor() );
}

void BurnDeviceWidget::slotStarted()
{
  //kdDebug() << "void BurnDeviceWidget::slotStarted()" << endl;
  m_progress->setValue(0);
  m_widgetStack->raiseWidget( m_progressBox);
  m_infoMessages->clear();
}

void BurnDeviceWidget::slotFinished( bool success )
{
  kdDebug() << "BurnDeviceWidget::slotFinished()" << endl;

  if( m_widgetStack->visibleWidget() == m_progressBox )
  {
    m_widgetStack->raiseWidget( m_mainVBox );
    setAcceptDrops( true );
  }
  
  if( !m_burnJob )
  {
    //looks like the jobs already finished
    return;
  }
  
  K3bDoc *doc = m_burnJob->doc();
  
  if( doc->isA( "K3bAudioDoc" ) )
  {
    //if this is an audio doc then there is a chance that some threads
    //started by the document are still running. The only way to find
    //out when those threads have finished is to wait for the changed
      //signal. It is not safe to delete the document and the job until
      //after the threads have finished, so we'll enter a new loop here
      //and slotDocChanged will exit for us
    connect( doc, SIGNAL( changed() ), this, SLOT( slotDocChanged() ) );
    //however, if the thread is already finished, the changed signal
    //will not be produced... so lets have a timeout too
    QTimer::singleShot( 3000, this, SLOT(slotDocChanged()) );
    m_inLoop = true;
    //disabling the burn button should prevent us from nesting loops
    m_burnButton->setEnabled( false );
    QApplication::eventLoop()->enterLoop();  
    m_burnButton->setEnabled( true );
    m_inLoop = false;
    disconnect( doc, SIGNAL( changed() ), this, SLOT( slotDocChanged() ) );
  }  
  m_burnJob->deleteLater();
  doc->deleteLater();
  m_dndBurn = 0; //just in case we were burning from a dnd
  m_burnJob = 0;
  kdDebug() << "finished BurnDeviceWidget::slotFinished()" << endl;
}

void BurnDeviceWidget::slotDocChanged()
{
  kdDebug() << "slotDocChanged()" << endl;
  if( m_inLoop )
  {
    QApplication::eventLoop()->exitLoop();
  }
}

void BurnDeviceWidget::slotDiscTypeSelected( BurnSidebarPlugin::DiscType type )
{
  m_startBurnButton->show();
  if( type == BurnSidebarPlugin::AudioCD )
  {
    if( m_device->writesCd() )
    {
      m_startBurnButton->setText( i18n("Burn audio CD") );
      m_startBurnButton->setEnabled( true );
    }
    else
    {
      m_startBurnButton->setText( i18n("Cannot write audio CDs") );
      m_startBurnButton->setEnabled( false );
    }
  }
  else if( type == BurnSidebarPlugin::DataCD )
  {
    if( m_device->writesCd() )
    {
      m_startBurnButton->setText( i18n("Burn data CD") );
      m_startBurnButton->setEnabled( true );
    }
    else
    {
      m_startBurnButton->setText( i18n("Cannot write data CDs") );
      m_startBurnButton->setEnabled( false );
    }
  }
  else if( type == BurnSidebarPlugin::DataDVD )
  {
    if( m_device->writesDvd() )
    {
      m_startBurnButton->setText( i18n("Burn data DVD") );
      m_startBurnButton->setEnabled( true );
    }
    else
    {
      m_startBurnButton->setText( i18n("Cannot write data DVDs") );
      m_startBurnButton->setEnabled( false );
    }
  }
}

void BurnDeviceWidget::slotStartBurnClicked()
{
  m_widgetStack->raiseWidget( m_burnVBox );
  setAcceptDrops( false );
}

#include "burndevicewidget.moc"
