using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text; 
using System.Threading;
using System.Web;
using System.Windows.Forms;

namespace AppTime
{
    class WebServer
    {  
        HttpListener listener;
        Thread thread;
        //public HttpListenerRequest Request;
        //public HttpListenerResponse Response;
        public string WebRootPath;
        HashSet<string> defaultPage = new HashSet<string>(StringComparer.OrdinalIgnoreCase){
                    "index.html",
                    "index.htm",
                    "default.html",
                    "default.htm"
                };

        public void Start(int port, object controller, string webfolder = "web")
        {
            thread = new Thread(() =>
            {
                WebRootPath = webfolder = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), webfolder);
                var folders = Directory.GetDirectories(webfolder, "*", SearchOption.AllDirectories);

                var webroot = $@"http://localhost:{port}/";


                listener = new HttpListener();
                foreach (var f in folders)
                {
                    listener.Prefixes.Add(webroot + f.Substring(webfolder.Length + 1).Replace("\\", "/") + "/");
                }
                listener.Prefixes.Add(webroot);
                listener.Start();

                while (true)
                {
                    var ctx = listener.GetContext();
                    //Debug.WriteLine($"Web Request:{ctx.Request.Url}");
                    //ThreadPool.QueueUserWorkItem(_ => processRequest(ctx, webfolder, controller));
                    new Thread(() => processRequest(
                        ctx,
                        webfolder,
                        controller)
                    )
                    { IsBackground = true, Name = "WebServer Process Request" }.Start();
                }
            })
            {
                Name = "WebServer",
                IsBackground = true
            };
            thread.Start();
        }


        void processRequest(HttpListenerContext context, string webfolder, object controller)
        {
            var request = context.Request; 
            var file = webfolder + request.RawUrl.Replace("/", "\\");
            string query = "";
            var p = file.IndexOf("?");
            if (p > 0)
            {
                query = file.Substring(p + 1);
                file = file.Substring(0, p);
            }
            var response = context.Response;
            byte[] data = null;


            if (Path.GetFileName(Path.GetDirectoryName(file)).Equals("data", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    if (string.IsNullOrWhiteSpace(query))
                    {
                        using var reader = new StreamReader(request.InputStream);
                        query = reader.ReadToEnd();
                    }
                    var info = JObject.Parse(HttpUtility.UrlDecode(query));
                    var method = controller.GetType().GetMethod(Path.GetFileName(file));
                    var args = info.Value<JArray>("args").Cast<object>().ToArray();
                    var @params = method.GetParameters();
                    for (var i = 0; i < args.Length; i++)
                    {
                        if (args[i] is JObject jo)
                        {
                            args[i] = jo.ToObject(@params[i].ParameterType);
                        }
                        else
                        {
                            args[i] = Convert.ChangeType(args[i], @params[i].ParameterType);
                        }
                    } 

                    var result = method.Invoke(controller, args);
                    if (result is byte[] bytes)
                    {
                        data = bytes;
                    }
                    else
                    {
                        data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(result));
                    }
                }
                catch (Exception ex)
                {
                    data = Encoding.UTF8.GetBytes($"bad request: method:{Path.GetFileName(file)} query:{query}");
                }
            }
            else
            {
                if (Directory.Exists(file))
                {
                    var def = Directory.GetFiles(file).FirstOrDefault(f => defaultPage.Contains(Path.GetFileName(f), StringComparer.OrdinalIgnoreCase));
                    if (def != null)
                    {
                        file = def;
                    }
                }

                if (File.Exists(file))
                {
                    var mime = MimeMapping.GetMimeMapping(file);
                    response.Headers.Add("Content-type", mime);
                    if (mime.StartsWith("text/"))
                    {
                        response.ContentEncoding = Encoding.GetEncoding("utf-8");
                        data = Encoding.UTF8.GetBytes(File.ReadAllText(file, Encoding.Default));
                    }
                    else
                    {
                        data = File.ReadAllBytes(file);
                    }
                }
            }

            if (data == null)
            {
                data = Encoding.UTF8.GetBytes($"file not found: {file}");
            }

            response.ContentLength64 = data.Length;

            var output = response.OutputStream;
            try
            {
                output.Write(data, 0, data.Length);
            }
            catch (HttpListenerException ex)
            {

            }
            output.Close();
        }

         
        public void Stop()
        {
            thread.Abort();
            listener.Stop();
            listener.Close();
        }
    }
}