#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define bool int
#define byte unsigned char
#define numChars  32

// Here we are using global variables simply to be compatible with the Arduino program structure
// This is REALLY bad practice so please don't do it otherwise!
long int count = 0;
long int error = 0;

enum states {state1=1, state2, state3, state4};
bool channelAState=0;
bool channelBState=0;

enum states state;
char receivedChars[numChars]="00";   // an array to store the received data

void initialiseEncoderStateMachine();
void updateEncoderStateMachine();
enum states get_current_state();

int main()
{
    // Replicates the setup in Arduino
    printf("Enter a pair of characters representing initial states of channels A and B\n");
    scanf("%s",receivedChars);
    channelAState = receivedChars[0]!='0';
    channelBState = receivedChars[1]!='0';

    initialiseEncoderStateMachine();
    printf("State %d, count %ld, error %ld\n", state, count, error);
    printf("Enter a pair of characters representing channels A and B, enter 99 to end\n");

    // Replicates the loop in Arduino
    do
    {
        scanf("%s",receivedChars);
        if (strcmp(receivedChars, "99")==0)
        {
            break;
        }
        channelAState = receivedChars[0]!='0';
        channelBState = receivedChars[1]!='0';
        updateEncoderStateMachine();
        printf("State %d, count %ld, error %ld\n", state, count, error);

    }
    while(1);
    return 0;
}


// Could be implemented as a C macro, which would be faster
// but I've forgotten how to define C macros
enum states get_current_state()
{
	/* If A is 0 and B is 0, system is in State 1
	   If A is 1 and B is 0, system is in State 2
	   If A is 1 and B is 1, system is in State 3
	   If A is 0 and B is 1, system is in State 4 */

	if (channelAState    && channelBState)    return state3;
	if (channelAState    && (!channelBState)) return state2;
	if ((!channelAState) && channelBState)    return state4;
	if ((!channelAState) && (!channelBState)) return state1;

	return state1; // silences clang-diagnostic-return-type warning
}


// doesn't have to be a fn anymore, but will be to maintain readability, expandability,
// compatibility with setup() fn
// also speed impact probably doesn't matter much since setup() runs once per boot
void initialiseEncoderStateMachine()
{
	// gets current state (initial) state and sets it
	state = get_current_state();
}


void updateEncoderStateMachine()
{
	enum states new_state = get_current_state(); // get new state, don't update state yet so states can be compared

	// check new state is valid, coming from state1, increment error count if not
	// adjust count as appropriate if new state is valid
	// repeat for all other state cases
	switch (state)
	{
	case state1:
		switch (new_state) {
			case state1: break;
			case state2: count++; break;
			case state3: error++; break;
			case state4: count--; break;
		}

		break;
	case state2:
		switch (new_state) {
			case state1: count--; break;
			case state2: break;
			case state3: count++; break;
			case state4: error++; break;
		}
		break;
	case state3:
		switch (new_state) {
			case state1: error++; break;
			case state2: count--; break;
			case state3: break;
			case state4: count++; break;
		}
		break;
	case state4:
		switch (new_state) {
			case state1: count++; break;
			case state2: error++; break;
			case state3: count--; break;
			case state4: break;
		}
		break;
	}

	state = new_state; // set new state
}
