#ifdef WIN32
#define XP_WIN32
#define NSAPI_PUBLIC __declspec(dllexport)
#else
#define XP_UNIX
#define NSAPI_PUBLIC
#endif

#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include "nsapi.h"
#include "../common/cse.h"

static config_t g_static_config;
static config_t *g_config;
static char *g_hostname;
static CRITICAL g_lock;

void
cse_log(char *fmt, ...)
{
#ifdef DEBUG
  va_list args;
  FILE *file = fopen("/tmp/log", "a+");

  if (file) {
    va_start(args, fmt);
    vfprintf(file, fmt, args);
    va_end(args);
    fclose(file);
  }
#endif
}

void
cse_error(config_t *config, char *format, ...)
{
  char buf[BUF_LENGTH];
  va_list args;

  va_start(args, format);
  vsprintf(buf, format, args);
  va_end(args);

  config->error = cse_strdup(config->p, buf);
}

void *cse_malloc(int size)
{
  return PERM_MALLOC((size));
}

void cse_free(void *data) {}

void
cse_set_socket_cleanup(int socket, void *pool)
{
}

void
cse_kill_socket_cleanup(int socket, void *pool)
{
}

void *
cse_create_lock(config_t *config)
{
  return crit_init();
}

int
cse_lock(void *lock)
{
  if (lock)
    crit_enter((CRITICAL) lock);

  return 1;
}

void
cse_unlock(void *lock)
{
  if (lock)
    crit_exit((CRITICAL) lock);
}

static void
write_env(stream_t *s, Session *sn, Request *rq)
{
  char buf[1024];
  char *host;
  char *query = 0;
  char *user;
  char *auth_type;

  cse_write_string(s, CSE_PROTOCOL, pblock_findval("protocol", rq->reqpb));
  cse_write_string(s, CSE_METHOD, pblock_findval("method", rq->reqpb));
  cse_write_string(s, CSE_URI, pblock_findval("uri", rq->reqpb));

  /* XXX: problems with post */
  
  cse_write_string(s, CSE_QUERY_STRING, pblock_findval("query", rq->reqpb));

  if (request_header("host", &host, sn, rq) == REQ_PROCEED && host) {
    char *p;

    strcpy(buf, host);
    p = strchr(buf, ':');
    
    if (p) {
      *p = 0;

      cse_write_string(s, CSE_SERVER_NAME, buf);
      cse_write_string(s, CSE_SERVER_PORT, p + 1);
    }
    else {
      cse_write_string(s, CSE_SERVER_NAME, host);
      sprintf(buf, "%d", conf_getglobals()->Vport);
      cse_write_string(s, CSE_SERVER_PORT, buf);
    }
  }
  else {
    if (! g_hostname)
      g_hostname = strdup(util_hostname());
    cse_write_string(s, CSE_SERVER_NAME, g_hostname);
    sprintf(buf, "%d", conf_getglobals()->Vport);
    cse_write_string(s, CSE_SERVER_PORT, buf);
  }

  /* Can the user configure this so DNS lookups don't occur?
  if (session_dns(sn))
    cse_write_string(s, CSE_REMOTE_HOST, session_dns(sn));
  */
  
  cse_write_string(s, CSE_REMOTE_ADDR, pblock_findval("ip", sn->client));
  
  cse_write_string(s, CSE_REMOTE_USER, pblock_findval("auth-user", rq->vars));
  cse_write_string(s, CSE_AUTH_TYPE, pblock_findval("auth-type", rq->vars));

  sprintf(buf, "%d", s->srun->session);
  cse_write_string(s, CSE_SESSION_GROUP, buf);

  /* SSL */
  if (security_active) {
    char *keysize;
    char *cert;

    cse_write_string(s, CSE_IS_SECURE, "");
    cse_write_string(s, CSE_HEADER, "HTTPS");
    cse_write_string(s, CSE_VALUE, "on");
    keysize = pblock_findval("keysize", sn->client);
    if (keysize) {
      cse_write_string(s, CSE_HEADER, "HTTPS_KEYSIZE");
      cse_write_string(s, CSE_VALUE, keysize);
    }
    keysize = pblock_findval("secret-keysize", sn->client);
    if (keysize) {
      cse_write_string(s, CSE_HEADER, "HTTPS_SECRETKEYSIZE");
      cse_write_string(s, CSE_VALUE, keysize);
    }

    cert = pblock_findval("auth-cert", rq->vars);
    if (cert) {
      cse_write_string(s, CSE_CLIENT_CERT, cert);
      cse_write_string(s, CSE_HEADER, "SSL_CLIENT_CERT");
      cse_write_string(s, CSE_VALUE, cert);
    }
  } 
}

