// FMRadioBootloader.cpp: implementation of the CFMRadioBootloader class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "USBRadio.h"
#include "FMRadioDevice.h"
#include "FMRadioBootloader.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CFMRadioBootloader::CFMRadioBootloader(HANDLE fmRadioDataHandle, BYTE* pEndpoint0ReportBuffer, DWORD endpoint0ReportBufferSize, BYTE* pEndpoint2ReportBuffer, DWORD endpoint2ReportBufferSize)
{
	//Initialize all the data in the bootloader, and set progress percentage to 0
	m_FMRadioDataHandle = fmRadioDataHandle;
	m_pEndpoint0ReportBuffer = pEndpoint0ReportBuffer;
	m_Endpoint0ReportBufferSize = endpoint0ReportBufferSize;
	m_pEndpoint2ReportBuffer = pEndpoint2ReportBuffer;
	m_Endpoint2ReportBufferSize = endpoint2ReportBufferSize;
	m_ProgressPercentage = 0;
}

CFMRadioBootloader::~CFMRadioBootloader()
{

}

bool CFMRadioBootloader::BootloadMCU(BYTE swVersion, BYTE hwVersion, BOOL *bCloseOnFail)
{
	//Initialize status and error to false
	bool status = false;
	bool error = false;

	//Determine what needs to happen based upon the SW and HW version:
	//If the software version is less than or equal to the non-bootloadable version, do not bootload and continue if possible
	//If the software version is less than the image here and hardware version is the same, bootload it
	//If the software version is greater than the image here, and hareware version is the same or greater, then a new app needs to be downloaded
	//If the hardware version is less than the image here, then bootload the last image available for that hardware
	//Otherwise, software and hardware version are equal and up to date, so return status of true

	if (swVersion <= FMRADIO_SW_VERSION_NOT_BOOTLOADABLE)
	{
		/*********************************************************************/
		// Firmware version 6 is not bootloadable and will become corrupted
		// if bootloaded.  The USBRadio player will not bootload, but will
		// support version 6 firmware as long as it is possible
		/*********************************************************************/

		status = false;
		
		// false - attempt to use the old firmware with this player
		// true  - close the player immediately
		*bCloseOnFail = false;
	}
	else if ((swVersion < FMRADIO_SW_VERSION) && (hwVersion == FMRADIO_HW_VERSION))
	{
		//If the device has the same hardware version but old software version, update the device

		MessageBox(NULL, "Your USB FM Radio Device needs to be updated. Click OK to begin and do not remove the device until the process notifies you of completion.", "Update", MB_OK | MB_ICONEXCLAMATION);

		m_ProgressPercentage = 0;

		//Enter bootload mode by specifying the current hw/sw version
		if (EnterBootloadMode(swVersion, hwVersion))
		{
			WORD crc;

			//Loop through each page in the MCU image
			for (int i = 0; ((i < MCUIMAGE_PAGES) && (!error)); i++)
			{
				//Set the current page
				if (SetPage(g_MCUCurrentImage[i * MCUIMAGE_PAGE_LENGTH]))
				{
					//Erase the page in the MCU
					if (ErasePage(g_MCUCurrentImage[i * MCUIMAGE_PAGE_LENGTH]))
					{
						//Put a mask on our current page (0x55)
						for (int j = (i * MCUIMAGE_PAGE_LENGTH) + 1; j < (i * MCUIMAGE_PAGE_LENGTH) + 1 + 512; j++)
						{
							g_MCUCurrentImage[j] ^= 0x55;
						}

						//Write the page to the MCU
						if (WritePage(g_MCUCurrentImage[i * MCUIMAGE_PAGE_LENGTH], &(g_MCUCurrentImage[(i * MCUIMAGE_PAGE_LENGTH) + 1]), 512))
						{
							//Read back the crc on the page just written
							if (CheckCRCPage(&crc))
							{
								//Increment progress percentage
								m_ProgressPercentage += 1;

								//If the crc read back doesn't match the our crc, then an error occurred
								if (crc != (g_MCUCurrentImage[(i * MCUIMAGE_PAGE_LENGTH) + 1 + 512] << 8 | g_MCUCurrentImage[(i * MCUIMAGE_PAGE_LENGTH) + 1 + 512 + 1]))
								{
									error = true;
									MessageBox(NULL, "CRC Error during flash write", "Error", MB_OK | MB_ICONEXCLAMATION);
								}
							}
							else
							{
								error = true;
								MessageBox(NULL, "Error checking CRC", "Error", MB_OK | MB_ICONEXCLAMATION);
							}
						}
						else
						{
							error = true;
							MessageBox(NULL, "Error during write page", "Error", MB_OK | MB_ICONEXCLAMATION);
						}
					}
					else
					{
						error = true;
						MessageBox(NULL, "Error during erase page", "Error", MB_OK | MB_ICONEXCLAMATION);
					}
				}
				else
				{
					error = true;
					MessageBox(NULL, "Error during set page", "Error", MB_OK | MB_ICONEXCLAMATION);
				}
			}

			if (!error)
			{
				//Reset the firmware
				ResetFirmware();

				//Get current time and see if timout has occurred
				SYSTEMTIME systemTime;
				GetSystemTime(&systemTime);
				WORD startTime = systemTime.wSecond + 3;

				while ((systemTime.wSecond - startTime) < 3)
				{
					GetSystemTime(&systemTime);
				}

				//Add 1 to the progress percentage
				m_ProgressPercentage += 1;
			}
		}
		else
		{
			error = true;
		}
			
		//If there weren't any errors, then return true
		if (!error)
		{
			MessageBox(NULL, "Update completed.", "Update", MB_OK | MB_ICONEXCLAMATION);
			status = true;
		}
	}
	else if (((swVersion > FMRADIO_SW_VERSION) && (hwVersion == FMRADIO_HW_VERSION)) || (hwVersion > FMRADIO_HW_VERSION))
	{
		//If the device has the same hardware but newer software or newer hardware, then notify them the player is out of date

		if (MessageBox(NULL, "A new version of the USB FM Radio player needs to be used with this device, download now?", "Update", MB_YESNO | MB_ICONQUESTION) == IDYES)
		{
			ShellExecute(GetDesktopWindow(), "open", "http://www.silabs.com/usbradio", NULL, NULL, SW_SHOW);
		}
		else
		{
			MessageBox(NULL, "Goto http://www.silabs.com/usbradio when you would like to download the new USB FM Radio player.", "Update", MB_OK | MB_ICONEXCLAMATION);
		}
	}
	else if (hwVersion < FMRADIO_HW_VERSION)
	{
		//If the hardware version is not up to date then check for the previous version to bootload

		//No version except 1 exists, so nothing here
	}
	else if ((swVersion == FMRADIO_SW_VERSION) && (hwVersion == FMRADIO_HW_VERSION))
	{
		//If the hardware and software version are up to date, then return success
		status = true;
	}

	return status;
}

