/*-*-c++-*-
 * $Id: tcpplugin.cpp,v 1.1 2002/01/28 15:38:33 holzheu Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (Michael.Geselbracht@de.ibm.com)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "generic_cpp.h"
#include "tcpplugin.h"

#include <qobject.h>
#include <qsocket.h>
#include <qtimer.h>
#include <qmessagebox.h>
#include <qdialog.h>
#include <qlineedit.h>
#include <qspinbox.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qapplication.h>

#ifdef WIN32
#include <winsock2.h>
#else
#include <netinet/in.h>
#endif
#include <stdlib.h>

RXbuffer::RXbuffer() {
	_len = 0;
	_tmp = 0;
	_buf = (char *)malloc(1024);
	_size = _buf ? 1024 : 0;
}

RXbuffer::~RXbuffer() {
	free(_buf);
}

QString
RXbuffer::left(unsigned int len) {
	if (len == 0)
		return QString::null;

	if (len > _len)
		len = _len;

	if (_tmp)
		free(_tmp);
	_tmp = (char *)malloc(len + 1);
	memcpy(_tmp, _buf, len);
	_tmp[len] = 0;
	return QString(_tmp);
}

void
RXbuffer::remove(unsigned int len) {
	if (len == 0)
		return;
	if (len >= _len)
		_len = 0;
	else {
		_len -= len;
		memmove(_buf, _buf + len, _len);
	}
}

void
RXbuffer::append(const char *buf, int len) {
	if (len <= 0)
		return;
	unsigned int nlen = _len + len;
	if (nlen > _size) {
		_size = ((nlen / 1024) + 1) * 1024;
		_buf = (char *)realloc(_buf, _size);
	}
	memcpy(_buf + _len, buf, len);
	_len = nlen;
}

CTcpChild::CTcpChild()
	: CChildImplementation()
, port(8192)
, validAnswer(-1)
, startAnswer(-1)
, runningAnswer(-1)
, killAnswer(-1)
, timeout(1000)
, established(false)
, timedout(false)
, failed(false)
, justStarted(false)
, dest(DEMUX_NONE)
{
	mh.c = '?';
	mh.l = 0;
	socket = new QSocket(this);
	timer = new QTimer(this);

	CtrlBuffer = "";
	connect(timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
	connect(socket, SIGNAL(error(int)), this, SLOT(slotError(int)));
	connect(socket, SIGNAL(connected()), this, SLOT(slotConnected()));
	connect(socket, SIGNAL(connectionClosed()), this, SLOT(slotConnectionClosed()));
	connect(socket, SIGNAL(readyRead()), this, SLOT(slotReadData()));
}

CTcpChild::~CTcpChild()
{
	timer->stop();
	socket->close();
}

void
CTcpChild::slotTimeout() {
	dest = DEMUX_NONE;
	timedout = true;
}

void
CTcpChild::slotConnected() {
	timer->stop();
	established = true;
}

void
CTcpChild::slotReadData() {
	int lf;
	char tmp[1024];

	// fill our raw input buffer
	int res;
	do {
		res = socket->readBlock(tmp, sizeof(tmp));
		if (res < 0) {
			slotError(QSocket::ErrSocketRead);
			break;
		}
		if (res > 0)
			rawBuffer.append(tmp, res);
	} while (res == sizeof(tmp));
	if (rawBuffer.isEmpty())
		return;

	// de-multiplex the raw buffer into our 3 IO buffers.
	do {
		if (mh.l == 0) {
			if (rawBuffer.length() < (int) sizeof(mh))
				break;
			mh = *(msgHead *)(const char *)rawBuffer;
			rawBuffer.remove(sizeof(mh));
			mh.l = ntohs(mh.l);
			dest = DEMUX_NONE;
			switch (mh.c) {
				case 'O':
					dest = DEMUX_STDOUT;
					break;
				case 'E':
					dest = DEMUX_STDERR;
					break;
				case 'C':
					dest = DEMUX_CTRL;
					break;
				default:
					QMessageBox::critical(0, tr("TCPplugin FATAL ERROR"),
						tr("slotReadData(): received unknown multiplexer code 0x%1\n").arg(mh.c,0,16));
					return;
			}
		}
		if (dest != DEMUX_NONE) {
			QString ts = rawBuffer.left(mh.l);
			if (!ts.isEmpty()) {
				rawBuffer.remove(ts.length());
				mh.l -= ts.length();
				switch (dest) {
					case DEMUX_CTRL:
						CtrlBuffer += ts;
						break;
					case DEMUX_STDOUT:
						if (justStarted)
							sendHeader();
						emit sigStdout(ts);
						break;
					case DEMUX_STDERR:
						if (justStarted)
							sendHeader();
						emit sigStderr(ts);
				}
			}
		}
	} while (!rawBuffer.isEmpty());

	// Check the control buffer for any messages
	bool childDied = false;
	while (!CtrlBuffer.isEmpty()) {
		int dummy;
		char c = *(const char *)CtrlBuffer;
		int *valPtr = 0;
		switch (c) {
			case 'A':
				valPtr = &dummy;
				break;
			case 'V':
				valPtr = &validAnswer;
				break;
			case 'R':
				valPtr = &runningAnswer;
				break;
			case 'S':
				valPtr = &startAnswer;
				break;
			case 'K':
				valPtr = &killAnswer;
				break;
			case 'D':
				valPtr = &dummy;
				childDied = true;
				break;
			default:
				QMessageBox::critical(0, tr("TCPplugin FATAL ERROR"),
					tr("slotReadData(): received unknown control code 0x%1\n").arg(c,0,16));
		}
		if (valPtr != 0) {
			lf = CtrlBuffer.find('\n');
			if (lf >= 0) {
				*valPtr = CtrlBuffer.mid(1, lf - 1).toInt();
				CtrlBuffer.remove(0, lf + 1);
			} else
				break;
		}
	}
	if (childDied)
		emit sigChildDied();
}

void
CTcpChild::slotError(int err) {
	established = false;
	failed = true;
	dest = 0;
	QString msg;
	switch (err) {
		case QSocket::ErrConnectionRefused:
			msg = tr("Connection refused");
			break;
		case QSocket::ErrHostNotFound:
			msg = tr("Host not found");
			break;
		case QSocket::ErrSocketRead:
			msg = tr("Read from socket failed");
			break;
		default:
			msg = tr("Unknown error");
	}
	QMessageBox::critical(0, tr("TCPplugin error"),
		tr(
		"<QT>Connection to <B>%1:%2</B> broken.<BR/>"
		"Reason: %3</QT>").arg(host).arg(port).arg(msg));
	socket->close();
}

void
CTcpChild::slotConnectionClosed() {
	established = false;
	failed = true;
	dest = 0;
	QMessageBox::critical(0, tr("TCPplugin error"),
		tr(
		"<QT>Connection to <B>%1:%2</B> broken.<BR/>"
		"Reason: Connection closed by foreign host</QT>").arg(host).arg(port));
	socket->close();
}

void
CTcpChild::sendHeader() {
	justStarted = false;
	emit sigStderr(
		tr("TCPplugin: connected to %1\n").arg(socket->peerName()));
}

void
CTcpChild::writeMsg(QString cmd, char dst) {
	msgHead h;
	h.c = dst;
	cmd = cmd.stripWhiteSpace() + "\n";
	h.l = cmd.length();
	h.l = htons(h.l);
	socket->writeBlock((const char *)&h, sizeof(h));
	socket->writeBlock((const char *)cmd, cmd.length());
}

bool
CTcpChild::valid()
{
	if (!established)
		if (!tryConnect())
			return false;
	validAnswer = -1;
	writeMsg(QString("valid? %1").arg(path));
	timer->start(timeout, true);
	while (established && !timedout && validAnswer == -1)
		qApp->processEvents(100);
	timer->stop();
	return (validAnswer == 1);
}

bool
CTcpChild::running()
{
	if (!established)
		return false;
	runningAnswer = -1;
	writeMsg("running?");
	timer->start(timeout, true);
	while (established && !timedout && runningAnswer == -1)
		qApp->processEvents(100);
	timer->stop();
	return (runningAnswer == 1);
}

bool
CTcpChild::tryConnect() {
	bool ret = false;
	timedout = false;
	failed =false;
	if (!host.isEmpty() && port != 0) {
		timer->start(timeout, true);
		socket->connectToHost(host, port);
		while (!established && !failed && !timedout)
			qApp->processEvents(100);
		ret = established;
	}
	return ret;
}

void
CTcpChild::forkChild()
{
	if (!established)
		if (!tryConnect())
			return;
	startAnswer = -1;
	QString tmp = QString("start! %1 ").arg(path.length());
	tmp += path;
	tmp += " ";
	tmp += argv;
	tmp += "\n";
	justStarted = true;
	writeMsg(tmp);
	timer->start(timeout, true);
	while (established && !timedout && startAnswer == -1)
		qApp->processEvents(100);
	timer->stop();
	if (startAnswer == 1)
		emit sigChildStarted();
}

void
CTcpChild::killChild(int sig)
{
	if (!established)
		return;
	killAnswer = -1;
	writeMsg(QString("kill! %1").arg(sig));
	timer->start(timeout, true);
	while (established && !timedout && killAnswer == -1)
		qApp->processEvents(100);
	timer->stop();
}

bool
CTcpChild::writeStdin(QString s)
{
	if (!established)
		return false;
	emit sigBusy();
	writeMsg(s, 'I');
	return true;
}

int CTcpChild::
configure(QWidget *parent) {
	QDialog d(parent, "configDialog", true);
	d.setCaption(tr("TCPplugin Configure"));
	QGridLayout *l = new QGridLayout(&d, 8, 2, 5);

	l->addWidget(new QLabel(tr("Host"), &d),      0, 0);
	l->addWidget(new QLabel(tr("Port"), &d),      1, 0);
	l->addWidget(new QLabel(tr("Timeout"), &d),   2, 0);
	l->addWidget(new QLabel(tr("Path"), &d),      3, 0);
	l->addWidget(new QLabel(tr("Arguments"), &d), 4, 0);

	QLineEdit *hostEntry = new QLineEdit(host, &d);
	l->addWidget(hostEntry, 0, 1);

	QSpinBox *portEntry = new QSpinBox(1, 65535, 1, &d);
	portEntry->setValue(8192);
	l->addWidget(portEntry, 1, 1);

	QSpinBox *timeoutEntry = new QSpinBox(0, 9999, 1, &d);
	timeoutEntry->setValue(timeout);
	timeoutEntry->setSuffix(" ms");
	l->addWidget(timeoutEntry, 2, 1);

	QLineEdit *pathEntry = new QLineEdit(path, &d);
	l->addWidget(pathEntry, 3, 1);

	QLineEdit *argvEntry = new QLineEdit(argv, &d);
	l->addWidget(argvEntry, 4, 1);

	l->setRowStretch(5, 10);

	QFrame *separator = new QFrame(&d);
	separator->setLineWidth(1);
	separator->setMidLineWidth(0);
	separator->setFrameStyle(QFrame::HLine | QFrame::Sunken);
	separator->setMinimumSize(0, 2);
	l->addMultiCellWidget(separator, 6, 6, 0, 1);

	QBoxLayout *bl = new QHBoxLayout();
	bl->addStretch(10);
	QPushButton *okButton = new QPushButton("Ok", &d);
	bl->addWidget(okButton);
	QPushButton *cancelButton = new QPushButton("Cancel", &d);
	bl->addWidget(cancelButton);

	l->addMultiCellLayout(bl, 7, 7, 0, 1);

	connect(cancelButton, SIGNAL(clicked()), &d, SLOT(reject()));
	connect(okButton, SIGNAL(clicked()), &d, SLOT(accept()));

	d.setMinimumWidth(350);
	okButton->setFocus();

	int ret = d.exec();
	if (ret == QDialog::Accepted) {
		_configured = true;
		host = hostEntry->text().stripWhiteSpace();
		port = portEntry->cleanText().toInt();
		timeout = timeoutEntry->cleanText().toInt();
		path = pathEntry->text().stripWhiteSpace();
		argv = argvEntry->text().stripWhiteSpace();
		if (established && !host.isEmpty()) {
			timer->stop();
			socket->close();
			tryConnect();
		}
	}
	return ret;
}

cfgMap
CTcpChild::getConfigValues() {
	QString tmp;
	cfgMap *map = new cfgMap();
	map->insert("host", host);
	tmp.setNum(port);
	map->insert("port", tmp);
	tmp.setNum(timeout);
	map->insert("timeout", tmp);
	map->insert("path", path);
	map->insert("argv", argv);
	return *map;
}

void
CTcpChild::setConfigValues(cfgMap v) {
	host = v["host"];
	path = v["path"];
	argv = v["argv"];
	port = (v["port"].isEmpty()) ? v["port"].toInt() : 8192;
	timeout = (v["timeout"].isEmpty()) ? v["timeout"].toInt() : 1000;
	_configured = true;
}

bool
CTcpChild::configured() {
	return _configured;
}

#ifdef __cplusplus
extern "C" {
#endif

static CTcpChild *theChild = 0;

static CChildImplementation *my_factory() {
	if (!theChild)
		theChild = new CTcpChild();
	return theChild;
}

DLLEXPORT DECLARE_PLUGIN(
	"Remote lcrash",
	"1.0",
	"This runs lcrash on a remote machine",
	"Fritz Elfert &lt;elfert@de.ibm.com&gt;",
	"Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation<BR/>This plugin is freely redistributable under LGPL.<BR/>See COPYING for more information.",
	my_factory,
	1
);

#ifdef __cplusplus
};
#endif

#ifndef WIN32
#include "tcpplugin.moc"
#endif

/*
 * Local variables:
 * c-basic-offset: 8
 * End:
 */

