using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YoutubeExplode.Videos;
using YoutubeExplode;
using System.Net.Http;
using System.Net;
using System.IO;
using TessesYoutubeDownloader.Server.Models;
using Newtonsoft.Json;

using YoutubeExplode.Videos.Streams;
using YoutubeExplode.Channels;
using YoutubeExplode.Playlists;
using youtube_downloader.Server.Models;

namespace youtube_downloader.Server.Functions
{
    public class Downloader
    {
        public Downloader()
        {
               //  TessesYoutubeDownloader.Server.Functions.Downloader.DL.DownloadThread().GetAwaiter().GetResult();

        }
       public  List<InfomationQueueItem> infoQueue = new List<InfomationQueueItem>();
        public static YoutubeClient CreateYoutubeClient()
        {

            ServicePointManager
.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
            HttpClientHandler handler = new HttpClientHandler();
            handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
            Http = new HttpClient(handler);

            return new YoutubeExplode.YoutubeClient(Http);
        }

        internal static VideoDownloadProgress GetProgress()
        {
            return P;
        }


        static HttpClient Http;
       internal YoutubeClient ytc = CreateYoutubeClient();
        static VideoDownloadProgress P=new VideoDownloadProgress();
        Progress<double> DownloadP =new Progress<double>( (e)=> { P.Progress = (int)(e * 100.0); });
        
       public List<SavedVideoObject> Queue = new List<SavedVideoObject>();

        internal static string GetQueue()
        {
            string q;
            lock (DL.Queue)
            {
                q = JsonConvert.SerializeObject(DL.Queue.Select<SavedVideoObject, SavedVideo>(o => o.Video)) ;
            }
            return q;
        }
        public async Task ListenForQueueItem()
        {
            do
            {
                InfomationQueueItem item;
                bool canAdd = false;
                lock (infoQueue)
                {
                    canAdd = infoQueue.Count > 0;
                    if (canAdd)
                    {
                        item = infoQueue[0];
                        infoQueue.RemoveAt(0);
                    }
                    else
                    {
                        item = null;
                    }
                }
                if (canAdd)
                {
                    try
                    {
                        var items = await item.DownloadData();
                        if (item.DownloadActualDataAfterwards)
                        {
                            _DownloadVideos(items);
                        }
                    }catch(Exception ex)
                    {

                        Console.WriteLine(ex.Message);
                        _ = ex;
                    }
                }
            } while (true);
        }
        internal static void ModQueue(string mvto, string index)
        {
            try
            {
                //?mv=up|down|top|bottom|remove,int&i=0,last
                lock (DL.Queue)
                {
                    int index2 = 0;
                    if (index == "last")
                    {
                        index2 = DL.Queue.Count - 1;
                    }
                    else
                    {
                        if (!int.TryParse(index, out index2))
                        {
                            index2 = 0;
                        }
                    }
                    if (index2 >= DL.Queue.Count)
                    {
                        index2 = DL.Queue.Count - 1;
                    }
                    if (mvto == "top")
                    {
                        var v = DL.Queue[index2];
                        DL.Queue.Remove(v);
                        DL.Queue.Insert(0,v);
                    }
                    else if (mvto == "bottom")
                    {
                        var v = DL.Queue[index2];
                        DL.Queue.Remove(v);
                        DL.Queue.Add(v);
                    }
                    else if (mvto == "remove")
                    {
                        var v = DL.Queue[index2];
                        DL.Queue.Remove(v);

                    }
                    else if (mvto == "up")
                    {
                        if (index2 > 0)
                        {
                            var v = DL.Queue[index2];
                            DL.Queue.Remove(v);

                            DL.Queue.Insert(index2 - 1, v);
                        }

                    }
                    else if (mvto == "down")
                    {
                        if (index2 < DL.Queue.Count - 1)
                        {
                            var v = DL.Queue[index2];
                            DL.Queue.Remove(v);

                            DL.Queue.Insert(index2 + 1, v);
                        }
                    }
                    else
                    {
                        int n1;

                        if (int.TryParse(mvto, out n1))
                        {
                            var v = DL.Queue[index2];
                            DL.Queue.Remove(v);
                            if (n1 > index2)
                            {
                                DL.Queue.Insert(n1 - 1, v);
                            }
                            else
                            {
                                DL.Queue.Insert(n1, v);
                            }
                        }
                    }

                }
            }catch(Exception ex)
            {
                Console.WriteLine(ex.Message);

                _ = ex;
            }
        }

