using log4net;
using log4net.Repository.Hierarchy;
using skyscraper5.Dvb;
using skyscraper5.Skyscraper.IO;
using System.IO;

namespace skyscraper5.Mpeg2
{
    public class PsiDecoder : ITsPacketProcessor, IPayloadUnitDecoder
    {
        public IPsiProcessor PsiProcessor { get; }
		private int pid;
        private int state;
        private byte headerA, headerB, headerC;
        private byte[] psiPayload;
        private int psiPayloadNeeded;
        private int psiPayloadOffset;
        private int copyOps;
		private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);

		public PsiDecoder(int pid, IPsiProcessor psiProcessor)
        {
            PsiProcessor = psiProcessor;
            this.pid = pid;
        }

        public void PushPacket(TsPacket packet)
        {
			Span<byte> span = packet.GetPayload();
            int automataIterations = 0;

            if (packet.PID == 0x0881)
                ;

            while(span.Length > 0)
            {
                switch(state)
                {
                    case 0:
                        //Noch keine PSI am zusammenbauen.
                        if (!packet.PayloadUnitStart)
                        {
                            //im TS Paket beginnt keine neue, also wegwerfen.
                            return;
                        }
                        byte startOffset = span[0];
                        span = span.Slice(1);
                        span = span.Slice(startOffset);
                        state = 1;
                        break;
                    case 1:
                        //Section Header Byte 1 extrahieren;
                        if (span.Length == 0)
                            return;
						if (automataIterations == 0)
						{
							if (packet.PayloadUnitStart)
								span = span.Slice(1);
						}
						if (span[0] == 0xff)
                        {
                            return;
                        }
                        headerA = span[0];
                        span = span.Slice(1);
                        state = 2;
                        break;
					case 2:
						//Section Header Byte 2 extrahieren;
						headerB = span[0];
						span = span.Slice(1);
						state = 3;
						break;
					case 3:
						//Section Header Byte 3 extrahieren;
						headerC = span[0];
						span = span.Slice(1);
						state = 4;
                        psiPayloadNeeded = headerB;
                        psiPayloadNeeded <<= 8;
                        psiPayloadNeeded += headerC;
                        psiPayloadNeeded &= 0x0fff;
                        psiPayload = new byte[psiPayloadNeeded];
						break;
                    case 4:
                        if (automataIterations == 0)
                        {
                            if (packet.PayloadUnitStart)
                                span = span.Slice(1);
                        }
                        int copyable = Math.Min(psiPayloadNeeded, span.Length);
                        Copy(span, 0, psiPayload, psiPayloadOffset, copyable);
                        psiPayloadNeeded -= copyable;
                        psiPayloadOffset += copyable;
                        span = span.Slice(copyable);
                        if (psiPayloadNeeded == 0)
                        {
                            AssemblePsiPayload(headerA, headerB, headerC, psiPayload);
							headerA = 0;
							headerB = 0;
							headerC = 0;
							psiPayload = null;
							psiPayloadNeeded = 0;
							psiPayloadOffset = 0;
							copyOps = 0;
							state = 1;
						}
						break;
                    default:
                        throw new NotImplementedException(String.Format("PSI Decoder finite automata state {0}", state));
				}
                automataIterations++;
            }
        }

		private void AssemblePsiPayload(byte headerA, byte headerB, byte headerC, byte[] psiPayload)
		{
            PsiSection section = new PsiSection();
            section.Append(new byte[] { headerA, headerB, headerC });
            section.HeaderComplete = true;
            section.Append(psiPayload);
            if (!section.Valid)
            {
                //logger.ErrorFormat("Invalid MPEG-2 section");
                OnCrcError?.Invoke(section, pid, section.SectionLength);
                return;
            }
            PsiProcessor.GatherPsi(section, pid);
            OnValidSection?.Invoke(section, pid, section.SectionLength);
		}

		public void PacketLoss()
        {
            state = 0;
            headerA = 0;
            headerB = 0;
            headerC = 0;
            psiPayload = null;
            psiPayloadNeeded = 0;
            psiPayloadOffset = 0;
            copyOps = 0;
        }

        private void Copy(Span<byte> input, int inputOffset, byte[] output, int outputOffset, int length)
        {
            for (int i = 0; i < length; i++)
            {
                output[outputOffset + i] = input[inputOffset + i];
            }
            copyOps++;
        }

		public delegate void OnSection(PsiSection section, int pid, long available);
		public event OnSection OnValidSection;
		public event OnSection OnCrcError;
	}
    
}
