package com.fuse.projects.fuse_light.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.
  * @author Aapo Kyrola
  * @copyright Aapo Kyrola
  */
public class FUSELightConnection extends FUSEClientConnection {
	
	/* Commands from updating user registry and authenticating */
	protected static final String FUSECMD_LOGIN = "LOGIN";
	protected static final String FUSECMD_REGISTER = "REGISTER";
	protected static final String FUSECMD_UPDATE = "UPDATE";
	protected static final String FUSECMD_INFORETRIEVE = "INFORETRIEVE";
	
	/* Client alive message */
	protected static final String FUSECMD_STATUSOK = "STATUSOK";
	
	/* Security */
	protected static final String FUSECMD_VERSIONCHECK = "VERSIONCHECK";
	protected static final String FUSECMD_KEYENCRYPT   = "KEYENCRYPTED";
		
	/* Security and compability checks */
	protected boolean checkedCorrectVersion = false;
	protected boolean checkedKeyEncryption  = false;
	
	
	
	/* Commands */
	protected static final String FUSECMD_BROADCASTALL = "BROADCASTALL";
	protected static final String FUSECMD_BROADCASTTO  = "BROADCASTTO";
	protected static final String FUSECMD_USERKILL = "USERKILL";


	
	/* Connection id */
	protected static int n = 0;
	
	public FUSELightConnection(FUSEConnectionManager fcm) {
		super(fcm);
		this.setName("FUSELightConnection " + id);
	}
	
	
	/**
	  * Processes messages from the client
	  */
	public void handleClientMessage(String msg) {
		try {
			int off = msg.indexOf(" ");
			
			lastMessage = System.currentTimeMillis();
			
			String command = off >= 0 ? msg.substring(0, off) : msg;
			
			
			/* Check if client's version is compatible with server */
			if (command.equals(FUSECMD_VERSIONCHECK)) {
				
				StringTokenizer st = new StringTokenizer(msg, " ");
				st.nextToken();
				String version = st.nextToken().trim();
				String allowedVersions = FUSEEnvironment.getProperty("listener.allowedVersions");
				if (allowedVersions == null) {
					Log.status("Please set listener.allowedVersions");
					generateError(-1000, "FUSE server misconfiguration error. Please contact administrator!");
					return;
				}
				StringTokenizer zt = new StringTokenizer(allowedVersions, ",");
				while(zt.hasMoreTokens()) {
					if(version.equals(zt.nextToken())) {
						checkedCorrectVersion = true;	
						break;
					}
				}
				if (checkedCorrectVersion == false) {
					 generateError(-400, "Version not correct. Allowed=" + allowedVersions);
					 dieTime = System.currentTimeMillis() + 1200;
				} else {
					Log.status("Correct version!");
				}
				return;
			/* Security check needed to establish connection */
			} else if (command.equals(FUSECMD_KEYENCRYPT)) {
				StringTokenizer st = new StringTokenizer(msg, " ");
				st.nextToken();
				String encryptedKey = st.nextToken().trim();
				Log.status("+" + encryptedKey + "+");
				String correctKey = FUSEEnvironment.getSecretKey().decodeKey(myKey);
				Log.status("*" + correctKey + "*");
				if (correctKey.equals(encryptedKey)) {
					checkedKeyEncryption = true;
				} else {
					Log.status("## Key encryption failed!!" + client.getInetAddress().getHostName());
					generateError(-501, "Key encryption failed!");
					dieTime = System.currentTimeMillis() + 1200;
					
				}
				return;
			/* Used to prevent multiple logging from same ip */
			}
			
			if (checkedKeyEncryption == false || checkedCorrectVersion == false) {
				return;
			}
			
			
			
			/* Client alive message */
			if (command.equals(FUSECMD_STATUSOK)) {
				// don't do anything.
			/* Broadcast all: BROADCASTALL /<MESSAGETYPE>/<MESSAGEDATA> */
			} else if (command.equals(FUSECMD_BROADCASTALL) && user != null) {
				StringTokenizer zt = new StringTokenizer(msg, "/");
				zt.nextToken();
				String type = zt.nextToken();
				String data = msg.substring(1+msg.indexOf("/", msg.indexOf(type, 8)));
				fcm.broadcastToAll(type, data);
			/* Broadcast message to specified users: BROADCASTTO /user1 user2 user3/MESSAGETYPE/<MESSAGEDATA>*/
			} else if (command.equals(FUSECMD_BROADCASTTO) && user != null) {
				StringTokenizer zt = new StringTokenizer(msg, "/");
				zt.nextToken();
				String users = zt.nextToken();
				String type = zt.nextToken();
				String data = msg.substring(1+msg.indexOf("/", msg.indexOf(type, 8 + users.length())));
				
				StringTokenizer st = new StringTokenizer(users, " ");
				Vector recipients = new Vector(st.countTokens());
				while(st.hasMoreTokens()) {
					recipients.addElement(st.nextToken());
				}
				fcm.broadcastToUsers(recipients, type, data);
			/* Force user to be disconnected and assign a temporary ip-banning */
			} else if (command.equals(FUSECMD_USERKILL) && user != null) {
				AccessControl ac = FUSEEnvironment.getAccessControl();
				/* Check if user has privileges to kill other users */
				if (ac.getIsMemberOfGroup(user, "can_kill")) {
					/* Parse user to kill */
					StringTokenizer zt = new StringTokenizer(msg, " ");
					zt.nextToken();
					String userToKill = zt.nextToken();
					
					/* Get user's connection */
					FUSEClientConnection conn = fcm.getUserConnection(userToKill);
					if (conn == null) {
						generateError(-1999, "User not connected");	
						return;
					}
					
					Log.status("USER " + user.getName() + " KILLS " + userToKill);
					
					/* Ban IP. Default banning time is 10 mins */
					long banningTime = Long.parseLong(FUSEEnvironment.getProperty("access_control.banning_time", "600000"));
					ac.setIpBanned(conn.getClientIp(), new Date(new Date().getTime() + banningTime));
					
					/* Disconnect connection */
					conn.release();
				} else {
					generateError(-2000, "You have not enough privileges");
				}	
			/* Check if there is something project dependent */
			} else if (projectSpecificCommand(command, msg) == true) {
			
			/* Update or register new user */	
			} else if (command.equals(FUSECMD_REGISTER) || command.equals(FUSECMD_UPDATE)) {
				try {
					boolean update = command.equals(FUSECMD_UPDATE);
					if (update == true && user == null) {
						generateError(-3, "cannot update user who has not logged in");
						return;
					}
				
					FUSEUser fuseUser = null;
					if (update == true) fuseUser = FUSEEnvironment.getRegister().loadUser(this.user.getName());
					else fuseUser = FUSEEnvironment.getRegister().createNewUser();
					
					fuseUser.handleFusePRegistration(msg.substring(off+1));
					
					if (update == false) FUSEEnvironment.getRegister().saveUser(fuseUser);
					else FUSEEnvironment.getRegister().updateUser(fuseUser);
					
					this.user = fuseUser;
					generateOk();
				} catch (UserExistsException uee) {
					generateError(-2, "user already exists");	
				} catch (Exception e) {
					Log.error(e);
					generateError(-1, "weird error");
				}
			/* Send client user data */
			} else if (command.equals(FUSECMD_INFORETRIEVE)) {
				try {
					/* User authenticates in but does not show up.
					   UserInfoString is sent to the client */
					StringTokenizer st = new StringTokenizer(msg, " ");
					st.nextToken();
						
					String username = st.nextToken();
					String password = st.nextToken();
					
					this.user = FUSEEnvironment.getAuthenticator().login(username,password);
					Log.status(getUserInfoString());
					processFuseMessage("USEROBJECT", user.toFusePString());
				} catch (IncorrectLoginException ile) {
					generateError(-3, "login incorrect");
				} catch (Exception e) {
					Log.error("#WIERD ERROR#");
					Log.error(e);
					generateError(-1, "weird error");
				}
			/* Authentication */
			} else if (command.equals(FUSECMD_LOGIN)) {
				if (!(checkedCorrectVersion == true && checkedKeyEncryption == true)) {
					generateError(-500, "connection not secured");
					return;
				}
				
				try {
					StringTokenizer st = new StringTokenizer(msg, " ");
					st.nextToken();
					
					/* Note: username or password may not have spaces */
					String username = st.nextToken();
					String password = st.nextToken();
					
					this.user = FUSEEnvironment.getAuthenticator().login(username,password);
					
					gotOk = true;
					
					generateOk();
					
					/* Communicate other people in this world about new figure in World */ 
		
					writeLog(this.user,client,this);
				} catch (IncorrectLoginException ile) {
					generateError(-3, "login incorrect");
				} catch (Exception e) {
					Log.error("#WIERD ERROR#");
					Log.error(e);
					generateError(-1, "weird error");
				}
			} else {
				generateError(-999, "Could not understand! Command was=" + command);
			}
		} catch (Exception e) {
			Log.error(e);
		}
	}
	
