/*
 * 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/spinlock.h>
#include <net/ip.h>

#include <asm/uaccess.h>

#include "hardcore.h"

#define IP(a,b,c,d) ((a << 24) + (b << 16) + (c << 8) + (d))

// list of sruns needing closing
static srun_conn_t *g_srun_close_list;

// lock for all srun modifications
static spinlock_t g_srun_lock = SPIN_LOCK_UNLOCKED;

static void browser_busy(browser_t *browser);

/**
 * Readies an srun for a close.  The actual close will happen later.
 */
static void
srun_start_close(srun_conn_t *srun)
{
  srun_conn_t *prev = srun->prev;
  srun_conn_t *next = srun->next;

  if (srun->is_dead)
    return;

  srun->is_dead = 1;
      
  if (next)
    next->prev = prev;
      
  if (prev)
    prev->next = next;

  if (srun->parent->conn_head == srun)
    srun->parent->conn_head = next;

  srun->next = g_srun_close_list;
  g_srun_close_list = srun;
}

/**
 * Handles the callback when the socket to the srun changes state.
 * This will happen when the socket connects or when it closes.
 *
 * @param sock the socket between resind and the srun
 */
static void
srun_state_change(struct sock *sock)
{
  srun_conn_t *srun = sock->user_data;
  
  LOG(("resin: srun state_change %x %d %x\n", sock, sock->state, srun));

  // called when the connection to the srun completes
  if (sock->state == TCP_ESTABLISHED && srun) {
    browser_t *browser = srun->browser;
    
    if (browser && browser->state == RHC_SRUN_CONNECTING) {
      browser->state = RHC_SRUN_START;

      browser_wake(browser);
    }
  }
  // called when the srun connection closes
  else if (srun) {
    browser_t *browser = srun->browser;

    // if the srun is currently in a connection, close the connection
    if (! browser) {
    }
    else if (browser->state == RHC_SRUN_CONNECTING) {
      browser->state = RHC_BROWSER_QUIT;
      browser->srun_conn = 0;
      browser->srun_sock = 0;
      sock->user_data = 0;
      
      browser_wake(browser);
    }
    else {
      browser->state = RHC_BROWSER_QUIT;
      browser->srun_conn = 0;
      browser->srun_sock = 0;
      sock->user_data = 0;
      
      browser_busy(browser);
      browser_wake(browser);
    }
    
    // Remove the srun from the srun list
    srun_start_close(srun);
  }
}

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

  /* If the srun connection has data, mark it and wake the daemon. */
  if (browser && sock->state == TCP_ESTABLISHED) {
    if (browser->state >= RHC_SRUN_START &&
        browser->state <= RHC_SRUN_RECV_DATA) {
      browser_wake(browser);
    }
  }
}

/**
 * Connects to a srun.
 *
 * @param srun the srun to connect to
 * @param conn the client connection object
 *
 * @return 0 on success
 */
