/*
 * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.servlets;

import com.caucho.util.L10N;
import com.caucho.util.Alarm;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.ReadWritePair;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;
import com.caucho.config.types.*;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * TCP connection pool for the HTTP proxy
 */
public class TcpPool {
  static protected final Logger log =
    Logger.getLogger(TcpPool.class.getName());
  static final L10N L = new L10N(TcpPool.class);

  private ArrayList<String> _hosts = new ArrayList<String>();
  private Server []_servers;
  private int _roundRobin;
  private long _failRecoverTime = 30 * 1000L;

  /**
   * Adds an address
   */
  public void addAddress(String address)
  {
    _hosts.add(address);
  }

  /**
   * Adds a host
   */
  public void addHost(String host)
  {
    _hosts.add(host);
  }

  /**
   * Sets the fail recover time.
   */
  public void setFailRecoverTime(Period period)
  {
    _failRecoverTime = period.getPeriod();
  }

  public int getServerCount()
  {
    return _hosts.size();
  }

  /**
   * Initialize the servlet with the server's sruns.
   */
  public void init()
    throws ServletException
  {
    if (_hosts.size() == 0)
      throw new ServletException(L.l("HttpProxyServlet needs at least one host."));

    _servers = new Server[_hosts.size()];

    for (int i = 0; i < _hosts.size(); i++) {
      String host = _hosts.get(i);

      _servers[i] = new Server(host);
    }
  }

  /**
   * Handle the request.
   */
  public Server nextServer()
  {
    synchronized (this) {
      long now = Alarm.getCurrentTime();
      
      int startIndex = _roundRobin;
      _roundRobin = (_roundRobin + 1) % _servers.length;

      int bestCost = Integer.MAX_VALUE;
      Server bestServer = null;

      for (int i = 0; i < _servers.length; i++) {
	int index = (startIndex + i) % _servers.length;

	Server server = _servers[index];

	if (_failRecoverTime < now - server.getLastFailTime()
	    && server.getConnectionCount() < bestCost) {
	  bestServer = server;
	  bestCost = server.getConnectionCount();
	}
      }

      if (bestServer != null)
	return bestServer;
    
      int index = _roundRobin;
      _roundRobin = (_roundRobin + 1) % _servers.length;

      return _servers[index];
    }
  }

  class Server {
    private Path _path;
    private long _lastFailTime;

    private int _connectionCount;

    Server(String host)
    {
      if (host.startsWith("http"))
	_path = Vfs.lookup(host);
      else
	_path = Vfs.lookup("http://" + host);
    }

    String getURL()
    {
      return _path.getURL();
    }

    ReadWritePair open(String uri)
      throws IOException
    {
      try {
	Path path = _path.lookup(uri);

	ReadWritePair pair = path.openReadWrite();

	if (pair != null) {
	  synchronized (this) {
	    _connectionCount++;
	  }
	}

	return pair;
      } catch (IOException e) {
	fail();

	throw e;
      }
    }

    long getLastFailTime()
    {
      return _lastFailTime;
    }

    int getConnectionCount()
    {
      return _connectionCount;
    }

    void fail()
    {
      _lastFailTime = Alarm.getCurrentTime();
    }

    void close()
    {
      synchronized (this) {
	_connectionCount--;
      }
    }
  }
}
