using System.Linq;
using SimpleHttp;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using TessesYoutubeDownloader.Server.Models;
using Newtonsoft.Json;

using System.Net;
using System.Threading.Tasks;
using youtube_downloader.Server.Models;

namespace youtube_downloader
{
    public class SSE
    {
        public static SSE ServerSentEventItem = new SSE();
        List<HttpListenerResponse> Streams = new List<HttpListenerResponse>();
        public void RegisterResponse(HttpListenerResponse rp)
        {
            Streams.Add(rp);
        }
        public async Task PushEventAsync(string event_name,object data)
        {
            await PushEventAsync(event_name, JsonConvert.SerializeObject(data));
        }
        public async Task PushEventAsync(string event_name,string data)
        {
            await PushEventRawAsync($"event: {event_name}\ndata: {data}");

        }
        public async Task PushEventRawAsync(string raw_data)
        {
            foreach (var items in Streams)
            {
                try
                {
                    var bytes = Encoding.UTF8.GetBytes(raw_data + "\n\n");
                    await items.OutputStream.WriteAsync(bytes, 0, bytes.Length);
                    await items.OutputStream.FlushAsync();
                }
                catch (Exception ex)
                {
                    _ = ex;
                }
            }
        }
    }

    class Program
    {
        static string webSitePath;
        static void Main(string[] arg)
        {

            Thread t = new Thread(new ThreadStart(() => {
                Server.Functions.Downloader.DL.DownloadThread().GetAwaiter().GetResult();
            }));
            t.Start();
            Thread t2 = new Thread(new ThreadStart(() => {
                Server.Functions.Downloader.DL.ListenForQueueItem().GetAwaiter().GetResult();
            }));
            t2.Start();
          
           
            // we need to get our app name so that 
            // we can create unique names for our mutex and our pipe
            webSitePath = Server.Functions.Downloader.DL.GetPath(true, "WebSite");

            Route.Before += Route_Before;
          

            /* Generic */
            Route.Add("/api/AddItems", (HttpAction)AddItems, "POST");
            Route.Add("/api/AddFile/{Url}", (HttpAction)AddFile);
            Route.Add("/api/AddCaptions/{Id}", (HttpAction)AddCaptions);

            /* Videos */
            Route.Add("/api/AddVideoInfo/{Id}", (HttpAction)AddVideoInfo);
            Route.Add("/api/AddVideo/{Id}", (HttpAction)AddVideo);
            Route.Add("/api/AddVideoRes/{R}/{Id}", (HttpAction)AddVideoRes);
            Route.Add("/api/Redownload", (HttpAction)Redownload);
            Route.Add("/api/Redownload/{R}", (HttpAction)RedownloadRes);
            Route.Add("/api/Watch/{VideoId}", (HttpAction)Watch);

            /* Playlist */
            Route.Add("/api/AddPlaylistOnly/{Id}", (HttpAction)AddPlaylistOnly);
            Route.Add("/api/AddPlaylist/{Id}", (HttpAction)AddPlaylist);
            Route.Add("/api/AddPlaylistRes/{R}/{Id}", (HttpAction)AddPlaylistRes);

            /* Search */
            Route.Add("/api/SearchOnly/{text}", (HttpAction)SearchOnly);
            Route.Add("/api/Search/{text}", (HttpAction)Search);
           
            /* Channel */
            Route.Add("/api/AddChannelOnly/{Id}", (HttpAction)AddChannelOnly);
            Route.Add("/api/AddChannel/{Id}", (HttpAction)AddChannel);
            Route.Add("/api/AddChannelRes/{R}/{Id}", (HttpAction)AddChannelRes);

            /* User */
            Route.Add("/api/AddUserOnly/{Id}", (HttpAction)AddUserOnly);
            Route.Add("/api/AddUser/{Id}", (HttpAction)AddUser);
            Route.Add("/api/AddUserRes/{R}/{Id}", (HttpAction)AddUserRes);

            /* Queue and Progress */
            Route.Add("/api/QueueList", (HttpAction)QueueList);
            Route.Add("/api/QueueMove/{From}/{To}", (HttpAction)QueueMove);
            Route.Add("/api/QueueMove2/{To}/{Id}", (HttpAction)QueueMove2);
            Route.Add("/api/Progress", (HttpAction)VideoProgress);
            Route.Add("/api/SSE", (HttpAction)ServerSentEvents);
           
            /* Storage */
            Route.Add("/api/Storage/GetDirectories/{Path}", (HttpAction)StorageGetDirectories);
            Route.Add("/api/Storage/GetFiles/{Path}", (HttpAction)StorageGetFiles);
            Route.Add("/api/Storage/DirectoryExists/{Path}", (HttpAction)StorageDirectoryExists);
            Route.Add("/api/Storage/FileExists/{Path}", (HttpAction)StorageFileExists);
            Route.Add("/api/Storage/File/{Path}", (HttpAction)StorageFile);
            Route.Add("/api/Storage/Video/{Id}",(HttpAction)Video);
            Route.Add("/api/Storage/VideoRes/{Res}/{Id}",(HttpAction)VideoRes);
            Route.Add("/api/upload/", (HttpAction)UploadFiles, "POST");
           

            /* Other */
            Route.Add("/", (HttpAction)Index);
           Route.Add("/{Path}", (HttpAction)RootPath);
            Console.WriteLine("Almost Ready To Listen");

            if (arg.Length > 0)
            {

                HttpServer.ListenAsync(arg[0], CancellationToken.None, Route.OnHttpRequestAsync).Wait();

            }
            else
            {
                HttpServer.ListenAsync(3250, CancellationToken.None, Route.OnHttpRequestAsync).Wait();
            }
        }

