using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Globalization;
using UnityEngine;
//using Ionic.Zlib;

namespace HTTP
{
    public class Response
    {
        public Request request;
        public int status = 200;
        public string message = "OK";
        public byte[] bytes;

        Dictionary<string, List<string>> headers = new Dictionary<string, List<string>> ();

        public string Text {
            get {
                if (bytes == null)
                    return "";
                return System.Text.UTF8Encoding.UTF8.GetString (bytes);
            }
        }

        public string Asset {
            get {
                throw new NotSupportedException ("This can't be done, yet.");
            }
        }

        public Hashtable Object {
            get {
                if ( bytes == null )
                {
                    return null;
                }
                
                bool result = false;
                Hashtable obj = (Hashtable)JSON.JsonDecode( this.Text, ref result );
                //Hashtable obj = JsonUtility.FromJson<Hashtable>(this.Text);
                if ( !result )
                {
                    obj = null;
                }
                
                return obj;
            }
        }
        
        public ArrayList Array {
            get {
                if ( bytes == null )
                {
                    return null;
                }
                
                bool result = false;
                ArrayList array = (ArrayList)JSON.JsonDecode( this.Text, ref result );
                //ArrayList array = JsonUtility.FromJson<ArrayList>(this.Text);

                if ( !result )
                {
                    array = null;
                }
                
                return array;
            }
        }
        
        void AddHeader (string name, string value)
        {
            name = name.ToLower ().Trim ();
            value = value.Trim ();
            if (!headers.ContainsKey (name))
                headers[name] = new List<string> ();
            headers[name].Add (value);
        }

        public List< string > GetHeaders()
        {
            List< string > result = new List< string >();
            foreach (string name in headers.Keys) {
                foreach (string value in headers[name]) {
                    result.Add( name + ": " + value );
                }
            }

            return result;
        }

        public List<string> GetHeaders (string name)
        {
            name = name.ToLower ().Trim ();
            if (!headers.ContainsKey (name))
                return new List<string> ();
            return headers[name];
        }

        public string GetHeader (string name)
        {
            name = name.ToLower ().Trim ();
            if (!headers.ContainsKey (name))
                return string.Empty;
            return headers[name][headers[name].Count - 1];
        }

        public Response ()
        {
            //ReadFromStream (stream);
        }

        string ReadLine (Stream stream)
        {
            var line = new List<byte> ();
            while (true) {
                int c = stream.ReadByte ();
                if (c == -1) {
                    throw new HTTPException("Unterminated Stream Encountered.");
                }
                if ((byte)c == Request.EOL[1])
                    break;
                line.Add ((byte)c);
            }
            var s = ASCIIEncoding.ASCII.GetString (line.ToArray ()).Trim ();
            return s;
        }

        string[] ReadKeyValue (Stream stream)
        {
            string line = ReadLine (stream);
            if (line == "")
                return null;
            else {
                var split = line.IndexOf (':');
                if (split == -1)
                    return null;
                var parts = new string[2];
                parts[0] = line.Substring (0, split).Trim ();
                parts[1] = line.Substring (split + 1).Trim ();
                return parts;
            }

        }

        public void ReadFromStream( Stream inputStream )
        {
            //var inputStream = new BinaryReader(inputStream);
            var top = ReadLine (inputStream).Split (new char[] { ' ' });

            if (!int.TryParse (top[1], out status))
                throw new HTTPException ("Bad Status Code");

            // MemoryStream is a disposable
            // http://stackoverflow.com/questions/234059/is-a-memory-leak-created-if-a-memorystream-in-net-is-not-closed
            using (var output = new MemoryStream ()) {
                message = string.Join (" ", top, 2, top.Length - 2);
                headers.Clear ();

                while (true) {
                    // Collect Headers
                    string[] parts = ReadKeyValue( inputStream );
                    if ( parts == null )
                        break;
                    AddHeader( parts[ 0 ], parts[ 1 ] );
                }

                if ( request.cookieJar != null )
                {
                    List< string > cookies = GetHeaders( "set-cookie" );
                    for ( int cookieIndex = 0; cookieIndex < cookies.Count; ++cookieIndex )
                    {
                        string cookieString = cookies[ cookieIndex ];
                        if ( cookieString.IndexOf( "domain=", StringComparison.CurrentCultureIgnoreCase ) == -1 )
                        {
                            cookieString += "; domain=" + request.uri.Host;
                        }

                        if ( cookieString.IndexOf( "path=", StringComparison.CurrentCultureIgnoreCase ) == -1 )
                        {
                            cookieString += "; path=" + request.uri.AbsolutePath;
                        }

                        request.cookieJar.SetCookie( new Cookie( cookieString ) );
                    }
                }

                if ( GetHeader( "transfer-encoding" ) == "chunked" ) {
                    while (true) {
                        // Collect Body
                        int length = int.Parse( ReadLine( inputStream ), NumberStyles.AllowHexSpecifier );

                        if ( length == 0 ) {
                            break;
                        }
                        
                        for (int i = 0; i < length; i++) {
                            output.WriteByte( (byte)inputStream.ReadByte() );
                        }

                        //forget the CRLF.
                        inputStream.ReadByte();
                        inputStream.ReadByte();
                    }

                    while (true) {
                        //Collect Trailers
                        string[] parts = ReadKeyValue( inputStream );
                        if ( parts == null )
                            break;
                        AddHeader( parts[0], parts[1] );
                    }
                    
                } else {
                    // Read Body
                    int contentLength = 0;

                    try {
                        contentLength = int.Parse( GetHeader("content-length") );
                    } catch {
                        contentLength = 0;
                    }

                    int _b;
                    while( ( contentLength == 0 || output.Length < contentLength )
                              && (_b = inputStream.ReadByte()) != -1 ) {
                        output.WriteByte((byte)_b);
                    }

                    if( contentLength > 0 && output.Length != contentLength ) {
                        throw new HTTPException ("Response length does not match content length");
                    }
                }

                if ( GetHeader( "content-encoding" ).Contains( "gzip" ) ) {
                    //bytes = UnZip( output );
                }
                else {
                    bytes = output.ToArray();
                }
            }
        }


        //byte[] UnZip(MemoryStream output) {
        //    var cms = new MemoryStream ();
        //    output.Seek(0, SeekOrigin.Begin);
        //    using (var gz = new GZipStream(output, CompressionMode.Decompress)) {
        //        var buf = new byte[1024];
        //        int byteCount = 0;
        //        while ((byteCount = gz.Read(buf, 0, buf.Length)) > 0) {
        //            cms.Write(buf, 0, byteCount);
        //        }
        //    }
        //    return cms.ToArray();
        //}

    }
}
