/***********************************************************************

GECCO, a platform for command and control gaming

Copyright (C) 2001 Henrik Bäärnhielm, Andreas Enblom, Niklas
Hallenfur, Karl Hasselström, Henrik Hägerström, Oskar Linde, Jing Fu
Zi and Jon Åslund

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.

***********************************************************************/

package gecco.server.unitmanager;
import gecco.server.automaton.AutomatonMap;
import gecco.server.core.*;
import java.lang.*;

import java.util.*;

/**
 *  This class acts as a container for all units in the game. It keeps track all
 *  units and unit types, the observers of all units, and certain things.
 *
 * @author     Henrik Bäärnhielm, Karl Hasselström, Henrik Hägerström
 * @created    April 24, 2001
 */
public class UnitManager {
    /**
     *  Contains all units, hashed on their handles.
     */
    protected HashMap units;

    /**
     *  Array containing _all_ roles, including the gods eye roles!
     */
    protected String[] roles;

    /**
     *  All gods eye roles!
     */
    protected HashSet godseyeRoles;
    /**
     *  All active units for each role, hashed on the role name. A unit is
     *  active for a role if the role can see the unit, either because the role
     *  observes the unit or because the unit is inside the visibility range of
     *  some unit that the role observes.
     */
    protected HashMap activeUnits;
    /**
     *  All unit types in the game, hashed on their names.
     */
    protected HashMap unitTypes;
    /**
     *  For each role, the set of units that the role observes, hashed on the
     *  role name.
     */
    protected HashMap rolesToObserverUnitsMap;
    /**
     *  For each role, the set of units alien to that role, hashed on the role
     *  name.
     */
    protected HashMap rolesToAlienUnitsMap;
    /**
     *  An array of sets, one set for each x coordinate on the map. Each set
     *  contains the units that currently is at a point with that x coordinate.
     */
    protected HashSet[] xArray;
    /**
     *  An array of sets, one set for each y coordinate on the map. Each set
     *  contains the units that currently is at a point with that y coordinate.
     */
    protected HashSet[] yArray;

    // NEVER use negative integers as handle for units!
    // The client uses negative handles for ghost units!
    /**
     *  Description of the Field
     */
    private int nextAvailableHandle = 0;



    /**
     *  Constructor for the UnitManager object
     *
     * @param  roles         The roles in the game.
     * @param  godseyeRoles  The roles with god's eye status.
     * @param  automatonMap  The instance of the class that encapsulates the
     *      map.
     */
    public UnitManager(String[] roles, Set godseyeRoles, AutomatonMap automatonMap) {
        units = new HashMap();
        activeUnits = new HashMap();
        unitTypes = new HashMap();
        rolesToObserverUnitsMap = new HashMap();
        rolesToAlienUnitsMap = new HashMap();

        int xmax = automatonMap.getXBound();
        int ymax = automatonMap.getYBound();
        xArray = new HashSet[xmax + 1];
        yArray = new HashSet[ymax + 1];
        for (int i = 0; i < xArray.length; i++) {
            xArray[i] = new HashSet();
        }
        for (int i = 0; i < yArray.length; i++) {
            yArray[i] = new HashSet();
        }

        this.roles = roles;
        this.godseyeRoles = new HashSet(godseyeRoles);
        for (int i = 0; i < roles.length; i++) {
            HashSet roleUnits = new HashSet();
            activeUnits.put(roles[i], roleUnits);
            HashSet observerUnits = new HashSet();
            rolesToObserverUnitsMap.put(roles[i], observerUnits);
            HashSet alienUnits = new HashSet();
            rolesToAlienUnitsMap.put(roles[i], alienUnits);
        }
    }


    /**
     *  Gets the Roles attribute of the UnitManager object
     *
     * @return    The array of role names.
     */
    public String[] getRoles() {
        return roles;
    }


    /**
     *  Gets the GodsEyeRoles attribute of the UnitManager object
     *
     * @return    The set of roles with god's eye status.
     */
    public Set getGodsEyeRoles() {
        return godseyeRoles;
    }


