package it.gotoandplay.example.android.SimpleChat;

import it.gotoandplay.smartfoxclient.ISFSEventListener;
import it.gotoandplay.smartfoxclient.SFSEvent;
import it.gotoandplay.smartfoxclient.SmartFoxClient;
import it.gotoandplay.smartfoxclient.data.Room;
import it.gotoandplay.smartfoxclient.data.User;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.app.TabActivity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TabHost;
import android.widget.TextView;

/**
 * Simple chat example.
 * 
 * The activity shows the usage of SmartFoxServer Java Client API
 * to connect to server, login and send and receive public messages,
 * simple room and user list management.
 */
public class SimpleChat extends TabActivity implements ISFSEventListener {
	/** The dialog box that asks the user for the server IP and port. */
	private static final int DIALOG_CONNECT_ID = 0;
	/** The connecting progress dialog box. */
	private static final int DIALOG_CONNECTING_ID = 1;
	/** The dialog box that displays the connecting error message. */
	private static final int DIALOG_CONNECTION_ERROR_ID = 2;
	/** The dialog that asks the user for desired username. */
	private static final int DIALOG_LOGIN_ID = 3;
	/** The dialog box that displays the login error message. */
	private static final int DIALOG_LOGIN_ERROR_ID = 4;
	/** The dialog that is displayed when the connection is lost. */
	private static final int DIALOG_CONNECTION_LOST_ID = 5;
	
	/** The zone in which the user will be logged in. */
	private static final String SFS_ZONE = "simpleChat";
	
	/** The SmartFoxClient instance used to communicate with the server. */
	private SmartFoxClient mSFSClient;
	
	/** Stores the login error message. It's used by the login error dialog box.*/
	private String mLoginError = "";
    
	/** The TextView that contains the chat messages. */
	private TextView mTextChat;
	/** The ScrollView that wraps the the TextView with the chat messages. */
	private ScrollView mScrollChat;
	/** The EditText view where the user can enter chat message.*/
	private EditText mTextMessage;
	/** The button that sends the chat message entered by the user. */
	private Button mButtonSend;
	/** The ListAdapter with the user names. */
	private ArrayAdapter<String> mUsers;

	//================================================
	// Acivity lifecycle related methods
	//================================================
	
	/**
	 * Called when the activity is first created.
	 * 
	 * Inits the UI, adds the SmartFoxClient event handlers
	 * and shows the dialog box that asks for the server IP and port. 
	 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        initUI();
        resetUI();
    	
        //Creates new instance of SmatFoxClient and adds the event handlers
        mSFSClient = new SmartFoxClient(true);
        mSFSClient.addEventListener(SFSEvent.onConnection, this);
        mSFSClient.addEventListener(SFSEvent.onConnectionLost, this);
        mSFSClient.addEventListener(SFSEvent.onLogin, this);
        mSFSClient.addEventListener(SFSEvent.onRoomListUpdate, this);
        mSFSClient.addEventListener(SFSEvent.onJoinRoom, this);
        mSFSClient.addEventListener(SFSEvent.onUserEnterRoom, this);
        mSFSClient.addEventListener(SFSEvent.onUserLeaveRoom, this);
        mSFSClient.addEventListener(SFSEvent.onPublicMessage, this);
        
        //Displays the connect dialog box so the user can enter
        //the server IP and port.
        showDialog(DIALOG_CONNECT_ID);
    }
    
	/**
	 * Called when the activity is destroyed.
	 *
	 * Disconnects from the server.
	 */
	@Override
	protected void onDestroy() {
        super.onDestroy();
        
        //Removes the event handlers and disconnect from the server.
		if(mSFSClient != null) {
	        mSFSClient.removeEventListener(SFSEvent.onConnection, this);
	        mSFSClient.removeEventListener(SFSEvent.onConnectionLost, this);
	        mSFSClient.removeEventListener(SFSEvent.onLogin, this);
	        mSFSClient.removeEventListener(SFSEvent.onRoomListUpdate, this);
	        mSFSClient.removeEventListener(SFSEvent.onJoinRoom, this);
	        mSFSClient.removeEventListener(SFSEvent.onUserEnterRoom, this);
	        mSFSClient.removeEventListener(SFSEvent.onUserLeaveRoom, this);
	        mSFSClient.removeEventListener(SFSEvent.onPublicMessage, this);
	        
			mSFSClient.disconnect();
		}
		
		//Exiting with System.exit(0) to avoid problem with persisting MINA threads
		System.exit(0);
	}

	//================================================
	// SmartFoxServer related methods
	//================================================
	
