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

namespace skyscraper5.Scorcher
{
	internal class SdtGenerator : PsiGenerator
	{
		public ushort TransportStreamId { get; }
		public ushort OriginalNetworkId { get; }

		public SdtGenerator(ushort transportStreamId, ushort originalNetworkId) : base(new TimeSpan(0, 0, 4), 0x0011)
		{
			TransportStreamId = transportStreamId;
			OriginalNetworkId = originalNetworkId;
			services = new List<SdtGeneratorService>();
		}

		public SdtGeneratorService CreateService(ushort serviceId, bool eitScheduleFlag = false,
			bool eitPresentFollowingFlag = false, RunningStatus runningStatus = RunningStatus.Running,
			bool freeCaMode = false)
		{
			SdtGeneratorService result = new SdtGeneratorService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode);
			services.Add(result);
			return result;
		}

		private List<SdtGeneratorService> services;

		private byte[] SerializeServices()
		{
			MemoryStream ms = new MemoryStream();
			foreach (SdtGeneratorService service in services)
			{
				byte[] descriptorBuffer = service.SerializeDescriptors();
				ushort descriptorLength = (ushort)descriptorBuffer.Length;

				ms.WriteUInt16BE(service.ServiceId);

				byte byteA = 0;
				byteA |= 0xfc;	//reserved	
				if (service.EitScheduleFlag)
					byteA |= 0x02;
				if (service.EitPresentFollowingFlag)
					byteA |= 0x01;
				ms.WriteUInt8(byteA);

				byte byteB = 0;
				byteB |= (byte)((int)service.RunningStatus << 5);
				if (service.FreeCaMode)
					byteB |= 0x10;
				byteB |= (byte)((descriptorLength & 0x0f00) >> 8);
				ms.WriteUInt8(byteB);

				ms.WriteUInt8((byte)(descriptorLength & 0x00ff));
				ms.Write(descriptorBuffer, 0, descriptorBuffer.Length);
			}

			return ms.ToArray();
		}

		public override byte[] BuildPsi()
		{
			byte[] serializedServices = SerializeServices();

			ushort sectionLength = (ushort)(2 + 3 + 2 + 1 + serializedServices.Length + 4);	//TODO: calculate section length
			if (sectionLength > 1021)
				throw new Exception("section too long");
			sectionLength &= 0x3fff;

			MemoryStream ms = new MemoryStream();
			ms.WriteUInt8(0x42);

			byte byteA = 0;
			byteA |= 0x80;	//section_syntax_indicator
			byteA |= 0x40;	//reserved_future_use
			byteA |= 0x30;	//reserved
			byteA |= (byte)(sectionLength >> 8);
			ms.WriteUInt8(byteA);

			byte byteB = (byte)(sectionLength & 0x00ff);
			ms.WriteUInt8(byteB);

			ms.WriteUInt16BE(TransportStreamId);

			byte byteC = 0;
			byteC |= 0xc0;	//reserved
			byteC |= (byte)(versionNumber << 1);	//version_number
			byteC |= 0x01;	//current_next_indicator
			ms.WriteUInt8(byteC);

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

			ms.WriteUInt16BE(OriginalNetworkId);	//original network id
			ms.WriteUInt8(0xff);	//reserved future use

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

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

			return ms.ToArray();
		}
	}
}