int
srun_connect(srun_t *srun, browser_t *browser)
{
  struct sockaddr_in sin;
  struct socket *sock;
  struct srun_conn_t *srun_conn;
  int error;

  LOG(("connect %p r:%p\n", browser->srun_sock, srun));

  // XXX: error?? temporary
  if (browser->srun_sock)
    return -1;

  browser->state = RHC_SRUN_CONNECTING;
  browser->sin_offset = 0;
  browser->sin_length = 0;

  // Use any available srun
  if (srun->conn_head) {
    struct srun_conn_t *next;
    
    srun_conn = srun->conn_head;
    next = srun_conn->next;
    
    if (next)
      next->prev = 0;
    srun->conn_head = next;
    
    srun_conn->prev = 0;
    srun_conn->next = 0;

    browser->srun_conn = srun_conn;
    srun_conn->browser = browser;
    browser->srun_sock = srun_conn->sock;
    
    browser->state = RHC_SRUN_START;

    // put the connection back in the queue
    browser_wake(browser);
    
    LOG(("reuse srun:%p next:%p\n", srun_conn, srun->conn_head));

    return 1;
  }

  error = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
  if (error < 0) {
    LOG(("error %d\n", error));
    return error;
  }

  sin.sin_family = AF_INET;
  sin.sin_addr = srun->addr;
  sin.sin_port = htons(srun->port);

  LOG(("resin: connect(%u.%u.%u.%u:%d)\n",
       NIPQUAD(sin.sin_addr.s_addr),
       ntohs(sin.sin_port)));
  
  srun_conn = kmalloc(sizeof(srun_conn_t), GFP_KERNEL);
  memset(srun_conn, 0, sizeof(srun_conn_t));

  srun_conn->parent = srun;
  srun_conn->browser = browser;
  browser->srun_conn = srun_conn;
  browser->srun_sock = sock;

  srun_conn->sock = sock;

  if (sock->sk) {
    sock->sk->user_data = srun_conn;
    sock->sk->state_change = srun_state_change;
    sock->sk->data_ready = srun_data_ready;
  }
  else {
    LOG(("no sk\n"));
  }
    
  error = sock->ops->connect(sock, (struct sockaddr *) &sin, sizeof(sin),
                             O_NONBLOCK);

  LOG(("resin: connect(%u.%u.%u.%u:%d) -> %d\n",
       NIPQUAD(sin.sin_addr.s_addr),
       ntohs(sin.sin_port),
       error));

  if ((error >= 0 || error == -EAGAIN || error == -EINPROGRESS) &&
      sock->sk && sock->sk->user_data) {
    // attach the new srun to the current connection
    
    // If the connection completed, change the state to SRUN_START
    if (error >= 0) {
      browser->state = RHC_SRUN_START;
      
      // put the connection back in the queue
      browser_wake(browser);
    }
  }
  else {
    browser->state = RHC_BROWSER_QUIT;
    sock_release(sock);
    browser_busy(browser);
    browser_wake(browser);
  }

  return 1;
}

static void
browser_busy(browser_t *browser)
{
  char buf[128];

  browser->next_state = browser->state;
  browser->state = RHC_BROWSER_WRITE;

  format_now(buf);
    
  browser_printf(browser, "HTTP/1.0 503 Server Busy\r\n");
  browser_printf(browser, "Connection: close\r\n");
  browser_printf(browser, "Date: %s\r\n", buf);
  browser_printf(browser, "Server: Resin-HardCore\r\n");
  browser_printf(browser, "\r\n");
  browser_printf(browser, "<title>Server Busy</title>\n");
  browser_printf(browser, "<h1>503 Server Busy</h1>\n");
  browser_printf(browser, "The server is busy or currently unavailable.\n");
  browser_printf(browser, "Please try again later.\n");
  browser_printf(browser, "<hr>\n");
  browser_printf(browser, "<em>Resin-Hardcore/2.0</em>\n");
}

/**
 * Write data to the srun
 *
 * @param conn the connection object which is sending
 */
