using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Tesses.Http
{
    public static class RequestHandler
    {
        private class __IREQ : IRequestHandler
        {
            HandleRequestAsync a;
            public __IREQ(HandleRequestAsync hra)
            {
                a=hra;
            }
            public async Task Handle(ServerContext ctx)
            {
                if(a!=null)
                    await a(ctx);
            }
        }
        private static bool DefaultFileExists(VirtualStorage storage,string[] _defaultFileNames,string path,out string name)
        {
            foreach(var def in _defaultFileNames)
            {
                name = storage.PathCombine(path, def);
                if(storage.FileExists(name))
                {
                    return true;
                }
            }
            name = "";
            return false;
        }
        private static bool DefaultFileExists(string[] _defaultFileNames,string path,out string name)
        {
            foreach(var def in _defaultFileNames)
            {
                name = Path.Combine(path, def);
                if(File.Exists(name))
                {
                    return true;
                }
            }
            name = "";
            return false;
        }
        public static IRequestHandler FromVirtualStorage(VirtualStorage storage,string[] filenames=null,bool listDirectories=true,IRequestHandler _notFound=null)
        {
              if(filenames==null)
                filenames = new string[]{
                    "index.html",
                    "index.htm",
                    "default.html",
                    "default.htm"
                };

            if(_notFound == null)
                _notFound=error;
            
            return FromDelegate(async(e)=>{
                  string someUrl = WebUtility.UrlDecode(e.Request.CurrentUrl.Split(new char[]{'?'},2)[0].Substring(1));
            //Console.WriteLine(someUrl);
            if (storage.DirectoryExists(someUrl))
            {
                string name;
                if(DefaultFileExists(storage,filenames,someUrl,out name))
                {
                    e.Response.SendFile(storage,name);
                }else{
                    if(listDirectories){
                    
                    List<string> items=new List<string>();
                    foreach(var item in storage.EnumerateDirectories(someUrl))
                    {
                        items.Add(item + "/");
                    }
                    foreach(var item in storage.EnumerateFiles(someUrl))
                    {
                        items.Add(item);
                    }
                    
                    e.Response.SendFileListing($"Index of {e.Request.CurrentUrl.Split(new char[]{'?'},2)[0]}","Directory listing",items);
                    }else{
                        e.Response.StatusLine=403;
                        await _notFound.Handle(e);
                    }
                }
            }
            else if (storage.FileExists(someUrl))
            {
                e.Response.SendFile(storage,someUrl);
            }
            else
            {
              e.Response.StatusLine=404;
                        await _notFound.Handle(e);
            }      
            });
        }
        public static IRequestHandler FromDirectory(string directory,string[] filenames=null,bool listDirectories=true,IRequestHandler _notFound=null)
        {
            if(filenames==null)
                filenames = new string[]{
                    "index.html",
                    "index.htm",
                    "default.html",
                    "default.htm"
                };

            if(_notFound == null)
                _notFound=error;
            
            return FromDelegate(async(e)=>{
                  string someUrl = Path.Combine(directory,WebUtility.UrlDecode(e.Request.CurrentUrl.Split(new char[]{'?'},2)[0].Substring(1)).Replace('/', Path.DirectorySeparatorChar));
            //Console.WriteLine(someUrl);
            if (Directory.Exists(someUrl))
            {
                string name;
                if(DefaultFileExists(filenames,someUrl,out name))
                {
                    e.Response.SendFile(name);
                }else{
                    if(listDirectories){
                    
                    List<string> items=new List<string>();
                    foreach(var item in Directory.GetDirectories(someUrl))
                    {
                        items.Add(Path.GetFileName(item) + "/");
                    }
                    foreach(var item in Directory.GetFiles(someUrl))
                    {
                        items.Add(Path.GetFileName(item));
                    }
                    
                    e.Response.SendFileListing($"Index of {e.Request.CurrentUrl.Split(new char[]{'?'},2)[0]}","Directory listing",items);
                    }else{
                        e.Response.StatusLine=403;
                        await _notFound.Handle(e);
                    }
                }
            }
            else if (File.Exists(someUrl))
            {
                e.Response.SendFile(someUrl);
            }
            else
            {
              e.Response.StatusLine=404;
                        await _notFound.Handle(e);
            }      
            });
        }
        public static IRequestHandler FromDelegate(HandleRequestAsync hra)
        {
            return new __IREQ(hra);
        }
        public static IRequestHandler FromDelegateSync(HandleRequest req)
        {
            return new __IREQ(async(e)=>{
                await Task.Run(()=>{
                    if(req != null)
                    req(e);
                });
            });
        }
        internal static IRequestHandler error=new ErrorRequestHandler();
        public static IRequestHandler Guaranteed(ServerContext ctx,IRequestHandler value)
        {
            
            if(value == null){
                 ctx.Response.StatusLine =404;
                 return error;
                 
            }
            
            return value;
        }
    }
    public interface IRequestHandler
    {
        Task Handle(ServerContext ctx);
    }
    public delegate Task HandleRequestAsync(ServerContext ctx);
    public delegate void HandleRequest(ServerContext ctx);
    public sealed class HTTPServer
    {
        public IPEndPoint LocalEndPoint {get{return (IPEndPoint)_listener.LocalEndpoint;}}
        IRequestHandler req;
        public bool AllowMultipleRequestsOnOneConnection {get;set;}
        public HTTPServer(HandleRequestAsync handler,TcpListener lstn)
        {
            req=RequestHandler.FromDelegate(handler);
            _listener=lstn;
        }
        public HTTPServer(HandleRequest handler,TcpListener lstn)
        {
            req = RequestHandler.FromDelegateSync(handler);
        }
        public HTTPServer(IRequestHandler handler,TcpListener lstn)
        {
            req=handler;
            _listener=lstn;
        }   
        public Func<ServerContext,bool> FirstTime {get;set;}=null;
        public bool CatchExceptions {get;set;} = true;
        TcpListener _listener;
        public void Listen(CancellationToken token=default(CancellationToken))
        {
            ListenAsync(token).Wait();
        }
        public async Task ListenAsync(CancellationToken token=default(CancellationToken))
        {
             _listener.Start();
            using (var r = token.Register(() => _listener.Stop())) { 
            while (!token.IsCancellationRequested)
                {
                    try{
                    
                    var socket=await _listener.AcceptTcpClientAsync();
                    Task.Factory.StartNew(async()=>{
                        try{
                        await HandleConnectionAsync(socket);
                        }catch(Exception ex)
                    {
                        _=ex;
                        if(!CatchExceptions)
                        {
                            throw ex;
                        }
                    }
                    }).Wait(0);
                    
                    }catch(Exception ex)
                    {
                        _=ex;
                         if(!CatchExceptions)
                        {
                            throw ex;
                        }
                    }
                }
            }
        }
        private async Task HandleConnectionAsync(TcpClient clt)
        {
            bool first=true;
            bool allowMultipleRequests=AllowMultipleRequestsOnOneConnection;
            while(clt.Connected)
            {
                var s=clt.GetStream();
                HttpParser parser=new HttpParser(s);
                 parser.ReceiveHeaders();
                 parser.SentHeaders=new HeaderCollection();
                if(!allowMultipleRequests)
                {
                    parser.SentHeaders.Add("Connection","close");
                }else{
                    parser.SentHeaders.Add("Connection","keep-alive");
                }
                var version = this.GetType().Assembly.GetName().Version;
                parser.SentHeaders.Add("Server",$"Tesses.Http/{version.ToString()}");

               
                var ctx = new ServerContext(()=>{return clt.Connected;},parser,(IPEndPoint)clt.Client.LocalEndPoint,(IPEndPoint)clt.Client.RemoteEndPoint);
                if(first && FirstTime != null)
                {
                    if(!FirstTime(ctx))
                    {
                        return;
                    }
                }
                await req.Handle(ctx);
               
                first=false;
                if(parser.ReceivedHeaders.ContainsKey("Connection"))
                {
                    if(parser.ReceivedHeaders["Connection"].Contains("close"))
                    {
                        break;
                    }
                }
                if(!allowMultipleRequests) break;
            }
            clt.Dispose();
        }
    }

    
}
