using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Threading;
using QCRPG.Framework;
using QCRPG.TSL.FMHCI.Packets;

namespace QCRPG.TSL.FMHCI;

public class FMHCIAPI : CommonAPI
{
	private const int responseWaitTimeMs = 1000;

	private const int resetWaitTimeMs = 3000;

	private ProtocolHCI protocolHCI;

	private ConnectionSerialPort connectionSerialPort;

	private string sharedConnectionPortName = "";

	private bool deviceEmulationMode;

	public FMHCIAPI(PacketListBase packetList, Logger logger, PacketReceiveMode packetReceiveMode, bool deviceEmulationMode)
		: base(packetList, logger, packetReceiveMode)
	{
		this.deviceEmulationMode = deviceEmulationMode;
		protocolHCI = new ProtocolHCI();
		protocolHCI.autoDetectProtocolIDByteToUse = deviceEmulationMode;
	}

	protected override void CreateReceivePacketHandler()
	{
		receivePacketHandler = new FMHCIReceivePacketHandler(packetList);
	}

	public override void Send(PacketBase packet)
	{
		FMHCIOutgoingPacketData fMHCIOutgoingPacketData = new FMHCIOutgoingPacketData(packet.ToByteArray());
		if (packet.Type == PacketType.Command)
		{
			fMHCIOutgoingPacketData.fmhciPacketType = FMHCIPacketDataType.Command;
		}
		else if (packet.Type == PacketType.Notification || packet.Type == PacketType.Response)
		{
			fMHCIOutgoingPacketData.fmhciPacketType = FMHCIPacketDataType.Event;
		}
		else
		{
			fMHCIOutgoingPacketData.fmhciPacketType = FMHCIPacketDataType.Raw;
		}
		packetDataSender.SendSync(fMHCIOutgoingPacketData);
		packet.Flags |= 2u;
		sendPacketNotifier.NotifyObservers(packet);
	}

	public override void SendAsync(PacketBase packet)
	{
		FMHCIOutgoingPacketData fMHCIOutgoingPacketData = new FMHCIOutgoingPacketData(packet.ToByteArray());
		if (packet.Type == PacketType.Command)
		{
			fMHCIOutgoingPacketData.fmhciPacketType = FMHCIPacketDataType.Command;
		}
		else if (packet.Type == PacketType.Notification || packet.Type == PacketType.Response)
		{
			fMHCIOutgoingPacketData.fmhciPacketType = FMHCIPacketDataType.Event;
		}
		else
		{
			fMHCIOutgoingPacketData.fmhciPacketType = FMHCIPacketDataType.Raw;
		}
		packetDataSender.SendAsync(fMHCIOutgoingPacketData);
		packet.Flags |= 2u;
		sendPacketNotifier.NotifyObservers(packet);
	}

	public void Send(FMHCIOutgoingPacketData fmhciOutgoingPacketData, bool performValidPacketCheck = false)
	{
		packetDataSender.SendSync(fmhciOutgoingPacketData);
		if (performValidPacketCheck)
		{
			try
			{
				PacketBase packetBase = packetList.BytesToPacket(fmhciOutgoingPacketData.bytes);
				packetBase.Flags |= 2u;
				sendPacketNotifier.NotifyObservers(packetBase);
			}
			catch (Exception)
			{
			}
		}
	}

