/*
 * Copyright (c) 1999 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 Public 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.
 *
 * @author Scott Ferguson
 *
 * $Id: mod_caucho.c,v 1.18 1999/12/02 17:02:03 ferg Exp $
 */

#include "cse.h"

/**
 * Decodes the first 3 characters of the session to see which
 * JVM owns it.
 */
static int
decode_session(char *session)
{
  int value = 0;
  int i;

  for (i = 2; i >= 0; i--) {
    int code = session[i];

    if (code >= 'a' && code <= 'z')
      value = 64 * value + code - 'a';
    else if (code >= 'A' && code <= 'Z')
      value = 64 * value + code - 'A' + 26;
    else if (code >= '0' && code <= '9')
      value = 64 * value + code - 'A' + 52;
    else if (code == '_')
      value = 64 * value + 62;
    else if (code == '/')
      value = 64 * value + 63;
    else
      return -1;
  }

  if (i > -1)
    return -1;
  else
    return value;
}

int
cse_session_from_string(char *source, char *cookie)
{
  char *match = strstr(source, cookie);
  
  if (match)
    return decode_session(match + strlen(cookie));

  return -1;
}

/**
 * Adds a new host to the configuration
 */
void
cse_add_host_int(config_t *config, const char *hostname,
		 int port, int is_backup)
{
  struct hostent *hostent;
  srun_t *srun;

  if (config->srun_size == 1 && ! config->srun_list[0].hostname)
    config->srun_size = 0;

  // Resize if too many hosts.
  if (config->srun_size >= config->srun_capacity) {
    int capacity = config->srun_capacity;
    srun_t *srun_list;

    if (capacity == 0)
      capacity = 4;

    srun_list = (srun_t *) cse_alloc(config,
				     2 * capacity * sizeof(srun_t));
    memset(srun_list, 0, 2 * capacity * sizeof(srun_t));
    if (config->srun_list)
      memcpy(srun_list, config->srun_list, capacity * sizeof(srun_t));
    config->srun_capacity = 2 * capacity;
    cse_free(config, config->srun_list);
    config->srun_list = srun_list;
  }

  hostent = gethostbyname(hostname);
  if (hostent && hostent->h_addr) {
    srun = &config->srun_list[config->srun_size];

    srun->hostname = cse_strdup(config, hostname);
    srun->host = (struct in_addr *) cse_alloc(config, sizeof (struct in_addr));
    memcpy(srun->host, hostent->h_addr, sizeof(struct in_addr));
    srun->port = port;
    srun->is_backup = is_backup;
    srun->open_sock = -1;
    srun->sock = -1;

    /*
     * XXX: for dynamic srun
     * srun->session = cse_query_session(config, srun, config->pool);
     */
    srun->session = config->srun_size;

    config->srun_size++;
  }
  else {
    cse_error(config, "CSE cannot find host %s\n", hostname);
  }
}

/**
 * Select a client JVM randomly.
 */

static int
random_host(char *ip, int requestTime)
{
  int host = time * 23 + g_count++;

  for (; *ip; ip++) {
    host = 23 * host + *ip;
  }

  if (host < 0)
    return -host;
  else
    return host;
}

static void
cse_close(stream_t *s, char *msg)
{
  LOG("close %d %s\n", s->socket, msg);

  close(s->socket);
  if (s->srun) {
    s->srun->open_sock = -1;
    s->srun->sock = -1;
  }
  s->socket = -1;
}

static int
cse_reuse(stream_t *s, config_t *config, srun_t *srun, int request_time)
{
  memset(s, 0, sizeof(*s));
  s->pool = r->pool;
  s->config = config;
  s->write_length = 0;
  s->read_length = 0;
  s->read_offset = 0;
  s->socket = srun->sock;
  s->srun = srun;

  srun->is_dead = 0;
  srun->open_sock = srun->sock;
  srun->sock = -1;
  srun->last_time = request_time;
  LOG("reopen %d\n", s->socket);
}

static void
cse_recycle(stream_t *s)
{
  if (g_srun_keepalive) {
    if (s->srun && s->socket >= 0) {
      s->srun->sock = s->socket;
      s->srun->open_sock = -1;

      LOG("reuse %d\n", s->srun->sock);
    }
    else if (s->srun && s->srun->open_sock >= 0) {
      LOG("close2 %d\n", s->srun->open_sock);

      close(s->srun->open_sock);
      s->srun->open_sock = -1;
    }
  }
  else {
    if (s->socket >= 0)
      close(s->socket);
    if (s->srun)
      s->srun->open_sock = -1;
  }
}

static int
open_connection(stream_t *s, config_t *config,
		int sessionIndex, char *ip, int requestTime)
{
  int sessionIndex;
  int size;
  int i;
  int host;
  int incr;

  size = config->srun_size;
  if (size < 1)
    size = 1;

  if (sessionIndex < 0) {
    host = random_host(ip, requestTime) % size;
  }
  else {
    for (host = 0; host < size; host++) {
      if (config->srun_list[host].session == sessionIndex)
	break;
    }
    if (host == size) {
      host = random_host(ip, requestTime) % size;
    }
  }
  
  if (host < 0)
    host = -host;
  
  incr = 2 * (host & 1) - 1;

  // XXX: if the session belongs to a host, we should retry for a few seconds?

  for (i = 0; i < size; i++) {
    srun_t *srun = config->srun_list + (host + i * (incr + size)) % size;

    if (sessionIndex < 0 && srun->is_backup) {
    }
    else if (srun->sock > 0 && srun->last_time + LIVE_TIME >= r->request_time) {
      cse_reuse(s, config, srun, r);
      return 1;
    }
    else if (srun->is_dead && size > 0 &&
	     srun->last_time + DEAD_TIME >= r->request_time &&
	     config->srun_size > 1) {
    }
    else if (cse_open(s, config, srun, r->pool, r->server)) {
      s->srun = srun;
      srun->is_dead = 0;
      srun->last_time = r->request_time;
      return 1;
    }
    else {
      srun->is_dead = 1;
      srun->last_time = r->request_time;
    }
  }

  // Okay, the primaries failed.  So try the secondaries.
  for (i = 0; i < size; i++) {
    srun_t *srun = config->srun_list + (host + i * (incr + size)) % size;

    if (srun->sock > 0 && srun->last_time + LIVE_TIME >= r->request_time) {
      cse_reuse(s, config, srun, r);
      return 1;
    }
    else if (cse_open(s, config, srun, r->pool, r->server)) {
      s->srun = srun;
      srun->is_dead = 0;
      srun->last_time = r->request_time;
      return 1;
    }
    else {
      srun->is_dead = 1;
      srun->last_time = r->request_time;
    }
  }

  connection_error(config, config->srun_list, r);
  return -1;
}