	/**
	 * Connects to SmartFoxServer instance.
	 * 
	 * @param ip the server IP. 
	 */
	private void connectToServer(final String ip) {
		showDialog(DIALOG_CONNECTING_ID);
		
		//connect() method is called in separate thread
        //so it does not blocks the UI
		final SmartFoxClient sfs = mSFSClient;
		new Thread() {
			@Override
			public void run() {
				sfs.connect(ip);
			}
		}.start();
	}
	
	/**
	 * Connects to SmartFoxServer instance.
	 * 
	 * @param ip the server IP.
	 * @param port the server port. 
	 */
	private void connectToServer(final String ip, final int port) {
		showDialog(DIALOG_CONNECTING_ID);
		
		//connect() method is called in separate thread
        //so it does not blocks the UI
		final SmartFoxClient sfs = mSFSClient;
		new Thread() {
			@Override
			public void run() {
				sfs.connect(ip, port);
			}
		}.start();
	}

	/**
	 * Handles the SmartFoxServer related events.
	 * 
	 * @param event the SmartFoxServer event.
	 */
	public void handleEvent(final SFSEvent event) {
		//The android UI is not thread safe and SFS events are dispatched
		//in different thread so we create a runnable that runs on
		//the UI thread to update the UI 
		runOnUiThread(new Runnable() {
			public void run() {
				if(event.getName().equals(SFSEvent.onConnection))
				{
					//if the connections is successful login dialog is shown
					if(event.getParams().getBool("success"))
					{
						removeDialog(DIALOG_CONNECTING_ID);
						showDialog(DIALOG_LOGIN_ID);
		            }
		            //otherwise error message is shown
		            else
		            {
		            	removeDialog(DIALOG_CONNECTING_ID);
		            	showDialog(DIALOG_CONNECTION_ERROR_ID);
		            }
		        }
				//When the connection is lost
				//the UI is disabled and message is shown
				else if(event.getName().equals(SFSEvent.onConnectionLost))
				{
					resetUI();
					showDialog(DIALOG_CONNECTION_LOST_ID);
				}
				else if(event.getName().equals(SFSEvent.onLogin))
				{
		            //if the login is not successful then error message and
		            //the login dialog are shown until it's successful
					if(!event.getParams().getBool("success"))
					{
		                mLoginError = event.getParams().getString("error");
		                showDialog(DIALOG_LOGIN_ERROR_ID);
		            }
		        }
		        //When the room list is received the user is joined to the default room
		        else if(event.getName().equals(SFSEvent.onRoomListUpdate))
		        {
		            mSFSClient.autoJoin();
		        }
		        //When the user joins new room then a message is added to
		        //the chat history and the UI is enabled
		        else if(event.getName().equals(SFSEvent.onJoinRoom))
		        {
		            Room room = (Room)event.getParams().get("room");
		    		for(User user : room.getUserList().values()) {
		    			mUsers.add(user.getName());
		    		}
		    		
		            appendChatMessage("Room [" + room.getName() + "] joined\n");
					
		            //Enables the chat UI
		            enableChatUI();
		        }
		        //When a user enter the room the user list is updated
		        else if(event.getName().equals(SFSEvent.onUserEnterRoom))
		        {
		        	User user = (User)event.getParams().get("user");
		        	mUsers.add(user.getName());
		        }
		        //When a user leave the room the user list is updated
		        else if(event.getName().equals(SFSEvent.onUserLeaveRoom))
		        {
		            String userName = (String)event.getParams().get("userName");
		            mUsers.remove(userName);
		        }
		        //When public message is received it's added to the chat history
		        else if(event.getName().equals(SFSEvent.onPublicMessage))
		        {
		            User sender = (User)event.getParams().get("sender");
		            String msg = event.getParams().getString("message");
		            appendChatMessage("[" + sender.getName() + "]: " + msg + "\n");
		        }
			}
		});
	}
	
	/**
	 * Sends the message entered by the user to the server.
	 */
	private void sendChatMessage() {
		//Gets the message the user have entered....
		String message = mTextMessage.getText().toString();
		if(message.length() > 0) {
			//... and if it's the message is not empty
			//it's send to the server
			mSFSClient.sendPublicMessage(message);
			mTextMessage.setText("");
		}
	}

	
	
	//================================================
	// UI related methods
	//================================================
	
