using System;
using System.Collections.Generic;
using System.IO;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Scorcher
{
	public class PmtGenerator : PsiGenerator
	{
		public ushort ProgramNumber { get; }
		public ushort PcrPid { get; }

		public PmtGenerator(uint pid, ushort programNumber, ushort PcrPid) : base(new TimeSpan(0,0,3), pid)
		{
			ProgramNumber = programNumber;
			this.PcrPid = PcrPid;
			descriptors = new List<TsDescriptor>();
			streams = new List<PmtGeneratorStream>();
		}

		private List<TsDescriptor> descriptors;
		private List<PmtGeneratorStream> streams;

		private byte[] SerializeDescriptors()
		{
			MemoryStream ms = new MemoryStream();
			foreach (TsDescriptor descriptor in descriptors)
			{
				ms.WriteUInt8(descriptor.GetDescriptorId());	//descriptor_tag
				
				byte[] descriptorBytes = descriptor.Serialize();
				ms.WriteUInt8((byte)descriptorBytes.Length);	//descriptor_length
				ms.Write(descriptorBytes, 0, descriptorBytes.Length);
			}
			return ms.ToArray();
		}

		private byte[] SerializeStreams()
		{
			MemoryStream ms = new MemoryStream();
			foreach (PmtGeneratorStream stream in streams)
			{
				ms.WriteUInt8(stream.StreamType);
				ms.WriteUInt16BE((ushort)(stream.ElementaryPid | 0xe000));

				byte[] descriptorLoop = stream.SerializeDescriptors();
				ms.WriteUInt16BE((ushort)(descriptorLoop.Length | 0xf000));	//ES_Info_length
				ms.Write(descriptorLoop, 0, descriptorLoop.Length);
			}

			return ms.ToArray();
		}

		public override byte[] BuildPsi()
		{
			byte[] descriptorBuffer = SerializeDescriptors();
			int programInfoLength = descriptorBuffer.Length;
			byte[] streamBuffer = SerializeStreams();
			int sectionLength = 9 + descriptorBuffer.Length + streamBuffer.Length + 4;

			//TODO: calculate section length here

			MemoryStream ms = new MemoryStream();
			ms.WriteUInt8(0x02);	//table_id

			byte byte2 = 0;
			byte2 |= 0x80;	//section_syntax_indicator
			//'0'
			byte2 |= 0x30;  //reserved
			byte2 += (byte)((sectionLength & 0x0300) >> 8);
			ms.WriteUInt8(byte2);   //section length, part 1

			byte byte3 = (byte)(sectionLength & 0x00ff);
			ms.WriteUInt8(byte3);   //section length, part 2

			ms.WriteUInt16BE(ProgramNumber);

			byte byte4 = 0;
			byte4 |= 0xc0;	//reserved
			byte4 |= (byte)((versionNumber & 0x1f) << 1);	//version number
			byte4 |= 0x01;	//current next indicator
			ms.WriteUInt8(byte4);

			ms.WriteUInt8(0);	//section number
			ms.WriteUInt8(0);	//last section number

			ms.WriteUInt16BE((ushort)(PcrPid | 0xe000));	//reserved & PCR_PID

			ms.WriteUInt16BE((ushort)(programInfoLength | 0xf000));	//reserved & program info length

			ms.Write(descriptorBuffer, 0, descriptorBuffer.Length);	//descriptors()

			ms.Write(streamBuffer, 0, streamBuffer.Length);

			uint crc = DvbCrc32.CreateCrc(ms, 0, (int)ms.Position);
			ms.WriteUInt32BE(crc);

			return ms.ToArray();
		}

		public void AddDescriptor(PrivateDataSpecifierDescriptor privateDataSpecifierDescriptor)
		{
			descriptors.Add(privateDataSpecifierDescriptor);
			versionNumber++;
		}

		public PmtGeneratorStream AddStream(byte streamType, ushort pid)
		{
			PmtGeneratorStream stream = new PmtGeneratorStream(streamType, pid);
			streams.Add(stream);
			versionNumber++;
			return stream;
		}
	}
}
