using System.Net;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net.Http;
using System;
using System.Linq;
using System.Web;
using Newtonsoft.Json;
using System.Net.Mime;
using HeyRed.Mime;

namespace Tesses.Http
{
    public class ServerContext
    {
       
        public ServerRequest Request {get;private set;}

        public ServerResponse Response {get;private set;}

        internal HttpParser p;
        public Stream GetRawStreamWithHeaders()
        {
            return new PrependStream(Encoding.UTF8.GetBytes(p.ReceivedHeaders.ToString()),p.GetRawStream());
        }
        public Stream GetRawStream()
        {
            return p.GetRawStream();
        }
        public bool IsConnected {get{
            if(_isConnected!=null) return _isConnected();

            return false;

        }}

        Func<bool> _isConnected;
        internal ServerContext(Func<bool> isConnected,HttpParser parser,IPEndPoint local,IPEndPoint remote)
        {
            _isConnected=isConnected;
            p=parser;
            Request=new ServerRequest(parser,remote);
           
            Response=new ServerResponse(isConnected,Request.RequestLine,parser,local);
           
            
        }

        
         
    }
    public class ServerResponse 
    {
        RequestLine ln;
        Func<bool> _isConnected;
        string ogurl;
        internal ServerResponse(Func<bool> isConnected,RequestLine line,HttpParser parser,IPEndPoint local)
        {
            
            ln=line;
            Address=local;
            _isConnected=isConnected;
            ogurl = ln.Path.Split('?')[0];
            this.parser=parser;
           StatusLine=200;
        }
         HttpParser parser;
        public IPEndPoint Address {get;set;}
        public Dictionary<string,List<string>> Headers {get {return parser.SentHeaders;}}

        public StatusLine StatusLine {get {return parser.SentHeaders.FirstLine;} set { parser.SentHeaders.FirstLine=value;}}
        
        public ServerResponse WithContentType(string contentType)
        {
            Headers.Add("Content-Type",contentType);
            return this;
        }
        public void SendStatusCodeHtml()
        {
            StringBuilder builder=new StringBuilder();
            builder.AppendLine("<html>");
            builder.AppendLine($"<head><title>{StatusLine.StatusCode} {StatusLine.GetReasonPhrase()}</title></head>");
            builder.AppendLine("<body>");
            builder.AppendLine($"<center><h1>{StatusLine.StatusCode} {StatusLine.GetReasonPhrase()}</h1></center>");
            builder.AppendLine($"<center><h4>{ogurl}</h4></center>");
            builder.AppendLine("<hr><center><a href=\"https://www.nuget.org/packages/Tesses.Http\">Tesses.Http</a></center>");
            builder.AppendLine("</body>");
            builder.AppendLine("</html>");

            WithContentType("text/html").SendText(builder);

            /*
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.18.0 (Ubuntu)</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
*/
        }
        public ServerResponse WithFileName(string filename,bool inline=false)
        {
            ContentDisposition contentDisposition=new ContentDisposition();
            contentDisposition.FileName=filename;
            contentDisposition.Inline=inline;
            Headers.Add("Content-Disposition",contentDisposition.ToString());
            return this;
        }
        public ServerResponse WithHeader(string key,string value)
        {
            Headers.Add(key,value);
            return this;
        }
        public void ServerSentEvents(SendEvents evt)
        {
            bool __connected=true;
            if(_isConnected == null) return;
            WithContentType("text/event-stream").WithHeader("Cache-Control","no-cache").parser.SendHeaders(); 
            try{ 
                EventHandler<SendEventArgs> cb= (sender,e0)=>{
                    if(__connected)
                    parser.WriteBody($"data: {e0.Data}\n\n");
                };
                evt.EventReceived += cb;
            while(_isConnected());
            evt.EventReceived -= cb;
            __connected=false;
            }catch(Exception ex)
            {
                _=ex;
            }
        }
        public void SendText(StringBuilder b)
        {
            SendText(b.ToString());
        }
        public void SendJson(object o)
        {
            WithContentType("application/json").SendText(JsonConvert.SerializeObject(o));
        }
        public void SendText(string text)
        {
            MemoryStream strm=new MemoryStream();
            using(var sw=new StreamWriter(strm))
            {
               
                sw.Write(text);
                sw.Flush();
            
            
            strm.Seek(0,SeekOrigin.Begin);
           
            SendResponseStream(strm);
            }
        }
        private string _getFileNameWithoutQuery(string url)
        {
            int r = url.IndexOf('?');
            if(r > -1)
            {
                return Path.GetFileName(url.Remove(r).TrimEnd('/')) + (url.EndsWith("/") ? "/" : "");
            }
            return Path.GetFileName(url.TrimEnd('/')) + (url.EndsWith("/") ? "/" : "");
        }
        public ServerResponse WithContextTypeFromFileName(string filename)
        {
            return WithContentType(MimeTypesMap.GetMimeType(filename));
        }
        public void SendRedirect(string redirectTo)
        {
            StatusLine=301;
            
            WithHeader("Location",redirectTo).WithHeader("Cache-Control","no-cache").parser.SendHeaders();
            
        }

