package sfs.cluster.lobby;

import it.gotoandplay.smartfoxserver.SmartFoxServer;
import it.gotoandplay.smartfoxserver.data.Room;
import it.gotoandplay.smartfoxserver.data.User;
import it.gotoandplay.smartfoxserver.data.Zone;
import it.gotoandplay.smartfoxserver.events.InternalEventObject;
import it.gotoandplay.smartfoxserver.extensions.AbstractExtension;
import it.gotoandplay.smartfoxserver.extensions.ExtensionHelper;
import it.gotoandplay.smartfoxserver.lib.ActionscriptObject;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

import org.json.JSONObject;

import sfs.cluster.lobby.data.ClusterEvent;
import sfs.cluster.lobby.data.ClusterRoom;
import sfs.cluster.lobby.data.ClusterUser;
import sfs.cluster.lobby.friendlist.FriendListManager;
import sfs.cluster.lobby.handlers.client.AddFriendHandler;
import sfs.cluster.lobby.handlers.client.CreateGameHandler;
import sfs.cluster.lobby.handlers.client.GetGameList;
import sfs.cluster.lobby.handlers.client.IClientRequestHandler;
import sfs.cluster.lobby.handlers.client.JoinGameHandler;
import sfs.cluster.lobby.handlers.cluster.OnBatchUserRemove;
import sfs.cluster.lobby.handlers.cluster.OnFriendListUpdate;
import sfs.cluster.lobby.handlers.cluster.OnGameUpdate;
import sfs.cluster.lobby.handlers.cluster.OnPrivateMessage;
import sfs.cluster.lobby.handlers.cluster.OnRoomCreated;
import sfs.cluster.lobby.handlers.internal.InternalHandler;
import sfs.cluster.lobby.handlers.internal.LoginHandler;
import sfs.cluster.lobby.handlers.internal.LogoutHandler;
import sfs.cluster.lobby.handlers.internal.PMHandler;

/**
 * Main Lobby Extension
 */
public class ClusterLobbyExtension extends AbstractExtension
{
	static int WORKER_COUNT = 4;
	public static String THE_VERSION = "1.1.0";

	//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	// Local objects
	//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	
	DataStore dataStore;
	
	ExtensionHelper helper; 
	public Logger log;
	public Zone currentZone;
	public Room lobbyRoom;
	public String nodeId;
	ClusterEventHandler clusterEventHandler;
	LinkedBlockingQueue<ClusterEvent> mailBox;
	Executor threadPool;
	FriendListManager friendListManager;
	GameListUpdater gameListUpdater;
	
	Map<String, InternalHandler> sfsEventHandlers;
	Map<String, IClientRequestHandler> sfsRequestHandlers;
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > Extension override methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */

	@Override
	public void init()
	{
		// grab a reference to the data store
		dataStore = DataStore.instance();
		
		// extension helper
		helper = ExtensionHelper.instance();
		
		// logger
		log = SmartFoxServer.log;
		
		// current zone reference
		currentZone = SmartFoxServer.getInstance().getZone( this.getOwnerZone() );
		currentZone.setPrivMsgInternalEvent(true);
		
		// the lobby rooms
		lobbyRoom = currentZone.getRoomByName("Lobby");
		
		// request and event handler maps
		sfsEventHandlers = new HashMap<String, InternalHandler>();
		sfsRequestHandlers = new HashMap<String, IClientRequestHandler>();
		
		// friend list manager
		friendListManager = new FriendListManager(currentZone, this);
		
		// create node id
        nodeId = ClusterUtils.generateNodeId();
        
        // Init node user count
        getNodeUserCounts().put(nodeId, new AtomicInteger(0));
        
        // Setup message queue in post office
        mailBox = new LinkedBlockingQueue<ClusterEvent>();
        getPostOffice().put(nodeId, mailBox);
        
        clusterEventHandler = new ClusterEventHandler(this, mailBox);
        
        // initialize evt/req handlers
		initializeEventHandlers();
		initializeRequestHandlers();
		initializeClusterEventHandlers();
        
		// Start the executors for the clusterEventHandler
        threadPool = Executors.newFixedThreadPool( WORKER_COUNT );
        for (int i = 0; i < WORKER_COUNT; i++)
        	threadPool.execute( clusterEventHandler );
        
        // start the game list updater
        gameListUpdater = new GameListUpdater(this);
        
        log.info("---{ Cluster Lobby started }---");
        log.info("Version: " + THE_VERSION);
        log.info("Node Name: " + nodeId);

	}
	
	

	@Override
	public void destroy()
	{
		log.info("Cluster Lobby shutting down...");
	}
	
	
	@Override
	public void handleRequest(String cmd, JSONObject jso, User u, int fromRoom)
	{
		if (cmd.length() > 0)
		{
			IClientRequestHandler handler = sfsRequestHandlers.get(cmd);
			
			if (handler != null)
			{
				try
				{
					handler.handleRequest(jso, u, fromRoom);
				}
				catch (Exception e) 
				{
					log.warning("Problems handling a request -> " + cmd + ": " + e.getMessage());
					e.printStackTrace();
				}
			}
		}
	}
	
	
	public void handleInternalEvent(InternalEventObject event)
	{
	    // TODO Auto-generated method stub
	    String type = event.getEventName();
	    
	    InternalHandler handler = sfsEventHandlers.get(type);
	    if ( handler != null )
	    {
	    	try
            {
	            handler.handleInternalEvent(event);
            }
            catch (Exception e)
            {
	            log.warning("Error while handling event: " + type + ", Message: " + e.getMessage());
	            e.printStackTrace();
            }
	    }
	}
	
