package com.fuse.net;

/*
	FUSE Light Server - multiuser server application
	Copyright (C) 2000 Aapo Kyrola / Sulake Oy  Helsinki, Finland

	This program 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.

	This program 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.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/


import java.io.*;
import java.util.*;
import java.text.*;

import com.fuse.*;
import com.fuse.storage.*;
import com.fuse.storage.data_objects.*;


/**
  * FUSEConnectionManager acts as a FUSEConnection pool.
  * FUSEConnections are responsible of notifying the ConnectionManager
  * when they get closed and can be returned to the pool.
  * @author Aapo Kyrola
  */
public class FUSEConnectionManager extends Thread {
	
	protected Vector availableConnections;
	protected Vector busyConnections;
	protected int maxConnections, total = 0;
	
	protected final int MAXIMUM_ALLOWED_DELAY_BETWEEN_MSGS = 60000;	// 60 secs
	protected boolean closed = false;
	protected FUSEConnectionFactory factory;
	

	/** 
	  * Constructor
	  * @param maxConnections max number of simultaneous connections
	  * @param factory factory object which constructs new network connection handlers
	  */
	public FUSEConnectionManager(int maxConnections, FUSEConnectionFactory factory) {
		this.maxConnections = maxConnections;
		this.factory = factory;
		availableConnections = new Vector(maxConnections/2);
		busyConnections = new Vector(maxConnections/2);
		
	
		Log.status("Starting connection manager");
		this.start();
		this.setPriority(Thread.MIN_PRIORITY);
	}
	
	public void close() {
		this.closed = true;
	}
	
	public void open() {
		this.closed = false;
	}
	
	/**
	  * Returns the number of handled connections during connection managers
	  * life time
	  */
	public int getTotalOfHandledConnections() {
		return total;
	}
	
	/* Checks for dead connections */
	public void run() {
		while(true) {
			try {
				this.sleep(20000);
			} catch (InterruptedException ie) {
			}
			Vector clonedConnections;
			synchronized(busyConnections) {
				clonedConnections = (Vector) busyConnections.clone();
			}
			for(int i=0; i<clonedConnections.size();i++) {
				FUSEClientConnection conn = (FUSEClientConnection) clonedConnections.elementAt(i);
				if (System.currentTimeMillis()-conn.getLastConnectionTime() >= MAXIMUM_ALLOWED_DELAY_BETWEEN_MSGS) {
					Log.status("## Throwing a connection away!");
					conn.release();
				}
			}
			clonedConnections = null;
			writeLoadLog();
		}
	}
	
	/**
	  * Writes log of the number of people in each room
	  */
	private void writeLoadLog() {
		/* IMPLEMENTATION REMOVED FROM FuseLight */
	}
	

	
	/**
	  * Returns a free connection or throws NoConnectionsAvailableException
	  * if there are not any available
	  */
	 FUSEClientConnection getConnection() throws NoConnectionsAvailableException{
		if (closed == true) return null;
		if (availableConnections.size() > 0) {
			FUSEClientConnection conn = (FUSEClientConnection) availableConnections.lastElement();
			availableConnections.removeElementAt(availableConnections.size()-1);
			busyConnections.addElement(conn);
			synchronized(conn) {
				conn.notify();
			}
			total++;
			return conn;
		} else {
			if (busyConnections.size() < maxConnections) {
				FUSEClientConnection conn = factory.createNewConnection(this);
				busyConnections.addElement(conn);
				total++;
				
				return conn;
			} else {
				throw new NoConnectionsAvailableException();
			}
		}
		
	}
	
	
	/**
	  * Broadcasts message to all connections
	  * @param type type for data (such as "CHAT")
	  * @param data the actual data
	  */
	public void broadcastToAll(String type, String data) {
		synchronized(busyConnections) {
			FUSEClientConnection conn;
			for(int i=busyConnections.size()-1; i>=0; i--) {
				conn = (FUSEClientConnection) busyConnections.elementAt(i);
				conn.processFuseMessage(type, data);
			}
		}
	}
	
	/**
	  * Broadcasts message to a list of users
	  * @param users Vector of user names
	  * @param type type for data (such as "WHISPER")
	  * @param data the actual data
	  */
	public void broadcastToUsers(Vector users, String type, String data) {
		synchronized(busyConnections) {
			FUSEClientConnection conn;
			FUSEUser user;
			for(int i=busyConnections.size()-1; i>=0; i--) {
				conn = (FUSEClientConnection) busyConnections.elementAt(i);
				user = conn.getUser();
				if (user != null && users.contains(user.getName())) {
					conn.processFuseMessage(type, data);
				}
			}
		}
	}
	
	/**
	  * Returns connection for given username or NULL if not found
	  */
	public FUSEClientConnection getUserConnection(String username) {
		synchronized(busyConnections) {
			FUSEClientConnection conn;
			FUSEUser user;
			for(int i=busyConnections.size()-1; i>=0; i--) {
				conn = (FUSEClientConnection) busyConnections.elementAt(i);
				user = conn.getUser();
				if (user != null && user.getName().equals(username)) {
					return conn;
				}
			}
		}
		return null;
	}
	
	
	/**
	  * Called by a closed Connection
	  */
	public void releaseConnection(FUSEClientConnection conn) {
		Log.status("Release connection " + conn);
		busyConnections.removeElement(conn);
		conn = null;
	}
	
	public Vector getBusyConnections() {
		return busyConnections;
	}
	
	public int getMaxConnections() {
		return maxConnections;
	}
	
	public int getNumOfActiveConnections() {
		return busyConnections.size();
	}
	
	
}
