/*
This software is subject to the license described in the License.txt file 
included with this software distribution. You may not use this file except in compliance 
with this license.

Copyright (c) Dynastream Innovations Inc. 2012
All rights reserved.
*/



#include "StdAfx.h"
#include "BikeSpdCadSensor.h"

/**************************************************************************
 * BikeSpdCadSensor::ANT_eventNotification
 * 
 * Process ANT channel event
 *
 * ucEventCode_: code of ANT channel event
 * pucEventBuffer_: pointer to buffer containing data received from ANT,
 *		or a pointer to the transmit buffer in the case of an EVENT_TX
 * 
 * returns: N/A
 *
 **************************************************************************/
void BikeSpdCadSensor::ANT_eventNotification(UCHAR ucEventCode_, UCHAR* pucEventBuffer_)
{
	switch(ucEventCode_)
	{
	case EVENT_TX:
		HandleTransmit((UCHAR*) pucEventBuffer_);
		break;
	default:
		break;
	}
}



/**************************************************************************
 * BikeSpdCadSensor::InitializeSim
 * 
 * Initializes simulator variables
 * 
 * returns: N/A
 *
 **************************************************************************/
void BikeSpdCadSensor::InitializeSim()
{
	// Simulation Timer
	ulRunTime16000 = 0;
	ulTimerInterval = 247;	// First event, before cadence/speed events, to initialize sim data
	ulNextCadInterval = 667; //90 rpm
	ulNextSpdInterval = 248; //30kph, 207cm wheel

	// Cadence
	usCadEventCount = 0;
	usCadTime1024 = 0;
	ucCurCadence = (UCHAR)this->numericUpDown_Sim_CadCurOutput->Value;		// Default current cadence is set on UI
	ucMinCadence = (UCHAR)this->numericUpDown_Sim_CadMinOutput->Value;		// Default minimum cadence is set on UI
	ucMaxCadence = (UCHAR)this->numericUpDown_Sim_CadMaxOutput->Value;		// Default maximum cadence is set on UI
	ucCadSimDataType = SIM_FIXED;
	bCadSweepAscending = TRUE;
	
	// Speed
	usSpdEventCount = 0;
	usSpdTime1024 = 0;
	ulCurSpeed = (ULONG) (System::Convert::ToDouble(this->numericUpDown_Sim_SpdCurOutput->Value) * 1000);	// Initial value set on UI (km/h -> meter/h)
	ulMinSpeed = (ULONG) (System::Convert::ToDouble(this->numericUpDown_Sim_SpdMinOutput->Value) * 1000);	// Initial value set on UI (km/h -> meter/h)
	ulMaxSpeed = (ULONG) (System::Convert::ToDouble(this->numericUpDown_Sim_SpdMaxOutput->Value) * 1000);	// Initial value set on UI (km/h -> meter/h)
	ucWheelCircumference = System::Convert::ToByte(this->numericUpDown_Sim_WheelCircumference->Value);	// Initial value set on UI
	ucSpdSimDataType = SIM_FIXED;
	bSpdSweepAscending = TRUE;

}

/**************************************************************************
 * BikeSpdCadSensor::HandleTransmit
 * 
 * Encode data generated by simulator for transmission
 *
 * pucTxBuffer_: pointer to the transmit buffer
 * 
 * returns: N/A
 *
 **************************************************************************/
void BikeSpdCadSensor::HandleTransmit(UCHAR* pucTxBuffer_)
{
	// Transmission is always the same page
	pucTxBuffer_[0] = (UCHAR) (usCadTime1024 & 0xFF);
	pucTxBuffer_[1] = (UCHAR) (usCadTime1024 >> 8) & 0xFF;
	pucTxBuffer_[2] = (UCHAR) (usCadEventCount & 0xFF);
	pucTxBuffer_[3] = (UCHAR) (usCadEventCount >> 8) & 0xFF;
	pucTxBuffer_[4] = (UCHAR) (usSpdTime1024 & 0xFF);
	pucTxBuffer_[5] = (UCHAR) (usSpdTime1024 >> 8) & 0xFF;
	pucTxBuffer_[6] = (UCHAR) (usSpdEventCount & 0xFF);
	pucTxBuffer_[7] = (UCHAR) (usSpdEventCount >> 8) & 0xFF;
}


