/*
 * Remote Laboratory Sensor Monitor Part
 *
 * 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 3 of the License, or
 * (at your option) 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * (c) 2012-2019 Timothy Pearson
 * Raptor Engineering
 * http://www.raptorengineeringinc.com
 */

#include "define.h"
#include "part.h"

#include <tdeaboutdata.h>   //::createAboutData()
#include <tdeaction.h>
#include <tdelocale.h>
#include <ktextedit.h>
#include <tdefiledialog.h>
#include <tdemessagebox.h>  //::start()
#include <tdeparts/genericfactory.h>
#include <kstatusbar.h>
#include <kstdaction.h>
#include <knuminput.h>
#include <kmdcodec.h>
#include <kurlrequester.h>
#include <tqfile.h>        //encodeName()
#include <tqtimer.h>       //postInit() hack
#include <tqvbox.h>
#include <tqsocket.h>
#include <tqmutex.h>
#include <tqlayout.h>
#include <tqeventloop.h>
#include <tqapplication.h>
#include <tqgroupbox.h>
#include <tqcheckbox.h>
#include <tqpushbutton.h>
#include <tqprogressbar.h>
#include <unistd.h>       //access()
#include <stdint.h>

#include <tqpainter.h>

#include "tracewidget.h"
#include "floatspinbox.h"
#include "layout.h"

#define NETWORK_COMM_TIMEOUT_MS 2500

enum connectionModes {
	ModeIdle					= 0,
	ModeInterruptRequested				= 1,
	ModeGetSample					= 2
};

enum connectionStates {
	ModeIdle_StateSensorListRequest			= 0,
	ModeIdle_StateProcessSensorList			= 1,
	ModeIdle_StateStatusRequest			= 2,
	ModeIdle_StateProcessStatus			= 3,
	ModeIdle_StateResetRequest			= 4,
	ModeIdle_StateProcessReset			= 5,
	ModeIdle_StateDelay				= 6,
	ModeIdle_StatePaused				= 7,
	ModeIdle_StateExternalRequest			= 8,
	ModeGetSample_StateSampleRequest		= 9,
	ModeGetSample_StateProcessSample		= 10
};

namespace RemoteLab {

typedef KParts::GenericFactory<RemoteLab::SensorMonitorPart> Factory;
#define CLIENT_LIBRARY "libremotelab_sensormonitor"
K_EXPORT_COMPONENT_FACTORY(libremotelab_sensormonitor, RemoteLab::Factory)

TQValueTimer::TQValueTimer(TQObject *parent, const char *name)
	: TQTimer(parent, name)
{
	connect(this, SIGNAL(timeout()), this, SLOT(timeoutHandler()));
}

TQValueTimer::~TQValueTimer() {
	//
}

void TQValueTimer::timeoutHandler() {
	emit(valueTimeout(m_value));
}

int TQValueTimer::value() {
	return m_value;
}

void TQValueTimer::setValue(int value) {
	m_value = value;
}

TraceControlWidget::TraceControlWidget(TQWidget *parent, const char *name)
	: TQWidget(parent, name), m_minimumTimeStep(0.0), m_nominalTimeStep(1.0)
{
	TQGridLayout *topGrid = new TQGridLayout(this);
	m_groupBox = new TQGroupBox(this);
	m_groupBox->setColumnLayout(0, TQt::Vertical);
	topGrid->addMultiCellWidget(m_groupBox, 0, 0, 0, 0);
	m_groupBox->setTitle(i18n("Unknown Channel"));
	m_primaryLayout = new TQGridLayout(m_groupBox->layout(), 1, 1, KDialog::spacingHint());

	m_channelEnabledCheckBox = new TQCheckBox(m_groupBox);
	connect(m_channelEnabledCheckBox, SIGNAL(clicked()), this, SLOT(enableClicked()));
	m_channelEnabledCheckBox->setText(i18n("Enable"));
	m_primaryLayout->addMultiCellWidget(m_channelEnabledCheckBox, 0, 0, 0, 2);

	m_valueLabel = new TQLabel(m_groupBox);
	m_valueLabel->setText(i18n("<qt><nobr>Value: <b>%1</b></qt>").arg(i18n("<unknown>")));
	m_primaryLayout->addMultiCellWidget(m_valueLabel, 1, 1, 0, 2);
	m_valueLabel->hide();

	m_timestampLabel = new TQLabel(m_groupBox);
	m_timestampLabel->setText(i18n("<qt><nobr>Sampled: <b>%1</b></qt>").arg(i18n("<unknown>")));
	m_primaryLayout->addMultiCellWidget(m_timestampLabel, 2, 2, 0, 2);
	m_timestampLabel->hide();

	m_timestepLabel1 = new TQLabel(m_groupBox);
	m_timestepLabel1->setText(i18n("<qt><nobr>Sample every:</qt>"));
	m_primaryLayout->addMultiCellWidget(m_timestepLabel1, 3, 3, 0, 0);
	m_timestepLabel1->hide();

	m_timestepSpinBox = new FloatSpinBox(m_groupBox);
	m_timestepSpinBox->setFloatMax(60*60*24);	// 1 day
	connect(m_timestepSpinBox, SIGNAL(floatValueChanged(double)), this, SLOT(timestepChanged(double)));
	m_primaryLayout->addMultiCellWidget(m_timestepSpinBox, 3, 3, 1, 1);
	m_timestepSpinBox->hide();

	m_timestepLabel2 = new TQLabel(m_groupBox);
	m_timestepLabel2->setText(i18n("<qt><nobr>seconds<qt>"));
	m_primaryLayout->addMultiCellWidget(m_timestepLabel2, 3, 3, 2, 2);
	m_timestepLabel2->hide();

	m_clearPushButton = new TQPushButton(m_groupBox);
	m_clearPushButton->setText(i18n("Erase Existing Data"));
	connect(m_clearPushButton, SIGNAL(clicked()), this, SLOT(clearTraceDataClicked()));
	m_primaryLayout->addMultiCellWidget(m_clearPushButton, 5, 5, 0, 2);
	m_clearPushButton->setSizePolicy(TQSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::Minimum));
	m_clearPushButton->hide();

