/*
 * 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/socket.h>
#include <linux/proc_fs.h>
#include <linux/in.h>

#include "hardcore.h"
#include "../common/srun.h"

/**
 * Writes a string to the buffer.
 */
static int
write_string(char *buf, int offset, int code, char *string)
{
  int i = offset;
  int len = 0;
  int ch;

  if (! string)
    return offset;

  // XXX: check total length
  buf[i] = code;
  i += 4;

  for (len = 0; (ch = string[len]); len++)
    buf[i++] = ch;

  buf[offset + 1] = len >> 16;
  buf[offset + 2] = len >> 8;
  buf[offset + 3] = len;

  return i;
}

/**
 * Writes a code packed to the buffer.
 */
static int
write_packet(char *buf, int offset, int code, char *data, int len)
{
  // XXX: check total length
  buf[offset] = code;
  buf[offset + 1] = len >> 16;
  buf[offset + 2] = len >> 8;
  buf[offset + 3] = len;

  memcpy(buf + offset + 4, data, len);

  return offset + 4 + len;
}

/**
 * Writes a code packed to the buffer.
 */
static int
write_code(char *buf, int offset, int code)
{
  // XXX: check total length
  buf[offset] = code;
  buf[offset + 1] = 0;
  buf[offset + 2] = 0;
  buf[offset + 3] = 0;

  return offset + 4;
}

/**
 * Sends a POST continuation to the servlet runner.
 *
 * Called from either RHC_SRUN_START or RHC_SRUN_POST
 */
void
srun_send_post(browser_t *browser)
{
  int len = browser->request_length;
  
  int sublen = browser->cin_length - browser->cin_offset;
  char *data = browser->cin_buf + browser->cin_offset;
  int offset = 0;

  browser->next_state = browser->state;

  if (len > 0) {
    LOG(("sp post cl:%d cin:%d\n", len, sublen));

    if (sublen == 0) {
      browser->state = RHC_SRUN_POST;
      browser_wake(browser);
      return;
    }
    
    if (sublen > len)
      sublen = len;

    if (sublen > 4096 - 8)
      sublen = 4096 - 8;
      
    offset = write_packet(browser->sout_buf, offset, CSE_DATA, data, sublen);

    len -= sublen;

    browser->request_length = len;
    browser->cin_offset += sublen;
  }
  
  if (len <= 0) {
    LOG(("sp end\n"));
    offset = write_code(browser->sout_buf, offset, CSE_END);
  }
  
  browser->sout_offset = 0;
  browser->sout_length = offset;

  browser->state = RHC_SRUN_SEND;
  browser_wake(browser);

  return;
}

/**
 * Sends the request to the servlet runner.
 *
 * Called from either RHC_SRUN_START or RHC_SRUN_POST
 */
int
resin_srun(browser_t *browser)
{
  char *buf = browser->sout_buf;
  int offset = 0;
  int i;

  browser->in_header = 0;

  LOG(("srun %s\n", browser->url));

  offset = write_string(buf, offset, CSE_METHOD, browser->method);
  offset = write_string(buf, offset, CSE_URI, browser->url);
  offset = write_string(buf, offset, CSE_PROTOCOL, browser->protocol);

  for (i = 0; i < browser->header_count; i++) {
    offset = write_string(buf, offset, CSE_HEADER, browser->key[i]);
    offset = write_string(buf, offset, CSE_VALUE, browser->value[i]);
  }

  if (*browser->method == 'P' && browser->request_length >= 0) {
    int len = browser->request_length;
    browser->post_offset = browser->cin_length;

    LOG(("post %s cl:%d cin:%d\n", browser->method, len,
         browser->cin_length - browser->cin_offset));
    
    if (len <= browser->cin_length - browser->cin_offset) {
      char *data = browser->cin_buf + browser->cin_offset;
      
      offset = write_packet(buf, offset, CSE_DATA, data, len);

      browser->request_length -= len;
      browser->cin_offset += len;
    }
    else {
      char *data = browser->cin_buf + browser->cin_offset;
      int sublen = browser->cin_length - browser->cin_offset;
      offset = write_packet(buf, offset, CSE_DATA, data, sublen);

      browser->request_length -= sublen;
      browser->cin_offset += sublen;
    }
  }

  if (browser->request_length > 0) {
    browser->next_state = RHC_SRUN_RECV_HEADER;
    browser->state = RHC_SRUN_SEND;
  } else {
    offset = write_code(buf, offset, CSE_END);

    browser->next_state = RHC_SRUN_RECV_HEADER;
    browser->state = RHC_SRUN_SEND;
  }
  
  browser->sout_offset = 0;
  browser->sout_length = offset;

  return 0;
}

