/*
  TODO:
    maybe have a queue for aux/command ??
*/

#include"gpgproc.h"

#include<qsocketnotifier.h>
#include<qtimer.h>
#include"safedelete.h"
#include"qpipe.h"

#ifdef Q_WS_WIN
#include<qprocess.h>
#include<windows.h>
#else
#include<unistd.h>
#include"sprocess.h"
#endif

//----------------------------------------------------------------------------
// GPGProc
//----------------------------------------------------------------------------
class GPGProc::Private
{
public:
	Private() {}

	QByteArray statusbuf;
	SafeDelete sd;

#ifdef Q_WS_WIN
	QProcess *proc;
#else
	SProcess *proc;
#endif
	QPipe pipeAux, pipeCommand, pipeStatus;

	QTimer *t;
	QSocketNotifier *snStatus;
};

GPGProc::GPGProc()
:QObject(0)
{
	d = new Private;
	d->proc = 0;

	d->snStatus = 0;
	d->t = 0;
}

GPGProc::~GPGProc()
{
	closePipes();

	if(d->proc) {
		d->proc->disconnect(this);
		if(d->proc->isRunning())
			d->proc->kill();
		d->sd.deleteLater(d->proc);
	}

	delete d;
}

void GPGProc::closePipes()
{
	d->pipeAux.close();
	d->pipeCommand.close();
	d->pipeStatus.close();

	delete d->snStatus;
	d->snStatus = 0;
	delete d->t;
	d->t = 0;
}

bool GPGProc::isRunning() const
{
	return d->proc->isRunning();
}

bool GPGProc::normalExit() const
{
	return d->proc->normalExit();
}

int GPGProc::exitStatus() const
{
	return d->proc->exitStatus();
}

bool GPGProc::start(const QString &bin, const QStringList &args, bool useExtra)
{
	if(d->proc) {
		closePipes();
		d->proc->disconnect(this);
		d->sd.deleteLater(d->proc);
		d->proc = 0;
	}
	d->statusbuf.resize(0);

	if(!d->pipeAux.open()) {
#ifdef GPG_DEBUG
		printf("Error creating pipeAux\n");
#endif
		closePipes();
		return false;
	}
	if(!d->pipeCommand.open()) {
#ifdef GPG_DEBUG
		printf("Error creating pipeCommand\n");
#endif
		closePipes();
		return false;
	}
	if(!d->pipeStatus.open()) {
#ifdef GPG_DEBUG
		printf("Error creating pipeStatus\n");
#endif
		closePipes();
		return false;
	}


#ifdef Q_WS_WIN
	if(!d->pipeAux.writeEnd().winDupHandle()) {
#ifdef GPG_DEBUG
		printf("Error dup'ing pipeAux\n");
#endif
		closePipes();
		return false;
	}
	if(!d->pipeCommand.writeEnd().winDupHandle()) {
#ifdef GPG_DEBUG
		printf("Error dup'ing pipeCommand\n");
#endif
		closePipes();
		return false;
	}
	if(!d->pipeStatus.readEnd().winDupHandle()) {
#ifdef GPG_DEBUG
		printf("Error dup'ing pipeStatus\n");
#endif
		closePipes();
		return false;
	}

	// timer
	d->t = new QTimer;
	connect(d->t, SIGNAL(timeout()), SLOT(timeout()));
	d->t->start(100);
#else
	// status socket notifier
	d->pipeStatus.readEnd().setBlock(false);
	d->snStatus = new QSocketNotifier(d->pipeStatus.readEnd().id(), QSocketNotifier::Read);
	connect(d->snStatus, SIGNAL(activated(int)), SLOT(sn_activated(int)));
#endif

	/*{
		// test: write some data into the pipe
		QCString cs = "Foobar";
		d->pipeStatus.write(cs);
		QByteArray buf = d->pipeStatus.readAll();
		QCString cs2 = buf.data();
		if(cs != cs2)
			printf("Pipe test failed.\n");
		else
			printf("Pipe test succeeded.\n");
	}*/

#ifdef Q_WS_WIN
	d->proc = new QProcess;
#else
	d->proc = new SProcess;
#endif

#ifdef GPG_DEBUG
	printf("Pipe setup complete.\n");
#endif

	QStringList fullargs;
	fullargs += bin;
	fullargs += "--no-tty";

	if(useExtra) {
		fullargs += "--enable-special-filenames";

		fullargs += "--status-fd";
		fullargs += d->pipeStatus.writeEnd().toString();

		fullargs += "--command-fd";
		fullargs += d->pipeCommand.readEnd().toString();
	}

	for(QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) {
		const QString &a = (*it);
		if(a == "-&?")
			fullargs += (QString("-&") + d->pipeAux.readEnd().toString());
		else
			fullargs += a;
	}

	// show full command
#ifdef GPG_DEBUG
	printf("GPGProc: ");
	for(QStringList::ConstIterator it2 = fullargs.begin(); it2 != fullargs.end(); ++it2) {
		printf("%s ", (*it2).latin1());
	}
	printf("\n");
#endif

	d->proc->setArguments(fullargs);

#ifndef Q_WS_WIN
	QValueList<int> plist;
	plist += d->pipeAux.writeEnd().id();
	plist += d->pipeCommand.writeEnd().id();
	plist += d->pipeStatus.readEnd().id();
	d->proc->setClosePipeList(plist);
#endif

	connect(d->proc, SIGNAL(readyReadStdout()), SLOT(proc_readyReadStdout()));
	connect(d->proc, SIGNAL(readyReadStderr()), SLOT(proc_readyReadStderr()));
	connect(d->proc, SIGNAL(wroteToStdin()), SLOT(proc_wroteToStdin()));
	connect(d->proc, SIGNAL(processExited()), SLOT(proc_processExited()));

	bool ok = d->proc->start();

	// we don't care about these
	d->pipeAux.closeReadEnd();
	d->pipeCommand.closeReadEnd();
	d->pipeStatus.closeWriteEnd();

	if(!ok) {
		closePipes();
		d->proc->disconnect(this);
		d->sd.deleteLater(d->proc);
		d->proc = 0;
		return false;
	}

	return true;
}

