context: using System;
  using System.Globalization;
  using System.IO;
  using System.Net;
  using System.Text;
  using MediaBrowser.Model.Logging;
  using MediaBrowser.Model.Text;
  using SocketHttpListener.Primitives;
  
  namespace SocketHttpListener.Net
  {
      public sealed class HttpListenerResponse : IDisposable
      {
          bool disposed;
          Encoding content_encoding;
          long content_length;
          bool cl_set;
          string content_type;
          CookieCollection cookies;
          WebHeaderCollection headers = new WebHeaderCollection();
          bool keep_alive = true;
          Stream output_stream;
          Version version = HttpVersion.Version11;
          string location;
          int status_code = 200;
          string status_description = "OK";
          bool chunked;
          HttpListenerContext context;
  
          internal bool HeadersSent;
          internal object headers_lock = new object();
  
          private readonly ILogger _logger;
          private readonly ITextEncoding _textEncoding;
  
          internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding)
          {
              this.context = context;
              _logger = logger;
              _textEncoding = textEncoding;
          }
  
          internal bool CloseConnection
          {
              get
              {
                  return headers["Connection"] == "close";
              }
          }
  
          public Encoding ContentEncoding
          {
              get
              {
                  if (content_encoding == null)
                      content_encoding = _textEncoding.GetDefaultEncoding();
                  return content_encoding;
              }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  content_encoding = value;
              }
          }
  
          public long ContentLength64
          {
              get { return content_length; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (HeadersSent)
                      throw new InvalidOperationException("Cannot be changed after headers are sent.");
  
                  if (value < 0)
                      throw new ArgumentOutOfRangeException("Must be >= 0", "value");
  
                  cl_set = true;
                  content_length = value;
              }
          }
  
          public string ContentType
          {
              get { return content_type; }
              set
              {
                  // TODO: is null ok?
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  content_type = value;
              }
          }
  
          // RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
          public CookieCollection Cookies
          {
              get
              {
                  if (cookies == null)
                      cookies = new CookieCollection();
                  return cookies;
              }
              set { cookies = value; } // null allowed?
          }
  
          public WebHeaderCollection Headers
          {
              get { return headers; }
              set
              {
                  /**
                   *	"If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
                   *	WWW-Authenticate header using the Headers property, an exception will be
                   *	thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
                   *	You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
                  */
                  // TODO: check if this is marked readonly after headers are sent.
                  headers = value;
              }
          }
  
          public bool KeepAlive
          {
              get { return keep_alive; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  keep_alive = value;
              }
          }
  
          public Stream OutputStream
          {
              get
              {
                  if (output_stream == null)
                      output_stream = context.Connection.GetResponseStream();
                  return output_stream;
              }
          }
  
          public Version ProtocolVersion
          {
              get { return version; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (value == null)
                      throw new ArgumentNullException("value");
  
                  if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
                      throw new ArgumentException("Must be 1.0 or 1.1", "value");
  
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  version = value;
              }
          }
  
          public string RedirectLocation
          {
              get { return location; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  location = value;
              }
          }
  
          public bool SendChunked
          {
              get { return chunked; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  chunked = value;
              }
          }
  
          public int StatusCode
          {
              get { return status_code; }
              set
              {
                  if (disposed)
                      throw new ObjectDisposedException(GetType().ToString());
  
                  if (value < 100 || value > 999)
                      throw new ProtocolViolationException("StatusCode must be between 100 and 999.");
                  status_code = value;
                  status_description = GetStatusDescription(value);
              }
          }
  
          internal static string GetStatusDescription(int code)
          {
              switch (code)
              {
                  case 100: return "Continue";
                  case 101: return "Switching Protocols";
                  case 102: return "Processing";
                  case 200: return "OK";
                  case 201: return "Created";
                  case 202: return "Accepted";
                  case 203: return "Non-Authoritative Information";
                  case 204: return "No Content";
                  case 205: return "Reset Content";
                  case 206: return "Partial Content";
                  case 207: return "Multi-Status";
                  case 300: return "Multiple Choices";
                  case 301: return "Moved Permanently";
                  case 302: return "Found";
                  case 303: return "See Other";
                  case 304: return "Not Modified";
                  case 305: return "Use Proxy";
                  case 307: return "Temporary Redirect";
                  case 400: return "Bad Request";
                  case 401: return "Unauthorized";
                  case 402: return "Payment Required";
                  case 403: return "Forbidden";
                  case 404: return "Not Found";
                  case 405: return "Method Not Allowed";
                  case 406: return "Not Acceptable";
                  case 407: return "Proxy Authentication Required";
                  case 408: return "Request Timeout";
                  case 409: return "Conflict";
                  case 410: return "Gone";
                  case 411: return "Length Required";
                  case 412: return "Precondition Failed";
                  case 413: return "Request Entity Too Large";
                  case 414: return "Request-Uri Too Long";
                  case 415: return "Unsupported Media Type";
                  case 416: return "Requested Range Not Satisfiable";
                  case 417: return "Expectation Failed";
                  case 422: return "Unprocessable Entity";
                  case 423: return "Locked";
                  case 424: return "Failed Dependency";
                  case 500: return "Internal Server Error";
                  case 501: return "Not Implemented";
                  case 502: return "Bad Gateway";
                  case 503: return "Service Unavailable";
                  case 504: return "Gateway Timeout";
                  case 505: return "Http Version Not Supported";
                  case 507: return "Insufficient Storage";
              }
              return "";
          }
  
          public string StatusDescription
          {
              get { return status_description; }
              set
              {
                  status_description = value;
              }
          }
  
          void IDisposable.Dispose()
          {
              Close(true); //TODO: Abort or Close?
          }
  
          public void Abort()
          {
              if (disposed)
                  return;
  
              Close(true);
          }
  
          public void AddHeader(string name, string value)
          {
              if (name == null)
                  throw new ArgumentNullException("name");
  
              if (name == "")
                  throw new ArgumentException("'name' cannot be empty", "name");
  
              //TODO: check for forbidden headers and invalid characters
              if (value.Length > 65535)
                  throw new ArgumentOutOfRangeException("value");
  
              headers.Set(name, value);
          }
  
          public void AppendCookie(Cookie cookie)
          {
              if (cookie == null)
                  throw new ArgumentNullException("cookie");
  
              Cookies.Add(cookie);
          }
  
          public void AppendHeader(string name, string value)
          {
              if (name == null)
                  throw new ArgumentNullException("name");
  
              if (name == "")
                  throw new ArgumentException("'name' cannot be empty", "name");
  
              if (value.Length > 65535)
                  throw new ArgumentOutOfRangeException("value");
  
              headers.Add(name, value);
          }
  
          private void Close(bool force)
          {
              if (force)
              {
                  _logger.Debug("HttpListenerResponse force closing HttpConnection");
              }
              disposed = true;
              context.Connection.Close(force);
          }
  
          public void Close()
          {
              if (disposed)
                  return;
  
              Close(false);
          }
  
          public void Redirect(string url)
          {
              StatusCode = 302; // Found
              location = url;
          }
  
          bool FindCookie(Cookie cookie)
          {
              string name = cookie.Name;
              string domain = cookie.Domain;
              string path = cookie.Path;
              foreach (Cookie c in cookies)
              {
                  if (name != c.Name)
                      continue;
                  if (domain != c.Domain)
                      continue;
                  if (path == c.Path)
                      return true;
              }
  
              return false;
          }
  
          public void DetermineIfChunked()
          {
              if (chunked)
              {
                  return ;
              }
  
              Version v = context.Request.ProtocolVersion;
              if (!cl_set && !chunked && v >= HttpVersion.Version11)
                  chunked = true;
              if (!chunked && string.Equals(headers["Transfer-Encoding"], "chunked"))
              {
                  chunked = true;
              }
          }
  
          internal void SendHeaders(bool closing, MemoryStream ms)
          {
              Encoding encoding = content_encoding;
              if (encoding == null)
                  encoding = _textEncoding.GetDefaultEncoding();
  
              if (content_type != null)
              {
                  if (content_encoding != null && content_type.IndexOf("charset=", StringComparison.OrdinalIgnoreCase) == -1)
                  {
                      string enc_name = content_encoding.WebName;
                      headers.SetInternal("Content-Type", content_type + "; charset=" + enc_name);
                  }
                  else
                  {
                      headers.SetInternal("Content-Type", content_type);
                  }
              }
  
              if (headers["Server"] == null)
                  headers.SetInternal("Server", "Mono-HTTPAPI/1.0");
  
              CultureInfo inv = CultureInfo.InvariantCulture;
              if (headers["Date"] == null)
                  headers.SetInternal("Date", DateTime.UtcNow.ToString("r", inv));
  
              if (!chunked)
              {
                  if (!cl_set && closing)
                  {
                      cl_set = true;
                      content_length = 0;
                  }
  
                  if (cl_set)
                      headers.SetInternal("Content-Length", content_length.ToString(inv));
              }
  
              Version v = context.Request.ProtocolVersion;
              if (!cl_set && !chunked && v >= HttpVersion.Version11)
                  chunked = true;
  
              /* Apache forces closing the connection for these status codes:
               *	HttpStatusCode.BadRequest 		400
               *	HttpStatusCode.RequestTimeout 		408
               *	HttpStatusCode.LengthRequired 		411
               *	HttpStatusCode.RequestEntityTooLarge 	413
               *	HttpStatusCode.RequestUriTooLong 	414
               *	HttpStatusCode.InternalServerError 	500
               *	HttpStatusCode.ServiceUnavailable 	503
               */
              bool conn_close = status_code == 400 || status_code == 408 || status_code == 411 ||
                      status_code == 413 || status_code == 414 ||
                      status_code == 500 ||
                      status_code == 503;
  
              if (conn_close == false)
                  conn_close = !context.Request.KeepAlive;
  
              // They sent both KeepAlive: true and Connection: close!?
              if (!keep_alive || conn_close)
              {
                  headers.SetInternal("Connection", "close");
                  conn_close = true;
              }
  
              if (chunked)
                  headers.SetInternal("Transfer-Encoding", "chunked");
  
              //int reuses = context.Connection.Reuses;
              //if (reuses >= 100)
              //{
              //    _logger.Debug("HttpListenerResponse - keep alive has exceeded 100 uses and will be closed.");
  
              //    force_close_chunked = true;
              //    if (!conn_close)
              //    {
              //        headers.SetInternal("Connection", "close");
              //        conn_close = true;
              //    }
              //}
  
              if (!conn_close)
              {
                  if (context.Request.ProtocolVersion <= HttpVersion.Version10)
                      headers.SetInternal("Connection", "keep-alive");
              }
  
              if (location != null)
                  headers.SetInternal("Location", location);
  
              if (cookies != null)
              {
                  foreach (Cookie cookie in cookies)
                      headers.SetInternal("Set-Cookie", cookie.ToString());
              }
  
              using (StreamWriter writer = new StreamWriter(ms, encoding, 256, true))
              {
                  writer.Write("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
                  string headers_str = headers.ToStringMultiValue();
                  writer.Write(headers_str);
                  writer.Flush();
              }
  
              int preamble = encoding.GetPreamble().Length;
              if (output_stream == null)
                  output_stream = context.Connection.GetResponseStream();
  
              /* Assumes that the ms was at position 0 */
              ms.Position = preamble;
              HeadersSent = true;
          }
  
          public void SetCookie(Cookie cookie)
          {
              if (cookie == null)
                  throw new ArgumentNullException("cookie");
  
              if (cookies != null)
              {
                  if (FindCookie(cookie))
                      throw new ArgumentException("The cookie already exists.");
              }
              else
              {
  
gt:                 cookies = new CookieCollection();