/***************************************************************************
 *   Copyright (C) 2005 by David Saxton                                    *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "colorcombo.h"
#include "diode.h"
#include "ecled.h"
#include "ecnode.h"
#include "libraryitem.h"
#include "matrixdisplay.h"
#include "simulator.h"

#include <kdebug.h>
#include <klocale.h>
#include <qpainter.h>
#include <qstring.h>

Item* MatrixDisplay::construct( ItemDocument *itemDocument, bool newItem, const char *id )
{
	return new MatrixDisplay( (ICNDocument*)itemDocument, newItem, id );
}


LibraryItem* MatrixDisplay::libraryItem()
{
	return new LibraryItem(
		"ec/matrix_display",
		i18n("Matrix Display"),
		i18n("Outputs"),
		"matrixdisplay.png",
		LibraryItem::lit_component,
		MatrixDisplay::construct );
}


MatrixDisplay::MatrixDisplay( ICNDocument *icnDocument, bool newItem, const char *id )
	: Component( icnDocument, newItem, id ? id : "matrix_display" )
{
	m_name = i18n("Matrix Display");
	m_desc = i18n("A matrix display of LEDs with a configurable number of columns and rows.");
	m_bDynamicContent = true;
	
	//BEGIN Reset members
	for ( unsigned i = 0; i < max_md_height; i++ )
		m_pRowNodes[i] = 0l;
	for ( unsigned i = 0; i < max_md_width; i++ )
		m_pColNodes[i] = 0l;
	
	m_lastUpdatePeriod = 0.0;
	m_r = m_g = m_b = 0.0;
	m_bRowCathode = true;
	m_numRows = 0;
	m_numCols = 0;
	//END Reset members
	
	createProperty( "0-rows", Variant::Type::Int );
	property("0-rows")->setCaption( i18n("Rows") );
	property("0-rows")->setMinValue(1);
	property("0-rows")->setMaxValue(max_md_height);
	property("0-rows")->setValue(7);
	
	createProperty( "1-cols", Variant::Type::Int );
	property("1-cols")->setCaption( i18n("Columns") );
	property("1-cols")->setMinValue(1);
	property("1-cols")->setMaxValue(max_md_width);
	property("1-cols")->setValue(5);
	
	createProperty( "color", Variant::Type::Color );
	property("color")->setCaption( i18n("Color") );
	property("color")->setColorScheme( ColorCombo::LED );
	
	createProperty( "diode-configuration", Variant::Type::Select );
	property("diode-configuration")->setCaption( i18n("Configuration") );
	property("diode-configuration")->setAllowed( QStringList::split(',',"Row Cathode,Column Cathode") );
	property("diode-configuration")->setValue("Row Cathode");
	property("diode-configuration")->setAdvanced(true);
}


MatrixDisplay::~MatrixDisplay()
{
}


void MatrixDisplay::dataChanged()
{
	QColor color = dataColor("color");
	m_r = double(color.red())	/ double(0x100);
	m_g = double(color.green())	/ double(0x100);
	m_b = double(color.blue())	/ double(0x100);
	
	int numRows = dataInt("0-rows");
	int numCols = dataInt("1-cols");
	
	bool ledsChanged = (numRows != int(m_numRows)) || (numCols != int(m_numCols));
	
	if (ledsChanged)
		initPins( numRows, numCols );
	
	bool rowCathode = dataString("diode-configuration") == "Row Cathode";
	if ( (rowCathode != m_bRowCathode) || ledsChanged)
	{
		m_bRowCathode = rowCathode;
		
		for ( unsigned i = 0; i < m_numCols; i++ )
		{
			for ( unsigned j = 0; j < m_numRows; j++ )
			{
				removeElement( m_pDiodes[i][j], false );
				if (rowCathode)
					m_pDiodes[i][j] = createDiode( m_pColNodes[i], m_pRowNodes[j] );
				else
					m_pDiodes[i][j] = createDiode( m_pRowNodes[j], m_pColNodes[i] );
			}
		}
	}
}


void MatrixDisplay::initPins( unsigned numRows, unsigned numCols )
{
	if ( (numRows == m_numRows) && (numCols == m_numCols) )
		return;
	
	if ( numRows > max_md_height )
		numRows = max_md_height;
	
	if ( numCols > max_md_width )
		numCols = max_md_width;
	
	m_lastUpdatePeriod = 0.0;
	
	//BEGIN Remove diodes
	// All the diodes are going to be readded from dataChanged (where this
	// function is called from), so easiest just to delete the diodes now and
	// resize.
	
	for ( unsigned i = 0; i < m_numCols; i++ )
	{
		for ( unsigned j = 0; j < m_numRows; j++ )
			removeElement( m_pDiodes[i][j], false );
	}
	
	m_avgBrightness.resize(numCols);
	m_lastBrightness.resize(numCols);
	m_pDiodes.resize(numCols);
	
	for ( unsigned i = 0; i < numCols; i++ )
	{
		m_avgBrightness[i].resize(numRows);
		m_lastBrightness[i].resize(numRows);
		m_pDiodes[i].resize(numRows);
		
		for ( unsigned j = 0; j < numRows; j++ )
		{
			m_avgBrightness[i][j] = 0.0;
			m_lastBrightness[i][j] = 255;
			m_pDiodes[i][j] = 0l;
		}
			
	}
	//END Remove diodes
	
	
	//BEGIN Create or destroy pins
	if ( numCols >= m_numCols )
	{
		for ( unsigned i = m_numCols; i < numCols; i++ )
			m_pColNodes[i] = createPin( 0, 0, 270, colPinID(i) );
	}
	else
	{
		for ( unsigned i = numCols; i < m_numCols; i++ )
		{
			removeNode( colPinID(i) );
			m_pColNodes[i] = 0l;
		}
	}
	m_numCols = numCols;
	
	if ( numRows >= m_numRows )
	{
		for ( unsigned i = m_numRows; i < numRows; i++ )
			m_pRowNodes[i] = createPin( 0, 0, 0, rowPinID(i) );
	}
	else
	{
		for ( unsigned i = numRows; i < m_numRows; i++ )
		{
			removeNode( rowPinID(i) );
			m_pRowNodes[i] = 0l;
		}
	}
	m_numRows = numRows;
	//END Create or destroy pins
	
	
	//BEGIN Position pins et al
	setSize( -int(numCols+1)*8, -int(numRows+1)*8, int(numCols+1)*16, int(numRows+1)*16, true );
	
	for ( int i = 0; i < int(m_numCols); i++ )
	{
		m_nodeMap[colPinID(i)].x = offsetX() + 16 + 16*i;
		m_nodeMap[colPinID(i)].y = offsetY() + height() + 8;
	}
	
	for ( int i = 0; i < int(m_numRows); i++ )
	{
		m_nodeMap[rowPinID(i)].x = offsetX() - 8;
		m_nodeMap[rowPinID(i)].y = offsetY() + 16 + 16*i;
	}
	
	updateAttachedPositioning();
	//END Position pins et al
}


QString MatrixDisplay::colPinID( int col ) const
{
	return QString("col_%1").arg(QString::number(col));
}
QString MatrixDisplay::rowPinID( int row ) const
{
	return QString("row_%1").arg(QString::number(row));
}


void MatrixDisplay::stepNonLogic()
{
	double interval = 1./LINEAR_UPDATE_RATE;
	
	for ( unsigned i = 0; i < m_numCols; i++ )
	{
		for ( unsigned j = 0; j < m_numRows; j++ )
			m_avgBrightness[i][j] += ECLed::brightness( m_pDiodes[i][j]->current() )*interval;
	}
	
	m_lastUpdatePeriod += interval;
}


void MatrixDisplay::drawShape( QPainter &p )
{
	if ( isSelected() )
		p.setPen(m_selectedCol);
	p.drawRect( boundingRect() );
	
	initPainter(p);
	
	const int _x = int(x()+offsetX());
	const int _y = int(y()+offsetY());
	
	// To avoid flicker, require at least a 10 ms sample before changing
	// the brightness
	double minUpdatePeriod = 0.0099;
	
	for ( int i = 0; i < int(m_numCols); i++ )
	{
		for ( int j = 0; j < int(m_numRows); j++ )
		{
			if ( m_lastUpdatePeriod > minUpdatePeriod )
				m_lastBrightness[i][j] = unsigned(m_avgBrightness[i][j]/m_lastUpdatePeriod);
			
			double _b = m_lastBrightness[i][j];
			
			QColor brush = QColor( uint(255-(255-_b)*(1-m_r)), uint(255-(255-_b)*(1-m_g)), uint(255-(255-_b)*(1-m_b)) );
			p.setBrush(brush);
			p.setPen( Qt::NoPen );
			p.drawEllipse( _x+10+i*16, _y+10+j*16, 12, 12 );
		}
	}
	
	if ( m_lastUpdatePeriod > minUpdatePeriod )
	{
		m_lastUpdatePeriod = 0.0;
	
		for ( unsigned i = 0; i < m_numCols; i++ )
		{
			for ( unsigned j = 0; j < m_numRows; j++ )
				m_avgBrightness[i][j] = 0.0;
		}
	}
	
	deinitPainter(p);
}