        internal static void DownloadChannelOnly(string id)
        {
            ChannelId? v = ChannelId.TryParse(id);
            if (v.HasValue)
            {
                InfomationQueueItem item = new InfomationQueueItem(v.Value,Resolution.NoConvert,false);
                lock (DL.infoQueue)
                {
                    DL.infoQueue.Insert(0, item);
                }
            }
        }

            internal static void DownloadUserOnly(string id)
        {
            UserName? v = UserName.TryParse(id);
            if (v.HasValue)
            {
                InfomationQueueItem item = new InfomationQueueItem(v.Value, Resolution.NoConvert, false);
                lock (DL.infoQueue)
                {
                    DL.infoQueue.Insert(0, item);
                }
            }
        }

        public bool Continue(string v)
        {
            if (File.Exists(v))
            {
                using(var f = File.OpenRead(v))
                {
                    return f.Length == 0;
                }
            }
            return true;
        }
        private string gStorageLocation()
        {
            if (System.IO.File.Exists("loc.txt"))
            {
                string loc=System.IO.File.ReadAllText("loc.txt");
                try
                {
                    System.IO.Directory.CreateDirectory(loc);
                    if (System.IO.Directory.Exists(loc))
                    {
                        return loc;
                    }
                }catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);

                    _ = ex;
                }
            }
            return Environment.CurrentDirectory;
        }
        public string StorageLocation { get { return gStorageLocation(); } }
        private void _DownloadVideos(SavedVideoObject[] items)
        {   
            
            lock (Queue)
            {
                foreach (var item in items)
                {
                    Queue.Insert(0, item);
                    //new elements get added to begining
                }
            }
        }
        public async Task DownloadThread()
        {
            do
            {
                bool canDownload = false;
                SavedVideoObject v;
                lock (Queue)
                {
                   canDownload= Queue.Count > 0;
                    if (canDownload)
                    {
                        v = Queue[0];
                        Queue.RemoveAt(0);
                        P.Saved = v.Video;
                        Console.WriteLine($"Download: {v.Video.Title}");
                    }
                    else
                    {
                        v = null;
                    }
                }

                if (canDownload)
                {
                    try
                    {
                        switch (v.Resolution)
                        {
                            case Resolution.Convert:

                                string mypath = GetPath(true, "Converted", v.Video.Id + "-vidonly.bkp");
                                string mypathaudio = GetPath(true, "Audio", v.Video.Id + "incomplete.mp4");
                                string mypathCompleteAudio = GetPath(true, "Audio", v.Video.Id + ".mp4");
                                string mypathComplete = GetPath(true, "Converted", v.Video.Id + ".mp4");
                                string mypathIncompleteConverting = GetPath(true, "Converted", "conv.mkv");

                                if (Continue(mypathComplete))
                                {
                                    var s3 = await ytc.Videos.Streams.GetManifestAsync(v.Video.Id);
                                    var best2 = s3.GetAudioOnlyStreams().GetWithHighestBitrate();



                                    var best = s3.GetVideoOnlyStreams().GetWithHighestVideoQuality();
                                    P.Length = best.Size.Bytes + best2.Size.Bytes;
                                    ProgressTwo p = new ProgressTwo(best.Size.Bytes, best2.Size.Bytes, DownloadP);

                                    using (var destStrm = System.IO.File.Open(mypath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                                    {
                                        long pos = 0;
                                        long len = 0;
                                        using (var srcStrm = await ytc.Videos.Streams.GetAsync(best))
                                        {
                                            len = srcStrm.Length;
                                            pos = destStrm.Length;
                                            IProgress<double> myProgress = p.Video;
                                            if (pos >= len)
                                            {

                                                myProgress.Report(1);

                                            }
                                            srcStrm.Seek(destStrm.Length, SeekOrigin.Begin);
                                            byte[] buffer = new byte[4096];
                                            int read = 0;
                                            do
                                            {
                                                read = srcStrm.Read(buffer, 0, buffer.Length);
                                                destStrm.Write(buffer, 0, read);

                                                pos += read;
                                                double myP = (double)pos / (double)len;
                                                myProgress.Report(myP);
                                            }
                                            while (read > 0);
                                        }

                                    }

                                    IProgress<double> pv = p.Video;
                                    pv.Report(1);
                                    if (Continue(mypathCompleteAudio))
                                    {
                                        long pos = 0;
                                        long len = 0;
                                        using (var destStrm = System.IO.File.Open(mypathaudio, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                                        {
                                            using (var srcStrm = await ytc.Videos.Streams.GetAsync(best2))
                                            {
                                                len = srcStrm.Length;
                                                pos = destStrm.Length;
                                                IProgress<double> myProgress = p.Audio;
                                                if (pos >= len)
                                                {

                                                    myProgress.Report(1);

                                                }
                                                srcStrm.Seek(destStrm.Length, SeekOrigin.Begin);
                                                byte[] buffer = new byte[4096];
                                                int read = 0;
                                                do
                                                {
                                                    read = srcStrm.Read(buffer, 0, buffer.Length);
                                                    destStrm.Write(buffer, 0, read);

                                                    pos += read;
                                                    double myP = (double)pos / (double)len;
                                                    myProgress.Report(myP);
                                                }
                                                while (read > 0);
                                            }

                                        }
                                        File.Move(mypathaudio, mypathCompleteAudio);
                                    }
                                    IProgress<double> pa = p.Video;
                                    pa.Report(1);
                                    ffmpeg.mux(mypath, mypathCompleteAudio, mypathIncompleteConverting);


                                    File.Move(mypathIncompleteConverting, mypathComplete);
                                }
                                break;
                            case Resolution.NoConvert:
                                string mypath2 = GetPath(true, "NotConverted", v.Video.Id + "incomplete.mp4");
                                string mypath2Complete = GetPath(true, "NotConverted", v.Video.Id + ".mp4");

                                if (Continue(mypath2Complete))
                                {
                                    var s = await ytc.Videos.Streams.GetManifestAsync(v.Video.Id);
                                    var best = s.GetMuxedStreams().GetWithHighestVideoQuality();
                                    P.Length = best.Size.Bytes;
                                    long pos = 0;
                                    long len = 0;
                                    using (var destStrm = System.IO.File.Open(mypath2, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                                    {
                                        using (var srcStrm = await ytc.Videos.Streams.GetAsync(best))
                                        {
                                            len = srcStrm.Length;
                                            pos = destStrm.Length;
                                            IProgress<double> myProgress = DownloadP;
                                            if (pos >= len)
                                            {

                                                myProgress.Report(1);

                                            }
                                            srcStrm.Seek(destStrm.Length, SeekOrigin.Begin);
                                            byte[] buffer = new byte[4096];
                                            int read = 0;
                                            do
                                            {
                                                read = srcStrm.Read(buffer, 0, buffer.Length);
                                                destStrm.Write(buffer, 0, read);

                                                pos += read;
                                                double myP = (double)pos / (double)len;
                                                myProgress.Report(myP);
                                            }
                                            while (read > 0);
                                        }

                                    }
                                    File.Move(mypath2, mypath2Complete);
                                }


                                break;
                            case Resolution.Audio:
                                string mypath3 = GetPath(true, "Audio", v.Video.Id + "incomplete.mp4");
                                string mypath3Complete = GetPath(true, "Audio", v.Video.Id + ".mp4");
                                if (Continue(mypath3Complete))
                                {
                                    var s2 = await ytc.Videos.Streams.GetManifestAsync(v.Video.Id);
                                    var best2 = s2.GetAudioOnlyStreams().GetWithHighestBitrate();
                                    P.Length = best2.Size.Bytes;
                                    long pos = 0;
                                    long len = 0;
                                    using (var destStrm = System.IO.File.Open(mypath3, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                                    {
                                        using (var srcStrm = await ytc.Videos.Streams.GetAsync(best2))
                                        {
                                            len = srcStrm.Length;
                                            pos = destStrm.Length;
                                            IProgress<double> myProgress = DownloadP;
                                            if (pos >= len)
                                            {

                                                myProgress.Report(1);

                                            }
                                            srcStrm.Seek(destStrm.Length, SeekOrigin.Begin);
                                            byte[] buffer = new byte[4096];
                                            int read = 0;
                                            do
                                            {
                                                read = srcStrm.Read(buffer, 0, buffer.Length);
                                                destStrm.Write(buffer, 0, read);

                                                pos += read;
                                                double myP = (double)pos / (double)len;
                                                myProgress.Report(myP);
                                            }
                                            while (read > 0);
                                        }

                                    }
                                    File.Move(mypath3, mypath3Complete);
                                }
                                break;
                        }
                    }catch(Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                System.Threading.Thread.Sleep(1);
            } 
            while (true);
        }
        internal void _DownloadThumbnail(int w,int h,string id,string tnail)
        {
            try
            {
                string p = GetPath(true,"Thumbnails", w.ToString() + 'x' + h.ToString(), id + ".jpg");
                if (!File.Exists(p))
                {
                    ffmpeg.download_thumbnail(tnail, p);
                 }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);

                _ = ex;
            }
        }
        public static async Task<List<SavedMedia>> Search(string text)
        {
            List<SavedMedia> media = new List<SavedMedia>();
            try
            {
               
                await DL.ytc.Search.GetVideosAsync(text).ForEachAsync((e) => { 
                    foreach(var t in e.Thumbnails)
                    {
                        DL._DownloadThumbnail(t.Resolution.Width, t.Resolution.Height, e.Id, t.Url);
                    }
                    media.Add(new SavedMedia() {Title = e.Title, Id = e.Id, Kind = InfoType.Video }); 
                
                });
                await DL.ytc.Search.GetPlaylistsAsync(text).ForEachAsync((e) => {
                    foreach (var t in e.Thumbnails)
                    {
                        DL._DownloadThumbnail(t.Resolution.Width, t.Resolution.Height, e.Id, t.Url);
                    }
                    media.Add(new SavedMedia() { Title = e.Title, Id = e.Id, Kind = InfoType.Playlist }); 
                
                });
                await DL.ytc.Search.GetChannelsAsync(text).ForEachAsync((e) => {
                    foreach (var t in e.Thumbnails)
                    {
                        DL._DownloadThumbnail(t.Resolution.Width, t.Resolution.Height, e.Id, t.Url);
                    }
                    media.Add(new SavedMedia() { Title = e.Title, Id = e.Id, Kind = InfoType.Channel }); 
                
                });
            }catch (Exception ex)
            {
                _ = ex;
            }
            return media;
        }
        internal static List<SavedVideoObject> GetQueueItems()
        {
            return DL.Queue;
        }

       
        public static void DownloadVideo(string id,Resolution res)
        {
            VideoId? v = VideoId.TryParse(id);
            if (v.HasValue)
            {
                InfomationQueueItem item = new InfomationQueueItem(v.Value, res, true);
                lock (DL.infoQueue)
                {
                    DL.infoQueue.Insert(0, item);
                }
            }
        }
        public static void DownloadVideo(string v)
        {
            DownloadVideo(v, Resolution.NoConvert);
        }
        public static void DownloadPlaylist(string id,Resolution res)
        {
            PlaylistId? v = PlaylistId.TryParse(id);
            if (v.HasValue)
            {
                InfomationQueueItem item = new InfomationQueueItem(v.Value, res, true);
                lock (DL.infoQueue)
                {
                    DL.infoQueue.Insert(0, item);
                }
            }
        }
        public static void DownloadPlaylist(string id)
        {
            DownloadPlaylist(id, Resolution.NoConvert);
        }
        public static void DownloadChannel(string id,Resolution res)
        {
            ChannelId? v = ChannelId.TryParse(id);
            if (v.HasValue)
            {
                InfomationQueueItem item = new InfomationQueueItem(v.Value, res, true);
                lock (DL.infoQueue)
                {
                    DL.infoQueue.Insert(0, item);
                }
            }
        }
        public static void DownloadChannel(string id)
        {
            DownloadChannel(id, Resolution.NoConvert);
        }
        public static void DownloadUser(string name, Resolution res)
        {
            UserName? v=UserName.TryParse(name);
            if (v.HasValue)
            {
                InfomationQueueItem item = new InfomationQueueItem(v.Value, res, true);
                lock (DL.infoQueue)
                {
                    DL.infoQueue.Insert(0, item);
                }
            }
        }

        public static void DownloadUser(string name)
        {
            DownloadUser(name, Resolution.NoConvert);
        }
        public string GetPath(bool createParent,params string[] _path)
        {

            if (createParent)
            {
                string dir = GetPath(_path);
                try
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(dir));
                }catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);

                    _ = ex;
                }
                return dir;
            }
            else
            {
                return GetPath(_path);
            }
        }
        private string GetPath(params string[] _path)
        {
            string[] array2 = new string[_path.Length + 1];
            array2[0] = StorageLocation;
            Array.Copy(_path, 0, array2, 1,_path.Length);
            return System.IO.Path.Combine(array2);
        }
        public static Downloader DL = new Downloader();
       
    }
}