	m_sampleTimer = new TQTimer();
	connect(m_sampleTimer, SIGNAL(timeout()), this, SIGNAL(newSampleDesired()));
}

TraceControlWidget::~TraceControlWidget() {
	m_sampleTimer->stop();
	delete m_sampleTimer;
}

void TraceControlWidget::clearTraceDataClicked() {
	emit(clearTraceRequested());
}

void TraceControlWidget::startSampleTimer(int msecs) {
	if (m_channelEnabledCheckBox->isOn()) {
		m_nominalTimeStep = msecs/1.0e3;
		m_sampleTimer->start(msecs, FALSE);
	}
	else {
		m_sampleTimer->stop();
	}
}

void TraceControlWidget::stopSampleTimer() {
	m_sampleTimer->stop();
}

void TraceControlWidget::setTraceEnabled(bool enabled) {
	m_channelEnabledCheckBox->setChecked(enabled);
	if (enabled) {
		m_timestepLabel1->show();
		m_timestepLabel2->show();
		m_timestepSpinBox->show();
		m_valueLabel->show();
		m_timestampLabel->show();
		m_clearPushButton->show();
	}
	else {
		m_timestepLabel1->hide();
		m_timestepLabel2->hide();
		m_timestepSpinBox->hide();
		m_valueLabel->hide();
		m_timestampLabel->hide();
		m_clearPushButton->hide();
	}
}

void TraceControlWidget::setTraceName(TQString name) {
	m_groupBox->setTitle(name);
}

double TraceControlWidget::timestep() {
	return m_nominalTimeStep;
}

void TraceControlWidget::setTimestep(double seconds) {
	m_nominalTimeStep = seconds;
	m_timestepSpinBox->setFloatValue(m_nominalTimeStep);
	startSampleTimer(m_nominalTimeStep*1.0e3);
}

void TraceControlWidget::setMinTimestep(double seconds) {
	m_minimumTimeStep = seconds;
	m_timestepSpinBox->setFloatMin(seconds);
	if (seconds < 0.001) {
		m_timestepSpinBox->setPrecision(3);
	}
	else if (seconds < 0.01) {
		m_timestepSpinBox->setPrecision(2);
	}
	else if (seconds < 0.1) {
		m_timestepSpinBox->setPrecision(1);
	}
	else {
		m_timestepSpinBox->setPrecision(0);
	}
}

void TraceControlWidget::enableClicked() {
	bool enabled = m_channelEnabledCheckBox->isOn();
	emit(enableChanged(enabled));
	startSampleTimer(m_nominalTimeStep*1.0e3);
}

void TraceControlWidget::timestepChanged(double value) {
	m_sampleTimer->stop();
	startSampleTimer(value*1.0e3);
}

void TraceControlWidget::setCurrentSampleValue(double value, TQString units) {
	m_valueLabel->setText(i18n("<qt><nobr>Value: <b>%1%2</b></qt>").arg(value).arg(units));
}

void TraceControlWidget::setCurrentSampleTimestamp(TQDateTime timestamp) {
	m_timestampLabel->setText(i18n("<qt><nobr>Sampled: <b>%1 UTC</b></qt>").arg(timestamp.toString()));
}