	@Override
	/**
	 * Callback for creating dialogs that are managed by the activity.
	 */
	protected Dialog onCreateDialog(int id) {
		switch(id) {
		case DIALOG_CONNECT_ID:
			return getConnectDialog();
			
		case DIALOG_CONNECTING_ID:
			return getConnectingDialog();
			
		case DIALOG_CONNECTION_ERROR_ID:
			return getConnectionErrorDialog();
	        
		case DIALOG_LOGIN_ID:
			return getLoginDialog();
			
		case DIALOG_LOGIN_ERROR_ID:
			return getLoginErrorDialog();
			
		case DIALOG_CONNECTION_LOST_ID:
			return getConnectionLostDialog();
			
		default:
			return super.onCreateDialog(id);
		}
	}
	
	/**
	 * Inits the UI.
	 */
	private void initUI() {
        setContentView(R.layout.main);
        TabHost tabHost = getTabHost();
        //Creates the chat tab
        tabHost.addTab(tabHost.newTabSpec("tab_chat")
        		.setIndicator("Chat")
        		.setContent(R.id.layout_chat));
        //Creates the user list tab
        tabHost.addTab(tabHost.newTabSpec("tab_users")
        		.setIndicator("User List")
        		.setContent(R.id.list_users));
        //Sets the chat tab current tab.
        tabHost.setCurrentTab(0);
        
        //Gets reference to the UI elements for later use.
        mTextChat = (TextView)findViewById(R.id.text_chat);
        mScrollChat = (ScrollView)findViewById(R.id.scroll_chat);
        mTextMessage = (EditText)findViewById(R.id.text_message);
        mButtonSend = (Button)findViewById(R.id.button_send);
        ListView listUsers = (ListView)findViewById(R.id.list_users);
        
        //Creates the users list adapter. 
        mUsers = new ArrayAdapter<String>(this, R.layout.user_list_item);
        listUsers.setAdapter(mUsers);
        
        //Adds the send button lister.
        mButtonSend.setOnClickListener(new OnClickListener(){
			public void onClick(View v) {
				sendChatMessage();
			}});
	}
	
	/**
	 * Resets the UI.
	 * 
	 * Clears the chat histroy and closes all dialog boxes.
	 */
    private void resetUI() {
    	mButtonSend.setEnabled(false);
    	mTextMessage.setEnabled(false);
    	mTextChat.setText("");
    	mUsers.clear();
    	
    	removeDialog(DIALOG_CONNECTING_ID);
    	removeDialog(DIALOG_CONNECTION_ERROR_ID);
    	removeDialog(DIALOG_CONNECTION_LOST_ID);
    	removeDialog(DIALOG_LOGIN_ID);
	}
    
    /**
     * Enables the chat related UI.
     */
	private void enableChatUI() {
    	mTextMessage.setEnabled(true);
    	mButtonSend.setEnabled(true);
	}
	
	/**
	 * Adds a chat message at the end of the chat history.
	 * 
	 * @param message the message to be added.
	 */
	private void appendChatMessage(final CharSequence message) {
		//Appends the message...
		mTextChat.append(message);
		//...and scroll the view to show the last message.
		mScrollChat.fullScroll(ScrollView.FOCUS_DOWN); 
	}
	
	/**
	 * Returns the connect dialog.
	 * 
	 * Asks the user for the server IP and port. 
	 * 
	 * @return the connect dialog.
	 */
	private Dialog getConnectDialog() {
		LayoutInflater inflater = (LayoutInflater)this.getSystemService(
				LAYOUT_INFLATER_SERVICE);
		View layout = inflater.inflate(R.layout.connect_dialog,
		                (ViewGroup) findViewById(R.id.connect_dialog_layout_root));

		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setView(layout)
			.setCancelable(true)
			//if the user press the back button it quits the application.
			.setOnCancelListener(new OnCancelListener(){
				public void onCancel(DialogInterface dialog) {
					SimpleChat.this.finish();					
				}})
        	.setPositiveButton(R.string.connect_dialog_connect, new DialogInterface.OnClickListener() {
    			//when the user click on the "OK" buttong it tries to connect to
        		//SmartFoxServer using the provided IP and port.
        		public void onClick(DialogInterface dialog, int id) {
        			Dialog connectDialog = (Dialog)dialog;
        			//Gets the IP address and port entered by the user
        			EditText textServerIP =
        				(EditText)connectDialog.findViewById(R.id.text_server_ip);
        			EditText textServerPort =
        				(EditText)connectDialog.findViewById(R.id.text_server_port);
        			String serverIP = textServerIP.getText().toString();
        			String serverPortString = textServerPort.getText().toString();
        			connectDialog.dismiss();
        			//if the user have entered port number it uses it...
        			if(serverPortString.length() > 0) {
	        			int serverPort = Integer.parseInt(serverPortString);
	        			//tries to connect to the server
	        			connectToServer(serverIP, serverPort);
        			}
        			//...otherwise uses the default port number
        			else
        			{
	        			//tries to connect to the server
	        			connectToServer(serverIP);
        			}
        		}
        	});
		return builder.create();
	}
	
