//Author:    Timothy Pearson <kb9vqf@pearsoncomputing.net>, (C) 2012
//Copyright: See COPYING file that comes with this distribution

// TDE MDI interface based on a (passable) tutorial by Andrea Bergia et al.

#include "remotemdi.h"

#include <cassert>
using namespace std;

#include <pwd.h>

#include <tdeapplication.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <tdeconfig.h>
#include <tdemessagebox.h>

#include <tqlabel.h>
#include <tqtimer.h>
#include <tqlayout.h>
#include <kiconloader.h>
#include <kstdaction.h>
#include <kstatusbar.h>
#include <tdemdichildview.h>
#include <tdelistbox.h>
#include <tdeactionclasses.h>
#include <kedittoolbar.h>
#include <kkeydialog.h>
#include <libtdeldap.h>

#include "views/instrumentview.h"
#include "dialogs/selectserverdlg.h"

#define STATUSBAR_TIMEOUT_ID 5

RemoteMDI::RemoteMDI()
    : KMdiMainFrm(0, "RemoteMDI", KMdi::ChildframeMode), m_children(0), m_rsvSvrSocket(NULL), connToServerConnecting(false), connToServerState(-1), connToServerTimeoutTimer(NULL)
{
	setXMLFile("remotelabui.rc");
	setIcon(SmallIcon("remote_laboratory_client"));

	masterPollTimer = new TQTimer();
	connect(masterPollTimer, SIGNAL(timeout()), this, SLOT(masterPoll()));

	// Create some actions
	KStdAction::close(this, SLOT(closeCurrent()), actionCollection());
	KStdAction::quit(this, SLOT(close()), actionCollection());

	TDEActionCollection *const ac = actionCollection();
	setStandardToolBarMenuEnabled(true);
	KStdAction::quit(TQT_TQOBJECT(this), TQT_SLOT(close()), ac);
	KStdAction::configureToolbars(TQT_TQOBJECT(this), TQT_SLOT(configToolbars()), ac);
	KStdAction::keyBindings(TQT_TQOBJECT(this), TQT_SLOT(configKeys()), ac);
	connect_action = new TDEAction(i18n("Connect to Server"), "connect_creating", TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(connectToServer()), ac, "connect_server");
	disconnect_action = new TDEAction(i18n("Disconnect from Server"), "connect_no", TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(disconnectFromServer()), ac, "disconnect_server");

	setMenuForSDIModeSysButtons(menuBar());

	createGUI(0);

	// Add Window menu
	if ( !isFakingSDIApplication() ) {
		menuBar()->insertItem(i18n("&Window"), windowMenu(), -1, 4);
	}
	// Hide task bar as no windows are currently active
	hideViewTaskBar();

	// When we change view, change the status bar text
	connect(this, SIGNAL(viewActivated(KMdiChildView*)), this, SLOT(currentChanged(KMdiChildView*)));

	ac->setHighlightingEnabled(true);
	connect(ac, TQT_SIGNAL(actionStatusText(const TQString&)), this, TQT_SLOT(updateStatusBarMainMessage(const TQString&) ));
	connect(ac, TQT_SIGNAL(clearStatusText()), statusBar(), TQT_SLOT(clear()));

	// Create the status bar
	updateStatusBarMainMessage(i18n("No active instruments"));
	KStatusBar* sb = statusBar();
	if (sb) {
		sb->insertItem(i18n("Unknown Time Remaining"), STATUSBAR_TIMEOUT_ID, 0, true);
	}

	processActions();

	processLockouts();

	showMaximized();
}

RemoteMDI::~RemoteMDI()
{
	if (masterPollTimer) {
		masterPollTimer->stop();
		delete masterPollTimer;
	}

	while (m_pCurrentWindow) {
		closeCurrent();
	}

	if (m_rsvSvrSocket) {
		m_rsvSvrSocket->clearPendingData();
		m_rsvSvrSocket->close();
		delete m_rsvSvrSocket;
		m_rsvSvrSocket = NULL;
	}
}

