using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json.Linq;
using Winbooks.TORM.OM;
using System.ComponentModel;

namespace Winbooks.Apis.Services
{
    public class UserCredential : INotifyPropertyChanged
    {
        #region ** Properties **
        private string _url;
        /// <summary>
        /// Url of api
        /// </summary>
        public string Url { get { return this._url; } }

        private string _exchangeToken;
        /// <summary>
        /// Exchange Token of the granted access
        /// </summary>
        public string ExchangeToken { get { return this._exchangeToken; } }

        private string _email;
        /// <summary>
        /// Email of the granted access
        /// </summary>
        public string Email { get { return this._email; } }

        private string _refreshToken;
        /// <summary>
        /// Refresh token that received base on the exchanged token, url and email
        /// </summary>
        public string RefreshToken { get { return this._refreshToken; } }

        private string _accessToken;
        /// <summary>
        /// Access token that received base on the exchanged token, url and email
        /// </summary>
        public string AccessToken { get { return this._accessToken; } }

        private bool _isAuthentication = false;
        /// <summary>
        /// True if the refresh token and access token is generated
        /// </summary>
        public bool IsAuthentication { get { return this._isAuthentication; } }
        #endregion ** Properties **

        public string LogFile { get; set; }

        /// <summary>
        /// Services of the api
        /// </summary>
        /// <param name="url">url of the rapi</param>
        /// <param name="email">email of the granted access</param>
        /// <param name="exchangeToken">exchange token of the granted access</param>
        public UserCredential(string url, string email, string exchangeToken)
        {
            this._url = url;
            this._email = email;
            this._exchangeToken = exchangeToken;
            this.LogFile = "";
        }

        #region ** Methods **

