package sfs2x.extensions.games.spacewar2.lobby;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;

import com.smartfoxserver.cluster.core.SFSCluster;
import com.smartfoxserver.v2.core.SFSEventType;
import com.smartfoxserver.v2.entities.SFSRoomRemoveMode;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSArray;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.entities.match.MatchExpression;
import com.smartfoxserver.v2.entities.match.NumberMatch;
import com.smartfoxserver.v2.entities.match.StringMatch;
import com.smartfoxserver.v2.entities.variables.RoomVariable;
import com.smartfoxserver.v2.entities.variables.SFSRoomVariable;
import com.smartfoxserver.v2.exceptions.SFSCreateRoomException;
import com.smartfoxserver.v2.exceptions.SFSJoinRoomException;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import com.smartfoxserver.v2.extensions.ExtensionLogLevel;
import com.smartfoxserver.v2.extensions.SFSExtension;
import com.smartfoxserver.v2.mmo.CreateMMORoomSettings;
import com.smartfoxserver.v2.mmo.Vec3D;

import sfs2x.extensions.games.spacewar2.lobby.handlers.event.GameNodeEventHandler;
import sfs2x.extensions.games.spacewar2.shared.util.Constants;


public class SW2ZoneExtension extends SFSExtension
{
	private SFSObject gameCfg;
	private SFSObject levelsCfg;
	private SFSObject starshipsCfg;
	private SFSObject weaponsCfg;
	
	@Override
	public void init()
	{
		try
		{
			// Load configuration file
			loadGameConfig();
			
			// Add client request handler
	        addRequestHandler(Constants.REQ_INIT, new StartGameReqHandler());
	        
	        // Add game nodes event handler (events coming from game nodes)
	        addEventHandler(SFSEventType.GAME_NODE_EVENT, new GameNodeEventHandler());
		}
		catch (IOException e)
		{
			trace(ExtensionLogLevel.ERROR, "SpaceWar 2 configuration could not be loaded");
		}
		
		trace(ExtensionLogLevel.INFO, "SpaceWar 2 Zone Extension initialized");
	}
	
	/**
	 * Load the game configuration from the external configuration file and save it to cluster globals.
	 */
	private void loadGameConfig() throws IOException
	{
		// Load configuration file
		String cfgData = FileUtils.readFileToString(new File(this.getCurrentFolder() + "SpaceWar2.config"));
		
		// Convert to temporary SFSObject
		ISFSObject tempCfg = SFSObject.newFromJsonData(cfgData);
		
		// Get global game settings
		gameCfg = (SFSObject)tempCfg.getSFSObject(Constants.CONFIG_SETTINGS);
		
		// Levels
		ISFSArray tmpLevelsCfg = tempCfg.getSFSArray(Constants.CONFIG_LEVELS);
		levelsCfg = new SFSObject();
		
		for (int i = 0; i < tmpLevelsCfg.size(); i++)
		{
			ISFSObject level = tmpLevelsCfg.getSFSObject(i);
			String name = level.getUtfString("name");
			
			levelsCfg.putSFSObject(name, level);
		}
		
		// Starships
		ISFSArray tmpStarshipsCfg = tempCfg.getSFSArray(Constants.CONFIG_STARSHIPS);
		starshipsCfg = new SFSObject();
		
		for (int i = 0; i < tmpStarshipsCfg.size(); i++)
		{
			ISFSObject starship = tmpStarshipsCfg.getSFSObject(i);
			String model = starship.getUtfString("model");
			
			starshipsCfg.putSFSObject(model, starship);
		}
		
		// Weapons
		ISFSArray tmpWeaponsCfg = tempCfg.getSFSArray(Constants.CONFIG_WEAPONS);
		weaponsCfg = new SFSObject();
		
		for (int i = 0; i < tmpWeaponsCfg.size(); i++)
		{
			ISFSObject weapon = tmpWeaponsCfg.getSFSObject(i);
			String model = weapon.getUtfString("model");
			
			weaponsCfg.putSFSObject(model, weapon);
		}
		
		// Create cluster-global configuration object
		Map<String, Serializable> config = new HashMap<String, Serializable>();
		config.put(Constants.CONFIG_SETTINGS, gameCfg);
		config.put(Constants.CONFIG_LEVELS, levelsCfg);
		config.put(Constants.CONFIG_STARSHIPS, starshipsCfg);
		config.put(Constants.CONFIG_WEAPONS, weaponsCfg);
		
		// Assign configuration objects to cluster globals
		SFSCluster.getLobbyService().getGlobals().set(config);
		
		trace(ExtensionLogLevel.INFO, "SpaceWar 2 configuration loaded");
	}
	