    /**
     *  Determines if a given role has status as god's eye.
     *
     * @param  role  A role name.
     * @return       true if the role has god's eye status, false otherwise
     */
    public boolean isGodseye(String role) {
        return godseyeRoles.contains(role);
    }


    /**
     *  Gets all units in the game.
     *
     * @return    A collection of all units.
     */
    public Collection getAllUnits() {
        return units.values();
    }


    /**
     *  Gets a unit, given its handle.
     *
     * @param  handle  A handle of a unit.
     * @return         The unit with the given handle, or null, if no such unit
     *      exists.
     */
    public Unit getUnit(int handle) {
        return (Unit) units.get(new Integer(handle));
    }


    /**
     *  Gets a unit type, given its name.
     *
     * @param  unitType  The name of the unit type.
     * @return           The unit type.
     */
    public UnitType getUnitType(String unitType) {
        return (UnitType) unitTypes.get(unitType);
    }


    /**
     *  Gets the set of blocked actions of a given unit.
     *
     * @param  u  The unit.
     * @return    The set of blocked actions.
     */
    public Set getBlockedActions(Unit u) {
        return u.getBlockedActions();
    }


    /**
     *  Gets the set of non-blocked actions of a given unit.
     *
     * @param  u  The unit.
     * @return    The set of non-blocked actions.
     */
    public Set getUnblockedActions(Unit u) {
        UnitType uType = u.getUnitType();
        Set retSet = new HashSet(uType.getActionNames());
        retSet.removeAll(u.getBlockedActions());
        return retSet;
    }


    /**
     *  Gets the BlockedActionStrings attribute of the UnitManager object
     *
     * @return    The BlockedActionStrings value
     */
    /*
     * public Set getBlockedActionStrings(Unit u) {
     * UnitType uType = u.getUnitType();
     * Set retSet = new HashSet();
     * Iterate over all blocked actions
     * Iterator it = u.getBlockedActions().iterator();
     * while (it.hasNext()) {
     * ActionDefinition actDef = uType.getActionDefinition((String) it.next());
     * retSet.add(actDef);
     * }
     * return retSet;
     * }
     */

    /**
     *  Gets the BlockedActionStrings attribute of the UnitManager object
     *
     *  Gets the UnblockedActionStrings attribute of the UnitManager object
     *
     * @return    The BlockedActionStrings value
     * @return    The UnblockedActionStrings value
     */
    /*
     * public Set getUnblockedActionStrings(Unit u) {
     * UnitType uType = u.getUnitType();
     * Set fooSet = new HashSet(uType.getActionNames());
     * fooSet.removeAll(u.getBlockedActions());
     * Set retSet = new HashSet();
     * Iterator it = fooSet.iterator();
     * while (it.hasNext()) {
     * ActionDefinition actDef = uType.getActionDefinition((String) it.next());
     * retSet.add(actDef);
     * }
     * return retSet;
     * }
     */

    /**
     *  Gets the BlockedActionStrings attribute of the UnitManager object
     *
     *  Gets the UnblockedActionStrings attribute of the UnitManager object
     *
     *  Gets the handles of all units in the game.
     *
     * @return    The BlockedActionStrings value
     * @return    The UnblockedActionStrings value
     * @return    An array of handles.
     */
    public int[] getHandles() {
        Set keys = units.keySet();
        int[] handles = new int[keys.size()];
        Integer[] intKeys = (Integer[]) keys.toArray(new Integer[0]);

        for (int i = 0; i < handles.length; i++) {
            handles[i] = intKeys[i].intValue();
        }

        return handles;
    }