SensorMonitorPart::SensorMonitorPart(TQWidget *parentWidget, const char *widgetName, TQObject *parent, const char *name, const TQStringList&)
	: RemoteInstrumentPart( parent, name ), m_base(NULL), m_commHandlerState(0), m_connectionActiveAndValid(false), m_tickerState(0), stopTraceUpdate(false)
{
	// Initialize important base class variables
	m_clientLibraryName = CLIENT_LIBRARY;

	// Initialize mutex
	m_connectionMutex = new TQMutex(false);

	// Initialize kpart
	setInstance(Factory::instance());
	setWidget(new TQVBox(parentWidget, widgetName));

	// Create timers
	m_forcedUpdateTimer = new TQTimer(this);
	connect(m_forcedUpdateTimer, SIGNAL(timeout()), this, SLOT(mainEventLoop()));
	m_updateTimeoutTimer = new TQTimer(this);
	connect(m_updateTimeoutTimer, SIGNAL(timeout()), this, SLOT(mainEventLoop()));
	m_pingDelayTimer = new TQTimer(this);
	connect(m_pingDelayTimer, SIGNAL(timeout()), this, SLOT(mainEventLoop()));

	// Initialize data
	m_hdivs = 10;
	m_vdivs = 8;
	m_maxNumberOfTraces = 0;
	for (int traceno=0; traceno<=MAXTRACES; traceno++) {
		m_samplesInTrace[traceno] = 0;
		m_channelActive[traceno] = false;
		m_traceUnits[traceno] = "";
		m_traceControlWidgetList[traceno] = NULL;
		m_sampleRequestInProgress[traceno] = false;
	}

	// Create widgets
	m_base = new SensorMonitorBase(widget());
	m_traceControlWidgetGrid = new TQGridLayout(m_base->traceControlLayoutWidget);
	m_traceWidget = m_base->traceWidget;
	m_traceWidget->setSizePolicy(TQSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding));
	m_traceWidget->setNumberOfCursors(4);
	m_traceWidget->setZoomCursorStartIndex(0);
	m_traceWidget->setCursorOrientation(0, TQt::Horizontal);
	m_traceWidget->setCursorOrientation(1, TQt::Horizontal);
	m_traceWidget->setCursorOrientation(2, TQt::Vertical);
	m_traceWidget->setCursorOrientation(3, TQt::Vertical);
	m_traceWidget->setCursorEnabled(0, true);
	m_traceWidget->setCursorEnabled(1, true);
	m_traceWidget->setCursorEnabled(2, true);
	m_traceWidget->setCursorEnabled(3, true);
	m_traceWidget->setCursorName(0, "Cursor H1");
	m_traceWidget->setCursorName(1, "Cursor H2");
	m_traceWidget->setCursorName(2, "Cursor V1");
	m_traceWidget->setCursorName(3, "Cursor V2");
	m_traceWidget->setCursorPosition(0, 25);
	m_traceWidget->setCursorPosition(1, 75);
	m_traceWidget->setCursorPosition(2, 25);
	m_traceWidget->setCursorPosition(3, 75);
	TraceNumberList activeTraces;
	for (uint trace=0; trace<MAXTRACES; trace++) {
		activeTraces.append(trace);
	}
	m_traceWidget->setCursorActiveTraceList(0, activeTraces);
	m_traceWidget->setCursorActiveTraceList(1, activeTraces);
	m_traceWidget->setCursorActiveTraceList(2, activeTraces);
	m_traceWidget->setCursorActiveTraceList(3, activeTraces);
	m_traceWidget->setZoomBoxEnabled(true);

	m_base->traceZoomWidget->setSizePolicy(TQSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding));
	connect(m_traceWidget, SIGNAL(zoomBoxChanged(const TQRectF&)), this, SLOT(updateZoomWidgetLimits(const TQRectF&)));

	// Initialize widgets
	connect(m_base->runControlStartButton, SIGNAL(clicked()), this, SLOT(acquisitionStartButtonClicked()));
	connect(m_base->runControlStopButton, SIGNAL(clicked()), this, SLOT(acquisitionStopButtonClicked()));
	connect(m_base->waveformSave, SIGNAL(clicked()), this, SLOT(saveWaveforms()));
	connect(m_base->waveformRecall, SIGNAL(clicked()), this, SLOT(recallWaveforms()));
	connect(m_base->autoSave, SIGNAL(clicked()), this, SLOT(processLockouts()));

	TQTimer::singleShot(0, this, TQT_SLOT(postInit()));
}

SensorMonitorPart::~SensorMonitorPart() {
	if (m_connectionMutex->locked()) {
		printf("[WARNING] Exiting when data transfer still in progress!\n\r"); fflush(stdout);
	}

	disconnectFromServer();
	delete m_connectionMutex;
}

void SensorMonitorPart::processLockouts() {
	TQWidget* mainWidget = widget();
	if (mainWidget) {
		if ((m_socket) && (m_socket->state() == TQSocket::Connected) && (connToServerState > 0) && (connToServerConnecting == false)) {
			mainWidget->setEnabled(true);
		}
		else {
			mainWidget->setEnabled(false);
		}
	}
	if (stopTraceUpdate) {
		m_base->runControlStartButton->setEnabled(true);
		m_base->runControlStopButton->setEnabled(false);
		m_base->waveformSave->setEnabled(true);
		m_base->waveformRecall->setEnabled(true);
		m_base->autoSave->setEnabled(true);
		if (m_base->autoSave->isOn()) {
			m_base->autoSaveFile->setEnabled(true);
		}
		else {
			m_base->autoSaveFile->setEnabled(false);
		}
	}
	else {
		m_base->runControlStartButton->setEnabled(false);
		m_base->runControlStopButton->setEnabled(true);
		m_base->waveformSave->setEnabled(false);
		m_base->waveformRecall->setEnabled(false);
		m_base->autoSave->setEnabled(false);
		m_base->autoSaveFile->setEnabled(false);
	}
}

void SensorMonitorPart::resizeToHint() {
	resize(widget()->sizeHint());
}

void SensorMonitorPart::connectionClosed() {
	closeURL();
}

void SensorMonitorPart::postInit() {
	setUsingFixedSize(false);
}

bool SensorMonitorPart::openURL(const KURL &url) {
	int ret;
	ret = connectToServer(url.url());
	processLockouts();
	return (ret != 0);
}

