using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Rds
{
    class RdsDecoder
    {
        private static ConstructorInfo[] constructorInfos;
        private static RdsMessageAttribute[] messageTypes;

        private static void InitializeMessageTypes()
        {
            if (constructorInfos == null)
            {
                constructorInfos = new ConstructorInfo[Byte.MaxValue];
                messageTypes = new RdsMessageAttribute[Byte.MaxValue];
            }
            else
                return;

            Type[] typeCandidates = typeof(RdsDecoder).Assembly.GetTypes();
            bool validTypeFound = false;
            foreach (Type typeCandidate in typeCandidates)
            {
                Attribute customAttributes = typeCandidate.GetCustomAttributes(typeof(RdsMessageAttribute)).FirstOrDefault();
                if (customAttributes == null)
                    continue;

                RdsMessageAttribute rdsAttribute = customAttributes as RdsMessageAttribute;
                ConstructorInfo constructorInfo = typeCandidate.GetConstructor(new Type[] { typeof(byte[]) });
                if (constructorInfo == null)
                    throw new RdsException("constructor for {0} missing.", typeCandidate.Name);
                messageTypes[rdsAttribute.MessageElementCode] = rdsAttribute;
                constructorInfos[rdsAttribute.MessageElementCode] = constructorInfo;
                validTypeFound = true;
            }

            if (!validTypeFound)
                throw new RdsException("failed to load rds message types");
        }

        public RdsMessage DecodeRdsMessage(byte[] packetPayload)
        {
            if (packetPayload.Length == 2 && packetPayload[0] == 0xfe && packetPayload[1] == 0xff)
                return null;
            
            MemoryStream level1 = new MemoryStream(packetPayload, false);
            RdsMessageStream ms = new RdsMessageStream(level1);

            if (ms.ReadUInt8() != 0xfe)
                return null;

            //Message container decodieren.
            ms.BeginCrcCalculation();
            ushort add = ms.ReadUInt16BE();
            int siteAddress = add & 0xffc0;
            int encoderAddress = add & 0x003f;

            byte sqc = ms.ReadUInt8();

            byte mfl = ms.ReadUInt8();

            byte[] msg = ms.ReadBytes(mfl);
            ms.EndCrcCalculation();

            ushort crc = ms.ReadUInt16BE();
            if (!ms.ValidateCrc(crc))
                return null;

            if (ms.ReadUInt8() != 0xff)
                return null;

            RdsMessage result = DecodeInnerMessage(siteAddress, encoderAddress, sqc, msg);
            return result;
        }

        private RdsMessage DecodeInnerMessage(int siteAddress, int encoderAddress, byte sequenceCounter, byte[] buffer)
        {
            // file:///C:/Users/Anwender/Desktop/Pile%20of%20Shame/UECP_6_02_final_060912.pdf
            InitializeMessageTypes();

            MemoryStream ms = new MemoryStream(buffer, false);
            byte mec = ms.ReadUInt8();
            if (messageTypes[mec] == null || constructorInfos[mec] == null)
                throw new NotImplementedException(String.Format("RDS Message Type {0:X2}", mec));

            RdsMessageAttribute rdsMessageAttribute = messageTypes[mec];
            ConstructorInfo rdsMessageConstructor = constructorInfos[mec];
            RdsMessage result = null;
            switch (rdsMessageAttribute.HeaderType)
            {
                case RdsMessageHeaderType.Mel:
                    byte mel = ms.ReadUInt8();
                    result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(mel) });
                    break;
                case RdsMessageHeaderType.None:
                    result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(ms.GetAvailableBytes()) });
                    break;
                case RdsMessageHeaderType.DsnPsn:
                    byte dsn = ms.ReadUInt8();
                    byte psn = ms.ReadUInt8();
                    result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(ms.GetAvailableBytes()) });
                    ((IDsn)result).DataSetNumber = dsn;
                    ((IPsn)result).ProgrammeServiceNumber = psn;
                    break;
                case RdsMessageHeaderType.DsnPsnMel:
                    byte dsn2 = ms.ReadUInt8();
                    byte psn2 = ms.ReadUInt8();
                    byte mel2 = ms.ReadUInt8();
                    result = (RdsMessage)rdsMessageConstructor.Invoke(new object[] { ms.ReadBytes(mel2) });
                    ((IDsn)result).DataSetNumber = dsn2;
                    ((IPsn)result).ProgrammeServiceNumber = psn2;
                    break;
                default:
                    throw new NotImplementedException(rdsMessageAttribute.HeaderType.ToString());
            }

            result.SiteAddress = siteAddress;
            result.EncoderAddress = encoderAddress;
            result.SequenceNumber = sequenceCounter;
            return result;
        }

    }
}
