/*
 * 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/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

#include <pthread.h>

#include "cse.h"
#include "proxy.h"

static config_t *g_config;
static pthread_mutex_t lock;

typedef struct proxy_t {
  int ss;
  cache_t *cache;
} proxy_t;

void
cse_log(char *fmt, ...)
{
}

void *
cse_create_lock()
{
  return 0;
}

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

int
cse_lock(void *lock)
{
  return 1;
}

void
cse_unlock(void *lock)
{
}

void
cse_error(config_t *config, char *format, ...)
{
}

void cse_free(config_t *config, void *data) {}

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

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

static int
cse_bind(int port)
{
  struct sockaddr_in sin;
  int val = 0;

  int sock = socket(AF_INET, SOCK_STREAM, 0);

  memset(&sin, 0, sizeof(sin));
  sin.sin_port = htons(port);
  
  if (bind(sock, (struct sockaddr *) &sin, sizeof (sin)) < 0) {
    printf("bad bind\n");
    return -1;
  }

  val = 1;
  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
    printf("bad set sock opt\n");
    return -1;
  }
  
  listen(sock, 5);

  return sock;
}

static int
connection_error(config_t *config, srun_t *srun, response_t *res)
{
  char *error_page = config->error_page;
  
  if (error_page) {
    cse_rprintf(res, "HTTP/1.0 302 Redirect\r\n");
    cse_rprintf(res, "Location: %s\r\n", error_page);
    cse_rprintf(res, "\r\n");
    cse_rprintf(res, "Page has moved to %s\n", error_page);
    return;
  }

  cse_rprintf(res, "HTTP/1.0 503 Connection Error\r\n");
  cse_rprintf(res, "Content-Type: text/html\r\n");
  cse_rprintf(res, "\r\n");

  cse_rprintf(res, "<html><body bgcolor='white'>");
  cse_rprintf(res, "<h1>Can't contact servlet runner at %s:%d</h1>", 
              srun->hostname ? srun->hostname : "localhost",
              srun->port);
  cse_rprintf(res, "</body></html>");
}

/**
 * Writes request parameters to srun.
 */
static void
write_env(stream_t *s, request_t *req)
{
  char buf[1024];
  int i;
  
  const char *host;
  int port;

  //cse_write_string(s, CSE_PROTOCOL, r->protocol);
  cse_write_string(s, CSE_PROTOCOL, "HTTP/1.0");
  cse_write_string(s, CSE_METHOD, req->method);
  cse_write_string(s, CSE_URI, req->uri);

  /*
  if (r->args)
    cse_write_string(s, CSE_QUERY_STRING, r->args);

  cse_write_string(s, CSE_SERVER_NAME, ap_get_server_name(r));
  port = ap_get_server_port(r);
  cse_write_string(s, CSE_SERVER_PORT, ap_psprintf(r->pool, "%u", port));

  host = ap_get_remote_host(c, r->per_dir_config, REMOTE_HOST);
  if (host)
    cse_write_string(s, CSE_REMOTE_HOST, host);

  cse_write_string(s, CSE_REMOTE_ADDR, c->remote_ip);
  cse_write_string(s, CSE_REMOTE_PORT,
		   ap_psprintf(r->pool, "%d", ntohs(c->remote_addr.sin_port)));
  */
}

/**
 * Writes a response from srun to the client
 */
static int
cse_write_response(stream_t *s, int len, request_t *req, response_t *res)
{
  while (len > 0) {
    int sublen;
    int writelen;
    int sentlen;

    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 = sublen;
    while (writelen > 0) {
      sentlen = cse_rwrite(res, s->read_buf + s->read_offset, writelen);
      if (sentlen < 0) {
	cse_close(s, "write");
	return -1;
      }

      writelen -= sublen;
    }
    
    s->read_offset += sublen;
    len -= sublen;
  }
  
  return 1;
}

/**
 * Copy data from the JVM to the browser.
 */
static int
send_data(stream_t *s, request_t *req, response_t *res, int ack)
{
  int code = CSE_END;
  char buf[8193];
  char key[8193];
  char value[8193];
  int i;
  
  do {
    int l1, l2, l3;
    int len;

    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; i < len && buf[i] != ' '; i++) {
      }
      
      if (buf[i + 1] != '2' && res->entry) {
        res->entry->is_cacheable = 0;
        res->entry = 0;
      }
      
      cse_rprintf(res, "HTTP/1.0 %s\r\n", buf + i + 1);
      break;

    case CSE_HEADER:
      cse_read_all(s, key, len);
      key[len] = 0;
      cse_read_string(s, value, sizeof(value));
      cse_add_header(res, key, value);
      break;

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

    case CSE_FLUSH:
      //ap_rflush(r);
      break;

    case CSE_SEND_HEADER:
      cse_flush_headers(res, -1);
      break;

    case -1:
      break;

    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, request_t *req, response_t *res, config_t *config)
{
  int code;
  
  write_env(s, req);
  
  cse_write_packet(s, CSE_END, 0, 0);
  
  code = send_data(s, req, res, CSE_END);
  
  return code == CSE_END;
}

static void
handle_proxy(request_t *req, response_t *res, int sock)
{
  config_t *config = g_config;
  stream_t s;
  int retval;
  int reuse;
  int session_index;
  char *ip;
  int now = 0;
  int rand = 0;
  
  session_index = 0;
  ip = "111234";
 
  if (! cse_open_connection(&s, config, session_index, ip,
                            now, rand, 0)) {
    connection_error(config, config->srun_list, res);
    cse_flush_request(res);
    return;
  }

  reuse = write_request(&s, req, res, config);
  cse_flush_request(res);

  if (reuse)
    cse_recycle(&s);
  else
    cse_close(&s, "no reuse");
}

static void
thread_main(proxy_t *proxy, request_t *req, response_t *res)
{
  int ss = proxy->ss;
  struct sockaddr_in sin;
  int len = sizeof(sin);
  char buf[4096];
  cache_t *cache = proxy->cache;
    
  int sock;
    
  memset(&sin, 0, sizeof(&sin));
    
  sock = accept(ss, (struct sockaddr *) &sin, &len);
  if (sock < 0) {
    close(ss); // XXX: other possibilities
    return;
  }

  response_start(res, sock);
  
  request_parse(req, sock);

  if (! cache_fill(cache, req, res))
    handle_proxy(req, res, sock);
  
  cse_flush_request(res);

  close(sock);
}

static void *
thread_start(void *v_proxy)
{
  proxy_t *proxy = v_proxy;
  request_t *request = request_create();
  response_t *response = response_create();
  
  while (1) {
    thread_main(proxy, request, response);
  }
}

/*
int
main()
{
  int ss;
  config_t config;
  pthread_t thread1;
  proxy_t proxy;

  g_config = &config;
  memset(g_config, 0, sizeof(config));

  memset(&proxy, 0, sizeof(proxy));

  g_config->path = "/home/ferg/ws/resin/conf/test.conf";
  cse_init_config(g_config);
    
  ss = cse_bind(8090);
  if (ss < 0) {
    return 1;
  }

  proxy.ss = ss;
  proxy.cache = cache_create(1024);

  pthread_mutex_init(&lock, 0);

  pthread_create(&thread1, 0, thread_start, &proxy);
  //pthread_create(0, 0, thread_start, &ss);

  thread_start(&proxy);

  printf("done\n");
}
*/