bool SensorMonitorPart::closeURL() {
	disconnectFromServer();
	m_url = KURL();
	return true;
}

void SensorMonitorPart::disconnectFromServerCallback() {
	m_forcedUpdateTimer->stop();
	m_updateTimeoutTimer->stop();
}

void SensorMonitorPart::connectionFinishedCallback() {
	connect(m_socket, SIGNAL(readyRead()), m_socket, SLOT(processPendingData()));
	m_socket->processPendingData();
	connect(m_socket, SIGNAL(newDataReceived()), this, SLOT(mainEventLoop()));
	m_tickerState = 0;
	m_commHandlerState = ModeIdle_StateSensorListRequest;
	m_commHandlerMode = ModeIdle;
	m_socket->setDataTimeout(NETWORK_COMM_TIMEOUT_MS);
	m_updateTimeoutTimer->start(NETWORK_COMM_TIMEOUT_MS, TRUE);
	processLockouts();
	mainEventLoop();
	return;
}

void SensorMonitorPart::connectionStatusChangedCallback() {
	processLockouts();
}

#define UPDATEDISPLAY_TIMEOUT		m_connectionActiveAndValid = false;														\
					m_tickerState = 0;																\
					if (m_commHandlerState != ModeIdle_StateProcessReset) {												\
						m_commHandlerState = ModeIdle_StateResetRequest;											\
					}																		\
					m_commHandlerMode = ModeIdle;															\
					for (int traceno=0; traceno<=MAXTRACES; traceno++) {												\
						m_sampleRequestInProgress[traceno] = false;												\
					}																		\
					m_socket->clearIncomingData();															\
					setStatusMessage(i18n("Server ping timeout.  Please verify the status of your network connection."));						\
					m_updateTimeoutTimer->start(NETWORK_COMM_TIMEOUT_MS, TRUE);											\
					m_connectionMutex->unlock();															\
					return;

#define SET_WATCHDOG_TIMER		if (!m_updateTimeoutTimer->isActive()) m_updateTimeoutTimer->start(NETWORK_COMM_TIMEOUT_MS, TRUE);
#define PAT_WATCHDOG_TIMER		m_updateTimeoutTimer->stop(); m_updateTimeoutTimer->start(NETWORK_COMM_TIMEOUT_MS, TRUE);

#define SET_NEXT_STATE(x)		if ((m_commHandlerMode == ModeIdle) || (m_commHandlerMode == ModeGetSample)) {									\
						m_commHandlerState = x;															\
					}																		\
					else {																		\
						m_commHandlerState = ModeIdle_StateExternalRequest;											\
						EXEC_NEXT_STATE_IMMEDIATELY														\
					}
#define SET_NEXT_STATE_DATA_WAITING(x)	m_commHandlerState = x;

#define EXEC_NEXT_STATE_IMMEDIATELY	m_forcedUpdateTimer->start(0, TRUE);

void SensorMonitorPart::setTickerMessage(TQString message) {
	m_connectionActiveAndValid = true;
	TQString tickerChar;
	switch (m_tickerState) {
		case 0:
			tickerChar = "-";
			break;
		case 1:
			tickerChar = "\\";
			break;
		case 2:
			tickerChar = "|";
			break;
		case 3:
			tickerChar = "/";
			break;
	}
	setStatusMessage(message + TQString("... %1").arg(tickerChar));
	m_tickerState++;
	if (m_tickerState > 3) {
		m_tickerState = 0;
	}
}