/**
 * Writes headers to srun.
 */
static void
write_headers(stream_t *s, Session *sn, Request *rs, int *content_length)
{
  int i;
  *content_length = -1;

  if (! rs->headers)
    return;

  for (i = 0; i < rs->headers->hsize; i++) {
    struct pb_entry *entry = rs->headers->ht[i];

    for (; entry; entry = entry->next) {
      char *name = entry->param->name;
      char *value = entry->param->value;

      if (! strcasecmp(name, "Content-type"))
	cse_write_string(s, CSE_CONTENT_TYPE, value);
      else if (! strcasecmp(name, "Content-length")) {
	cse_write_string(s, CSE_CONTENT_LENGTH, value);
	*content_length = atoi(value);
      }
      else {
	cse_write_string(s, CSE_HEADER, name);
	cse_write_string(s, CSE_VALUE, value);
      }
    }
  }
}

static int
cse_write_response(stream_t *s, int len, Session *sn, Request *rq)
{
  while (len > 0) {
    int sublen;
    int writelen;

    if (s->read_offset >= s->read_length && cse_fill_buffer(s) < 0)
      return -1;

    sublen = s->read_length - s->read_offset;
    if (len < sublen)
      sublen = len;

    writelen = net_write(sn->csd, s->read_buf + s->read_offset, sublen);
    /* XXX: Don't close here, just suck up the remaining data.
    if (writelen < sublen) {
      cse_log("error2 (%d) %d(%d) %d\n", s->socket, writelen, sublen, errno);
      return -1;
    }
    */
    s->read_offset += sublen;
    len -= sublen;
  }
  
  /* ap_rflush(r); */

  return 1;
}

static int
send_data(stream_t *s, Session *sn, Request *rq, int ack)
{
  int code = CSE_END;
  char buf[8193];
  char key[8193];
  char value[8193];
  int status;
 int i;
  
  do {
    int l1, l2, l3;
    int len;

    /* ap_reset_timeout(r); */
    
    code = cse_read_byte(s);
    l1 = cse_read_byte(s) & 0xff;
    l2 = cse_read_byte(s) & 0xff;
    l3 = cse_read_byte(s);
    len = (l1 << 16) + (l2 << 8) + (l3 & 0xff);

    if (s->socket < 0)
      return -1;

    switch (code) {
    case CSE_STATUS:
      cse_read_all(s, buf, len);
      buf[len] = 0;
      for (i = 0; buf[i] && buf[i] != ' '; i++) {
      }
      if (buf[i])
	i++;

      status = atoi(buf + i);
      for (; buf[i] && buf[i] != ' '; i++) {
      }
      if (buf[i])
	i++;

      protocol_status(sn, rq, status, buf + i);
      break;

    case CSE_HEADER:
      cse_read_all(s, key, len);
      key[len] = 0;
      cse_read_string(s, value, sizeof(value));
      key[0] = toupper(key[0]);
      for (i = 1; key[i]; i++)
	key[i] = tolower(key[i]);

	pblock_nvinsert(key, value, rq->srvhdrs);
     break;

    case CSE_DATA:
      if (cse_write_response(s, len, sn, rq) < 0)
	return -1;
      break;

    case CSE_SEND_HEADER:
      protocol_start_response(sn, rq);
      break;

    case -1:
      return -1;

    default:
      cse_read_all(s, buf, len);
      break;
    }
  } while (code > 0 && code != CSE_END && code != CSE_CLOSE && code != ack);

  return code;
}

