/*
 * 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/proc_fs.h>
#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#include <net/ip.h>

#include "hardcore.h"

typedef struct entry_t {
  char buf[256];
  char *url;

  int use_count;
  int expires;

  struct file *file;
  int length;

  char is_live;
  char is_dead;
} entry_t;

#define CACHE_SIZE 128

static int g_count;
static struct entry_t g_cache[CACHE_SIZE];

static struct dentry *lookup_create(struct nameidata *nd, int is_dir)
{
	struct dentry *dentry;

	down(&nd->dentry->d_inode->i_sem);
	dentry = ERR_PTR(-EEXIST);
	if (nd->last_type != LAST_NORM)
		goto fail;
	dentry = lookup_hash(&nd->last, nd->dentry);
	if (IS_ERR(dentry))
		goto fail;
	if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode)
		goto enoent;
	return dentry;
enoent:
	dput(dentry);
	dentry = ERR_PTR(-ENOENT);
fail:
	return dentry;
}

int
cache_mkdir(const char *pathname, int mode)
{
  int error = 0;
  struct dentry *dir;
  struct nameidata nd;
  
  lock_kernel();
  if (path_init(pathname, LOOKUP_PARENT, &nd))
    error = path_walk(pathname, &nd);
  if (error)
    goto out;
  dir = lookup_create(&nd, 1);
  error = PTR_ERR(dir);
  if (!IS_ERR(dir)) {
    error = vfs_mkdir(nd.dentry->d_inode, dir, mode);
    dput(dir);
  }
  up(&nd.dentry->d_inode->i_sem);
  path_release(&nd);
out:
  unlock_kernel();

  return error;
}

/**
 * Callback from the readdir routine for removing all the files in the
 * cache directory.
 *
 * @param vdir our parameter storing the directory file struct
 * @param name the name of the listed file
 * @param namlen the length of the name
 * @param offset ??
 * @param ino inode of the file
 */
static int
remove_file(void *vdir, const char *name, int namlen,
            off_t offset, ino_t ino)
{
  struct file *dir = (struct file *) vdir;
  struct dentry *file;
  struct inode *inode = dir->f_dentry->d_inode;
  struct qstr qstr;

  if (name[0] == '.')
    return 0;
  
  qstr.name = name;
  qstr.len = namlen;
  qstr.hash = full_name_hash(name, namlen);

  LOG(("remove %s\n", name));
  
  file = d_lookup(dir->f_dentry, &qstr);

  if (file && inode)
    vfs_unlink(inode, file);

  if (file)
    dput(file);
  
  return 0;
}

static entry_t *
cache_get(char *url)
{
  int hash = 37;
  int ch;
  int i;

  for (i = 0; (ch = url[i]); i++)
    hash = 65521 * hash + ch * 29;

  hash &= 0x7fffffff;

  for (i = 5; i > 0; i--) {
    hash %= CACHE_SIZE;

    if (! g_cache[hash].url)
      return &g_cache[hash];
    else if (! strcmp(g_cache[hash].url, url))
      return &g_cache[hash];

    hash++;
  }

  return 0;
}

/**
 * Remove a cache entry
 */
static void
cache_remove(entry_t *cache)
{
  LOG(("cache remove %s\n", cache->url));
  
  cache->is_live = 0;
  cache->url = 0;
  
  /*
   * Don't remove because we'll reuse it.
  cache->file = 0;

  if (file) {
    // XXX: unlink, too
    filp_close(file, current->files);
  }
  */
}

/**
 * Remove a cache entry
 */
static void
cache_truncate(entry_t *cache)
{
  struct file *file = cache->file;
  struct inode *inode = file->f_dentry->d_inode;

  LOG(("cache truncate %s %p\n", cache->url ? cache->url : "null", inode));
  
  cache->is_live = 0;
  cache->url = 0;

  if (inode)
    inode->i_op->truncate(inode);
  
  LOG(("post truncate\n"));
}

int
cache_start(browser_t *browser)
{
  char buf[256];
  struct file *file = 0;
  struct nameidata nd;
  int flag;
  int err;
  entry_t *cache = cache_get(browser->url);

  LOG(("cache start %s %p\n", browser->url, cache));

  // If already have an entry, don't cache
  // XXX: really need to do an LRU here
  if (! cache)
    return 0;
  else if (! cache->url && cache->file) {
    // Truncate and return the cache
    cache_truncate(cache);
    
    cache->file->f_pos = 0;
    cache->is_live = 0;
    cache->url = cache->buf;
    strcpy(cache->url, browser->url);

    browser->cache = cache;
    cache->length = 0;
      
    return 1;
  }
  else if (! cache->url) {
  }
  else if (cache->expires < browser->now) {
    // Truncate and return the cache
    cache_truncate(cache);
    
    cache->file->f_pos = 0;
    cache->is_live = 0;
    cache->url = cache->buf;
    strcpy(cache->url, browser->url);

    browser->cache = cache;
    cache->length = 0;
      
    return 1;
  }
  else
    return 0;

  sprintf(buf, "/usr/tmp/caucho/cache/t%d.tmp", g_count++);
  
  flag = O_CREAT|0x3;
  
  // err = open_namei(buf, flag, 0664, &nd);
  err = -1;
  if (err < 0) {
    LOG(("can't open 0\n"));
    return 0;
  }
  
  file = dentry_open(nd.dentry, nd.mnt, flag);
  if (! file) {
    LOG(("can't open\n"));
    return 0;
  }
    
  cache->is_live = 0;
  cache->url = cache->buf;
  strcpy(cache->url, browser->url);

  browser->cache = cache;
  cache->file = file;
  cache->length = 0;
      
  LOG(("caching %s!\n", cache->url));
    
  return 1;
}