void SensorMonitorPart::mainEventLoop() {
	TQDataStream ds(m_socket);
	ds.setPrintableData(true);

	if (!m_connectionMutex->tryLock()) {
		EXEC_NEXT_STATE_IMMEDIATELY
		return;
	}

	if (m_socket) {
		if ((m_commHandlerMode == ModeIdle) || (m_commHandlerMode == ModeInterruptRequested)) {
			// Normal operation
			switch (m_commHandlerState) {
				case ModeIdle_StateSensorListRequest:
					PAT_WATCHDOG_TIMER
					ds << TQString("SENSORS");
					m_socket->writeEndOfFrame();
					SET_NEXT_STATE_DATA_WAITING(ModeIdle_StateProcessSensorList)
					break;
				case ModeIdle_StateProcessSensorList:
					if (m_socket->canReadFrame()) {
						PAT_WATCHDOG_TIMER

						ds >> m_sensorList;
						m_socket->clearFrameTail();

						m_maxNumberOfTraces = m_sensorList.count();
						updateTraceControlWidgets();

						SET_NEXT_STATE(ModeIdle_StateStatusRequest)
						EXEC_NEXT_STATE_IMMEDIATELY
					}
					else {
						if (!m_updateTimeoutTimer->isActive()) {
							UPDATEDISPLAY_TIMEOUT
						}
					}
					break;
				case ModeIdle_StateStatusRequest:
					PAT_WATCHDOG_TIMER

					// Ping remote system
					ds << TQString("PING");
					m_socket->writeEndOfFrame();

					SET_NEXT_STATE_DATA_WAITING(ModeIdle_StateProcessStatus)
					break;
				case ModeIdle_StateProcessStatus:
					// Get all data
					if (m_socket->canReadFrame()) {
						PAT_WATCHDOG_TIMER

						TQString status;
						ds >> status;
						m_socket->clearFrameTail();

						if (status == "") {
							// Transfer probably failed
							UPDATEDISPLAY_TIMEOUT
						}
						else if (status == "PONG") {
							// Do nothing
						}

						setTickerMessage(i18n("Connected"));

						m_pingDelayTimer->start(250, TRUE);
						SET_NEXT_STATE(ModeIdle_StateDelay);
					}
					else {
						if (!m_updateTimeoutTimer->isActive()) {
							UPDATEDISPLAY_TIMEOUT
						}
					}
					break;
				case ModeIdle_StateResetRequest:
					// Reset remote system
					ds << TQString("RESET");
					m_socket->writeEndOfFrame();

					SET_NEXT_STATE_DATA_WAITING(ModeIdle_StateProcessReset)
					break;
				case ModeIdle_StateProcessReset:
					// Get all data
					if (m_socket->canReadFrame()) {
						PAT_WATCHDOG_TIMER

						TQString status;
						ds >> status;
						m_socket->clearFrameTail();

						if (status == "RESET") {
							SET_NEXT_STATE(ModeIdle_StateStatusRequest)
							EXEC_NEXT_STATE_IMMEDIATELY
						}
					}
					else {
						if (!m_updateTimeoutTimer->isActive()) {
							UPDATEDISPLAY_TIMEOUT
						}
					}
					break;
				case ModeIdle_StateDelay:
					// Let the client and server rest for a bit to lower CPU/network overhead
					if (!m_pingDelayTimer->isActive()) {
						EXEC_NEXT_STATE_IMMEDIATELY
						// Execute query on next event loop
						SET_NEXT_STATE(ModeIdle_StateStatusRequest);
					}
					PAT_WATCHDOG_TIMER
					break;
				case ModeIdle_StatePaused:
					PAT_WATCHDOG_TIMER
					break;
				case ModeIdle_StateExternalRequest:
					m_commHandlerMode = ModeGetSample;
					m_commHandlerState = m_commHandlerNextState;
					break;
			}
		}
		else if (m_commHandlerMode == ModeGetSample) {
			if (m_commHandlerState == ModeGetSample_StateSampleRequest) {
				PAT_WATCHDOG_TIMER
				ds << TQString("SAMPLE");
				ds << m_sampleRequestIndex;
				m_socket->writeEndOfFrame();
				SET_NEXT_STATE_DATA_WAITING(ModeGetSample_StateProcessSample)
				setTickerMessage(i18n("Obtaining new data point for sensor %1").arg(m_sensorList[m_sampleRequestIndex].name));
			}
			else if (m_commHandlerState == ModeGetSample_StateProcessSample) {
				if (m_socket->canReadFrame()) {
					PAT_WATCHDOG_TIMER

					TQString result;
					double newValue;
					TQDateTime timestamp;

					ds >> result;
					if (result == "ACK") {
						ds >> newValue;
						ds >> timestamp;

						TQDoubleArray sampleArray = m_traceWidget->samples(m_sampleRequestIndex);
						TQDoubleArray positionArray = m_traceWidget->positions(m_sampleRequestIndex);
						m_samplesInTrace[m_sampleRequestIndex]++;
						sampleArray.resize(m_samplesInTrace[m_sampleRequestIndex]);
						positionArray.resize(m_samplesInTrace[m_sampleRequestIndex]);
						sampleArray[m_samplesInTrace[m_sampleRequestIndex]-1] = newValue;
						positionArray[m_samplesInTrace[m_sampleRequestIndex]-1] = (timestamp.toTime_t()+(timestamp.time().msec()*1.0e-3));
	
						m_traceWidget->setSamples(m_sampleRequestIndex, sampleArray);
						m_traceWidget->setPositions(m_sampleRequestIndex, positionArray);
						m_base->traceZoomWidget->setSamples(m_sampleRequestIndex, sampleArray);
						m_base->traceZoomWidget->setPositions(m_sampleRequestIndex, positionArray);

						m_traceControlWidgetList[m_sampleRequestIndex]->setCurrentSampleValue(newValue, m_sensorList[m_sampleRequestIndex].units);
						m_traceControlWidgetList[m_sampleRequestIndex]->setCurrentSampleTimestamp(timestamp);
	
						updateGraticule();
						m_traceWidget->repaint(false);
						m_base->traceZoomWidget->repaint(false);
					}

					processAutosave();
					m_socket->clearFrameTail();
					m_sampleRequestInProgress[m_sampleRequestIndex] = false;
					m_commHandlerMode = ModeIdle;
					m_pingDelayTimer->start(250, TRUE);
					SET_NEXT_STATE(ModeIdle_StateDelay);
					EXEC_NEXT_STATE_IMMEDIATELY
				}
				else {
					if (!m_updateTimeoutTimer->isActive()) {
						UPDATEDISPLAY_TIMEOUT
					}
				}
			}
		}

		SET_WATCHDOG_TIMER
	}
	else {
		SET_NEXT_STATE(ModeIdle_StateStatusRequest);
		m_commHandlerMode = ModeIdle;
	}

	m_connectionMutex->unlock();
}