/**************************************************************************
 * BikeSpdCadSensor::onTimerTock
 * 
 * Simulates a device event, updating simulator data based on this event
 * Modifications to the timer interval are applied immediately after this
 * at ANTChannel
 *
 * usEventTime_: current time (ms)
 *
 * returns: N/A
 *
 **************************************************************************/
void BikeSpdCadSensor::onTimerTock(USHORT eventTime)
{
	// Update master time
	ulRunTime16000 += (ulTimerInterval << 4);	// Multiply by 16 to convert from ms to 1/16000s

	// Handle event and calculate time until next cadence/speed event
	if(ulNextCadInterval < ulNextSpdInterval)	// Cadence event
	{
		// Update event count
		++usCadEventCount;
		// Update event time
		usCadTime1024 = (USHORT) ((ulRunTime16000 << 3) / 125);	// Convert to 1/1024s - multiply by 1024/16000		
		
		switch(ucCadSimDataType)
		{
		case SIM_FIXED:
			// Value does not change
			break;
		case SIM_SWEEP:
			{
			// Cadence sweeps between min and max
			// The jump offset is calculated versus position against the max so it won't get stuck on low values for a long time and won't speed through high values too fast
			ULONG tempOffset = ucMaxCadence-ucCurCadence;
			tempOffset = ((tempOffset & 0xC0) >> 6) + ((tempOffset & 0x20) >>5) + ((tempOffset & 0x10) >>4)+1;
			if(bCadSweepAscending)
				ucCurCadence += (UCHAR) tempOffset;
			else
				ucCurCadence -= (UCHAR) tempOffset;
			// Ensure next value is not less than min or more than max
			if(ucCurCadence >= ucMaxCadence)
			{
				ucCurCadence = ucMaxCadence;
				bCadSweepAscending = FALSE;
			}
			if(ucCurCadence <= ucMinCadence)
			{
				ucCurCadence = ucMinCadence;
				bCadSweepAscending = TRUE;
			}
			break;
			}
		default:
			break;
		}
		// Adjust speed interval with the time already elapsed in cadence event
		ulNextSpdInterval -= ulNextCadInterval;
		// Update cadence time interval (in ms)
		if(ucCurCadence)
			ulNextCadInterval = (ULONG) 60000/ucCurCadence;		// 60 seconds/revolutions per minute
		else
			ulNextCadInterval = 600000;	// coasting (no events, setting interval to a very large value)
		// Find out which event will occur next
		if(ulNextCadInterval < ulNextSpdInterval)
			ulTimerInterval = ulNextCadInterval;
		else
			ulTimerInterval = ulNextSpdInterval;
	}
	else	// Speed event
	{
		// Update event count
		++usSpdEventCount;
		// Update event time
		usSpdTime1024 = (USHORT) ((ulRunTime16000 << 3) / 125);	// Convert to 1/1024s - multiply by 1024/16000

		switch(ucSpdSimDataType)
		{
			case SIM_FIXED:
				// Speed value does not change
				break;
			case SIM_SWEEP:
				{
				// Cadence sweeps between min and max
				// The jump offset is calculated versus position against the max so it won't get stuck on low values for a long time and won't speed through high values too fast
				ULONG tempOffset = ulMaxSpeed-ulCurSpeed;
				tempOffset = ((tempOffset & 0x7000) >> 6) + ((tempOffset & 0xE00) >> 5) + ((tempOffset & 0x1C) >> 4)+0x7F;
				if(bSpdSweepAscending)
					ulCurSpeed += tempOffset;
				else
					ulCurSpeed -= tempOffset;
				// Ensure value is not less than min or more than max
				if(ulCurSpeed >= ulMaxSpeed)
				{
					ulCurSpeed = ulMaxSpeed;
					bSpdSweepAscending = FALSE;
				}
				if(ulCurSpeed <= ulMinSpeed)
				{
					ulCurSpeed = ulMinSpeed;
					bSpdSweepAscending = TRUE;
				}
				break;
				}
			default:
				break;
		}
		// Adjust cadence interval with the time already elapsed in speed event
		ulNextCadInterval -= ulNextSpdInterval;
		// Update speed time interval (in ms)
		if(ulCurSpeed)
			ulNextSpdInterval = (ULONG) 36000 * ucWheelCircumference/ulCurSpeed;  // in ms  (wheel circumference is in cm, speed in meter/h)
		else
			ulNextSpdInterval = 600000;	// Stopping (no events, setting interval to a very large value)
		//Find out which event will occur next
		if(ulNextSpdInterval < ulNextCadInterval)
			ulTimerInterval = ulNextSpdInterval;
		else
			ulTimerInterval = ulNextCadInterval;
	}

	if(ulTimerInterval < 1)	//this prevents timerInterval=0 if values are identical and could be used to prevent too short timer intervals
		ulTimerInterval = 1;

	UpdateDisplay();
}