	@Override
	public Object handleInternalRequest(Object params)
	{
	    // TODO Auto-generated method stub
	    return null;
	}
	
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > Utility public methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */
	
	
	public void sendEventToNode(String nodeName, ClusterEvent event)
	{
		BlockingQueue<ClusterEvent> syphon = getPostOffice().get(nodeName);
		
		if (syphon != null)
			syphon.offer(event);
		else
			log.warning("Destination node is not available -> " + nodeName);	
	}
	
	/*
	 * Get a user from his name in the DataStore
	 */
	public ClusterUser getUserByName(String name)
	{
		return getClusterUsersByName().get(name);
	}
	
	public Map<String, ClusterRoom> getClusterRoomList()
	{
		return this.getClusterRoomsByName();
	}
	
	public Zone getCurrentZone()
	{
		return currentZone;
	}
	
	/*
	 * Get this node name
	 */
	public String getNodeId()
	{
		return nodeId;
	}
	
	public Room getLobbyRoom()
	{
		return this.lobbyRoom;
	}
	
	/*
	 * Add user in DataStore
	 */
	public void addUser(ClusterUser cu)
	{
		getClusterUsersByName().put(cu.getName(), cu);
	}
	
	public void removeUser(String name)
	{
		friendListManager.removeList(name);
		getClusterUsersByName().remove(name);
	}
	
	public void addRoom(ClusterRoom room)
	{
		getClusterRoomsByName().put(room.getName(), room);
	}
	
	public void removeRoom(ClusterRoom room)
	{
		getClusterRoomsByName().put(room.getName(), room);
	}
	
	public boolean isRoomAvailable(String roomName)
	{
		return getClusterRoomsByName().containsKey(roomName);
	}
	
	public FriendListManager getFriendListManager()
	{
		return friendListManager;
	}
	
	public boolean isUserOnline(String name)
	{
		return getClusterUsersByName().containsKey(name);
	}
	
	public void incrementUserCount()
	{
		AtomicInteger count = getNodeUserCounts().get(this.nodeId);
		
		if (count != null)
			count.getAndIncrement();
	}
	
	public void decrementUserCount()
	{
		AtomicInteger count = getNodeUserCounts().get(this.nodeId);
				
		if (count != null)
			count.getAndDecrement();
	}
	
	public void updateGameList(String gameType, String gameName)
	{
		gameListUpdater.setGameChanged(gameType, gameName);
	}
	
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > Private methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */
	
	
	/*
	 * Initialize internal event handlers
	 */
	private void initializeEventHandlers()
	{
		LogoutHandler logoutHandler = new LogoutHandler(this);
		
		sfsEventHandlers.put(InternalEventObject.EVENT_LOGIN, new LoginHandler(this));
		
		sfsEventHandlers.put(InternalEventObject.EVENT_LOGOUT, logoutHandler);				// 
		sfsEventHandlers.put(InternalEventObject.EVENT_USER_EXIT, logoutHandler);			// Handled by the same object
		sfsEventHandlers.put(InternalEventObject.EVENT_USER_LOST, logoutHandler);			//
		
		sfsEventHandlers.put(InternalEventObject.EVENT_PRIVATE_MESSAGE, new PMHandler(this));
	}
	
	
	/*
	 * Initialize request handlers
	 */
	private void initializeRequestHandlers()
	{
		sfsRequestHandlers.put("addFriend", new AddFriendHandler(this));
		sfsRequestHandlers.put("createGame", new CreateGameHandler(this));
		sfsRequestHandlers.put("joinGame", new JoinGameHandler(this));
		sfsRequestHandlers.put("gameList", new GetGameList(this));
	}
	
	/*
	 * Initialize cluster evt handlers
	 */
	private void initializeClusterEventHandlers()
	{
		clusterEventHandler.addEventHandler(ClusterEvent.ON_FRIEND_LIST_UPDATE, new OnFriendListUpdate(this));
		clusterEventHandler.addEventHandler(ClusterEvent.ON_PRIVATE_MESSAGE, new OnPrivateMessage(this));
		clusterEventHandler.addEventHandler(ClusterEvent.ON_ROOM_CREATED, new OnRoomCreated(this));
		clusterEventHandler.addEventHandler(ClusterEvent.ON_GAME_UPDATE, new OnGameUpdate(this));
		clusterEventHandler.addEventHandler(ClusterEvent.ON_BATCH_USER_REMOVE, new OnBatchUserRemove(this));	
	}
	
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > Unused methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */
	public void handleRequest(String arg0, ActionscriptObject arg1, User arg2, int arg3)
	{
	    // TODO Auto-generated method stub
	    
	}
	
	public void handleRequest(String arg0, String[] arg1, User arg2, int arg3)
	{
	    // TODO Auto-generated method stub 
	}
			
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > DataStore access methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */


	/**
     * @return the clusterUsersByName
     */
    public Map<String, ClusterUser> getClusterUsersByName()
    {
	    return dataStore.clusterUsersByName;
    }


	/**
     * @return the clusterRoomsByName
     */
    Map<String, ClusterRoom> getClusterRoomsByName()
    {
	    return dataStore.clusterRoomsByName;
    }


    Map<String, BlockingQueue<ClusterEvent>> getPostOffice()
    {
	    return dataStore.postOffice;
    }


    Map<String, AtomicInteger> getNodeUserCounts()
    {
	    return dataStore.nodeUserCounts;
    }
}
