/***************************************************************************
 *   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 "flipflop.h"
#include "icndocument.h"
#include "logic.h"
#include "libraryitem.h"
#include "node.h"
#include "simulator.h"

#include <kiconloader.h>
#include <klocale.h>
#include <qpainter.h>


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

LibraryItem* ECDFlipFlop::libraryItem()
{
	return new LibraryItem(
		"ec/d_flipflop",
		i18n("D Flip-Flop"),
		i18n("Integrated Circuits"),
		"ic3.png",
		LibraryItem::lit_component,
		ECDFlipFlop::construct );
}

ECDFlipFlop::ECDFlipFlop( ICNDocument *icnDocument, bool newItem, const char *id )
	: Component( icnDocument, newItem, (id) ? id : "d_flipflop" )
{
	m_name = i18n("D-Type Flip-Flop");
	m_desc = i18n("The output state is set from the input state when the clock is pulsed.");
	
	setSize( -32, -24, 64, 48 );

	init2PinLeft( -8, 8 );
	init2PinRight( -8, 8 );
	
	m_prevD[0] = m_prevD[1] = false;
	m_whichPrevD = 0;
	m_prevDSimTime = 0;
	m_pSimulator = Simulator::self();
	
	m_bPrevClock = false;
	m_pD = createLogicIn( m_pNNode[0] );
	m_pClock = createLogicIn( m_pNNode[1] );
	m_pQ = createLogicOut( m_pPNode[0], false );
	m_pQBar = createLogicOut( m_pPNode[1], false );
	
	setp = createLogicIn( createPin( 0, -32, 90, "set" ) );
	rstp = createLogicIn( createPin( 0, 32, 270, "rst" ) );
	
	addDisplayText( "D",	QRect( -32,	-16,	20, 16 ), "D" );
	addDisplayText( ">",	QRect( -32,	0,		20, 16 ), ">" ); 
	addDisplayText( "Q",	QRect( 12,	-16,	20, 16 ), "Q" );
	addDisplayText( "Q'",	QRect( 12,	0,		20, 16 ), "Q'" );
	addDisplayText( "Set",	QRect( -16,	-20,	32, 16 ), "Set" );
	addDisplayText( "Rst",	QRect( -16,	4,		32, 16 ), "Rst" );
	
	m_pD->setCallback( this, (CallbackPtr)(&ECDFlipFlop::inputChanged) );
	m_pClock->setCallback( this, (CallbackPtr)(&ECDFlipFlop::clockChanged) );
	setp->setCallback( this, (CallbackPtr)(&ECDFlipFlop::asyncChanged) );
	rstp->setCallback( this, (CallbackPtr)(&ECDFlipFlop::asyncChanged) );
	
	inStateChanged(false);
}

ECDFlipFlop::~ECDFlipFlop()
{
}

void ECDFlipFlop::asyncChanged(bool)
{
	bool set = setp->isHigh();
	bool rst = rstp->isHigh();
	if(set || rst)
	{
		m_pQ->setHigh(set);
		m_pQBar->setHigh(rst);
	}
}

void ECDFlipFlop::inputChanged( bool newState )
{
	unsigned long long simTime = m_pSimulator->time();
	if ( (simTime == m_prevDSimTime) && (newState == m_prevD[m_whichPrevD]) )
		return;
	
	m_prevDSimTime = simTime;
	m_whichPrevD = 1-m_whichPrevD;
	m_prevD[m_whichPrevD] = newState;
}

void ECDFlipFlop::clockChanged( bool newState )
{
	bool set = setp->isHigh();
	bool rst = rstp->isHigh();
	
	bool fallingEdge = m_bPrevClock && !newState;
	m_bPrevClock = newState;
	
	if( set || rst ) return;
	
	if (fallingEdge)
	{
		unsigned long long simTime = m_pSimulator->time();
		bool d = ( simTime == m_prevDSimTime ) ? m_prevD[1-m_whichPrevD] : m_prevD[m_whichPrevD];
		
		m_pQ->setHigh(d);
		m_pQBar->setHigh(!d);
	}
}
	
void ECDFlipFlop::inStateChanged(bool)
{
	// Only called when the flipflop is created.
	m_pQ->setHigh(false);
	m_pQBar->setHigh(true);
}
//END class ECDFlipFlop


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

LibraryItem* ECJKFlipFlop::libraryItem()
{
	return new LibraryItem(
		QString::QString("ec/jk_flipflop"),
		i18n("JK Flip-Flop"),
		i18n("Integrated Circuits"),
		"ic3.png",
		LibraryItem::lit_component,
		ECJKFlipFlop::construct );
}

ECJKFlipFlop::ECJKFlipFlop( ICNDocument *icnDocument, bool newItem, const char *id )
	: Component( icnDocument, newItem, (id) ? id : "jk_flipflop" )
{
	m_name = i18n("JK-Type Flip-Flop");
	m_desc = i18n("The output state is set according to J and K when the clock is pulsed.");
	
	setSize( -32, -32, 64, 64 );

	init3PinLeft( -16, 0, 16 );
	init2PinRight( -16, 16 );
	
	m_pJ = createLogicIn( m_pNNode[0] );
	m_pClock = createLogicIn( m_pNNode[1] );
	m_pK = createLogicIn( m_pNNode[2] );
	
	m_pQ = createLogicOut( m_pPNode[0], false );
	m_pQBar = createLogicOut( m_pPNode[1], false );

	setp = createLogicIn( createPin( 0, -40, 90, "set" ) );
	rstp = createLogicIn( createPin( 0, 40, 270, "rst" ) );
	
	addDisplayText( "J",	QRect( -32,	-24,	20, 16 ), "J" );
	addDisplayText( ">",	QRect( -32,	-8,		20, 16 ), ">" );
	addDisplayText( "K",	QRect( -32,	8,		20, 16 ), "K" ); 
	addDisplayText( "Q",	QRect( 12,	-24,	20, 16 ), "Q" );
	addDisplayText( "Q'",	QRect( 12,	8,		20, 16 ), "Q'" );
	addDisplayText( "Set",	QRect( -16,	-28,	32, 16 ), "Set" );
	addDisplayText( "Rst",	QRect( -16,	12,		32, 16 ), "Rst" );
		
	m_pClock->setCallback( this, (CallbackPtr)(&ECJKFlipFlop::clockChanged) );
	setp->setCallback( this, (CallbackPtr)(&ECJKFlipFlop::asyncChanged) );
	rstp->setCallback( this, (CallbackPtr)(&ECJKFlipFlop::asyncChanged) );
	
	inStateChanged(false);
}

ECJKFlipFlop::~ECJKFlipFlop()
{
}

void ECJKFlipFlop::clockChanged(bool newvalue) 
{
	bool j = m_pJ->isHigh();
	bool k = m_pK->isHigh();
	bool set = setp->isHigh();
	bool rst = rstp->isHigh();
	
	if( set || rst ) return;
	
// a JK flip-flop change state when clock do 1->0
	if (!newvalue && (j || k)) {
		if ( j && k ) {
			m_pQ->setHigh(!prev_state);
			m_pQBar->setHigh(prev_state);
			prev_state = !prev_state;
		} else {
			// (J=1 && K=0) || (J=0 && K=1)
			m_pQ->setHigh(j);
			m_pQBar->setHigh(k);
			prev_state = j;
		}	
	}
}

void ECJKFlipFlop::asyncChanged(bool)
{
	bool set = setp->isHigh();
	bool rst = rstp->isHigh();
	
	if (set || rst) {
		m_pQ->setHigh(set);
		m_pQBar->setHigh(rst);
		prev_state = set;
	}
}
	
void ECJKFlipFlop::inStateChanged(bool)
{
	m_pQBar->setHigh(true);
	m_pQ->setHigh(false);
	prev_state = false;
}
//END class ECJKFlipFlop


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

LibraryItem* ECSRFlipFlop::libraryItem()
{
	return new LibraryItem(
		QString::QString("ec/sr_flipflop"),
		i18n("SR Flip-Flop"),
		i18n("Integrated Circuits"),
		"ic3.png",
		LibraryItem::lit_component,
		ECSRFlipFlop::construct );
}

ECSRFlipFlop::ECSRFlipFlop( ICNDocument *icnDocument, bool newItem, const char *id )
	: Component( icnDocument, newItem, (id) ? id : "sr_flipflop" )
{
	m_name = i18n("SR Flip-Flop");
	m_desc = i18n("The output is made high by holding <i>set</i> high, and low by holding <i>reset</i> high.");
	
	setSize( -24, -24, 48, 48 );

	init2PinLeft( -8, 8 );
	init2PinRight( -8, 8 );
	
	m_pS = createLogicIn( m_pNNode[0] );
	m_pR = createLogicIn( m_pNNode[1] );
	m_pQ = createLogicOut( m_pPNode[0], true );
	m_pQBar = createLogicOut( m_pPNode[1], false );
	
	old_q1 = true;
	old_q2 = false;
	m_pQ->setHigh(old_q1);
	m_pQBar->setHigh(old_q2);
	
	addDisplayText( "S", QRect( -24, -16, 20, 16 ), "S" );
	addDisplayText( "R", QRect( -24, 0, 20, 16 ), "R" );
	addDisplayText( "Q", QRect( 4, -16, 20, 16 ), "Q" );
	addDisplayText( "Q'", QRect( 4, 0, 20, 16 ), "Q'" );
	
	m_pS->setCallback( this, (CallbackPtr)(&ECSRFlipFlop::inStateChanged) );
	m_pR->setCallback( this, (CallbackPtr)(&ECSRFlipFlop::inStateChanged) );
	m_pQ->setCallback( this, (CallbackPtr)(&ECSRFlipFlop::inStateChanged) );
	m_pQBar->setCallback( this, (CallbackPtr)(&ECSRFlipFlop::inStateChanged) );
}

ECSRFlipFlop::~ECSRFlipFlop()
{
}

void ECSRFlipFlop::inStateChanged(bool)
{
	// Q = v_q1, Q-bar = v_q2
	bool new_q1 = false;
	bool new_q2 = false;
	
	bool s = m_pS->isHigh();
	bool r = m_pR->isHigh();
	bool q1 = m_pQ->isHigh();
	bool q2 = m_pQBar->isHigh();
	
	// Easy ones to do :-)
	if (!q1) new_q2 = true;
	if (!q2) new_q1 = true;
	
	if ( q1 && q2 )
	{
		if ( s && !r )
		{
			new_q1 = true;
			new_q2 = false;
		}
		else if ( !s && r )
		{
			new_q1 = false;
			new_q2 = true;
		}
		else if ( s && r )
		{
			new_q1 = old_q1;
			new_q2 = old_q2;
		}
		else if ( !s && !r )
		{
			new_q1 = false;
			new_q2 = false;
		}
	}
	else if ( q1 && !q2 )
	{
		// Note: We only need to set the value of v_q2
		if ( r && !s ) new_q2 = true;
		else new_q2 = false;
	}
	else if ( !q1 && q2 )
	{
		// Note: We only need to set the value of v_q1
		if ( s && !r ) new_q1 = true;
		else new_q1 = false;
	}
	
	old_q1 = new_q1;
	old_q2 = new_q2;
	
	m_pQ->setHigh(new_q1);
	m_pQBar->setHigh(new_q2);
}
//END class ECSRFlipFlop