	public void OpenConnection(string portName, int baudRate, Handshake handshake = Handshake.None)
	{
		if (connectionSerialPort != null)
		{
			connectionSerialPort.Close();
		}
		protocolHCI.Reset();
		ChangeBottomProtocol(protocolHCI);
		connectionSerialPort = new ConnectionSerialPort();
		connectionSerialPort.RegisterConnectionStateChangeInfoObserver(connectionStateChangeInfoObserver);
		connectionSerialPort.Open(portName, baudRate, handshake);
		ChangeConnection(connectionSerialPort);
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, $"Opened FM HCI connection (portName = {portName}, dataRate = {baudRate})"));
	}

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

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

	public override void AddToSharedSerialConnection(string portName)
	{
		if (connectionSerialPort != null && connectionSerialPort.IsOpen())
		{
			throw new Exception("Serial port connection is open, refusing to add to shared connection");
		}
		if (GetSharedConnectionPortName() != "" && GetSharedConnectionPortName() != portName)
		{
			RemoveFromSharedSerialConnection(sharedConnectionPortName);
		}
		protocolHCI.Reset();
		ChangeBottomProtocol(protocolHCI, usePacketDataReceiver: false);
		APIBase.sharedConnectionManager.AddRoute(portName, 20, protocolBottom);
		AddSleepProtocolToSharedConnection(portName);
		ChangeConnection(APIBase.sharedConnectionManager.GetConnection(portName));
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, $"Opened FM HCI to shared connection (portName = {portName})"));
		sharedConnectionPortName = portName;
		APIBase.sharedConnectionClosedMap[portName].Add(delegate
		{
			sharedConnectionPortName = "";
		});
		APIBase.sharedConnectionClosedMap[portName].Add(delegate
		{
			base.CloseConnection();
		});
	}

	public string GetSharedConnectionPortName()
	{
		if (sharedConnectionPortName == "")
		{
			return sharedConnectionPortName;
		}
		if (!APIBase.IsSharedConnectionActiveAndOpen(sharedConnectionPortName))
		{
			sharedConnectionPortName = "";
		}
		return sharedConnectionPortName;
	}

	private void SharedPortClosedHandler()
	{
		if (APIBase.sharedConnectionClosedMap.ContainsKey(sharedConnectionPortName))
		{
			APIBase.sharedConnectionClosedMap[sharedConnectionPortName].Remove(SharedPortClosedHandler);
		}
		sharedConnectionPortName = "";
	}

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

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

	public void WriteNVM(string filename)
	{
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Starting FM HCI NVM download"));
		NVMFileReader nVMFileReader = new NVMFileReader();
		List<NVMTag> list = nVMFileReader.ReadTags(filename);
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		try
		{
			List<int> list2 = new List<int>();
			foreach (NVMTag item in list)
			{
				DefaultDataWrite defaultDataWrite = new DefaultDataWrite();
				defaultDataWrite.UserFields.Mode = (uint)item.tagNumber;
				defaultDataWrite.UserFields.DataLength = (uint)item.tagLength;
				if (defaultDataWrite.UserFields.GetModeAsDefaultDataModeEnum() == DefaultDataModeEnum.InvalidDefaultDataModeValue)
				{
					list2.Add(item.tagNumber);
					Logger obj = logger;
					string[] array = new string[5] { "Unknown tag value 0x", null, null, null, null };
					int tagNumber = item.tagNumber;
					array[1] = tagNumber.ToString("X2");
					array[2] = " (";
					int tagNumber2 = item.tagNumber;
					array[3] = tagNumber2.ToString();
					array[4] = ")";
					obj.Enqueue(new LogMessage(LogMessageType.Warning, string.Concat(array)));
				}
				for (int i = 0; i < item.tagLength; i++)
				{
					defaultDataWrite.UserFields.DataList[i] = item.tagValue[i];
				}
				Send(defaultDataWrite);
				List<PacketBase> packets = modeAgnosticDataReceiver.ReceiveAll(defaultDataWrite.ExpectedResponseTypes, 1000);
				CommandCompleteForDefaultDataWrite packet = (CommandCompleteForDefaultDataWrite)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForDefaultDataWrite), packets);
				CheckStatus(packet);
			}
			if (list2.Count > 0)
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append("Unknown tag value(s) during NVM load: ");
				bool flag = true;
				foreach (int item2 in list2)
				{
					if (flag)
					{
						flag = false;
					}
					else
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append("0x" + item2.ToString("X2") + " (" + item2 + ")");
				}
				logger.Enqueue(new LogMessage(LogMessageType.Warning, stringBuilder.ToString()));
			}
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Finished FM HCI NVM download"));
		}
		finally
		{
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	public void PatchFirmware(string filename)
	{
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Starting FM HCI Firmware Patching"));
		byte[] array = File.ReadAllBytes(filename);
		int num = 0;
		int num2 = array.Length;
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		bool useBTProtocolIDBytes = protocolHCI.UseBTProtocolIDBytes;
		protocolHCI.UseBTProtocolIDBytes = true;
		try
		{
			EdlPatchDetachRequest edlPatchDetachRequest = new EdlPatchDetachRequest();
			Send(edlPatchDetachRequest);
			List<PacketBase> packets = modeAgnosticDataReceiver.ReceiveAll(edlPatchDetachRequest.ExpectedResponseTypes, 3000);
			EdlStatusResponse packet = (EdlStatusResponse)PacketHelper.GetPacketTypeFromList(typeof(EdlStatusResponse), packets);
			CheckStatus(packet);
			CommandCompleteForNopBt packet2 = (CommandCompleteForNopBt)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForNopBt), packets);
			CheckStatus(packet2);
			EdlSetPatchHeaderRequest edlSetPatchHeaderRequest = new EdlSetPatchHeaderRequest();
			edlSetPatchHeaderRequest.UserFields.ProductId = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			edlSetPatchHeaderRequest.UserFields.BuildVersion = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			edlSetPatchHeaderRequest.UserFields.BaseAddress = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			edlSetPatchHeaderRequest.UserFields.EntryAddress = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			edlSetPatchHeaderRequest.UserFields.Length = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			edlSetPatchHeaderRequest.UserFields.Crc = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			edlSetPatchHeaderRequest.UserFields.ControlBits = Conversions.ByteArrayToUint(array, num, 4, elementZeroIsMsb: false);
			num += 4;
			num2 -= 4;
			Send(edlSetPatchHeaderRequest);
			packets = modeAgnosticDataReceiver.ReceiveAll(edlSetPatchHeaderRequest.ExpectedResponseTypes, 3000);
			packet = (EdlStatusResponse)PacketHelper.GetPacketTypeFromList(typeof(EdlStatusResponse), packets);
			CheckStatus(packet);
			packet2 = (CommandCompleteForNopBt)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForNopBt), packets);
			CheckStatus(packet2);
			uint num3 = edlSetPatchHeaderRequest.UserFields.BaseAddress;
			while (num2 > 0)
			{
				int num4 = num2;
				if (num4 > 239)
				{
					num4 = 239;
				}
				EdlPoke8Request edlPoke8Request = new EdlPoke8Request();
				edlPoke8Request.UserFields.DataLength = (uint)num4;
				edlPoke8Request.UserFields.Address = num3;
				for (int i = 0; i < num4; i++)
				{
					edlPoke8Request.UserFields.DataList[i] = array[num++];
				}
				num2 -= num4;
				num3 += (uint)num4;
				Send(edlPoke8Request);
				packets = modeAgnosticDataReceiver.ReceiveAll(edlPoke8Request.ExpectedResponseTypes, 3000);
				packet = (EdlStatusResponse)PacketHelper.GetPacketTypeFromList(typeof(EdlStatusResponse), packets);
				CheckStatus(packet);
				packet2 = (CommandCompleteForNopBt)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForNopBt), packets);
				CheckStatus(packet2);
			}
			EdlPatchAttachRequest edlPatchAttachRequest = new EdlPatchAttachRequest();
			Send(edlPatchAttachRequest);
			packets = modeAgnosticDataReceiver.ReceiveAll(edlPatchAttachRequest.ExpectedResponseTypes, 3000);
			packet = (EdlStatusResponse)PacketHelper.GetPacketTypeFromList(typeof(EdlStatusResponse), packets);
			CheckStatus(packet);
			packet2 = (CommandCompleteForNopBt)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForNopBt), packets);
			CheckStatus(packet2);
			EdlResetRequest packet3 = new EdlResetRequest();
			Send(packet3);
			Thread.Sleep(500);
			try
			{
				EdlGetAppVerRequest edlGetAppVerRequest = new EdlGetAppVerRequest();
				Send(edlGetAppVerRequest);
				packets = modeAgnosticDataReceiver.ReceiveAll(edlGetAppVerRequest.ExpectedResponseTypes, 1000);
				packet2 = (CommandCompleteForNopBt)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForNopBt), packets);
				CheckStatus(packet2);
			}
			catch (TimeoutException)
			{
				logger.NotifyObserver(new LogMessage(LogMessageType.Warning, "Timeout getting app version, retrying"));
				EdlGetAppVerRequest edlGetAppVerRequest2 = new EdlGetAppVerRequest();
				Send(edlGetAppVerRequest2);
				packets = modeAgnosticDataReceiver.ReceiveAll(edlGetAppVerRequest2.ExpectedResponseTypes, 1000);
				packet2 = (CommandCompleteForNopBt)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForNopBt), packets);
				CheckStatus(packet2);
			}
			logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Finished FM HCI Firmware Patching"));
		}
		finally
		{
			protocolHCI.UseBTProtocolIDBytes = useBTProtocolIDBytes;
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	public void LoadSpurTable(string filename)
	{
		SpurTableFileReader spurTableFileReader = new SpurTableFileReader();
		List<SpurTableEntry> list = spurTableFileReader.ReadSpurTableEntries(filename);
		bool flag = false;
		bool flag2 = false;
		LogMessage logMessage = new LogMessage(LogMessageType.Warning, "Spur table has the following invalid lines:");
		if (spurTableFileReader.GetInvalidLineCount() > 0)
		{
			flag = true;
			foreach (SpurTableInvalidLine invalidLine in spurTableFileReader.GetInvalidLines())
			{
				logMessage.AppendLine(invalidLine.ToString());
			}
		}
		if (list.Count == 0)
		{
			logger.Enqueue(new LogMessage(LogMessageType.Error, "Spur table file has no valid entries"));
			return;
		}
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		logger.Enqueue(new LogMessage(LogMessageType.Info, "Starting Spur Table Load, " + list.Count + " Entries"));
		try
		{
			bool flag3 = true;
			int num = list.Count;
			int num2 = 0;
			while (num > 0)
			{
				SetSpurFilteringTable setSpurFilteringTable = new SetSpurFilteringTable();
				int num3 = num;
				if (flag3)
				{
					if (num > 15)
					{
						setSpurFilteringTable.UserFields.SetModeAsModeEnum(SetSpurFilteringTable.UserFieldsWrapper.ModeEnum.START);
						num3 = 15;
					}
					else
					{
						setSpurFilteringTable.UserFields.SetModeAsModeEnum(SetSpurFilteringTable.UserFieldsWrapper.ModeEnum.START_AND_END);
					}
					flag3 = false;
				}
				else if (num > 15)
				{
					setSpurFilteringTable.UserFields.SetModeAsModeEnum(SetSpurFilteringTable.UserFieldsWrapper.ModeEnum.CONTINUE);
					num3 = 15;
				}
				else
				{
					setSpurFilteringTable.UserFields.SetModeAsModeEnum(SetSpurFilteringTable.UserFieldsWrapper.ModeEnum.END);
				}
				setSpurFilteringTable.UserFields.NumberOfSpurFrequenciesToFollow = (uint)num;
				setSpurFilteringTable.UserFields.NumberOfSpurEntries = (uint)num3;
				int num4 = 0;
				while (num4 < num3)
				{
					setSpurFilteringTable.UserFields.SpurEntriesList[num4].SpurFrequency = list[num2].frequency;
					setSpurFilteringTable.UserFields.SpurEntriesList[num4].NumberOfSpursToTrack = list[num2].spursToTrack;
					setSpurFilteringTable.UserFields.SpurEntriesList[num4].Spur0Entry = list[num2].spur0;
					setSpurFilteringTable.UserFields.SpurEntriesList[num4].Spur1Entry = list[num2].spur1;
					setSpurFilteringTable.UserFields.SpurEntriesList[num4].Spur2Entry = list[num2].spur2;
					num4++;
					num2++;
					num--;
				}
				Send(setSpurFilteringTable);
				List<PacketBase> packets = modeAgnosticDataReceiver.ReceiveAll(setSpurFilteringTable.ExpectedResponseTypes, 1000);
				CommandCompleteForSetSpurFilteringTable packet = (CommandCompleteForSetSpurFilteringTable)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForSetSpurFilteringTable), packets);
				CheckStatus(packet);
			}
			if (flag)
			{
				logger.Enqueue(logMessage);
				flag2 = true;
			}
			SanityCheckSpurTableEntries(list);
			logger.Enqueue(new LogMessage(LogMessageType.Info, "Finished Spur Table Load"));
		}
		finally
		{
			if (flag && !flag2)
			{
				logger.Enqueue(logMessage);
				flag2 = true;
			}
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	private void SanityCheckSpurTableEntries(List<SpurTableEntry> spurTableEntries)
	{
		List<uint> list = new List<uint>();
		foreach (SpurTableEntry spurTableEntry in spurTableEntries)
		{
			if (spurTableEntry.frequency < 65000)
			{
				logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz seems too low in frequency"));
			}
			if (spurTableEntry.frequency > 108000)
			{
				logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz seems too high in frequency"));
			}
			if (list.Contains(spurTableEntry.frequency))
			{
				logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Duplicate entry with frequency of {spurTableEntry.frequency}kHz"));
			}
			else
			{
				list.Add(spurTableEntry.frequency);
			}
			if (spurTableEntry.spursToTrack == 0)
			{
				if (spurTableEntry.spur0 != 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has non-zero first spur, but spurs to track = 0"));
				}
				if (spurTableEntry.spur1 != 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has non-zero second spur, but spurs to track = 0"));
				}
				if (spurTableEntry.spur2 != 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has non-zero third spur, but spurs to track = 0"));
				}
			}
			if (spurTableEntry.spursToTrack == 1)
			{
				if (spurTableEntry.spur0 == 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has zero first spur, but spurs to track = 1"));
				}
				if (spurTableEntry.spur1 != 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has non-zero second spur, but spurs to track = 1"));
				}
				if (spurTableEntry.spur2 != 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has non-zero third spur, but spurs to track = 1"));
				}
			}
			if (spurTableEntry.spursToTrack == 2)
			{
				if (spurTableEntry.spur0 == 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has zero first spur, but spurs to track = 2"));
				}
				if (spurTableEntry.spur1 == 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has zero second spur, but spurs to track = 2"));
				}
				if (spurTableEntry.spur2 != 0)
				{
					logger.Enqueue(new LogMessage(LogMessageType.Warning, $"Entry with frequency of {spurTableEntry.frequency}kHz has non-zero third spur, but spurs to track = 2"));
				}
			}
		}
	}

	public void SendPokeWord(int Address, byte[] Data)
	{
		Array.Reverse(Data);
		PokeWord pokeWord = new PokeWord();
		pokeWord.UserFields.Address = (uint)Address;
		pokeWord.UserFields.DataLength = (uint)Data.Length;
		for (int i = 0; i < Data.Length; i++)
		{
			pokeWord.UserFields.DataList[i] = Data[i];
		}
		logger.NotifyObserver(new LogMessage(LogMessageType.Info, "Starting PokeWord"));
		ModeAgnosticDataReceiver modeAgnosticDataReceiver = new ModeAgnosticDataReceiver(this);
		modeAgnosticDataReceiver.Startup();
		try
		{
			Send(pokeWord);
			List<PacketBase> list = null;
			CommandCompleteForPokeByte commandCompleteForPokeByte = null;
			list = modeAgnosticDataReceiver.ReceiveAll(pokeWord.ExpectedResponseTypes, 3000);
			commandCompleteForPokeByte = (CommandCompleteForPokeByte)PacketHelper.GetPacketTypeFromList(typeof(CommandCompleteForPokeByte), list);
			CheckStatus(commandCompleteForPokeByte);
		}
		finally
		{
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	private void CheckStatus(PacketBase packet)
	{
		foreach (PacketFieldInfo fieldInfo in packet.Info.FieldInfoList)
		{
			if (fieldInfo.ParameterName == "Status")
			{
				ErrorCodeEnum errorCodeEnum = ErrorCodeInfo.ValueToErrorCode(Convert.ToUInt32(fieldInfo.Value, 16));
				if (errorCodeEnum != 0)
				{
					throw new Exception("Status field indicates error: " + errorCodeEnum);
				}
				return;
			}
		}
		throw new Exception("Packet type doesn't have a status field: " + packet.GetType().ToString());
	}
}
