/*
 * Copyright (c) 1999-2001 Caucho Technology.  All rights reserved.
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  In particular,
 * the following conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 *
 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL Caucho OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.      
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <errno.h>

#include "cse.h"
#include "proxy.h"
#include "../common/version.h"

static time_t last_time;
static char timebuf[1024];

response_t *
response_create()
{
  response_t *res = (response_t *) cse_malloc(sizeof(response_t));

  if (! res)
    return 0;

  memset(res, 0, sizeof(response_t));

  return res;
}

/**
 * Free a response.
 */
void
response_free(response_t *res)
{
  cse_free(res);
}

void
response_start(response_t *res)
{
  res->length = 0;
  res->offset = 0;
  res->entry = 0;
  res->after_headers = 0;
  res->chunked = 0;
  res->entry = 0;
}

static int
flush_buffer(response_t *res, char *buffer, int length)
{
  connection_t *conn = res->conn;
  fd_set write_set;
  struct timeval timeout;
  int result = -1;
  int i = 3;
  
  if (conn->fd < 0)
    return -1;

  do {
    timeout.tv_sec = 15;
    timeout.tv_usec = 0;
    FD_ZERO(&write_set);
    FD_SET(conn->fd, &write_set);

    result = select(conn->fd + 1, 0, &write_set, 0, &timeout);
    if (result > 0) {
      result = conn->ops->write(conn, buffer, length);
      if (result > 0) {
        i = 3;
        length -= result;
        buffer += result;
      }
    }
  } while (length > 0 && result > 0 ||
           result < 0 && i-- > 0 && conn->fd >= 0 && errno == EINTR);

  if (length == 0)
    return 0;
  else if (result <= 0) {
    conn->ops->close(conn);
    return -1;
  }
  else
    return 0;
}

int
cse_rwrite(response_t *res, char *buf, int length)
{
  int result = 0;

  if (res->length + length >= 8192) {
    result = flush_buffer(res, res->buffer, res->length);
    res->length = 0;

    if (result < 0) {
      res->request->keepalive = 0;
      return result;
    }
    
    if (length >= 8192) {
      result = flush_buffer(res, buf, length);
      
      if (result < 0)
        res->request->keepalive = 0;
      
      return result;
    }
  }
  
  memcpy(res->buffer + res->length, buf, length);
  res->length += length;

  return length;
}

void
cse_rprintf(response_t *res, char *fmt, ...)
{
  char buf[8192];
  int len;
  
  va_list list;

  va_start(list, fmt);
  len = vsprintf(buf, fmt, list);

  cse_rwrite(res, buf, len);
  
  va_end(list);
}

void
cse_add_header(response_t *res, char *key, char *value)
{
  cse_rprintf(res, "%s: %s\r\n", key, value);
}

void
cse_flush_headers(response_t *res, int length)
{
  time_t now;
  struct tm *tm;

  time(&now);
  
  if (now != last_time) {
    last_time = now;

    strftime(timebuf, sizeof(timebuf),
             "%a, %d %b %Y %H:%M:%S GMT",
             gmtime(&now));
  }

  cse_rprintf(res, "Server: %s\n\n", VERSION);
  cse_rprintf(res, "Date: %s\r\n\r\n", timebuf);
}

/**
 * Sends the headers back with the response.
 *
 * @param res the response object.
 * @param buf buffer from the JDK containing the results
 * @param length length of the JDK buffer results.
 *
 * @return the length of the buffer read
 */
int
response_send_headers(response_t *res, char *buf, int length)
{
  request_t *req = res->request;
  time_t now;
  struct tm *tm;
  int i;
  int status;

  /* XXX: kill for now until we get the threading and counts right */
  if (req->keepalive && ! server_allow_keepalive(req->server))
    req->keepalive = 0;

  res->chunked = req->keepalive;

  status = (buf[0] - '0') * 100 + (buf[1] - '0') * 10 + buf[2] - '0';

  for (i = 0; i < length && buf[i] != '\n'; i++) {
  }

  if (req->version >= HTTP_1_1)
    cse_rprintf(res, "HTTP/1.1 ");
  else if (req->version >= HTTP_1_0) {
    cse_rprintf(res, "HTTP/1.0 ");
    res->chunked = 0;
  }

  cse_rwrite(res, buf, i);
  
  time(&now);
  
  if (now != last_time) {
    last_time = now;

    strftime(timebuf, sizeof(timebuf),
             "%a, %d %b %Y %H:%M:%S GMT",
             gmtime(&now));
  }

  for (i++; i < length; i++) {
    char *key = buf + i;
    char *value = 0;
    int code;

    if (*key == '\n') {
      i++;
      break;
    }

    for (; i < length && buf[i] != ':'; i++) {
    }
    buf[i] = 0;
    value = buf + i + 1;
    
    for (; i < length && buf[i] != '\n'; i++) {
    }
    buf[i] = 0;
    
    code = int_map_get(g_header_map, key);
    if (code == CONTENT_LENGTH)
      res->chunked = 0;

    cse_rprintf(res, "\r\n%s: %s", key, value);
  }

  if (! req->keepalive)
    res->chunked = 0;

  if (res->chunked)
    cse_rprintf(res, "\r\nTransfer-Encoding: chunked");

  if (! req->keepalive && req->version >= HTTP_1_1)
    cse_rprintf(res, "\r\nConnection: close");

  if (res->chunked)
    cse_rprintf(res, "\r\nDate: %s\r\n", timebuf);
  else
    cse_rprintf(res, "\r\nDate: %s\r\n\r\n", timebuf);

  return i;
}

int
response_write_chunk(response_t *res, char *buf, int length)
{
  if (length <= 0)
    return 0;

  if (res->chunked)
    cse_rprintf(res, "\r\n%x\r\n", length);
    
  return cse_rwrite(res, buf, length);
}

int
cse_flush_request(response_t *res)
{
  int result = 0;
  
  if (res->length > 0)
    result = flush_buffer(res, res->buffer, res->length);
  
  if (result < 0)
    res->request->keepalive = 0;
  
  res->length = 0;

  return result;
}

int
cse_close_request(response_t *res)
{
  int result = 0;
  
  if (res->chunked)
    cse_rprintf(res, "\r\n0\r\n\r\n");

  if (res->length > 0)
    result = flush_buffer(res, res->buffer, res->length);
  
  if (result < 0)
    res->request->keepalive = 0;
  
  res->length = 0;

  return result;
}
