//=============================================================================
//
//   File : kvi_kvs_processmanager.cpp
//   Created on Wed 07 Apr 2004 03:03:52 by Szymon Stefanek
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2004-2007 Szymon Stefanek <pragma at kvirc dot net>
//
//   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 opinion) 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.
//
//=============================================================================

#define __KVIRC__

#include "kvi_kvs_processmanager.h"
#include "kvi_window.h"
#include "kvi_out.h"
#include "kvi_locale.h"
#include "kvi_parameterlist.h"
#include "kvi_app.h"
#include "kvi_console.h"
#include "kvi_kvs_script.h"
//#include <QSysInfo>

KviKvsProcessAsyncOperation::KviKvsProcessAsyncOperation(KviKvsProcessDescriptorData * d)
: KviKvsAsyncOperation(d->pWnd)
{
	m_pData = d;
	m_pProcess = 0;
	m_pExtendedRunTimeData = new KviKvsExtendedRunTimeData(new KviKvsHash(),TRUE);
	m_pPingTimer = 0;
	m_pRunTimeTimer = 0;
	m_bDeletePending = false;
}

KviKvsProcessAsyncOperation::~KviKvsProcessAsyncOperation()
{
	if(m_pPingTimer)delete m_pPingTimer;
	if(m_pRunTimeTimer)delete m_pRunTimeTimer;
	if(m_pProcess)
	{
		QObject::disconnect(m_pProcess,0,this,0);
		m_pProcess->kill();
		delete m_pProcess;
	}
	delete m_pExtendedRunTimeData;
	if(m_pData->pCallback)delete m_pData->pCallback;
	if(m_pData->pMagic)delete m_pData->pMagic;
	delete m_pData;
}

bool KviKvsProcessAsyncOperation::start()
{
	QStringList args;
	
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_NOSHELL)
	{
		args = QStringList::split(" ",m_pData->szCommandline);
	} else {
		QString szShell = m_pData->szShell;
		if(szShell.isEmpty())
		{
#ifdef COMPILE_ON_WINDOWS
			// [01:26:00] <PragmaOff> btw, what is qt_winunicode ?
			// [01:26:12] <kode54> Qt export specific to win32
			// [01:26:27] <kode54> bool which indicates whether system is Unicode (NT) or not
			// [01:26:58] <kode54> not sure if that's documented, but it is a public export
			//
			// [02:50:21] <kode54> if ( QApplication::winVersion() & Qt::WV_NT_based )
			// [02:50:41] <kode54> I see another implementation using that, maybe it is the official way of detecting that :[
#ifdef COMPILE_USE_QT4
			szShell = !(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) ? "cmd.exe /c" : "command.com /c";
#else
			szShell = "command.com /c";
#endif

			// Thnx kode54 :)
#else
			szShell = "sh -c";
#endif
		}
		args = QStringList::split(" ",szShell);
		args.append(m_pData->szCommandline);
	}
	
	m_pProcess = new KviProcess(args);
	int c = KviProcess::Stdin;
	
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTDOUT)
	{
		connect(m_pProcess,SIGNAL(readyReadStdout()),this,SLOT(readStdout()));
		c |= KviProcess::Stdout;
	}
	
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTDERR)
	{
		connect(m_pProcess,SIGNAL(readyReadStderr()),this,SLOT(readStderr()));
		c |= KviProcess::Stderr;
	}

	m_pProcess->setCommunication(c);
	
	connect(m_pProcess,SIGNAL(processExited()),this,SLOT(processExited()));
	
	if(!m_pProcess->start())
	{
		return false;
	}
	
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTARTED)
	{
		QString szPid; 
		szPid.setNum((int)(m_pProcess->processIdentifier()));
		if(trigger(EventStarted,szPid))
		{
			triggerSelfDelete();
			return true;
		}
	}

	if(m_pData->iMaxRunTime > 0)
	{
		m_pRunTimeTimer = new QTimer(this);
		connect(m_pRunTimeTimer,SIGNAL(timeout()),this,SLOT(maxRunTimeExpired()));
		m_pRunTimeTimer->start(m_pData->iMaxRunTime);
	}
	
	if(m_pData->iPingTimeout > 0)
	{
		m_pPingTimer = new QTimer(this);
		connect(m_pPingTimer,SIGNAL(timeout()),this,SLOT(ping()));
		m_pPingTimer->start(m_pData->iPingTimeout);
	}
	
	return true;
}

void KviKvsProcessAsyncOperation::ping()
{
	if(trigger(EventPing,QString::null))
	{
		triggerSelfDelete();
	}
}

void KviKvsProcessAsyncOperation::triggerSelfDelete()
{
	if(m_bDeletePending)return;
	m_bDeletePending = true;
	QTimer::singleShot(m_pData->iMaxRunTime,this,SLOT(selfDelete()));
}

void KviKvsProcessAsyncOperation::selfDelete()
{
	delete this;
}

void KviKvsProcessAsyncOperation::maxRunTimeExpired()
{
	trigger(EventTerminated,"0");
	triggerSelfDelete();
}

