using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Mime;

namespace Tesses.Http
{
    public sealed class MultipartParser
    {
        string boundary="";
        HttpParser p;
        public MultipartParser(HttpParser parser)
        {
            p=parser;

            
            //boundary=----WebKitFormBoundaryyGTVag4OQVNjqdSl
            string _bstr;
            if(p.ReceivedHeaders.TryGetFirst("Content-Type",out _bstr))
            {
                //multipart/*; boundary=somevalue
                int offsetOfBoundary = _bstr.IndexOf("boundary=");
                if(!_bstr.StartsWith("multipart/")) throw new Exception("not a multipart request/response");
                if(offsetOfBoundary < 0) throw new Exception("No \"boundary=\" in multipart header");
                offsetOfBoundary+=9;
                int offsetOfSemiAfterBoundary = _bstr.IndexOf(';',offsetOfBoundary);
                if(offsetOfSemiAfterBoundary < 0)
                {
                    boundary=_bstr.Substring(offsetOfBoundary);
                }
                else
                {
                    boundary=_bstr.Substring(offsetOfBoundary,offsetOfSemiAfterBoundary - offsetOfBoundary);
                }
                //we got the boundary str
                

            }else{
                throw new Exception("No Content-Type found");
            }
        }
        #region DAJURIC_SIMPLE_HTTP_CODE
        public IEnumerable<MultipartEntry> Parse(OnFile onFile,bool disposeStream=true)
        {
            
        
            string bound = "--" + boundary;
            //Console.WriteLine("BEFORE STREAM");
            var inputStream = new BufferedStream(p.ReadBody());
             //Print("Before ParseUntillBoundaryEnd");
              //Console.WriteLine("Before ParseUntilBoundaryEnd");
         
            parseUntillBoundaryEnd(inputStream, new MemoryStream(), bound);
            //Console.WriteLine("After ParseUntilBoundaryEnd");
            //Print("After ParseUntillBoundaryEnd");
            while (true)
            {
                //Print("Before ParseSection");
                var (n, v, fn, hdrs,ok) = parseSection(inputStream, "\r\n" + bound, onFile);
                if(!ok) break;
                //Print("After ParseSection");
                

                v.Position = 0;
                yield return new MultipartEntry(n,hdrs,fn,v);

                if(disposeStream)
                {
                    v.Dispose();
                }
               
            }

            
        }
        
        //this region of code is somewhat modified from https://github.com/dajuric/simple-http/
         /// <summary>
    /// Delegate executed when a file is about to be read from a body stream.
    /// </summary>
    /// <param name="fieldName">Field name.</param>
    /// <param name="fileName">name of the file.</param>
    /// <param name="contentType">Content type.</param>
    /// <returns>Stream to be populated.</returns>
    public delegate Stream OnFile(string fieldName, string fileName, string contentType);
        
         private static bool parseUntillBoundaryEnd(Stream source, Stream destination, string boundary)
        {
            var checkBuffer = new byte[boundary.Length]; //for boundary checking

            int b, i = 0;
            while ((b = source.ReadByte()) != -1)
            {
                //Console.WriteLine(i);
                if (i == boundary.Length) //boundary found -> go to the end of line
                {
                    if (b == '\n') break;
                    if(b == '-') {
                        source.ReadByte();
                        return false;
                    }
                    continue;
                }

                if (b == boundary[i]) //start filling the check buffer
                {
                    checkBuffer[i] = (byte)b;
                    i++;
                }
                else
                {
                    var idx = 0;
                    while (idx < i) //write the buffer data to stream
                    {
                        destination.WriteByte(checkBuffer[idx]);
                        idx++;
                    }

                    i = 0;
                    destination.WriteByte((byte)b); //write the current byte
                }
            }
            return true;
        }

        private static (string Name, Stream Value, string FileName, Dictionary<string,List<string>> headers,bool isOk) parseSection(Stream source, string boundary, OnFile onFile)
        {
            //we must read two bytes
           HeaderCollection coll=new HeaderCollection(source);
           ContentDisposition disposition=new ContentDisposition();
           string ct="application/octet-stream";
           string n="";
           string fn="";
           if(coll.ContainsKey("Content-Disposition"))
           {
               disposition=new ContentDisposition(coll["Content-Disposition"][0]);
                
                n=disposition.Parameters["name"];
                fn=disposition.FileName;
                
           }
           if(!coll.TryGetFirst("Content-Type",out ct))
           {
               ct="application/octet-stream";
           }
            //Print("Before ReadContentDisposition");
            //var (n, fn, ct) = readContentDisposition(source);
            //Print("After ReadContentDisposition");
             //\r\n (empty row)



            var dst = String.IsNullOrEmpty(fn) ? new MemoryStream() : onFile(n, fn, ct);
            if (dst == null)
                throw new ArgumentException(nameof(onFile), "The on-file callback must return a stream.");
            //Print("Before ParseUntillBodyEnd");
            bool isOk = parseUntillBoundaryEnd(source, dst, boundary);
            //Print("Before ParseUntillBodyEnd");
            return (n, dst, fn, coll,isOk);
        }

       
        #endregion
        public IEnumerable<MultipartEntry> Parse(bool disposeStream=true)
        {
            return Parse((e,f,g)=>{return new MemoryStream();},disposeStream);
        }
    }

    public class MultipartEntry : IDisposable
    {
        internal MultipartEntry(string name,Dictionary<string,List<string>> headers,string filename,Stream strm)
        {
            Name=name;
            this.Stream=strm;
            FileName = filename;
            HasFileName=!string.IsNullOrWhiteSpace(filename);
            Headers =headers;
            string ct;
            if(Headers.TryGetFirst("Content-Type",out ct))
            {
                this.MimeType=ct;
            }
        }
        public Dictionary<string,List<string>> Headers {get;set;}
        public bool HasFileName {get;private set;}
        public string MimeType {get;private set;}
        public string FileName {get;private set;}

        public string Name {get;private set;}

        public Stream Stream {get;private set;}

        public void Dispose()
        {
            Stream.Dispose();
        }

        public byte[] GetData()
        {
           return Stream.AsArray();
        }

        public string GetStringData()
        {
            return GetData().AsString();
        }
    }
}