void RemoteMDI::updateStatusBarMessage() {
	TQString windowStatusBarMessage;
	if (m_pCurrentWindow) {
		windowStatusBarMessage = m_windowStatusBarMapping[TQT_TQOBJECT(m_pCurrentWindow)];
	}

	KStatusBar* sb = statusBar();
	if (sb) {
		sb->message(m_mainStatusBarMessage + ((windowStatusBarMessage != "")?" [" + i18n("Instrument") + ": " + windowStatusBarMessage + "]":""));
	}
}

void RemoteMDI::updateStatusBarMainMessage(const TQString& message) {
	m_mainStatusBarMessage = message;
	updateStatusBarMessage();
}

void RemoteMDI::updateStatusBarWindowMessage(const TQString& message, const TQObject* window) {
	const TQObject* windowObject = window;
	if (!windowObject) {
		windowObject = sender();
	}

	if (windowObject) {
		m_windowStatusBarMapping[windowObject] = message;
	}

	updateStatusBarMessage();
}

void RemoteMDI::resizeEvent(TQResizeEvent *e) {
	KMdiMainFrm::resizeEvent(e);
	setSysButtonsAtMenuPosition();
}

void RemoteMDI::processActions() {
	// Add dynamic actions
	TDEActionCollection *const ac = actionCollection();

	TDEAction* action;
	ServiceType st;
	for (ServiceList::Iterator it(m_activeStation.services.begin()); it != m_activeStation.services.end(); ++it) {
		st = *it;
		action = new TDEAction(i18n("Launch")+" "+st.name, st.clientLibrary, TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(startModule()), ac, st.clientLibrary.ascii());
		m_instrumentActionList.append(action);
	}
	plugActionList("instrumentMenu_actionlist", m_instrumentActionList);
	plugActionList("instrumentToolBar_actionlist", m_instrumentActionList);
}

void RemoteMDI::startModule() {
	const TDEAction* sendingAction = dynamic_cast<const TDEAction*>(sender());
	if (sendingAction) {
		bool serviceFound = false;
		ServiceType st;
		for (ServiceList::Iterator it(m_activeStation.services.begin()); it != m_activeStation.services.end(); ++it) {
			st = *it;
			if (st.clientLibrary == sendingAction->name()) {
				serviceFound = true;
				break;
			}
		}

		if (!serviceFound) {
			KMessageBox::error(this, i18n("<qt>The active laboratory workspace does not support the requested service</qt>"), i18n("Service Unavailable"));
			return;
		}

		RemoteLab::InstrumentView* view = new RemoteLab::InstrumentView(st.clientLibrary, st.name, (mdiMode() == KMdi::ToplevelMode) ? 0 : this);
		view->setName(st.clientLibrary.ascii());
		connect(view, SIGNAL(statusMessageSet(const TQString&)), this, SLOT(updateStatusBarWindowMessage(const TQString&)));
		if (st.singleInstance) {
			const_cast<TDEAction*>(sendingAction)->setEnabled(false);
		}
		openNewWindow(view);
		showViewTaskBar();
		if (m_serverHost != "") {
			view->connectServer(m_serverHost);
		}
	}
}

int RemoteMDI::getNewTicket() {
	int ret = -1;

	LDAPCredentials credentials;
	KerberosTicketInfoList ticketList = LDAPManager::getKerberosTicketList();
	if (ticketList.count() > 0) {
		TQStringList princParts = TQStringList::split("@", ticketList[0].cachePrincipal);
		credentials.username = princParts[0];
		credentials.realm = princParts[1];
	}
	else {
		struct passwd* pwd = getpwuid(geteuid());
		if (pwd) {
			credentials.username = TQString(pwd->pw_name);
		}
	}
	int result = LDAPManager::getKerberosPassword(credentials, i18n("Please provide Kerberos credentials"), false, this);
	if (result == KDialog::Accepted) {
		TQString errorstring;
		TQString service;
		if (LDAPManager::obtainKerberosTicket(credentials, service, &errorstring) != 0) {
			KMessageBox::error(this, i18n("<qt>Failed to obtain ticket<p>%1</qt>").arg(errorstring), i18n("Failed to obtain Kerberos ticket"));
		}
		else {
			ret = 0;
		}
	}

	return ret;
}