/***************************************************************************
 * BikeSpdCadSensor::UpdateDisplay
 * 
 * Updates displayed simulator data on GUI
 *
 * returns: N/A
 *
 **************************************************************************/
void BikeSpdCadSensor::UpdateDisplay()
{
	this->label_Trn_CadCountDisplay->Text = System::Convert::ToString(usCadEventCount);  // Cadence event count
	this->label_Trn_CadenceTimeDisplay->Text = System::Convert::ToString(usCadTime1024); // Time of last cadence event (1/1024s)
	this->label_Trn_SpdCountDisplay->Text = System::Convert::ToString(usSpdEventCount);  // Speed event cont
	this->label_Trn_SpeedTimeDisplay->Text = System::Convert::ToString(usSpdTime1024);  // Time of last speed event (1/1024s)
	if(ucCadSimDataType == SIM_SWEEP)	// Update only if it is changing
		this->numericUpDown_Sim_CadCurOutput->Value = ucCurCadence;	// Current cadence generated by simulator (rpm)
	if(ucSpdSimDataType == SIM_SWEEP)	// Update only if it is changing
		this->numericUpDown_Sim_SpdCurOutput->Value = System::Convert::ToDecimal((double) ulCurSpeed/1000); // Current speed generated, meters/h -> km/h
}