/**
 * Writes a string to the buffer.
 */
static int
cse_write(char *buf, int off, char *str)
{
  while (*str)
    buf[off++] = *str++;

  return off;
}

/**
 * Proxies the headers from the srun to the browser.
 *
 * @return 1 if the browser should be rewoken
 */
int
srun_read_header(browser_t *browser)
{
  char *buf = browser->sin_buf;
  int off = browser->sin_offset;
  int len = browser->sin_length;
  int code;
  int packet_len = browser->packet_len;
  int state = browser->state;
  int next_state = browser->next_state;
  int i;
  char *out_buf = browser->cout_buf;
  int out_len = browser->cout_length;
  int is_done = 0;
  struct iovec *iov = browser->iov;
  int iov_index = 0;

  while (! is_done && off < len) {
    switch (state) {
    case RHC_SRUN_RECV_HEADER:
    case RHC_SRUN_BODY:
      // If don't have the 4 byte header, wait for more data
      if (len - off < 4) {
        if (off > 0) {
          for (i = len - off - 1; i >= 0; i--)
            buf[i] = buf[off + i];
          browser->sin_offset = 0;
          browser->sin_length = len - off;
        }
        else {
          browser->sin_offset = off;
          browser->sin_length = len;
        }
        browser->state = state;
        return 1;
      }
      
      code = buf[off];
      packet_len = (((buf[off + 1] & 0xff) << 16) + 
                    ((buf[off + 2] & 0xff) << 8) + 
                    ((buf[off + 3] & 0xff)));
      
      switch (code) {
      case CSE_STATUS:
        LOG(("status %d\n", packet_len));
        state = RHC_SRUN_RECV_STATUS;
        off += 4;
        break;
        
      case CSE_HEADER:
        // LOG(("header %d\n", packet_len));
        state = RHC_SRUN_RECV_KEY;
        off += 4;
        break;

      case CSE_VALUE:
        // LOG(("value %d\n", packet_len));
        state = RHC_SRUN_RECV_VALUE;
        off += 4;
        break;

      case CSE_SEND_HEADER:
        out_len = cse_write(out_buf, out_len, "Date: ");
        out_len += format_now(out_buf + out_len);
        
        out_buf[out_len++] = '\r';
        out_buf[out_len++] = '\n';
        
        out_buf[out_len++] = '\r';
        out_buf[out_len++] = '\n';
        
        LOG(("header %s %s\n",
             browser_state_name(browser),
             out_buf));

        // XXX: more caching
        if (browser->status == 200 && browser->now < browser->expires) {
          cache_start(browser);
        }

        LOG(("headersdone %s time-delta:%d\n",
             browser_state_name(browser),
             browser->now - browser->expires));
        state = RHC_SRUN_BODY;
        off += 4;
        iov[0].iov_base = out_buf;
        iov[0].iov_len = out_len;
        iov_index = 1;
        break;
        
      case CSE_DATA:
        LOG(("data %d\n", packet_len));
        off += 4;
        state = RHC_SRUN_RECV_DATA;
        break;
        
      case CSE_ACK:
        LOG(("ack %d\n", packet_len));
        off += 4;
        browser->state = state;
        browser->next_state = next_state;
        srun_send_post(browser);
        state = browser->state;
        next_state = browser->next_state;
        is_done = 1;
        break;
        
      case CSE_END:
      case CSE_CLOSE:
        LOG(("quit %d\n", packet_len));
        state = RHC_BROWSER_QUIT;
        off += 4;
        is_done = 1;

        if (browser->cache)
          cache_finish(browser);
        break;

      default:
        LOG(("skip %c %d\n", code, packet_len));
        off += 4; 
        if (packet_len > 0) {
          next_state = state;
          state = RHC_SRUN_RECV_SKIP;
        }
        break;
      }
      break;

    case RHC_SRUN_RECV_STATUS:
      out_len = 0;
      browser->cout_length = 0;
      browser->cout_offset = 0;

      // XXX: must restrict length
      for (; packet_len > 0 && off < len; packet_len--, off++)
        out_buf[out_len++] = buf[off];

      if (packet_len <= 0) {
        // HTTP/1.x nnn msg
        browser->status = (100 * (out_buf[9] - '0') +
                           10 * (out_buf[10] - '0') +
                           out_buf[11] - '0');
        out_buf[7] = '0'; // force HTTP/1.0

        out_buf[out_len++] = '\r';
        out_buf[out_len++] = '\n';

        out_len = cse_write(out_buf, out_len, "Connection: close\r\n");
        out_len = cse_write(out_buf, out_len, "Server: ResinHardCore/1.3\r\n");

        out_buf[out_len] = 0;
        
        state = RHC_SRUN_RECV_HEADER;
        
        browser->key_start = out_buf + out_len;
      }
      break;

    case RHC_SRUN_RECV_KEY:
      // XXX: must restrict length
      for (; packet_len > 0 && off < len; packet_len--, off++)
        out_buf[out_len++] = buf[off];

      if (packet_len <= 0) {
        out_buf[out_len++] = ':';
        out_buf[out_len++] = ' ';
        out_buf[out_len] = 0;
        
        // LOG(("key %s\n", browser->key_start));

        browser->value_start = out_buf + out_len;
        
        state = RHC_SRUN_RECV_HEADER;
      }
      break;

    case RHC_SRUN_RECV_VALUE:
      // XXX: must restrict length
      for (; packet_len > 0 && off < len; packet_len--, off++)
        out_buf[out_len++] = buf[off];

      if (packet_len <= 0) {
        out_buf[out_len++] = '\r';
        out_buf[out_len++] = '\n';
        out_buf[out_len] = 0;
        
        LOG(("%s", browser->key_start));

        if (! strnicmp(browser->key_start, "Expires:", 8)) {
          browser->expires = parse_date(browser->value_start);
          LOG(("Expires %d\n", browser->expires));
        }
        
        state = RHC_SRUN_RECV_HEADER;
        browser->key_start = out_buf + out_len;
      }
      break;

    case RHC_SRUN_RECV_DATA:
      LOG(("recv-data %d %d\n", packet_len, len - off));
      if (packet_len <= len - off) {
        iov[iov_index].iov_base = buf + off;
        iov[iov_index].iov_len = packet_len;
        off += packet_len;
        state = RHC_SRUN_BODY;

      }
      else {
        iov[iov_index].iov_base = buf + off;
        iov[iov_index].iov_len = len - off;
        packet_len -= len - off;
        off = len;
      }
      
      if (browser->cache)
        cache_write(browser, iov[iov_index].iov_base, iov[iov_index].iov_len);
      
      iov_index++;
      break;

    case RHC_SRUN_RECV_SKIP:
      if (packet_len < len - off) {
        off += packet_len;
        state = next_state;
      }
      else {
        packet_len -= len - off;
        off = len;
      }
      break;

    default:
      is_done = 1;
      break;
    }
  }

  LOG(("done %d %d\n", off, len));

  if (off < len) {
    browser->sin_offset = off;
    browser->sin_length = len;
  }
  else {
    browser->sin_offset = 0;
    browser->sin_length = 0;
  }
  browser->cout_length = out_len;
  browser->packet_len = packet_len;
  browser->state = state;
  browser->next_state = next_state;

  if (iov_index > 0) {
    browser->write_state = state;
    browser->state = RHC_BROWSER_WRITE;
    browser->iov_index = iov_index;
    
    return browser_write_iov(browser) > 0;
  }
  else
    return 1;
}
