using System;
using System.Collections.Generic;
using System.IO;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Dvb.Psi.Model;
using skyscraper5.Skyscraper;

namespace skyscraper5.Scorcher
{
	public class TsGeneratorStream : Stream
	{
		public TsGeneratorStream()
		{
			nullPacketGenerator = new NullPacketGenerator();
			unusedPids = new List<uint>();
			for (uint i = 32; i < 8186; i++)
			{
				unusedPids.Add(i);
			}
			unusedPrograms = new List<ushort>();
			for (ushort i = ushort.MinValue; i < ushort.MaxValue; i++)
			{
				unusedPrograms.Add(i);
			}
			rng = new Random();
		}


		private SdtGenerator sdtGenerator;
		private TotGenerator totGenerator;
		private TdtGenerator tdtGenerator;
		private CatGenerator catGenerator;
		private List<PmtGenerator> pmtGenerators;
		private NullPacketGenerator nullPacketGenerator;
		private DataPipingGenerator dataPipingGenerator;
		private PatGenerator patGenerator;

		private Random rng;
		private List<uint> unusedPids;
		private List<ushort> unusedPrograms;

		#region Public Methods

		public ushort? DataPipingPid { get; private set; }
		public SdtGeneratorService AddService(ushort serviceId, bool eitScheduleFlag = false, bool eitPresentFollowingFlag = false, RunningStatus runningStatus = RunningStatus.Running, bool freeCaMode = false)
		{
			if (!TransportStreamId.HasValue)
				TransportStreamId = rng.NextUInt16();
			if (!OriginalNetworkId.HasValue)
				OriginalNetworkId = rng.NextUInt16();
			if (sdtGenerator == null)
				sdtGenerator = new SdtGenerator(TransportStreamId.Value, OriginalNetworkId.Value);

			return sdtGenerator.CreateService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode);
		}

		public PmtGenerator AddProgram(ushort programId, ushort pmtPid, ushort pcrPid = 0x1fff)
		{
			if (!TransportStreamId.HasValue)
				TransportStreamId = rng.NextUInt16();
			if (patGenerator == null)
				patGenerator = new PatGenerator(TransportStreamId.Value);

			patGenerator.AddProgram(programId, pmtPid);

			if (pmtGenerators == null)
			{
				pmtGenerators = new List<PmtGenerator>();
			}

			PmtGenerator pmtGenerator = new PmtGenerator(pmtPid, programId, pcrPid);
			pmtGenerators.Add(pmtGenerator);
			unusedPids.Remove(pmtPid);
			unusedPids.Remove(pcrPid);
			return pmtGenerator;
		}

		public void AddCaSystem(ushort caSystemId, ushort caPid, byte[] privateData = null)
		{
			if (catGenerator == null)
				catGenerator = new CatGenerator();

			catGenerator.AddCaSystem(caSystemId, caPid, privateData);
		}

		public ushort? TransportStreamId { get; set; }
		public ushort? OriginalNetworkId { get; set; }

		public bool GenerateTdt
		{
			get => tdtGenerator != null;
			set
			{
				if (value)
					tdtGenerator = new TdtGenerator();
				else
					tdtGenerator = null;
			}
		}

		public bool GenerateTot
		{
			get => totGenerator != null;
			set
			{
				if (value)
					totGenerator = new TotGenerator();
				else
					totGenerator = null;
			}
		}
		#endregion