	//----------------------------------------------------------------------------------------
	
	public class StartGameReqHandler extends BaseClientRequestHandler
    {
        @Override
        public void handleClientRequest(User sender, ISFSObject params)
        {
        	// Extract level name and validate it
        	String levelName = params.getUtfString("name");
        	
        	// Validate level ID
        	if (levelsCfg.containsKey(levelName))
        	{
        		try
        		{
					createOrJoinRoom(sender, levelName);
				}
        		catch (SFSJoinRoomException e)
        		{
					// Nothing to do: a ROOM JOIN ERROR event is dispatched to the client automatically
        			trace(ExtensionLogLevel.ERROR, e);
				}
        		catch (SFSCreateRoomException e)
        		{
        			// Nothing to do: a ROOM CREATION ERROR event is dispatched to the client automatically
        			trace(ExtensionLogLevel.ERROR, e);
				}
        	}
        	else
        	{
        		// Send error notification back to client
        		send(Constants.REQ_INIT_ERROR, null, sender);
        	}
        }
        
        private void createOrJoinRoom(User player, String levelName) throws SFSJoinRoomException, SFSCreateRoomException
        {
        	// Set the MMORoom settings to create a new Room if needed
        	CreateMMORoomSettings mmors = new CreateMMORoomSettings();
        	mmors.setGroupId(Constants.ROOMS_GROUP_NAME);
			mmors.setName("SW2_" + System.currentTimeMillis());
			mmors.setMaxUsers(50);
			mmors.setDynamic(true);
			mmors.setAutoRemoveMode(SFSRoomRemoveMode.WHEN_EMPTY);
			mmors.setExtension(new CreateMMORoomSettings.RoomExtensionSettings("SpaceWar2-Cluster", "sfs2x.extensions.games.spacewar2.game.SW2RoomExtension"));
			
			mmors.setDefaultAOI(new Vec3D(1300, 750, 0));
			mmors.setUserMaxLimboSeconds(gameCfg.getInt(Constants.SETTING_GAME_ABORT_SECS) + gameCfg.getInt(Constants.SETTING_GAME_START_SECS) + 20);
			mmors.setProximityListUpdateMillis(20); // We need this to be almost realtime to make the client and server simulation almost identical
			mmors.setSendAOIEntryPoint(false);
			
			// Set requirements to allow users find the Room (see match expression below) and other settings in Room Variables
			List<RoomVariable> roomVars = new ArrayList<RoomVariable>();
			roomVars.add(new SFSRoomVariable(Constants.ROOMVAR_GAME_LEVEL, levelName));
			roomVars.add(new SFSRoomVariable(Constants.ROOMVAR_USER_MIN_XP, player.getVariable(Constants.USERVAR_EXPERIENCE).getIntValue()));
			roomVars.add(new SFSRoomVariable(Constants.ROOMVAR_GAME_STATE, Constants.GAMESTATE_WAITING));
			mmors.setRoomVariables(roomVars);
			
			// Set the match expression to search for an existing Room (see Room Variables set above):
			// - it must match the game level selected by the user
			// - it must have a minimum required ranking equal to or lower than the ranking of the user
			//   searching the Room to join, as set when the Room was created
			// - the game must not be running yet, unless mid game join is allowed
			MatchExpression exp = new MatchExpression(Constants.ROOMVAR_GAME_LEVEL, StringMatch.EQUALS, levelName)
					 							 .and(Constants.ROOMVAR_USER_MIN_XP, NumberMatch.LESS_THAN_OR_EQUAL_TO, player.getVariable(Constants.USERVAR_EXPERIENCE).getIntValue())
												 .and(Constants.ROOMVAR_GAME_STATE, NumberMatch.LESS_THAN_OR_EQUAL_TO, (gameCfg.getBool(Constants.SETTING_ALLOW_MIDGAME_JOIN) ? Constants.GAMESTATE_RUNNING : Constants.GAMESTATE_WAITING));
			
			// Execute
			SFSCluster.getLobbyService().getApi().quickJoinOrCreateRoom(player, exp, Arrays.asList(Constants.ROOMS_GROUP_NAME), mmors, true);
        }
    }
}
