var sys = require('./sys');
var net = require('./net');
var events = require('events');

var CRLF = "\r\n";
var STATUS_CODES = exports.STATUS_CODES = {
  100 : 'Continue',
  101 : 'Switching Protocols',
  200 : 'OK',
  201 : 'Created',
  202 : 'Accepted',
  203 : 'Non-Authoritative Information',
  204 : 'No Content',
  205 : 'Reset Content',
  206 : 'Partial Content',
  300 : 'Multiple Choices',
  301 : 'Moved Permanently',
  302 : 'Moved Temporarily',
  303 : 'See Other',
  304 : 'Not Modified',
  305 : 'Use Proxy',
  400 : 'Bad Request',
  401 : 'Unauthorized',
  402 : 'Payment Required',
  403 : 'Forbidden',
  404 : 'Not Found',
  405 : 'Method Not Allowed',
  406 : 'Not Acceptable',
  407 : 'Proxy Authentication Required',
  408 : 'Request Time-out',
  409 : 'Conflict',
  410 : 'Gone',
  411 : 'Length Required',
  412 : 'Precondition Failed',
  413 : 'Request Entity Too Large',
  414 : 'Request-URI Too Large',
  415 : 'Unsupported Media Type',
  500 : 'Internal Server Error',
  501 : 'Not Implemented',
  502 : 'Bad Gateway',
  503 : 'Service Unavailable',
  504 : 'Gateway Time-out',
  505 : 'HTTP Version not supported'
};

var connectionExpression = /Connection/i;
var transferEncodingExpression = /Transfer-Encoding/i;
var closeExpression = /close/i;
var chunkExpression = /chunk/i;
var contentLengthExpression = /Content-Length/i;


/* Abstract base class for ServerRequest and ClientResponse. */
function IncomingMessage (socket) {
  events.EventEmitter.call(this);

  this.socket = socket;
  this.httpVersion = null;
  this.headers = {};

  this.method = null;

  // response (client) only
  this.statusCode = null;
}
sys.inherits(IncomingMessage, events.EventEmitter);
exports.IncomingMessage = IncomingMessage;

IncomingMessage.prototype._parseQueryString = function () {
  throw new Error("_parseQueryString is deprecated. Use require(\"querystring\") to parse query strings.\n");
};

IncomingMessage.prototype.setBodyEncoding = function (enc) {
  // TODO: Find a cleaner way of doing this.
  this.socket.setEncoding(enc);
};

IncomingMessage.prototype.pause = function () {
  this.socket.readPause();
};

IncomingMessage.prototype.resume = function () {
  this.socket.readResume();
};

IncomingMessage.prototype._addHeaderLine = function (field, value) {
  if (field in this.headers) {
    // TODO Certain headers like 'Content-Type' should not be concatinated.
    // See https://www.google.com/reader/view/?tab=my#overview-page
    this.headers[field] += ", " + value;
  } else {
    this.headers[field] = value;
  }
};

function OutgoingMessage () {
  events.EventEmitter.call(this);

  this.output = [];
  this.outputEncodings = [];

  this.closeOnFinish = false;
  this.chunkEncoding = false;
  this.shouldKeepAlive = true;
  this.useChunkedEncodingByDefault = true;

  this.flushing = false;

  this.finished = false;
}
sys.inherits(OutgoingMessage, events.EventEmitter);
exports.OutgoingMessage = OutgoingMessage;

OutgoingMessage.prototype.send = function (data, encoding) {
  var length = this.output.length;

  if (length === 0) {
    this.output.push(data);
    encoding = encoding || "ascii";
    this.outputEncodings.push(encoding);
    return;
  }

  var lastEncoding = this.outputEncodings[length-1];
  var lastData = this.output[length-1];

  if ((lastEncoding === encoding) ||
      (!encoding && data.constructor === lastData.constructor)) {
    if (lastData.constructor === String) {
      this.output[length-1] = lastData + data;
    } else {
      this.output[length-1] = lastData.concat(data);
    }
    return;
  }

  this.output.push(data);
  encoding = encoding || "ascii";
  this.outputEncodings.push(encoding);
};

