#include "digirule1.h"

#define AUTO_CLOCK_TIME		250		// in milliseconds

///////////////////////////////////////////////////////////////////////////////
// private data

static const uint8 binary_lut[2][16] =
{
	{ 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 },	// down
	{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 },	// up
};

static const uint8 gray_lut[2][16] =
{
	{ 8, 0, 3, 1, 5, 7, 2, 6, 9, 11, 14, 10, 4, 12, 15, 13 },	// down
	{ 1, 3, 6, 2, 12, 4, 7, 5, 0, 8, 11, 9, 13, 15, 10, 14 },	// up
};

static const uint8 (*lut)[16];		// pointer to one of the above tables

static enum
{
	AutoClockStateManual,
	AutoClockStateDown,
	AutoClockStateDownCancel,
	AutoClockStateUp,
	AutoClockStateUpCancel,
} auto_clock_state;

static uint16 timer;

static void x( void );
static void q( bool_t n );

///////////////////////////////////////////////////////////////////////////////
// public functions

void mode_standard_init( counter_mode_t mode )
{
	all_leds_set( 0 );
	NOT_LED = X_LED = SR_LED = QNOT_LED = 1;
	lut = (mode == CounterModeGray) ? gray_lut : binary_lut;
	auto_clock_state = AutoClockStateManual;
}

