#include "icsneo/device/device.h"
#include "icsneo/device/idevicesettings.h"
#include "icsneo/communication/message/filter/main51messagefilter.h"
#include <cstring>

using namespace icsneo;

IDeviceSettings::IDeviceSettings(Device* device, size_t size)
	: device(device), report(device->report), structSize(size) {}

IDeviceSettings::IDeviceSettings(warn_t createInoperableSettings, Device* device)
	: disabled(true), readonly(true), report(device->report), structSize(0) { (void)createInoperableSettings; }

std::optional<uint16_t> IDeviceSettings::CalculateGSChecksum(const std::vector<uint8_t>& settings) {
	const uint16_t* p = reinterpret_cast<const uint16_t*>(settings.data());
	size_t words = settings.size();
	if(words % 2 == 1)
		return std::nullopt; // Somehow settings is not word aligned
	words /= 2;

	uint16_t gsCrc = 0;
	while(words--) {
		uint16_t temp = *p;

		for (int i = 0; i < 16; i++) {
			bool iBit = temp & 1;

			int iCrcNxt;
			//CRCNXT = NXTBIT EXOR CRC_RG(15);
			if (gsCrc & (1 << 15))
				iCrcNxt = iBit ^ 1;
			else
				iCrcNxt = iBit;
			iCrcNxt = iCrcNxt & 0x01;


			// CRC_RG(15:1) = CRC_RG(14:0); // shift left by
			gsCrc = gsCrc << 1;
			gsCrc = gsCrc & 0xFFFE;// clear first bit

			if (iCrcNxt)//CRC_RG(14:0) = CRC_RG(14:0) EXOR (4599hex);
				gsCrc = gsCrc ^ 0xa001;

			temp >>= 1;
		}

		p++;
	}
	return gsCrc;
}

CANBaudrate IDeviceSettings::GetEnumValueForBaudrate(int64_t baudrate) {
	switch(baudrate) {
		case 20000:
			return BPS20;
		case 33000:
			return BPS33;
		case 50000:
			return BPS50;
		case 62000:
			return BPS62;
		case 83000:
			return BPS83;
		case 100000:
			return BPS100;
		case 125000:
			return BPS125;
		case 250000:
			return BPS250;
		case 500000:
			return BPS500;
		case 800000:
			return BPS800;
		case 1000000:
			return BPS1000;
		case 666000:
			return BPS666;
		case 2000000:
			return BPS2000;
		case 4000000:
			return BPS4000;
		case 5000000:
			return CAN_BPS5000;
		case 6667000:
			return CAN_BPS6667;
		case 8000000:
			return CAN_BPS8000;
		case 10000000:
			return CAN_BPS10000;
		default:
			return (CANBaudrate)-1;
	}
}

int64_t IDeviceSettings::GetBaudrateValueForEnum(CANBaudrate enumValue) {
	switch(enumValue) {
		case BPS20:
			return 20000;
		case BPS33:
			return 33000;
		case BPS50:
			return 50000;
		case BPS62:
			return 62000;
		case BPS83:
			return 83000;
		case BPS100:
			return 100000;
		case BPS125:
			return 125000;
		case BPS250:
			return 250000;
		case BPS500:
			return 500000;
		case BPS800:
			return 800000;
		case BPS1000:
			return 1000000;
		case BPS666:
			return 666000;
		case BPS2000:
			return 2000000;
		case BPS4000:
			return 4000000;
		case CAN_BPS5000:
			return 5000000;
		case CAN_BPS6667:
			return 6667000;
		case CAN_BPS8000:
			return 8000000;
		case CAN_BPS10000:
			return 10000000;
		default:
			return -1;
	}
}