void SensorMonitorPart::updateZoomWidgetLimits(const TQRectF& zoomRect) {
	for (int traceno=0; traceno<m_maxNumberOfTraces; traceno++) {
		TQRectF fullZoomRect = m_traceWidget->displayLimits(traceno);
		double widthSpan = fullZoomRect.width()-fullZoomRect.x();
		double heightSpan = fullZoomRect.height()-fullZoomRect.y();

		TQRectF zoomLimitsRect((fullZoomRect.x()+(widthSpan*(zoomRect.x()/100.0))), (fullZoomRect.y()+(heightSpan*(zoomRect.y()/100.0))), (fullZoomRect.x()+(widthSpan*((zoomRect.x()/100.0)+(zoomRect.width()/100.0)))), (fullZoomRect.y()+(heightSpan*((zoomRect.y()/100.0)+(zoomRect.height()/100.0)))));

		m_base->traceZoomWidget->setDisplayLimits(traceno, zoomLimitsRect);
	}
}

void SensorMonitorPart::updateGraticule() {
	m_traceWidget->setNumberOfHorizontalDivisions(m_hdivs);
	m_traceWidget->setNumberOfVerticalDivisions(m_vdivs);
	m_base->traceZoomWidget->setNumberOfHorizontalDivisions(m_hdivs);
	m_base->traceZoomWidget->setNumberOfVerticalDivisions(m_vdivs);

	if (m_maxNumberOfTraces > 0) m_traceWidget->setTraceColor(0, TQColor(255, 255, 255));
	if (m_maxNumberOfTraces > 1) m_traceWidget->setTraceColor(1, TQColor(128, 255, 128));
	if (m_maxNumberOfTraces > 2) m_traceWidget->setTraceColor(2, TQColor(255, 255, 128));
	if (m_maxNumberOfTraces > 3) m_traceWidget->setTraceColor(3, TQColor(128, 128, 255));

	if (m_maxNumberOfTraces > 0) m_base->traceZoomWidget->setTraceColor(0, TQColor(255, 255, 255));
	if (m_maxNumberOfTraces > 1) m_base->traceZoomWidget->setTraceColor(1, TQColor(128, 255, 128));
	if (m_maxNumberOfTraces > 2) m_base->traceZoomWidget->setTraceColor(2, TQColor(255, 255, 128));
	if (m_maxNumberOfTraces > 3) m_base->traceZoomWidget->setTraceColor(3, TQColor(128, 128, 255));

	for (int traceno=0; traceno<m_maxNumberOfTraces; traceno++) {
		m_traceWidget->setTraceEnabled(traceno, m_channelActive[traceno]);
		m_traceWidget->setTraceName(traceno, i18n("Sensor %1").arg(m_sensorList[traceno].name));
		m_traceWidget->setTraceHorizontalUnits(traceno, "s");
		m_traceWidget->setTraceVerticalUnits(traceno, m_sensorList[traceno].units);

		m_base->traceZoomWidget->setTraceEnabled(traceno, m_channelActive[traceno], TraceWidget::SummaryText);
		m_base->traceZoomWidget->setTraceName(traceno, i18n("Sensor %1").arg(m_sensorList[traceno].name));
		m_base->traceZoomWidget->setTraceHorizontalUnits(traceno, "s");
		m_base->traceZoomWidget->setTraceVerticalUnits(traceno, m_sensorList[traceno].units);

		m_traceWidget->setNumberOfSamples(traceno, m_samplesInTrace[traceno]);
		m_base->traceZoomWidget->setNumberOfSamples(traceno, m_samplesInTrace[traceno]);

		double starttime = 0.0;
		double endtime = 0.0;
		if (m_samplesInTrace[traceno] > 0) {
			starttime = m_traceWidget->positions(traceno)[0];
			endtime = m_traceWidget->positions(traceno)[m_samplesInTrace[traceno]-1];
		}
		m_traceWidget->setDisplayLimits(traceno, TQRectF(starttime, m_sensorList[traceno].max, endtime, m_sensorList[traceno].min));
		if (m_traceControlWidgetList[traceno]) {
			m_traceControlWidgetList[traceno]->setTraceEnabled(m_channelActive[traceno]);
		}
	}
	updateZoomWidgetLimits(m_traceWidget->zoomBox());
}

void SensorMonitorPart::updateTraceControlWidgets() {
	// Add or remove trace control widgets as needed...
	int i;
	for (i=0; i<m_maxNumberOfTraces;i++) {
		if (!m_traceControlWidgetList[i]) {
			m_traceControlWidgetList[i] = new TraceControlWidget(m_base->traceControlLayoutWidget);
			connect(m_traceControlWidgetList[i], SIGNAL(enableChanged(bool)), this, SLOT(traceControlEnableChanged(bool)));
			connect(m_traceControlWidgetList[i], SIGNAL(newSampleDesired()), this, SLOT(processNewSampleRequest()));
			connect(m_traceControlWidgetList[i], SIGNAL(clearTraceRequested()), this, SLOT(traceControlClearRequested()));
			m_traceControlWidgetGrid->addMultiCellWidget(m_traceControlWidgetList[i], i, i, 0, 0);
			m_traceControlWidgetList[i]->setTraceName(i18n("Sensor %1").arg(m_sensorList[i].name));
			m_traceControlWidgetList[i]->show();
			// Set sample rate
			m_traceControlWidgetList[i]->setMinTimestep(m_sensorList[i].mininterval);
			m_traceControlWidgetList[i]->setTimestep(m_sensorList[i].nominalinterval);
		}
	}
	for (i=m_maxNumberOfTraces; i<MAXTRACES;i++) {
		if (m_traceControlWidgetList[i]) {
			m_traceControlWidgetGrid->remove(m_traceControlWidgetList[i]);
			delete m_traceControlWidgetList[i];
		}
	}
}

