/*
 * Copyright (c) 1999-2000 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"

struct cache_t {
  pthread_mutex_t lock;
  
  int capacity;
  int mask;

  cache_entry_t **entries;
};

cache_t *
cache_create(int initial_capacity)
{
  int capacity;
  cache_t *cache;
  int size;
  
  for (capacity = 32; capacity < initial_capacity; capacity *= 2) {
  }

  cache = cse_malloc(sizeof(cache_t));
  
  memset(cache, 0, sizeof(cache_t));

  pthread_mutex_init(&cache->lock, 0);
  
  cache->capacity = capacity;
  cache->mask = capacity - 1;

  size = capacity * sizeof(cache_entry_t *);
  cache->entries = (cache_entry_t **) cse_malloc(size);
  memset(cache->entries, 0, size);

  return cache;
}

static cache_entry_t *
cache_entry_create(char *host, char *uri)
{
  cache_entry_t *entry;

  entry = cse_malloc(sizeof(cache_entry_t));
  
  memset(entry, 0, sizeof(cache_entry_t));

  entry->host = strdup(host);
  entry->uri = strdup(uri);
  entry->fd = -1;

  return entry;
  
}

static cache_entry_t *
cache_get(cache_t *cache, request_t *req, int *p_fill)
{
  int hash = 37;
  cache_entry_t *old_entry = 0;
  cache_entry_t *entry = 0;
  char *host = req->host;
  char *uri = req->uri;
  int i;

  if (! host)
    host = "";
  
  if (! uri)
    uri = "";

  for (i = 0; host[i]; i++)
    hash = 65521 * hash + host[i] + 19;
  
  for (i = 0; uri[i]; i++)
    hash = 65521 * hash + uri[i] + 19;

  hash = hash & cache->mask;

  pthread_mutex_lock(&cache->lock);
  entry = cache->entries[hash];
  
  if (! entry) {
    entry = cache_entry_create(host, uri);
    cache->entries[hash] = entry;
    entry->use_count++;
    entry->is_cacheable = 1;
  }
  else if (entry->host && ! strcmp(entry->host, host) &&
           entry->uri && ! strcmp(entry->uri, uri)) {
    if (entry->is_cacheable)
      entry->use_count++;
    else
      entry = 0;
  }
  else {
    old_entry = entry;
    old_entry->is_dead = 1;
    
    entry = cache_entry_create(host, uri);
    cache->entries[hash] = entry;
    entry->use_count++;
    entry->is_cacheable = 1;
  }
  pthread_mutex_unlock(&cache->lock);

  return entry;
}

int
cache_fill(cache_t *cache, request_t *req, response_t *res)
{
  int fill = 0;
  cache_entry_t *entry;

  entry = cache_get(cache, req, &fill);

  if (! entry || entry->is_dead)
    return 0;

  else if (entry->data) {
    cse_rprintf(res, "HTTP/1.0 200 OK");
    cse_rwrite(res, entry->headers, entry->header_size);
    cse_flush_headers(res, entry->length);

    cse_rwrite(res, entry->data, entry->length);

    return 1;
  }
  
  else if (entry->cache_file) {
    int fd;
    char buf[8192];
    char *data;
    int sublen;
    int len;
    
    fd = open(entry->cache_file, O_RDONLY);
    
    if (fd < 0) {
      entry->is_cacheable = 0;
      return 0;
    }

    cse_rprintf(res, "HTTP/1.0 200 OK");
    cse_rwrite(res, entry->headers, entry->header_size);
    cse_flush_headers(res, entry->length);
    
    entry->data = cse_malloc(entry->length);

    sublen = entry->length;
    data = entry->data;
    while (sublen > 0 && (len = read(fd, data, entry->length)) > 0) {
      cse_rwrite(res, data, len);
      sublen -= len;
      data += len;
    }
    close(fd);

    return 1;
  }
  else if (entry->is_cacheable)
    res->entry = entry;
  
  return 0;
}