bool IDeviceSettings::ValidateLINBaudrate(int64_t baudrate) {
	switch(baudrate) {
		case 4800:
			// fallthrough
		case 9600:
			// fallthrough
		case 10400:
			// fallthrough
		case 10417:
			// fallthrough
		case 10504:
			// fallthrough
		case 10593:
			// fallthrough
		case 10684:
			// fallthrough
		case 10776:
			// fallthrough
		case 10870:
			// fallthrough
		case 10965:
			// fallthrough
		case 11062:
			// fallthrough
		case 19200:
			return true;
		default:
			return false;
	}
}

bool IDeviceSettings::refresh() {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	std::vector<uint8_t> rxSettings;
	bool ret = device->com->getSettingsSync(rxSettings);
	if(!ret) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	constexpr size_t GsSize = 3 * sizeof(uint16_t); // <version> <length> <checksum>
	if(rxSettings.size() < GsSize) { // We need to at least have the header of GLOBAL_SETTINGS
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	const uint16_t gsVersion = rxSettings[0] | (rxSettings[1] << 8);

	rxSettings.erase(rxSettings.begin(), rxSettings.begin() + GsSize);

	if(gsVersion != GS_VERSION) {
		report(APIEvent::Type::SettingsVersionError, APIEvent::Severity::Error);
		return false;
	}

	settings = std::move(rxSettings);
	settingsInDeviceRAM = settings;
	settingsLoaded = true;

	// TODO Warn user that their API version differs from the device firmware version
	//if(settings.size() != structSize)

	return settingsLoaded;
}

bool IDeviceSettings::apply(bool temporary) {
	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	std::vector<uint8_t> bytestream;
	bytestream.resize(7 + settings.size());
	bytestream[0] = 0x00;
	bytestream[1] = GS_VERSION;
	bytestream[2] = GS_VERSION >> 8;
	bytestream[3] = (uint8_t)settings.size();
	bytestream[4] = (uint8_t)(settings.size() >> 8);
	std::optional<uint16_t> gsChecksum = CalculateGSChecksum(settings);
	if(!gsChecksum) {
		// Could not calculate the checksum for some reason
		report(APIEvent::Type::SettingsChecksumError, APIEvent::Severity::Error);
		return false;
	}
	bytestream[5] = (uint8_t)*gsChecksum;
	bytestream[6] = (uint8_t)(*gsChecksum >> 8);
	memcpy(bytestream.data() + 7, getMutableRawStructurePointer(), settings.size());

	// Pause I/O with the device while the settings are applied
	device->stopHeartbeat();

	std::shared_ptr<Main51Message> msg = std::dynamic_pointer_cast<Main51Message>(device->com->waitForMessageSync([this, &bytestream]() {
		return device->com->sendCommand(Command::SetSettings, bytestream);
	}, std::make_shared<Main51MessageFilter>(Command::SetSettings), std::chrono::milliseconds(5000)));

	if(!msg || msg->data[0] != 1) { // We did not receive a response
		// Attempt to get the settings from the device so we're up to date if possible
		if(refresh()) {
			// refresh succeeded but previously there was an error
			report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
		}
		return false;
	}

	refresh();
	// The device might modify the settings once they are applied, however in this case it does not update the checksum
	// We refresh to get these updates, update the checksum, and send it back so it's all in sync
	gsChecksum = CalculateGSChecksum(settings);
	if(!gsChecksum) {
		// Could not calculate the checksum for some reason
		report(APIEvent::Type::SettingsChecksumError, APIEvent::Severity::Error);
		return false;
	}
	bytestream[5] = (uint8_t)*gsChecksum;
	bytestream[6] = (uint8_t)(*gsChecksum >> 8);
	memcpy(bytestream.data() + 7, getMutableRawStructurePointer(), settings.size());

	msg = std::dynamic_pointer_cast<Main51Message>(device->com->waitForMessageSync([this, &bytestream]() {
		return device->com->sendCommand(Command::SetSettings, bytestream);
	}, std::make_shared<Main51MessageFilter>(Command::SetSettings), std::chrono::milliseconds(5000)));
	if(!msg || msg->data[0] != 1) {
		// Attempt to get the settings from the device so we're up to date if possible
		if(refresh()) {
			// refresh succeeded but previously there was an error
			report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
		}
		return false;
	}

	if(!temporary) {
		msg = std::dynamic_pointer_cast<Main51Message>(device->com->waitForMessageSync([this]() {
			return device->com->sendCommand(Command::SaveSettings);
		}, std::make_shared<Main51MessageFilter>(Command::SaveSettings), std::chrono::milliseconds(5000)));
	}

	device->startHeartbeat();

	refresh(); // Refresh our buffer with what the device has, whether we were successful or not

	bool ret = (msg && msg->data[0] == 1); // Device sends 0x01 for success
	if(!ret) {
		report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
	}
	return ret;
}

bool IDeviceSettings::applyDefaults(bool temporary) {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	device->stopHeartbeat();

	std::shared_ptr<Main51Message> msg = std::dynamic_pointer_cast<Main51Message>(device->com->waitForMessageSync([this]() {
		return device->com->sendCommand(Command::SetDefaultSettings);
	}, std::make_shared<Main51MessageFilter>(Command::SetDefaultSettings), std::chrono::milliseconds(5000)));
	if(!msg || msg->data[0] != 1) {
		// Attempt to get the settings from the device so we're up to date if possible
		if(refresh()) {
			// refresh succeeded but previously there was an error
			report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
		}
		return false;
	}

	// This short wait helps on FIRE devices, otherwise the checksum might be wrong!
	std::this_thread::sleep_for(std::chrono::milliseconds(3));

	refresh();
	// The device might modify the settings once they are applied, however in this case it does not update the checksum
	// We refresh to get these updates, update the checksum, and send it back so it's all in sync
	std::vector<uint8_t> bytestream;
	bytestream.resize(7 + settings.size());
	bytestream[0] = 0x00;
	bytestream[1] = GS_VERSION;
	bytestream[2] = GS_VERSION >> 8;
	bytestream[3] = (uint8_t)settings.size();
	bytestream[4] = (uint8_t)(settings.size() >> 8);
	const std::optional<uint16_t> gsChecksum = CalculateGSChecksum(settings);
	if(!gsChecksum) {
		// Could not calculate the checksum for some reason
		report(APIEvent::Type::SettingsChecksumError, APIEvent::Severity::Error);
		return false;
	}
	bytestream[5] = (uint8_t)*gsChecksum;
	bytestream[6] = (uint8_t)(*gsChecksum >> 8);
	memcpy(bytestream.data() + 7, getMutableRawStructurePointer(), settings.size());

	msg = std::dynamic_pointer_cast<Main51Message>(device->com->waitForMessageSync([this, &bytestream]() {
		return device->com->sendCommand(Command::SetSettings, bytestream);
	}, std::make_shared<Main51MessageFilter>(Command::SetSettings), std::chrono::milliseconds(5000)));
	if(!msg || msg->data[0] != 1) {
		// Attempt to get the settings from the device so we're up to date if possible
		if(refresh()) {
			// refresh succeeded but previously there was an error
			report(APIEvent::Type::NoDeviceResponse, APIEvent::Severity::Error);
		}
		return false;
	}

	if(!temporary) {
		msg = std::dynamic_pointer_cast<Main51Message>(device->com->waitForMessageSync([this]() {
			return device->com->sendCommand(Command::SaveSettings);
		}, std::make_shared<Main51MessageFilter>(Command::SaveSettings), std::chrono::milliseconds(5000)));
	}

	device->startHeartbeat();

	refresh(); // Refresh our buffer with what the device has, whether we were successful or not

	bool ret = (msg && msg->data[0] == 1); // Device sends 0x01 for success
	if(!ret) {
		report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error);
	}
	return ret;
}