static int
socket_send(struct sock *sock, char *buf, int len)
{
  mm_segment_t oldfs;
  struct msghdr msg;
  struct iovec iov;

  if (! sock)
    return -1;
  
  if (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;
  msg.msg_flags	= MSG_DONTWAIT|MSG_NOSIGNAL;
  
  iov.iov_base = buf;
  iov.iov_len = len;

  LOG(("sending %p %p %d %p\n", sock, buf, len,
       sock->prot ? (char *) sock->prot->sendmsg : (char *) sock->prot));

  // these are the killer
  oldfs = get_fs(); set_fs(KERNEL_DS);
  len = sock->prot->sendmsg(sock, &msg, len);
  set_fs(oldfs);

  LOG(("post send %d\n", len));
  
  return len;
}

/**
 * Write data to the srun
 *
 * @param browser The browser connection
 */
void
srun_send(browser_t *browser)
{
  struct socket *sock = browser->srun_sock;
  int offset = browser->sout_offset;
  int len = browser->sout_length;
  char *buf = browser->sout_buf + offset;

  if (! sock)
    return;
  
  if (offset >= len) {
    browser->state = browser->next_state;
    return;
  }

  len = socket_send(sock->sk, buf, len - offset);

  if (len < 0)
    LOG(("send error %d\n", len)); // XXX: no space is possible
  else {
    LOG(("srun sent %d\n", len));
    browser->sout_offset += len;
    if (browser->sout_offset >= browser->sout_length)
      browser->state = browser->next_state;
  }
}

/**
 * Reads from the client socket into the srun in buffer (sin_*)
 *
 * @param browser The browser connection
 *
 * @return number of bytes read
 */
int
srun_recv(browser_t *browser)
{
  int len;
  
  if (browser->sin_offset >= browser->sin_length) {
    browser->sin_offset = 0;
    browser->sin_length = 0;
  }

  len = sizeof(browser->sin_buf) - browser->sin_length;
  if (len < 0)
    return len;

  LOG(("recv off:%d len:%d\n", browser->sin_offset, browser->sin_length));

  len = socket_recv(browser->srun_sock->sk,
                    browser->sin_buf + browser->sin_length,
                    len);

  if (len > 0)
    browser->sin_length += len;

  return len;
}

/**
 * Recycles the srun structure so another connection can reuse it.
 */
void
srun_recycle(srun_conn_t *srun_conn)
{
  srun_t *srun = srun_conn->parent;

  if (! srun->is_live) {
    srun_start_close(srun_conn);
    return;
  }

  srun_conn->browser = 0;
  srun_conn->next = srun->conn_head;
  srun_conn->prev = 0;
  
  if (srun->conn_head)
    srun->conn_head->prev = srun_conn;
  
  srun->conn_head = srun_conn;
    
  LOG(("recycle srun:%p\n", srun));
}

/**
 * Closes the srun connection.
 *
 * @param srun_conn the srun connection
 */
static void
srun_conn_close(srun_conn_t *srun_conn)
{
  if (srun_conn->browser) {
    browser_t *browser = srun_conn->browser;
    srun_conn->browser = 0;
    browser->srun_conn = 0;
    browser_close(browser);
  }
  
  if (srun_conn->sock) {
    struct socket *socket = srun_conn->sock;

    if (socket->sk)
      socket->sk->user_data = 0;
      
    srun_conn->sock = 0;
    
    if (socket)
      sock_release(socket);
  }

  kfree(srun_conn);
}

/**
 * Adds a new srun.
 */
void
srun_create(resin_t *resin, int index, int ipv4, int port)
{
  srun_t *srun;

  // XXX: lock

  srun = &resin->srun_list[index];

  if (srun->is_live)
    return;

  LOG(("init: index:%d ip:%p port:%d\n", index, ipv4, port));

  if (index >= resin->srun_count)
    srun_set_count(resin, index + 1);
  
  srun->resin = resin;
  srun->addr.s_addr = htonl(ipv4);
  srun->port = port;
  srun->is_backup = 0;
  
  srun->session_index = index;
  
  srun->conn_head = 0;
  srun->is_live = 1;
}

/**
 * Initialize the srun
 */
void
srun_init(resin_t *resin)
{
  srun_create(resin, 0, IP(127, 0, 0, 1), 6802);
}

/**
 * Cleans up any sruns awaiting close.  Always called from the resind thread.
 */
void
srun_execute(resin_t *resin)
{
  while (g_srun_close_list) {
    srun_conn_t *srun_conn;
    
    spin_lock_irq(&g_srun_lock);
    
    srun_conn = g_srun_close_list;
    g_srun_close_list = srun_conn->next;
    
    spin_unlock_irq(&g_srun_lock);

    srun_conn_close(srun_conn);
  }
}

/**
 * Closes the srun
 */
void
srun_close(srun_t *srun)
{
  spin_lock_irq(&g_srun_lock);
  
  if (! srun || ! srun->is_live) {
    spin_unlock_irq(&g_srun_lock);
    return;
  }

  srun->is_live = 0;
  
  while (srun->conn_head)
    srun_start_close(srun->conn_head);
    
  spin_unlock_irq(&g_srun_lock);
}

/**
 * Sets the maximum number of sruns
 */
void
srun_set_count(resin_t *resin, int count)
{
  int i;

  for (i = count; i < resin->srun_count; i++)
    srun_close(&resin->srun_list[i]);
  
  for (i = resin->srun_count; i < count; i++) {
    resin->srun_list[i].resin = resin;
    resin->srun_list[i].is_live = 0;
  }

  resin->srun_count = count;
}

/**
 * Cleans up all sruns awaiting close.  Called only from resind
 */
void
srun_cleanup(resin_t *resin)
{
  int i;

  LOG(("closing srun\n"));
      
  for (i = 0; i < resin->srun_count; i++)
    srun_close(&resin->srun_list[i]);
  
  srun_execute(resin);
  
  LOG(("closed srun\n"));
}
