
/****************************************************************************
** execute.cpp file.
**
**   Created : 
**        by : Varol Okan 
** Copyright : (c) Varol Okan
**   License : GPL v 2.0
**
** This file handles the user input to generate a DVD menu.
** Please note that this is work in progress and depends also on the
** developement of dvdauthor, which is at the moment in version 0.67.
** Future versions of dvdauthor might need changes to this file.
**
*****************************************************************************/
#include <sys/stat.h>
#include <stdlib.h>
#ifdef QDVD_LINUX 
#include <unistd.h>
#endif

#include "execute.h"
#include "global.h"

#include <qthread.h>
#include <qvariant.h>
#include <qpushbutton.h>
#include <qtextedit.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qimage.h>
#include <qpixmap.h>
#include <qstringlist.h>
#include <qthread.h>
#include <qtimer.h>
#include <qcheckbox.h>
#include <qlineedit.h>
#include <qsplitter.h>
#include <qfiledialog.h>

#include "win32.h"
#include "utils.h"
#include "messagebox.h"
#include "errormessages.h"

Execute::Execute ( QObject * parent, const char * name)
	: QProcess (parent, name)
{
	initMe();
}

Execute::Execute ( const QString & arg0, QObject * parent, const char * name)
	: QProcess (arg0, parent, name)
{
	initMe ();
}

Execute::Execute ( const QStringList & args, QObject * parent, const char * name)
	: QProcess (args, parent, name)
{
	initMe ();
}

Execute::~Execute ()
{

}

void Execute::initMe ()
{
	m_iMaxWait = 10;
	m_iSeconds = 15;
	m_iCurrentCommand = 0;
	m_pDialog = NULL;
}

void Execute::raise ()
{
	if (m_pDialog)
		m_pDialog->raise ();
}

void Execute::createDialog(bool bKeep)
{
	// if the dialog ought to remain, then don't do nothin'
//printf ("bKeep=<%d> dialog=<%X>\n", bKeep, m_pDialog);
	if ( (bKeep) && (m_pDialog) )
		return;
	// else remove the current dialog, and proceed ...
	if (m_pDialog)
		delete m_pDialog;

	m_pDialog = new MyDialog ();
	m_pDialog->show();
	m_pDialog->editCommandText->clear ();
	m_pDialog->setCloseDelay(m_iSeconds);
	connect ( m_pDialog, SIGNAL(destroyed()), this, SLOT(slotDialogClosed  ()) );
	connect( this, SIGNAL(readyReadStderr()), this, SLOT(slotReadFromStderr()) );
	connect( this, SIGNAL(readyReadStdout()), this, SLOT(slotReadFromStdout()) );
	connect( this, SIGNAL(processExited  ()), this, SLOT(slotProcessExited ()) );
}

int Execute::system( QStringList &commandList, QStringList *pListEnv)
{
//	if (isRunning ())
//		return -1;
	createDialog();

	m_environmentList.clear();
	m_commandList = commandList;
	if (m_commandList.count() == 0)
		m_commandList.append (QString (""));
	if (pListEnv)
		m_environmentList = *pListEnv;
	m_iCurrentCommand = 0;
	return startCommand ();
}

int Execute::system( QString &commandString, QStringList *pListEnv)
{
//	if (isRunning ())
//		return -1;
	// Should work just as the system () call.
	// The only difference is that this call is non blocking ...
	// NOTE : commandString could hold multiple commands.
	createDialog ();

	m_environmentList.clear();
	m_commandList.clear ();
	m_commandList.append (commandString);
	if (pListEnv)
		m_environmentList = *pListEnv;
	m_iCurrentCommand = 0;
	return startCommand ();
}

int Execute::renderMenu (QString qsMenuName)
{
	// Here we start rendering the menu ...
	// This will go to QDVDAuthor::slotRenderMenu and from there to the propper DVDMenu
	emit (signalRenderMenu(qsMenuName));
	// Increase the command counter ...
	m_iCurrentCommand ++;
	// Start the next process, or start the timer to terminate the whole thing ...
//	if (startCommand() == 0)
//		m_pDialog->processingFinished (m_iSeconds);
	return 1;
}

