/*
 * Copyright (c) 1998-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").  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.      
 *
 * @author Scott Ferguson
 */

#include <linux/kernel.h>

#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/time.h>
#include <net/ip.h>

#include <asm/uaccess.h>

#include "hardcore.h"

static browser_t *g_browser_ready;
static spinlock_t g_ready_lock = SPIN_LOCK_UNLOCKED;

static browser_t *g_browser_free;
static browser_t *g_browser_list;

static int g_has_accept = 0;

/**
 * Wakes the browser connection, readying it for execution by browser_execute.
 */
void
browser_wake(browser_t *browser)
{
  if (browser->is_active)
    return;

  spin_lock_irq(&g_ready_lock);

  if (! browser->is_active) {
    browser->is_active = 1;
    browser->next = g_browser_ready;
    g_browser_ready = browser;
  }
  
  spin_unlock_irq(&g_ready_lock);

  resind_wake();
}

/**
 * The server socket receives callbacks when accepting a new client socket.
 * Each client is associated with a connection_t structure.
 */
static void
accept_state_change(struct sock *sock)
{
  LOG(("resin: accept state_change %p %d %p\n", sock, sock->state,
       sock->socket));

  if (sock->state == TCP_ESTABLISHED) {
    g_has_accept = 1;
    
    // Wake the server to actually accept the request
    resind_wake();
  }
}

/**
 * Handles the callback when the socket to the browser changes state.
 * Normally, this only occurs on socket close.
 *
 * @param sock the socket between resind and the client
 */
static void
browser_state_change(struct sock *sock)
{
  browser_t *browser = sock->user_data;
  
  LOG(("resin: browser state_change %x %d\n", sock, sock->state));

  if (sock->state == TCP_CLOSE && browser) {
    sock->user_data = 0;
    browser->state = RHC_BROWSER_QUIT;
    browser->is_dead = 1;

    browser_wake(browser);
  }
}

/**
 * Handles the callback when the socket to the browser has more data to
 * read.
 *
 * @param sock the socket between resind and the browser
 * @param bytes in theory, the number of bytes to read.  In reality, this
 *              seems to be unused.
 */
static void
browser_data_ready(struct sock *sock, int bytes)
{
  browser_t *browser = sock->user_data;
  
  LOG(("resin: browser read %x %d %p %d\n", sock, sock->state, browser, bytes));

  if (sock->state == TCP_ESTABLISHED && browser)
    browser_wake(browser);
}

/**
 * Handles the callback when the socket to the browser has space for
 * a write
 *
 * @param sock the socket between resind and the browser
 */
static void
browser_write_space(struct sock *sock)
{
  browser_t *browser = sock->user_data;
  
  if (browser && browser->state == RHC_BROWSER_WRITE) {
    LOG(("resin: browser write %x %d %p %d\n",
         sock, sock->state, browser, browser->state));
  
    browser_wake(browser);
  }
}

/**
 * create a new browser browserection
 */
static void
browser_create()
{
  browser_t *browser = kmalloc(sizeof(browser_t), GFP_KERNEL);

  if (browser) {
    memset(browser, 0, sizeof(browser_t));
    
    browser->resin = &g_resin;
    
    browser->next = g_browser_free;
    g_browser_free = browser;
    
    browser->next_browser = g_browser_list;
    if (g_browser_list)
      g_browser_list->prev_browser = browser;
    g_browser_list = browser;
  }
  else
    LOG(("can't allocate\n"));
}

/**
 * Allocate a new browser object
 */
static browser_t *
browser_allocate(resin_t *resin)
{
  browser_t *browser = g_browser_free;
  
  if (browser) {
    g_browser_free = g_browser_free->next;
    browser->next = 0;
    browser->is_active = 0;
    browser->is_dead = 0;
    browser->cache = 0;
  }

  return browser;
}

/**
 * Accept a socket from the server socket.
 */
