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.net.*;
import java.util.*;
import java.text.*;

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

/**
  * Handles the data connection between client and fuseserver for an
  * user. 
  * NOTE: From FUSE Light lots of functionality of this class has been
  * removed and moved to com.fuse.projects.fuse_light.net.FUSELightConnection
  * @author Aapo Kyrola
  */
public class FUSEClientConnection extends Thread {

	protected int id;
	protected Socket client;
	protected String myKey;
	
	protected boolean dead = false;
	

	/* Access log object */
	protected Access accessLogEntry = null;
	
	
	protected DataInputStream in;
	protected PrintWriter out;
	
	/* time of last message received */
	protected FUSEConnectionManager fcm;
	
	protected long lastMessage = 0, dieTime = -1;
	
	/* User associated to this connection */
	protected FUSEUser user = null;
	protected String clientIp = null;
	
	protected boolean gotOk = true;
	
	
	/* Connection id */
	protected static int n = 0;
	
	
	
	public FUSEClientConnection(FUSEConnectionManager fcm) {
		super();
		this.fcm = fcm;
		this.id = n++;
		this.setName("FUSEConnection " + id);
	}
	
	
	public int getId() {
		return id;
	}
	
	
	/**
	  * Takes accepted socket and starts handling the connection
	  */
	void handleConnection(Socket socket) {
		Log.status("Handling connection id= " + id);
		this.client = socket;
		try {
			in = new DataInputStream(client.getInputStream());
			out =new PrintWriter(client.getOutputStream(), true);
		} catch (IOException ioe) {
			release();
			Log.error(ioe);
		}
		
		/* Check if this IP-address is banned */
		if (FUSEEnvironment.getAccessControl().getIpBanned(client.getInetAddress().getHostAddress())) {
			Log.status("REFUSE address:" + client.getInetAddress().getHostAddress());
			
			/* You can specify your own error-message for banned users */
			generateError(-2000, FUSEEnvironment.getProperty("refuse.error_message", "Your connection was refused"));
				
			dieTime = System.currentTimeMillis() + 1200;	// just time the error message to get sent
		}
			
		this.lastMessage = System.currentTimeMillis();
		if (!this.isAlive()) {
			this.start();
		}
		
		/* Send key which the client should handle correctly and return before
		   the connection can be opened */
		myKey = FUSEEnvironment.getSecretKey().generateKey();
		processFuseMessage("SECRET_KEY", myKey);
	}
	
	
	public void run() {
		byte[] len = new byte[4];
		String line = new String();
		int length;
		while(dead == false) {
			if (client == null) {
				try {
					synchronized(this) {
						this.wait();
					}
				} catch (InterruptedException ie) {;}
				continue;
			}
			
			try {
				in.readFully(len);
				try {
					length = Integer.parseInt(new String(len).trim());
				} catch (NumberFormatException nfe) {
					continue;
				}
				byte[] dataChunk = new byte[length];
				in.readFully(dataChunk);
				String data = new String(dataChunk);
			
				if (dieTime == -1) {
					handleClientMessage(data);
				} else if (System.currentTimeMillis() > dieTime) {
					release();
				}
				
				this.sleep(20);
			} catch (InterruptedException ie) {
				Log.error(ie);	
			} catch (EOFException eo) {
				Log.error("EOF Exception");
				release();
			} catch (IOException ioe) {
				Log.error(ioe);
				release();
			}
		}
	}
	
	/**
	  * Called by connectionManager to check whether 
	  * the connection should be timeouted 
	  */
	protected long getLastConnectionTime() {
		return lastMessage;
	}
	
	/**
	  * Returns the ip of this connection as String
	  */
	public String getClientIp() {
		return client.getInetAddress().getHostAddress();
	}
	
	
	/**
	  * Kills this connection (throws user out)
	  */
	public synchronized void release() {
		/* Broadcast logoff to other clients */
		this.user=null;
		releaseConnection();
	}
	
	protected void releaseConnection() {
		
		try {
			if (client != null) client.close();
		} catch (IOException ioe) {
			Log.error(ioe);
		}
		if (this.dead == true) return;
		try {
			if (accessLogEntry != null) {
				accessLogEntry.setLogoutTime(new Date());
				FUSEDatabase db = FUSEEnvironment.getDatabaseProxy().getDatabase();
				db.insert(accessLogEntry);
				accessLogEntry = null;
			}
		} catch (Exception e) {
			Log.error(e);
		}
		
		client =  null;
		this.user=null;
		
		/* Connections are not recycled (yet) */
		this.dead = true;
		interrupt();
		
		fcm.releaseConnection(this);
	}
	
	/**
	  * Processes and sends a fuse message to the client:
	  * # @type \r data\r##
	  */
	public void processFuseMessage(String type, String data) {
		try {
			StringBuffer sb = new StringBuffer(7 + type.length() + data.length());
			/* For security reasons don't accept # inside messages */
			data = data.replace('#', '*');
			type = type.replace('#', '*');
			
			/* Compose fuse-protocol-compatible message */
			sb.append("#");
			sb.append(type);
			sb.append("\r");
			sb.append(data);
			sb.append("\r##");
			out.println(sb.toString()); 
		} catch (Exception e) {
			Log.error(e);	
		}
	}
	
	public void handleClientMessage(String msg) {
		/** IMPLEMENTATION REMOVED FROM FuseLight-sourcetree */
		/** See com.fuse.projects.fuse_light.net.FuseLightConnection **/
	}
	
	/**
	  * Override this if you want to handle some different "non-action" messages
	  * in your project. Return true if you did handle the message, false otherwise
	  */
	protected boolean projectSpecificCommand(String command, String msg) throws Exception {
		return false;
	}
	
	
	/**
	  * Returns the user of this connectino
	  */
	public FUSEUser getUser() {
		return user;
	}
	
	/**
	  * Construct fuse-protocol representation of the user
	  * to send to the client
	  */
	public String getUserInfoString() {
		return user.toFusePString();
	}
	
	
	protected void generateOk() {
		out.println("# OK ##");
	}
	
	protected void generateError(int errorCode, String message) {
		out.println("# ERROR " + message + " ##");
	}
}	