int Execute::startCommand ()
{
	QStringList *pListEnv = NULL;
	// This function is called from system, and from slotProcessExited ();
	
	// First we check if there are any commands left in the QStringList.
	if (m_iCurrentCommand > m_commandList.count()-1)	{
		// Do some cleaning up. Not needed but better to be done ...
		m_commandList.clear ();
		m_iCurrentCommand = 0;
		return 0;
	}
	// second we take care of the environment.
	if (!m_environmentList.isEmpty())
		pListEnv = &m_environmentList;
	// Here we add the command to the top (command line) in the GUI.
	m_pDialog->editCommandText->append (QString ("cmd> ") + m_commandList[m_iCurrentCommand]);

	// if the command is to render a menu it is done internally and not through a command line (yet)
	// Thus we do the rendering and return here ...
	if (m_commandList[m_iCurrentCommand].find( QString(RENDER_MENU)) > -1)
 		return renderMenu(m_commandList[m_iCurrentCommand].right(m_commandList[m_iCurrentCommand].length() - QString (RENDER_MENU).length()));
	
	// Here we generate a simple shell script to execute the commands under the shell.
	// QProcess is currently not flexible enough to handle this internal.
	QString qsExecute = Global::qsTempPath + QString ( "/execute.sh" );
	QFile file ( qsExecute );
	if ( file.open( IO_WriteOnly ) ) {
		QTextStream stream( &file );
		stream << Global::qsBashString << m_commandList[m_iCurrentCommand];
		file.close();
	}
	// And here we grant read/write/execute permission.
	chmod ( qsExecute.utf8 ( ), S_IXGRP | S_IWGRP | S_IRGRP | S_IEXEC | S_IRUSR | S_IWRITE | S_IROTH | S_IXOTH | S_IWOTH );
	// Increase the command counter ...
	m_iCurrentCommand ++;
	// Set the script to execute ...
	clearArguments ( );
	addArgument    ( qsExecute );
	// And eh voila, ici on executer les commands.
	start ( pListEnv );
	// return to the calling process (thread)
	return 1;
}
// How about adding another timer, which verifies that the process did not die for what reasons ever ?
// Also the tatus could indicate <Running>...<blank>...<Running>...<blank>...
// I have to think about it since a avi->mpeg2 conversion would def. take longer then a few seconds ...

void Execute::slotProcessExited ()
{
	if (m_pDialog)	{
		slotReadFromStdout();
		slotReadFromStderr();
	}
	else
		printf ("<%s><%s>\n", (const char *)readStdout(), (const char *)readStderr());
	// Start the next process, or start the timer to terminate the whole thing ...
	if (startCommand() == 0)
               m_pDialog->processingFinished (m_iSeconds);
}

void Execute::slotDialogClosed()
{
	m_pDialog = NULL;
//printf (" Execute::slotDialogClosed Dialog was closed ...\n");
}
	
void Execute::setCloseDelay (uint iSeconds)
{
	// This function sets the number of seconds the GUI should wait
	// AFTER the process has terminated before auto closing the GUI.
	// range can be [0 - int]
	m_iSeconds = iSeconds;
}

uint Execute::getCloseDelay ()
{
	return m_iSeconds;
}

void Execute::setMaxWait (uint iSeconds)
{
	// This function sets the number of seconds that the system - call
	// will wait for the termination of the process. If the process
	// does not termintate within this time period, a error message
	// is send to the GUI interface and
	// range can be [0 - int]
	m_iMaxWait = iSeconds;
}

uint Execute::getMaxWait ()
{
	return m_iMaxWait;
}

void Execute::slotReadFromStderr()
{
	static QColor errorColor = QColor (255, 0, 0);
	if (m_pDialog)	{
		// Okay, errors red, warnings yellow please ...
		QColor stdColor = m_pDialog->textOutput->color();
		QString strError = readStderr ();
		if (strError.lower().find ("err") > -1)
			errorColor = QColor(255, 0, 0);
		else
			errorColor = QColor (220,220,0);
		m_pDialog->textOutput->setColor (errorColor);
		m_pDialog->textOutput->append (strError);
		m_pDialog->textOutput->setColor (stdColor);
	}
	else
		printf ("<%s>\n", (const char *)readStderr());
}

void Execute::slotReadFromStdout()
{
	if (m_pDialog)	{
		m_pDialog->textOutput->setColor (QColor( 27, 212, 7 ) );
		m_pDialog->textOutput->append (readStdout ());
	}
	else
		printf ("<%s>\n", (const char *)readStdout());
}