int64_t IDeviceSettings::getBaudrateFor(Network net) const {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return -1;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return -1;
	}

	switch(net.getType()) {
		case Network::Type::CAN: {
			const CAN_SETTINGS* cfg = getCANSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::CANSettingsNotAvailable, APIEvent::Severity::Error);
				return -1;
			}

			int64_t baudrate = GetBaudrateValueForEnum((CANBaudrate)cfg->Baudrate);
			if(baudrate == -1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return -1;
			}
			return baudrate;
		}
		case Network::Type::SWCAN: {
			const SWCAN_SETTINGS* cfg = getSWCANSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::SWCANSettingsNotAvailable, APIEvent::Severity::Error);
				return -1;
			}

			int64_t baudrate = GetBaudrateValueForEnum((CANBaudrate)cfg->Baudrate);
			if(baudrate == -1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return -1;
			}
			return baudrate;
		}
		case Network::Type::LSFTCAN: {
			const CAN_SETTINGS* cfg = getLSFTCANSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LSFTCANSettingsNotAvailable, APIEvent::Severity::Error);
				return -1;
			}

			int64_t baudrate = GetBaudrateValueForEnum((CANBaudrate)cfg->Baudrate);
			if(baudrate == -1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return -1;
			}
			return baudrate;
		}
		case Network::Type::LIN: {
			const LIN_SETTINGS* cfg = getLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return -1;
			}

			return cfg->Baudrate;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return -1;
	}
}