	/**
	 * Returns the connecting progress dialog.
	 * 
	 * Snows the users that the connection is in progress.
	 * 
	 * @return  the connecting progress dialog.
	 */
	private Dialog getConnectingDialog() {
		ProgressDialog connectingDialog;
		connectingDialog = new ProgressDialog(this);
		//Shows the "Connecting.." message
		connectingDialog.setMessage(
				this.getString(R.string.connecting_dialog_connecting));
		connectingDialog.setCancelable(true);
		//if the user press the back button it quits the application.
		connectingDialog.setOnCancelListener(new OnCancelListener(){
			public void onCancel(DialogInterface dialog) {
				SimpleChat.this.finish();					
			}});
		return connectingDialog;
	}

	/**
	 * Returns the connection error dialog.
	 * 
	 * Shows the error message and displays the connect dialog again.
	 * 
	 * @return the connection error dialog.
	 */
	private Dialog getConnectionErrorDialog() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		//Shows the error message...
        builder.setMessage(R.string.connection_error_dialog_connection_error)
        	.setCancelable(false)
        	.setNeutralButton(R.string.button_ok, new DialogInterface.OnClickListener() {
        		//...and when is "OK" button is clicked shows the connect dialog.
        		public void onClick(DialogInterface dialog, int id) {
        			removeDialog(DIALOG_CONNECTION_ERROR_ID);
        			showDialog(DIALOG_CONNECT_ID);
        		}
        	});
        return builder.create();
	}
	
	/**
	 * Returns the login dialog box.
	 * 
	 * Asks the user for the desired username.
	 * If there was error occurred while trying to login the user
	 * (if the username is already take for example) this dialog is shown again.
	 * 
	 * @return the login dialog box.
	 */
	private Dialog getLoginDialog() {
		LayoutInflater inflater = (LayoutInflater)this.getSystemService(
				LAYOUT_INFLATER_SERVICE);
		View layout = inflater.inflate(R.layout.login_dialog,
		                  (ViewGroup) findViewById(R.id.login_dialog_layout_root));

		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setView(layout)
			.setCancelable(true)
			//if the user press the back button it quits the application.
			.setOnCancelListener(new OnCancelListener(){
				public void onCancel(DialogInterface dialog) {
					SimpleChat.this.finish();					
				}})
        	.setPositiveButton(R.string.login_dialog_login, new DialogInterface.OnClickListener() {
        		//When the "login" button is clicked tries to log the user
        		public void onClick(DialogInterface dialog, int id) {
        			//gets the username enter by the user. 
        			Dialog loginDialog = (Dialog)dialog;
        			EditText textUserName =
        				(EditText)loginDialog.findViewById(R.id.text_username);
        			String userName = textUserName.getText().toString();
        			//hides the dialog
        			removeDialog(DIALOG_LOGIN_ID);
        			//sends the login request
        			mSFSClient.login(SFS_ZONE, userName, "");
        		}
        	});
		return builder.create();
	}
	
	/**
	 * Returns the login error dialog.
	 * 
	 * Shows the error message and displays the login dialog again.
	 * 
	 * @return the login error dialog.
	 */
	private Dialog getLoginErrorDialog() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		//Shows the error message...
		builder.setMessage(mLoginError)
        	.setCancelable(false)
        	.setNeutralButton(R.string.button_ok, new DialogInterface.OnClickListener() {
        		//...and when is "OK" button is clicked shows the login dialog.
        		public void onClick(DialogInterface dialog, int id) {
        			removeDialog(DIALOG_LOGIN_ERROR_ID);
        			showDialog(DIALOG_LOGIN_ID);
        		}
        	});
        return builder.create();
	}
	
	/**
	 * Returns the connection lost dialog.
	 * 
	 * The dialog shows message and when it's closed shows
	 * the connect dialog box.
	 * 
	 * @return the connection lost dialog.
	 */
	private Dialog getConnectionLostDialog() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		//Shows the error message...
        builder.setMessage(R.string.connection_lost_dialog_connection_lost)
        	.setCancelable(false)
        	.setNeutralButton(R.string.button_ok, new DialogInterface.OnClickListener() {
        		//...and when is "OK" button is clicked shows the connect dialog.
        		public void onClick(DialogInterface dialog, int id) {
        			removeDialog(DIALOG_CONNECTION_LOST_ID);
        			showDialog(DIALOG_CONNECT_ID);
        		}
        	});
        return builder.create();
	}
}