void RemoteMDI::finishConnectingToServer() {
	if (!m_rsvSvrSocket) {
		connToServerState = -1;
		connToServerConnecting = false;
		processLockouts();
		return;
	}

	if (connToServerConnecting) {
		switch(connToServerState) {
			case 0:
				if (!connToServerTimeoutTimer) {
					connToServerTimeoutTimer = new TQTimer;
					connToServerTimeoutTimer->start(5000, TRUE);
				}
				if ((m_rsvSvrSocket->state() == TQSocket::Connecting) || (m_rsvSvrSocket->state() == TQSocket::HostLookup)) {
					if (!connToServerTimeoutTimer->isActive()) {
						connToServerState = -3;
						connToServerConnecting = false;
						disconnectFromServer();
						KMessageBox::error(this, i18n("<qt>Unable to establish connection to remote server</qt>"), i18n("Connection Failed"));
						return;
					}
				}
				else {
					if (m_rsvSvrSocket->state() == TQSocket::Connected) {
						printf("[DEBUG] Initial connection established...\n\r"); fflush(stdout);
						m_rsvSvrSocket->setDataTimeout(5000);
						m_rsvSvrSocket->setUsingKerberos(true);
						delete connToServerTimeoutTimer;
						connToServerTimeoutTimer= NULL;
						connToServerState = 1;
					}
					else {
						connToServerState = -1;
						connToServerConnecting = false;
						disconnectFromServer();
						KMessageBox::error(this, i18n("<qt>Unable to establish connection to remote server</qt>"), i18n("Connection Failed"));
						return;
					}
				}
				break;
			case 1:
				if (m_rsvSvrSocket->kerberosStatus() == TDEKerberosClientSocket::KerberosInitializing) {
					// Do nothing
				}
				else {
					if (m_rsvSvrSocket->kerberosStatus() != TDEKerberosClientSocket::KerberosInUse) {
						connToServerState = -1;
						connToServerConnecting = false;
						disconnectFromServer();

						// Try to get a valid ticket
						if (getNewTicket() == 0) {
							// Retry connection if no obvious errors were detected
							TQTimer::singleShot(0, this, SLOT(connectToServer()));
							return;
						}
						else {
							KMessageBox::error(this, i18n("<qt>Unable to establish Kerberos protocol with remote server<p>Please verify that you currently hold a valid Kerberos ticket</qt>"), i18n("Connection Failed"));
							return;
						}
					}
					else {
						connect(m_rsvSvrSocket, SIGNAL(readyRead()), m_rsvSvrSocket, SLOT(processPendingData()));
						m_rsvSvrSocket->processPendingData();
						connToServerState = 2;
					}
				}
				break;
			case 2:
				// Connection established!
				// Read magic number and proto version from server
				TQDataStream* ds = new TQDataStream(m_rsvSvrSocket);
				ds->setPrintableData(true);
				while (!m_rsvSvrSocket->canReadFrame()) {
					tqApp->processEvents();
					if (!m_rsvSvrSocket) {
						return;
					}
				}
				TQ_UINT32 magicnum;
				TQ_UINT32 protover;
				*ds >> magicnum;
				*ds >> protover;
				m_rsvSvrSocket->clearFrameTail();
				printf("[DEBUG] Got magic number %d and protocol version %d\n\r", magicnum, protover); fflush(stdout);
				if ((magicnum == MAGIC_NUMBER) && (protover == PROTOCOL_VERSION)) {
					// Request server name
					TQString serverName;
					*ds << TQString("NAME");
					m_rsvSvrSocket->writeEndOfFrame();
					while (!m_rsvSvrSocket->canReadFrame()) {
						tqApp->processEvents();
						if (!m_rsvSvrSocket) {
							return;
						}
					}
					*ds >> serverName;
					m_rsvSvrSocket->clearFrameTail();

					// Set caption if a valid server name was received
					if ((serverName != "") && (serverName != "ERRINVCMD")) {
						setCaption(TQString("%1 - %2").arg(serverName).arg(kapp->caption()));
					}

					delete ds;
					disconnect_action->setEnabled(true);
					promptForStationType();
				}
				else {
					delete ds;
					disconnectFromServer();
					KMessageBox::error(this, i18n("<qt>The remote server is not compatible with this client</qt>"), i18n("Connection Failed"));
				}
				connToServerState = 3;
				connToServerConnecting = false;
				masterPollTimer->start(0, TRUE);
				processLockouts();
				break;
		}

		TQTimer::singleShot(0, this, SLOT(finishConnectingToServer()));
	}
}