    /**
     *  Gets the handles of all units that a given role commands.
     *
     * @param  role  The name of the role.
     * @return       An array of handles that the role commands.
     */
    public int[] getCommandingUnits(String role) {
        Iterator commandUnits = units.values().iterator();
        Iterator handleIt;
        HashSet handles = new HashSet();
        int[] intHandles;
        int handle = 0;

        while (commandUnits.hasNext()) {
            Unit u = (Unit) commandUnits.next();
            if (role.equals(u.getCommander())) {
                handles.add(new Integer(u.getHandle()));
            }
        }

        intHandles = new int[handles.size()];
        handleIt = handles.iterator();
        while (handleIt.hasNext()) {
            intHandles[handle++] = ((Integer) handleIt.next()).intValue();
        }

        return intHandles;
    }


    /**
     *  Gets the visibility range of a given unit.
     *
     * @param  u  The unit.
     * @return    The range of the unit.
     */
    public int getRange(Unit u) {
        return u.getUnitType().getRange();
    }


    /**
     *  Gets the names of all roles that observes a given unit.
     *
     * @param  unit  The unit.
     * @return       An array of role names observing the unit.
     */
    public String[] getRolesObservingUnit(Unit unit) {
        // TODO
        // Add index for this method! Must be a O(1)-lookup!
        return (String[]) unit.getObservers().toArray(new String[0]);
    }


    /**
     *  Gets the names of all roles that is alien to a given unit.
     *
     * @param  unit  A unit.
     * @return       An array of role names alien to the unit.
     */
    public String[] getRolesAlienToUnit(Unit unit) {
        // TODO
        // Add index for this method! Must be a O(1)-lookup!
        Set s = new HashSet();
        for (int i = 0; i < roles.length; i++) {
            if (!isGodseye(roles[i])) {
                s.add(roles[i]);
            }
        }
        s.removeAll(unit.getObservers());
        return (String[]) s.toArray(new String[0]);
    }


    /**
     *  Gets the set of units that a given role observes.
     *
     * @param  role  The role name.
     * @return       The set of units that the role observes.
     */
    public Set getObservingUnits(String role) {
        return (Set) rolesToObserverUnitsMap.get(role);
    }


    /**
     *  Gets the set of units that is alien to a given role.
     *
     * @param  role  The role name.
     * @return       The set of alien units.
     */
    public Set getAlienUnits(String role) {
        return (Set) rolesToAlienUnitsMap.get(role);
    }


    /**
     *  Gets the set of units at a given position.
     *
     * @param  x  The x coordinate of the position.
     * @param  y  The y coordinate of the position.
     * @return    The set of units at the position.
     */
    public Set getUnitsAtAutomatonPosition(int x, int y) {
        HashSet s = new HashSet(xArray[x]);
        s.retainAll(yArray[y]);
        return s;
    }


    /**
     *  Determines if a given is active for a given role.
     *
     * @param  role  The name of the role.
     * @param  unit  The handle of the unit.
     * @return       true if the unit is active for the role, false otherwise.
     */
    public boolean isUnitActive(String role, int unit) {
        return ((HashSet) activeUnits.get(role)).contains(getUnit(unit));
    }


    /**
     *  Determines if a given role commands any unit.
     *
     * @param  role  The name of the role.
     * @return       true if there exists a unit which the role commands, false
     *      otherwise.
     */
    public boolean isRoleCommander(String role) {
        Iterator it = units.values().iterator();
        while (it.hasNext()) {
            Unit u = (Unit) it.next();
            if (role.equals(u.getCommander())) {
                return true;
            }
        }
        return false;
    }


    /**
     *  Gets all unit types in the game.
     *
     * @return    The collection of all unit types.
     */
    public Collection getUnitTypes() {
        return unitTypes.values();
    }


    /**
     *  Creates a unit of a given unit type.
     *
     * @param  unitTypeName  The name of the unit type.
     * @return               A new unit of the given type.
     */
    public Unit createUnit(String unitTypeName) {
        // Unit factory method, creates a unit of the given unit type,
        // sets the handle, and stores it.
        UnitType unitType = (UnitType) unitTypes.get(unitTypeName);
        if (unitType == null) {
            throw new IllegalArgumentException("Unknown unit type '" + unitTypeName + "'");
        }
        try {
            Unit u = (Unit) unitType.getUnitClass().newInstance();
            u.setHandle(nextAvailableHandle++);
            addUnit(u);
            return u;
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("Couldn't instanciate Unit class!");
        } catch (InstantiationException e) {
            throw new IllegalArgumentException("Couldn't instanciate Unit class!");
        }
    }