void GPGProc::writeToStdin(const QByteArray &buf)
{
	d->proc->writeToStdin(buf);
}

void GPGProc::writeToAux(const QByteArray &buf)
{
	d->pipeAux.write(buf);
	wroteToAux();
}

void GPGProc::writeToCommand(const QByteArray &buf)
{
	d->pipeCommand.write(buf);
	wroteToCommand();
}

void GPGProc::closeStdin()
{
	d->proc->closeStdin();
}

void GPGProc::closeAux()
{
	d->pipeAux.close();
}

void GPGProc::closeCommand()
{
	d->pipeCommand.close();
}

QByteArray GPGProc::readStdout()
{
	return d->proc->readStdout();
}

QByteArray GPGProc::readStderr()
{
	return d->proc->readStderr();
}

void GPGProc::proc_readyReadStdout()
{
	SafeDeleteLock s(&d->sd);
	readyReadStdout();
}

void GPGProc::proc_readyReadStderr()
{
	SafeDeleteLock s(&d->sd);
	readyReadStderr();
}

void GPGProc::proc_wroteToStdin()
{
	SafeDeleteLock s(&d->sd);
	wroteToStdin();
}

void GPGProc::proc_processExited()
{
	SafeDeleteLock s(&d->sd);

#ifdef Q_WS_WIN
	timeout();
	delete d->t;
	d->t = 0;
#else
	// maybe there is some status left??
	QByteArray buf = d->pipeStatus.readAll();
	if(!buf.isEmpty())
		processStatusData(buf);
#endif
	processExited();
}

#ifdef Q_WS_WIN
void GPGProc::timeout()
{
	QByteArray buf = d->pipeStatus.readAll();
	if(!buf.isEmpty())
		processStatusData(buf);
}

void GPGProc::sn_activated(int)
{
}
#else
void GPGProc::timeout()
{
}

void GPGProc::sn_activated(int)
{
	bool done;
	QByteArray buf = d->pipeStatus.readAll(&done);
	if(done) {
		d->pipeStatus.close();
		delete d->snStatus;
		d->snStatus = 0;
	}
	if(!buf.isEmpty())
		processStatusData(buf);
}
#endif

void GPGProc::processStatusData(const QByteArray &buf)
{
	// append to the statusbuf
	int oldsize = d->statusbuf.size();
	d->statusbuf.resize(oldsize + buf.size());
	memcpy(d->statusbuf.data() + oldsize, buf.data(), buf.size());

	// extract all lines
	QStringList list;
	while(1) {
		int n = 0;
		int len = d->statusbuf.size();
		char *p = (char *)d->statusbuf.data();
		for(; n < len; ++n) {
			if(p[n] == '\n')
				break;
		}
		// no full line
		if(n >= len)
			break;
		// extract the string from statusbuf
		++n;
		QCString cs;
		cs.resize(n+1);
		memcpy(cs.data(), p, n);
		int newsize = len - n;
		memmove(p, p + n, newsize);
		d->statusbuf.resize(newsize);

		// convert to string without newline
		QString str = QString::fromUtf8(cs);
		str.truncate(str.length()-1);
		// ensure it has a proper header
		if(str.left(9) != "[GNUPG:] ")
			continue;
		// take it off
		str = str.mid(9);
		// add to the list
		list += str;
	}

	for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it)
		statusLine(*it);
}