        public void UpdateInfo(string accessToken, string refreshToken)
        {
            this._accessToken = accessToken;
            this._refreshToken = refreshToken;
        }
        /// <summary>
        /// Get the encoding of the email
        /// </summary>
        /// <returns></returns>
        public string FormatEmail()
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(this.Email));
        }

        /// <summary>
        /// Get Access Token and Refresh Token
        /// </summary>
        /// <returns></returns>
        public bool Authentication()
        {
            HttpClient httpClient = this.CreateHttpClient(this.FormatEmail());

            FormUrlEncodedContent parameters = new FormUrlEncodedContent(new Dictionary<string, string> { { "grant_type", "exchange_token" }, { "code", ExchangeToken } });
            var response = httpClient.PostAsync(this.Url + "/OAuth20/Token", parameters).Result;

            if (this.IsSuccessed(response.StatusCode))
            {
                string result = response.Content.ReadAsStringAsync().Result;
                // parse result as json string
                JObject jobject = JObject.Parse(result);
                this._accessToken = (string)jobject["access_token"];
                this._refreshToken = (string)jobject["refresh_token"];
                this._isAuthentication = true;
                return true;
            }
            throw new Exception("Authentication is not success, an error has occured : Status Code " + response.StatusCode + " " + response.Content.ReadAsStringAsync().Result);
        }

        /// <summary>
        /// Refresh the Access Token
        /// </summary>
        /// <returns></returns>
        public bool RefreshAccessToken()
        {
            HttpClient httpClient = this.CreateHttpClient(this.FormatEmail());
            var parameters = new FormUrlEncodedContent(new Dictionary<string, string> { { "grant_type", "refresh_token" }, { "code", this.RefreshToken } });
            var response = httpClient.PostAsync(this.Url + "/OAuth20/Token", parameters).Result;

            if (this.IsSuccessed(response.StatusCode))
            {
                string result = response.Content.ReadAsStringAsync().Result;
                // parse result as json string
                JObject jobject = JObject.Parse(result);
                this._accessToken = (string)jobject["access_token"];
                this._refreshToken = (string)jobject["refresh_token"];
                this._isAuthentication = true;
                NotifyPropertyChanged("_refreshToken");
                return true;
            }
            else
            {
                throw new Exception(response.Content.ReadAsStringAsync().Result);
            }
        }

        /// <summary>
        /// Create a HttpClient
        /// </summary>
        /// <param name="authorization">authorization of the request</param>
        /// <param name="parameters">parameters of the request. It will be grant_type or exchange_token</param>
        /// <param name="code">the action of the request</param>
        /// <returns></returns>
        public HttpClient CreateHttpClient(string authorization)
        {
            HttpClient httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorization);

            return httpClient;
        }

        /// <summary>
        /// Create a HttpClient
        /// </summary>
        public HttpClient CreateHttpClient()
        {
            if (!this.IsAuthentication)
            {
                if (!this.Authentication())
                    return null;
            }
            HttpClientHandler handler = new HttpClientHandler();
            handler.AllowAutoRedirect = false;
            HttpClient httpClient = new HttpClient(handler);
            httpClient.Timeout = new TimeSpan(0, 0, 300);
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken);
            return httpClient;
        }

        /// <summary>
        /// Set new access token to httpClient
        /// </summary>
        /// <param name="oldHttpClient">HttpClient with old access token</param>
        /// <returns></returns>
        public HttpClient RefreshHttpClient(HttpClient oldHttpClient)
        {
            if (this.IsAuthentication)
            {
                oldHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", this.AccessToken);
            }
            return oldHttpClient;
        }

        /// <summary>
        /// Get Url to post
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public string GetUrl(string parameters)
        {
            return String.Concat(this.Url, "/app", parameters);
        }

        public string GetUrl(string parameters, IDictionary<string, string> setupConditon)
        {
            if (setupConditon == null || setupConditon.Count == 0)
            {
                return GetUrl(parameters);
            }
            return String.Concat(this.Url, "/app", parameters, "?" + string.Join("&", setupConditon.Select(x => x.Key + "=" + x.Value).ToArray()));
        }

        private string GetUrlWithSettingFilter(string plainUrl, IDictionary<string, string> setupConditon)
        {
            if (setupConditon == null || setupConditon.Count == 0)
            {
                return plainUrl;
            }
            return String.Concat(plainUrl, "?" + string.Join("&", setupConditon.Select(x => x.Key + "=" + x.Value).ToArray()));
        }

        public string GetUrl(string parameters, BaseEntitySetting setting)
        {
            if (setting == null || (setting.IsShowId && setting.GetEntityLevel == 1 && setting.StateGettingObj == ResultState.None))
            {
                return GetUrl(parameters);
            }
            Dictionary<string, string> settingCondition = new Dictionary<string, string>();
            if (!setting.IsShowId)
            {
                settingCondition.Add("isShowId", "false");
            }
            if (setting.GetEntityLevel > 1)
            {
                settingCondition.Add("maxLevel", setting.GetEntityLevel.ToString());
            }
            if (setting.StateGettingObj != ResultState.None)
            {
                settingCondition.Add("state", setting.StateGettingObj.ToString());
            }
            return GetUrl(parameters, settingCondition);

        }

        public string GetUrlWithSettingFilter(string plainUrl, BaseEntitySetting setting)
        {
            if (setting == null || (setting.IsShowId && setting.GetEntityLevel == 1 && setting.StateGettingObj == ResultState.None))
            {
                return plainUrl;
            }
            Dictionary<string, string> settingCondition = new Dictionary<string, string>();
            if (!setting.IsShowId)
            {
                settingCondition.Add("isShowId", "false");
            }
            if (setting.GetEntityLevel > 1)
            {
                settingCondition.Add("maxLevel", setting.GetEntityLevel.ToString());
            }
            if (setting.StateGettingObj != ResultState.None)
            {
                settingCondition.Add("state", setting.StateGettingObj.ToString());
            }
            return GetUrlWithSettingFilter(plainUrl, settingCondition);
        }

        /// <summary>
        /// Get the status of responde code
        /// </summary>
        /// <param name="responseCode">http status code of the request</param>
        /// <returns>true = successed else it will be false</returns>
        public bool IsSuccessed(HttpStatusCode responseCode)
        {
            switch (responseCode)
            {
                case System.Net.HttpStatusCode.OK:
                case System.Net.HttpStatusCode.Created:
                    return true;
            }

            return false;
        }

        #endregion ** Methods **

        #region ** Get **
        /// <summary>
        /// Get asyn
        /// </summary>
        /// <param name="httpClient">http client of request</param>
        /// <param name="url">url of the request base on the url formular of api</param>
        /// <returns>true = successed else it returns exception</returns>
        private string GetAsync(HttpClient httpClient, string url, bool log = true)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return "";
            
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            if (log)
                Log(url + " (GET)");
            var response = httpClient.GetAsync(url).Result;
            stopwatch.Stop();
            if (log)
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                this._isAuthentication = false;
                if (this.RefreshAccessToken())
                {
                    return GetAsync(this.RefreshHttpClient(httpClient), url, false);
                }
            }
            string ret = response.Content.ReadAsStringAsync().Result;
            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                return ret;
            if (response.StatusCode == HttpStatusCode.NoContent)
            {
                return string.Empty;
            }
            throw new Exception(response.StatusCode.ToString() + " - " + ret);
        }

        private IDictionary<string, string> GetAsyncWithChunkingData(HttpClient httpClient, string url, bool log = true)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return null;
            bool isContinue = true;
            IDictionary<string, string> lstResult = new Dictionary<string, string>();
            string continuePath = string.Empty;
            while (isContinue)
            {
                if (!string.IsNullOrEmpty(continuePath))
                {
                    if (httpClient.DefaultRequestHeaders.Contains("ContinuePath"))
                    {
                        httpClient.DefaultRequestHeaders.Remove("ContinuePath");
                    }
                    httpClient.DefaultRequestHeaders.Add("ContinuePath", continuePath);
                }

                System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
                stopwatch.Start();
                if (log)
                    Log(url + " (GET) "+continuePath);
                var response = httpClient.GetAsync(url).Result;
                stopwatch.Stop();
                if (log)
                    Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");
                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    this._isAuthentication = false;
                    this.RefreshAccessToken();
                }

                if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NoContent || response.StatusCode == HttpStatusCode.Created)
                {
                    if (response.Headers.Contains("ContinuePath"))
                    {
                        continuePath = response.Headers.GetValues("ContinuePath").FirstOrDefault();
                        if (string.IsNullOrEmpty(continuePath) || continuePath == "UpdatedTypes")
                        {
                            isContinue = false;
                        }
                        lstResult.Add(continuePath, response.Content.ReadAsStringAsync().Result);
                    }
                    else
                    {
                        isContinue = false;
                        lstResult.Add("", response.Content.ReadAsStringAsync().Result);
                    }
                    continue;
                }
                if (response.StatusCode == HttpStatusCode.NoContent)
                {
                    return null;
                }
                throw new Exception(response.StatusCode.ToString() + " - " + response.Content.ReadAsStringAsync().Result);
            }
            return lstResult;
        }

        /// <summary>
        /// Get async
        /// </summary>
        /// <param name="url">url to get data base on the formular of api</param>
        /// <returns></returns>
        public string GetAsync(string url)
        {
            return GetAsync(this.CreateHttpClient(), url);
        }

        public IDictionary<string, string> GetAsyncWithChunkingData(string url)
        {
            return GetAsyncWithChunkingData(this.CreateHttpClient(), url);
        }

        #endregion ** Get **

        #region ** Get with condition **
        /// <summary>
        /// Get data with the filter
        /// </summary>
        /// <param name="url">url to get the data base on the formular of api</param>
        /// <returns></returns>
        public string PostAsync(string url)
        {
            return PostAsync(this.CreateHttpClient(), url);
        }

        /// <summary>
        /// Get data with filter
        /// </summary>
        /// <param name="httpClient">http client is created with te Credential</param>
        /// <param name="url">url to get the data base on the formular of api</param>
        /// <returns></returns>
        public string PostAsync(HttpClient httpClient, string url)
        {
            return PostAsync(httpClient, url, "");
        }

        /// <summary>
        /// Get data with filter
        /// </summary>
        /// <param name="url">url to get the data base on the formular of api</param>
        /// <param name="condition">condition of the filter</param>
        /// <returns>result of the request</returns>
        public IDictionary<string, string> PostAsync(string url, string condition)
        {
            return PostAsyncWithChunkingData(this.CreateHttpClient(), url, condition);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpClient"></param>
        /// <param name="url"></param>
        /// <param name="condition"></param>
        /// <returns></returns>
        public string PostAsync(HttpClient httpClient, string url, string condition, bool log = true)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return "";

            if (!String.IsNullOrWhiteSpace(condition))
            {
                if (url.Contains('?'))
                {
                    url = url.Replace("?", "/ExecuteCriteria?");
                }
                else
                {
                    url += "/ExecuteCriteria";
                }
            }
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            if (log)
                Log(url + " (POST)", condition);
            var response = httpClient.PostAsync(url, new StringContent(condition)).Result;
            stopwatch.Stop();
            if (log)
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");

            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                if (!this.RefreshAccessToken())
                    throw new Exception("Unauthorized request");
                return PostAsync(httpClient, url, condition, false);
            }

            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                return response.Content.ReadAsStringAsync().Result;

            if (response.StatusCode == HttpStatusCode.NoContent)
            {
                return null;
            }
            throw new Exception(response.StatusCode.ToString() + " - " + response.Content.ReadAsStringAsync().Result);
        }

        public IDictionary<string, string> PostAsyncWithChunkingData(HttpClient httpClient, string url, string condition)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return null;
            IDictionary<string, string> lstResult = new Dictionary<string, string>();
            if (!String.IsNullOrWhiteSpace(condition))
            {
                if (url.Contains('?'))
                {
                    url = url.Replace("?", "/ExecuteCriteria?");
                }
                else
                {
                    url += "/ExecuteCriteria";
                }
            }
            bool isContinue = true;
            string continuePath = "";

            while (isContinue)
            {
                var stringContent = new StringContent(condition);
                if (!string.IsNullOrEmpty(continuePath))
                {
                    if (httpClient.DefaultRequestHeaders.Contains("ContinuePath"))
                    {
                        httpClient.DefaultRequestHeaders.Remove("ContinuePath");
                    }
                    httpClient.DefaultRequestHeaders.Add("ContinuePath", continuePath);
                }

                System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
                stopwatch.Start();
                Log(url + " (POST) " + continuePath, condition);
                var response = httpClient.PostAsync(url, stringContent).Result;
                stopwatch.Stop();
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");
                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    this._isAuthentication = false;
                    this.RefreshAccessToken();
                }
                if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                {
                    if (response.Headers.Contains("ContinuePath"))
                    {
                        continuePath = response.Headers.GetValues("ContinuePath").FirstOrDefault();
                        if (string.IsNullOrEmpty(continuePath) || continuePath == "UpdatedTypes")
                        {
                            isContinue = false;
                        }
                        lstResult.Add(continuePath, response.Content.ReadAsStringAsync().Result);
                    }
                    else
                    {
                        isContinue = false;
                        lstResult.Add("", response.Content.ReadAsStringAsync().Result);
                    }
                    continue;
                }
                if (response.StatusCode == HttpStatusCode.NoContent)
                {
                    return null;
                }
                throw new Exception(response.StatusCode.ToString() + " - " + response.Content.ReadAsStringAsync().Result);
            }
            return lstResult;
        }
        #endregion ** Get With condition **

        #region ** Update **
        public string UpdateAsync(HttpClient httpClient, string url, string data, bool log = true)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return "";
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            if (log)
                Log(url + " (PUT)", data);
            var response = httpClient.PutAsync(url, new StringContent(data)).Result;
            stopwatch.Stop();
            if (log)
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                if (!this.RefreshAccessToken())
                    throw new Exception("Unauthorized request");

                return UpdateAsync(httpClient, url, data, false);
            }
            string ret = response.Content.ReadAsStringAsync().Result;
            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                return ret;

            throw new Exception(response.StatusCode.ToString() + " - " + ret);
        }

        public string UpdateAsync(string url, string data)
        {
            return UpdateAsync(this.CreateHttpClient(), url, data);
        }
        #endregion ** Update **

        #region ** Insert **
        public string InsertAsync(HttpClient httpClient, string url, string data, bool log = true)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return "";

            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            if (log)
                Log(url + " (POST)", data);
            var response = httpClient.PostAsync(url, new StringContent(data)).Result;
            stopwatch.Stop();
            if (log)
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");

            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                if (!this.RefreshAccessToken())
                    throw new Exception("Unauthorized request");

                return InsertAsync(httpClient, url, data, false);
            }

            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
            {
                string ret = response.Content.ReadAsStringAsync().Result;
                return ret;
            }
            throw new Exception(response.StatusCode.ToString() + " - " + response.Content.ReadAsStringAsync().Result);
        }

        public string InsertAsync(string url, string data)
        {
            return InsertAsync(this.CreateHttpClient(), url, data);
        }
        #endregion ** Insert **

        #region ** Delete **
        public string DeleteAsync(HttpClient httpClient, string url, bool log = true)
        {
            if (httpClient == null || String.IsNullOrWhiteSpace(url))
                return "";

            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            if (log)
                Log(url + " (DELETE)");
            var response = httpClient.DeleteAsync(url).Result;
            stopwatch.Stop();
            if (log)
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                if (!this.RefreshAccessToken())
                    throw new Exception("Unauthorized request");

                return DeleteAsync(httpClient, url, false);
            }

            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                return response.Content.ReadAsStringAsync().Result;

            throw new Exception(response.StatusCode.ToString() + " - " + response.Content.ReadAsStringAsync().Result);
        }

        public string DeleteAsync(string url)
        {
            return DeleteAsync(this.CreateHttpClient(), url);
        }

        public string DeleteAsync(string url, string condition, bool log = true)
        {
            HttpClient httpClient = this.CreateHttpClient();

            if (httpClient == null || String.IsNullOrWhiteSpace(url) || String.IsNullOrWhiteSpace(condition))
                return "";

            url = string.Concat(url, "/DeleteByCriteria");
            System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            if (log)
                Log(url + " (POST)", condition);
            var response = httpClient.PostAsync(url, new StringContent(condition)).Result;
            stopwatch.Stop();
            if (log)
                Log("-> " + response.StatusCode.ToString() + " (" + stopwatch.ElapsedMilliseconds.ToString() + " ms)" + "\r\n");
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                if (!this.RefreshAccessToken())
                    throw new Exception("Unauthorized request");

                return DeleteAsync(url, condition, false);
            }

            if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.Created)
                return response.Content.ReadAsStringAsync().Result;

            throw new Exception(response.StatusCode.ToString() + " - " + response.Content.ReadAsStringAsync().Result);
        }
        #endregion ** Delete **

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string name)
        {
            PropertyChangedEventHandler eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(name));
            }
        }

        public void Log(string text)
        {
            if (string.IsNullOrWhiteSpace(LogFile)) return;
            System.IO.File.AppendAllText(LogFile, text + "\r\n", Encoding.UTF8);
        }

        public void Log(string url, string json)
        {
            if (string.IsNullOrWhiteSpace(LogFile)) return;
            this.Log("\r\nurl: " + url + "\r\n\r\n" + JsonHelper.FormatJson(json));
        }
    }

    class JsonHelper
    {
        private const string INDENT_STRING = "    ";
        public static string FormatJson(string str)
        {
            var indent = 0;
            var quoted = false;
            var sb = new StringBuilder();
            for (var i = 0; i < str.Length; i++)
            {
                var ch = str[i];
                switch (ch)
                {
                    case '{':
                    case '[':
                        sb.Append(ch);
                        if (!quoted)
                        {
                            sb.AppendLine();
                            Enumerable.Range(0, ++indent).ForEach(item => sb.Append(INDENT_STRING));
                        }
                        break;
                    case '}':
                    case ']':
                        if (!quoted)
                        {
                            sb.AppendLine();
                            Enumerable.Range(0, --indent).ForEach(item => sb.Append(INDENT_STRING));
                        }
                        sb.Append(ch);
                        break;
                    case '"':
                        sb.Append(ch);
                        bool escaped = false;
                        var index = i;
                        while (index > 0 && str[--index] == '\\')
                            escaped = !escaped;
                        if (!escaped)
                            quoted = !quoted;
                        break;
                    case ',':
                        sb.Append(ch);
                        if (!quoted)
                        {
                            sb.AppendLine();
                            Enumerable.Range(0, indent).ForEach(item => sb.Append(INDENT_STRING));
                        }
                        break;
                    case ':':
                        sb.Append(ch);
                        if (!quoted)
                            sb.Append(" ");
                        break;
                    default:
                        sb.Append(ch);
                        break;
                }
            }
            return sb.ToString();
        }
    }

    static class Extensions
    {
        public static void ForEach<T>(this IEnumerable<T> ie, Action<T> action)
        {
            foreach (var i in ie)
            {
                action(i);
            }
        }
    }
}