bool KviKvsProcessAsyncOperation::trigger(CallbackEvent e,const QString &szData)
{
	if(m_bDeletePending)return false;
	
	if(!g_pApp->windowExists(m_pData->pWnd))
	{
		if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_KILLIFNOWINDOW)
		{
			return true;
		}
		m_pData->pWnd = g_pApp->activeConsole();
	}

	if(m_pData->pCallback)
	{
		KviKvsVariantList params;
		params.setAutoDelete(true);
		
		switch(e)
		{
			case EventStdout:
				params.append(new KviKvsVariant(QString("stdout")));
			break;
			case EventStderr:
				params.append(new KviKvsVariant(QString("stderr")));
			break;
			case EventTerminated:
				params.append(new KviKvsVariant(QString("terminated")));
			break;
			case EventStarted:
				params.append(new KviKvsVariant(QString("started")));
			break;
			case EventPing:
				params.append(new KviKvsVariant(QString("ping")));
			break;
			default:
				debug("Ops... unknown trigger() CallbackEvent parameter in KviProcessDescriptor::trigger()");
				return false;
			break;
		}

		params.append(new KviKvsVariant(szData));
		if (m_pData->pMagic) params.append(m_pData->pMagic);

		KviKvsVariant retVal;
		int iRet = m_pData->pCallback->run(m_pData->pWnd,&params,&retVal,KviKvsScript::PreserveParams,m_pExtendedRunTimeData);
		if(!iRet)
		{
			m_pData->pWnd->output(KVI_OUT_PARSERERROR,
				__tr2qs("Error triggered from process callback handler: killing process"));
			return true;
		}

		if(!retVal.isNothing())
		{
			QString sz;
			retVal.asString(sz);
			m_pProcess->writeToStdin(sz);
		}
	
		if(iRet & KviKvsScript::HaltEncountered)
		{
			// halt encountered: kill the process
			return true;
		}
	}

	return false;
}

void KviKvsProcessAsyncOperation::readStdout()
{
	if(m_bDeletePending)return;
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_OUTPUTBYBLOCKS)
	{
		QByteArray a = m_pProcess->readStdout();
		if(a.size() > 0)
			m_szStdoutBuffer += QString(a);
	} else {
		QString l = m_pProcess->readLineStdout();
		bool bBreak = false;
		while((!l.isNull()) && (!bBreak))
		{
			if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTDOUT)
			{
				if(trigger(EventStdout,l))
				{
					bBreak = true;
					triggerSelfDelete();
				}
			}

			l = m_pProcess->readLineStdout();
		}
	}
}

void KviKvsProcessAsyncOperation::readStderr()
{
	if(m_bDeletePending)return;
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_OUTPUTBYBLOCKS)
	{
		QByteArray a = m_pProcess->readStderr();
		if(a.size() > 0)
			m_szStderrBuffer += QString(a);
	} else {
		QString l = m_pProcess->readLineStderr();
		bool bBreak = false;
		while((!l.isNull()) && (!bBreak))
		{
			if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTDERR)
			{
				if(trigger(EventStderr,l))
				{
					bBreak = true;
					triggerSelfDelete();
				}
			}

			l = m_pProcess->readLineStderr();
		}
	}
}


void KviKvsProcessAsyncOperation::processExited()
{
	if(m_bDeletePending)return;

	readStdout(); // just to make sure
	readStderr(); // just to make sure
	
	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_OUTPUTBYBLOCKS)
	{
		// trigger Stdout and Stderr once
		if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTDOUT)
		{
			if(trigger(EventStdout,m_szStdoutBuffer))
			{
				triggerSelfDelete();
				return;
			}
		}
		
		if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERSTDERR)
		{
			if(trigger(EventStdout,m_szStderrBuffer))
			{
				triggerSelfDelete();
				return;
			}
		}
	}

	if(m_pData->iFlags & KVI_KVS_PROCESSDESCRIPTOR_TRIGGERTERMINATED)
	{
		QString szRetVal;
		szRetVal.setNum(m_pProcess->exitStatus());
		trigger(EventTerminated,szRetVal);
	}
	
	triggerSelfDelete();
}


/*

KviKvsProcessManager::KviKvsProcessManager()
: QObject()
{
}

KviKvsProcessManager::~KviKvsProcessManager()
{
}

void KviKvsProcessManager::init()
{
	if(m_pInstance)return;
	m_pInstance = new KviProcessManager();
}

void KviKvsProcessManager::done()
{
	if(!m_pInstance)return;
	delete m_pInstance;
	m_pInstance = 0;
}

bool KviKvsProcessManager::execute(KviKvsProcessAsyncOperationData * d)
{
	KviKvsProcessAsyncOperation * pd = new KviKvsProcessAsyncOperation(d,this);
	if(!pd->start())
	{
		//delete d; <-- delete by KviKvsProcessAsyncOperation
		delete pd;
		return false;
	}
	return true;
}
*/