bool IDeviceSettings::setBaudrateFor(Network net, int64_t baudrate) {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	switch(net.getType()) {
		case Network::Type::CAN: {
			if(baudrate > 1000000) { // This is an FD baudrate. Use setFDBaudrateFor instead.
				report(APIEvent::Type::CANFDSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			CAN_SETTINGS* cfg = getMutableCANSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::CANSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			CANBaudrate newBaud = GetEnumValueForBaudrate(baudrate);
			if(newBaud == (CANBaudrate)-1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return false;
			}
			cfg->Baudrate = (uint8_t)newBaud;
			cfg->auto_baud = false;
			cfg->SetBaudrate = AUTO; // Device will use the baudrate value to set the TQ values
			return true;
		}
		case Network::Type::LSFTCAN: {
			CAN_SETTINGS* cfg = getMutableLSFTCANSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LSFTCANSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			CANBaudrate newBaud = GetEnumValueForBaudrate(baudrate);
			if(newBaud == (CANBaudrate)-1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return false;
			}
			cfg->Baudrate = (uint8_t)newBaud;
			cfg->auto_baud = false;
			cfg->SetBaudrate = AUTO; // Device will use the baudrate value to set the TQ values
			return true;
		}
		case Network::Type::SWCAN: {
			SWCAN_SETTINGS* cfg = getMutableSWCANSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::SWCANSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			CANBaudrate newBaud = GetEnumValueForBaudrate(baudrate);
			if(newBaud == (CANBaudrate)-1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return false;
			}
			cfg->Baudrate = (uint8_t)newBaud;
			cfg->auto_baud = false;
			cfg->SetBaudrate = AUTO; // Device will use the baudrate value to set the TQ values
			return true;
		}
		case Network::Type::LIN: {
			LIN_SETTINGS* cfg = getMutableLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			bool valid = ValidateLINBaudrate(baudrate);
			if(!valid) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return false;
			}
			cfg->Baudrate = (uint32_t)baudrate;
			return true;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return false;
	}
}

int64_t IDeviceSettings::getFDBaudrateFor(Network net) const {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return -1;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return -1;
	}

	switch(net.getType()) {
		case Network::Type::CAN: {
			const CANFD_SETTINGS* cfg = getCANFDSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::CANFDSettingsNotAvailable, APIEvent::Severity::Error);
				return -1;
			}

			int64_t baudrate = GetBaudrateValueForEnum((CANBaudrate)cfg->FDBaudrate);
			if(baudrate == -1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return -1;
			}

			return baudrate;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return -1;
	}
}