        #region Generic
        private static void AddItems(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
        {
            var f = request.ParseBody(arguments);
            foreach (var file in f.Values)
            {

                using (var req = new StreamReader(file.Value))
                {
                    List<IDResolutionTypeTriplet> tripletlst = JsonConvert.DeserializeObject<List<IDResolutionTypeTriplet>>(req.ReadToEnd());
                    Server.Functions.Downloader.DownloadItems(tripletlst);
                    response.Redirect("/");
                }
            }


        }
        public static void AddFile(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
        {
            Server.Functions.Downloader.DownloadFile(arguments["Url"]);
        }
        public static void AddCaptions(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
        {
            Server.Functions.Downloader.DownloadCaptions(arguments["Id"]);
        }
        #endregion
        #region Video
        public static void AddVideoInfo(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadVideoInfo(System.Web.HttpUtility.UrlDecode(args["Id"]), Resolution.NoConvert);
            rp.AsRedirect("/");
        }
        public static void AddVideo(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadVideo(System.Web.HttpUtility.UrlDecode(args["Id"]));

            rp.AsRedirect("/");
        }

        public static void AddVideoRes(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadVideo(System.Web.HttpUtility.UrlDecode(args["Id"]), (Resolution)int.Parse(args["R"]));
            rp.AsRedirect("/");
        }
        public static void Redownload(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            foreach (var item in Directory.GetFiles(Server.Functions.Downloader.DL.GetPath(true, "Info"), "*.json"))
            {
                string id = System.IO.Path.GetFileNameWithoutExtension(item);
                Server.Functions.Downloader.DownloadVideo(id, Resolution.NoConvert);
            }
            rp.AsRedirect("/");
        }
       
        public static void RedownloadRes(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            foreach (var item in Directory.GetFiles(Server.Functions.Downloader.DL.GetPath(true, "Info"), "*.json"))
            {
                string id = System.IO.Path.GetFileNameWithoutExtension(item);
                Server.Functions.Downloader.DownloadVideo(id, (Resolution)int.Parse(args["R"]));
            }
            rp.AsRedirect("/");
        }
        public static void Watch(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            var txt = Templating.RenderFile(Path.Combine(webSitePath, "watch_page.thtml"), args); //populate template
            rp.AsText(txt);
        }
        #endregion
        #region Playlist
        public static void AddPlaylistOnly(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadPlaylistOnly(System.Web.HttpUtility.UrlDecode(args["Id"]), Resolution.NoConvert);
            rp.AsRedirect("/");
        }
          public static void AddPlaylist(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadPlaylist(System.Web.HttpUtility.UrlDecode(args["Id"]));

            rp.AsRedirect("/");
        }
        public static void AddPlaylistRes(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadPlaylist(System.Web.HttpUtility.UrlDecode(args["Id"]), (Resolution)int.Parse(args["R"]));
            rp.AsRedirect("/");
        }
        #endregion
        #region Search
        public static void SearchOnly(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string search = System.Web.HttpUtility.UrlDecode(args["text"]);
            string json = JsonConvert.SerializeObject(Server.Functions.Downloader.Search(search, false));
            rp.AsText(json, "application/json");

        }
        public static void Search(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string search = System.Web.HttpUtility.UrlDecode(args["text"]);
            string json = JsonConvert.SerializeObject(Server.Functions.Downloader.Search(search));
            rp.AsText(json, "application/json");

        }
        #endregion
        #region Channel
        public static void AddChannelOnly(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadChannelOnly(System.Web.HttpUtility.UrlDecode(args["Id"]));

            rp.AsRedirect("/");
        }
        public static void AddChannel(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadChannel(System.Web.HttpUtility.UrlDecode(args["Id"]));

            rp.AsRedirect("/");
        }
        public static void AddChannelRes(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadChannel(System.Web.HttpUtility.UrlDecode(args["Id"]), (Resolution)int.Parse(args["R"]));
            rp.AsRedirect("/");
        }
        #endregion
        #region User
        public static void AddUserOnly(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadUserOnly(System.Web.HttpUtility.UrlDecode(args["Id"]));
            rp.AsRedirect("/");
        }
        public static void AddUser(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadUser(System.Web.HttpUtility.UrlDecode(args["Id"]));

            rp.AsRedirect("/");
        }
        public static void AddUserRes(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.DownloadUser(System.Web.HttpUtility.UrlDecode(args["Id"]), (Resolution)int.Parse(args["R"]));
            rp.AsRedirect("/");
        }
        #endregion
        #region Queue And Progress
        public static void QueueList(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string json = Server.Functions.Downloader.GetQueue();
            rp.AsText(json, "application/json");
        }
        public static void QueueMove(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            Server.Functions.Downloader.ModQueue(args["To"], args["From"]);
            rp.AsRedirect("/");
        }
        public static void QueueMove2(HttpListenerRequest request, HttpListenerResponse response, Dictionary<string, string> arguments)
        {
            Server.Functions.Downloader.ModQueue2(arguments["To"], arguments["Id"]);
            response.AsRedirect("/");
        }
        public static void VideoProgress(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string json = JsonConvert.SerializeObject(Server.Functions.Downloader.GetProgress());
            rp.AsText(json, "application/json");
        }
        public static void ServerSentEvents(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            rp.AddHeader("Cache-Control", "no-cache");
            rp.ContentType = "text/event-stream";
            rp.WithCode();
            Console.WriteLine(rp.SendChunked);
            rp.SendChunked = false;
            rp.ContentEncoding = Encoding.UTF8;

            SSE.ServerSentEventItem.RegisterResponse(rp);
            


        }
        #endregion
        #region Storage
        public static void StorageGetDirectories(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string path = Server.Functions.Downloader.DL.GetPath(true, args["Path"]);

            if (Directory.Exists(path))
            {
                string json = Newtonsoft.Json.JsonConvert.SerializeObject(Directory.EnumerateDirectories(path).Select<string, string>((o) => { return Path.GetFileName(o); }));
                rp.AsText(json, "application/json");
            }
            else
            {
                rp.AsText("[]", "application/json");
            }
        }
        public static void StorageGetFiles(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string path = Server.Functions.Downloader.DL.GetPath(true, args["Path"]);

            if (Directory.Exists(path))
            {
                string json = Newtonsoft.Json.JsonConvert.SerializeObject(Directory.EnumerateFiles(path).Select<string, string>((o) => { return Path.GetFileName(o); }));
                rp.AsText(json, "application/json");
            }
            else
            {
                rp.AsText("[]", "application/json");
            }
        }
        public static void StorageDirectoryExists(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string path = Server.Functions.Downloader.DL.GetPath(true, args["Path"]);
            string json = Directory.Exists(path) ? "true" : "false";
            rp.AsText(json, "text/plain");

        }
        public static void StorageFileExists(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string path = Server.Functions.Downloader.DL.GetPath(true, args["Path"]);
            string json = File.Exists(path) ? "true" : "false";
            rp.AsText(json, "text/plain");

        }
        public static void StorageFile(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            if (args["Path"].StartsWith("config/"))
            {
                rp.AsText("Access denied");
            }
            else
            {
                string path = Server.Functions.Downloader.DL.GetPath(true, args["Path"]);

                rp.AsFile(rq, path);
            }
        }
        public static void Video(HttpListenerRequest rq,HttpListenerResponse rp,Dictionary<string,string> args)
        {
            YoutubeExplode.Videos.VideoId? vid = YoutubeExplode.Videos.VideoId.TryParse(args["Id"]);
            if (vid.HasValue)
            {
                string path = Server.Functions.Downloader.DL.GetPath(true, "NotConverted",vid.Value +".mp4");
                rp.AddHeader("Content-Disposition", GetVideoContentDisposition(vid.Value).ToString());
                rp.AsFile(rq, path);
            }
            else
            {
                rp.WithCode(HttpStatusCode.BadRequest);
                rp.AsText("Invalid Video ID or URL", "text/plain");
            }
        }
        public static void VideoRes(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            YoutubeExplode.Videos.VideoId? vid = YoutubeExplode.Videos.VideoId.TryParse(args["Id"]);
            if (vid.HasValue)
            {
                int res;
                if(int.TryParse(args["Res"],out res))
                {
                    if (res > 2 || res < 0)
                    {
                        rp.WithCode(HttpStatusCode.BadRequest);
                        rp.AsText($"Invalid Resolution Number must be either 0, 1 or 2", "text/plain");
                    }
                    else
                    {
                        string[] m = new string[] { "Converted", "NotConverted", "Audio" };
                        string path = Server.Functions.Downloader.DL.GetPath(true, m[res], vid.Value + ".mp4");
                        rp.AddHeader("Content-Disposition", GetVideoContentDisposition(vid.Value).ToString());
                        rp.AsFile(rq, path);
                    }
                }

                else
                {
                    rp.WithCode(HttpStatusCode.BadRequest);
                    rp.AsText("Res is not a number", "text/plain");
                }
               
            }
            else
            {
                rp.WithCode(HttpStatusCode.BadRequest);
                rp.AsText("Invalid Video ID or URL", "text/plain");
            }
        }
        public static System.Net.Mime.ContentDisposition GetVideoContentDisposition(string id)
        {
            var cd = new System.Net.Mime.ContentDisposition();
            string filename = GetVideoName(id);
            cd.FileName = filename;
            
            return cd;
        }
        public static string GetVideoName(string id)
        {
            string name = id + ".mp4";
            string path = Server.Functions.Downloader.DL.GetPath(true, "Info", id + ".json");
            if (File.Exists(path))
            {
                string info=File.ReadAllText(path);
                name= JsonConvert.DeserializeObject<SavedVideo>(info).Title + ".mp4";
            }

            string asAscii = Encoding.ASCII.GetString(
            Encoding.Convert(
                Encoding.UTF8,
                Encoding.GetEncoding(
                    Encoding.ASCII.EncodingName,
                        new EncoderReplacementFallback(string.Empty),
                        new DecoderExceptionFallback()
                    ),
                    Encoding.UTF8.GetBytes(name)
                )
            );
            return asAscii;
        }
        public static void UploadFiles(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            var files = rq.ParseBody(args);
            foreach (var f in files.Values)
                f.Save(Path.Combine(webSitePath, f.FileName));
            rp.AsText("uploaded", "text/plain");
        }
        #endregion
        #region Other
        public static void Index(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            rp.AsFile(rq, Path.Combine(webSitePath, "index.html"));
        }
        public static void RootPath(HttpListenerRequest rq, HttpListenerResponse rp, Dictionary<string, string> args)
        {
            string path = Path.Combine(webSitePath, args["Path"].Split(new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]);

            rp.AsFile(rq, path);
        }
        #endregion

        public static bool Route_Before(HttpListenerRequest request, HttpListenerResponse response)
        {
            response.WithCORS();
            return false;
        }

    }
}