void RemoteMDI::masterPoll() {
	// Query current termination timestamp
	if (m_rsvSvrSocket) {
		if ((m_rsvSvrSocket->state() == TQSocket::Connected) && (!connToServerConnecting)) {
			TQDataStream ds(m_rsvSvrSocket);
			ds.setPrintableData(true);
			TQ_ULLONG terminationStamp;
			long long currentStamp;
			ds << TQString("TSTP");
			m_rsvSvrSocket->writeEndOfFrame();
			while (!m_rsvSvrSocket->canReadFrame()) {
				tqApp->processEvents();
				if (!m_rsvSvrSocket) {
					masterPollTimer->start(1000, TRUE);
					return;
				}
			}
			ds >> terminationStamp;
			m_rsvSvrSocket->clearFrameTail();
			currentStamp = TQDateTime::currentDateTime().toTime_t();

			KStatusBar* sb = statusBar();
			if (sb) {
				if (terminationStamp == 0) {
					sb->changeItem(i18n("Unlimited Time Remaining"), STATUSBAR_TIMEOUT_ID);
				}
				else {
					long long difference = terminationStamp - currentStamp;
					int seconds = 0;
					int minutes = 0;
					int hours = 0;
					int days = 0;
					if (difference >= 0) {
						days = (difference / 86400);
						difference = difference - (days * 86400);
						hours = (difference / 3600);
						difference = difference - (hours * 3600);
						minutes = (difference / 60);
						difference = difference - (minutes * 60);
						seconds = difference;
					}
					TQString differenceString;
					if (days > 0) {
						differenceString.append(i18n("%1 day(s), ").arg(days));
					}
					if ((days > 0) || (hours > 0)) {
						differenceString.append(i18n("%1 hours(s), ").arg(hours));
					}
					if ((days > 0) || (hours > 0) || (minutes > 0)) {
						differenceString.append(i18n("%1 minutes(s), ").arg(minutes));
					}
					differenceString.append(i18n("%1 seconds(s)").arg(seconds));
					sb->changeItem(i18n("%1 Remaining").arg(differenceString), STATUSBAR_TIMEOUT_ID);
				}
			}
		}
	}

	masterPollTimer->start(1000, TRUE);
}

void RemoteMDI::connectToServer() {
	if (m_rsvSvrSocket) {
		if (m_rsvSvrSocket->state() != TQSocket::Idle) {
			printf("[DEBUG] Not connecting because the socket is still in state %d\n\r", m_rsvSvrSocket->state()); fflush(stdout);
			return;
		}
	}

	connect_action->setEnabled(false);
	disconnect_action->setEnabled(true);

	// Connect to the central reservation/control server
	if (!m_rsvSvrSocket) {
		m_rsvSvrSocket = new TDEKerberosClientSocket(this);
		connect(m_rsvSvrSocket, SIGNAL(connectionClosed()), this, SLOT(connectionClosedHandler()));
		connect(m_rsvSvrSocket, TQT_SIGNAL(statusMessageUpdated(const TQString&)), this, TQT_SLOT(updateStatusBarMainMessage(const TQString&) ));
	}
	m_rsvSvrSocket->setServiceName("ulab");
	TQStringList disallowedMechList;
	disallowedMechList.append("GSS-SPNEGO");
	m_rsvSvrSocket->setDisallowedMechanisms(disallowedMechList);
	if (m_serverHost != "") {
		m_rsvSvrSocket->setServerFQDN(m_serverHost);
		m_rsvSvrSocket->connectToHost(m_serverHost, 4004);

		// Finish connecting when appropriate
		connToServerState = 0;
		connToServerConnecting = true;
		TQTimer::singleShot(0, this, SLOT(finishConnectingToServer()));
	}
}