/**************************************************************************
 * BikeSpdCadSensor::numericUpDown_Sim_WheelCircumference_ValueChanged
 * 
 * Updates wheel circumference value, if updated (either by the user or internally)
 * Validation is already performed by the numericUpDown control
 *
 * returns:  N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::numericUpDown_Sim_WheelCircumference_ValueChanged(System::Object^  sender, System::EventArgs^  e) 
{
	ucWheelCircumference = System::Convert::ToByte(this->numericUpDown_Sim_WheelCircumference->Value);		 
}


/**************************************************************************
 * BikeSpdCadSensor::checkBox_Sim_SpeedSweeping_CheckedChanged
 * 
 * Select method to generate simulator data, from user input (GUI)
 *
 * returns: N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::checkBox_Sim_SpeedSweeping_CheckedChanged(System::Object^  sender, System::EventArgs^  e) 
{
	this->numericUpDown_Sim_SpdCurOutput->Enabled = !this->numericUpDown_Sim_SpdCurOutput->Enabled;
	this->numericUpDown_Sim_SpdMinOutput->Enabled = !this->numericUpDown_Sim_SpdMinOutput->Enabled;
	this->numericUpDown_Sim_SpdMaxOutput->Enabled = !this->numericUpDown_Sim_SpdMaxOutput->Enabled;

	if(this->checkBox_Sim_SpdSweeping->Checked)
	{
		bSpdSweepAscending = TRUE;
		ucSpdSimDataType = SIM_SWEEP;
	}
	else
	{
		ucSpdSimDataType = SIM_FIXED;
	}
}


/**************************************************************************
 * BikeSpdCadSensor::checkBox_Sim_CadenceSweeping_CheckedChanged
 * 
 * Select method to generate simulator data, from user input (GUI)
 *
 * returns: N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::checkBox_Sim_CadenceSweeping_CheckedChanged(System::Object^  sender, System::EventArgs^  e) 
{
	this->numericUpDown_Sim_CadCurOutput->Enabled = !this->numericUpDown_Sim_CadCurOutput->Enabled;
	this->numericUpDown_Sim_CadMinOutput->Enabled = !this->numericUpDown_Sim_CadMinOutput->Enabled;
	this->numericUpDown_Sim_CadMaxOutput->Enabled = !this->numericUpDown_Sim_CadMaxOutput->Enabled;
	
	if(this->checkBox_Sim_CadSweeping->Checked)
	{
		bCadSweepAscending = TRUE;
		ucCadSimDataType = SIM_SWEEP;
	}
	else
	{
		ucCadSimDataType = SIM_FIXED;
	}
}


/**************************************************************************
 * BikeSpdCadSensor::numericUpDown_Sim_SpdCurOutput_ValueChanged
 * 
 * Validates and updates the current speed, from user input (GUI)
 * 
 * returns: N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::numericUpDown_Sim_SpdCurOutput_ValueChanged(System::Object^  sender, System::EventArgs^  e)
{
	// This value is raised whenever the value changes, even if internally
	// Only update the current pulse if set by the user
	if(this->numericUpDown_Sim_SpdCurOutput->Enabled)
	{
		ulCurSpeed = (ULONG) (System:: Convert::ToDouble(this->numericUpDown_Sim_SpdCurOutput->Value) * 1000);	// km/h -> meter/h
		if(ulCurSpeed)
			ulNextSpdInterval = (ULONG) 36000 * ucWheelCircumference/ulCurSpeed;  // in ms  (wheel circumference is in cm, speed in meter/h)
		else
			ulNextSpdInterval = 600000;	// Stopping (no speed events in a very long time, so display can interpret it as stopping)
		ForceUpdate();
	}	
}



/**************************************************************************
 * BikeSpdCad::numericUpDown_Sim_SpdMinMaxOutput_ValueChanged
 * 
 * If the user has changed the min or max speed, validate that
 * minimum < current <  maximum
 * 
 * returns: N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::numericUpDown_Sim_SpdMinMaxOutput_ValueChanged(System::Object^  sender, System::EventArgs^  e)
{
	ULONG ulPrevSpeed = ulCurSpeed;

	// This event is raised whenever the min and max value change, even if internally
	// Check min<max if in min/max mode, and force min<cur<max
	if(this->numericUpDown_Sim_SpdMinOutput->Value < this->numericUpDown_Sim_SpdMaxOutput->Value)
	{
		ulMinSpeed = (ULONG) (System::Convert::ToDouble(this->numericUpDown_Sim_SpdMinOutput->Value) * 1000);	// km/h -> meters/h
		ulMaxSpeed = (ULONG) (System::Convert::ToDouble(this->numericUpDown_Sim_SpdMaxOutput->Value) * 1000);	// km/h -> meters/h
		if(ulCurSpeed > ulMaxSpeed)
			ulCurSpeed = ulMaxSpeed;
		else if(ulCurSpeed < ulMinSpeed)
			ulCurSpeed = ulMinSpeed;
		if(ulCurSpeed != ulPrevSpeed)
		{
			this->numericUpDown_Sim_SpdCurOutput->Value = System::Convert::ToDecimal((double) ulCurSpeed/1000); // meters/h -> km/h
			if(ulCurSpeed)
				ulNextSpdInterval = (ULONG) 36000 * ucWheelCircumference/ulCurSpeed;  // in ms  (wheel circumference is in cm, speed in meter/h)
			else
				ulNextSpdInterval = 600000;	// Stopping (no speed events in a very long time, so display can interpret it as stopping)
			ForceUpdate();
		}
	}
	else
	{ 
		// If the values were invalid, set numeric values to last valid values
		this->numericUpDown_Sim_SpdMinOutput->Value = System::Convert::ToDecimal((double) ulMinSpeed/1000); // meters/h -> km/h
		this->numericUpDown_Sim_SpdMaxOutput->Value = System::Convert::ToDecimal((double) ulMaxSpeed/1000); // meters/h -> km/h
	}
}


/**************************************************************************
 * BikeSpdCadSensor::numericUpDown_Sim_CadCurOutput_ValueChanged
 * 
 * Validates and updates the current cadence, from user input (GUI)
 * 
 * returns: N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::numericUpDown_Sim_CadCurOutput_ValueChanged(System::Object^  sender, System::EventArgs^  e)
{
	// This value is raised whenever the value changes, even if internally
	// Only update the current pulse if set by the user
	if(this->numericUpDown_Sim_CadCurOutput->Enabled)
	{
		ucCurCadence = System::Convert::ToByte(this->numericUpDown_Sim_CadCurOutput->Value);
		if(ucCurCadence)
			ulNextCadInterval = (ULONG) 60000/ucCurCadence;		// 60 seconds/revolutions per minute
		else	
			ulNextCadInterval = 600000;	// Coasting (no cadence events in a very long time so that display can interpret as coasting)
		ForceUpdate();
	}	
}


/**************************************************************************
 * BikeSpdCad::numericUpDown_Sim_CadMinMaxOutput_ValueChanged
 * 
 * If the user has changed the min or max cadence, validate that
 * minimum < current <  maximum
 * 
 * returns: N/A
 *
 **************************************************************************/
