using skyscraper8.Mpeg2.Psi;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;

namespace skyscraper8.Experimentals.NdsSsu
{
	internal class NdsSsuParser : PrivateSectionParser
	{
		private readonly NdsSsuHandler _handler;
		private readonly int _pid;

		public NdsSsuParser(NdsSsuHandler handler, int pid)
		{
			_handler = handler;
			_pid = pid;
		}

		private int error;
		protected override void HandleTable(byte tableId, int sourcePid, byte[] payload)
		{
			error++;
		}

		protected override void HandleTable(byte tableId, int sourcePid, byte sectionNumber, byte lastSectionNumber, ushort tableIdExtension, byte[] payload)
		{
			if (tableId != 0x9e)
			{
				_handler.OnNdsSsuError(_pid, NdsSsuError.WrongTable);
				return;
			}

			if (payload[0] == 0x00 && payload[1] == 0xff && payload[2] == 0x00 && payload[3] == 0x00)
			{
				//TODO: payload name
				return;
			}

			if (payload[0] != 0x01)
			{
				_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
				return;
			}

			if (payload[1] != 0xff)
			{
				_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
				return;
			}

			if (payload[2] != 0x00)
			{
				_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
				return;
			}

			if (payload[3] > 0x20)
			{
				_handler.OnNdsSsuError(_pid, NdsSsuError.InvalidMagic);
				return;
			}

			if (dataMaps == null)
				dataMaps = new NdsSsuDataMap[ushort.MaxValue];
			if (dataMaps[tableIdExtension] == null)
			{
				dataMaps[tableIdExtension] = new NdsSsuDataMap();
				if (!_handler.TestNdsSsuFileExists(_pid, tableIdExtension))
				{
					_handler.OnNdsSsuFileAnnouncement(_pid, tableIdExtension);
				}
				else
				{
					return;
				}
			}
			if (dataMaps[tableIdExtension].WasProcessed)
			{
				return;
			}
			
			if (dataMaps[tableIdExtension].WasAlreadyPushed(payload, sectionNumber, lastSectionNumber))
			{
				if (dataMaps[tableIdExtension].IsComplete)
				{
					_handler.OnNdsSsuFileComplete(_pid, tableIdExtension, dataMaps[tableIdExtension]);
					dataMaps[tableIdExtension].SetSucessfullyProcessed();
				}
				return;
			}
			dataMaps[tableIdExtension].PushPacket(payload,sectionNumber,lastSectionNumber);

			Tuple<int, int, bool> completionStatus = dataMaps[tableIdExtension].GetCompletionStatus();
			_handler.OnNdsSsuProgress(_pid, tableIdExtension, completionStatus.Item1, completionStatus.Item2, completionStatus.Item3);
		}

		private NdsSsuDataMap[] dataMaps;
	}

	public class NdsSsuDataMap : IDisposable
	{

		private Superblock[] superblocks;

		public void PushPacket(byte[] payload, byte currentSection, byte lastSection)
		{
			byte maxNumSuperblocks = payload[3];
			maxNumSuperblocks++;
			if (superblocks == null)
				superblocks = new Superblock[maxNumSuperblocks];
			if (superblocks.Length < maxNumSuperblocks)
				Array.Resize(ref superblocks, maxNumSuperblocks);
			if (superblocks[payload[3]] == null)
			{
				superblocks[payload[3]] = new Superblock(lastSection);
			}

			superblocks[payload[3]].PushPacket(payload, currentSection, lastSection);
		}

		class Superblock : IDisposable
		{
			private byte[][] blocks;

			public Superblock(byte lastSection)
			{
				int maxNumBlocks = lastSection;
				maxNumBlocks++;
				blocks = new byte[maxNumBlocks][];
			}

			public void PushPacket(byte[] payload, byte currentSection, byte lastSection)
			{
				int maxNumBlocks = lastSection;
				maxNumBlocks++;
				if (blocks.Length > maxNumBlocks)
					Array.Resize(ref blocks, maxNumBlocks);
				blocks[currentSection] = payload;
			}

			public bool WasAlreadyPushed(byte currentSection)
			{
				byte maxNumBlocks = currentSection;
				maxNumBlocks++;

				if (blocks.Length < currentSection)
					return false;

				return blocks[currentSection] != null;
			}

			public bool IsComplete
			{
				get
				{
					if (blocks == null)
						return false;

					for (int i = 0; i < blocks.Length; i++)
					{
						if (blocks[i] == null)
							return false;
					}

					return true;
				}
			}

			public int BlocksDone
			{
				get
				{
					int result = 0;
					for (int i = 0; i < blocks.Length; i++)
					{
						if (blocks[i] != null)
							result++;
					}

					return result;
				}
			}

			public int BlocksTotal => blocks.Length;

			public void WriteToStream(Stream fileStream)
			{
				for (int i = 0; i < blocks.Length; i++)
				{
					fileStream.Write(blocks[i], 4, blocks[i].Length - 4);
				}
			}

			public void Dispose()
			{
				for (int i = 0; i < blocks.Length; i++)
				{
					blocks[i] = null;
				}
			}

			public long CalculateLength()
			{
				long result = 0;
				for (int i = 0; i < blocks.Length; i++)
				{
					result += (blocks[i].Length - 4);
				}
				return result;
			}
		}

		public bool WasAlreadyPushed(byte[] payload, byte sectionNumber, byte lastSectionNumber)
		{
			if (superblocks == null)
				return false;

			byte maxNumSuperblocks = payload[3];
			maxNumSuperblocks++;
			if (superblocks.Length < maxNumSuperblocks)
				return false;

			if (superblocks[payload[3]] == null)
				return false;

			return superblocks[payload[3]].WasAlreadyPushed(sectionNumber);
		}

		public bool IsComplete
		{
			get
			{
				if (superblocks == null)
					return false;

				for (int i = 0; i < superblocks.Length; i++)
				{
					if (superblocks[i] == null)
						return false;
					if (!superblocks[i].IsComplete)
						return false;
				}

				return true;
			}
		}
		
		public Tuple<int, int, bool> GetCompletionStatus()
		{
			int done = 0;
			int total = 0;
			bool theresMore = false;
			for (int i = 0; i < this.superblocks.Length; i++)
			{
				if (superblocks[i] != null)
				{
					done += superblocks[i].BlocksDone;
					total += superblocks[i].BlocksTotal;
				}
				else
				{
					theresMore = true;
				}
			}

			return new Tuple<int, int, bool>(done, total, theresMore);
		}
		
		public bool WasProcessed { get; private set; }

		public void SetSucessfullyProcessed()
		{
			WasProcessed = true;
		}

		public void WriteToStream(Stream fileStream)
		{
			for (int x = 0; x < superblocks.Length; x++)
			{
				superblocks[x].WriteToStream(fileStream);
			}
		}

		public byte[] WriteToByteArray()
		{
			byte[] buffer = new byte[CalculateLength()];
			MemoryStream ms = new MemoryStream(buffer);
			WriteToStream(ms);
			return ms.GetBuffer();
		}

		public void Dispose()
		{
			for (int x = 0; x < superblocks.Length; x++)
			{
				superblocks[x].Dispose();
				superblocks[x] = null;
			}
		}

		public long CalculateLength()
		{
			long result = 0;
			for (int x = 0; x < superblocks.Length; x++)
			{
				result += superblocks[x].CalculateLength();
			}
			return result;
		}
	}
}