static int
write_request(stream_t *s, Session *sn, Request *rq, config_t *config)
{
  int content_length;
  int code;
  char buf[BUF_LENGTH];
  int isFirst = 1;
  netbuf *inbuf = sn->inbuf;

  write_env(s, sn, rq);
  
  write_headers(s, sn, rq, &content_length);

  /* read post data */
  while (content_length > 0) {
    int len;

    if (inbuf->pos >= inbuf->cursize) {
      if (netbuf_grab(inbuf, BUF_LENGTH) <= 0)
	return 0;
    }

    len = inbuf->cursize - inbuf->pos;
    if (len > BUF_LENGTH)
      len = BUF_LENGTH;
    if (len <= 0)
      return 0;
    
    memcpy(buf, inbuf->inbuf + inbuf->pos, len);
    inbuf->pos += len;

    content_length -= len;
    buf[len] = 0;
    
    if (! isFirst) {
      code = send_data(s, sn, rq, CSE_ACK);
      if (code < 0 || code == CSE_END || code == CSE_CLOSE)
	break;
    }

    cse_write_packet(s, CSE_DATA, buf, len);
    isFirst = 0;
  }

  cse_write_packet(s, CSE_END, 0, 0);

  code = send_data(s, sn, rq, CSE_END);
  
  return code == CSE_END;
}

static void
net_printf(Session *sn, char *fmt, ...)
{
  va_list args;
  char buf[4096];
  
  va_start(args, fmt);
  vsprintf(buf, fmt, args);
  va_end(args);

  net_write(sn->csd, buf, strlen(buf));
}

static void
connection_error(config_t *config, srun_item_t *srun_item, Session *sn, Request *rq)
{
  srun_t *srun = srun_item->srun;
  int fd = -1;

  /* Writing of error_page added by Jose Manuel Cantera Fonseca */
  if (config->error_page)
    fd = open(config->error_page, O_RDONLY);
  
  if (fd >= 0) {
    char buf[1024];
    int len;

    while ((len = read(fd, buf, sizeof(buf))) > 0)
      net_write(sn->csd, buf, len);

    close(fd);
  }
  else {
    net_printf(sn, "<html><body bgcolor='white'>\n");
    net_printf(sn, "<h1>Cannot contact servlet runner at %s:%d</h1>\n", 
               srun->hostname ? srun->hostname : "localhost",
               srun->port);
    net_printf(sn, "</body></html>\n");
  }
}

static int
get_session_index(Request *rq)
{
  int i;
  int session;
  char *uri;

  for (i = 0; i < rq->headers->hsize; i++) {
    struct pb_entry *entry = rq->headers->ht[i];

    for (; entry; entry = entry->next) {
      char *name = entry->param->name;
      char *value = entry->param->value;

      if (strcasecmp(name, "cookie"))
	continue;

      session = cse_session_from_string(value, "JSESSIONID=");
      if (session >= 0)
	return session;
    }
  }

  uri = pblock_findval("uri", rq->reqpb);
  
  return cse_session_from_string(uri, g_config->session_url_prefix);
}

static void
cse_init(char *resin_conf)
{
  g_lock = crit_init();
    
  crit_enter(g_lock);
    
  if (! g_config) {
    g_static_config.path = resin_conf;

    cse_init_config(&g_static_config);
    
    g_config = &g_static_config;
    
    crit_exit(g_lock);
  }
}

NSAPI_PUBLIC int
caucho_filter(pblock *pb, Session *sn, Request *rq)
{
  char *host;
  char hostbuf[1024];
  int port;
  char *uri = 0;

  LOG(("start caucho_filter\n"));

  if (! g_config) {
    char *conf = pblock_findval("conf", pb);

    if (conf)
      cse_init(conf);
    else
      cse_init("c:\resin1.1");
  }
  
  cse_update_config(g_config, rq->req_start);
  
  if (request_header("host", &host, sn, rq) == REQ_PROCEED && host) {
    char *p;

    strcpy(hostbuf, host);
    p = strchr(hostbuf, ':');
    
    if (p) {
      port = atoi(p + 1);
      *p = 0;
    }
    else
      port =  conf_getglobals()->Vport;
  }
  else {
    strcpy(hostbuf, util_hostname());
    port =  conf_getglobals()->Vport;
  }

  uri = pblock_findval("uri", rq->reqpb);
  if (cse_match_request(g_config, hostbuf, port, uri, 1)) {
    param_free(pblock_remove("name", rq->vars));
    pblock_nvinsert("name", "resin", rq->vars);

    return REQ_PROCEED;
  }
  
  return REQ_NOACTION;
}

