package sfs.cluster.lobby;

import it.gotoandplay.smartfoxserver.SmartFoxServer;
import it.gotoandplay.smartfoxserver.config.ConfigData;
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.ConcurrentHashMap;
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 sfs.cluster.lobby.data.ClusterEvent;
import sfs.cluster.lobby.data.ClusterRoom;
import sfs.cluster.lobby.data.ClusterUser;
import sfs.cluster.lobby.data.RoomReservation;
import sfs.cluster.lobby.handlers.client.IClientRequestHandler;
import sfs.cluster.lobby.handlers.cluster.OnCreateGame;
import sfs.cluster.lobby.handlers.cluster.OnJoinGame;
import sfs.cluster.lobby.handlers.internal.GameJoinHandler;
import sfs.cluster.lobby.handlers.internal.GameLoginHandler;
import sfs.cluster.lobby.handlers.internal.GameLogoutHandler;
import sfs.cluster.lobby.handlers.internal.GameRoomLostHandler;
import sfs.cluster.lobby.handlers.internal.InternalHandler;

/**
 * Main Game Extension
 */
public class ClusterGameServerExtension extends AbstractExtension
{
	static int WORKER_COUNT = 2;
	
	DataStore dataStore;
	
	ExtensionHelper helper; 
	Logger log;
	Zone currentZone;
	String nodeId;
	ClusterEventHandler clusterEventHandler;
	LinkedBlockingQueue<ClusterEvent> mailBox;
	Executor threadPool;
	
	Map<String, RoomReservation> reservations;
	
	Map<String, InternalHandler> sfsEventHandlers;
	Map<String, IClientRequestHandler> sfsRequestHandlers;
	
	String gameServerType;

	@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() );
		
		nodeId = ClusterUtils.generateNodeId();
        
        // Initialize 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);
        
        threadPool = Executors.newFixedThreadPool( WORKER_COUNT );
        for (int i = 0; i < WORKER_COUNT; i++)
        	threadPool.execute( clusterEventHandler );

        
    	// request and event handler maps
    	sfsEventHandlers = new HashMap<String, InternalHandler>();
    	sfsRequestHandlers = new HashMap<String, IClientRequestHandler>();
    	
    	// initialize evt/req handlers
		initializeEventHandlers();
		initializeRequestHandlers();
		initializeClusterEventHandlers();
        
        // Init reservations map
        reservations = new ConcurrentHashMap<String, RoomReservation>();
        
        // Set the local server game type
        gameServerType = ClusterUtils.getGameTypeFromPort(String.valueOf(ConfigData.SERVER_PORT));
        
        // Ouch! Server is NOT assigned a valid game type! Should not happen
        if (gameServerType == null)
        	log.severe(" >>> GAME TYPE NOT ASSIGNED TO THIS GAME SERVER!!! <<<");
        
        log.info("---{ Cluster Game Lobby started }---");
        log.info("Version: " + ClusterLobbyExtension.THE_VERSION);
        log.info("Node Name: " + nodeId);
	}
	
	@Override
	public void destroy()
	{
	    // TODO Auto-generated method stub

	}
	
	public void handleInternalEvent(InternalEventObject event)
	{
		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();
            }
	    }
	    
	}
	
	public void handleRequest(String cmd, ActionscriptObject params, User sender, int roomId)
	{
	    // TODO Auto-generated method stub
	    
	}
	
	public void handleRequest(String cmd, String[] params, User sender, int roomId)
	{
	    // TODO Auto-generated method stub
	    
	}
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > Public extension methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */
	public String getNodeId()
	{
		return nodeId;
	}
	
	public String getGameType()
	{
		return gameServerType;
	}
	
	public RoomReservation getReservation(String name)
	{
		return reservations.get(name);
	}
	
	public void addReservation(RoomReservation reservation)
	{
		if (!reservations.containsKey(reservation.getOwner()))
		{
			reservations.put(reservation.getOwner(), reservation);
		}
		
		else
		{
			log.warning("Duplicate reservation for user: " + reservation.getOwner());
		}
	}
	
	public void removeReservation(String name)
	{
		reservations.remove(name);
	}
	
	public Zone getCurrentZone()
	{
		return currentZone;
	}
	
	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);
	}
	
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > Private methods
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 */
	
	
	/*
	 * Initialize internal event handlers
	 */
	private void initializeEventHandlers()
	{
		InternalHandler logoutHandler = new GameLogoutHandler(this);
		
		// login handler
		sfsEventHandlers.put(InternalEventObject.EVENT_LOGIN, new GameLoginHandler(this));
		
		// user exit/lost/logout all go with the same handler
		sfsEventHandlers.put(InternalEventObject.EVENT_LOGOUT, logoutHandler);
		sfsEventHandlers.put(InternalEventObject.EVENT_USER_EXIT, logoutHandler);
		sfsEventHandlers.put(InternalEventObject.EVENT_USER_LOST, logoutHandler);
		
		// join handler
		sfsEventHandlers.put(InternalEventObject.EVENT_JOIN, new GameJoinHandler(this));	
		
		// room lost handler
		sfsEventHandlers.put(InternalEventObject.EVENT_ROOM_LOST, new GameRoomLostHandler(this));
	}
	
	
	/*
	 * Initialize request handlers
	 */
	private void initializeRequestHandlers()
	{
		// not needed
	}
	
	/*
	 * Initialize cluster evt handlers
	 */
	private void initializeClusterEventHandlers()
	{
		clusterEventHandler.addEventHandler(ClusterEvent.ON_CREATE_GAME, new OnCreateGame(this));
		clusterEventHandler.addEventHandler(ClusterEvent.ON_JOIN_GAME, new OnJoinGame(this));
	}
	
	
	/* 
	 * ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
	 * > 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;
    }
	
	
}
