//============================================================================
// BDInfo - Blu-ray Video and Audio Analysis Tool
// Copyright © 2010 Cinema Squid
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//=============================================================================

using System;
using System.Collections.Generic;

namespace BDInfo
{
    public enum TSStreamType : byte
    {
        Unknown = 0,
        MPEG1_VIDEO = 0x01,
        MPEG2_VIDEO = 0x02,
        AVC_VIDEO = 0x1b,
        MVC_VIDEO = 0x20,
        VC1_VIDEO = 0xea,
        MPEG1_AUDIO = 0x03,
        MPEG2_AUDIO = 0x04,
        LPCM_AUDIO = 0x80,
        AC3_AUDIO = 0x81,
        AC3_PLUS_AUDIO = 0x84,
        AC3_PLUS_SECONDARY_AUDIO = 0xA1,
        AC3_TRUE_HD_AUDIO = 0x83,
        DTS_AUDIO = 0x82,
        DTS_HD_AUDIO = 0x85,
        DTS_HD_SECONDARY_AUDIO = 0xA2,
        DTS_HD_MASTER_AUDIO = 0x86,
        PRESENTATION_GRAPHICS = 0x90,
        INTERACTIVE_GRAPHICS = 0x91,
        SUBTITLE = 0x92
    }

    public enum TSVideoFormat : byte
    {
        Unknown = 0,
        VIDEOFORMAT_480i = 1,
        VIDEOFORMAT_576i = 2,
        VIDEOFORMAT_480p = 3,
        VIDEOFORMAT_1080i = 4,
        VIDEOFORMAT_720p = 5,
        VIDEOFORMAT_1080p = 6,
        VIDEOFORMAT_576p = 7,
    }

    public enum TSFrameRate : byte
    {
        Unknown = 0,
        FRAMERATE_23_976 = 1,
        FRAMERATE_24 = 2,
        FRAMERATE_25 = 3,
        FRAMERATE_29_97 = 4,
        FRAMERATE_50 = 6,
        FRAMERATE_59_94 = 7
    }

    public enum TSChannelLayout : byte
    {
        Unknown = 0,
        CHANNELLAYOUT_MONO = 1,
        CHANNELLAYOUT_STEREO = 3,
        CHANNELLAYOUT_MULTI = 6,
        CHANNELLAYOUT_COMBO = 12
    }

    public enum TSSampleRate : byte
    {
        Unknown = 0,
        SAMPLERATE_48 = 1,
        SAMPLERATE_96 = 4,
        SAMPLERATE_192 = 5,
        SAMPLERATE_48_192 = 12,
        SAMPLERATE_48_96 = 14
    }

    public enum TSAspectRatio
    {
        Unknown = 0,
        ASPECT_4_3 = 2,
        ASPECT_16_9 = 3,
        ASPECT_2_21 = 4
    }

    public class TSDescriptor
    {
        public byte Name;
        public byte[] Value;

        public TSDescriptor(byte name, byte length)
        {
            Name = name;
            Value = new byte[length];
        }

        public TSDescriptor Clone()
        {
            var descriptor =
                new TSDescriptor(Name, (byte)Value.Length);
            Value.CopyTo(descriptor.Value, 0);
            return descriptor;
        }
    }

    public abstract class TSStream
    {
        public TSStream()
        {
        }

        public override string ToString()
        {
            return string.Format("{0} ({1})", CodecShortName, PID);
        }

        public ushort PID;
        public TSStreamType StreamType;
        public List<TSDescriptor> Descriptors = null;
        public long BitRate = 0;
        public long ActiveBitRate = 0;
        public bool IsVBR = false;
        public bool IsInitialized = false;
        public string LanguageName;
        public bool IsHidden = false;

        public ulong PayloadBytes = 0;
        public ulong PacketCount = 0;
        public double PacketSeconds = 0;
        public int AngleIndex = 0;

        public ulong PacketSize => PacketCount * 192;

        private string _LanguageCode;
        public string LanguageCode
        {
            get => _LanguageCode;
            set
            {
                _LanguageCode = value;
                LanguageName = LanguageCodes.GetName(value);
            }
        }

