using System;
using System.Threading;

namespace hdsdump.f4m {

    /// <summary>
    /// Describes a specific piece of media.
    /// </summary>
    public class Media: IDisposable {
        /// <summary>
        /// Location of the media.
        /// </summary>
        public string url;

        /// <summary>
        /// The bitrate of the media in kilobits per second.
        /// </summary>
        public int bitrate;

        /// <summary>
        /// The type of the media.
        /// </summary>
        public string type;

        /// <summary>
        /// The label of the media.
        /// </summary>
        public string label;

        /// <summary>
        /// The language of the media.
        /// </summary>
        public string lang;

        /// <summary>
        /// Flag indicating that this is an alternate media.
        /// </summary>
        public bool alternate;

        public string audioCodec;
        public string videoCodec;
        public string cueInfoId;
        public string href;

        /// <summary>
        /// Information about the AdditionalHeader used with the media. AdditionalHeader contains DRM metadata.
        /// </summary>
        public DRMAdditionalHeader drmAdditionalHeader;

        /// <summary>
        /// Represents all information needed to bootstrap playback of 
        /// HTTP streamed media. It contains either a byte array
        /// of, or a URL to, the bootstrap information in the format that corresponds 
        /// to the bootstrap profile. It is optional.
        /// </summary>
        public BootstrapInfo bootstrapInfo;

        /// <summary>
        /// The stream metadata in its binary representation.
        /// </summary>
        public byte[] metadata;

        /// <summary>
        /// The XMP metadata in its binary representation.
        /// </summary>
        public byte[] xmp;

        /// <summary>
        /// Represents the Movie Box, or "moov" atom, for one representation of 
        /// the piece of media. It is an optional child element of <media>.
        /// </summary>
        public byte[] moov;

        /// <summary>
        /// Width of the resource in pixels.
        /// </summary>
        public int width;

        /// <summary>
        /// Height of the resource in pixels.
        /// </summary>
        public int height;

        /// <summary>
        /// Store multicast group spec string
        /// </summary>
        public string multicastGroupspec;

        /// <summary>
        /// Store multicast stream name
        /// </summary>
        public string multicastStreamName;

        public string baseURL  = "";
        public string streamId = "";

        // Constructor
        public Media(XmlNodeEx node, string serverBaseURL = "", string idPrefix = "", int nestedBitrate = 0) {
            Parse(node, serverBaseURL, idPrefix, nestedBitrate);
        }

        /// <summary>
        /// Parses media from XML node.
        /// </summary>
        public void Parse(XmlNodeEx node, string serverBaseURL = "", string idPrefix = "", int nestedBitrate = 0) {
            baseURL = serverBaseURL;

            url = node.GetAttributeStr("url");
            url = URL.getAbsoluteUrl(baseURL, url);

            streamId = node.GetAttributeStr("streamId");
            bitrate  = node.GetAttributeInt("bitrate");
            if (bitrate == 0)
                bitrate = nestedBitrate;
            if (bitrate == 0) {
                XmlNodeEx parentManifest = (XmlNodeEx)node.ParentNode;
                bitrate = parentManifest.GetAttributeInt("bitrate");
            }
            if (bitrate == 0) {
                bitrate = node.GetAttributeInt("width");
            }
            if (bitrate == 0) {
                if (!string.IsNullOrEmpty(streamId)) {
                    var m = System.Text.RegularExpressions.Regex.Match(streamId, @"(\d+)", System.Text.RegularExpressions.RegexOptions.RightToLeft);
                    if (m.Success)
                        int.TryParse(m.Groups[1].Value, out bitrate);
                    else {
                        // by index
                        int idx = 0;
                        foreach (XmlNodeEx n in node.ParentNode.ChildNodes) {
                            if (n.Name != node.Name) continue;
                            if (n == node) {
                                bitrate = idx;
                                break;
                            }
                            idx++;
                        }
                    }
                }
            }

            drmAdditionalHeader = new DRMAdditionalHeader() {
                id = idPrefix + node.GetAttributeStr("drmAdditionalHeaderId", F4MUtils.GLOBAL_ELEMENT_ID)
            };
            bootstrapInfo = new BootstrapInfo() {
                id = idPrefix + node.GetAttributeStr("bootstrapInfoId", F4MUtils.GLOBAL_ELEMENT_ID)
            };
            height = node.GetAttributeInt("height");
            width  = node.GetAttributeInt("width" );

            multicastGroupspec  = node.GetAttributeStr("groupspec");
            multicastStreamName = node.GetAttributeStr("multicastStreamName");
            label      = node.GetAttributeStr("label");
            type       = node.GetAttributeStr("type", StreamingItemType.VIDEO);
            lang       = node.GetAttributeStr("lang");
            audioCodec = node.GetAttributeStr("audioCodec");
            videoCodec = node.GetAttributeStr("videoCodec");
            cueInfoId  = node.GetAttributeStr("cueInfoId");
            href       = node.GetAttributeStr("href");
            alternate  = (node.GetAttributeStr("alternate").Length > 0);

            moov     = node.GetData("moov"    );
            metadata = node.GetData("metadata");
            if ((metadata!=null) && metadata.Length > 0) {
                // if width and height are not already set by the media
                // attributes and they are already present in metadata 
                // object, then copy their values to the media properties
                if (width == 0) {
                    width = node.GetChildNodeAttributeInt("metadata", "width");
                }
                if (height == 0) {
                    height = node.GetChildNodeAttributeInt("metadata", "height");
                }
            }

            xmp = node.GetData("xmpMetadata");

            if (string.IsNullOrEmpty(label)) {
                if (!string.IsNullOrEmpty(lang))
                    label = lang;
                else
                    label = string.IsNullOrEmpty(streamId) ? bitrate.ToString() : streamId;
            }
        }