static int
browser_accept(resin_t *resin)
{
  struct sock *ss = resin->accept_socket->sk;
  struct sock *sock;
  browser_t *browser;
  struct timeval time_now;
  int err;

  if (! ss)
    return 0;
  
  sock = ss->prot->accept(ss, O_NONBLOCK, &err);

  if (! sock)
    return 0;

  LOG(("accept %p\n", sock));

  browser = browser_allocate(resin);

  // can't allocate the browser, so kill it
  // XXX: how does the listen queue work?
  if (! browser) {
    sock->prot->close(sock, 0);
    return 1;
  }

  // Technically the wrong socket structure, but sendmsg needs the socket
  // Hopefully, this won't confuse anything.
  sock->socket = browser->resin->accept_socket;

  browser->browser_sock = sock;
  browser->is_dead = 0;
  browser->cin_offset = 0;
  browser->cin_length = 0;
  browser->post_offset = 0;
  browser->in_header = 0;

  browser->request_length = -1;

  browser->srun_conn = 0;
  browser->srun_sock = 0;
    
  do_gettimeofday(&time_now);
  browser->now = time_now.tv_sec;

  browser->cache = 0;
  browser->expires = 0;

  browser->cout_offset = 0;
  browser->cout_length = 0;
  browser->iov_index = 0;
  
  sock->user_data = browser;

  sock->state_change = browser_state_change;
  sock->data_ready = browser_data_ready;
  sock->write_space = browser_write_space;

  // Wake the browser in case it has some data
  browser->state = RHC_BROWSER_START;
  browser_wake(browser);

  return 1;
}

/**
 * Closes a browser
 */
void
browser_close(browser_t *browser)
{
  LOG(("close %p\n", browser));
  
  if (browser->browser_sock) {
    struct sock *sock = browser->browser_sock;
    browser->browser_sock = 0;

    sock->socket = 0;
    sock->user_data = 0;
    sock->prot->close(sock, 0);
  }

  // make the srun available to subsequent requests
  if (browser->srun_conn)
    srun_recycle(browser->srun_conn);
    
  browser->srun_conn = 0;
  browser->srun_sock = 0;

  if (browser->cache)
    cache_kill(browser);

  browser->next = g_browser_free;
  g_browser_free = browser;
}

/**
 * Reads from a socket
 *
 * @param sock the socket receiving the browser
 * @param buf the buffer receiving the data
 * @param len the length of the expected data
 *
 * @return the number of bytes actually received
 */
int
socket_recv(struct sock *sock, char *buf, int len)
{
  mm_segment_t oldfs;
  struct msghdr msg;
  struct iovec iov;

  int addr_len = 0;

  if (! sock || len <= 0)
    return -1;

  msg.msg_name =  0;
  msg.msg_namelen = 0;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = 0;
  msg.msg_controllen = 0;
  iov.iov_base = buf;
  iov.iov_len = len;
      
  LOG(("socket read %d\n", len));

  oldfs = get_fs(); set_fs(KERNEL_DS);
  len = sock->prot->recvmsg(sock, &msg, len, 1, 0, &addr_len);
  set_fs(oldfs);

  LOG(("post read %d\n", len));

  return len;
}

/**
 * Reads from the client socket into the client in buffer (cin_*)
 *
 * If there isn't enough room in the buffer, e.g. for pipelined requests,
 * shift the buffer down.
 *
 * @param browser the browser
 */
static int
browser_recv(browser_t *browser)
{
  char *buf = browser->cin_buf;
  int head = browser->cin_offset;
  int tail = browser->cin_length;
  int capacity = sizeof(browser->cin_buf);
  int len;

  LOG(("stuff in %d head %d tail %d cap %d\n", browser->in_header,
       head, tail, capacity));
  
  if (browser->in_header) {
  }
  else if (head == tail) {
    browser->cin_offset = 0;
    tail = 0;
  }
  else if (2 * head < capacity || capacity < 2 * tail) {
    int i;
    int delta = tail - head;

    for (i = 0; i < delta; i++)
      buf[i] = buf[i + head];

    browser->cin_offset = 0;
    tail = delta;
  }

  LOG(("pre-recv tail:%d\n", tail));

  len = socket_recv(browser->browser_sock, buf + tail, capacity - tail);

  LOG(("post-recv tail:%d %d\n", tail, len));
  
  if (len > 0)
    browser->cin_length = tail + len;
  else if (len == -EAGAIN || len == -EWOULDBLOCK)
    browser->cin_length = tail;
  else {
    browser->cin_length = tail;
    browser->state = RHC_BROWSER_QUIT;
    browser_wake(browser);
  }

  return len;
}

/**
 * Prints data to the browser buffer.
 */
void
browser_print(browser_t *browser, char *data)
{
  char *buf;
  
  if (browser->cout_offset >= browser->cout_length) {
    browser->cout_offset = 0;
    browser->cout_length = 0;
  }

  buf = browser->cout_buf + browser->cout_length;
  while ((*buf++ = *data++)) {
  }

  browser->cout_length = buf - browser->cout_buf + 1;

  /* XXX: this isn't quite right. */
  if (browser->iov_index == 0) {
    browser->iov_index = 1;
    browser->iov[0].iov_base = browser->cout_buf + browser->cout_offset;
  }
  
  browser->iov[0].iov_len = browser->cout_length - browser->cout_offset;
}