NSAPI_PUBLIC int
caucho_service(pblock *pb, Session *sn, Request *rq)
{
  stream_t s;
  int reuse;
  int session_index = -1;
  char *ip = "";
  int rand = 0;

  LOG(("start caucho_service\n"));

  if (! g_config) {
    char *conf = pblock_findval("conf", pb);

    if (conf)
      cse_init(conf);
    else
      cse_init("c:\\resin1.1");
  }

  cse_update_config(g_config, rq->req_start);
  
  param_free(pblock_remove("content-type", rq->srvhdrs));
  
  session_index = get_session_index(rq);
 
  if (! cse_open_connection(&s, g_config, session_index, rq->req_start, 0)) {
    connection_error(g_config, g_config->srun_list, sn, rq);
  
    return REQ_PROCEED;
  }

  reuse = write_request(&s, sn, rq, g_config);

  /*
  //ap_kill_timeout(r);
  //ap_rflush(r);
  */

  if (reuse)
    cse_recycle(&s, rq->req_start);
  else
    cse_close(&s, "no reuse");

  return REQ_PROCEED;
}

static void
jvm_status(config_t *config, Session *sn)
{
  int i;
  stream_t s;

  net_printf(sn, "<center><table border=2 cellspacing=0 cellpadding=2 width='60%%'>\n");
  net_printf(sn, "<tr><th>Host</th>\n");
  net_printf(sn, "    <th>Connect<br>Timeout</th>\n");
  net_printf(sn, "    <th>Live<br>Time</th>\n");
  net_printf(sn, "    <th>Dead<br>Time</th>\n");
  net_printf(sn, "</tr>\n");

  for (i = 0; i < config->srun_size; i++) {
    srun_item_t *srun_item = config->srun_list + i;
    srun_t *srun = srun_item->srun;
    int port;

    if (! srun)
      return;

    port = srun->port;

    net_printf(sn, "<tr>");

    if (! cse_open(&s, config, srun_item, 0, 1)) {
      net_printf(sn, "<td bgcolor='#ff6666'>%s:%d%s (down)</td>",
		 srun->hostname ? srun->hostname : "localhost",
		 port, srun_item->is_backup ? "*" : "");
    }
    else {
      net_printf(sn, "<td bgcolor='#66ff66'>%s:%d%s (ok)</td>",
		 srun->hostname ? srun->hostname : "localhost",
		 port, srun_item->is_backup ? "*" : "");

      net_printf(sn, "<td align=right>%d</td><td align=right>%d</td><td align=right>%d</td>",
                 srun->connect_timeout, srun->live_time, srun->dead_time);
      
      cse_close(&s, "caucho-status");
      continue;
    }
  }

  net_printf(sn, "</table></center>\n");
}

NSAPI_PUBLIC int
caucho_status(pblock *pb, Session *sn, Request *rq)
{
  web_app_t *app;
  location_t *loc;

  LOG(("start caucho_status\n"));

  if (! g_config) {
    char *conf = pblock_findval("conf", pb);
	if (conf)
		cse_init(conf);
	else
		cse_init("c:\\resin1.1");
  }
  
  cse_update_config(g_config, rq->req_start);

  param_free(pblock_remove("content-type", rq->srvhdrs));
  pblock_nvinsert("content-type", "text/html", rq->srvhdrs);

  net_printf(sn, "<html><title>Status : Caucho Servlet Engine</title>\n");
  net_printf(sn, "<body bgcolor=white>\n");
  net_printf(sn, "<h1>Status : Caucho Servlet Engine</h1>\n");
  
  jvm_status(g_config, sn);

  net_printf(sn, "<p><center><table border=2 cellspacing=0 cellpadding=2 width='60%%'>\n");
  net_printf(sn, "<tr><th width='50%%'>Host\n");
  net_printf(sn, "    <th>url-pattern\n");
  
  app = g_config ? g_config->applications : 0;
  for (; app; app = app->next) {
    for (loc = app->locations; loc; loc = loc->next) {
      net_printf(sn, "<tr bgcolor='#ffcc66'><td>%s%s<td>%s%s%s%s%s</tr>\n", 
                 app->host && app->host[0] ? app->host : "*",
                 app->prefix,
                 loc->prefix,
                 ! loc->is_exact && ! loc->suffix ? "/*" : 
                 loc->suffix && loc->prefix[0] ? "/" : "",
                 loc->suffix ? "*" : "",
                 loc->suffix ? loc->suffix : "",
                 loc->ignore ? " (ignore)" : "");
    }
  }

  net_printf(sn, "</table></center>\n");
  net_printf(sn, "</body></html>\n");

  return REQ_PROCEED;
}