bool IDeviceSettings::setFDBaudrateFor(Network net, int64_t baudrate) {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	switch(net.getType()) {
		case Network::Type::CAN: {
			CANFD_SETTINGS* cfg = getMutableCANFDSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::CANFDSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			CANBaudrate newBaud = GetEnumValueForBaudrate(baudrate);
			if(newBaud == (CANBaudrate)-1) {
				report(APIEvent::Type::BaudrateNotFound, APIEvent::Severity::Error);
				return false;
			}
			cfg->FDBaudrate = (uint8_t)newBaud;
			return true;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return false;
	}
}

bool IDeviceSettings::isTerminationSupportedFor(Network net) const {
	for(const auto& group : getTerminationGroups()) {
		for(const auto& supportedNet : group) {
			if(net == supportedNet)
				return true;
		}
	}
	return false;
}

bool IDeviceSettings::canTerminationBeEnabledFor(Network net) const {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	// Even though we will not be writing here, if the settings are read only the termination will not be enablable
	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	// Reference the mutable termination enables as we want to allow a disable/enable within a group without applying
	ICSNEO_UNALIGNED(const uint64_t*) currentQueuedTerminationEnables = const_cast<IDeviceSettings*>(this)->getMutableTerminationEnables();
	if(currentQueuedTerminationEnables == nullptr) {
		report(APIEvent::Type::TerminationNotSupportedDevice, APIEvent::Severity::Error);
		return false;
	}

	for(const auto& group : getTerminationGroups()) {
		bool found = false;
		for(const auto& supportedNet : group) {
			if(net == supportedNet) {
				found = true;
				break;
			}
		}

		if(found) {
			for(const auto& supportedNet : group) {
				// Allow termination on the current network even if it's already enabled
				if(net == supportedNet)
					continue;

				const auto cmNet = supportedNet.getCoreMini();
				if(!cmNet.has_value() || uint64_t(*cmNet) >= 64) {
					// Hitting this assert means that a supported network has an invalid CoreMini Network ID
					assert(false);
					continue;
				}

				// If this network is enabled, it excludes the queried network from being enabled
				if((*currentQueuedTerminationEnables >> uint64_t(*cmNet)) & 0x1) {
					report(APIEvent::Type::AnotherInTerminationGroupEnabled, APIEvent::Severity::Error);
					return false;
				}
			}
			return true;
		}
	}
	return false;
}

std::optional<bool> IDeviceSettings::isTerminationEnabledFor(Network net) const {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return std::nullopt;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return std::nullopt;
	}

	ICSNEO_UNALIGNED(const uint64_t*) terminationEnables = getTerminationEnables();
	if(terminationEnables == nullptr) {
		report(APIEvent::Type::TerminationNotSupportedDevice, APIEvent::Severity::Error);
		return std::nullopt;
	}

	const auto cmNet = net.getCoreMini();
	if(!cmNet.has_value() || uint64_t(*cmNet) >= 64 || !isTerminationSupportedFor(net)) {
		report(APIEvent::Type::TerminationNotSupportedNetwork, APIEvent::Severity::Error);
		return std::nullopt;
	}

	return (*terminationEnables >> uint64_t(*cmNet)) & 0x1;
}

bool IDeviceSettings::setTerminationFor(Network net, bool enabled) {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	ICSNEO_UNALIGNED(uint64_t*) terminationEnables = getMutableTerminationEnables();
	if(terminationEnables == nullptr) {
		report(APIEvent::Type::TerminationNotSupportedDevice, APIEvent::Severity::Error);
		return false;
	}

	// This function reports its own error statuses
	if(!canTerminationBeEnabledFor(net))
		return false;

	const auto cmNet = net.getCoreMini();
	if(!cmNet.has_value() || uint8_t(*cmNet) >= 64) {
		report(APIEvent::Type::TerminationNotSupportedNetwork, APIEvent::Severity::Error);
		return false;
	}

	const uint64_t mask = 1ull << uint8_t(*cmNet);
	if(enabled)
		*terminationEnables |= mask;
	else
		*terminationEnables &= ~mask;
	return true;
}