void RemoteMDI::promptForStationType() {
	if (!m_rsvSvrSocket) {
		return;
	}
	if (m_rsvSvrSocket->state() != TQSocket::Connected) {
		return;
	}

	TQDataStream ds(m_rsvSvrSocket);
	ds.setPrintableData(true);

	// Request list of laboratory stations
	StationList slist;
	ds << TQString("LIST");
	m_rsvSvrSocket->writeEndOfFrame();
	while (!m_rsvSvrSocket->canReadFrame()) {
		tqApp->processEvents();
		if (!m_rsvSvrSocket) {
			return;
		}
	}
	ds >> slist;
	m_rsvSvrSocket->clearFrameTail();

	SelectServerDialog select(this, 0, slist);
	const int ret = select.exec();
	if (ret == KDialog::Accepted) {
		TQString result;
		ds << TQString("BIND");
		m_rsvSvrSocket->writeEndOfFrame();
		ds << select.m_selectedStation;
		m_rsvSvrSocket->writeEndOfFrame();
		while (!m_rsvSvrSocket->canReadFrame()) {
			tqApp->processEvents();
			if (!m_rsvSvrSocket) {
				return;
			}
		}
		ds >> result;
		m_rsvSvrSocket->clearFrameTail();
		if (result == "OK") {
			// Success!
			m_activeStation = select.m_selectedStation;
			processActions();
		}
		else if (result == "ERRUNAVAL") {
			KMessageBox::error(this, i18n("<qt>No stations of the specified type are currently available<p>Please try again later</qt>"), i18n("Insufficient Laboratory Resources"));
			disconnectFromServer();
		}
		else if (result == "ERRPREVCN") {
			KMessageBox::error(this, i18n("<qt>You are already connected to a laboratory station<p>Please disconnect and try again</qt>"), i18n("Multiple Connections Detected"));
			disconnectFromServer();
		}
		else {
			KMessageBox::error(this, i18n("<qt>Unknown server error<p>Please reconnect and try again</qt>"), i18n("Internal Error"));
			disconnectFromServer();
		}
	}
	else {
		disconnectFromServer();
	}
}

void RemoteMDI::disconnectFromServer() {
	connect_action->setEnabled(false);
	disconnect_action->setEnabled(false);

	m_instrumentActionList.clear();
	unplugActionList("instrumentMenu_actionlist");
	unplugActionList("instrumentToolBar_actionlist");

	// Close all windows
	closeAllViews();

	if (m_rsvSvrSocket) {
		m_rsvSvrSocket->clearPendingData();
		m_rsvSvrSocket->close();
		delete m_rsvSvrSocket;
		m_rsvSvrSocket = NULL;
	}

	connect_action->setEnabled(true);
	processLockouts();
}

void RemoteMDI::connectionClosedHandler() {
	disconnectFromServer();
	KMessageBox::error(this, i18n("<qt>The remote server has closed the connection</qt>"), i18n("Connection Terminated"));
}

void RemoteMDI::processLockouts() {
	bool connected = false;
	if (m_rsvSvrSocket) {
		connected = ((m_rsvSvrSocket->state() == TQSocket::Connected) && (connToServerConnecting == false) && (connToServerState > 0));
	}

	connect_action->setEnabled(!connected);
	disconnect_action->setEnabled(connected);

	for (TQPtrList<TDEAction>::Iterator it(m_instrumentActionList.begin()); it != m_instrumentActionList.end(); ++it) {
		(*it)->setEnabled(connected);
	}

	if (!connected) {
		KStatusBar* sb = statusBar();
		if (sb) {
			sb->changeItem(i18n("Unknown Time Remaining"), STATUSBAR_TIMEOUT_ID);
		}
	}
}

void RemoteMDI::configToolbars() {
	KEditToolbar dialog(factory(), this);
	dialog.showButtonApply(false);

	if (dialog.exec()) {
		applyMainWindowSettings(kapp->config(), "window");
	}
}

void RemoteMDI::configKeys() {
	KKeyDialog::configure(actionCollection(), this);
}

void RemoteMDI::setServerHost(TQString server) {
	m_serverHost = server;
	connectToServer();
}