bool CFMRadioBootloader::EnterBootloadMode(BYTE swVersion, BYTE hwVersion)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, UNIQUE_BL_ID, swVersion, hwVersion};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
			status = true;
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
	{
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);
	}

	return status;
}

bool CFMRadioBootloader::GetSoftwareVersion(BYTE* swVersion)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, GET_SW_VERSION_COMMAND, BLANK, BLANK};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, BLANK};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (GetMCUBootloadReport(response, RESPONSE_BUFFER_SIZE))
			{
				*swVersion = response[1];
				status = true;
			}
			else
				MessageBox(NULL, "Error Reading Data", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::GetHardwareVersion(BYTE* hwVersion)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, GET_HW_VERSION_COMMAND, BLANK, BLANK};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, BLANK};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (GetMCUBootloadReport(response, RESPONSE_BUFFER_SIZE))
			{
				*hwVersion = response[1];
				status = true;
			}
			else
				MessageBox(NULL, "Error Reading Data", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::SetPage(BYTE page)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, SET_PAGE_COMMAND, page, BLANK};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, COMMAND_FAILED};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (GetMCUBootloadReport(response, RESPONSE_BUFFER_SIZE) && (response[1] == COMMAND_OK))
				status = true;
			else
				MessageBox(NULL, "Error Reading Data", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::ErasePage(BYTE page)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, ERASE_PAGE_COMMAND, page, BLANK};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, COMMAND_FAILED};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (GetMCUBootloadReport(response, RESPONSE_BUFFER_SIZE) && (response[1] == COMMAND_OK))
				status = true;
			else
				MessageBox(NULL, "Error Reading Data", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::WritePage(BYTE page, BYTE* data, DWORD dataSize)
{
	bool status = false;
	bool error = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, WRITE_PAGE_COMMAND, page, BLANK};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, BLANK};
	FLASH_BUFFER flashBuffer;

	memset(flashBuffer, BLANK, FLASH_BUFFER_SIZE);
	flashBuffer[0] = FLASH_REPORT;

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{		
			for (DWORD i = 0; ((i < dataSize) && (!error)); i += (FLASH_BUFFER_SIZE - 1))
			{
				for (DWORD j = i; ((j < (i + (FLASH_BUFFER_SIZE - 1))) && (j < dataSize)); j++)
				{
					flashBuffer[(j - i) + 1] = data[j] ^ FLASH_DATA_MASK;
				}
				
				if (!SetMCUBootloadReport(flashBuffer, FLASH_BUFFER_SIZE))
					error = true;

				memset(&flashBuffer[1], BLANK, (FLASH_BUFFER_SIZE - 1));
			}

			if (!error)
				status = true;
			else
				MessageBox(NULL, "Error During Flash Write", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::CheckCRCPage(WORD* pageCRC)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, CRC_ON_PAGE_COMMAND, BLANK, BLANK};
	CRC_BUFFER response = {CRC_REPORT, BLANK, BLANK};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (GetMCUBootloadReport(response, CRC_BUFFER_SIZE))
			{
				*pageCRC = response[1] | (response[2] << 8);
				status = true;
			}
			else
				MessageBox(NULL, "Error Reading Data", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::ReadFlashByte(BYTE* data, WORD address)
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, READ_FLASH_BYTE_COMMAND, ((address & 0xFF00) >> 8), (address & 0xFF)};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, BLANK};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (GetMCUBootloadReport(response, CRC_BUFFER_SIZE))
			{
				*data = response[1];
				status = true;
			}
			else
				MessageBox(NULL, "Error Reading Data", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::ResetFirmware()
{
	bool status = false;
	COMMAND_BUFFER	command = {WRITE_REPORT, RESET_DEVICE_COMMAND, BLANK, BLANK};
	RESPONSE_BUFFER response = {RESPONSE_REPORT, BLANK};

	if (m_FMRadioDataHandle)
	{
		if (SetMCUBootloadReport(command, COMMAND_BUFFER_SIZE))
		{
			if (!GetMCUBootloadReport(response, RESPONSE_BUFFER_SIZE))
				status = true;
		}
		else
			MessageBox(NULL, "Error Sending Code", "Error", MB_OK | MB_ICONEXCLAMATION);
	}
	else
		MessageBox(NULL, "Invalid Device Handle", "Error", MB_OK | MB_ICONEXCLAMATION);

	return status;
}

bool CFMRadioBootloader::SetMCUBootloadReport(BYTE* dataBuffer, DWORD dataBufferSize)
{
	bool status = false;

	//Make sure our handle isn't NULL
	if (m_FMRadioDataHandle)
	{
		//Ensure there will be room in the endpoint buffer for the data requested
		if (dataBufferSize <= m_Endpoint0ReportBufferSize)
		{
			//Clear out the endpoint 0 buffer
			memset(m_pEndpoint0ReportBuffer, 0, m_Endpoint0ReportBufferSize);	

			//Assign the rest of the buffer with the data to write
			for (BYTE i = 0; i < dataBufferSize; i++)
			{
				m_pEndpoint0ReportBuffer[i] = dataBuffer[i];
			}

			//Call set feature to write the data
			if (HidD_SetFeature(m_FMRadioDataHandle, m_pEndpoint0ReportBuffer, m_Endpoint0ReportBufferSize))
				status = true;
			else
				DWORD err = GetLastError();
		}
	}
	
	return status;
}

bool CFMRadioBootloader::GetMCUBootloadReport(BYTE* dataBuffer, DWORD dataBufferSize)
{
	bool status = false;

	//Make sure our handle isn't NULL
	if (m_FMRadioDataHandle)
	{
		//Ensure there will be room in the endpoint buffer for the data requested
		if (dataBufferSize <= (m_Endpoint0ReportBufferSize - 1))
		{
			//Check to see if the report to read is a response or crc
			if ((dataBuffer[0] == RESPONSE_REPORT) || (dataBuffer[0] == CRC_REPORT))
			{
				//Clear out the endpoint 0 buffer
				memset(m_pEndpoint0ReportBuffer, 0, m_Endpoint0ReportBufferSize);	

				//Refill the endpoint buffer with the data to be sent
				for (BYTE i = 0; i < dataBufferSize; i++)
				{
					m_pEndpoint0ReportBuffer[i] = dataBuffer[i];
				}

				//Call get feature to read the data
				if (HidD_GetFeature(m_FMRadioDataHandle, m_pEndpoint0ReportBuffer, m_Endpoint0ReportBufferSize))
				{
					//Refill the data buffer with the return values of endpoint buffer
					for (BYTE i = 0; i < dataBufferSize; i++)
					{
						dataBuffer[i] = m_pEndpoint0ReportBuffer[i];
					}

					status = true;
				}
			}
		}
	}
	
	return status;
}