std::optional<bool> IDeviceSettings::isCommanderResistorEnabledFor(Network net) const {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return std::nullopt;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return std::nullopt;
	}

	switch(net.getType()) {
		case Network::Type::LIN: {
			const LIN_SETTINGS* cfg = getLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return std::nullopt;
			}

			return (cfg->CommanderResistor != RESISTOR_OFF);
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return std::nullopt;
	}
}

bool IDeviceSettings::setCommanderResistorFor(Network net, bool resistor_on) {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	switch(net.getType()) {
		case Network::Type::LIN: {
			LIN_SETTINGS* cfg = getMutableLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			cfg->CommanderResistor = resistor_on ? RESISTOR_ON : RESISTOR_OFF;
			return true;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return false;
	}
}

std::optional<LINMode> IDeviceSettings::getLINModeFor(Network net) const {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return std::nullopt;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return std::nullopt;
	}

	switch(net.getType()) {
		case Network::Type::LIN: {
			const LIN_SETTINGS* cfg = getLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return std::nullopt;
			}

			return static_cast<LINMode>(cfg->Mode);
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return std::nullopt;
	}
}

bool IDeviceSettings::setLINModeFor(Network net, LINMode mode) {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	switch(net.getType()) {
		case Network::Type::LIN: {
			LIN_SETTINGS* cfg = getMutableLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			cfg->Mode = static_cast<uint8_t>(mode);
			return true;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return false;
	}
}

std::optional<uint8_t> IDeviceSettings::getLINCommanderResponseTimeFor(Network net) const {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return std::nullopt;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return std::nullopt;
	}

	switch(net.getType()) {
		case Network::Type::LIN: {
			const LIN_SETTINGS* cfg = getLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return std::nullopt;
			}

			return cfg->numBitsDelay;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return std::nullopt;
	}
}

bool IDeviceSettings::setLINCommanderResponseTimeFor(Network net, uint8_t bits) {
	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	switch(net.getType()) {
		case Network::Type::LIN: {
			LIN_SETTINGS* cfg = getMutableLINSettingsFor(net);
			if(cfg == nullptr) {
				report(APIEvent::Type::LINSettingsNotAvailable, APIEvent::Severity::Error);
				return false;
			}

			cfg->numBitsDelay = bits;
			return true;
		}
		default:
			report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error);
			return false;
	}
}

template<typename T> bool IDeviceSettings::applyStructure(const T& newStructure) {
	if(!settingsLoaded) {
		report(APIEvent::Type::SettingsReadError, APIEvent::Severity::Error);
		return false;
	}

	if(disabled) {
		report(APIEvent::Type::SettingsNotAvailable, APIEvent::Severity::Error);
		return false;
	}

	if(readonly) {
		report(APIEvent::Type::SettingsReadOnly, APIEvent::Severity::Error);
		return false;
	}

	// This function is only called from C++ so the caller's structure size and ours should never differ
	if(sizeof(T) != structSize) {
		report(APIEvent::Type::SettingsStructureMismatch, APIEvent::Severity::Error);
		return false; // The wrong structure was passed in for the current device
	}
	size_t copySize = sizeof(T);
	if(copySize > settings.size()) {
		report(APIEvent::Type::SettingsStructureTruncated, APIEvent::Severity::EventWarning);
		copySize = settings.size(); // TODO Warn user that their structure is truncated
	}
	// Warn user that the device firmware doesn't support all the settings in the current API
	if(copySize < settings.size())
		report(APIEvent::Type::DeviceFirmwareOutOfDate, APIEvent::Severity::EventWarning);

	memcpy(settings.data(), &newStructure, structSize);
	return apply();
}

bool IDeviceSettings::setMiscIOAnalogOutputEnabled(uint8_t pin, bool enabled) {
	(void)pin;
	(void)enabled;
	report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
	return false;
}

