/* Baoti has a sensor that is a fixed distance before the transducers
    This code checks the sensor via the parallel port.
	It then counts off a number of encoder clicks
	It then sets a "digital input" high for a fixed period of time.
	Parameters are configured by c:\baotiParallel.txt

*/

#include <windows.h>
#include <stdio.h>
#include <io.h>
#include "dlportio.h"
#include "HLPRXBaotiParallel.h"
#include "RQProtected.h"
#include "NDTSErrorInternal.h"

typedef struct 
{
	unsigned long outPort;
	unsigned char outValue;
	unsigned long inPort;
	unsigned long bitA;
	unsigned long bitB;
	unsigned long scanEncoder;
	unsigned long tubeStartEncoder;
	unsigned long batchStopEncoder;
 	long clicksD1 ;
	long clicksD2 ;
	long clicksEndScan;
	float endBatchPulseTime;
} IniFileSettingsType;

IniFileSettingsType ini;

typedef enum
{
	empty,
	firstBar,
	start,
	midScan,
	nearEnd,
	atEnd
} StateEnum ;

typedef enum
{
	EncoderCallback,
	ResetEncoderCallback,
	SawAnalogueChange,
	SawDIChange
} StateCallingEvent;
// Outputs from the state machine - default to zero / false
typedef struct 
{
	NDTS_Boolean iwStart;
	NDTS_Boolean iwEndBatch;
	NDTS_Boolean doFakeEndScan;
} OutputsStruct;


typedef struct 
{
	StateEnum state;
	OutputsStruct out;
	long clicksAtStateStart;
	unsigned long cardIndex;
	NDTS_Boolean nestedEncCallback;
	LARGE_INTEGER timeAtStateStart;
} GlobalsStruct;

GlobalsStruct g;

// Function proto's private to this file
void doStateMachine(StateCallingEvent callingEvent);
HLPRX_API void CALL_CONV HLPRX_GetEncoderOutputCallback(unsigned long cardIndex, unsigned long encoderIndex, long *pPosition);
HLPRX_API void CALL_CONV HLPRX_SetResetEncoderOutputCallback(unsigned long cardIndex, unsigned long encoderIndex);


NDTS_Boolean CALL_CONV HLPRX_InitialiseBaotiParallel(unsigned long cardIndex)
{
	FILE *fileHandle = fopen("C:\\BaotiParallel.txt", "r") ;
 
	if (!fileHandle) 
	{
		// Need to tidy all this up and set the values from IW.
		ini.outPort = 0x378;
		ini.outValue = 0xff;
		ini.inPort = 0x379;
		ini.bitA = 0x5;
		ini.bitB = 0x3;
		return NDTS_BTrue ;
	}


	if (fscanf(fileHandle, "OutPort =%x\n", &ini.outPort) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "OutValue =%x\n", &ini.outValue) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "InPort =%x\n", &ini.inPort) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "BitA =%x\n", &ini.bitA) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "BitB =%x\n", &ini.bitB) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "ScanEncoder =%d\n", &ini.scanEncoder) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "TubeStartEncoder =%d\n", &ini.tubeStartEncoder) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "BatchStopEncoder =%d\n", &ini.batchStopEncoder) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "ClicksD1 =%d\n", &ini.clicksD1) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "ClicksD2 =%d\n", &ini.clicksD2) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "ClicksEndScan =%d\n", &ini.clicksEndScan) != 1) return NDTS_BFalse;
	if (fscanf(fileHandle, "EndBatchPulseTime =%f\n", &ini.endBatchPulseTime) != 1) return NDTS_BFalse;

	fclose(fileHandle) ;

	// Set the data lines high to provide power.
	DlPortWritePortUchar(ini.outPort, ini.outValue);

	memset(&g,0, sizeof(g));
	g.cardIndex = cardIndex;

	if (!RQ_SetGetEncoderOutputHook(cardIndex, HLPRX_GetEncoderOutputCallback)) return NDTS_BFalse;
	if (!RQ_SetResetEncoderOutputHook(cardIndex, HLPRX_SetResetEncoderOutputCallback))  return NDTS_BFalse;

	return NDTS_BTrue;
}


HLPRX_API NDTS_Boolean CALL_CONV HLPRX_GetBaotiDI(unsigned int *value)
{
	// DI 0 doesn't work in the Baoti IW release - use 1 and 2
//	*value = (g.out.iwStart << 1) | (g.out.iwEndBatch << 2);
//	doStateMachine(SawDIChange);

	// Can't get it to work - just return D1 and D2 for A and B
		unsigned long portVal = DlPortReadPortUchar(ini.inPort);
	unsigned long inpA = (portVal & (1 << ini.bitA))?0:1;
	unsigned long inpB = (portVal & (1 << ini.bitB))?0:1;

	*value = (inpA << 1) | (inpB << 2);
	return NDTS_BTrue;
}

