/*
 * eForce CAN Bootloader
 *
 * Written by Vojtěch Michal
 *
 * Copyright (c) 2020, 2021 eforce FEE Prague Formula
 *
 * This file contains an example code showing the reference integration of the bootloader into your project.
 * It is based on code from FSE10::AMS, which may be used as a reference (uses BL since october 2020).
 * The following code assumes that you have some code generated from CANdb included (e.g. #include <can_AMS.h>).
 *
 * The rest should be pretty self-explanatory. If you have not implemented serial output for your unit, then debug_printf
 * will not be available. Identifier 'can' is of some type modelling a CanManager and contains method for polling whether given peripheral
 * is able to send a message.
 */

struct BootloaderDriver {
    //TODO modify this identifier to match the name of unit you want to make bootloader-aware
	constexpr static Bootloader_BootTarget thisUnit = Bootloader_BootTarget_AMS;

	static void sendBootloaderPingResponse(bool bootloader_pending) {
		Bootloader_PingResponse_t msg;
		msg.Target = thisUnit;
		msg.BootloaderPending = bootloader_pending;

		//wait for both peripherals to have an empty mailbox (ping response is transmitted to both buses in this case.
		for (; !can.hasEmptyMailbox<1>() || !can.hasEmptyMailbox<2>(););
		send(msg);
	}

	static int pingReceivedCallback(Bootloader_Ping_t* data) {
		if (data->Target != thisUnit)
			return 1; //Ignore bootloader requests targeted at someone else

		if (!data->BootloaderRequested) { //Bootloader is not requested ... the master only polls connected units
			debug_printf(("AMS: BL ping received.\r\n"));
			sendBootloaderPingResponse(/*bootloader_pending=*/false);
			return 0;
		}

        //TODO add your application specific logic here - prevent entering the bootloader during critical operations
		if (!airs.tsIdle()) { //Refuse to enter the bootloader when AIRs are closed.
			sendBootloaderPingResponse(/*bootloader_pending=*/false);
			debug_printf(("AMS: Refused to enter BL during critical process (closed AIRs).\r\n"));
			return 1;
		}
		sendBootloaderPingResponse(/*bootloader_pending=*/true);


        //TODO perform cleanup tasks, terminate all ongoing processes. The software is going to shut down.
		can.FlushSerialOutput();
		can.FlushBuses(/*timeout=*/100_ms);

		boot::resetTo(boot::BackupDomain::magic::bootloader);
	}
};

//Called from main somethime during the initialization. Supplies pointer to callback function when BootloaderPing is received
void setupBootloaderCANCallback() {
	Bootloader_Ping_on_receive(BootloaderDriver::pingReceivedCallback);
}
