using System;
using System.Text;

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
	/// <summary>
	/// ZXHawk: Core Class
	/// * Handles all messaging (OSD) operations *
	/// </summary>
	public partial class ZXSpectrum
	{
		/// <summary>
		/// Writes a message to the OSD
		/// </summary>
		public void SendMessage(string message, MessageCategory category)
		{
			if (!CheckMessageSettings(category))
				return;

			StringBuilder sb = new StringBuilder();

			switch (category)
			{
				case MessageCategory.Tape:
					sb.Append("DATACORDER: ");
					sb.Append(message);
					break;
				case MessageCategory.Input:
					sb.Append("INPUT DETECTED: ");
					sb.Append(message);
					break;
				case MessageCategory.Disk:
					sb.Append("DISK DRIVE: ");
					sb.Append(message);
					break;
				case MessageCategory.Emulator:
				case MessageCategory.Misc:
					sb.Append("ZXHAWK: ");
					sb.Append(message);
					break;
			}

			CoreComm.Notify(sb.ToString());
		}

		/// <summary>
		/// Called when certain input presses are detected
		/// </summary>
		public void OSD_FireInputMessage(string input)
		{
			StringBuilder sb = new StringBuilder();
			sb.Append(input);
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Input);
		}

		/// <summary>
		/// Disk message that is fired on core init
		/// </summary>
		public void OSD_DiskInit()
		{
			StringBuilder sb = new StringBuilder();
			if (_machine.diskImages != null && _machine.UPDDiskDevice != null)
			{
				sb.Append("Disk Media Imported (count: " + _machine.diskImages.Count + ")");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator);
			}
		}

		/// <summary>
		/// Disk message that is fired when a new disk is inserted into the drive
		/// </summary>
		public void OSD_DiskInserted()
		{
			StringBuilder sb = new StringBuilder();

			if (_machine.UPDDiskDevice == null)
			{
				sb.Append("No Drive Present");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
				return;
			}

			sb.Append("DISK INSERTED (" + _machine.DiskMediaIndex + ": " + _diskInfo[_machine.DiskMediaIndex].Name + ")");
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
		}

		/// <summary>
		/// Tape message that prints the current status of the tape device
		/// </summary>
		public void OSD_ShowDiskStatus()
		{
			StringBuilder sb = new StringBuilder();

			if (_machine.UPDDiskDevice == null)
			{
				sb.Append("No Drive Present");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
				return;
			}

			if (_diskInfo.Count == 0)
			{
				sb.Append("No Disk Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
				return;
			}

			if (_machine.UPDDiskDevice != null)
			{
				if (_machine.UPDDiskDevice.DiskPointer == null)
				{
					sb.Append("No Disk Loaded");
					SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
					return;
				}

				sb.Append("Disk: " + _machine.DiskMediaIndex + ": " + _diskInfo[_machine.DiskMediaIndex].Name);
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
				sb.Clear();

				string protection = "None";
				protection = Enum.GetName(typeof(ProtectionType), _machine.UPDDiskDevice.DiskPointer.Protection);
				if (protection == "None")
					protection += " (OR UNKNOWN)";

				sb.Append("Detected Protection: " + protection);
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
				sb.Clear();

				sb.Append("Status: ");

				if (_machine.UPDDiskDevice.DriveLight)
					sb.Append("READING/WRITING DATA");
				else
					sb.Append("UNKNOWN");

				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Disk);
				sb.Clear();
			}
		}

		/// <summary>
		/// Tape message that is fired on core init
		/// </summary>
		public void OSD_TapeInit()
		{
			if (_tapeInfo.Count == 0)
				return;

			StringBuilder sb = new StringBuilder();
			sb.Append("Tape Media Imported (count: " + _tapeInfo.Count + ")");
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator);
		}

		/// <summary>
		/// Tape message that is fired when tape is playing
		/// </summary>
		public void OSD_TapePlaying()
		{
			if (_tapeInfo.Count == 0)
				return;

			StringBuilder sb = new StringBuilder();
			sb.Append("PLAYING (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when tape is stopped
		/// </summary>
		public void OSD_TapeStopped()
		{
			if (_tapeInfo.Count == 0)
				return;

			StringBuilder sb = new StringBuilder();
			sb.Append("STOPPED (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when tape is rewound
		/// </summary>
		public void OSD_TapeRTZ()
		{
			if (_tapeInfo.Count == 0)
				return;

			StringBuilder sb = new StringBuilder();
			sb.Append("REWOUND (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when a new tape is inserted into the datacorder
		/// </summary>
		public void OSD_TapeInserted()
		{
			if (_tapeInfo.Count == 0)
				return;

			StringBuilder sb = new StringBuilder();
			sb.Append("TAPE INSERTED (" + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name + ")");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}


		/// <summary>
		/// Tape message that is fired when a tape is stopped automatically
		/// </summary>
		public void OSD_TapeStoppedAuto()
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("STOPPED (Auto Tape Trap Detected)");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when a tape is started automatically
		/// </summary>
		public void OSD_TapePlayingAuto()
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("PLAYING (Auto Tape Trap Detected)");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when a new block starts playing
		/// </summary>
		public void OSD_TapePlayingBlockInfo(string blockinfo)
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("...Starting Block " + blockinfo);

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when a tape block is skipped (because it is empty)
		/// </summary>
		public void OSD_TapePlayingSkipBlockInfo(string blockinfo)
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("...Skipping Empty Block " + blockinfo);

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when a tape is started automatically
		/// </summary>
		public void OSD_TapeEndDetected(string blockinfo)
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("...Skipping Empty Block " + blockinfo);

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when user has manually skipped to the next block
		/// </summary>
		public void OSD_TapeNextBlock(string blockinfo)
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("Manual Skip Next " + blockinfo);

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that is fired when user has manually skipped to the next block
		/// </summary>
		public void OSD_TapePrevBlock(string blockinfo)
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}


			sb.Append("Manual Skip Prev " + blockinfo);

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Tape message that prints the current status of the tape device
		/// </summary>
		public void OSD_ShowTapeStatus()
		{
			StringBuilder sb = new StringBuilder();

			if (_tapeInfo.Count == 0)
			{
				sb.Append("No Tape Loaded");
				SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
				return;
			}

			sb.Append("Status: ");

			if (_machine.TapeDevice.TapeIsPlaying)
				sb.Append("PLAYING");
			else
				sb.Append("STOPPED");

			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
			sb.Clear();

			sb.Append("Tape: " + _machine.TapeMediaIndex + ": " + _tapeInfo[_machine.TapeMediaIndex].Name);
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
			sb.Clear();

			sb.Append("Block: ");
			sb.Append("(" + (_machine.TapeDevice.CurrentDataBlockIndex + 1) +
				" of " + _machine.TapeDevice.DataBlocks.Count + ") " +
				_machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].BlockDescription);
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
			sb.Clear();

			sb.Append("Block Pos: ");

			int pos = _machine.TapeDevice.Position;
			int end = _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].DataPeriods.Count;
			double p = 0;
			if (end != 0)
				p = ((double)pos / (double)end) * 100.0;

			sb.Append(p.ToString("N0") + "%");
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
			sb.Clear();

			// get position within the tape itself
			sb.Append("Tape Pos: ");
			var ind = _machine.TapeDevice.CurrentDataBlockIndex;
			int cnt = 0;
			for (int i = 0; i < ind; i++)
			{
				cnt += _machine.TapeDevice.DataBlocks[i].DataPeriods.Count;
			}
			// now we are at our current block
			int ourPos = cnt + pos;
			cnt += end;
			// count periods in the remaining blocks
			for (int i = ind + 1; i < _machine.TapeDevice.DataBlocks.Count; i++)
			{
				cnt += _machine.TapeDevice.DataBlocks[i].DataPeriods.Count;
			}
			// work out overall position within the tape
			p = 0;
			p = ((double)ourPos / (double)cnt) * 100.0;
			sb.Append(p.ToString("N0") + "%");
			SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape);
		}

		/// <summary>
		/// Checks whether message category is allowed to be sent
		/// </summary>
		public bool CheckMessageSettings(MessageCategory category)
		{
			switch (Settings.OSDMessageVerbosity)
			{
				case OSDVerbosity.Full:
					return true;
				case OSDVerbosity.None:
					return false;
				case OSDVerbosity.Medium:
					switch (category)
					{
						case MessageCategory.Disk:
						case MessageCategory.Emulator:
						case MessageCategory.Tape:
						case MessageCategory.Misc:
							return true;
						default:
							return false;
					}
				default:
					return true;
			}
		}

		/// <summary>
		/// Defines the different message categories
		/// </summary>
		public enum MessageCategory
		{
			/// <summary>
			/// No defined category as such
			/// </summary>
			Misc,
			/// <summary>
			/// User generated input messages (at the moment only tape/disk controls)
			/// </summary>
			Input,
			/// <summary>
			/// Tape device generated messages
			/// </summary>
			Tape,
			/// <summary>
			/// Disk device generated messages
			/// </summary>
			Disk,
			/// <summary>
			/// Emulator generated messages
			/// </summary>
			Emulator
		}
	}
}
