using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using QCRPG.Framework;
using QCRPG.TSL.NFC.NVM;
using QCRPG.TSL.NFC.Packets;

namespace QCRPG.TSL.NFC;

public class NFCAPI : APIBase
{
	private const string uartToI2CRequiredOnBottomMessage = "requires the bottom protocol to be the UART to I2C protocol";

	private ProtocolNCIReader protocolNCIReader;

	private ProtocolUARTToI2C protocolUARTToI2C;

	private ConnectionSerialPort connectionSerialPort;

	private GenericValueTypeNotifier<int> patchFirmwarePercentCompleteNotifier = new GenericValueTypeNotifier<int>();

	public void WriteNVM(string filename)
	{
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Starting NFC NVM download"));
		NVMDataDefinition nVMDataDefinition = NVMData.LoadFromFile(filename);
		if (nVMDataDefinition.IsMaster)
		{
			throw new Exception("Refusing to write a master NVM file to NVM; specify a runtime NVM file");
		}
		if (nVMDataDefinition.FormatVersion < 0 || nVMDataDefinition.FormatVersion > 1)
		{
			throw new Exception("Unsupported NVM file format version: " + nVMDataDefinition.FormatVersion);
		}
		if (nVMDataDefinition.SupportedChipVersions.Count == 0)
		{
			SupportedChipVersion supportedChipVersion = new SupportedChipVersion();
			supportedChipVersion.VersionString = "1.0";
			supportedChipVersion.VersionRegisterAddressHex = "7C000";
			supportedChipVersion.VersionRegisterValueHex = "10";
			nVMDataDefinition.SupportedChipVersions.Add(supportedChipVersion);
		}
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		try
		{
			bool flag = false;
			foreach (SupportedChipVersion supportedChipVersion2 in nVMDataDefinition.SupportedChipVersions)
			{
				logger.NotifyObserver(new LogMessage(LogMessageType.Info, $"Checking for supported chip version {supportedChipVersion2.VersionString} (register 0x{supportedChipVersion2.VersionRegisterAddressHex} with value 0x{supportedChipVersion2.VersionRegisterValueHex})"));
				uint startAddress = Conversions.HexStringToUint(supportedChipVersion2.VersionRegisterAddressHex);
				uint num = Conversions.HexStringToUint(supportedChipVersion2.VersionRegisterValueHex);
				MemoryAccessPeekCmd memoryAccessPeekCmd = new MemoryAccessPeekCmd();
				memoryAccessPeekCmd.UserFields.ItemSize = 0u;
				memoryAccessPeekCmd.UserFields.NumberOfItems = 1u;
				memoryAccessPeekCmd.UserFields.AddressDelta = 1u;
				memoryAccessPeekCmd.UserFields.AccessDelay = 16u;
				memoryAccessPeekCmd.UserFields.StartAddress = startAddress;
				Send(memoryAccessPeekCmd);
				MemoryAccessRsp memoryAccessRsp = (MemoryAccessRsp)modeAgnosticDataReceiver.Receive(typeof(MemoryAccessRsp), 1000);
				if (memoryAccessRsp.UserFields.Status != 0)
				{
					throw new Exception("Non-zero status in peek response: 0x" + memoryAccessRsp.UserFields.Status.ToString("X2"));
				}
				uint num2 = memoryAccessRsp.UserFields.DataListOfLists[0][0];
				if (num == num2)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				throw new Exception("Unsupported chip version");
			}
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Chip version is supported"));
			foreach (Section section in nVMDataDefinition.Sections)
			{
				foreach (Parameter parameter in section.Parameters)
				{
					logger.NotifyObserver(new LogMessage(LogMessageType.Info, $"Writing parameter {parameter.Name} (address 0x{parameter.AddressHex} = value 0x{parameter.ValueHex})"));
					uint startAddress2 = Conversions.HexStringToUint(parameter.AddressHex);
					uint num3 = Conversions.HexStringToUint(parameter.ValueHex);
					MemoryAccessPokeCmd memoryAccessPokeCmd = new MemoryAccessPokeCmd();
					memoryAccessPokeCmd.UserFields.SetItemSizeAsMemoryAccessItemSizeEnum(MemoryAccessItemSizeEnum.ITEM_SIZE_32_BITS);
					memoryAccessPokeCmd.UserFields.NumberOfItems = 1u;
					memoryAccessPokeCmd.UserFields.AddressDelta = 1u;
					memoryAccessPokeCmd.UserFields.AccessDelay = 16u;
					memoryAccessPokeCmd.UserFields.StartAddress = startAddress2;
					memoryAccessPokeCmd.UserFields.DataListOfLists[0][0] = (num3 & 0xFF000000u) >> 24;
					memoryAccessPokeCmd.UserFields.DataListOfLists[0][1] = (num3 & 0xFF0000) >> 16;
					memoryAccessPokeCmd.UserFields.DataListOfLists[0][2] = (num3 & 0xFF00) >> 8;
					memoryAccessPokeCmd.UserFields.DataListOfLists[0][3] = num3 & 0xFFu;
					Send(memoryAccessPokeCmd);
					MemoryAccessRsp memoryAccessRsp2 = (MemoryAccessRsp)modeAgnosticDataReceiver.Receive(typeof(MemoryAccessRsp), 1000);
					if (memoryAccessRsp2.UserFields.Status != 0)
					{
						throw new Exception("Non-zero status in poke response: 0x" + memoryAccessRsp2.UserFields.Status.ToString("X2"));
					}
				}
				logger.NotifyObserver(new LogMessage(LogMessageType.Info, "NFC NVM download successful"));
			}
		}
		finally
		{
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	public NFCAPI(PacketListBase packetList, Logger logger, PacketReceiveMode packetReceiveMode)
		: base(packetList, logger, packetReceiveMode)
	{
		protocolUARTToI2C = new ProtocolUARTToI2C();
	}

	public void OpenConnection(byte i2cSlaveAddress, string portName, int baudRate)
	{
		protocolUARTToI2C.I2CSlaveAddress = i2cSlaveAddress;
		ChangeBottomProtocol(protocolUARTToI2C);
		protocolNCIReader = new ProtocolNCIReader();
		ChangeTopProtocol(protocolNCIReader);
		if (connectionSerialPort != null)
		{
			connectionSerialPort.Close();
		}
		connectionSerialPort = new ConnectionSerialPort();
		connectionSerialPort.RegisterConnectionStateChangeInfoObserver(connectionStateChangeInfoObserver);
		connectionSerialPort.Open(portName, baudRate);
		ChangeConnection(connectionSerialPort);
		ConnectionStateChangeInfo connectionStateChangeInfo = new ConnectionStateChangeInfo();
		connectionStateChangeInfo.slaveAddressChanged = true;
		connectionStateChangeInfo.slaveAddress = i2cSlaveAddress;
		connectionStateChangeInfoNotifier.NotifyObservers(connectionStateChangeInfo);
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, string.Format("Opened NFC connection (slaveAddress = 0x{0}, portName = {1}, dataRate = {2})", i2cSlaveAddress.ToString("X2"), portName, baudRate)));
	}