/**
 * Prints data to the browser buffer.
 */
void
browser_printf(browser_t *browser, char *fmt, ...)
{
  char *buf = 0;
  va_list args;
  
  if (browser->cout_offset >= browser->cout_length) {
    browser->cout_offset = 0;
    browser->cout_length = 0;
  }

  buf = browser->cout_buf + browser->cout_length;
  va_start(args, fmt);
  browser->cout_length += vsprintf(buf, fmt, args);
  va_end(args);

  if (browser->iov_index == 0) {
    browser->iov_index = 1;
    browser->iov[0].iov_base = browser->cout_buf + browser->cout_offset;
  }
  
  browser->iov[0].iov_len = browser->cout_length - browser->cout_offset;
}

/**
 * Write data to the browser
 *
 * @param browser The browser object which is sending
 */
int
browser_write_iov(browser_t *browser)
{
  mm_segment_t oldfs;
  struct iovec *iov = browser->iov;
  int iov_len = browser->iov_index;
  struct msghdr msg;
  struct sock *sock = browser->browser_sock;
  int len;
  int sent_len;
  int i;
  int delta = 0;

  if (! sock)
    return -1;
  
  if (iov_len <= 0) {
    LOG(("iov-len messup\n"));
    return -1;
  }

  LOG(("iov_len(%d)\n",iov_len));
  
  len = 0;
  for (i = 0; i < iov_len; i++) {
    LOG(("iov(buf:%p,len:%d)\n",
         iov[i].iov_base, iov[i].iov_len));
    
    ((char *) iov[i].iov_base)[iov[i].iov_len] = 0;
    len += iov[i].iov_len;

    /*
    if (iov[i].iov_len == 0)
      delta++;
    */
  }

  msg.msg_name =  0;
  msg.msg_namelen = 0;
  msg.msg_iov = iov + delta;
  msg.msg_iovlen = iov_len - delta;
  msg.msg_control = 0;
  msg.msg_controllen = 0;
  msg.msg_flags	= MSG_DONTWAIT|MSG_NOSIGNAL;

  LOG(("sending %p %p %d %d\n", sock, iov, iov_len, len));

  // These are needed to make the copy right
  oldfs = get_fs(); set_fs(KERNEL_DS);
  sent_len = sock->prot->sendmsg(sock, &msg, len);
  set_fs(oldfs);

  LOG(("post send %d\n", sent_len));

  if (sent_len == len) {
    browser->state = browser->write_state;
    return 1;
  }
  else if (sent_len == -EAGAIN)
    return 0;
  else if (sent_len < 0) {
    browser_close(browser);
    return -1;
  }
  else {
    // partial write
    for (i = 0; sent_len > 0 && i < iov_len; i++) {
      if (iov[i].iov_len <= sent_len) {
        sent_len -= iov[i].iov_len;
        iov[i].iov_len = 0;
      }
      else {
        iov[i].iov_len -= sent_len;
        iov[i].iov_base += sent_len;
        sent_len = 0;
      }
    }
    return 0;
  }
}

/**
 * free the browser structure
 */
static void
browser_free(browser_t *browser)
{
  browser_t *next = browser->next_browser;
  browser_t *prev = browser->prev_browser;
  
  if (browser->browser_sock)
    browser_close(browser);

  if (next)
    next->prev_browser = prev;
  
  if (prev)
    prev->next_browser = next;
  else
    g_browser_list = next;

  kfree(browser);
}

/*
 * Create accept socket
 */
int
accept_socket_create(struct sockaddr_in *sin, resin_t *resin)
{
  struct socket *sock;
  int error;

  LOG(("resin: create_socket(%u.%u.%u.%u:%d)\n",
       NIPQUAD(sin->sin_addr.s_addr),
       ntohs(sin->sin_port)));
  
  error = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
  if (error < 0)
    return error;

  error = sock->ops->bind(sock, (struct sockaddr *) sin, sizeof(*sin));
  if (error < 0)
    goto error;
  
  error = sock->ops->listen(sock, 5);
  if (error < 0)
    goto error;

  resin->accept_socket = sock;
  sock->sk->user_data = resin;

  LOG(("created -> %x:%x(%x,%x,%d,%x)\n", sock, resin->accept_socket,
       sock->sk, sock->sk->socket,
       sock->sk->state, sock->sk->user_data));
  
  if (sock->sk->state == TCP_LISTEN)
    LOG(("setting up TCP socket for listening\n"));

  sock->sk->state_change = accept_state_change;

  return 0;

  error:
  sock_release(sock);
  return error;
}

