/*
 * Remote Laboratory Seven Segment Display Widget
 *
 * 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 <stdlib.h>

#include <tqpainter.h>

#include "sevensegment.h"

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

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

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

unsigned char Display7Segment::segmentsForNumericDigit(unsigned char val, bool dp) {
	unsigned char ret = 0;
	if      (val == 0x0)  { ret = 0x7e; }
	else if (val == 0x1)  { ret = 0x30; }
	else if (val == 0x2)  { ret = 0x6d; }
	else if (val == 0x3)  { ret = 0x79; }
	else if (val == 0x4)  { ret = 0x33; }
	else if (val == 0x5)  { ret = 0x5b; }
	else if (val == 0x6)  { ret = 0x5f; }
	else if (val == 0x7)  { ret = 0x70; }
	else if (val == 0x8)  { ret = 0x7f; }
	else if (val == 0x9)  { ret = 0x73; }
	else if (val == 0xa)  { ret = 0x7d; }
	else if (val == 0xb)  { ret = 0x1f; }
	else if (val == 0xc)  { ret = 0x0d; }
	else if (val == 0xd)  { ret = 0x3d; }
	else if (val == 0xe)  { ret = 0x4f; }
	else if (val == 0xf)  { ret = 0x67; }
	else if (val == 0x10) { ret = 0x01; }	// "-"

	if (dp) {
		ret |= 0x80;
	}

	return ret;
}

void Display7Segment::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 Display7Segment::sizeHint() const {
	return TQSize(10 + 9 * (1 + (smallPoint ? 0 : 1)), 23);
}

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

Display7Segment::SegmentStyle Display7Segment::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 Display7Segment::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 Display7Segment::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( "Display7Segment::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( "Display7Segment::drawSegment: (%s) Internal error."
					"  Illegal segment id: %d\n",
					name( "unnamed" ), segmentNo );
#endif
		}
	}

#undef LINETO
#undef LIGHT
#undef DARK
}

Display7SegmentArray::Display7SegmentArray( TQWidget *parent, const char *name )
	: TQFrame( parent, name ),
	m_numberOfDigits(0),
	m_segmentStyle(Flat)
{
	init();
}

Display7SegmentArray::~Display7SegmentArray() {
	unsigned int i;

	// Delete displays
	for (i=0; i < m_numberOfDigits; i++) {
		delete m_displayArray[i];
	}
	delete m_displayArray;
	m_displayArray = NULL;
}

void Display7SegmentArray::init() {
	unsigned int i;

	// Set up grid layout
	m_layout = new TQGridLayout(this, 1, m_numberOfDigits + 1);
	TQSpacerItem *spacerItem = new TQSpacerItem(0, 0, TQSizePolicy::Expanding, TQSizePolicy::Fixed);
	m_layout->addItem(spacerItem);
	m_layout->setAutoAdd(true);

	// Allocate display array
	m_displayArray = (Display7Segment**)malloc(sizeof(Display7Segment*) * m_numberOfDigits);

	// Create displays
	for (i=0; i < m_numberOfDigits; i++) {
		m_displayArray[i] = new Display7Segment(this);
	}

	setSizePolicy(TQSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Minimum));

	// Set up displays
	for (i=0; i < m_numberOfDigits; i++) {
		m_displayArray[i]->setBackgroundColor(TQt::black);
		m_displayArray[i]->setPaletteForegroundColor(TQColor(0, 255, 64));
	}

	// The LED display can work one of two ways
#if 0
	// Separated segments
	for (i=0; i < m_numberOfDigits; i++) {
		m_displayArray[i]->setFrameStyle(TQFrame::Box | TQFrame::Raised);
	}
	setFrameStyle(TQFrame::NoFrame);
#else
	// Combined segments
	for (i=0; i < m_numberOfDigits; i++) {
		m_displayArray[i]->setFrameStyle(TQFrame::NoFrame);
	}
	setBackgroundColor(TQt::black);
	setFrameStyle(TQFrame::Box);
#endif
}

void Display7SegmentArray::setNumberOfDigits(unsigned int count) {
	unsigned int i;

	if (m_numberOfDigits != count) {
		// Delete displays
		if (m_displayArray) {
			for (i=0; i < m_numberOfDigits; i++) {
				delete m_displayArray[i];
			}
			delete m_displayArray;
			m_displayArray = NULL;
		}

		// Destroy layout
		if (m_layout) {
			delete m_layout;
		}

		// Set display count
		m_numberOfDigits = count;

		// Reinitialize
		init();
	}
}

void Display7SegmentArray::setSegmentStyle(SegmentStyle s) {
	unsigned int i;

	m_segmentStyle = s;
	for (i=0; i < m_numberOfDigits; i++) {
		m_displayArray[i]->setSegmentStyle((Display7Segment::SegmentStyle)m_segmentStyle);
	}
}

Display7SegmentArray::SegmentStyle Display7SegmentArray::segmentStyle() const {
	return m_segmentStyle;
}

void Display7SegmentArray::setValue(double value, int maxDecimalLength, bool forceMinDecimalLength) {
	bool dp;
	unsigned int i;
	TQString displayString = TQString("%1").arg(value);
	if (displayString.contains("e")) {
		maxDecimalLength = -1;
	}
	if (maxDecimalLength >= 0) {
		int decPos = displayString.find(".");
		if (decPos >= 0) {
			int decLen = (displayString.length() - 1) - decPos;
			if (decLen > maxDecimalLength) {
				displayString.truncate(displayString.length() - (decLen - maxDecimalLength));
			}
		}
		if (forceMinDecimalLength) {
			if (decPos < 0) {
				displayString.append(".");
				decPos = displayString.length() - 1;
			}
			for (int i = displayString.length(); i < decPos + maxDecimalLength + 1; i++) {
				displayString.append("0");
			}
		}
	}
	int last_char = displayString.length();
	int current_char = (last_char - (int)m_numberOfDigits);
	if (displayString.contains(".")) {
		current_char--;
	}
	if (displayString.length() > m_numberOfDigits) {
		// String is too large to display on the specified number of 7-segment digits
		for (i=0; i < m_numberOfDigits; i++) {
			m_displayArray[i]->setLitSegments(Display7Segment::segmentsForNumericDigit(0x10, false));
		}
	}
	else {
		for (i=0; i < m_numberOfDigits; i++) {
			if (current_char < 0) {
				m_displayArray[i]->setLitSegments(0x0);
			}
			else {
				dp = false;
				if (current_char < (displayString.length() - 1)) {
					if (displayString[current_char + 1] == '.') {
						dp = true;
					}
				}
				if (displayString[current_char] == '.') {
					current_char++;
				}
				if (displayString[current_char] == '-') {
					m_displayArray[i]->setLitSegments(Display7Segment::segmentsForNumericDigit(0x10, dp));
				}
				else if (displayString[current_char] == 'e') {
					m_displayArray[i]->setLitSegments(Display7Segment::segmentsForNumericDigit(0xe, dp));
				}
				else {
					m_displayArray[i]->setLitSegments(Display7Segment::segmentsForNumericDigit(TQString(displayString[current_char]).toInt(NULL, 10), dp));
				}
			}
			current_char++;
		}
	}
}

#include "sevensegment.moc"