OutgoingMessage.prototype.sendHeaderLines = function (first_line, headers) {
  var sentConnectionHeader = false;
  var sendContentLengthHeader = false;
  var sendTransferEncodingHeader = false;

  // first_line in the case of request is: "GET /index.html HTTP/1.1\r\n"
  // in the case of response it is: "HTTP/1.1 200 OK\r\n"
  var messageHeader = first_line;
  var field, value;
  for (var i in headers) {
    if (headers[i] instanceof Array) {
      field = headers[i][0];
      value = headers[i][1];
    } else {
      if (!headers.hasOwnProperty(i)) continue;
      field = i;
      value = headers[i];
    }

    messageHeader += field + ": " + value + CRLF;

    if (connectionExpression.test(field)) {
      sentConnectionHeader = true;
      if (closeExpression.test(value)) this.closeOnFinish = true;

    } else if (transferEncodingExpression.test(field)) {
      sendTransferEncodingHeader = true;
      if (chunkExpression.test(value)) this.chunkEncoding = true;

    } else if (contentLengthExpression.test(field)) {
      sendContentLengthHeader = true;

    }
  }

  // keep-alive logic
  if (sentConnectionHeader == false) {
    if (this.shouldKeepAlive &&
        (sendContentLengthHeader || this.useChunkedEncodingByDefault)) {
      messageHeader += "Connection: keep-alive\r\n";
    } else {
      this.closeOnFinish = true;
      messageHeader += "Connection: close\r\n";
    }
  }

  if (sendContentLengthHeader == false && sendTransferEncodingHeader == false) {
    if (this.useChunkedEncodingByDefault) {
      messageHeader += "Transfer-Encoding: chunked\r\n";
      this.chunkEncoding = true;
    }
    else {
      this.closeOnFinish = true;
    }
  }

  messageHeader += CRLF;

  this.send(messageHeader);
  // wait until the first body chunk, or finish(), is sent to flush.
};

OutgoingMessage.prototype.sendBody = function (chunk, encoding) {
  encoding = encoding || "ascii";
  if (this.chunkEncoding) {
    this.send(process._byteLength(chunk, encoding).toString(16));
    this.send(CRLF);
    this.send(chunk, encoding);
    this.send(CRLF);
  } else {
    this.send(chunk, encoding);
  }

  if (this.flushing) {
    this.flush();
  } else {
    this.flushing = true;
  }
};

OutgoingMessage.prototype.flush = function () {
  this.emit("flush");
};

OutgoingMessage.prototype.finish = function () {
  if (this.chunkEncoding) this.send("0\r\n\r\n"); // last chunk
  this.finished = true;
  this.flush();
};


function ServerResponse (req) {
  OutgoingMessage.call(this);

  if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
    this.useChunkedEncodingByDefault = false;
    this.shouldKeepAlive = false;
  }
}
sys.inherits(ServerResponse, OutgoingMessage);
exports.ServerResponse = ServerResponse;

ServerResponse.prototype.sendHeader = function (statusCode, headers) {
  var reason = STATUS_CODES[statusCode] || "unknown";
  var status_line = "HTTP/1.1 " + statusCode.toString() + " " + reason + CRLF;
  this.sendHeaderLines(status_line, headers);
};


function ClientRequest (method, url, headers) {
  OutgoingMessage.call(this);

  this.shouldKeepAlive = false;
  if (method === "GET" || method === "HEAD") {
    this.useChunkedEncodingByDefault = false;
  } else {
    this.useChunkedEncodingByDefault = true;
  }
  this.closeOnFinish = true;

  this.sendHeaderLines(method + " " + url + " HTTP/1.1\r\n", headers);
}
sys.inherits(ClientRequest, OutgoingMessage);
exports.ClientRequest = ClientRequest;

ClientRequest.prototype.finish = function (responseListener) {
  this.addListener("response", responseListener);
  OutgoingMessage.prototype.finish.call(this);
};


