using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
#if NETFX_CORE
	using Windows.Security.Cryptography;
	using Windows.Security.Cryptography.Core;
	using Windows.Storage.Streams;
	using BestHTTP.PlatformSupport.IO;
	using FileStream = BestHTTP.PlatformSupport.IO.FileStream;
#elif UNITY_WP8
    using Cryptography = BestHTTP.PlatformSupport.Cryptography;
#else
	using Cryptography = System.Security.Cryptography;
	using FileStream = System.IO.FileStream;
#endif
namespace BestHTTP.Extensions
{
    public static class Extensions
    {
        #region ASCII Encoding (These are required because Windows Phone doesn't supports the Encoding.ASCII class.)
        /// 
        /// On WP8 platform there are no ASCII encoding.
        /// 
        public static string AsciiToString(this byte[] bytes)
        {
            StringBuilder sb = new StringBuilder(bytes.Length);
            foreach (byte b in bytes)
                sb.Append(b <= 0x7f ? (char)b : '?');
            return sb.ToString();
        }
        /// 
        /// On WP8 platform there are no ASCII encoding.
        /// 
        public static byte[] GetASCIIBytes(this string str)
        {
            byte[] result = new byte[str.Length];
            for (int i = 0; i < str.Length; ++i)
            {
                char ch = str[i];
                result[i] = (byte)((ch < (char)0x80) ? ch : '?');
            }
            return result;
        }
        public static void SendAsASCII(this BinaryWriter stream, string str)
        {
            for (int i = 0; i < str.Length; ++i)
            {
                char ch = str[i];
                stream.Write((byte)((ch < (char)0x80) ? ch : '?'));
            }
        }
        #endregion
        #region FileSystem WriteLine function support
        public static void WriteLine(this FileStream fs)
        {
            fs.Write(HTTPRequest.EOL, 0, 2);
        }
        public static void WriteLine(this FileStream fs, string line)
        {
            var buff = line.GetASCIIBytes();
            fs.Write(buff, 0, buff.Length);
            fs.WriteLine();
        }
        public static void WriteLine(this FileStream fs, string format, params object[] values)
        {
            var buff = string.Format(format, values).GetASCIIBytes();
            fs.Write(buff, 0, buff.Length);
            fs.WriteLine();
        }
        #endregion
        #region Other Extensions
        public static string GetRequestPathAndQueryURL(this Uri uri)
        {
            string requestPathAndQuery = uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped);
            // http://forum.unity3d.com/threads/best-http-released.200006/page-26#post-2723250
            if (string.IsNullOrEmpty(requestPathAndQuery))
                requestPathAndQuery = "/";
            return requestPathAndQuery;
        }
        public static string[] FindOption(this string str, string option)
        {
            //s-maxage=2678400, must-revalidate, max-age=0
            string[] options = str.ToLower().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            option = option.ToLower();
            for (int i = 0; i < options.Length; ++i)
                if (options[i].Contains(option))
                    return options[i].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
            return null;
        }
        public static void WriteArray(this Stream stream, byte[] array)
        {
            stream.Write(array, 0, array.Length);
        }
        #endregion
        #region String Conversions
        public static int ToInt32(this string str, int defaultValue = default(int))
        {
            if (str == null)
                return defaultValue;
            try
            {
                return int.Parse(str);
            }
            catch
            {
                return defaultValue;
            }
        }
        public static long ToInt64(this string str, long defaultValue = default(long))
        {
            if (str == null)
                return defaultValue;
            try
            {
                return long.Parse(str);
            }
            catch
            {
                return defaultValue;
            }
        }
        public static DateTime ToDateTime(this string str, DateTime defaultValue = default(DateTime))
        {
            if (str == null)
                return defaultValue;
            try
            {
                DateTime.TryParse(str, out defaultValue);
                return defaultValue.ToUniversalTime();
            }
            catch
            {
                return defaultValue;
            }
        }
        public static string ToStrOrEmpty(this string str)
        {
            if (str == null)
                return String.Empty;
            return str;
        }
        #endregion
        #region MD5 Hashing
        public static string CalculateMD5Hash(this string input)
        {
            return input.GetASCIIBytes().CalculateMD5Hash();
        }
        public static string CalculateMD5Hash(this byte[] input)
        {
#if NETFX_CORE
            var alg = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Md5);
            IBuffer buff = CryptographicBuffer.CreateFromByteArray(input);
            var hashed = alg.HashData(buff);
            var res = CryptographicBuffer.EncodeToHexString(hashed);
            return res;
#else
            var hash = Cryptography.MD5.Create().ComputeHash(input);
            var sb = new StringBuilder();
            foreach (var b in hash)
                sb.Append(b.ToString("x2"));
            return sb.ToString();
#endif
        }
        #endregion
        #region Efficient String Parsing Helpers
        internal static string Read(this string str, ref int pos, char block, bool needResult = true)
        {
            return str.Read(ref pos, (ch) => ch != block, needResult);
        }
        internal static string Read(this string str, ref int pos, Func block, bool needResult = true)
        {
            if (pos >= str.Length)
                return string.Empty;
            str.SkipWhiteSpace(ref pos);
            int startPos = pos;
            while (pos < str.Length && block(str[pos]))
                pos++;
            string result = needResult ? str.Substring(startPos, pos - startPos) : null;
            // set position to the next char
            pos++;
            return result;
        }
        internal static string ReadPossibleQuotedText(this string str, ref int pos)
        {
            string result = string.Empty;
            if (str == null)
                return result;
            // It's a quoted text?
            if (str[pos] == '\"')
            {
                // Skip the starting quote
                str.Read(ref pos, '\"', false);
                // Read the text until the ending quote
                result = str.Read(ref pos, '\"');
                // Next option
                str.Read(ref pos, ',', false);
            }
            else
                // It's not a quoted text, so we will read until the next option
                result = str.Read(ref pos, (ch) => ch != ',' && ch != ';');
            return result;
        }
        internal static void SkipWhiteSpace(this string str, ref int pos)
        {
            if (pos >= str.Length)
                return;
            while (pos < str.Length && char.IsWhiteSpace(str[pos]))
                pos++;
        }
        internal static string TrimAndLower(this string str)
        {
            if (str == null)
                return null;
            char[] buffer = new char[str.Length];
            int length = 0;
            for (int i = 0; i < str.Length; ++i)
            {
                char ch = str[i];
                if (!char.IsWhiteSpace(ch) && !char.IsControl(ch))
                    buffer[length++] = char.ToLowerInvariant(ch);
            }
            return new string(buffer, 0, length);
        }
        internal static char? Peek(this string str, int pos)
        {
            if (pos < 0 || pos >= str.Length)
                return null;
            return str[pos];
        }
        #endregion
        #region Specialized String Parsers
        //public, max-age=2592000
        internal static List ParseOptionalHeader(this string str)
        {
            List result = new List();
            if (str == null)
                return result;
            int idx = 0;
            // process the rest of the text
            while (idx < str.Length)
            {
                // Read key
                string key = str.Read(ref idx, (ch) => ch != '=' && ch != ',').TrimAndLower();
                HeaderValue qp = new HeaderValue(key);
                if (str[idx - 1] == '=')
                    qp.Value = str.ReadPossibleQuotedText(ref idx);
                result.Add(qp);
            }
            return result;
        }
        //deflate, gzip, x-gzip, identity, *;q=0
        internal static List ParseQualityParams(this string str)
        {
            List result = new List();
            if (str == null)
                return result;
            int idx = 0;
            while (idx < str.Length)
            {
                string key = str.Read(ref idx, (ch) => ch != ',' && ch != ';').TrimAndLower();
                HeaderValue qp = new HeaderValue(key);
                if (str[idx - 1] == ';')
                {
                    str.Read(ref idx, '=', false);
                    qp.Value = str.Read(ref idx, ',');
                }
                result.Add(qp);
            }
            return result;
        }
        #endregion
        #region Buffer Filling
        /// 
        /// Will fill the entire buffer from the stream. Will throw an exception when the underlying stream is closed.
        /// 
        public static void ReadBuffer(this Stream stream, byte[] buffer)
        {
            int count = 0;
            do
            {
                int read = stream.Read(buffer, count, buffer.Length - count);
                if (read <= 0)
                    throw ExceptionHelper.ServerClosedTCPStream();
                count += read;
            } while (count < buffer.Length);
        }
        #endregion
        #region MemoryStream
        public static void WriteAll(this MemoryStream ms, byte[] buffer)
        {
            ms.Write(buffer, 0, buffer.Length);
        }
        public static void WriteString(this MemoryStream ms, string str)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(str);
            ms.WriteAll(buffer);
        }
        public static void WriteLine(this MemoryStream ms)
        {
            ms.WriteAll(HTTPRequest.EOL);
        }
        public static void WriteLine(this MemoryStream ms, string str)
        {
            ms.WriteString(str);
            ms.WriteLine();
        }
        #endregion
    }
    public static class ExceptionHelper
    {
        public static Exception ServerClosedTCPStream()
        {
            return new Exception("TCP Stream closed unexpectedly by the remote server");
        }
    }
}