	public void CloseConnection()
	{
		if (connectionSerialPort != null)
		{
			connectionSerialPort.Close();
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Closed NFC connection"));
		}
	}

	public bool IsConnectionOpen()
	{
		if (connectionSerialPort == null)
		{
			return false;
		}
		return connectionSerialPort.IsOpen();
	}

	public override void Send(PacketBase packet)
	{
		if (!CheckForAndDoSpecialHandling(packet))
		{
			base.Send(packet);
		}
	}

	public override void SendAsync(PacketBase packet)
	{
		if (!CheckForAndDoSpecialHandling(packet))
		{
			base.SendAsync(packet);
		}
	}

	private bool CheckForAndDoSpecialHandling(PacketBase packet)
	{
		if (packet is ReadI2c)
		{
			byte length = (byte)((ReadI2c)packet).UserFields.Length;
			I2CRead(length);
			return true;
		}
		if (packet is ReadI2cRegister)
		{
			byte registerAddress = (byte)((ReadI2cRegister)packet).UserFields.RegisterAddress;
			byte length2 = (byte)((ReadI2cRegister)packet).UserFields.Length;
			I2CRead(registerAddress, length2);
			return true;
		}
		return false;
	}

	protected override void SetupReceiveDataObservation()
	{
		protocolUARTToI2C.RegisterReceiveDataObserverPostProcessing(receiveDataObserver);
	}