void SensorMonitorPart::traceControlEnableChanged(bool enabled) {
	int i;
	int channel = -1;
	const TraceControlWidget* widget = dynamic_cast<const TraceControlWidget*>(sender());
	if (widget) {
		for (i=0; i<MAXTRACES;i++) {
			if (m_traceControlWidgetList[i] == widget) {
				channel = i;
				break;
			}
		}
		if ((channel >= 0) && (channel <=MAXTRACES)) {
			m_channelActive[channel] = enabled;
		}
	}

	updateGraticule();
	m_traceWidget->repaint(false);
	m_base->traceZoomWidget->repaint(false);
	updateTraceControlWidgets();
}

void SensorMonitorPart::traceControlClearRequested() {
	int i;
	int channel = -1;
	const TraceControlWidget* widget = dynamic_cast<const TraceControlWidget*>(sender());
	if (widget) {
		for (i=0; i<MAXTRACES;i++) {
			if (m_traceControlWidgetList[i] == widget) {
				channel = i;
				break;
			}
		}
		if ((channel >= 0) && (channel <=MAXTRACES)) {
			m_samplesInTrace[channel] = 0;
			TQDoubleArray sampleArray;
			TQDoubleArray positionArray;
			m_traceWidget->setSamples(m_sampleRequestIndex, sampleArray);
			m_traceWidget->setPositions(m_sampleRequestIndex, positionArray);
			m_base->traceZoomWidget->setSamples(m_sampleRequestIndex, sampleArray);
			m_base->traceZoomWidget->setPositions(m_sampleRequestIndex, positionArray);
			m_traceControlWidgetList[m_sampleRequestIndex]->setCurrentSampleValue(0, m_sensorList[channel].units);
			m_traceControlWidgetList[m_sampleRequestIndex]->setCurrentSampleTimestamp(TQDateTime());
		}
	}

	updateGraticule();
	m_traceWidget->repaint(false);
	m_base->traceZoomWidget->repaint(false);
	updateTraceControlWidgets();
}

void SensorMonitorPart::processNewSampleRequest() {
	int i;
	int channel = -1;
	const TraceControlWidget* widget = dynamic_cast<const TraceControlWidget*>(sender());
	if (widget) {
		for (i=0; i<MAXTRACES;i++) {
			if (m_traceControlWidgetList[i] == widget) {
				channel = i;
				break;
			}
		}
		if ((channel >= 0) && (channel <=MAXTRACES)) {
			if ((!stopTraceUpdate) && (m_commHandlerState != ModeIdle_StateProcessReset) && (m_commHandlerState != ModeIdle_StateResetRequest)) {
				if (!m_sampleRequestInProgress[channel]) {
					m_sampleRequestInProgress[channel] = true;
					processNewSampleRequest(channel);
				}
				else {
					setStatusMessage(i18n("Sample request made while previous sample not collected.  Some data was not captured (therefore lost) for sensor %1").arg(m_sensorList[channel].name));
				}
			}
		}
	}
}

void SensorMonitorPart::processNewSampleRequest(int channel) {
	TQValueTimer* senderTimer = const_cast<TQValueTimer*>(dynamic_cast<const TQValueTimer*>(sender()));
	if (senderTimer) {
		senderTimer->stop();
		delete senderTimer;
	}
	if (m_commHandlerMode == ModeIdle) {
		// Request a sample
		if (m_commHandlerState == ModeIdle_StateDelay) {
			m_commHandlerMode = ModeGetSample;
			m_commHandlerState = ModeGetSample_StateSampleRequest;
			EXEC_NEXT_STATE_IMMEDIATELY
		}
		else {
			m_commHandlerMode = ModeInterruptRequested;
			m_commHandlerNextState = ModeGetSample_StateSampleRequest;
		}
		m_sampleRequestIndex = channel;
	}
	else {
		// The main loop is already getting a sample
		// Resubmit the request later on
		if ((!stopTraceUpdate) && (m_commHandlerState != ModeIdle_StateProcessReset) && (m_commHandlerState != ModeIdle_StateResetRequest)) {
			TQValueTimer* timer = new TQValueTimer;
			timer->setValue(channel);
			connect(timer, SIGNAL(valueTimeout(int)), this, SLOT(processNewSampleRequest(int)));
			timer->start(10, TRUE);
		}
	}
}

void SensorMonitorPart::acquisitionStartButtonClicked() {
	stopTraceUpdate = false;
	processLockouts();
	if (m_socket) m_socket->clearIncomingData();
	m_commHandlerMode = ModeIdle;
	m_commHandlerState = ModeIdle_StateStatusRequest;
	EXEC_NEXT_STATE_IMMEDIATELY
}