        private uint _downloaded = 0;

        public bool Updating = false;
        public uint CurrentFragmentIndex = 0;
        public uint LastWritenFragment   = 0;
        public uint TotalFragments       = 0;
        public uint Downloaded {
            get { return _downloaded; }
            set {
                if (value != _downloaded) {
                    _downloaded = value;
                    DownloadedChanged?.Invoke(this, EventArgs.Empty);
                }
            }
        }

        public f4f.AdobeBootstrapBox Bootstrap;

        // Events
        public event EventHandler AfterUpdateBootstrap;
        public event EventHandler DownloadedChanged;

        private int bootstrapUpdateInterval = 2000; // 4.0 sec - recommend default fragment duration
        private int hdsMinimumBootstrapRefreshInterval = 1000;

        Timer bootstrapUpdateTimer;

        private void StartBootstrapUpdateTimer() {

            if (Bootstrap != null) {
                // for slow internet - disable sets interval
                //if (Bootstrap.fragmentRunTables.Count > 0) {
                //    var lastFrag = Bootstrap.GetLastFragment();
                //    bootstrapUpdateInterval = (int)(lastFrag.duration / Bootstrap.timeScale * 1000);
                //}

                if (bootstrapUpdateInterval < hdsMinimumBootstrapRefreshInterval) {
                    bootstrapUpdateInterval = hdsMinimumBootstrapRefreshInterval;
                }
            }

            if (bootstrapUpdateTimer == null) {
                bootstrapUpdateTimer = new Timer(OnBootstrapUpdateTimer, null, 0, bootstrapUpdateInterval);
            }
        }

        private void DestroyBootstrapUpdateTimer() {
            if (bootstrapUpdateTimer != null) {
                bootstrapUpdateTimer.Dispose();
                bootstrapUpdateTimer = null;
            }
        }

        private void OnBootstrapUpdateTimer(object state) {
            bootstrapInfo.data = HTTP.TryGETData(bootstrapInfo.url, out int retCode, out string status);

            if (retCode != 200) {
                Program.DebugLog("Error while loading UpdateBootstrapBox. Code: " + retCode + " Status: " + status);
            }

            UpfateBootstrapBox();
        }

        private void UpfateBootstrapBox() {
            if (f4f.Box.FindBox(bootstrapInfo.data, f4f.F4FConstants.BOX_TYPE_ABST) is f4f.AdobeBootstrapBox abst) {
                Bootstrap = abst;

                // Count total fragments by adding all entries in compactly coded segment table
                TotalFragments = Bootstrap.GetFragmentsCount();

                if (CurrentFragmentIndex < 1) {
                    CurrentFragmentIndex = Bootstrap.live ? (TotalFragments - 1) : Bootstrap.GetFirstFragment().firstFragment;
                }
            }

            if (CurrentFragmentIndex < 1)
                CurrentFragmentIndex = 1;

            if (CurrentFragmentIndex < TotalFragments)
                Updating = false;

            AfterUpdateBootstrap?.Invoke(this, EventArgs.Empty);
        }

        public void UpdateBootstrapInfo() {
            bool itWasLive = Bootstrap != null ? Bootstrap.live : false;

            if (!string.IsNullOrEmpty(bootstrapInfo.url)) {
                Updating = true;
                bootstrapInfo.data = HTTP.TryGETData(bootstrapInfo.url, out int retCode, out string status);

                if (retCode != 200) {
                    Program.DebugLog("Error while loading UpdateBootstrapBox. Code: " + retCode + " Status: " + status);
                }
            }

            UpfateBootstrapBox();

            if (!itWasLive && Bootstrap == null)
                throw new InvalidOperationException("Failed to parse bootstrap info. Not found the abst box.");

            if (Bootstrap != null && Bootstrap.live)
                StartBootstrapUpdateTimer();
        }

        public string GetFragmentUrl(uint fragId) {
            uint segId = Bootstrap.GetSegmentFromFragment(fragId);
            return ConstructFragmentRequest(baseURL, url, segId, fragId);
        }

        private string ConstructFragmentRequest(string serverBaseURL, string streamName, uint segmentId, uint fragmentId) {
            string requestUrl = "";
            if (streamName != null && streamName.IndexOf("http") != 0) {
                requestUrl = serverBaseURL + "/";
            }
            requestUrl += streamName;
            Uri uri = new Uri(requestUrl);
            requestUrl = uri.Scheme + "://" + uri.Host;
            if (((uri.Scheme == "http") && (uri.Port != 80)) || ((uri.Scheme == "https") && (uri.Port != 443))) {
                requestUrl += ":" + uri.Port;
            }
            requestUrl += uri.LocalPath + "Seg" + segmentId + "-Frag" + fragmentId;
            if (!string.IsNullOrEmpty(uri.Query)) {
                requestUrl += "?" + uri.Query;
            }
            if (!string.IsNullOrEmpty(uri.Fragment)) {
                requestUrl += "#" + uri.Fragment;
            }
            return requestUrl;
        }

        public void SetCurrentFragmentIndexByTimestamp(uint timestamp) {
            if (Bootstrap != null && !Bootstrap.live) {
                CurrentFragmentIndex = Bootstrap.GetSegmentByTimestamp(timestamp);
            }
        }

        public override string ToString() {
            return "Media: " + label.Trim();
        }

        #region IDisposable Support
        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing) {
            if (!disposedValue) {
                if (disposing) {
                    if (bootstrapUpdateTimer != null)
                        bootstrapUpdateTimer.Dispose();
                }
                bootstrapUpdateTimer = null;
                disposedValue = true;
            }
        }

        public void Dispose() {
            Dispose(true);
        }
        #endregion

    }
}