		private byte[] GetNextPacket()
		{
			if (sdtGenerator != null)
			{
				if (sdtGenerator.PacketAvailable())
				{
					return sdtGenerator.GeneratePacket();
				}
			}
			if (totGenerator != null)
			{
				if (totGenerator.PacketAvailable())
				{
					return totGenerator.GeneratePacket();
				}
			}
			if (tdtGenerator != null)
			{
				if (tdtGenerator.PacketAvailable())
				{
					return tdtGenerator.GeneratePacket();
				}
			}
			if (catGenerator != null)
			{
				if (catGenerator.PacketAvailable())
				{
					return catGenerator.GeneratePacket();
				}
			}
			if (pmtGenerators != null)
			{
				foreach (PmtGenerator pmtGenerator in pmtGenerators)
				{
					if (pmtGenerator.PacketAvailable())
					{
						return pmtGenerator.GeneratePacket();
					}
				}
			}
			if (patGenerator != null)
			{
				if (patGenerator.PacketAvailable())
				{
					return patGenerator.GeneratePacket();
				}
			}
			if (dataPipingGenerator != null)
			{
				if (dataPipingGenerator.PacketAvailable())
				{
					return dataPipingGenerator.GeneratePacket();
				}
			}
			return nullPacketGenerator.GeneratePacket();
		}
		

		public override void Flush()
		{
			throw new NotImplementedException();
		}

		private void ValidateArgument(long number, string name)
		{
			if (number % 188 != 0)
			{
				long didYouMean = number;
				didYouMean /= 188;
				didYouMean++;
				didYouMean *= 188;
				throw new IOException(String.Format("{0} must be dividable by 188! You said {1}, but you probably meant {2}.", number, didYouMean));
			}
		}

		private long totalBytesDelivered;
		public override int Read(byte[] buffer, int offset, int count)
		{
			int result = 0;
			ValidateArgument(count, nameof(count));
			for (int i = 0; i < count / 188; i++)
			{
				byte[] nextPacket = GetNextPacket();
				Array.Copy(nextPacket, 0, buffer, offset, 188);
				offset += 188;
				totalBytesDelivered += 188;
				result += 188;
			}
			return result;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Artificially limits the length of the TS.
		/// </summary>
		/// <param name="value"></param>
		/// <exception cref="NotImplementedException"></exception>
		public override void SetLength(long value)
		{
			ValidateArgument(value, nameof(value));
			throw new NotImplementedException();
		}

		/// <summary>
		/// Writes something into the data-pipe PID. This will create a service inside the TS, if needed.
		/// </summary>
		/// <param name="buffer"></param>
		/// <param name="offset"></param>
		/// <param name="count"></param>
		/// <exception cref="NotImplementedException"></exception>
		public override void Write(byte[] buffer, int offset, int count)
		{
			if (!DataPipingPid.HasValue)
			{
				DataPipingPid = (ushort)rng.NextListItem(unusedPids, true);
			}
			if (!DataPipingServicePresent)
			{
				ushort programId = rng.NextListItem(unusedPrograms, true);
				ushort pmtPid = (ushort)rng.NextListItem(unusedPids, true);
				PmtGenerator pmtGenerator = AddProgram(programId, pmtPid);
				PmtGeneratorStream pmtGeneratorStream = pmtGenerator.AddStream(0xff, DataPipingPid.Value);
				pmtGeneratorStream.AddDescriptor(new DataBroadcastIdDescriptor(0x0001));
				pmtGeneratorStream.AddDescriptor(new StreamIdentifierDescriptor(0x01));
				pmtGeneratorStream.AddDescriptor(new PrivateDataSpecifierDescriptor(0x534f5048));
				SdtGeneratorService service = AddService(programId);
				service.AddName("Scorcher Data Piping", "sophia.net", ServiceDescriptor.ServiceTypeCoding.DataBroadcast);
				service.AddDescriptor(new DataBroadcastDescriptor(0x0001, 0x01, null, "eng", "Data Pipe"));
				service.AddDescriptor(new PrivateDataSpecifierDescriptor(0x534f5048));
				DataPipingServicePresent = true;
			}
			
			if (dataPipingGenerator == null)
			{
				dataPipingGenerator = new DataPipingGenerator(DataPipingPid.Value);
			}

			byte[] dataToEnqueue = new byte[count];
			Array.Copy(buffer, offset, dataToEnqueue, 0, count);
			dataPipingGenerator.Append(dataToEnqueue);
		}

		private bool DataPipingServicePresent;

		public override bool CanRead { get; }
		public override bool CanSeek { get; }
		public override bool CanWrite { get; }
		public override long Length { get; }
		public override long Position { get; set; }
	}
}