/**
 * Writes data to the cache.
 */
void
cache_write(browser_t *browser, char *data, int len)
{
  mm_segment_t oldfs;
  int write_len;
  struct file *file = browser->cache->file;
  
  LOG(("writing\n"));
  
  oldfs = get_fs(); set_fs(KERNEL_DS);
  write_len = file->f_op->write(file, data, len, &file->f_pos);
  set_fs(oldfs);

  LOG(("write %d -> %d\n", len, write_len));
}

/**
 * Kill the caching on this file because of some error during
 * the request.
 */
void
cache_kill(browser_t *browser)
{
  entry_t *cache = browser->cache;
  struct file *file = cache->file;

  LOG(("kill %s\n", browser->url));
  
  cache->file = 0;
  cache->url = 0;
  cache->is_live = 0;
  browser->cache = 0;
  
  filp_close(file, current->files);
  // XXX: needs unlink too
}

/**
 * Finish loading of a cache.
 */
void
cache_finish(browser_t *browser)
{
  entry_t *cache = browser->cache;
  struct file *file;

  if (! cache)
    return;

  browser->cache = 0;
  
  file = cache->file;
  
  cache->length = file->f_pos;
  cache->expires = browser->expires;

  cache->is_live = 1;
  
  LOG(("finished %s %p %d %p\n",
       cache->url, file, cache->length, browser));
}

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

  return off;
}

int
cache_lookup(browser_t *browser)
{
  entry_t *cache = cache_get(browser->url);

  if (! cache) {
    LOG(("no cache lookup %s\n", browser->url));
    return 0;
  }
  else if (! cache->is_live) {
    LOG(("not live %s %x\n", browser->url, cache->url));
    return 0;
  }
  else if (browser->now < cache->expires) {
    int len = 0;
    char *buf = browser->cout_buf;
    
    LOG(("cache lookup %s\n", browser->url));
  
    browser->cache = cache;
    browser->state = RHC_CACHE_WRITE;
    browser->cache_offset = 0;

    len = cse_write(buf, len, "HTTP/1.0 200 OK\r\n");
    len = cse_write(buf, len, "Connection: close\r\n");
    len = cse_write(buf, len, "Server: ResinHardCore/1.3\r\n");
    len += sprintf(buf + len, "Content-Length: %d\r\nDate: ", cache->length);
    len += format_now(buf + len);
    len = cse_write(buf, len, "\r\n\r\n");

    browser->cout_length = len;

    cache->use_count++;
    
    if (cache_read(browser))
      browser_wake(browser);
    
    return 1;
  }
  else {
    cache_remove(cache);
    
    return 0;
  }
}

int
cache_read(browser_t *browser)
{
  entry_t *cache = browser->cache;
  struct file *file = cache->file;
  int len;
  mm_segment_t oldfs;
  loff_t offset = browser->cache_offset;
  char *buf;

  oldfs = get_fs(); set_fs(KERNEL_DS);
  buf = browser->cout_buf + browser->cout_length;
  len = 4096 - browser->cout_length;
  if (len > cache->length - browser->cache_offset)
    len =  cache->length - browser->cache_offset;
  len = file->f_op->read(file, buf, len, &offset);
  set_fs(oldfs);
  
  if (len > 0) {
    LOG(("cache read %d live %d\n", len, cache->is_live));

    browser->cache_offset += len;

    if (browser->cache_offset >= cache->length) {
      browser->write_state = RHC_BROWSER_QUIT;
      browser->cache = 0;
    }
    else
      browser->write_state = browser->state;
    
    browser->state = RHC_BROWSER_WRITE;
      
    browser->iov[0].iov_base = browser->cout_buf;
    browser->iov[0].iov_len = browser->cout_length + len;
    browser->iov_index = 1;

    browser->cout_length = 0;
    
    return browser_write_iov(browser) > 0;
  }
  else {
    // done?
    LOG(("browser done len:%d live:%d?\n", len, cache->is_live));
    browser->cache = 0;
    browser->state = RHC_BROWSER_QUIT;
    return 1;
  }
}

static void
cache_clear()
{
  struct file *dir = filp_open("/usr/tmp/caucho/cache", O_RDONLY, 0777);
  int error;

  if (! dir)
    return;

  down(&dir->f_dentry->d_inode->i_sem);
  error = dir->f_op->readdir(dir, dir, remove_file);
  up(&dir->f_dentry->d_inode->i_sem);

  filp_close(dir, current->files);
}

int
cache_init()
{
  LOG(("cache init\n"));

  lock_kernel();

  cache_mkdir("/usr/tmp/caucho", 0777);
  cache_mkdir("/usr/tmp/caucho/cache", 0777);

  cache_clear();

  unlock_kernel();

  return 1;
}

/**
 * Cleanup the cache module on module shutdown.
 */
void
cache_cleanup()
{
  int i;
  
  for (i = 0; i < CACHE_SIZE; i++) {
    entry_t *cache = &g_cache[i];
    struct file *file = cache->file;

    // XXX: unlink?
    if (file)
      filp_close(file, current->files);
  }
}