void SensorMonitorPart::acquisitionStopButtonClicked() {
	stopTraceUpdate = true;
	processLockouts();
	for (int i=0; i<MAXTRACES;i++) {
		m_sampleRequestInProgress[i] = false;
	}
	m_commHandlerMode = ModeIdle;
	m_commHandlerState = ModeIdle_StatePaused;
	setStatusMessage(i18n("Acquisition stopped"));
	mainEventLoop();
}

void SensorMonitorPart::processAutosave() {
	if (m_base->autoSave->isOn()) {
		if (m_base->autoSaveFile->url() != "") {
			saveWaveforms(m_base->autoSaveFile->url());
		}
	}
}

#define WAVEFORM_MAGIC_NUMBER 2
#define WAVEFORM_FILE_VERSION 1

void SensorMonitorPart::saveWaveforms() {
	saveWaveforms(TQString::null);
}

void SensorMonitorPart::saveWaveforms(TQString fileName) {
	TQString saveFileName;
	if (fileName != "") {
		saveFileName = fileName;
	}
	else {
		saveFileName = KFileDialog::getSaveFileName(TQString::null, "*.wfm|Waveform Files (*.wfm)", 0, i18n("Save waveforms..."));
	}
	if (saveFileName != "") {
		TQFile file(saveFileName);
		file.open(IO_WriteOnly);
		TQDataStream ds(&file);
		TQ_INT32 magicNumber = WAVEFORM_MAGIC_NUMBER;
		TQ_INT32 version = WAVEFORM_FILE_VERSION;
		ds << magicNumber;
		ds << version;
		ds << m_sensorList;
		ds << m_hdivs;
		ds << m_vdivs;
		ds << m_maxNumberOfTraces;
		for (int traceno=0; traceno<m_maxNumberOfTraces; traceno++) {
			TQ_UINT8 boolValue;
			boolValue = m_channelActive[traceno];
			ds << boolValue;
			ds << m_traceControlWidgetList[traceno]->timestep();
			ds << m_samplesInTrace[traceno];
			ds << m_traceUnits[traceno];
			ds << m_traceWidget->samples(traceno);
			ds << m_traceWidget->positions(traceno);
		}
		for (int cursorno=0; cursorno<4; cursorno++) {
			ds << m_traceWidget->cursorPosition(cursorno);
		}
	}
}

void SensorMonitorPart::recallWaveforms() {
	TQString openFileName = KFileDialog::getOpenFileName(TQString::null, "*.wfm|Waveform Files (*.wfm)", 0, i18n("Open waveforms..."));
	if (openFileName != "") {
		TQFile file(openFileName);
		file.open(IO_ReadOnly);
		TQDataStream ds(&file);
		TQ_INT32 magicNumber;
		TQ_INT32 version;
		ds >> magicNumber;
		if (magicNumber == WAVEFORM_MAGIC_NUMBER) {
			ds >> version;
			if (version == WAVEFORM_FILE_VERSION) {
				ds >> m_sensorList;
				ds >> m_hdivs;
				ds >> m_vdivs;
				ds >> m_maxNumberOfTraces;
				updateTraceControlWidgets();
				for (int traceno=0; traceno<m_maxNumberOfTraces; traceno++) {
					TQ_UINT8 boolValue;
					ds >> boolValue;
					m_channelActive[traceno] = (boolValue!=0)?true:false;
					double timestep;
					ds >> timestep;
					m_traceControlWidgetList[traceno]->setTimestep(timestep);
					ds >> m_samplesInTrace[traceno];
					ds >> m_traceUnits[traceno];
					TQDoubleArray values;
					TQDoubleArray positions;
					ds >> values;
					ds >> positions;
					m_traceWidget->setNumberOfSamples(traceno, m_samplesInTrace[traceno], true);
					m_traceWidget->setSamples(traceno, values);
					m_traceWidget->setPositions(traceno, positions);
					m_base->traceZoomWidget->setSamples(traceno, values);
					m_base->traceZoomWidget->setPositions(traceno, positions);
					if (m_samplesInTrace[traceno] > 0) {
						m_traceControlWidgetList[traceno]->setCurrentSampleValue(values[m_samplesInTrace[traceno]-1], m_sensorList[traceno].units);
						TQDateTime lastSampleTime;
						lastSampleTime.setTime_t(positions[m_samplesInTrace[traceno]-1]);
						m_traceControlWidgetList[traceno]->setCurrentSampleTimestamp(lastSampleTime);
					}
				}
				for (int cursorno=0; cursorno<4; cursorno++) {
					double cursorPos;
					ds >> cursorPos;
					m_traceWidget->setCursorPosition(cursorno, cursorPos);
				}
				updateGraticule();
				m_traceWidget->repaint(false);
				m_base->traceZoomWidget->repaint(false);
				updateTraceControlWidgets();
			}
			else {
				KMessageBox::error(0, i18n("<qt>The selected waveform file version does not match this client</qt>"), i18n("Invalid File"));
			}
		}
		else {
			KMessageBox::error(0, i18n("<qt>Invalid waveform file selected</qt>"), i18n("Invalid File"));
		}
	}
}

TDEAboutData* SensorMonitorPart::createAboutData() {
	return new TDEAboutData( APP_NAME, I18N_NOOP( APP_PRETTYNAME ), APP_VERSION );
}

} //namespace RemoteLab

#include "part.moc"