        public bool IsVideoStream
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.MPEG1_VIDEO:
                    case TSStreamType.MPEG2_VIDEO:
                    case TSStreamType.AVC_VIDEO:
                    case TSStreamType.MVC_VIDEO:
                    case TSStreamType.VC1_VIDEO:
                        return true;

                    default:
                        return false;
                }
            }
        }

        public bool IsAudioStream
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.MPEG1_AUDIO:
                    case TSStreamType.MPEG2_AUDIO:
                    case TSStreamType.LPCM_AUDIO:
                    case TSStreamType.AC3_AUDIO:
                    case TSStreamType.AC3_PLUS_AUDIO:
                    case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
                    case TSStreamType.AC3_TRUE_HD_AUDIO:
                    case TSStreamType.DTS_AUDIO:
                    case TSStreamType.DTS_HD_AUDIO:
                    case TSStreamType.DTS_HD_SECONDARY_AUDIO:
                    case TSStreamType.DTS_HD_MASTER_AUDIO:
                        return true;

                    default:
                        return false;
                }
            }
        }

        public bool IsGraphicsStream
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.PRESENTATION_GRAPHICS:
                    case TSStreamType.INTERACTIVE_GRAPHICS:
                        return true;

                    default:
                        return false;
                }
            }
        }

        public bool IsTextStream
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.SUBTITLE:
                        return true;

                    default:
                        return false;
                }
            }
        }

        public string CodecName
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.MPEG1_VIDEO:
                        return "MPEG-1 Video";
                    case TSStreamType.MPEG2_VIDEO:
                        return "MPEG-2 Video";
                    case TSStreamType.AVC_VIDEO:
                        return "MPEG-4 AVC Video";
                    case TSStreamType.MVC_VIDEO:
                        return "MPEG-4 MVC Video";
                    case TSStreamType.VC1_VIDEO:
                        return "VC-1 Video";
                    case TSStreamType.MPEG1_AUDIO:
                        return "MP1 Audio";
                    case TSStreamType.MPEG2_AUDIO:
                        return "MP2 Audio";
                    case TSStreamType.LPCM_AUDIO:
                        return "LPCM Audio";
                    case TSStreamType.AC3_AUDIO:
                        if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
                            return "Dolby Digital EX Audio";
                        else
                            return "Dolby Digital Audio";
                    case TSStreamType.AC3_PLUS_AUDIO:
                    case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
                        return "Dolby Digital Plus Audio";
                    case TSStreamType.AC3_TRUE_HD_AUDIO:
                        return "Dolby TrueHD Audio";
                    case TSStreamType.DTS_AUDIO:
                        if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
                            return "DTS-ES Audio";
                        else
                            return "DTS Audio";
                    case TSStreamType.DTS_HD_AUDIO:
                        return "DTS-HD High-Res Audio";
                    case TSStreamType.DTS_HD_SECONDARY_AUDIO:
                        return "DTS Express";
                    case TSStreamType.DTS_HD_MASTER_AUDIO:
                        return "DTS-HD Master Audio";
                    case TSStreamType.PRESENTATION_GRAPHICS:
                        return "Presentation Graphics";
                    case TSStreamType.INTERACTIVE_GRAPHICS:
                        return "Interactive Graphics";
                    case TSStreamType.SUBTITLE:
                        return "Subtitle";
                    default:
                        return "UNKNOWN";
                }
            }
        }

        public string CodecAltName
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.MPEG1_VIDEO:
                        return "MPEG-1";
                    case TSStreamType.MPEG2_VIDEO:
                        return "MPEG-2";
                    case TSStreamType.AVC_VIDEO:
                        return "AVC";
                    case TSStreamType.MVC_VIDEO:
                        return "MVC";
                    case TSStreamType.VC1_VIDEO:
                        return "VC-1";
                    case TSStreamType.MPEG1_AUDIO:
                        return "MP1";
                    case TSStreamType.MPEG2_AUDIO:
                        return "MP2";
                    case TSStreamType.LPCM_AUDIO:
                        return "LPCM";
                    case TSStreamType.AC3_AUDIO:
                        return "DD AC3";
                    case TSStreamType.AC3_PLUS_AUDIO:
                    case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
                        return "DD AC3+";
                    case TSStreamType.AC3_TRUE_HD_AUDIO:
                        return "Dolby TrueHD";
                    case TSStreamType.DTS_AUDIO:
                        return "DTS";
                    case TSStreamType.DTS_HD_AUDIO:
                        return "DTS-HD Hi-Res";
                    case TSStreamType.DTS_HD_SECONDARY_AUDIO:
                        return "DTS Express";
                    case TSStreamType.DTS_HD_MASTER_AUDIO:
                        return "DTS-HD Master";
                    case TSStreamType.PRESENTATION_GRAPHICS:
                        return "PGS";
                    case TSStreamType.INTERACTIVE_GRAPHICS:
                        return "IGS";
                    case TSStreamType.SUBTITLE:
                        return "SUB";
                    default:
                        return "UNKNOWN";
                }
            }
        }

        public string CodecShortName
        {
            get
            {
                switch (StreamType)
                {
                    case TSStreamType.MPEG1_VIDEO:
                        return "MPEG-1";
                    case TSStreamType.MPEG2_VIDEO:
                        return "MPEG-2";
                    case TSStreamType.AVC_VIDEO:
                        return "AVC";
                    case TSStreamType.MVC_VIDEO:
                        return "MVC";
                    case TSStreamType.VC1_VIDEO:
                        return "VC-1";
                    case TSStreamType.MPEG1_AUDIO:
                        return "MP1";
                    case TSStreamType.MPEG2_AUDIO:
                        return "MP2";
                    case TSStreamType.LPCM_AUDIO:
                        return "LPCM";
                    case TSStreamType.AC3_AUDIO:
                        if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
                            return "AC3-EX";
                        else
                            return "AC3";
                    case TSStreamType.AC3_PLUS_AUDIO:
                    case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
                        return "AC3+";
                    case TSStreamType.AC3_TRUE_HD_AUDIO:
                        return "TrueHD";
                    case TSStreamType.DTS_AUDIO:
                        if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
                            return "DTS-ES";
                        else
                            return "DTS";
                    case TSStreamType.DTS_HD_AUDIO:
                        return "DTS-HD HR";
                    case TSStreamType.DTS_HD_SECONDARY_AUDIO:
                        return "DTS Express";
                    case TSStreamType.DTS_HD_MASTER_AUDIO:
                        return "DTS-HD MA";
                    case TSStreamType.PRESENTATION_GRAPHICS:
                        return "PGS";
                    case TSStreamType.INTERACTIVE_GRAPHICS:
                        return "IGS";
                    case TSStreamType.SUBTITLE:
                        return "SUB";
                    default:
                        return "UNKNOWN";
                }
            }
        }

        public virtual string Description => "";

        public abstract TSStream Clone();

        protected void CopyTo(TSStream stream)
        {
            stream.PID = PID;
            stream.StreamType = StreamType;
            stream.IsVBR = IsVBR;
            stream.BitRate = BitRate;
            stream.IsInitialized = IsInitialized;
            stream.LanguageCode = _LanguageCode;
            if (Descriptors != null)
            {
                stream.Descriptors = new List<TSDescriptor>();
                foreach (var descriptor in Descriptors)
                {
                    stream.Descriptors.Add(descriptor.Clone());
                }
            }
        }
    }

    public class TSVideoStream : TSStream
    {
        public TSVideoStream()
        {
        }

        public int Width;
        public int Height;
        public bool IsInterlaced;
        public int FrameRateEnumerator;
        public int FrameRateDenominator;
        public TSAspectRatio AspectRatio;
        public string EncodingProfile;

        private TSVideoFormat _VideoFormat;
        public TSVideoFormat VideoFormat
        {
            get => _VideoFormat;
            set
            {
                _VideoFormat = value;
                switch (value)
                {
                    case TSVideoFormat.VIDEOFORMAT_480i:
                        Height = 480;
                        IsInterlaced = true;
                        break;
                    case TSVideoFormat.VIDEOFORMAT_480p:
                        Height = 480;
                        IsInterlaced = false;
                        break;
                    case TSVideoFormat.VIDEOFORMAT_576i:
                        Height = 576;
                        IsInterlaced = true;
                        break;
                    case TSVideoFormat.VIDEOFORMAT_576p:
                        Height = 576;
                        IsInterlaced = false;
                        break;
                    case TSVideoFormat.VIDEOFORMAT_720p:
                        Height = 720;
                        IsInterlaced = false;
                        break;
                    case TSVideoFormat.VIDEOFORMAT_1080i:
                        Height = 1080;
                        IsInterlaced = true;
                        break;
                    case TSVideoFormat.VIDEOFORMAT_1080p:
                        Height = 1080;
                        IsInterlaced = false;
                        break;
                }
            }
        }

        private TSFrameRate _FrameRate;
        public TSFrameRate FrameRate
        {
            get => _FrameRate;
            set
            {
                _FrameRate = value;
                switch (value)
                {
                    case TSFrameRate.FRAMERATE_23_976:
                        FrameRateEnumerator = 24000;
                        FrameRateDenominator = 1001;
                        break;
                    case TSFrameRate.FRAMERATE_24:
                        FrameRateEnumerator = 24000;
                        FrameRateDenominator = 1000;
                        break;
                    case TSFrameRate.FRAMERATE_25:
                        FrameRateEnumerator = 25000;
                        FrameRateDenominator = 1000;
                        break;
                    case TSFrameRate.FRAMERATE_29_97:
                        FrameRateEnumerator = 30000;
                        FrameRateDenominator = 1001;
                        break;
                    case TSFrameRate.FRAMERATE_50:
                        FrameRateEnumerator = 50000;
                        FrameRateDenominator = 1000;
                        break;
                    case TSFrameRate.FRAMERATE_59_94:
                        FrameRateEnumerator = 60000;
                        FrameRateDenominator = 1001;
                        break;
                }
            }
        }

        public override string Description
        {
            get
            {
                string description = "";

                if (Height > 0)
                {
                    description += string.Format("{0:D}{1} / ",
                        Height,
                        IsInterlaced ? "i" : "p");
                }
                if (FrameRateEnumerator > 0 &&
                    FrameRateDenominator > 0)
                {
                    if (FrameRateEnumerator % FrameRateDenominator == 0)
                    {
                        description += string.Format("{0:D} fps / ",
                            FrameRateEnumerator / FrameRateDenominator);
                    }
                    else
                    {
                        description += string.Format("{0:F3} fps / ",
                            (double)FrameRateEnumerator / FrameRateDenominator);
                    }

                }
                if (AspectRatio == TSAspectRatio.ASPECT_4_3)
                {
                    description += "4:3 / ";
                }
                else if (AspectRatio == TSAspectRatio.ASPECT_16_9)
                {
                    description += "16:9 / ";
                }
                if (EncodingProfile != null)
                {
                    description += EncodingProfile + " / ";
                }
                if (description.EndsWith(" / "))
                {
                    description = description.Substring(0, description.Length - 3);
                }
                return description;
            }
        }

        public override TSStream Clone()
        {
            var stream = new TSVideoStream();
            CopyTo(stream);

            stream.VideoFormat = _VideoFormat;
            stream.FrameRate = _FrameRate;
            stream.Width = Width;
            stream.Height = Height;
            stream.IsInterlaced = IsInterlaced;
            stream.FrameRateEnumerator = FrameRateEnumerator;
            stream.FrameRateDenominator = FrameRateDenominator;
            stream.AspectRatio = AspectRatio;
            stream.EncodingProfile = EncodingProfile;

            return stream;
        }
    }

    public enum TSAudioMode
    {
        Unknown,
        DualMono,
        Stereo,
        Surround,
        Extended
    }

    public class TSAudioStream : TSStream
    {
        public TSAudioStream()
        {
        }

        public int SampleRate;
        public int ChannelCount;
        public int BitDepth;
        public int LFE;
        public int DialNorm;
        public TSAudioMode AudioMode;
        public TSAudioStream CoreStream;
        public TSChannelLayout ChannelLayout;

        public static int ConvertSampleRate(
            TSSampleRate sampleRate)
        {
            switch (sampleRate)
            {
                case TSSampleRate.SAMPLERATE_48:
                    return 48000;

                case TSSampleRate.SAMPLERATE_96:
                case TSSampleRate.SAMPLERATE_48_96:
                    return 96000;

                case TSSampleRate.SAMPLERATE_192:
                case TSSampleRate.SAMPLERATE_48_192:
                    return 192000;
            }
            return 0;
        }

        public string ChannelDescription
        {
            get
            {
                if (ChannelLayout == TSChannelLayout.CHANNELLAYOUT_MONO &&
                    ChannelCount == 2)
                {
                }

                string description = "";
                if (ChannelCount > 0)
                {
                    description += string.Format(
                        "{0:D}.{1:D}",
                        ChannelCount, LFE);
                }
                else
                {
                    switch (ChannelLayout)
                    {
                        case TSChannelLayout.CHANNELLAYOUT_MONO:
                            description += "1.0";
                            break;
                        case TSChannelLayout.CHANNELLAYOUT_STEREO:
                            description += "2.0";
                            break;
                        case TSChannelLayout.CHANNELLAYOUT_MULTI:
                            description += "5.1";
                            break;
                    }
                }
                if (AudioMode == TSAudioMode.Extended)
                {
                    if (StreamType == TSStreamType.AC3_AUDIO)
                    {
                        description += "-EX";
                    }
                    if (StreamType == TSStreamType.DTS_AUDIO ||
                        StreamType == TSStreamType.DTS_HD_AUDIO ||
                        StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
                    {
                        description += "-ES";
                    }
                }
                return description;
            }
        }

        public override string Description
        {
            get
            {
                string description = ChannelDescription;

                if (SampleRate > 0)
                {
                    description += string.Format(
                        " / {0:D} kHz", SampleRate / 1000);
                }
                if (BitRate > 0)
                {
                    description += string.Format(
                        " / {0:D} kbps", (uint)Math.Round((double)BitRate / 1000));
                }
                if (BitDepth > 0)
                {
                    description += string.Format(
                        " / {0:D}-bit", BitDepth);
                }
                if (DialNorm != 0)
                {
                    description += string.Format(
                        " / DN {0}dB", DialNorm);
                }
                if (ChannelCount == 2)
                {
                    switch (AudioMode)
                    {
                        case TSAudioMode.DualMono:
                            description += " / Dual Mono";
                            break;

                        case TSAudioMode.Surround:
                            description += " / Dolby Surround";
                            break;
                    }
                }
                if (description.EndsWith(" / "))
                {
                    description = description.Substring(0, description.Length - 3);
                }
                if (CoreStream != null)
                {
                    string codec = "";
                    switch (CoreStream.StreamType)
                    {
                        case TSStreamType.AC3_AUDIO:
                            codec = "AC3 Embedded";
                            break;
                        case TSStreamType.DTS_AUDIO:
                            codec = "DTS Core";
                            break;
                    }
                    description += string.Format(
                        " ({0}: {1})",
                        codec,
                        CoreStream.Description);
                }
                return description;
            }
        }

        public override TSStream Clone()
        {
            var stream = new TSAudioStream();
            CopyTo(stream);

            stream.SampleRate = SampleRate;
            stream.ChannelLayout = ChannelLayout;
            stream.ChannelCount = ChannelCount;
            stream.BitDepth = BitDepth;
            stream.LFE = LFE;
            stream.DialNorm = DialNorm;
            stream.AudioMode = AudioMode;
            if (CoreStream != null)
            {
                stream.CoreStream = (TSAudioStream)CoreStream.Clone();
            }

            return stream;
        }
    }

    public class TSGraphicsStream : TSStream
    {
        public TSGraphicsStream()
        {
            IsVBR = true;
            IsInitialized = true;
        }

        public override TSStream Clone()
        {
            var stream = new TSGraphicsStream();
            CopyTo(stream);
            return stream;
        }
    }

    public class TSTextStream : TSStream
    {
        public TSTextStream()
        {
            IsVBR = true;
            IsInitialized = true;
        }

        public override TSStream Clone()
        {
            var stream = new TSTextStream();
            CopyTo(stream);
            return stream;
        }
    }
}