	protected override void ShutdownReceiveDataObservation()
	{
		protocolUARTToI2C.UnregisterReceiveDataObserverPostProcessing(receiveDataObserver);
	}

	public void SetI2CSlaveAddress(byte slaveAddress)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("SetI2CSlaveAddress() requires the bottom protocol to be the UART to I2C protocol");
		}
		protocolUARTToI2C.I2CSlaveAddress = slaveAddress;
		ConnectionStateChangeInfo connectionStateChangeInfo = new ConnectionStateChangeInfo();
		connectionStateChangeInfo.slaveAddressChanged = true;
		connectionStateChangeInfo.slaveAddress = slaveAddress;
		connectionStateChangeInfoNotifier.NotifyObservers(connectionStateChangeInfo);
	}

	public void SetI2CReadResponseTimeoutMs(int timeoutMs)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("SetI2CReadResponseTimeoutMs() requires the bottom protocol to be the UART to I2C protocol");
		}
		protocolUARTToI2C.I2CReadResponseTimeoutMs = timeoutMs;
	}

	public void I2CWrite(byte[] data)
	{
		I2CWrite(data, data.Length);
	}

	public void I2CWrite(byte[] data, int length)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("I2CWrite() requires the bottom protocol to be the UART to I2C protocol");
		}
		protocolUARTToI2C.SendI2CWrite(new OutgoingPacketData(data, length));
		WriteI2c writeI2c = new WriteI2c();
		writeI2c.UserFields.Length = (uint)length;
		for (int i = 0; i < length; i++)
		{
			writeI2c.UserFields.DataList[i] = data[i];
		}
		writeI2c.Flags |= 2u;
		sendPacketNotifier.NotifyObservers(writeI2c);
	}

	public void I2CWrite(byte registerAddress, byte[] data)
	{
		I2CWrite(registerAddress, data, data.Length);
	}

	public void I2CWrite(byte registerAddress, byte[] data, int length)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("I2CWrite() requires the bottom protocol to be the UART to I2C protocol");
		}
		byte[] array = new byte[length + 1];
		array[0] = registerAddress;
		Array.Copy(data, 0, array, 1, length);
		protocolUARTToI2C.SendI2CWrite(new OutgoingPacketData(array));
		WriteI2cRegister writeI2cRegister = new WriteI2cRegister();
		writeI2cRegister.UserFields.RegisterAddress = registerAddress;
		writeI2cRegister.UserFields.Length = (uint)length;
		for (int i = 0; i < length; i++)
		{
			writeI2cRegister.UserFields.DataList[i] = data[i];
		}
		writeI2cRegister.Flags |= 2u;
		sendPacketNotifier.NotifyObservers(writeI2cRegister);
	}

	public byte I2CReadByte()
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("I2CReadByte() requires the bottom protocol to be the UART to I2C protocol");
		}
		return I2CRead(1)[0];
	}

	public byte I2CReadByte(byte registerAddress)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("I2CReadByte() requires the bottom protocol to be the UART to I2C protocol");
		}
		return I2CRead(registerAddress, 1)[0];
	}

	public byte[] I2CRead(byte length, bool skipNotify = false)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("I2CRead() requires the bottom protocol to be the UART to I2C protocol");
		}
		IncomingPacketData incomingPacketData = protocolUARTToI2C.SendI2CReadAndGetResponse(length);
		ReadI2c readI2c = new ReadI2c();
		readI2c.UserFields.Length = length;
		if (!skipNotify)
		{
			readI2c.Flags |= 2u;
			sendPacketNotifier.NotifyObservers(readI2c);
		}
		ReadI2cResponse readI2cResponse = new ReadI2cResponse();
		readI2cResponse.UserFields.Length = (uint)incomingPacketData.bytes.Length;
		for (int i = 0; i < length; i++)
		{
			readI2cResponse.UserFields.DataList[i] = incomingPacketData.bytes[i];
		}
		if (!skipNotify)
		{
			readI2cResponse.Flags |= 4u;
			receivePacketNotifier.NotifyObservers(readI2cResponse);
		}
		return incomingPacketData.bytes;
	}

	public byte[] I2CRead(byte registerAddress, byte length)
	{
		if (!(protocolBottom is ProtocolUARTToI2C))
		{
			throw new Exception("I2CRead() requires the bottom protocol to be the UART to I2C protocol");
		}
		protocolUARTToI2C.SendI2CWrite(new OutgoingPacketData(new byte[1] { registerAddress }), includeStopAndWaitForInterruptMacro: false);
		byte[] array = I2CRead(length, skipNotify: true);
		ReadI2cRegister readI2cRegister = new ReadI2cRegister();
		readI2cRegister.UserFields.RegisterAddress = registerAddress;
		readI2cRegister.UserFields.Length = length;
		readI2cRegister.Flags |= 2u;
		sendPacketNotifier.NotifyObservers(readI2cRegister);
		ReadI2cResponse readI2cResponse = new ReadI2cResponse();
		readI2cResponse.UserFields.Length = (uint)array.Length;
		for (int i = 0; i < length; i++)
		{
			readI2cResponse.UserFields.DataList[i] = array[i];
		}
		readI2cResponse.Flags |= 4u;
		receivePacketNotifier.NotifyObservers(readI2cResponse);
		return array;
	}

	public int PatchFirmware(string filename)
	{
		byte[] data = File.ReadAllBytes(filename);
		NFCFirmwarePatchTypeHandler nFCFirmwarePatchTypeHandler = new NFCFirmwarePatchTypeHandler(data);
		NFCFirmwarePatchTypeHandler.PatchType patchType = nFCFirmwarePatchTypeHandler.GetPatchType();
		switch (patchType)
		{
		case NFCFirmwarePatchTypeHandler.PatchType.Binary:
			logger.Enqueue(new LogMessage(LogMessageType.Info, "Detected binary data in patch file"));
			break;
		case NFCFirmwarePatchTypeHandler.PatchType.String:
			logger.Enqueue(new LogMessage(LogMessageType.Info, "Detected string data in patch file"));
			break;
		default:
			throw new Exception("Unknown patch type: " + patchType);
		}
		data = nFCFirmwarePatchTypeHandler.GetPatchDataAsBinary();
		return PatchFirmwareData(data);
	}

	public int PatchFirmwareData(string patchData)
	{
		patchData = patchData.Replace(" ", "").Trim();
		byte[] patchData2 = Conversions.HexStringToByteArray(patchData);
		return PatchFirmwareData(patchData2);
	}

	public int PatchFirmwareData(byte[] patchData)
	{
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Starting NFC firmware patch"));
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		int num = 0;
		int num2 = 0;
		try
		{
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Resetting the device"));
			CoreResetCmd coreResetCmd = new CoreResetCmd();
			coreResetCmd.UserFields.SetResetTypeAsResetTypeEnum(CoreResetCmd.UserFieldsWrapper.ResetTypeEnum.KeepConfiguration);
			Send(coreResetCmd);
			Thread.Sleep(200);
			CoreInitCmd coreInitCmd = new CoreInitCmd();
			Send(coreInitCmd);
			modeAgnosticDataReceiver.ReceiveAll(coreInitCmd.ExpectedResponseTypes, 1000);
			GenericCommand genericCommand = new GenericCommand();
			genericCommand.UserFields.Gid = 15u;
			genericCommand.UserFields.Oid = 1u;
			genericCommand.UserFields.PayloadLength = 1u;
			genericCommand.UserFields.PayloadList[0] = 1u;
			Send(genericCommand);
			modeAgnosticDataReceiver.Receive(typeof(GenericResponse), 1000);
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Creating a connection"));
			CoreConnCreateCmd coreConnCreateCmd = new CoreConnCreateCmd();
			coreConnCreateCmd.UserFields.SetDestinationTypeAsDestinationTypeEnum(CoreConnCreateCmd.UserFieldsWrapper.DestinationTypeEnum.PROPRIETARY);
			coreConnCreateCmd.UserFields.NumberOfDestinationSpecificParameters = 1u;
			coreConnCreateCmd.UserFields.DestinationSpecificParameterList[0].Type = 161u;
			coreConnCreateCmd.UserFields.DestinationSpecificParameterList[0].Length = 1u;
			coreConnCreateCmd.UserFields.DestinationSpecificParameterList[0].ValueList[0] = 0u;
			Send(coreConnCreateCmd);
			List<PacketBase> list = modeAgnosticDataReceiver.ReceiveAll(coreConnCreateCmd.ExpectedResponseTypes, 1000);
			byte b = (byte)((CoreConnCreateRsp)list[0]).UserFields.MaxDataPacketPayloadSize;
			byte b2 = (byte)(b - 3);
			byte connId = (byte)((CoreConnCreateRsp)list[0]).UserFields.ConnId;
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Sending patch data"));
			List<byte[]> list2 = Conversions.ChunkByteArray(patchData, b2);
			foreach (byte[] item in list2)
			{
				byte[] array = null;
				if (item.Length == b2)
				{
					array = new byte[b];
					array[0] = 17;
					array[1] = 0;
					array[2] = b2;
					Array.Copy(item, 0, array, 3, item.Length);
				}
				else
				{
					array = new byte[item.Length + 3];
					array[0] = 1;
					array[1] = 0;
					array[2] = (byte)item.Length;
					Array.Copy(item, 0, array, 3, item.Length);
				}
				RawData rawData = new RawData();
				rawData.UserFields.Length = (uint)array.Length;
				for (int i = 0; i < rawData.UserFields.Length; i++)
				{
					rawData.UserFields.DataList[i] = array[i];
				}
				Send(rawData);
				num2++;
				if (num2 == list2.Count)
				{
					modeAgnosticDataReceiver.Receive(typeof(CoreConnCreditsNtf), 5000);
				}
				else
				{
					modeAgnosticDataReceiver.Receive(typeof(CoreConnCreditsNtf), 1000);
				}
				num = num2 * 100 / list2.Count;
				patchFirmwarePercentCompleteNotifier.NotifyObservers(num);
			}
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Closing the connection"));
			CoreConnCloseCmd coreConnCloseCmd = new CoreConnCloseCmd();
			coreConnCloseCmd.UserFields.ConnId = connId;
			Send(coreConnCloseCmd);
			List<Type> list3 = new List<Type>();
			list3.Add(coreConnCloseCmd.ExpectedResponseTypes[0]);
			list3.Add(typeof(CoreResetNtf));
			try
			{
				modeAgnosticDataReceiver.Receive(list3.ToArray(), 1000);
			}
			catch (TimeoutException)
			{
				Send(coreConnCloseCmd);
				modeAgnosticDataReceiver.ReceiveAll(list3.ToArray(), 1000);
			}
			Thread.Sleep(200);
			genericCommand = new GenericCommand();
			genericCommand.UserFields.Gid = 15u;
			genericCommand.UserFields.Oid = 1u;
			genericCommand.UserFields.PayloadLength = 1u;
			genericCommand.UserFields.PayloadList[0] = 1u;
			Send(genericCommand);
			modeAgnosticDataReceiver.Receive(typeof(GenericResponse), 1000);
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "NFC firmware patching successful"));
			return num2;
		}
		finally
		{
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	public void RegisterPatchFirmwarePercentCompleteObserver(QCRPG.Framework.IObserver<int> observer)
	{
		patchFirmwarePercentCompleteNotifier.RegisterObserver(observer);
	}

	public void UnregisterPatchFirmwarePercentCompleteObserver(QCRPG.Framework.IObserver<int> observer)
	{
		patchFirmwarePercentCompleteNotifier.UnregisterObserver(observer);
	}

	public void ResetAndInit()
	{
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Resetting the device"));
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		try
		{
			CoreResetCmd coreResetCmd = new CoreResetCmd();
			coreResetCmd.UserFields.SetResetTypeAsResetTypeEnum(CoreResetCmd.UserFieldsWrapper.ResetTypeEnum.KeepConfiguration);
			Send(coreResetCmd);
			Thread.Sleep(200);
			CoreInitCmd coreInitCmd = new CoreInitCmd();
			Send(coreInitCmd);
			modeAgnosticDataReceiver.ReceiveAll(coreInitCmd.ExpectedResponseTypes, 1000);
		}
		finally
		{
			modeAgnosticDataReceiver.Shutdown();
		}
	}
}
