using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using System.Net.Http;

namespace Tesses.Http
{

    public struct FirstLine
    {
        public FirstLine(string line)
        {
            LineText=line;
        }
        public string LineText {get;set;}

        public static implicit operator FirstLine(string text)
        {
            return new FirstLine(text);
        }
        public static implicit operator string(FirstLine line)
        {
            return line.ToString();
        }
        public override string ToString()
        {
            return LineText;
        }

    }
    public struct RequestLine
    {
        
        public RequestLine(string method,string path,string httpVersion)
        {
            Method = method;
            Path= path;
            HttpVersion=httpVersion;
        }
        public RequestLine(string method,string path)
        {
            Method=method;
            Path=path;
            HttpVersion = "HTTP/1.1";
        }

        public string Method {get;set;}

        public string Path {get;set;}

        public string HttpVersion {get;set;}

        public static implicit operator RequestLine(FirstLine line)
        {
           string[] data=line.LineText.Split(' ');
            RequestLine Rline=new RequestLine(data[0],data[1],data[2]);
           
            return Rline;
        }

        public static implicit operator FirstLine(RequestLine line)
        {
            return new FirstLine(line.ToString());
        }
        public static implicit operator RequestLine(string path)
        {
            RequestLine req=new RequestLine();
            req.Method="GET";
            req.Path=path;
            req.HttpVersion="HTTP/1.1";
            return req;
        }
        public override string ToString()
        {
            return $"{Method} {Path} {HttpVersion}";
        }
    }
    public struct StatusLine 
    {
        public StatusLine(string httpVersion,HttpStatusCode code)
        {
            HttpVersion = httpVersion;
            StatusCode=(int)code;
        }
        public StatusLine(string httpVersion,int code)
        {
            HttpVersion = httpVersion;
            StatusCode = code;
        }
        
        public string HttpVersion {get;set;}
        public int StatusCode {get;set;}
        public static implicit operator StatusLine(FirstLine line)
        {
            //HTTP/1.1 200 OK
            StatusLine Sline=new StatusLine();
            string[] data=line.LineText.Split(' ');
            Sline.HttpVersion = data[0];
            int n=200;
            if(!int.TryParse(data[1],out n))
            {
                n=500;
            }
            Sline.StatusCode=n;
            return Sline;
        }
        public static implicit operator StatusLine(int code)
        {
            StatusLine line = new StatusLine();
            line.HttpVersion= "HTTP/1.1";
            line.StatusCode=code;
            return line;
        }
        public static implicit operator FirstLine(StatusLine line)
        {
            return new FirstLine(line.ToString());
        }
        
        public override string ToString()
        {
           return $"{HttpVersion} {StatusCode}";
        }
    }
    public class HeaderCollection : Dictionary<string,List<string>>
    {
        public HeaderCollection()
        {
            FirstLine="";
        }
        public HeaderCollection(string firstLine)
        {
            FirstLine=firstLine;
        }
        public HeaderCollection(Stream strm)
        {
            string[] headers=strm.ReadHttpHeaders().Split(new string[]{"\r\n"},StringSplitOptions.RemoveEmptyEntries);
            foreach(var hdr in headers)
            {
                Add(hdr);
            }
        }
        public FirstLine FirstLine {get;set;}
        public void Add(string header)
        {
            if(header.Contains(":"))
            {
                //its a kvp
                string[] hdr=header.Split(new string[]{": "},2,StringSplitOptions.None);
                if(hdr.Length ==1)
                {
                    this.Add(hdr[0],"");
                }else{
                    this.Add(hdr[0],hdr[1]);
                }
            }else{
                FirstLine = header;
            }
        }

        public override string ToString()
        {
            StringBuilder b=new StringBuilder();
            b.Append($"{FirstLine}\r\n");
            
            foreach(var kvps in this)
            {
                foreach(var value in kvps.Value)
                {
                    b.Append($"{kvps.Key}: {value}\r\n");
                }
            }
            b.Append("\r\n");
            return b.ToString();
        }
        internal byte[] AsBytes()
        {
            
             return Encoding.UTF8.GetBytes(ToString());
        }
        public void WriteHeaders(Stream strm)
        {
            byte[] buff=AsBytes();
            strm.Write(buff,0,buff.Length);
            strm.Flush();
        }
    }
    
    public class HttpParser
    {
        private class HttpBodyStream : Stream
        {
            Stream strm;
            long length;
            public HttpBodyStream(Stream src,long len)
            {
                this.strm = src;
                this.length=len;
            }
            public override bool CanRead => strm.CanRead;

            public override bool CanSeek => false;

            public override bool CanWrite => false;

            public override long Length => length;
            
            private long read=0;
            public override long Position { get {return read;} set => throw new NotImplementedException(); }

            public override void Flush()
            {
                strm.Flush();
            }
            public override int ReadByte()
            {
                if(length > 0 && read >= length) return -1;
                return strm.ReadByte();
            }

            public override int Read(byte[] buffer, int offset, int count)
            {
                int read0=0;
                if(length == 0) {
                    
                  read0= strm.Read(buffer,offset,count);
                  read+=read0;
                  return read0;
                }

                //i want to read some data
                read0=(int)Math.Min(count,length-read);
                if(read0 == 0) return 0;
        
                read0=strm.Read(buffer,offset,read0);
       
                read+=read0;

                return read0;
            }
            public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
            {
                  int read0=0;
                if(length == 0) {
                    
                  read0= await strm.ReadAsync(buffer,offset,count);
                  read+=read0;
                  return read0;
                }

                //i want to read some data
                read0=(int)Math.Min(count,length-read);
                if(read0 == 0) return 0;
        
                read0=await strm.ReadAsync(buffer,offset,read0);
       
                read+=read0;

                return read0;
            }

            public override long Seek(long offset, SeekOrigin origin)
            {
                throw new NotImplementedException();
            }

            public override void SetLength(long value)
            {
                throw new NotImplementedException();
            }

            public override void Write(byte[] buffer, int offset, int count)
            {
                throw new NotImplementedException();
            }
            public override void Close()
            {
                strm.Close();
            }
            protected override void Dispose(bool disposing)
            {
                if(disposing)
                {
                    strm.Dispose();
                }
            }
            public override async Task FlushAsync(CancellationToken cancellationToken)
            {
                 await strm.FlushAsync(cancellationToken);
            }
        }
        
        Stream _strm;
        public HttpParser(Stream strm)
        {
            
            _strm=strm;
             
        }
        public Stream GetRawStream()
        {
            return _strm;
        }
        public HeaderCollection SentHeaders {get;set;}

        public HeaderCollection ReceivedHeaders {get;set;}



        public void SendHeaders()
        {
            SentHeaders.WriteHeaders(_strm);

        }

        public void ReceiveHeaders()
        { 
            ReceivedHeaders = new HeaderCollection(_strm);
        }
        public void WriteBody(Stream strm)
        {
            strm.CopyTo(_strm);
            _strm.Flush();
        }
        public void WriteBody(string text)
        {
            WriteBody(System.Text.Encoding.UTF8.GetBytes(text));
        }
        public void WriteBody(byte[] data)
        {
            using(var ms = new MemoryStream())
            {
                ms.Write(data,0,data.Length);
                ms.Seek(0,SeekOrigin.Begin);
                WriteBody(ms);
            }
        }

        public Stream ReadBody()
        {
            long res=0;
            
            if(!ReceivedHeaders.TryGetFirst("Content-Length",out res))
            {
                res=0;
            }

            return new HttpBodyStream(_strm,res);
        }
    }
}