/**
 * Accept new connections
 *
 * No lock is needed on g_has_accept because it's cleared before
 * calling browser_accept in a loop.  Even if an accept_state_change
 * appears between the test and the set, we'll still run through
 * all the accepts using browser_accept.
 */
static void
accept_execute()
{
  if (g_has_accept) {
    g_has_accept = 0;
    LOG(("accept\n"));
      
    while (browser_accept(&g_resin)) {
    }
  }
}


/**
 * Executes all the active browser
 */
void
browser_execute()
{
  if (g_has_accept)
    accept_execute();

  while (g_browser_ready) {
    browser_t *browser;

    spin_lock_irq(&g_ready_lock);
    
    browser = g_browser_ready;
    g_browser_ready = g_browser_ready->next;
    browser->is_active = 0;
    
    spin_unlock_irq(&g_ready_lock);
      
    browser->next = 0;
    if (browser->is_dead)
      browser->state = RHC_BROWSER_QUIT;
      
    LOG(("browser %p %s\n", browser, browser_state_name(browser)));

    switch (browser->state) {
    case RHC_IDLE:
    case RHC_BROWSER_QUIT:
      browser_close(browser);
      break;

    case RHC_BROWSER_START:
    case RHC_BROWSER_SPACE:
    case RHC_BROWSER_METHOD:
    case RHC_BROWSER_URL:
    case RHC_BROWSER_PROTOCOL:
    case RHC_BROWSER_KEY:
    case RHC_BROWSER_VALUE:
    case RHC_BROWSER_EOL:
    case RHC_BROWSER_HEADER_DONE:
      browser_recv(browser);
      browser->in_header = 1;
      request_parse(browser);
      
      if (browser->state == RHC_BROWSER_BODY)
        browser_wake(browser);
      break;
        
    case RHC_BROWSER_BODY:
      browser->in_header = 0;
      if (! strcmp(browser->method, "GET") && cache_lookup(browser)) {
      }
      else {
        srun_connect(&g_resin.srun_list[0], browser);
      }
      break;
      
    case RHC_BROWSER_READ:
      if (browser_recv(browser) > 0) {
        browser->state = browser->next_state;
        browser_wake(browser);
      }
      break;
          
    case RHC_SRUN_START:
      browser->in_header = 0;
      resin_srun(browser);
      if (browser->state == RHC_SRUN_SEND)
        srun_send(browser);
      break;

    case RHC_SRUN_SEND:
      srun_send(browser);
      break;

    case RHC_SRUN_RECV_HEADER:
    case RHC_SRUN_RECV_STATUS:
    case RHC_SRUN_RECV_KEY:
    case RHC_SRUN_RECV_VALUE:
    case RHC_SRUN_RECV_SKIP:
    case RHC_SRUN_BODY:
    case RHC_SRUN_RECV_DATA:
      if (srun_recv(browser) > 0) {
        if (srun_read_header(browser))
          browser_wake(browser);
      }
      break;

    case RHC_SRUN_POST:
      if (browser_recv(browser) > 0) {
        browser->state = browser->next_state;
        srun_send_post(browser);
      }
      break;

    case RHC_CACHE_WRITE:
      if (cache_read(browser))
        browser_wake(browser);
      break;
      
    case RHC_BROWSER_WRITE:
      if (browser_write_iov(browser) > 0)
        browser_wake(browser);
      break;
    }
      
    if (g_has_accept)
      accept_execute();
  }
}

/**
 * Initialize the main socket stuff.
 */
static void
resin_init_socket(resin_t *resin)
{
  struct sockaddr_in sin;
  int error;
  int i;

  sin.sin_family = 0;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port = htons(resin->accept_port);

  for (i = 0; i < 16; i++)
    browser_create();

  error = accept_socket_create(&sin, resin);
  if (error < 0) {
    LOG(("error %d\n", error));
  }
}

/**
 * Initialize the browser structures
 */
void
browser_init(resin_t *resin)
{
  srun_init(resin);

  resin_init_socket(resin);
}

static void
resin_cleanup_socket(resin_t *resin)
{
  if (resin->accept_socket) {
    struct socket *sock = resin->accept_socket;
    resin->accept_socket = 0;

    sock->sk->user_data = 0;

    LOG(("closing server\n"));
    sock_release(sock);
    LOG(("closed server\n"));
  }
}

/**
 * Clean up the browser structures
 */
void
browser_cleanup(resin_t *resin)
{
  resin_cleanup_socket(resin);
  
  srun_cleanup(resin);
  
  LOG(("free\n"));
  
  while (g_browser_list)
    browser_free(g_browser_list);
  
  LOG(("browser done\n"));
}