        public void SendFileListing(string title,string desc,IEnumerable<string> entries)
        {
            if(!this.ln.Path.EndsWith("/"))
            {
                SendRedirect(this.ln.Path + '/');
                return;
            }
            StringBuilder b=new StringBuilder();
            b.Append($"<!doctype><html><head><title>{HttpUtility.HtmlEncode(title)}</title></head><body><center><h1>{HttpUtility.HtmlEncode(title)}</h1></center><center>{HttpUtility.HtmlEncode(desc)}</center><hr><a href=\"..\">../</a><br>");
            foreach(var entry in entries)
            {
                //<a href=
                b.Append($"<a href=\"{HttpUtility.HtmlAttributeEncode(entry)}\">{HttpUtility.HtmlEncode(_getFileNameWithoutQuery(entry))}</a><br>");
            }
            b.Append("<hr><br><center><a href=\"https://www.nuget.org/packages/Tesses.Http\">Tesses.Http</a></center></body></html>");

            WithContentType("text/html").SendText(b);
        }
        public void SendFileAsDownload(string file)
        {
            WithFileName(Path.GetFileName(file)).SendFile(file);
        }
         public void SendFileAsDownload(VirtualStorage storage,string file)
        {
            WithFileName(Path.GetFileName(file)).SendFile(storage,file);
        }
        public void SendFile(string file)
        {
            using(var f = File.OpenRead(file))
            {
                WithContextTypeFromFileName(file).SendRangableResponseStream(f);
            }
        }
        public void SendFile(VirtualStorage storage,string file)
        {
            using(var f = storage.Open(file,FileMode.Open,FileAccess.Read,FileShare.Read))
            {
                WithContextTypeFromFileName(file).SendRangableResponseStream(f);
            }
        }
        public void SendResponseStream(Stream strm)
        {
            
            
            Headers.Add("Content-Length",strm.Length.ToString());
            parser.SendHeaders();
            if(ln.Method != "HEAD")
                parser.WriteBody(strm);
            
            
        }

        public void SendRangableResponseStream(Stream strm)
        {
            
           
            if(parser.ReceivedHeaders.ContainsKey("Range") && strm.CanSeek)
            {
                 int start = 0, end = (int)strm.Length - 1;
                if (parser.ReceivedHeaders["Range"].Count > 1)
                {
                    throw new NotSupportedException("Multiple 'Range' headers are not supported.");
                }
                var range = parser.ReceivedHeaders["Range"][0].Replace("bytes=", String.Empty)
                                   .Split(new string[] { "-" }, StringSplitOptions.RemoveEmptyEntries)
                                   .Select(x => Int32.Parse(x))
                                   .ToArray();

                start = (range.Length > 0) ? range[0] : 0;
                end = (range.Length > 1) ? range[1] : (int)(strm.Length - 1);


                var hdrs = parser.SentHeaders;
                hdrs.Add("Accept-Ranges", "bytes");
                hdrs.Add("Content-Range", "bytes " + start + "-" + end + "/" + strm.Length);
                hdrs.Add("Content-Length", (end - start + 1).ToString());
                StatusLine = 206;

                parser.SendHeaders();
                if(ln.Method != "HEAD")
                    parser.WriteBody(new RangeStream(strm,start,end-start+1));

                
              
            }else{
                SendResponseStream(strm);
            }
            
            
        }
    }
    public class ServerRequest 
    {
        public MultipartParser GetMultipartParser()
        {
            return new MultipartParser(this.parser);
        }
        public ServerRequest(HttpParser parser,IPEndPoint remote)
        {
            

            Address=remote;
      
            this.parser=parser;
           OriginalUrl = RequestLine.Path;
        }
        HttpParser parser;
        public IPEndPoint Address {get;set;}

        public string GetQueryParameters(string pathAndQuery,Dictionary<string,List<string>> p)
        {
            string[] qParm = pathAndQuery.Split(new char[]{'?'},2,StringSplitOptions.RemoveEmptyEntries);
            if(qParm.Length == 0) return "";
            if(qParm.Length == 1)
            {
                return qParm[0];
            }else{
                foreach(var kvp in qParm[1].Split(new char[]{'&'},StringSplitOptions.RemoveEmptyEntries))
                {
                    string[] _kvp = kvp.Split(new char[]{'='},2,StringSplitOptions.RemoveEmptyEntries);
                    if(_kvp.Length > 0)
                    {
                        if(_kvp.Length == 2)
                        {
                            p.Add(_kvp[0],HttpUtility.UrlEncode(_kvp[1]));
                        }else{
                            p.Add(_kvp[0],"");
                        }

                    }
                }

                return qParm[0];
            }


            
            
        }
        public string OriginalUrl {get;private set;}

        public string CurrentUrl {get{return RequestLine.Path;} set{RequestLine=new RequestLine(RequestLine.Method,value,RequestLine.HttpVersion);}}
        public string GetQueryParameters(Dictionary<string,List<string>> p)
        {
            return GetQueryParameters(RequestLine.Path,p);
        }
        public RequestLine RequestLine {get{return parser.ReceivedHeaders.FirstLine;} set {parser.ReceivedHeaders.FirstLine=value;}}

        public Dictionary<string,List<string>> Headers {get {return parser.ReceivedHeaders;}}

        public string GetRequestString()
        {
            using(var sr=new StreamReader(GetRequestStream()))
            {
                return sr.ReadToEnd();
            }
        }
        public T GetRequestJson<T>()
        {
            return JsonConvert.DeserializeObject<T>(GetRequestString());
        }

        public void GetUrlEncodedPost(Dictionary<string,List<string>> p)
        {
            string urlData=$"{OriginalUrl}?{GetRequestString()}";
            GetQueryParameters(urlData,p);
        }

        public Stream GetRequestStream()
        {
            return parser.ReadBody();
        }
    }
}