void mode_standard_process( void )
{
	/////////////////////////////////////////////
	// gate

	if( button.gate_select.pressed )
	{
		A_LED = B_LED = 0;		// reset inputs
		
		if( NOT_LED )	// select next gate type
		{
			NOT_LED = 0;
			OR_LED = 1;
		}
		else if( OR_LED )
		{
			OR_LED = 0;
			AND_LED = 1;
		}
		else if( AND_LED )
		{
			AND_LED = 0;
			XOR_LED = 1;
		}
		else if( XOR_LED )
		{
			XOR_LED = 0;
			NOR_LED = 1;
		}
		else if( NOR_LED )
		{
			NOR_LED = 0;
			NAND_LED = 1;
		}
		else if( NAND_LED )
		{
			NAND_LED = 0;
			XNOR_LED = 1;
		}
		else if( XNOR_LED )
		{
			XNOR_LED = 0;
			NOT_LED = 1;
		}

		x();
	}
	else if( button.a.pressed )
	{
		A_LED ^= 1;
		x();
	}
	else if( button.b.pressed && !NOT_LED )		// B input disabled for NOT gate
	{
		B_LED ^= 1;
		x();
	}

	/////////////////////////////////////////////
	// flip flop

	if( button.ff_select.pressed )
	{
		SJTD_LED = RK_LED = 0;		// reset inputs
		
		if( SR_LED )	// select next flip-flop type
		{
			SR_LED = 0;
			JK_LED = 1;
		}
		else if( JK_LED )
		{
			JK_LED = 0;
			T_LED = 1;
		}
		else if( T_LED )
		{
			T_LED = 0;
			D_LED = 1;
		}
		else if( D_LED )
		{
			D_LED = 0;
			SR_LED = 1;
		}

		q( 0 );		// reset flip-flop
	}
	
	if( SR_LED )
	{
		if( button.sjtd.pressed )
			SJTD_LED ^= 1;
		
		else if( button.rk.pressed )
			RK_LED ^= 1;
		
#if SR_GATED_LATCH
		else if( button.clk.pressed )
			CLK_LED = 1;
		
		else if( button.clk.released )
		{
			CLK_LED = 0;
			
			if( Q_LED && QNOT_LED )				// if gate closing and outputs invalid
				q( ticks );						// randomly set or reset (simulate race condition)
		}
		
		if( button.clk.state )					// if gate open
		{
			if( SJTD_LED && RK_LED )			// invalid
				Q_LED = QNOT_LED = 1;

			else if( SJTD_LED && !RK_LED )		// set
				q( 1 );

			else if( !SJTD_LED && RK_LED )		// reset
				q( 0 );
		}		
#else
		else if( button.clk.pressed )
		{
			CLK_LED = 1;
			
			if( SJTD_LED && RK_LED )			// invalid
				Q_LED = QNOT_LED = 0;
			
			else if( SJTD_LED && !RK_LED )		// set
				q( 1 );
			
			else if( !SJTD_LED && RK_LED )		// reset
				q( 0 );
		}
		else if( button.clk.released )
			CLK_LED = 0;
#endif
	}
	else if( JK_LED )
	{
		if( button.sjtd.pressed )
			SJTD_LED ^= 1;

		else if( button.rk.pressed )
			RK_LED ^= 1;
		
		else if( button.clk.pressed )
		{
			CLK_LED = 1;
			
			if( SJTD_LED && RK_LED )			// toggle
				q( ~Q_LED );

			else if( SJTD_LED && !RK_LED )		// set
				q( 1 );

			else if( !SJTD_LED && RK_LED )		// reset
				q( 0 );
		}
		else if( button.clk.released )
			CLK_LED = 0;
	}
	else if( T_LED )
	{
		if( button.sjtd.pressed )
			SJTD_LED ^= 1;

		else if( button.clk.pressed )
		{
			CLK_LED = 1;
			
			if( SJTD_LED )		// toggle
				q( ~Q_LED );
		}
		else if( button.clk.released )
			CLK_LED = 0;
	}
	else if( D_LED )
	{
		if( button.sjtd.pressed )
			SJTD_LED ^= 1;

		else if( button.clk.pressed )
		{
			CLK_LED = 1;
			q( SJTD_LED );
		}
		else if( button.clk.released )
			CLK_LED = 0;
	}
	
	/////////////////////////////////////////////
	// 4-bit counter

	switch( auto_clock_state )
	{
		case AutoClockStateManual:
			if( button.down.pressed )
				ctr_leds_set( lut[0][ctr_leds_get()] );

			else if( button.up.pressed )
				ctr_leds_set( lut[1][ctr_leds_get()] );
			
			else if( button.down.held1 )
			{
				timer = 0;
				auto_clock_state = AutoClockStateDown;
			}
			else if( button.up.held1 )
			{
				timer = 0;
				auto_clock_state = AutoClockStateUp;
			}
			
			break;
			
		/////////////////////////////////////////
			
		case AutoClockStateDown:
			if( button.down.pressed )
				auto_clock_state = AutoClockStateDownCancel;
			
			else if( button.up.pressed )
				auto_clock_state = AutoClockStateManual;
			
			else if( ++timer == AUTO_CLOCK_TIME )
			{
				ctr_leds_set( lut[0][ctr_leds_get()] );
				timer = 0;
			}
			
			break;
			
		case AutoClockStateDownCancel:
			if( button.down.released )
				auto_clock_state = AutoClockStateManual;
			
			break;
			
		/////////////////////////////////////////
			
		case AutoClockStateUp:
			if( button.up.pressed )
				auto_clock_state = AutoClockStateUpCancel;
			
			else if( button.down.pressed )
				auto_clock_state = AutoClockStateManual;
			
			else if( ++timer == AUTO_CLOCK_TIME )
			{
				ctr_leds_set( lut[1][ctr_leds_get()] );
				timer = 0;
			}
			
			break;
			
		case AutoClockStateUpCancel:
			if( button.up.released )
				auto_clock_state = AutoClockStateManual;
			
			break;
	}
}

///////////////////////////////////////////////////////////////////////////////
// private functions

static void x( void )
{
	if( NOT_LED )
		X_LED = ~A_LED;
	
	else if( OR_LED )
		X_LED = A_LED | B_LED;
	
	else if( AND_LED )
		X_LED = A_LED & B_LED;
				
	else if( XOR_LED )
		X_LED = A_LED ^ B_LED;
				
	else if( NOR_LED )
		X_LED = ~(A_LED | B_LED);
	
	else if( NAND_LED )
		X_LED = ~(A_LED & B_LED);
				
	else if( XNOR_LED )
		X_LED = ~(A_LED ^ B_LED);
}

static void q( bool_t n )
{
	Q_LED = n;
	QNOT_LED = ~Q_LED;
}
