/*
 * Remote Laboratory FPGA Viewer 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-2013 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 <tdemessagebox.h>  //::start()
#include <tdeparts/genericfactory.h>
#include <tdemdimainfrm.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 <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 <math.h>

#include <tqpainter.h>

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

#define SERVER_TIMEOUT_MS 20000
#define FPGA_COMM_TIMEOUT_MS 2000
#define FPGA_DATA_PROCESSING_TIMEOUT_MS 19000

FPGALed::FPGALed(TQWidget *parent, const char *name)
	: KLed(parent, name), m_clickable(true)
{
	connect(this, SIGNAL(clicked()), this, SLOT(toggle()));

	setColor(green);
	setOffColor(TQApplication::palette(this).active().base().dark(200));
}

void FPGALed::setClickable(bool clickable) {
	if ((!clickable) && (m_clickable)) {
		disconnect(this, SIGNAL(clicked()), this, SLOT(toggle()));
	}
	else if ((clickable) && (!m_clickable)) {
		connect(this, SIGNAL(clicked()), this, SLOT(toggle()));
	}
	m_clickable = clickable;
}

void FPGALed::mouseReleaseEvent(TQMouseEvent *e) {
	if (e->button() == TQMouseEvent::LeftButton) {
		emit(clicked());
	}
}

FPGAPushButton::FPGAPushButton(TQWidget *parent, const char *name)
	: KLed(parent, name), mouseDown(false)
{
	off();
	setColor(green);
	setOffColor(TQApplication::palette(this).active().base().dark(200));
}

void FPGAPushButton::mousePressEvent(TQMouseEvent *e) {
	if (e->button() == TQMouseEvent::LeftButton) {
		on();
		mouseDown = true;
		emit(buttonPressed());
		emit(changed());
	}
}

void FPGAPushButton::mouseReleaseEvent(TQMouseEvent *e) {
	if (e->button() == TQMouseEvent::LeftButton) {
		off();
		mouseDown = false;
		emit(buttonReleased());
		emit(changed());
	}
}

void FPGAPushButton::enterEvent(TQEvent *e) {
	Q_UNUSED(e);
	if (mouseDown) {
		on();
		emit(buttonPressed());
		emit(changed());
	}
}

void FPGAPushButton::leaveEvent(TQEvent *e) {
	Q_UNUSED(e);
	if (mouseDown) {
		off();
		emit(buttonReleased());
		emit(changed());
	}
}

FPGA7Segment::FPGA7Segment( TQWidget *parent, const char *name )
	: TQFrame( parent, name )
{
	init();
}

FPGA7Segment::~FPGA7Segment() {
	free(m_prevSegments);
	free(m_currentSegments);
}

void FPGA7Segment::init() {
	setFrameStyle(TQFrame::Box | TQFrame::Raised);
	prevSegments = 0;
	val = 0;
	smallPoint = TRUE;
	setSegmentStyle(Flat);
	m_prevSegments = (char*)malloc(sizeof(char)*9);
	m_currentSegments = (char*)malloc(sizeof(char)*9);
	m_prevSegments[0] = 99;
	m_currentSegments[0] = 99;
	setSizePolicy(TQSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Minimum));
}

void FPGA7Segment::setLitSegments(unsigned char segs) {
	// This produces an array of up to 10 chars, with each char being the number of a lit segment, and the list being terminated with the number 99
	// The bit input in segs is active high
	// The bit sequence, MSB to LSB, is dp a b c d e f g
	// Segment letters are taken from ug130.pdf

	if (prevSegments != segs) {
		int i = 0;
		if (segs & 0x80) { m_currentSegments[i] = 7; i++; }
		if (segs & 0x40) { m_currentSegments[i] = 0; i++; }
		if (segs & 0x20) { m_currentSegments[i] = 2; i++; }
		if (segs & 0x10) { m_currentSegments[i] = 5; i++; }
		if (segs & 0x08) { m_currentSegments[i] = 6; i++; }
		if (segs & 0x04) { m_currentSegments[i] = 4; i++; }
		if (segs & 0x02) { m_currentSegments[i] = 1; i++; }
		if (segs & 0x01) { m_currentSegments[i] = 3; i++; }

		m_currentSegments[i] = 99;
	
		update();
	}

	prevSegments = segs;
}

void FPGA7Segment::drawContents( TQPainter *p )
{
	// Draw all segments
	TQPoint  pos;
	int digitSpace = smallPoint ? 2 : 1;
	int xSegLen    = width()*5/(1*(5 + digitSpace) + digitSpace);
	int ySegLen    = height()*5/12;
	int segLen     = ySegLen > xSegLen ? xSegLen : ySegLen;
	int xAdvance   = segLen*( 5 + 1 )/5;
	int xOffset    = ( width() - xAdvance + segLen/5 )/2;
	int yOffset    = ( height() - segLen*2 )/2;

	pos = TQPoint(xOffset, yOffset);
	drawDigit(pos, *p, segLen, m_currentSegments);
}

TQSize FPGA7Segment::sizeHint() const {
	return TQSize(10 + 9 * (1 + (smallPoint ? 0 : 1)), 23);
}

void FPGA7Segment::setSegmentStyle( SegmentStyle s ) {
	fill = (s == Flat || s == Filled);
	shadow = (s == Outline || s == Filled);
	update();
}

FPGA7Segment::SegmentStyle FPGA7Segment::segmentStyle() const {
	Q_ASSERT(fill || shadow);
	if (!fill && shadow) {
		return Outline;
	}
	if (fill && shadow) {
		return Filled;
	}
	return Flat;
}

static void addPoint( TQPointArray &a, const TQPoint &p ) {
	uint n = a.size();
	a.resize(n + 1);
	a.setPoint(n, p);
}

void FPGA7Segment::drawDigit(const TQPoint &pos, TQPainter &p, int segLen, const char *newSegs) {
	char updates[20][2];					// Can hold 2 times number of segments, only
								// first 10 used if segment table is correct
	int  nErases;
	int  nUpdates;
	const char *segs;
	int  i,j;
	
	const char erase      = 0;
	const char draw       = 1;
//	const char leaveAlone = 2;
	
	segs = m_prevSegments;
	for ( nErases=0; segs[nErases] != 99; nErases++ ) {
		updates[nErases][0] = erase;			// Get segments to erase to
		updates[nErases][1] = segs[nErases];		// remove old char
	}
	nUpdates = nErases;
	segs = newSegs;
	for(i = 0 ; segs[i] != 99 ; i++) {
		for ( j=0;  j<nErases; j++ ) {
			if ( segs[i] == updates[j][1] ) {	// Same segment ?
// FIXME
// Always redraw segments for now, as dragging windows in front of the LED display currently erases the occluded portion(s) of the display!
#if 0
				updates[j][0] = leaveAlone;	// yes, already on screen
				break;
#endif
			}
		}
		if ( j == nErases ) {				// If not already on screen
			updates[nUpdates][0] = draw;
			updates[nUpdates][1] = segs[i];
			nUpdates++;
		}
	}
	for ( i=0; i<nUpdates; i++ ) {
		if ( updates[i][0] == draw ) {
			drawSegment( pos, updates[i][1], p, segLen );
		}
		if (updates[i][0] == erase) {
			drawSegment( pos, updates[i][1], p, segLen, TRUE );
		}
	}

	memcpy(m_prevSegments, newSegs, sizeof(char)*9);
}

void FPGA7Segment::drawSegment(const TQPoint &pos, char segmentNo, TQPainter &p, int segLen, bool erase) {
	TQPoint pt = pos;
	int width = segLen/5;

	const TQColorGroup & g = colorGroup();
	TQColor lightColor,darkColor,fgColor;
	if (erase) {
		lightColor = backgroundColor();
		darkColor  = lightColor;
		fgColor    = lightColor;
	}
	else {
		lightColor = g.light();
		darkColor  = g.dark();
		fgColor    = g.foreground();
	}

#define LINETO(X,Y) addPoint( a, TQPoint(pt.x() + (X),pt.y() + (Y)))
#define LIGHT
#define DARK

	if ( fill ) {
		TQPointArray a(0);
	
		//The following is an exact copy of the switch below.
		//don't make any changes here
		switch ( segmentNo ) {
			case 0 :
				p.moveTo(pt);
				LIGHT;
				LINETO(segLen - 1,0);
				DARK;
				LINETO(segLen - width - 1,width);
				LINETO(width,width);
				LINETO(0,0);
				break;
			case 1 :
				pt += TQPoint(0 , 1);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,width);
				DARK;
				LINETO(width,segLen - width/2 - 2);
				LINETO(0,segLen - 2);
				LIGHT;
				LINETO(0,0);
				break;
			case 2 :
				pt += TQPoint(segLen - 1 , 1);
				p.moveTo(pt);
				DARK;
				LINETO(0,segLen - 2);
				LINETO(-width,segLen - width/2 - 2);
				LIGHT;
				LINETO(-width,width);
				LINETO(0,0);
				break;
			case 3 :
				pt += TQPoint(0 , segLen);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,-width/2);
				LINETO(segLen - width - 1,-width/2);
				LINETO(segLen - 1,0);
				DARK;
				if (width & 1) {            // adjust for integer division error
				LINETO(segLen - width - 3,width/2 + 1);
				LINETO(width + 2,width/2 + 1);
				} else {
				LINETO(segLen - width - 1,width/2);
				LINETO(width,width/2);
				}
				LINETO(0,0);
				break;
			case 4 :
				pt += TQPoint(0 , segLen + 1);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,width/2);
				DARK;
				LINETO(width,segLen - width - 2);
				LINETO(0,segLen - 2);
				LIGHT;
				LINETO(0,0);
				break;
			case 5 :
				pt += TQPoint(segLen - 1 , segLen + 1);
				p.moveTo(pt);
				DARK;
				LINETO(0,segLen - 2);
				LINETO(-width,segLen - width - 2);
				LIGHT;
				LINETO(-width,width/2);
				LINETO(0,0);
				break;
			case 6 :
				pt += TQPoint(0 , segLen*2);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,-width);
				LINETO(segLen - width - 1,-width);
				LINETO(segLen - 1,0);
				DARK;
				LINETO(0,0);
				break;
			case 7 :
				if ( smallPoint )   // if smallpoint place'.' between other digits
				pt += TQPoint(segLen + width/2 , segLen*2);
				else
				pt += TQPoint(segLen/2 , segLen*2);
				p.moveTo(pt);
				DARK;
				LINETO(width,0);
				LINETO(width,-width);
				LIGHT;
				LINETO(0,-width);
				LINETO(0,0);
				break;
			case 8 :
				pt += TQPoint(segLen/2 - width/2 + 1 , segLen/2 + width);
				p.moveTo(pt);
				DARK;
				LINETO(width,0);
				LINETO(width,-width);
				LIGHT;
				LINETO(0,-width);
				LINETO(0,0);
				break;
			case 9 :
				pt += TQPoint(segLen/2 - width/2 + 1 , 3*segLen/2 + width);
				p.moveTo(pt);
				DARK;
				LINETO(width,0);
				LINETO(width,-width);
				LIGHT;
				LINETO(0,-width);
				LINETO(0,0);
				break;
#if defined(QT_CHECK_RANGE)
				default :
					tqWarning( "FPGA7Segment::drawSegment: (%s) Internal error."
						"  Illegal segment id: %d\n",
						name( "unnamed" ), segmentNo );
#endif
			}
			// End exact copy
			p.setPen( fgColor );
			p.setBrush( fgColor );
			p.drawPolygon( a );
			p.setBrush( NoBrush );
			
			pt = pos;
	}
#undef LINETO
#undef LIGHT
#undef DARK

#define LINETO(X,Y) p.lineTo(TQPoint(pt.x() + (X),pt.y() + (Y)))
#define LIGHT p.setPen(lightColor)
#define DARK  p.setPen(darkColor)
	if ( shadow ) {
		switch ( segmentNo ) {
			case 0 :
				p.moveTo(pt);
				LIGHT;
				LINETO(segLen - 1,0);
				DARK;
				LINETO(segLen - width - 1,width);
				LINETO(width,width);
				LINETO(0,0);
				break;
			case 1 :
				pt += TQPoint(0,1);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,width);
				DARK;
				LINETO(width,segLen - width/2 - 2);
				LINETO(0,segLen - 2);
				LIGHT;
				LINETO(0,0);
				break;
			case 2 :
				pt += TQPoint(segLen - 1 , 1);
				p.moveTo(pt);
				DARK;
				LINETO(0,segLen - 2);
				LINETO(-width,segLen - width/2 - 2);
				LIGHT;
				LINETO(-width,width);
				LINETO(0,0);
				break;
			case 3 :
				pt += TQPoint(0 , segLen);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,-width/2);
				LINETO(segLen - width - 1,-width/2);
				LINETO(segLen - 1,0);
				DARK;
				if (width & 1) {            // adjust for integer division error
				LINETO(segLen - width - 3,width/2 + 1);
				LINETO(width + 2,width/2 + 1);
				} else {
				LINETO(segLen - width - 1,width/2);
				LINETO(width,width/2);
				}
				LINETO(0,0);
				break;
			case 4 :
				pt += TQPoint(0 , segLen + 1);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,width/2);
				DARK;
				LINETO(width,segLen - width - 2);
				LINETO(0,segLen - 2);
				LIGHT;
				LINETO(0,0);
				break;
			case 5 :
				pt += TQPoint(segLen - 1 , segLen + 1);
				p.moveTo(pt);
				DARK;
				LINETO(0,segLen - 2);
				LINETO(-width,segLen - width - 2);
				LIGHT;
				LINETO(-width,width/2);
				LINETO(0,0);
				break;
			case 6 :
				pt += TQPoint(0 , segLen*2);
				p.moveTo(pt);
				LIGHT;
				LINETO(width,-width);
				LINETO(segLen - width - 1,-width);
				LINETO(segLen - 1,0);
				DARK;
				LINETO(0,0);
				break;
			case 7 :
				if ( smallPoint )   // if smallpoint place'.' between other digits
				pt += TQPoint(segLen + width/2 , segLen*2);
				else
				pt += TQPoint(segLen/2 , segLen*2);
				p.moveTo(pt);
				DARK;
				LINETO(width,0);
				LINETO(width,-width);
				LIGHT;
				LINETO(0,-width);
				LINETO(0,0);
				break;
			case 8 :
				pt += TQPoint(segLen/2 - width/2 + 1 , segLen/2 + width);
				p.moveTo(pt);
				DARK;
				LINETO(width,0);
				LINETO(width,-width);
				LIGHT;
				LINETO(0,-width);
				LINETO(0,0);
				break;
			case 9 :
				pt += TQPoint(segLen/2 - width/2 + 1 , 3*segLen/2 + width);
				p.moveTo(pt);
				DARK;
				LINETO(width,0);
				LINETO(width,-width);
				LIGHT;
				LINETO(0,-width);
				LINETO(0,0);
				break;
#if defined(QT_CHECK_RANGE)
			default :
				tqWarning( "FPGA7Segment::drawSegment: (%s) Internal error."
					"  Illegal segment id: %d\n",
					name( "unnamed" ), segmentNo );
#endif
		}
	}

#undef LINETO
#undef LIGHT
#undef DARK
}

ImageViewerWindow::ImageViewerWindow(TQString caption, TQWidget *parent, const char *name)
	: KMdiChildView(caption, parent, name)
{
	TQGridLayout* layout = new TQGridLayout(this);
	m_label = new TQLabel(this);
	m_label->setScaledContents(true);
	layout->addWidget(m_label, 0, 0);
}

ImageViewerWindow::~ImageViewerWindow() {
	//
}

void ImageViewerWindow::resizeEvent(TQResizeEvent *) {
	//
}

void ImageViewerWindow::setPixmap(TQPixmap pixmap) {
	m_label->setPixmap(pixmap);
}

namespace RemoteLab {

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

#define LED_BASE_SIZE 20
#define LED_SIZE LED_BASE_SIZE,LED_BASE_SIZE

FPGAViewPart::FPGAViewPart(TQWidget *parentWidget, const char *widgetName, TQObject *parent, const char *name, const TQStringList&)
	: RemoteInstrumentPart( parent, name ), m_base(NULL), m_interfaceMode(BasicInterfaceMode),
	m_commHandlerState(0), m_commHandlerMode(0), m_commHandlerNextState(0), m_commHandlerNextMode(0), m_connectionActiveAndValid(false), m_tickerState(0), m_remoteInputModeEnabled(false), m_4bitInputValue(0), m_4bitOutputValue(0),
	m_8bitInputValue(0), m_8bitOutputValue(0), m_16bitInputValue(0), m_16bitOutputValue(0), m_7segDigit3OutputValue(0xff),
	m_7segDigit2OutputValue(0xff), m_7segDigit1OutputValue(0xff), m_7segDigit0OutputValue(0xff), m_sendingUserLogicReset(false),
	m_batchOutputFile(NULL), m_dataOutputFile(NULL), m_dataMemorySize(16384), m_dataMemorySizePrev(0),
	m_dataMemoryImageWidth(128), m_dataMemoryImageHeight(128),
	m_inputImageViewer(NULL), m_outputImageViewer(NULL)
{
	// 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_updateTimer = new TQTimer(this);
	m_timeoutTimer = new TQTimer(this);
	m_connectionTimer = new TQTimer(this);
	connect(m_connectionTimer, SIGNAL(timeout()), this, SLOT(finishConnectingToServer()));

	// Create widgets
	m_base = new FPGAViewBase(widget());

	// Load configuration
	m_config = new KSimpleConfig("ulab_client_part_fpgaviewer.conf", false);
	m_config->setGroup("UI");
	m_interfaceMode = (RemoteLab::FPGAViewPart::InterfaceMode)(m_config->readNumEntry("interfaceMode", BasicInterfaceMode));
	m_base->batchTestInputFile->setURL(m_config->readPathEntry("batchTestInputFile", ""));
	m_base->batchTestOutputFile->setURL(m_config->readPathEntry("batchTestOutputFile", ""));
	m_base->batchTest16BitCheckBox->setChecked(m_config->readBoolEntry("batchUsing16Bit", false));
	m_base->dataProcessingInputFile->setURL(m_config->readPathEntry("dataProcessingInputFile", ""));
	m_base->dataProcessingOutputFile->setURL(m_config->readPathEntry("dataProcessingOutputFile", ""));

	// Create menu actions
	// Submenus
	TDEActionCollection *const ac = actionCollection();
	m_modeSubMenu = new TDEActionMenu(i18n("Mode"), ac, "mode_submenu");
	m_menuActionList.append(m_modeSubMenu);
	// Menu items
	m_modeBasicEnabled = new TDEToggleAction(i18n("Basic"), TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(switchToBasicMode()), ac, "mode_basic_enabled");
	m_modeSubMenu->insert(m_modeBasicEnabled);
	m_modeIntermediateEnabled = new TDEToggleAction(i18n("Intermediate"), TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(switchToIntermediateMode()), ac, "mode_intermediate_enabled");
	m_modeSubMenu->insert(m_modeIntermediateEnabled);
	m_modeAdvancedEnabled = new TDEToggleAction(i18n("Advanced"), TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(switchToAdvancedMode()), ac, "mode_advanced_enabled");
	m_modeSubMenu->insert(m_modeAdvancedEnabled);

	// Initialize widgets
	m_base->group4BitInputLED3->setFixedSize(LED_SIZE);
	m_base->group4BitInputLED2->setFixedSize(LED_SIZE);
	m_base->group4BitInputLED1->setFixedSize(LED_SIZE);
	m_base->group4BitInputLED0->setFixedSize(LED_SIZE);
	m_base->group4BitOutputLED3->setFixedSize(LED_SIZE);
	m_base->group4BitOutputLED2->setFixedSize(LED_SIZE);
	m_base->group4BitOutputLED1->setFixedSize(LED_SIZE);
	m_base->group4BitOutputLED0->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED7->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED6->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED5->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED4->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED3->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED2->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED1->setFixedSize(LED_SIZE);
	m_base->group8BitInputLED0->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED7->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED6->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED5->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED4->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED3->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED2->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED1->setFixedSize(LED_SIZE);
	m_base->group8BitOutputLED0->setFixedSize(LED_SIZE);

	m_base->group4BitInputLED3->setState(KLed::Off);
	m_base->group4BitInputLED2->setState(KLed::Off);
	m_base->group4BitInputLED1->setState(KLed::Off);
	m_base->group4BitInputLED0->setState(KLed::Off);
	m_base->group4BitOutputLED3->setState(KLed::Off);
	m_base->group4BitOutputLED2->setState(KLed::Off);
	m_base->group4BitOutputLED1->setState(KLed::Off);
	m_base->group4BitOutputLED0->setState(KLed::Off);
	m_base->group8BitInputLED7->setState(KLed::Off);
	m_base->group8BitInputLED6->setState(KLed::Off);
	m_base->group8BitInputLED5->setState(KLed::Off);
	m_base->group8BitInputLED4->setState(KLed::Off);
	m_base->group8BitInputLED3->setState(KLed::Off);
	m_base->group8BitInputLED2->setState(KLed::Off);
	m_base->group8BitInputLED1->setState(KLed::Off);
	m_base->group8BitInputLED0->setState(KLed::Off);
	m_base->group8BitOutputLED7->setState(KLed::Off);
	m_base->group8BitOutputLED6->setState(KLed::Off);
	m_base->group8BitOutputLED5->setState(KLed::Off);
	m_base->group8BitOutputLED4->setState(KLed::Off);
	m_base->group8BitOutputLED3->setState(KLed::Off);
	m_base->group8BitOutputLED2->setState(KLed::Off);
	m_base->group8BitOutputLED1->setState(KLed::Off);
	m_base->group8BitOutputLED0->setState(KLed::Off);

	m_base->group4BitOutputLED3->setClickable(false);
	m_base->group4BitOutputLED2->setClickable(false);
	m_base->group4BitOutputLED1->setClickable(false);
	m_base->group4BitOutputLED0->setClickable(false);
	m_base->group8BitOutputLED7->setClickable(false);
	m_base->group8BitOutputLED6->setClickable(false);
	m_base->group8BitOutputLED5->setClickable(false);
	m_base->group8BitOutputLED4->setClickable(false);
	m_base->group8BitOutputLED3->setClickable(false);
	m_base->group8BitOutputLED2->setClickable(false);
	m_base->group8BitOutputLED1->setClickable(false);
	m_base->group8BitOutputLED0->setClickable(false);

#if 0
	TQFontMetrics group8BitTextMetrics(m_base->group8BitOutputValueText->font());
	m_base->group8BitInputValueText->setFixedSize(group8BitTextMetrics.width("0000"), group8BitTextMetrics.height());
	m_base->group8BitOutputValueText->setFixedSize(group8BitTextMetrics.width("0000"), group8BitTextMetrics.height());
#endif

	connect(m_base->group4BitInputLED3, SIGNAL(changed()), this, SLOT(process4BitInputChanges()));
	connect(m_base->group4BitInputLED2, SIGNAL(changed()), this, SLOT(process4BitInputChanges()));
	connect(m_base->group4BitInputLED1, SIGNAL(changed()), this, SLOT(process4BitInputChanges()));
	connect(m_base->group4BitInputLED0, SIGNAL(changed()), this, SLOT(process4BitInputChanges()));

	connect(m_base->group8BitInputLED7, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED6, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED5, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED4, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED3, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED2, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED1, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));
	connect(m_base->group8BitInputLED0, SIGNAL(clicked()), this, SLOT(process8BitInputChanges()));

	connect(m_base->group16BitInputValue, SIGNAL(valueChanged(int)), this, SLOT(process16BitInputChanges()));

	m_base->LEDOutputDisplayDigit0->setBackgroundColor(TQt::black);
	m_base->LEDOutputDisplayDigit1->setBackgroundColor(TQt::black);
	m_base->LEDOutputDisplayDigit2->setBackgroundColor(TQt::black);
	m_base->LEDOutputDisplayDigit3->setBackgroundColor(TQt::black);
	m_base->LEDOutputDisplayDigit0->setPaletteForegroundColor(TQColor(0, 255, 64));
	m_base->LEDOutputDisplayDigit1->setPaletteForegroundColor(TQColor(0, 255, 64));
	m_base->LEDOutputDisplayDigit2->setPaletteForegroundColor(TQColor(0, 255, 64));
	m_base->LEDOutputDisplayDigit3->setPaletteForegroundColor(TQColor(0, 255, 64));
	// The LED display can work one of two ways
#if 0
	// Separated segments
	m_base->LEDOutputDisplayDigit0->setFrameStyle(TQFrame::Box | TQFrame::Raised);
	m_base->LEDOutputDisplayDigit1->setFrameStyle(TQFrame::Box | TQFrame::Raised);
	m_base->LEDOutputDisplayDigit2->setFrameStyle(TQFrame::Box | TQFrame::Raised);
	m_base->LEDOutputDisplayDigit3->setFrameStyle(TQFrame::Box | TQFrame::Raised);
	m_base->frameLEDDisplay->setFrameStyle(TQFrame::NoFrame);
#else
	// Combined segments
	m_base->frameLEDDisplay->setBackgroundColor(TQt::black);
	m_base->LEDOutputDisplayDigit0->setFrameStyle(TQFrame::NoFrame);
	m_base->LEDOutputDisplayDigit1->setFrameStyle(TQFrame::NoFrame);
	m_base->LEDOutputDisplayDigit2->setFrameStyle(TQFrame::NoFrame);
	m_base->LEDOutputDisplayDigit3->setFrameStyle(TQFrame::NoFrame);
	m_base->frameLEDDisplay->setFrameStyle(TQFrame::Box | TQFrame::Raised);
#endif

	m_base->frameLCDDisplay->setBackgroundColor(TQColor(192, 192, 192));
	m_base->LCDOutputLabel->setBackgroundColor(TQColor(192, 192, 192));
	m_base->LCDOutputLabel->setPaletteForegroundColor(TQColor(0, 0, 0));
	TQFontMetrics lcdmetrics(m_base->LCDOutputLabel->font());
	int lcdwidth = lcdmetrics.width("0000000000000000");
	int lcdheight = lcdmetrics.height()*2;
	m_base->LCDOutputLabel->setFixedSize(lcdwidth, lcdheight);
	m_base->frameLCDDisplay->setFrameStyle(TQFrame::Box | TQFrame::Raised);

	connect(m_base->groupGlobalControlResetButton, SIGNAL(clicked()), this, SLOT(groupGlobalControlResetButtonClicked()));
	connect(m_base->batchTestRunButton, SIGNAL(clicked()), this, SLOT(batchTestRunButtonClicked()));
	connect(m_base->dataProcessingRunButton, SIGNAL(clicked()), this, SLOT(dataProcessingRunButtonClicked()));

	connect(m_base->batchTestInputFile, SIGNAL(textChanged(const TQString &)), this, SLOT(processLockouts()));
	connect(m_base->batchTestOutputFile, SIGNAL(textChanged(const TQString &)), this, SLOT(processLockouts()));
	connect(m_base->dataProcessingInputFile, SIGNAL(textChanged(const TQString &)), this, SLOT(processLockouts()));
	connect(m_base->dataProcessingOutputFile, SIGNAL(textChanged(const TQString &)), this, SLOT(processLockouts()));

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

FPGAViewPart::~FPGAViewPart() {
	// Save field state for restoration on next load
	m_config->setGroup("UI");
	m_config->writeEntry("interfaceMode", m_interfaceMode);
	m_config->writeEntry("batchTestInputFile", m_base->batchTestInputFile->url());
	m_config->writeEntry("batchTestOutputFile", m_base->batchTestOutputFile->url());
	m_config->writeEntry("batchUsing16Bit", m_base->batchTest16BitCheckBox->isChecked());
	m_config->writeEntry("dataProcessingInputFile", m_base->dataProcessingInputFile->url());
	m_config->writeEntry("dataProcessingOutputFile", m_base->dataProcessingOutputFile->url());
	m_config->sync();
	delete m_config;

	// Close any active image display windows
	m_interfaceMode = BasicInterfaceMode;
	if (m_inputImageViewer) {
		mdiMainForm()->closeWindow(m_inputImageViewer);
		m_inputImageViewer = NULL;
	}
	if (m_outputImageViewer) {
		mdiMainForm()->closeWindow(m_outputImageViewer);
		m_outputImageViewer = NULL;
	}

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

	disconnectFromServer();
	delete m_connectionMutex;
}

void FPGAViewPart::processAllGraphicsUpdates() {
	// This is an expensive operation
	// Use it sparingly!

	process4BitInputChanges();
	process4BitOutputChanges();
	process8BitInputChanges();
	process8BitOutputChanges();
	process16BitInputChanges();
	process16BitOutputChanges();

	processLCDOutputChanges();
	process7SegmentLEDOutputChanges();

	processLockouts();
}

void FPGAViewPart::process4BitInputChanges() {
	// Read LED status into m_4bitInputValue
	m_4bitInputValue = 0;
	if (m_base->group4BitInputLED3->state() == KLed::On) m_4bitInputValue |= 0x08;
	if (m_base->group4BitInputLED2->state() == KLed::On) m_4bitInputValue |= 0x04;
	if (m_base->group4BitInputLED1->state() == KLed::On) m_4bitInputValue |= 0x02;
	if (m_base->group4BitInputLED0->state() == KLed::On) m_4bitInputValue |= 0x01;

	m_base->group4BitInputValueText->setText(TQString("0x%1").arg(m_4bitInputValue, 0, 16));
}

void FPGAViewPart::process4BitOutputChanges() {
	// Write m_4bitOutputValue to LEDs
	m_base->group4BitOutputLED3->setState((m_4bitOutputValue & 0x08)?KLed::On:KLed::Off);
	m_base->group4BitOutputLED2->setState((m_4bitOutputValue & 0x04)?KLed::On:KLed::Off);
	m_base->group4BitOutputLED1->setState((m_4bitOutputValue & 0x02)?KLed::On:KLed::Off);
	m_base->group4BitOutputLED0->setState((m_4bitOutputValue & 0x01)?KLed::On:KLed::Off);

	// Write m_4bitOutputValue to label
	m_base->group4BitOutputValueText->setText(TQString("0x%1").arg(m_4bitOutputValue, 0, 16));
}

void FPGAViewPart::process8BitInputChanges() {
	if (m_remoteInputModeEnabled) {
		// Read LED status into m_8bitInputValue
		m_8bitInputValue = 0;
		if (m_base->group8BitInputLED7->state() == KLed::On) m_8bitInputValue |= 0x80;
		if (m_base->group8BitInputLED6->state() == KLed::On) m_8bitInputValue |= 0x40;
		if (m_base->group8BitInputLED5->state() == KLed::On) m_8bitInputValue |= 0x20;
		if (m_base->group8BitInputLED4->state() == KLed::On) m_8bitInputValue |= 0x10;
		if (m_base->group8BitInputLED3->state() == KLed::On) m_8bitInputValue |= 0x08;
		if (m_base->group8BitInputLED2->state() == KLed::On) m_8bitInputValue |= 0x04;
		if (m_base->group8BitInputLED1->state() == KLed::On) m_8bitInputValue |= 0x02;
		if (m_base->group8BitInputLED0->state() == KLed::On) m_8bitInputValue |= 0x01;

		m_base->group8BitInputLED7->setClickable(true);
		m_base->group8BitInputLED6->setClickable(true);
		m_base->group8BitInputLED5->setClickable(true);
		m_base->group8BitInputLED4->setClickable(true);
		m_base->group8BitInputLED3->setClickable(true);
		m_base->group8BitInputLED2->setClickable(true);
		m_base->group8BitInputLED1->setClickable(true);
		m_base->group8BitInputLED0->setClickable(true);
	}
	else {
		// Write m_8bitInputValue to LEDs
		m_base->group8BitInputLED7->setState((m_8bitInputValue & 0x80)?KLed::On:KLed::Off);
		m_base->group8BitInputLED6->setState((m_8bitInputValue & 0x40)?KLed::On:KLed::Off);
		m_base->group8BitInputLED5->setState((m_8bitInputValue & 0x20)?KLed::On:KLed::Off);
		m_base->group8BitInputLED4->setState((m_8bitInputValue & 0x10)?KLed::On:KLed::Off);
		m_base->group8BitInputLED3->setState((m_8bitInputValue & 0x08)?KLed::On:KLed::Off);
		m_base->group8BitInputLED2->setState((m_8bitInputValue & 0x04)?KLed::On:KLed::Off);
		m_base->group8BitInputLED1->setState((m_8bitInputValue & 0x02)?KLed::On:KLed::Off);
		m_base->group8BitInputLED0->setState((m_8bitInputValue & 0x01)?KLed::On:KLed::Off);

		m_base->group8BitInputLED7->setClickable(false);
		m_base->group8BitInputLED6->setClickable(false);
		m_base->group8BitInputLED5->setClickable(false);
		m_base->group8BitInputLED4->setClickable(false);
		m_base->group8BitInputLED3->setClickable(false);
		m_base->group8BitInputLED2->setClickable(false);
		m_base->group8BitInputLED1->setClickable(false);
		m_base->group8BitInputLED0->setClickable(false);
	}

	m_base->group8BitInputValueText->setText(TQString("").sprintf("0x%02x", m_8bitInputValue));

	if (m_remoteInputModeEnabled) {
		m_base->group8BitInput->setTitle(i18n("8-Bit Input (Switches)") + " [" + i18n("Remote Input Mode") + "]");
	}
	else {
		m_base->group8BitInput->setTitle(i18n("8-Bit Input (Switches)") + " [" + i18n("Local Input Mode") + "]");
	}
}

void FPGAViewPart::update8BitInputLEDs() {
	// Write m_8bitInputValue to LEDs
	m_base->group8BitInputLED7->setState((m_8bitInputValue & 0x80)?KLed::On:KLed::Off);
	m_base->group8BitInputLED6->setState((m_8bitInputValue & 0x40)?KLed::On:KLed::Off);
	m_base->group8BitInputLED5->setState((m_8bitInputValue & 0x20)?KLed::On:KLed::Off);
	m_base->group8BitInputLED4->setState((m_8bitInputValue & 0x10)?KLed::On:KLed::Off);
	m_base->group8BitInputLED3->setState((m_8bitInputValue & 0x08)?KLed::On:KLed::Off);
	m_base->group8BitInputLED2->setState((m_8bitInputValue & 0x04)?KLed::On:KLed::Off);
	m_base->group8BitInputLED1->setState((m_8bitInputValue & 0x02)?KLed::On:KLed::Off);
	m_base->group8BitInputLED0->setState((m_8bitInputValue & 0x01)?KLed::On:KLed::Off);
}

void FPGAViewPart::process8BitOutputChanges() {
	// Write m_8bitOutputValue to LEDs
	m_base->group8BitOutputLED7->setState((m_8bitOutputValue & 0x80)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED6->setState((m_8bitOutputValue & 0x40)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED5->setState((m_8bitOutputValue & 0x20)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED4->setState((m_8bitOutputValue & 0x10)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED3->setState((m_8bitOutputValue & 0x08)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED2->setState((m_8bitOutputValue & 0x04)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED1->setState((m_8bitOutputValue & 0x02)?KLed::On:KLed::Off);
	m_base->group8BitOutputLED0->setState((m_8bitOutputValue & 0x01)?KLed::On:KLed::Off);

	m_base->group8BitOutputValueText->setText(TQString("").sprintf("0x%02x", m_8bitOutputValue));
}

void FPGAViewPart::process16BitInputChanges() {
	// Read input into m_16bitInputValue
	m_16bitInputValue = m_base->group16BitInputValue->value();
}

void FPGAViewPart::process16BitOutputChanges() {
	// Write m_16bitOutputValue to label
	m_base->group16BitOutputValue->setText(TQString("").sprintf("0x%04x", m_16bitOutputValue));
}

void FPGAViewPart::processLCDOutputChanges() {
	// Write m_LCDOutputValue to label
	m_base->LCDOutputLabel->setText(m_LCDOutputValue);
}

void FPGAViewPart::process7SegmentLEDOutputChanges() {
	// Write LED digits to 7 segment displays
	// Note that the information stored in the data structures is bitwise inverted
	m_base->LEDOutputDisplayDigit0->setLitSegments(~m_7segDigit0OutputValue);
	m_base->LEDOutputDisplayDigit1->setLitSegments(~m_7segDigit1OutputValue);
	m_base->LEDOutputDisplayDigit2->setLitSegments(~m_7segDigit2OutputValue);
	m_base->LEDOutputDisplayDigit3->setLitSegments(~m_7segDigit3OutputValue);
}

void FPGAViewPart::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);
		}
	}

	// Interface mode switching handlers
	if (m_interfaceMode == BasicInterfaceMode) {
		m_modeBasicEnabled->setChecked(true);
		m_modeIntermediateEnabled->setChecked(false);
		m_modeAdvancedEnabled->setChecked(false);

		m_base->groupLCDDisplay->show();
		m_base->groupLEDDisplay->hide();
		m_base->groupBatchTest->hide();
		m_base->groupDataProcessing->hide();
		m_base->groupInputImage->hide();
		m_base->groupOutputImage->hide();

		if (m_inputImageViewer) {
			mdiMainForm()->closeWindow(m_inputImageViewer);
			m_inputImageViewer = NULL;
		}
		if (m_outputImageViewer) {
			mdiMainForm()->closeWindow(m_outputImageViewer);
			m_outputImageViewer = NULL;
		}
	}
	if (m_interfaceMode == IntermediateInterfaceMode) {
		m_modeBasicEnabled->setChecked(false);
		m_modeIntermediateEnabled->setChecked(true);
		m_modeAdvancedEnabled->setChecked(false);

		m_base->groupLCDDisplay->show();
		m_base->groupLEDDisplay->show();
		m_base->groupBatchTest->show();
		m_base->groupDataProcessing->hide();
		m_base->groupInputImage->hide();
		m_base->groupOutputImage->hide();

		if (m_inputImageViewer) {
			mdiMainForm()->closeWindow(m_inputImageViewer);
			m_inputImageViewer = NULL;
		}
		if (m_outputImageViewer) {
			mdiMainForm()->closeWindow(m_outputImageViewer);
			m_outputImageViewer = NULL;
		}
	}
	if (m_interfaceMode == AdvancedInterfaceMode) {
		m_modeBasicEnabled->setChecked(false);
		m_modeIntermediateEnabled->setChecked(false);
		m_modeAdvancedEnabled->setChecked(true);

		m_base->groupLCDDisplay->show();
		m_base->groupLEDDisplay->show();
		m_base->groupBatchTest->show();
		m_base->groupDataProcessing->show();
		m_base->groupInputImage->show();
		m_base->groupOutputImage->show();

		if (!m_inputImageViewer) {
			m_inputImageViewer = new ImageViewerWindow(i18n("Remote FPGA Input Image"));
			mdiMainForm()->addWindow(m_inputImageViewer);
			m_inputImageViewer->resize(m_dataMemoryImageWidth, m_dataMemoryImageHeight);
		}
		if (!m_outputImageViewer) {
			m_outputImageViewer = new ImageViewerWindow(i18n("Remote FPGA Output Image"));
			mdiMainForm()->addWindow(m_outputImageViewer);
			m_outputImageViewer->resize(m_dataMemoryImageWidth, m_dataMemoryImageHeight);
		}
	}

	if ((m_base->batchTestInputFile->url() != "") && (m_base->batchTestOutputFile->url() != "") && (m_commHandlerMode == 0) && (m_connectionActiveAndValid == true)) {
		m_base->batchTestRunButton->setEnabled(true);
	}
	else {
		m_base->batchTestRunButton->setEnabled(false);
	}

	if (m_commHandlerMode == 1) {
		m_base->batchTestInputFile->setEnabled(false);
		m_base->batchTestOutputFile->setEnabled(false);
		m_base->batchTest16BitCheckBox->setEnabled(false);
		m_base->batchTestRunButton->setEnabled(false);
		m_base->groupGlobalControlResetButton->setEnabled(false);
	}
	else {
		m_base->batchTestInputFile->setEnabled(true);
		m_base->batchTestOutputFile->setEnabled(true);
		m_base->batchTest16BitCheckBox->setEnabled(true);
	}

	if ((m_connectionActiveAndValid == true) && (m_commHandlerMode == 0)) {
		m_base->batchTestStatusLabel->setText(i18n("Ready"));
	}

	if ((m_base->dataProcessingInputFile->url() != "") && (m_base->dataProcessingOutputFile->url() != "") && (m_commHandlerMode == 0) && (m_connectionActiveAndValid == true)) {
		m_base->dataProcessingRunButton->setEnabled(true);
	}
	else {
		m_base->dataProcessingRunButton->setEnabled(false);
	}

	if (m_commHandlerMode == 2) {
		m_base->dataProcessingInputFile->setEnabled(false);
		m_base->dataProcessingOutputFile->setEnabled(false);
		m_base->dataProcessingGenerateValidationString->setEnabled(false);
		m_base->dataProcessingRunButton->setEnabled(false);
		m_base->groupGlobalControlResetButton->setEnabled(false);
	}
	else {
		m_base->dataProcessingInputFile->setEnabled(true);
		m_base->dataProcessingOutputFile->setEnabled(true);
		// FIXME
		// Re-enable this when a good way to implement it (using Kerberos?) is found!
		//m_base->dataProcessingGenerateValidationString->setEnabled(true);
		m_base->dataProcessingGenerateValidationString->setEnabled(false);
	}

	if ((m_connectionActiveAndValid == true) && (m_commHandlerMode == 0)) {
		m_base->dataProcessingStatusLabel->setText(i18n("Ready"));
		m_base->groupGlobalControlResetButton->setEnabled(!m_sendingUserLogicReset);
	}
}

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

void FPGAViewPart::switchToBasicMode() {
	m_interfaceMode = BasicInterfaceMode;
	processLockouts();
	TQTimer::singleShot(0, this, SLOT(resizeToHint()));
}

void FPGAViewPart::switchToIntermediateMode() {
	m_interfaceMode = IntermediateInterfaceMode;
	processLockouts();
	TQTimer::singleShot(0, this, SLOT(resizeToHint()));
}

void FPGAViewPart::switchToAdvancedMode() {
	m_interfaceMode = AdvancedInterfaceMode;
	processLockouts();
	TQTimer::singleShot(0, this, SLOT(resizeToHint()));
}

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

void FPGAViewPart::postInit() {
	processAllGraphicsUpdates();

	setUsingFixedSize(true);
	connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(updateDisplay()));
	connect(m_timeoutTimer, SIGNAL(timeout()), this, SLOT(updateDisplay()));
}

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

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

void FPGAViewPart::disconnectFromServerCallback() {
	m_updateTimer->stop();
	m_timeoutTimer->stop();
}

void FPGAViewPart::connectionFinishedCallback() {
	connect(m_socket, SIGNAL(readyRead()), m_socket, SLOT(processPendingData()));
	m_socket->processPendingData();
	connect(m_socket, SIGNAL(newDataReceived()), this, SLOT(updateDisplay()));
	m_tickerState = 0;
	m_commHandlerState = 0;
	m_commHandlerMode = 0;
	m_commHandlerNextState = 0;
	m_commHandlerNextMode = 0;
	m_timeoutTimer->start(FPGA_COMM_TIMEOUT_MS, TRUE);
	processLockouts();
	updateDisplay();
	return;
}

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

TQPtrList<TDEAction> FPGAViewPart::menuActionList() {
	return m_menuActionList;
}

void FPGAViewPart::groupGlobalControlResetButtonClicked() {
	m_sendingUserLogicReset = true;
	processLockouts();
}

void FPGAViewPart::batchTestRunButtonClicked() {
	m_commHandlerNextState = 0;
	m_commHandlerNextMode = 1;
	m_batchUsing16Bit = m_base->batchTest16BitCheckBox->isChecked();
	processLockouts();
}

void FPGAViewPart::dataProcessingRunButtonClicked() {
	m_commHandlerNextState = 0;
	m_commHandlerNextMode = 2;
	m_dataGenerateValidationString = m_base->dataProcessingGenerateValidationString->isChecked();
	processLockouts();
}

void FPGAViewPart::sendInputStatesToRemoteFPGA() {
	if (m_socket) {
		char data[64];

		process4BitInputChanges();
		process8BitInputChanges();
		process16BitInputChanges();

		// 4-bit inputs
		data[0]  = 'I';
		data[1]  = '\r';
		data[2]  = m_4bitInputValue;
		data[3]  = '\r';
		// 8-bit inputs
		data[4]  = 'B';
		data[5]  = '\r';
		data[6]  = m_8bitInputValue;
		data[7]  = '\r';
		// 16-bit inputs
		data[8]  = 'C';
		data[9]  = '\r';
		data[10] = (m_16bitInputValue&0xff00)>>8;
		data[11] = '\r';
		data[12] = m_16bitInputValue&0xff;
		data[13] = '\r';
		m_socket->writeBlock(data, 14);
		m_socket->writeBufferedData();

		if (m_sendingUserLogicReset) {
			// Send user logic reset request
			data[0] = 'R';
			data[1] = '\r';
			m_socket->writeBlock(data, 2);
			m_socket->writeBufferedData();

			m_sendingUserLogicReset = false;
			processLockouts();
		}
	}
}

void FPGAViewPart::receiveInputStatesFromRemoteFPGA() {
	if (m_socket) {
		char data[64];

		// LCD
		m_socket->readBlock(data, 32);
		char line[34];
		memcpy(line, data, 16);
		line[16] = '\n';
		memcpy(line+17, data+16, 16);
		line[33] = 0;
		m_LCDOutputValue = line;

		// Remote/Local input mode
		m_socket->readBlock(data, 1);
		if (data[0] == 0) {
			// Local mode
			m_remoteInputModeEnabled = false;
		}
		else {
			// Remote mode
			m_remoteInputModeEnabled = true;
		}

		// DSP RAM size
		m_socket->readBlock(data, 1);
		m_dataMemorySize = pow(2, data[0]);

		if (m_dataMemorySize != m_dataMemorySizePrev) {
			unsigned int newSize = round(sqrt(m_dataMemorySize));
			m_dataMemoryImageWidth = newSize;
			m_dataMemoryImageHeight = newSize;
			m_base->groupInputImage->setTitle(TQString("Input Image (%1x%2):").arg(m_dataMemoryImageWidth).arg(m_dataMemoryImageHeight));
			m_base->groupOutputImage->setTitle(TQString("Output Image (%1x%2):").arg(m_dataMemoryImageWidth).arg(m_dataMemoryImageHeight));
			if (m_inputImageViewer) {
				m_inputImageViewer->resize(m_dataMemoryImageWidth, m_dataMemoryImageHeight);
			}
			if (m_outputImageViewer) {
				m_outputImageViewer->resize(m_dataMemoryImageWidth, m_dataMemoryImageHeight);
			}
			m_dataMemorySizePrev = m_dataMemorySize;
		}

		// 4-bit outputs
		m_socket->readBlock(data, 1);
		m_4bitOutputValue = data[0];

		// 8-bit outputs
		m_socket->readBlock(data, 1);
		m_8bitOutputValue = data[0];

		// 16-bit outputs
		m_socket->readBlock(data, 2);
		m_16bitOutputValue = (data[0] << 8) | data[1];

		// 7-segment LED display
		m_socket->readBlock(data, 4);
		m_7segDigit3OutputValue = data[0];
		m_7segDigit2OutputValue = data[1];
		m_7segDigit1OutputValue = data[2];
		m_7segDigit0OutputValue = data[3];

		// Write changes to GUI
		process4BitOutputChanges();
		process8BitInputChanges();
		process8BitOutputChanges();
		process16BitOutputChanges();
		processLCDOutputChanges();
		process7SegmentLEDOutputChanges();
	}
}

#define UPDATEDISPLAY_TIMEOUT	m_connectionActiveAndValid = false;														\
				m_tickerState = 0;																\
				m_commHandlerState = 0;																\
				m_commHandlerMode = 0;																\
				m_commHandlerNextState = 0;															\
				m_commHandlerNextMode = 0;															\
				while (m_socket->bytesAvailable() > 0) {													\
					m_socket->readBlock(data, 64);														\
				}																		\
				setStatusMessage(i18n("Debug interface timeout, still waiting for data.  Please verify that the FPGA is properly configured."));		\
				m_timeoutTimer->start(FPGA_COMM_TIMEOUT_MS, TRUE);												\
				return;

#define POLL_FOR_DATA_IMMEDIATE	if (!m_updateTimer->isActive()) {															\
					m_updateTimer->start(10, TRUE);														\
				}

void FPGAViewPart::updateDisplay() {
	if (m_socket) {
		char data[64];

		if (m_commHandlerMode == 0) {
			// Normal operation
			switch (m_commHandlerState) {
				case 0:
					// Send current input states to remote system
					sendInputStatesToRemoteFPGA();

					// Clear input buffer to ensure proper sync
					m_socket->clearIncomingData();

					// Send request for all output states
					m_socket->writeLine("L\r");
					m_timeoutTimer->start(FPGA_COMM_TIMEOUT_MS, TRUE);

					m_commHandlerState = 1;
					POLL_FOR_DATA_IMMEDIATE
					break;
				case 1:
					// Get all data
					if (m_socket->bytesAvailable() >= 42) {
						if (m_socket->bytesAvailable() == 42) {
							// Process the received data packet
							receiveInputStatesFromRemoteFPGA();
	
							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(i18n("Running") + TQString("... %1").arg(tickerChar));
							m_tickerState++;
							if (m_tickerState > 3) {
								m_tickerState = 0;
							}
							m_timeoutTimer->start(FPGA_COMM_TIMEOUT_MS, TRUE);
	
							m_commHandlerState = m_commHandlerNextState;
							m_commHandlerMode = m_commHandlerNextMode;
							m_commHandlerNextState = 0;
							m_commHandlerNextMode = 0;
							POLL_FOR_DATA_IMMEDIATE
						}
						else {
							// Invalid data packet
							// State likely lost in remote debug module (e.g. FPGA reprogrammed)
							// Clear input buffers and allow timeout to occur in order to force resync
							m_socket->clearIncomingData();
						}
					}
					else {
						if (!m_timeoutTimer->isActive()) {
							UPDATEDISPLAY_TIMEOUT
						}
						else {
							POLL_FOR_DATA_IMMEDIATE
						}
					}
	
					break;
			}
			processLockouts();
		}
		else if (m_commHandlerMode == 1) {
			// Batch test mode
			// This expects to see a newline-delimited text file containing input values to test
			if (m_commHandlerState == 0) {
				processLockouts();
				m_base->batchTestStatusLabel->setText(i18n("Reading input data") + "...");
				m_batchInputValueList.clear();
				TQFile file(m_base->batchTestInputFile->url());
				if (file.open(IO_ReadOnly)) {
					TQTextStream stream(&file);
					TQString line;
					while (!stream.atEnd()) {
						line = stream.readLine();
						m_batchInputValueList.append(line.toUInt());
					}
					file.close();

					m_base->batchTestProgressBar->setTotalSteps(m_batchInputValueList.count());

					m_batchOutputFile = new TQFile(m_base->batchTestOutputFile->url());
					if (m_batchOutputFile->open(IO_ReadWrite | IO_Truncate)) {
						m_batchCurrentValueIndex = 0;
						m_commHandlerState = 1;
						POLL_FOR_DATA_IMMEDIATE
					}
					else {
						delete m_batchOutputFile;
						m_batchOutputFile = NULL;
						KMessageBox::error(0, i18n("<qt>Unable to open selected batch output file</qt>"), i18n("Batch Failed"));
						m_commHandlerMode = 0;
						m_commHandlerState = 0;
						m_base->batchTestProgressBar->reset();
						processLockouts();
					}
				}
				else {
					KMessageBox::error(0, i18n("<qt>Unable to open selected batch input file</qt>"), i18n("Batch Failed"));
					m_commHandlerMode = 0;
					m_commHandlerState = 0;
					m_base->batchTestProgressBar->reset();
					processLockouts();
				}
			}
			else if (m_commHandlerState == 1) {
				if (m_batchCurrentValueIndex >= m_batchInputValueList.count()) {
					// Done!
					m_batchOutputFile->flush();
					m_batchOutputFile->close();
					delete m_batchOutputFile;
					m_batchOutputFile = NULL;
					m_commHandlerMode = 0;
					m_commHandlerState = 0;
					m_base->batchTestProgressBar->reset();
					processLockouts();
				}
				else {
					m_base->batchTestStatusLabel->setText(i18n("Processing element") + " " + TQString("%1/%2").arg(m_batchCurrentValueIndex).arg(m_batchInputValueList.count()) + "...");
					if (m_batchUsing16Bit) {
						m_16bitInputValue = m_batchInputValueList[m_batchCurrentValueIndex];
					}
					else {
						m_8bitInputValue = m_batchInputValueList[m_batchCurrentValueIndex];
						update8BitInputLEDs();
					}
					sendInputStatesToRemoteFPGA();

					// Clear input buffer to ensure proper sync
					m_socket->clearIncomingData();

					// Send request for all output states
					m_socket->writeLine("L\r");
					m_timeoutTimer->start(FPGA_COMM_TIMEOUT_MS, TRUE);
					m_commHandlerState = 2;
					POLL_FOR_DATA_IMMEDIATE
				}
			}
			else if (m_commHandlerState == 2) {
				// Get all data
				if (m_socket->bytesAvailable() >= 42) {
					if (m_socket->bytesAvailable() == 42) {
						TQString line;
	
						// Process the received data packet
						receiveInputStatesFromRemoteFPGA();
	
						// Write received data to batch output file
						if (m_batchUsing16Bit) {
							line = TQString("%1\n").arg(m_16bitOutputValue);
						}
						else {
							line = TQString("%1\n").arg(m_8bitOutputValue);
						}
						m_batchOutputFile->writeBlock(line.ascii(), line.length());
						m_base->batchTestProgressBar->setProgress(m_batchCurrentValueIndex);
						m_batchCurrentValueIndex++;
	
						m_connectionActiveAndValid = true;
						setStatusMessage(i18n("Running batch test") + "...");
						m_timeoutTimer->start(FPGA_COMM_TIMEOUT_MS, TRUE);
	
						m_commHandlerState = 1;
						POLL_FOR_DATA_IMMEDIATE
					}
					else {
						// Invalid data packet
						// State likely lost in remote debug module (e.g. FPGA reprogrammed)
						// Clear input buffers and allow timeout to occur in order to force resync
						m_socket->clearIncomingData();
					}
				}
				else {
					if (!m_timeoutTimer->isActive()) {
						m_batchOutputFile->flush();
						m_batchOutputFile->close();
						delete m_batchOutputFile;
						m_batchOutputFile = NULL;
						m_base->batchTestProgressBar->reset();
						m_commHandlerMode = 0;
						m_commHandlerState = 0;
						processLockouts();
						UPDATEDISPLAY_TIMEOUT
					}
				}
			}
		}
		else if (m_commHandlerMode == 2) {
			// Data processing mode
			// This detects when the incoming data file is a picture (bmp, png, etc.) and processes it accordingly
			int i;

			if (m_commHandlerState == 0) {
				m_timeoutTimer->stop();
				processLockouts();
				m_base->dataProcessingStatusLabel->setText(i18n("Reading input data") + "...");
				m_batchInputValueList.clear();
				TQFile file(m_base->dataProcessingInputFile->url());
				if (file.open(IO_ReadOnly)) {
					// Is it an image?
					m_dataIsImage = m_dataInputImage.load(m_base->dataProcessingInputFile->url());

					if ((file.size() <= m_dataMemorySize) || ((m_dataIsImage) && ((m_dataInputImage.height()*m_dataInputImage.width()) <= m_dataMemorySize))) {
						m_base->dataProcessingProgressBar->setTotalSteps(0);
						m_base->dataProcessingProgressBar->setProgress(0);

						m_dataOutputFile = new TQFile(m_base->dataProcessingOutputFile->url());
						if (m_dataOutputFile->open(IO_ReadWrite)) {
							if (!m_dataIsImage) {
								m_dataByteArray = file.readAll();
							}
							else {
								// Create grayscale input image buffer
								TQImage displayImage = m_dataInputImage;

								// Populate m_dataByteArray and displayImage with image data
								int x;
								int y;
								unsigned char pixel_value;
								int imageWidth = m_dataInputImage.width();
								m_dataByteArray.resize(m_dataInputImage.height()*imageWidth);
								for (y=0; y<m_dataInputImage.height(); y++) {
									for (x=0; x<m_dataInputImage.width(); x++) {
										pixel_value = tqGray(m_dataInputImage.pixel(x,y));
										m_dataByteArray[(y*imageWidth)+x] = pixel_value;
										displayImage.setPixel(x, y, tqRgb(pixel_value, pixel_value, pixel_value));
									}
								}

								// Show image in GUI
								TQPixmap inputImagePixmap(displayImage);
								TQImage scaledDisplayImage = displayImage;
								scaledDisplayImage = scaledDisplayImage.smoothScale(m_base->ImageInputLabel->width(), m_base->ImageInputLabel->height());
								TQPixmap scaledInputImagePixmap(scaledDisplayImage);
								m_base->ImageInputLabel->setPixmap(scaledInputImagePixmap);
								m_inputImageViewer->setPixmap(inputImagePixmap);
							}

							m_base->dataProcessingStatusLabel->setText(i18n("Transmitting data to FPGA") + "...");

							// Transmit m_dataByteArray to FPGA
							m_socket->writeBlock("M\r", 2);
							m_socket->writeBufferedData();
							int len = m_dataByteArray.size();
							int txlen = m_dataMemorySize;
							if (len >= txlen) {
								len = txlen;
							}
							TQByteArray dataToSend(m_dataMemorySize);
							for (i=0; i<len; i++) {
								dataToSend[i] = m_dataByteArray[i];
							}
							for (; i<txlen; i++) {
								dataToSend[i] = 0;
							}
							m_base->dataProcessingProgressBar->setTotalSteps(txlen*2);
							m_base->dataProcessingProgressBar->setProgress(0);
							int offset = 0;
							while (offset < txlen) {
								m_socket->writeBlock(dataToSend.data()+offset, 1024);
								m_socket->writeBufferedData();
								offset = offset + 1024;
								m_base->dataProcessingProgressBar->setProgress(offset);
							}

							m_timeoutTimer->start(FPGA_DATA_PROCESSING_TIMEOUT_MS, TRUE);

							m_batchCurrentValueIndex = 0;
							m_commHandlerState = 1;
							POLL_FOR_DATA_IMMEDIATE
						}
						else {
							KMessageBox::error(0, i18n("<qt>Unable to open selected data output file</qt>"), i18n("Data Processing Failed"));
							m_commHandlerMode = 0;
							m_commHandlerState = 0;
							m_base->dataProcessingProgressBar->reset();
							processLockouts();
						}
					}
					else {
						KMessageBox::error(0, i18n("<qt>Selected data input file exceeds the maximum allowed size of 16,384 bytes</qt>"), i18n("Data Processing Failed"));
						m_commHandlerMode = 0;
						m_commHandlerState = 0;
						m_base->dataProcessingProgressBar->reset();
						processLockouts();
					}
				}
				else {
					KMessageBox::error(0, i18n("<qt>Unable to open selected data input file</qt>"), i18n("Data Processing Failed"));
					m_commHandlerMode = 0;
					m_commHandlerState = 0;
					m_base->dataProcessingProgressBar->reset();
					processLockouts();
				}
			}
			else if (m_commHandlerState == 1) {
				if (m_socket->bytesAvailable() >= 1) {
					TQByteArray recData(1);
					m_socket->readBlock(recData.data(), 1);

					m_base->dataProcessingStatusLabel->setText(i18n("Waiting for data from FPGA") + "...");

					m_timeoutTimer->start(FPGA_DATA_PROCESSING_TIMEOUT_MS, TRUE);

					m_commHandlerState = 2;
					POLL_FOR_DATA_IMMEDIATE
				}
				else {
					if (!m_timeoutTimer->isActive()) {
						m_dataOutputFile->flush();
						m_dataOutputFile->close();
						delete m_dataOutputFile;
						m_dataOutputFile = NULL;
						m_base->dataProcessingProgressBar->reset();
						m_commHandlerMode = 0;
						m_commHandlerState = 0;
						KMessageBox::error(0, i18n("<qt>Timeout occurred while attempting to transmit data to FPGA</qt>"), i18n("Data Processing Failed"));
						processLockouts();
						UPDATEDISPLAY_TIMEOUT
					}
				}
			}
			else if (m_commHandlerState == 2) {
				if (m_socket->bytesAvailable() >= m_dataMemorySize) {
					TQByteArray recData(m_dataMemorySize);
					int offset = 0;
					while (offset < m_dataMemorySize) {
						m_socket->readBlock(recData.data()+offset, 1024);
						offset = offset + 1024;
					}

					m_base->dataProcessingStatusLabel->setText(i18n("Writing data to file") + "...");
					if (!m_dataIsImage) {
						// Write data straight to file
						m_dataOutputFile->writeBlock(recData);
						m_base->ImageOutputLabel->setPixmap(TQPixmap());
						m_outputImageViewer->setPixmap(TQPixmap());
					}
					else {
						// Write data to image, then write image to file
						TQImage outputImage(m_dataInputImage.width(), m_dataInputImage.height(), m_dataInputImage.depth());
						int x;
						int y;
						unsigned char grey;
						int imageWidth = outputImage.width();
						m_dataByteArray.resize(outputImage.height()*imageWidth);
						for (y=0; y<outputImage.height(); y++) {
							for (x=0; x<outputImage.width(); x++) {
								grey = recData[(y*imageWidth)+x];
								outputImage.setPixel(x, y, tqRgb(grey, grey, grey));
							}
						}
						TQPixmap outputImagePixmap(outputImage);
						TQImage scaledOutputImage = outputImage;
						scaledOutputImage = scaledOutputImage.smoothScale(m_base->ImageOutputLabel->width(), m_base->ImageOutputLabel->height());
						TQPixmap scaledOutputImagePixmap(scaledOutputImage);
						m_base->ImageOutputLabel->setPixmap(scaledOutputImagePixmap);
						m_outputImageViewer->setPixmap(outputImagePixmap);
						outputImage.save(m_dataOutputFile, "PNG");
					}

					m_dataOutputFile->flush();

					// Calculate signature and write metadata to verification file
					m_dataOutputFile->reset();
					TQByteArray writtenFile = m_dataOutputFile->readAll();
					KMD5 writtenMD5(writtenFile);

					TQFile verificationFile(m_base->dataProcessingOutputFile->url() + ".vrf");
					if (verificationFile.open(IO_ReadWrite)) {
						TQCString datastring = writtenMD5.hexDigest();
						datastring.prepend("md5=");
						verificationFile.writeBlock(datastring);
						datastring = (TQString("datetime=%1").arg(TQDateTime::currentDateTime().toString())).ascii();
						verificationFile.writeBlock(datastring);
					}

					m_dataOutputFile->close();
					delete m_dataOutputFile;
					m_dataOutputFile = NULL;
					m_base->dataProcessingProgressBar->reset();
					m_commHandlerMode = 0;
					m_commHandlerState = 0;
					processLockouts();
					POLL_FOR_DATA_IMMEDIATE
				}
				else {
					// Update the GUI with status information
					m_base->dataProcessingProgressBar->setProgress(m_dataMemorySize + m_socket->bytesAvailable());
					if (m_socket->bytesAvailable() > 0) {
						m_base->dataProcessingStatusLabel->setText(i18n("Receiving data from FPGA") + "...");
					}

					if (!m_timeoutTimer->isActive()) {
						m_dataOutputFile->flush();
						m_dataOutputFile->close();
						delete m_dataOutputFile;
						m_dataOutputFile = NULL;
						m_base->dataProcessingProgressBar->reset();
						m_commHandlerMode = 0;
						m_commHandlerState = 0;
						KMessageBox::error(0, i18n("<qt>Timeout occurred while attempting to receive data from FPGA</qt>"), i18n("Data Processing Failed"));
						processLockouts();
						UPDATEDISPLAY_TIMEOUT
					}
				}
			}
		}
	}
	else {
		m_commHandlerState = 0;
		m_commHandlerMode = 0;
	}
}

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

} //namespace RemoteLab

#include "part.moc"