    /**
     *  Mark a given role as observer for a given unit.
     *
     * @param  role  The role name.
     * @param  u     The unit.
     */
    public void reportObserverForUnit(String role, Unit u) {
        // Move unit from set of alien units for this role to the
        // set of observer units.
        HashSet set = (HashSet) rolesToObserverUnitsMap.get(role);
        set.add(u);
        set = (HashSet) rolesToAlienUnitsMap.get(role);
        set.remove(u);
    }


    /**
     *  Adds a new unit type.
     *
     * @param  unitType  The unit type to be added.
     */
    public void addUnitType(UnitType unitType) {
        unitTypes.put(unitType.getName(), unitType);
    }


    /**
     *  Initialises a unit.
     *
     * @param  unit  The unit.
     */
    public void initializeUnitPosition(Unit unit) {
        xArray[unit.getIntX()].add(unit);
        yArray[unit.getIntY()].add(unit);
    }


    /**
     *  Report that a units position has been updated.
     *
     * @param  unit  The unit.
     * @param  oldX  The x coordinate of the old position.
     * @param  oldY  The y coordinate of the old position.
     */
    public void reportUpdatedUnitPosition(Unit unit, int oldX, int oldY) {
        xArray[oldX].remove(unit);
        yArray[oldY].remove(unit);
        xArray[unit.getIntX()].add(unit);
        yArray[unit.getIntY()].add(unit);
    }


    /**
     *  Mark a unit as active for a given role.
     *
     * @param  role  The name of the role.
     * @param  unit  The unit.
     * @return       true if the unit was not active already, false otherwise
     */
    public boolean activateUnit(String role, Unit unit) {
        HashSet roleUnits = (HashSet) activeUnits.get(role);
        return roleUnits.add(unit);
    }


    /**
     *  Mark a unit as non-active for a given role.
     *
     * @param  role  The name of the role.
     * @param  unit  The unit.
     * @return       true if the unit was active for the given role, false
     *      otherwise
     */
    public boolean deactivateUnit(String role, Unit unit) {
        HashSet roleUnits = (HashSet) activeUnits.get(role);
        return roleUnits.remove(unit);
    }


    /**
     *  Delete a unit from the game.
     *
     * @param  unit        The unit to be deleted.
     * @param  lastKnownX  The x coordinate of the last position of the unit.
     * @param  lastKnownY  The y coordinate of the last position of the unit.
     */
    public void deleteUnit(Unit unit, double lastKnownX, double lastKnownY) {
        // Remove unit from the hashset grid!
        int x = (int) lastKnownX;
        int y = (int) lastKnownY;
        xArray[x].remove(unit);
        yArray[y].remove(unit);

        // Remove unit from all observer and alien sets!
        Iterator it = rolesToObserverUnitsMap.values().iterator();
        while (it.hasNext()) {
            HashSet s = (HashSet) it.next();
            s.remove(unit);
        }
        it = rolesToAlienUnitsMap.values().iterator();
        while (it.hasNext()) {
            HashSet s = (HashSet) it.next();
            s.remove(unit);
        }

        // Done!

    }


    /**
     *  Adds a unit to the game.
     *
     * @param  u  The unit to be added.
     */
    private void addUnit(Unit u) {
        units.put(new Integer(u.getHandle()), u);
        // Set unit as alien for all roles!
        for (int i = 0; i < roles.length; i++) {
            if (!isGodseye(roles[i])) {
                HashSet alienUnits = (HashSet) rolesToAlienUnitsMap.get(roles[i]);
                alienUnits.add(u);
            }
        }
    }

}