System::Void BikeSpdCadSensor::numericUpDown_Sim_CadMinMaxOutput_ValueChanged(System::Object^  sender, System::EventArgs^  e)
{
	UCHAR ucPrevCadence = ucCurCadence;

	// This event is raised whenever the min and max value change, even if internally
	// Check min<max if in min/max mode, and force min<cur<max
	if(this->numericUpDown_Sim_CadMinOutput->Value < this->numericUpDown_Sim_CadMaxOutput->Value)
	{
		ucMinCadence = (UCHAR) this->numericUpDown_Sim_CadMinOutput->Value;
		ucMaxCadence = (UCHAR) this->numericUpDown_Sim_CadMaxOutput->Value;
		if(ucCurCadence > ucMaxCadence)
			ucCurCadence = ucMaxCadence;
		else if(ucCurCadence < ucMinCadence)
			ucCurCadence = ucMinCadence;
		if(ucCurCadence != ucPrevCadence)
		{
			this->numericUpDown_Sim_CadCurOutput->Value = ucCurCadence;
			if(ucCurCadence)
				ulNextCadInterval = (ULONG) 60000/ucCurCadence;		// 60 seconds/revolutions per minute
			else	
				ulNextCadInterval = 600000;	// Coasting (no cadence events in a very long time so that display can interpret as coasting)
			ForceUpdate();
		}
	}
	else
	{ 
		// If the values were invalid, set numeric values to last valid values
		this->numericUpDown_Sim_CadMinOutput->Value = ucMinCadence;
		this->numericUpDown_Sim_CadMaxOutput->Value = ucMaxCadence;
	}		 
}

/**************************************************************************
 * BikeSpdCad::ForceUpdate
 * 
 * Causes a timer event, to force the simulator to update all calculations
 * 
 * returns: N/A
 *
 **************************************************************************/
void BikeSpdCadSensor::ForceUpdate()
{
	timerHandle->Interval = 250;
}