	/**
	  * Override this if you want to handle some special 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;
	}
	
	
	protected void writeLog(FUSEUser user, Socket clientSocket, FUSELightConnection conn) {
		try {
			String logFile = (String) FUSEEnvironment.getProperty ("access_log.file");
			RandomAccessFile stream = new RandomAccessFile(new File (logFile), "rw");
			stream.seek (stream.length ());
			
			String logEntry = "";
			SimpleDateFormat sdf = new SimpleDateFormat ("dd.MM.yyyy HH:mm:ss");
			try {
				logEntry += "\n" + sdf.format (new Date());
				logEntry += "\t" + user.getName();
				logEntry += "\t" + clientSocket.getInetAddress().getHostAddress();
				logEntry += "\t" + clientSocket.getInetAddress().getHostName();
				logEntry += "\t" + ((FUSELightConnection) conn).id;
			} catch (Exception e) {
				logEntry = sdf.format(new Date()) + "\t " + e.getClass().getName() + "\t" + e.getMessage();
			}
			stream.writeBytes(logEntry);
			stream.close();
			
			Log.status("User " + user.getName() + " logged in.");
			Log.status("ip=" +  clientSocket.getInetAddress().getHostAddress() + " host=" +  clientSocket.getInetAddress().getHostName());
					
		} catch (IOException ioe) {
			Log.error(ioe);
		}
		
		try {
			// do the same into database!
			FUSEDatabase db = FUSEEnvironment.getDatabaseProxy().getDatabase();
			accessLogEntry = (Access) db.createNew("Access");
			accessLogEntry.setTime(new Date());
			accessLogEntry.setUser(user);
			accessLogEntry.setIp(clientSocket.getInetAddress().getHostAddress().toString());
			accessLogEntry.setDomain(clientSocket.getInetAddress().getHostName());
		} catch (Exception e) {
			Log.error(e);
		}
	}
	
}	