void RemoteMDI::openNewWindow(KMdiChildView *view) {
	// Add a child view
	m_children++;

	// The child view will be our child only if we aren't in Toplevel mode
	if (!view) {
		view = new KMdiChildView(i18n("View %1").arg(m_children), (mdiMode() == KMdi::ToplevelMode) ? 0 : this);
	}
	(new TQHBoxLayout(view))->setAutoAdd( true );

	// Add to the MDI and set as current
	if (mdiMode() == KMdi::ToplevelMode) {
		addWindow(view, KMdi::Detach);
	}
	else {
		addWindow(view);
	}
	currentChanged(view);

	// Handle termination
	connect(view, SIGNAL(childWindowCloseRequest(KMdiChildView*)), this, SLOT(childClosed(KMdiChildView*)));
}

void RemoteMDI::childWindowCloseRequest(KMdiChildView *pWnd) {
	RemoteLab::InstrumentView* iview = dynamic_cast<RemoteLab::InstrumentView*>(pWnd);
	if (iview) {
		// Give the child a chance to finish what it was doing and exit cleanly (i.e. without crashing!)
		iview->closeConnections();
		iview->hide();

		KMdiMainFrm::childWindowCloseRequest(pWnd);
	}
}

void RemoteMDI::currentChanged(KMdiChildView *current) {
	RemoteLab::InstrumentView* view = dynamic_cast<RemoteLab::InstrumentView*>(current);

	// Plug/unplug menus
	if (view) {
		unplugActionList("selectedInstrument_actionlist");
		plugActionList("selectedInstrument_actionlist", view->menuActionList());
	}

	// Update status bar and list box
	updateStatusBarMainMessage(i18n("Instrument %1 activated").arg(current->tabCaption()));
}

void RemoteMDI::closeCurrent() {
	// If there's a current view, close it
	if (m_pCurrentWindow) {
		closeSpecifiedWindow(m_pCurrentWindow);
	}
}

void RemoteMDI::closeSpecifiedWindow(KMdiChildView *window) {
	if (window) {
		// Notify the status bar of the removal of the window
		updateStatusBarWindowMessage(TQString::null, TQT_TQOBJECT(window));
		updateStatusBarMainMessage(i18n("Instrument %1 removed").arg(window->tabCaption()));

		// Unplug menus
		unplugActionList("selectedInstrument_actionlist");

		// We could also call removeWindowFromMdi, but it doesn't delete the
		// pointer. This way, we're sure that the view will get deleted.
		closeWindow(window);

		// Synchronize combo box
		if (m_pCurrentWindow) {
			currentChanged(m_pCurrentWindow);
		}
	}
}

void RemoteMDI::childClosed(KMdiChildView * w) {
	assert(w);

	// Set as active
	w->activate();
	assert(w == m_pCurrentWindow);

	// Unplug menus
	unplugActionList("selectedInstrument_actionlist");

	// Notify the status bar of the removal of the window
	updateStatusBarWindowMessage(TQString::null, TQT_TQOBJECT(w));
	updateStatusBarMainMessage(i18n("Instrument %1 removed").arg(w->tabCaption()));

	// Re-enable associated action
	RemoteLab::InstrumentView* view = dynamic_cast<RemoteLab::InstrumentView*>(w);
	if (view) {
		TQString libraryName = view->name();
		for (TQPtrList<TDEAction>::Iterator it(m_instrumentActionList.begin()); it != m_instrumentActionList.end(); ++it) {
			if ((*it)->name() == libraryName) {
				(*it)->setEnabled(true);
			}
		}
	}

	// Remove status bar text
	m_windowStatusBarMapping.remove(TQT_TQOBJECT(w));

	// Remove the view from MDI, BUT DO NOT DELETE IT! It is automatically deleted by TQt since it was closed.
	removeWindowFromMdi(w);
}

bool RemoteMDI::queryClose() {
	// Close all open connections
	KMdiIterator<KMdiChildView*> *it = createIterator();
	while (it->currentItem()) {
		KMdiChildView *c = dynamic_cast<KMdiChildView*>(it->currentItem());
		if (c) {
			RemoteLab::InstrumentView* iview = dynamic_cast<RemoteLab::InstrumentView*>(c);
			if (iview) {
				iview->closeConnections();
			}
		}
		it->next();
	}
	deleteIterator(it);

	// Save current MDI settings (window positions, etc.)
	// FIXME
	TDEConfig *c = kapp->config();

	c->sync();

	// Allow this window to close
	return true;
}

#include "remotemdi.moc"