/* Returns true if the message queue is finished and the socket
 * should be closed. */
function flushMessageQueue (socket, queue) {
  while (queue[0]) {
    var message = queue[0];

    while (message.output.length > 0) {
      if (!socket.writable) return true;

      var data = message.output.shift();
      var encoding = message.outputEncodings.shift();

      socket.send(data, encoding);
    }

    if (!message.finished) break;

    message.emit("sent");
    queue.shift();

    if (message.closeOnFinish) return true;
  }
  return false;
}


var parserFreeList = [];

function newParser (type) {
  var parser;
  if (parserFreeList.length) {
    parser = parserFreeList.shift();
    parser.reinitialize(type);
  } else {
    parser = new process.HTTPParser(type);

    parser.onMessageBegin = function () {
      parser.incoming = new IncomingMessage(parser.socket);
      parser.field = null;
      parser.value = null;
    };

    // Only servers will get URL events.
    parser.onURL = function (b, start, len) {
      var slice = b.asciiSlice(start, start+len);
      if (parser.incoming.url) {
        parser.incoming.url += slice;
      } else {
        // Almost always will branch here.
        parser.incoming.url = slice;
      }
    };

    parser.onHeaderField = function (b, start, len) {
      var slice = b.asciiSlice(start, start+len).toLowerCase();
      if (parser.value) {
        parser.incoming._addHeaderLine(parser.field, parser.value);
        parser.field = null;
        parser.value = null;
      }
      if (parser.field) {
        parser.field += slice;
      } else {
        parser.field = slice;
      }
    };

    parser.onHeaderValue = function (b, start, len) {
      var slice = b.asciiSlice(start, start+len);
      if (parser.value) {
        parser.value += slice;
      } else {
        parser.value = slice;
      }
    };

    parser.onHeadersComplete = function (info) {
      if (parser.field && parser.value) {
        parser.incoming._addHeaderLine(parser.field, parser.value);
      }

      parser.incoming.httpVersionMajor = info.versionMajor;
      parser.incoming.httpVersionMinor = info.versionMinor;

      if (info.method) {
        // server only
        parser.incoming.method = info.method;
      } else {
        // client only
        parser.incoming.statusCode = info.statusCode;
      }

      parser.onIncoming(parser.incoming, info.shouldKeepAlive);
    };

    parser.onBody = function (b, start, len) {
      parser.incoming.emit("data", b.slice(start, start+len));
    };

    parser.onMessageComplete = function () {
      parser.incoming.emit("eof");
    };
  }
  return parser;
}

function freeParser (parser) {
  if (parserFreeList.length < 1000) parserFreeList.push(parser);
}

function connectionListener (socket) {
  var self = this;
  var parser = newParser('request');
  // An array of responses for each socket. In pipelined connections
  // we need to keep track of the order they were sent.
  var responses = [];

  socket.addListener('dataLite', function (d, start, end) {
    parser.execute(d, start, end - start);
  });

  // is this really needed?
  socket.addListener('eof', function () {
    parser.finish();
    // unref the parser for easy gc
    freeParser(parser);

    if (responses.length == 0) {
      socket.close();
    } else {
      responses[responses.length-1].closeOnFinish = true;
    }
  });

  parser.socket = socket;
  // The following callback is issued after the headers have been read on a
  // new message. In this callback we setup the response object and pass it
  // to the user.
  parser.onIncoming = function (incoming, shouldKeepAlive) {
    var req = incoming;
    var res = new ServerResponse(req);

    res.shouldKeepAlive = shouldKeepAlive;
    res.addListener('flush', function () {
      if (flushMessageQueue(socket, responses)) {
        socket.close();
      }
    });
    responses.push(res);

    self.emit('request', req, res);
  };
}


function Server (requestListener, options) {
  net.Server.call(this, connectionListener);
  //server.setOptions(options);
  this.addListener('request', requestListener);
}
process.inherits(Server, net.Server);
exports.Server = Server;
exports.createServer = function (requestListener, options) {
  return new Server(requestListener, options);
};