bool IDeviceSettings::setMiscIOAnalogOutput(uint8_t pin, MiscIOAnalogVoltage voltage) {
	(void)pin;
	(void)voltage;
	report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
	return false;
}
std::optional<RADGPTPProfile> IDeviceSettings::getGPTPProfile() const {
	const auto* gptp = getGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	return static_cast<RADGPTPProfile>(gptp->profile);
}

bool IDeviceSettings::setGPTPProfile(RADGPTPProfile profile) {
	auto* gptp = getMutableGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return false;
	}
	gptp->profile = static_cast<uint8_t>(profile);
	return true;
}

std::optional<RADGPTPRole> IDeviceSettings::getGPTPRole() const {
	const auto* gptp = getGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	return static_cast<RADGPTPRole>(gptp->gptpPortRole);
}

bool IDeviceSettings::setGPTPRole(RADGPTPRole role) {
	auto* gptp = getMutableGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return false;
	}
	gptp->gptpPortRole = static_cast<uint8_t>(role);
	return true;
}

std::optional<uint8_t> IDeviceSettings::getGPTPEnabledPort() const {
	const auto* gptp = getGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	return gptp->gptpEnabledPort;
}

bool IDeviceSettings::setGPTPEnabledPort(uint8_t port) {
	auto* gptp = getMutableGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return false;
	}
	gptp->gptpEnabledPort = port;
	return true;
}

std::optional<bool> IDeviceSettings::isGPTPClockSyntonizationEnabled() const {
	const auto* gptp = getGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	return gptp->enableClockSyntonization != 0;
}

bool IDeviceSettings::setGPTPClockSyntonizationEnabled(bool enable) {
	auto* gptp = getMutableGPTPSettings();
	if(!gptp) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return false;
	}
	gptp->enableClockSyntonization = enable ? 1 : 0;
	return true;
}

std::optional<bool> IDeviceSettings::getLinuxBootEnabled() {
	const auto* os = getLinuxSettings();
	if(os == nullptr) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	return os->allowBoot != 0;
}

bool IDeviceSettings::setLinuxBootEnabled(bool enabled) {
	auto os = getMutableLinuxSettings();
	if(!os) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
		return false;
	}
	(*os)->allowBoot = enabled ? 1 : 0;
	return true;
}

std::optional<bool> IDeviceSettings::getExternalWifiAntennaEnabled() {
	const auto* os = getLinuxSettings();
	if(os == nullptr) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	return os->useExternalWifiAntenna != 0;
}

bool IDeviceSettings::setExternalWifiAntennaEnabled(bool enabled) {
	auto os = getMutableLinuxSettings();
	if(!os) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
		return false;
	}
	(*os)->useExternalWifiAntenna = enabled ? 1 : 0;
	return true;
}

std::optional<LinuxConfigurationPort> IDeviceSettings::getLinuxConfigurationPort() {
	const auto* os = getLinuxSettings();
	if(os == nullptr) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::EventWarning);
		return std::nullopt;
	}
	switch(static_cast<LinuxConfigurationPort>(os->ethConfigurationPort)) {
		case LinuxConfigurationPort::ETH01:
			return LinuxConfigurationPort::ETH01;
		case LinuxConfigurationPort::USB:
		default: // Any other value means the configuration interface is over USB
			return LinuxConfigurationPort::USB;
	}
}

bool IDeviceSettings::setLinuxConfigurationPort(LinuxConfigurationPort port) {
	switch(port) {
		case LinuxConfigurationPort::USB:
		case LinuxConfigurationPort::ETH01:
			break;
		default:
			report(APIEvent::Type::ParameterOutOfRange, APIEvent::Severity::Error);
			return false;
	}
	auto os = getMutableLinuxSettings();
	if(!os) {
		report(APIEvent::Type::SettingNotAvaiableDevice, APIEvent::Severity::Error);
		return false;
	}
	(*os)->ethConfigurationPort = static_cast<uint8_t>(port);
	return true;
}