HLPRX_API void CALL_CONV HLPRX_GetEncoderOutputCallback(unsigned long cardIndex, unsigned long encoderIndex, long *pPosition) 
{
	if (g.nestedEncCallback || cardIndex != g.cardIndex) return;
	if (encoderIndex != ini.scanEncoder &&  encoderIndex != ini.tubeStartEncoder && encoderIndex != ini.batchStopEncoder) return;

	g.nestedEncCallback = NDTS_BTrue;

	doStateMachine(EncoderCallback);

	// Digital inputs don't seem to be getting called from IW so send the signals as encoder readings
	if (encoderIndex == ini.scanEncoder) 
	{
		if (g.out.doFakeEndScan) *pPosition = ini.clicksEndScan;
	}
	else if (encoderIndex == ini.tubeStartEncoder)
	{
		*pPosition = (g.out.iwStart)?1000000:0;
	//	filelogt("tube start %d", *pPosition);
	}
	else if (encoderIndex == ini.batchStopEncoder)
	{
		*pPosition = (g.out.iwEndBatch)?1000000:0;
//		filelogt("Sending end batch of %d", *pPosition);
	}

	g.nestedEncCallback = NDTS_BFalse;
}

HLPRX_API void CALL_CONV HLPRX_SetResetEncoderOutputCallback(unsigned long cardIndex, unsigned long encoderIndex)
{
	if (encoderIndex != ini.scanEncoder || cardIndex != g.cardIndex) return;
	doStateMachine(ResetEncoderCallback);
}

void HLPRX_SawAnalogueChangeFromIW()
{
	doStateMachine(SawAnalogueChange);
}

void doStateMachine(StateCallingEvent callingEvent)
{
	unsigned long portVal = DlPortReadPortUchar(ini.inPort);
	unsigned long inpA = (portVal & (1 << ini.bitA))?0:1;
	unsigned long inpB = (portVal & (1 << ini.bitB))?0:1;
	int nextState = g.state;
	long clicks;
	float time;
	LARGE_INTEGER currentTime, freq;	
	static int doDebugPrint = 1;

	static int exitNow = 1;
	if (exitNow) return;

	RQ_Lock(g.cardIndex) ;  // Will be inside lock from GetEncoder but may not be from other fn's

	memset(&(g.out),0, sizeof(g.out));

	// Set the number of clicks in this state
	RQ_GetEncoderOutput(g.cardIndex, ini.scanEncoder, &clicks);
	clicks = g.clicksAtStateStart - clicks; // Encoder is reversed

	// Set the elapsed time in this state
	QueryPerformanceCounter(&currentTime) ;
	QueryPerformanceFrequency(&freq) ;
	time = (float) (currentTime.QuadPart - g.timeAtStateStart.QuadPart) / (float) freq.QuadPart ;

	switch(g.state)
	{
	case empty: // Machine is empty between batches
		if (inpA && inpB)  	nextState = firstBar;  // First bar has passed sensor
		break;
	case firstBar:	// First bar doesn't start turning encoder until it passes it.  This means it
					// takes less clicks between the sensor and the transucers than the other bars
			if (clicks > ini.clicksD1)	nextState = start;
			break;
	case start: // Tell IW to start 
		// Give IW a pulse train to try to keep everything in sync
		if ((unsigned long)(time * 10) % 5)
		{
			g.out.iwStart = NDTS_BTrue;
		}
		//	IW doesn't call reset encoder so chnage on a timer that must be larger
		// than it takes for IW to see the start and smaller than the time the 
		// shortest bar can take to go through machine
		//		if (callingEvent == ResetEncoderCallback) nextState = midScan;
		if (time > ini.endBatchPulseTime) nextState = midScan;
		break;
	case midScan:
		// InspectionWare has started scanning.
		// The same bar is passing the transducers and the start sensors
		if (!inpA) 	nextState = nearEnd;  
		if (callingEvent == SawAnalogueChange)
		{
			filelogt("IW has stopped scan - probably due to length being set too short");
			nextState = start; // IW reached end of scan and stopped
		}
		break;
	case nearEnd: // The end of the bar being scanned has passed A but not reached transducer
			g.out.iwEndBatch = (!inpA && !inpB);  // End the batch if another bar isn't butted up behind
			if (clicks > ini.clicksD2)	nextState = atEnd;
		break;
	case atEnd:	// The end of the bar has reached the transducer.  We have sent the large encoder value
				// to IW and are waiting for it to acknowledge by attempting to set an out of range analouge output
		// Give IW a pulse train to try to keep everything in sync
	//	if ((unsigned long)(time * 10) % 5)
		{
			g.out.iwEndBatch = (!inpA && !inpB);  
		}
		g.out.doFakeEndScan = NDTS_BTrue;
		if (callingEvent == SawAnalogueChange)	
		{
			nextState = (g.out.iwEndBatch ) ? empty : start;
		}
		break;
	}
	if (g.state != nextState)
	{
		filelogt("State change from %d to State=%d end batch%d", g.state, nextState, g.out.iwEndBatch);
		g.state = nextState;
		RQ_GetEncoderOutput(g.cardIndex, ini.scanEncoder, &g.clicksAtStateStart);
		QueryPerformanceCounter(&g.timeAtStateStart) ;

	}
	if (doDebugPrint)
	{
		static int calls = 0;
		static int callsPerPrint = 50;
		if (!(calls++ % callsPerPrint))
		{
			filelogt("port =%x, State=%d, A=%d, B=%d, clicks=%d, time=%f end batch=%d",
			portVal, g.state, inpA, inpB, clicks, time, g.out.iwEndBatch);
		}
	}
	RQ_Unlock(g.cardIndex);
}