MyDialog::MyDialog( QWidget* parent, const char* name, bool modal, WFlags fl )
    : QDialog( parent, name, modal, fl )
{
	if ( !name )
		setName( "ExecuteDialog" );
	setSizeGripEnabled( TRUE );
	MyDialogLayout = new QGridLayout( this, 1, 1, 11, 6, "MyDialogLayout"); 

	layout2 = new QHBoxLayout( 0, 0, 6, "layout2"); 

	buttonHelp = new QPushButton( this, "buttonHelp" );
	buttonHelp->setAutoDefault( TRUE );
	layout2->addWidget( buttonHelp );

	checkKeepOpen = new QCheckBox( this, "checkKeepOpen" );
	layout2->addWidget( checkKeepOpen );
	QSpacerItem* spacer = new QSpacerItem( 310, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
	layout2->addItem( spacer );

	buttonAnalyze = new QPushButton( this, "buttonAnalyze" );
	buttonAnalyze->setAutoDefault  ( true  );
	//buttonAnalyze->setEnabled      ( false );
	layout2->addWidget             ( buttonAnalyze );

	buttonExport = new QPushButton( this, "buttonExport" );
	buttonExport->setAutoDefault( TRUE );
	layout2->addWidget( buttonExport );

	buttonPlay = new QPushButton( this, "buttonPlay" );
	buttonPlay->setAutoDefault( TRUE );
	buttonPlay->setEnabled(false);
	layout2->addWidget( buttonPlay );

	buttonOk = new QPushButton( this, "buttonOk" );
	buttonOk->setAutoDefault( TRUE );
	buttonOk->setDefault( TRUE );
	layout2->addWidget( buttonOk );

	buttonCancel = new QPushButton( this, "buttonCancel" );
	buttonCancel->setAutoDefault( TRUE );
	layout2->addWidget( buttonCancel );

	MyDialogLayout->addLayout( layout2, 2, 0 );

	editCloseText = new QLineEdit( this, "editCloseText" );
	editCloseText->setPaletteForegroundColor( QColor( 170, 0, 255 ) );
	editCloseText->setPaletteBackgroundColor( QColor( 170, 170, 255 ) );
	editCloseText->setFrameShape( QLineEdit::NoFrame );
	editCloseText->setFrameShadow( QLineEdit::Plain );
	editCloseText->setReadOnly( TRUE );

	MyDialogLayout->addWidget( editCloseText, 1, 0 );

	splitter1 = new QSplitter( this, "splitter1" );
	splitter1->setOrientation( QSplitter::Vertical );

	editCommandText = new QTextEdit( splitter1, "editCommandText" );
	editCommandText->setMinimumSize( QSize( 0, 50 ) );
	editCommandText->setMaximumSize( QSize( 32767, 32767 ) );
	editCommandText->setBaseSize (QSize (editCommandText->width(), 50));
	editCommandText->setPaletteForegroundColor( QColor( 255, 85, 0 ) );
	editCommandText->setPaletteBackgroundColor( QColor( 0, 0, 0 ) );
	editCommandText->setWordWrap( QTextEdit::WidgetWidth );

	textOutput = new QTextEdit( splitter1, "textOutput" );
	textOutput->setPaletteForegroundColor( QColor( 27, 212, 7 ) );
	textOutput->setPaletteBackgroundColor( QColor( 0, 0, 0 ) );
	textOutput->setWordWrap( QTextEdit::WidgetWidth );

	MyDialogLayout->addWidget( splitter1, 0, 0 );
	languageChange();
	resize( QSize(740, 471).expandedTo(minimumSizeHint()) );
	clearWState( WState_Polished );

	// signals and slots connections
	connect( buttonAnalyze, SIGNAL( clicked ( ) ),     this, SLOT( slotAnalyze ( ) ) );
	connect( buttonExport,  SIGNAL( clicked ( ) ),     this, SLOT( slotExport  ( ) ) );
	connect( buttonPlay,    SIGNAL( clicked ( ) ),     this, SLOT( slotPlay    ( ) ) );
	connect( buttonOk,      SIGNAL( clicked ( ) ),     this, SLOT( accept      ( ) ) );
	connect( buttonCancel,  SIGNAL( clicked ( ) ),     this, SLOT( reject      ( ) ) );
	connect( checkKeepOpen, SIGNAL( toggled ( bool ) ),this, SLOT( slotKeepOpen( bool ) ) );

	char *pCheckClosed = getenv ( "QDVD_KEEPDLGOPEN" );
	if (  pCheckClosed )
	  checkKeepOpen->setChecked ( true );

	m_bDoneProcessing = false;
	m_pTimer = NULL;
	QValueList<int> listSizes;
	listSizes.append    (    50     );
	listSizes.append    (   1000    );
	splitter1->setSizes ( listSizes );
}

MyDialog::~MyDialog()
{
	// I know Qt does this but it is good programming style, 
	// to take care of all allocated objects.
	if (m_pTimer)
		delete m_pTimer;
}

void MyDialog::setCloseDelay (uint iSeconds)
{
	// This function sets the number of seconds the GUI should wait
	// AFTER the process has terminated before auto closing the GUI.
	// range can be [0 - int]
	m_iSeconds = iSeconds;
}

uint MyDialog::getCloseDelay ()
{
	return m_iSeconds;
}

void MyDialog::languageChange()
{
	setCaption( tr( "Execution Dialog" ) );
	buttonHelp->setText( tr( "&Help" ) );
	buttonHelp->setAccel( QKeySequence( tr( "F1" ) ) );
	checkKeepOpen->setText( tr( "Keep Open" ) );
	buttonAnalyze->setText( tr( "&Analyze log" ) );
	buttonAnalyze->setAccel( QKeySequence( QString::null ) );
	buttonExport->setText( tr( "&Export" ) );
	buttonExport->setAccel( QKeySequence( QString::null ) );
	buttonPlay->setText( tr( "&Play" ) );
	buttonPlay->setAccel( QKeySequence( QString::null ) );
	buttonOk->setText( tr( "&OK" ) );
	buttonOk->setAccel( QKeySequence( QString::null ) );
	buttonCancel->setText( tr( "&Cancel" ) );
	buttonCancel->setAccel( QKeySequence( QString::null ) );
	editCloseText->setText( tr( "Status : " ) );
}

void MyDialog::slotTimerDone ()
{
	static uint iCounter = 0;

	iCounter ++;
	if (iCounter > m_iSeconds)	{
		m_pTimer->stop();
		iCounter = 0;
		editCloseText->setText (tr ("Status : Done."));
		accept ();
	}
	editCloseText->setText (tr ("Status : Close dialog in %1 seconds.").arg (m_iSeconds - iCounter + 1));
}

void MyDialog::slotAnalyze ( )
{
  // this function will scan through the output and report 
  // the most common error messages.

  ErrorMessages errors;
  int t, iErrorCounter = 0;
  QSize dialogSize ( 700, 500 );
  QString qsHTML, qsCaption;
  QString qsOutputText = textOutput->text( );

  qsHTML  = "<TABLE WIDTH=\"100%\" ><TR><TD BGCOLOR=\"#9A3CFF\"><FONT SIZE=+2><B><CENTER>"; 
  qsHTML += tr ( "Please note this dialog only displays <B>KNOWN</B> errors.<P>" );
  qsHTML += "</CENTER></B></FONT>";
  qsHTML += tr ( "If the DVD still fails to build consider sending the file created through " );
  qsHTML += tr ( "[<FONT COLOR=WHITE>Tools -> feedback</FONT>] to the author of QDVDAuthor." );
  qsHTML += "</TD></TR></TABLE><HR>";
  for ( t=0; t<errors.count ( );t++ ) {
    if ( qsOutputText.find ( errors.errorString ( t ) ) > -1 ) {
      iErrorCounter ++;
      qsHTML += errors.helpMessage ( t );
    }
  }

  if ( iErrorCounter == 0 ) {
    qsHTML = tr ( "<P><FONT COLOR=DARKGREEN>Did not find any known error.</FONT><P>" );
    qsCaption = tr ( "No errors found." );
  }
  else if ( iErrorCounter == 1 )
    qsCaption = tr ( "Found 1 error" );
  else
    qsCaption = tr ( "Found %1 errors" ).arg ( iErrorCounter );
  MessageBox::html ( NULL, qsCaption, qsHTML, dialogSize  );

}

void MyDialog::processingFinished (int iSeconds)
{
  buttonAnalyze->setEnabled ( true );
  // We're done, timer stuff now ok
  m_bDoneProcessing = true;
  QString qsStorage = Global::qsTempPath + QString ("/") + Global::qsProjectName + QString ("/output.txt");
  saveOutput (qsStorage);
  // Start the timer if the checkbox is clear
  if (!checkKeepOpen->isChecked())        {
    startTimer(iSeconds);
  }
  // First we check if qplayer is there and executable ...
  QString qsPlayer = Global::qsExternalPlayer;
  QFileInfo fileInfo    ( qsPlayer );

  if ( !fileInfo.exists ( ) ) {
    Utils theUtils;
    qsPlayer = theUtils.checkForExe( qsPlayer );
    if ( qsPlayer.isEmpty ( ) )
      return;
    fileInfo.setFile ( qsPlayer );
  }
  // next we check if it is executable ...
  if (!fileInfo.isExecutable())
    return;
  // last we check if there has been something clreated to play at all ...
  fileInfo.setFile ( Global::qsProjectPath + QString ( "/VIDEO_TS/VIDEO_TS.BUP" ) );
  if ( ! fileInfo.exists ( ) )
    return;
  fileInfo.setFile ( Global::qsProjectPath + QString ( "/VIDEO_TS/VIDEO_TS.IFO" ) );
  if ( ! fileInfo.exists ( ) )
    return;
  fileInfo.setFile ( Global::qsProjectPath + QString ( "/VIDEO_TS/VIDEO_TS.VOB" ) );
  if ( ! fileInfo.exists ( ) )
    return;

  writeZoneCode ( );
  buttonPlay->setEnabled ( true );
}

void MyDialog::writeZoneCode ( )
{
  if ( ( Global::iRegionalZone == 0 ) || ( Global::iRegionalZone > 6 ) )
    return;

  const char arrayCodes[] = { 0x00, 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF };
  // * Region 1 - The U.S., U.S. territories and Canada
  // * Region 2 - Europe, Japan, the Middle East, Egypt, South Africa, Greenland
  // * Region 3 - Taiwan, Korea, the Philippines, Indonesia, Hong Kong
  // * Region 4 - Mexico, South America, Central America, Australia, New Zealand, Pacific Islands, Caribbean
  // * Region 5 - Russia (okay, former Russia), Eastern Europe, India, most of Africa, North Korea, Mongolia
  // * Region 6 - China 

  QFile ifo ( Global::qsProjectPath + "/VIDEO_TS/VIDEO_TS.IFO" );
  for ( int t=0; t<2; t++ )  {
    if  ( ifo.open ( IO_ReadWrite ) == true )  {
      if ( ifo.at  ( 0x23 ) )
        ifo.putch  ( arrayCodes[Global::iRegionalZone] );
    }
    ifo.close      ( );
    ifo.setName    ( Global::qsProjectPath + "/VIDEO_TS/VIDEO_TS.BUP" );
  }
}

// The next three are related to the Keep Open countdown
void MyDialog::startTimer (int iSeconds)
{
	m_iSeconds = iSeconds;
	if (m_iSeconds > 0)     {
		if (m_pTimer)
			delete m_pTimer;
		m_pTimer = new QTimer ( this );
		connect( m_pTimer, SIGNAL(timeout()), this, SLOT(slotTimerDone()) );
		m_pTimer->start (1000);
	}
}

void MyDialog::stopTimer ()
{
	if (m_pTimer)	{
		if (m_pTimer->isActive())	{
			m_pTimer->stop ();
		}
	}
}

void MyDialog::slotKeepOpen (bool bKeepOpen)
{
	// This could be called before processingFinished()
	if (! m_bDoneProcessing)
		return;
	if (bKeepOpen)	{
		stopTimer ();
		editCloseText->setText (tr ("Status : "));
	}
	else
		startTimer (m_iSeconds);
}

void MyDialog::slotPlay ()
{
  QString qsCommand, qsPlayer, qsFullPath;
  Utils theUtils;
  QDir thePath ( Global::qsProjectPath );

  qsPlayer = Global::qsExternalPlayer;

  qsPlayer = theUtils.checkForExe( qsPlayer );
  if ( qsPlayer.isEmpty ( ) )
    qsPlayer = Global::qsExternalPlayer;

  qsCommand = QString ( "%1 dvd://%2/VIDEO_TS/ &" ).arg ( qsPlayer ).arg ( thePath.absPath ( ) );

  if ( system ( qsCommand.ascii ( ) ) == -1 )
    return; // error
	
}

void MyDialog::slotExport ()
{
	QString qsSaveFileName = QFileDialog::getSaveFileName( Global::qsProjectPath, tr("Text files (*.txt *.TXT)"),
		this, tr("Save file dialog. Choose a filename to save under"));
  	if (qsSaveFileName.isEmpty())
		return;
	saveOutput (qsSaveFileName);
}

void MyDialog::saveOutput (QString qsFileName)
{
	QFile file( qsFileName );
	if ( file.open( IO_WriteOnly ) ) {
		QTextStream stream( &file );
		stream << editCommandText->text() << "\n";
		stream << "\n+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-\n\n";
		stream << textOutput->text() << "\n";
		file.close();
	}
}

