// Invoked once per request.
        public async Task Invoke(IOwinContext context)
        {
            using (var trace = new Trace(context.Request.Uri.PathAndQuery.ToString()))
            {
                string json, templateName, templateData;

                if (context.Request.Uri.PathAndQuery == "/favicon.ico")
                {
                    context.Response.StatusCode = 404;
                    await context.Response.WriteAsync(new byte[] { });

                    return;
                }

                try
                {
                    // pass the same method through for all requests
                    HttpMethod method = new HttpMethod(context.Request.Method);
                    var        uri    = (HandlebarsProxyConfiguration.Instance.Scheme + "://" +
                                         HandlebarsProxyConfiguration.Instance.Domain + ":" +
                                         HandlebarsProxyConfiguration.Instance.DomainPort +
                                         GetProxyUri(context.Request.Uri)).ToLower();

                    HttpRequestMessage request = new HttpRequestMessage(method, new Uri(uri));

                    // keep the same user agent
                    request.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]);

                    // hack the cookies we do and don't want
                    if (context.Request.Headers["Cookie"] != null &&
                        !string.IsNullOrEmpty(context.Request.Headers["Cookie"]))
                    {
                        request.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]);
                    }

                    // check if there is a response for this URI
                    HttpResponseMessage cachedResponse = _store[uri] as HttpResponseMessage;
                    if (cachedResponse != null && context.Request.Method == "GET")
                    {
                        // add an "IfNoneMatch" to the new request to validate this one.
                        if (cachedResponse.Headers.ETag != null)
                        {
                            request.Headers.TryAddWithoutValidation("If-None-Match", cachedResponse.Headers.ETag.ToString());
                        }
                    }


                    byte[] data;
                    HttpResponseMessage response;
                    switch (context.Request.Method)
                    {
                    case "PUT":
                    case "POST":
                        using (var ms = new MemoryStream())
                        {
                            context.Request.Body.CopyTo(ms);

                            var originalFormContent = Encoding.UTF8.GetString(ms.ToArray());

                            var newFormContent =
                                new FormUrlEncodedContent(originalFormContent.Split('&')
                                                          .Select(_ =>
                            {
                                var parts = _.Split('=');
                                var kvp   = new KeyValuePair <string, string>(Uri.UnescapeDataString(parts[0]),
                                                                              Uri.UnescapeDataString(parts[1]));
                                return(kvp);
                            }));

                            request.Content = newFormContent;
                            // request.Content.Headers.ContentType = new MediaTypeHeaderValue(context.Request.Headers["Content-Type"]);


                            response = await _client.SendAsync(request);

                            data = await response.Content.ReadAsByteArrayAsync();

                            if (response.Headers.Contains("Set-Cookie"))
                            {
                                var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault();
                                if (!string.IsNullOrEmpty(setCookie))
                                {
                                    var cookie = setCookie.ToString();
                                    cookie = cookie.Replace("." + HandlebarsProxyConfiguration.Instance.Domain.Replace("www", "").Trim('.'),
                                                            HandlebarsProxyConfiguration.Instance.Hostname);

                                    Console.WriteLine("Set Cookie: " + cookie);
                                    context.Response.Headers["Set-Cookie"] = cookie;
                                }
                            }
                        }
                        break;

                    default:
                        response = await _client.SendAsync(request);

                        data = await response.Content.ReadAsByteArrayAsync();

                        break;
                    }

                    // use the cached version
                    if (cachedResponse != null &&
                        response.StatusCode == HttpStatusCode.NotModified)
                    {
                        response = cachedResponse;
                        data     = await cachedResponse.Content.ReadAsByteArrayAsync();

                        await Console.Out.WriteLineAsync("Using Cached Response");
                    }
                    // save the response in the cache
                    else if (response.Headers.ETag != null && cachedResponse == null)
                    {
                        CacheItemPolicy policy = new CacheItemPolicy();
                        policy.AbsoluteExpiration = DateTimeOffset.Now.AddDays(1);
                        _store.Set(request.RequestUri.ToString(), response, policy);
                    }

                    // set headers we need for compatibility in weird situations
                    context.Response.Headers["Access-Control-Allow-Origin"]  = "*";
                    context.Response.Headers["Access-Control-Allow-Methods"] = "GET,PUT,POST,DELETE";
                    context.Response.Headers["Access-Control-Allow-Headers"] = "Content-Type";

                    // if this is a redirect, pass it through
                    var location = response.Headers.Location;
                    if (location != null)
                    {
                        var locationUri = new Uri(location.ToString());
                        // locationUri.Host = HandlebarsProxyConfiguration.Instance.Hostname;
                        // locationUri.Port = HandlebarsProxyConfiguration.Instance.Port;
                        context.Response.Headers["Location"] = locationUri.PathAndQuery;
                    }

                    // if this is an API request, then pass it staight to the client, no template logic
                    if (context.Request.Uri.PathAndQuery.StartsWith("/api") ||
                        !response.Content.Headers.ContentType.MediaType.Contains("application/json") ||
                        response.StatusCode == HttpStatusCode.Found ||
                        response.StatusCode == HttpStatusCode.Redirect ||
                        response.StatusCode == HttpStatusCode.TemporaryRedirect)
                    {
                        context.Response.StatusCode  = (int)response.StatusCode;
                        context.Response.ContentType = response.Content.Headers.ContentType.MediaType;
                        await context.Response.WriteAsync(data);

                        return;
                    }

                    // ok, now we apply some smarts, first things first, get the data as a JSON object
                    json = Encoding.UTF8.GetString(data);
                    var o = JObject.Parse(json);

                    o["debug"] = true;

                    // here we need to ensure that we are replacing the hostname with the configured local version
                    // this means any logic around hostnames uses this
                    o["_config"]["api"]       = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                    o["_config"]["cdn"]       = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; // "//cdn.archfashion.dev";
                    o["_request"]["protocol"] = "http";
                    o["_request"]["gzip"]     = false;
                    o["_request"]["fqdn"]     = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                    o["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;

                    // Add an experiment with the variation as instructed by the request
                    var qs = new UrlEncodingParser(context.Request.QueryString.ToUriComponent());
                    if (!string.IsNullOrEmpty(qs["experiment"]) &&
                        !string.IsNullOrEmpty(qs["alias"]))
                    {
                        JObject experiment = o["_experiment"] as JObject;
                        if (experiment == null)
                        {
                            experiment       = new JObject();
                            o["_experiment"] = experiment;
                        }
                        experiment.RemoveAll();
                        experiment.Add(qs["experiment"].Urlify(), JObject.Parse(@"{""id"":""rAnDoMlEtTeRs"",""variation"":0,""variation_alias"":""" + qs["alias"].Urlify() + @"""}"));
                    }

                    json = o.ToString(Formatting.None);

                    // send the modified response back to the client (obviously the developer is
                    // trying to work out what is being combined with the template)
                    if (qs["x-format"] == "json")
                    {
                        context.Response.StatusCode  = 200;
                        context.Response.ContentType = response.Content.Headers.ContentType.MediaType;
                        await context.Response.WriteAsync(json);

                        return;
                    }

                    // get the header which has the handlebars template in it
                    var xTemplate = response.Headers.GetValues("x-template").FirstOrDefault();
                    if (string.IsNullOrEmpty(xTemplate))
                    {
                        await Console.Error.WriteLineAsync("No x-template header for URL: " + request.RequestUri.ToString());

                        await context.Response.WriteAsync("No x-template header for URL: " + request.RequestUri.ToString());

                        return;
                    }

                    // get the local template
                    templateName = xTemplate.ToString();
                    var templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory +
                                                    "\\template",
                                                    templateName.Replace("/", "\\") + ".handlebars");

                    // try different file extension
                    if (!File.Exists(templatePath))
                    {
                        templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory +
                                                    "\\template",
                                                    templateName.Replace("/", "\\") + ".hbs");
                    }

                    // get the data of the template
                    templateData = File.ReadAllText(templatePath);

                    // if this is file not found send a friendly message
                    using (var render = new Trace("render"))
                    {
                        var r    = _template.Render(templateName, json);
                        var html = FillSectionData(r, json);

                        var donuts    = new List <string>();
                        var templates = new Dictionary <string, string>();

                        // detect the donuts
                        int index  = html.IndexOf("####donut:", 0, false);
                        int length = 4;
                        while (index != -1)
                        {
                            length = html.IndexOf("####", index + 10, false) - index - 10;
                            donuts.Add(html.ToString(index + 10, length));
                            if (index + length > html.Length)
                            {
                                break;
                            }
                            index = html.IndexOf("####donut:", index + length, false);
                        }

                        // execute any donuts
                        var sync = new object();

                        var tasks = new List <Task <KeyValuePair <string, string> > >(donuts.Count);
                        if (donuts.Count > 0)
                        {
                            using (var handler = new WebRequestHandler()
                            {
                                UseCookies = false,
                                AllowAutoRedirect = false
                            })
                                using (HttpClient client = new HttpClient(handler, false)
                                {
                                    BaseAddress = new Uri(HandlebarsProxyConfiguration.Instance.Scheme + "://" +
                                                          HandlebarsProxyConfiguration.Instance.Domain + ":" +
                                                          HandlebarsProxyConfiguration.Instance.DomainPort)
                                })
                                {
                                    foreach (var donut in donuts)
                                    {
                                        var duri = new Uri((HandlebarsProxyConfiguration.Instance.Scheme + "://" +
                                                            HandlebarsProxyConfiguration.Instance.Domain + ":" +
                                                            HandlebarsProxyConfiguration.Instance.DomainPort +
                                                            GetProxyUri(donut)).ToLower());

                                        await Console.Out.WriteLineAsync("Getting Donut: " + duri.ToString());

                                        HttpRequestMessage drequest = new HttpRequestMessage(HttpMethod.Get, duri);

                                        drequest.Headers.Authorization = _authHeader;
                                        drequest.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]);
                                        drequest.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]);

                                        // the donut response's don't have etags so instead compare the cookies
                                        // as changes to the cookie state will be what impacts the cache, eg: login and out
                                        // or experiment changes
                                        HttpResponseMessage dcachedResponse = _store[duri.ToString()] as HttpResponseMessage;
                                        if (dcachedResponse != null &&
                                            (dcachedResponse.Headers.Contains("Cookie") &&
                                             string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("Cookie").ToArray()) ==
                                             string.Join(",", drequest.Headers.GetValues("Cookie").ToArray())) &&
                                            (dcachedResponse.Headers.Contains("User-Agent") &&
                                             string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("User-Agent").ToArray()) ==
                                             string.Join(",", drequest.Headers.GetValues("User-Agent").ToArray()))
                                            )
                                        {
                                            // OK, from here we can do the continue task without actually "dong anything"
                                            await Console.Out.WriteLineAsync("Cached Response: " + duri.ToString());

                                            var dDataTask = dcachedResponse.Content.ReadAsByteArrayAsync();
                                            Task.WaitAll(dDataTask);

                                            var doo = JObject.Parse(Encoding.UTF8.GetString(dDataTask.Result));

                                            // within the handlebars environment replace the
                                            doo["_config"]["api"]       = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                            doo["_config"]["cdn"]       = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork;
                                            doo["_request"]["protocol"] = "http";
                                            doo["_request"]["gzip"]     = false;
                                            doo["_request"]["fqdn"]     = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                            doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;

                                            try
                                            {
                                                doo.Remove("_experiment");
                                                doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented));
                                            }
                                            catch (Exception exp)
                                            {
                                                Console.Out.WriteLineAsync("Cached Donut Error: " + duri.ToString() + " : " + exp.Message);
                                            }

                                            var dxTemplate = donut;
                                            if (dcachedResponse.Headers.Contains("x-template"))
                                            {
                                                dxTemplate = dcachedResponse.Headers.GetValues("x-template").FirstOrDefault();
                                            }

                                            var t = Task.FromResult(new KeyValuePair <string, string>(donut, _template.Render(dxTemplate, doo.ToString(Formatting.None))));
                                            lock (sync)
                                            {
                                                tasks.Add(t);
                                            }
                                        }
                                        else
                                        {
                                            var t = client.SendAsync(drequest, CancellationToken.None)
                                                    .ContinueWith(_ =>
                                            {
                                                if (_.IsFaulted ||
                                                    !_.Result.IsSuccessStatusCode)
                                                {
                                                    Console.Out.WriteLine("Live Donut Error: " + duri.ToString());
                                                }
                                                else
                                                {
                                                    CacheItemPolicy policy    = new CacheItemPolicy();
                                                    policy.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
                                                    _store.Set(_.Result.RequestMessage.RequestUri.ToString(), _.Result, policy);
                                                }

                                                var tcontent = _.Result.Content.ReadAsStringAsync();
                                                Task.WaitAll(tcontent);

                                                KeyValuePair <string, string> kvp;

                                                var dxTemplate = donut;
                                                if (_.Result.Headers.Contains("x-template"))
                                                {
                                                    dxTemplate = _.Result.Headers.GetValues("x-template").FirstOrDefault();
                                                }

                                                try
                                                {
                                                    var doo = JObject.Parse(tcontent.Result);

                                                    // within the handlebars environment replace the
                                                    doo["_config"]["api"]       = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                                    doo["_config"]["cdn"]       = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork;
                                                    doo["_request"]["protocol"] = "http";
                                                    doo["_request"]["gzip"]     = false;
                                                    doo["_request"]["fqdn"]     = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                                    doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;

                                                    try
                                                    {
                                                        doo.Remove("_experiment");
                                                        doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented));
                                                    }
                                                    catch (Exception exp)
                                                    {
                                                        Console.Out.WriteLine("Live Donut Error: " + duri.ToString());
                                                        Console.WriteLine(exp.Message);
                                                        Console.WriteLine(exp.StackTrace);
                                                    }

                                                    kvp = new KeyValuePair <string, string>(dxTemplate, _template.Render(donut, doo.ToString(Formatting.None)));
                                                }
                                                catch (JsonReaderException)
                                                {
                                                    kvp = new KeyValuePair <string, string>(dxTemplate, tcontent.Result);
                                                }

                                                return(kvp);
                                            });

                                            if (!t.IsFaulted)
                                            {
                                                lock (sync)
                                                {
                                                    tasks.Add(t);
                                                }
                                            }
                                        }
                                    }


                                    try
                                    {
                                        Task.WaitAll(tasks.ToArray());
                                    }
                                    catch (Exception exp)
                                    {
                                        Console.WriteLine("Awaiting Donuts: " + exp.Message);
                                    }
                                }
                        }

                        // go through the sucessful tasks
                        foreach (var task in tasks)
                        {
                            html.Replace("####donut:" + task.Result.Key + "####", task.Result.Value);
                        }

                        context.Response.ContentType = "text/html";

                        // make sure the temp partials are cleared
                        _handlebars.Clear();
                        await context.Response.WriteAsync(html.ToString());

                        Console.WriteLine("Request Complete: " + context.Request.Uri.ToString());
                        return;
                    }
                }
                catch (Exception exp)
                {
                    // output any errors
                    Console.Error.WriteLine(context.Request.Uri.ToString() +
                                            "\n" +
                                            exp.Message +
                                            "\n" +
                                            exp.StackTrace);

                    // send them to the client as well
                    Task.WaitAll(context.Response.WriteAsync(context.Request.Uri.ToString() +
                                                             "\n" +
                                                             exp.Message +
                                                             "\n" +
                                                             exp.StackTrace));

                    return;
                }
            }
        }
        // Invoked once per request.
        public async Task Invoke(IOwinContext context)
        {
            using (var trace = new Trace(context.Request.Uri.PathAndQuery.ToString()))
            {
                string json, templateName, templateData;

                if (context.Request.Uri.PathAndQuery == "/favicon.ico")
                {
                    context.Response.StatusCode = 404;
                    await context.Response.WriteAsync(new byte[] { });
                    return;
                }

                try
                {
                    // pass the same method through for all requests
                    HttpMethod method = new HttpMethod(context.Request.Method);
                    var uri = (HandlebarsProxyConfiguration.Instance.Scheme + "://" +
                               HandlebarsProxyConfiguration.Instance.Domain + ":" +
                               HandlebarsProxyConfiguration.Instance.DomainPort +
                               GetProxyUri(context.Request.Uri)).ToLower();

                    HttpRequestMessage request = new HttpRequestMessage(method, new Uri(uri));
                    
                    // keep the same user agent
                    request.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]);
                    
                    // hack the cookies we do and don't want
                    if (context.Request.Headers["Cookie"] != null &&
                        !string.IsNullOrEmpty(context.Request.Headers["Cookie"]))
                    {
                        request.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]);
                    }

                    // check if there is a response for this URI
                    HttpResponseMessage cachedResponse = _store[uri] as HttpResponseMessage;
                    if (cachedResponse != null && context.Request.Method == "GET")
                    {
                        // add an "IfNoneMatch" to the new request to validate this one.
                        if (cachedResponse.Headers.ETag != null)
                            request.Headers.TryAddWithoutValidation("If-None-Match", cachedResponse.Headers.ETag.ToString());                        
                    }

                    
                    byte[] data;
                    HttpResponseMessage response;
                    switch (context.Request.Method)
                    {
                        case "PUT":
                        case "POST":
                            using (var ms = new MemoryStream())
                            {
                                context.Request.Body.CopyTo(ms);

                                var originalFormContent = Encoding.UTF8.GetString(ms.ToArray());

                                var newFormContent = 
                                new FormUrlEncodedContent(originalFormContent.Split('&')
                                                                             .Select(_ =>
                                                                             {
                                                                                 var parts = _.Split('=');
                                                                                 var kvp = new KeyValuePair<string, string>(Uri.UnescapeDataString(parts[0]), 
                                                                                                                            Uri.UnescapeDataString(parts[1]));
                                                                                 return kvp;
                                                                             }));

                                request.Content = newFormContent;
                                // request.Content.Headers.ContentType = new MediaTypeHeaderValue(context.Request.Headers["Content-Type"]);

                                
                                response = await _client.SendAsync(request);
                                data = await response.Content.ReadAsByteArrayAsync();

                                if (response.Headers.Contains("Set-Cookie"))
                                {
                                    var setCookie = response.Headers.GetValues("Set-Cookie").FirstOrDefault();
                                    if (!string.IsNullOrEmpty(setCookie))
                                    {
                                        var cookie = setCookie.ToString();
                                        cookie = cookie.Replace("." + HandlebarsProxyConfiguration.Instance.Domain
                                            .Replace("www.", "")
                                            .Replace("m.staging", "")
                                            .Replace("staging.", "")
                                            .Replace("m.archfashion.", "archfasion.")
                                            .Trim('.'),
                                            "." + HandlebarsProxyConfiguration.Instance.Hostname.Replace("www.", ""));

                                        cookie = cookie.Replace("; secure; HttpOnly", "");
                                        //cookie = cookie.Replace("HttpOnly", "");

                                        Console.WriteLine("Set Cookie: " + cookie);
                                        context.Response.Headers["Set-Cookie"] = cookie;
                                    }
                                }
                            }
                            break;

                        default:
                            response = await _client.SendAsync(request);
                            data = await response.Content.ReadAsByteArrayAsync();
                            break;
                    }

                    // use the cached version
                    if (cachedResponse != null && 
                        response.StatusCode == HttpStatusCode.NotModified)
                    {   
                        response = cachedResponse;
                        data = await cachedResponse.Content.ReadAsByteArrayAsync();
                        await Console.Out.WriteLineAsync("Using Cached Response");
                    }                      
                    // save the response in the cache
                    else if (response.Headers.ETag != null && cachedResponse == null)
                    {
                        CacheItemPolicy policy = new CacheItemPolicy();
                        policy.AbsoluteExpiration = DateTimeOffset.Now.AddDays(1);
                        _store.Set(request.RequestUri.ToString(), response, policy);
                    }

                    // set headers we need for compatibility in weird situations                       
                    context.Response.Headers["Access-Control-Allow-Credentials"] = "true";

                    var origin = "http://" + HandlebarsProxyConfiguration.Instance.Hostname;
                    if (context.Request.Headers.ContainsKey("origin"))
                        origin = context.Request.Headers["origin"];

                    context.Response.Headers["Access-Control-Allow-Origin"] = origin;
                    context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS";
                    context.Response.Headers["Access-Control-Allow-Headers"] = "X-Requested-With, Content-Type, withCredentials, Set-Cookie";

                    // if this is a redirect, pass it through
                    var location = response.Headers.Location;
                    if (location != null)
                    {
                        var locationUri = new Uri(location.ToString());
                        // locationUri.Host = HandlebarsProxyConfiguration.Instance.Hostname;
                        // locationUri.Port = HandlebarsProxyConfiguration.Instance.Port;
                        context.Response.Headers["Location"] = locationUri.PathAndQuery;
                    }

                    // if this is an API request, then pass it staight to the client, no template logic
                    if (context.Request.Uri.PathAndQuery.StartsWith("/api") ||
                        !response.Content.Headers.ContentType.MediaType.Contains("application/json") ||
                        response.StatusCode == HttpStatusCode.Found ||
                        response.StatusCode == HttpStatusCode.Redirect ||
                        response.StatusCode == HttpStatusCode.TemporaryRedirect)
                    {
                        context.Response.StatusCode = (int)response.StatusCode;
                        context.Response.ContentType = response.Content.Headers.ContentType.MediaType;
                        await context.Response.WriteAsync(data);
                        return;
                    }

                    // ok, now we apply some smarts, first things first, get the data as a JSON object
                    json = Encoding.UTF8.GetString(data);
                    var o = JObject.Parse(json);

                    o["debug"] = true;

                    // here we need to ensure that we are replacing the hostname with the configured local version
                    // this means any logic around hostnames uses this
                    o["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                    o["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork; // "//cdn.archfashion.dev";
                    o["_request"]["protocol"] = "http";
                    o["_request"]["gzip"] = false;
                    o["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                    o["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;

                    // Add an experiment with the variation as instructed by the request
                    var qs = new UrlEncodingParser(context.Request.QueryString.ToUriComponent());
                    if (!string.IsNullOrEmpty(qs["experiment"]) &&
                        !string.IsNullOrEmpty(qs["alias"]))
                    {
                        JObject experiment = o["_experiment"] as JObject;
                        if (experiment == null)
                        {
                            experiment = new JObject();
                            o["_experiment"] = experiment;
                        }
                        experiment.RemoveAll();
                        experiment.Add(qs["experiment"].Urlify(), JObject.Parse(@"{""id"":""rAnDoMlEtTeRs"",""variation"":0,""variation_alias"":""" + qs["alias"].Urlify() + @"""}"));
                    }

                    json = o.ToString(Formatting.None);

                    // send the modified response back to the client (obviously the developer is 
                    // trying to work out what is being combined with the template)
                    if (qs["x-format"] == "json")
                    {
                        context.Response.StatusCode = 200;
                        context.Response.ContentType = response.Content.Headers.ContentType.MediaType;
                        await context.Response.WriteAsync(json);
                        return;
                    }

                    // get the header which has the handlebars template in it
                    var xTemplate = response.Headers.GetValues("x-template").FirstOrDefault();
                    if (string.IsNullOrEmpty(xTemplate))
                    {
                        await Console.Error.WriteLineAsync("No x-template header for URL: " + request.RequestUri.ToString());
                        await context.Response.WriteAsync("No x-template header for URL: " + request.RequestUri.ToString());
                        return;
                    }

                    // get the local template
                    templateName = xTemplate.ToString();
                    var templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory +
                                                    "\\template",
                                                    templateName.Replace("/", "\\") + ".handlebars");

                    // try different file extension
                    if (!File.Exists(templatePath))
                        templatePath = Path.Combine(HandlebarsProxyConfiguration.Instance.Directory +
                                                    "\\template",
                                                    templateName.Replace("/", "\\") + ".hbs");

                    // get the data of the template
                    templateData = File.ReadAllText(templatePath);

                    // if this is file not found send a friendly message
                    using (var render = new Trace("render"))
                    {

                        var r = _template.Render(templateName, json);
                        var html = FillSectionData(r, json);

                        var donuts = new List<string>();
                        var templates = new Dictionary<string, string>();

                        // detect the donuts
                        int index = html.IndexOf("####donut:", 0, false);
                        int length = 4;
                        while (index != -1)
                        {
                            length = html.IndexOf("####", index + 10, false) - index - 10;
                            donuts.Add(html.ToString(index + 10, length));
                            if (index + length > html.Length) break;
                            index = html.IndexOf("####donut:", index + length, false);
                        }

                        // execute any donuts
                        var sync = new object();

                        var tasks = new List<Task<KeyValuePair<string, string>>>(donuts.Count);
                        if (donuts.Count > 0)
                        {                           
                                using (var handler = new WebRequestHandler()
                                {
                                    UseCookies = false,
                                    AllowAutoRedirect = false                                    
                                })
                                using (HttpClient client = new HttpClient(handler, false)
                                {
                                    BaseAddress = new Uri(HandlebarsProxyConfiguration.Instance.Scheme + "://" +
                                                          HandlebarsProxyConfiguration.Instance.Domain + ":" +
                                                          HandlebarsProxyConfiguration.Instance.DomainPort)

                                })
                                {

                                    foreach (var donut in donuts)
                                    {
                                        var duri = new Uri((HandlebarsProxyConfiguration.Instance.Scheme + "://" +
                                                            HandlebarsProxyConfiguration.Instance.Domain + ":" +
                                                            HandlebarsProxyConfiguration.Instance.DomainPort +
                                                            GetProxyUri(donut)).ToLower());

                                        await Console.Out.WriteLineAsync("Getting Donut: " + duri.ToString());
                                        HttpRequestMessage drequest = new HttpRequestMessage(HttpMethod.Get, duri);

                                        drequest.Headers.Authorization = _authHeader;
                                        drequest.Headers.TryAddWithoutValidation("User-Agent", context.Request.Headers["User-Agent"]);
                                        drequest.Headers.TryAddWithoutValidation("Cookie", context.Request.Headers["Cookie"]);

                                        // the donut response's don't have etags so instead compare the cookies
                                        // as changes to the cookie state will be what impacts the cache, eg: login and out
                                        // or experiment changes
                                        HttpResponseMessage dcachedResponse = _store[duri.ToString()] as HttpResponseMessage;
                                        if (dcachedResponse != null &&
                                            (dcachedResponse.Headers.Contains("Cookie") && 
                                            string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("Cookie").ToArray()) ==
                                            string.Join(",", drequest.Headers.GetValues("Cookie").ToArray())) &&
                                            (dcachedResponse.Headers.Contains("User-Agent") && 
                                            string.Join(",", dcachedResponse.RequestMessage.Headers.GetValues("User-Agent").ToArray()) ==
                                            string.Join(",", drequest.Headers.GetValues("User-Agent").ToArray())) 
                                            )
                                        {
                                            // OK, from here we can do the continue task without actually "dong anything"
                                            await Console.Out.WriteLineAsync("Cached Response: " + duri.ToString());
                                        
                                            var dDataTask = dcachedResponse.Content.ReadAsByteArrayAsync();
                                            Task.WaitAll(dDataTask);

                                            var doo = JObject.Parse(Encoding.UTF8.GetString(dDataTask.Result));

                                            // within the handlebars environment replace the 
                                            doo["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                            doo["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork;
                                            doo["_request"]["protocol"] = "http";
                                            doo["_request"]["gzip"] = false;
                                            doo["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                            doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;

                                            try
                                            {
                                                doo.Remove("_experiment");
                                                doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented));
                                            }
                                            catch (Exception exp)
                                            {
                                                Console.Out.WriteLineAsync("Cached Donut Error: " + duri.ToString() + " : " + exp.Message);                                                                                        
                                            }

                                            var dxTemplate = donut;
                                            if (dcachedResponse.Headers.Contains("x-template"))
                                                dxTemplate = dcachedResponse.Headers.GetValues("x-template").FirstOrDefault();                                                                  
                        
                                            var t = Task.FromResult(new KeyValuePair<string, string>(donut, _template.Render(dxTemplate, doo.ToString(Formatting.None))));
                                            lock (sync)
                                            {
                                                tasks.Add(t);
                                            }
                                        }
                                        else
                                        {
                                            var t = client.SendAsync(drequest, CancellationToken.None)
                                                          .ContinueWith(_ =>
                                                          {
                                                              
                                                              if (_.IsFaulted ||
                                                                  !_.Result.IsSuccessStatusCode)
                                                              {
                                                                  Console.Out.WriteLine("Live Donut Error: " + duri.ToString());                                        
                                                              }
                                                              else
                                                              {
                                                                  CacheItemPolicy policy = new CacheItemPolicy();
                                                                  policy.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
                                                                  _store.Set(_.Result.RequestMessage.RequestUri.ToString(), _.Result, policy);
                                                              }

                                                              var tcontent = _.Result.Content.ReadAsStringAsync();
                                                              Task.WaitAll(tcontent);

                                                              KeyValuePair<string, string> kvp;

                                                              var dxTemplate = donut;
                                                              if (_.Result.Headers.Contains("x-template"))
                                                                  dxTemplate = _.Result.Headers.GetValues("x-template").FirstOrDefault();

                                                              try
                                                              {                                                                  
                                                                  var doo = JObject.Parse(tcontent.Result);

                                                                  // within the handlebars environment replace the 
                                                                  doo["_config"]["api"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                                                  doo["_config"]["cdn"] = HandlebarsProxyConfiguration.Instance.ContentDeliveryNetwork;
                                                                  doo["_request"]["protocol"] = "http";
                                                                  doo["_request"]["gzip"] = false;
                                                                  doo["_request"]["fqdn"] = "http://" + HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;
                                                                  doo["_request"]["hostname"] = HandlebarsProxyConfiguration.Instance.Hostname + ":" + HandlebarsProxyConfiguration.Instance.Port;

                                                                  try
                                                                  {
                                                                      doo.Remove("_experiment");
                                                                      doo.Add("_experiment", o["_experiment"].ToString(Formatting.Indented));
                                                                  }
                                                                  catch (Exception exp)
                                                                  {
                                                                      Console.Out.WriteLine("Live Donut Error: " + duri.ToString());
                                                                      Console.WriteLine(exp.Message);
                                                                      Console.WriteLine(exp.StackTrace);
                                                                  }

                                                                  kvp = new KeyValuePair<string, string>(dxTemplate, _template.Render(donut, doo.ToString(Formatting.None)));
                                                              }
                                                              catch (JsonReaderException)
                                                              {
                                                                  kvp = new KeyValuePair<string, string>(dxTemplate, tcontent.Result);
                                                              }

                                                              return kvp;
                                                              
                                                          });

                                            if (!t.IsFaulted)
                                            {
                                                lock (sync)
                                                {
                                                    tasks.Add(t);
                                                }
                                            }
                                        }
                                    }


                                try
                                {
                                    Task.WaitAll(tasks.ToArray());
                                }
                                catch (Exception exp)
                                {
                                    Console.WriteLine("Awaiting Donuts: " + exp.Message);
                                }
                            }
                        }

                        // go through the sucessful tasks
                        foreach (var task in tasks)
                        {
                            await Console.Out.WriteLineAsync("Replacing Donut: " + task.Result.Key);
                            html.Replace("####donut:" + task.Result.Key + "####", task.Result.Value);
                        }

                        context.Response.ContentType = "text/html";

                        // make sure the temp partials are cleared
                        _handlebars.Clear();
                        await context.Response.WriteAsync(html.ToString());
                        Console.WriteLine("Request Complete: " + context.Request.Uri.ToString());
                        return;
                    }

                }
                catch (Exception exp)
                {
                    // output any errors
                    Console.Error.WriteLine(context.Request.Uri.ToString() +
                                            "\n" +
                                            exp.Message +
                                            "\n" +
                                            exp.StackTrace);

                    // send them to the client as well
                    Task.WaitAll(context.Response.WriteAsync(context.Request.Uri.ToString() +
                                                             "\n" +
                                                             exp.Message +
                                                             "\n" +
                                                             exp.StackTrace));

                    return;
                }




            }
            
        }