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

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.core;

import java.util.*;

/**
 *  This class represents a unit in the game, and must be subclassed by every
 *  game implementation. Most of the attributes is set up in configuration
 *  files, and the things to be done for the game programmer is to initialize
 *  all properties in a constructor, using the method setProperty.
 *
 * @author     Henrik Bäärnhielm, Karl Hasselström, Henrik Hägerström
 * @created    April 27, 2001
 */
public abstract class Unit implements Comparable {

    /**
     *  The handle identifies the unit during communication with clients.
     */
    protected int handle;
    /**
     *  This is the action that the unit is currently executing.
     */
    protected Action activeAction;
    /**
     *  The name of the unit appears in the client, and is set up in a
     *  configuration file.
     */
    protected String name;
    /**
     *  The type of the unit appears in the client, and is set up in a
     *  configuration file. This attribute also identifies to the server which
     *  UnitType this unit is an instance of.
     */
    protected String type;
    /**
     *  An instance of the unit type of this unit.
     */
    protected UnitType unitType;
    /**
     *  Flag to indicate if the unit is destroyed.
     */
    protected boolean isDestroyed = false;
    /**
     *  The name of the role that commands this unit, that is, the role that has
     *  the right to execute actions with the unit.
     */
    protected String commander;
    /**
     *  The set of observers contains the names of all roles that has observing
     *  rights on this unit, that is, that sees everything this unit sees.
     */
    protected HashSet observers = new HashSet();
    /**
     *  The x-coordinate of the unit's position.
     */
    protected double x;
    /**
     *  The y-coordinate of the unit's position.
     */
    protected double y;
    /**
     *  The x-coordinate of the unit's position as an integer. This value should
     *  be Math.floor(x).
     */
    protected int intX;
    /**
     *  The y-coordinate of the unit's position as an integer. This value should
     *  be Math.floor(y).
     */
    protected int intY;
    /**
     *  Container for the properties of the unit. A property is represented as a
     *  String. The keys of the Map are property names, and the values are
     *  property values.
     */
    protected TypedProperties properties = new TypedProperties();
    /**
     *  Contains all properties that have been updated during the current tick,
     *  and the new values.
     */
    protected HashMap updatedProperties = new HashMap();
    /**
     *  Contains all Actions that have been blocked, that is, have been stopped
     *  during execution for some reason.
     */
    protected HashSet blockedActions = new HashSet();
    /**
     *  ???
     */
    protected HashSet updatedActions = new HashSet();


    /**
     *  Constructor for the Unit object
     */
    public Unit() {
    }


    /**
     *  Sets the Name attribute of the Unit object
     *
     * @param  _name  The new Name value
     */
    public void setName(String _name) {
        name = _name;
    }


    /**
     *  Sets the UnitType attribute of the Unit object
     *
     * @param  _type  The new unit type
     */
    public void setUnitType(String _type) {
        type = _type;

        // Load default properties
        unitType = ReferenceHolder.getUnitManager().getUnitType(type);
        properties.loadProperties(unitType.getProperties());
    }


    /**
     *  Sets the Handle attribute of the Unit object
     *
     * @param  _handle  The new handle
     */
    public void setHandle(int _handle) {
        handle = _handle;
    }


    /**
     *  Sets the Position attribute of the Unit object
     *
     * @param  _x  The x coordinate of the new position
     * @param  _y  The y coordinate of the new position
     */
    public void setPosition(double _x, double _y) {
        x = _x;
        y = _y;
        // Java's default operation when casting a double to an int
        // is to floor the double, i.e. x = 1.94 -> intX = 1
        intX = (int) x;
        intY = (int) y;
    }


    /**
     *  Sets the Commander attribute of the Unit object
     *
     * @param  _commander  The new Commander value
     */
    public void setCommander(String _commander) {
        commander = _commander;
        observers.add(_commander);
        ReferenceHolder.getUnitManager().reportObserverForUnit(commander, this);
    }


    /**
     *  Sets the ActiveAction attribute of the Unit object
     *
     * @param  action  The new ActiveAction value
     */
    public void setActiveAction(Action action) {
        activeAction = action;
    }


    /**
     *  Sets a property to a Double value.
     *
     * @param  name   The name of the property.
     * @param  value  The value of the property.
     */
    public void setProperty(String name, double value) {
        properties.setProperty(name, value);
        updatedProperties.put(name, "" + value);
    }


    /**
     *  Sets a property to an Integer value.
     *
     * @param  name   The name of the property.
     * @param  value  The value of the property.
     */
    public void setProperty(String name, int value) {
        properties.setProperty(name, value);
        updatedProperties.put(name, "" + value);
    }


    /**
     *  Sets a property to a String value.
     *
     * @param  name   The name of the property.
     * @param  value  The value of the property.
     */
    public void setProperty(String name, String value) {
        properties.setProperty(name, value);
        updatedProperties.put(name, value);
    }


    /**
     *  Gets the name of the unit.
     *
     * @return    The name
     */
    public String getName() {
        return name;
    }


    /**
     *  Gets the unit type of the unit.
     *
     * @return    The unit type
     */
    public UnitType getUnitType() {
        return unitType;
    }


    /**
     *  Gets the handle of the unit.
     *
     * @return    The handle
     */
    public int getHandle() {
        return handle;
    }


    /**
     *  Gets the x coordinate of the unit's position.
     *
     * @return    The x coordinate.
     */
    public double getX() {
        return x;
    }


    /**
     *  Gets the y coordinate of the unit's position.
     *
     * @return    The y coordinate.
     */
    public double getY() {
        return y;
    }


    /**
     *  Gets the x coordinate of the unit's position, cast as an integer, using
     *  the floor function
     *
     * @return    The integer x coordinate.
     */
    public int getIntX() {
        return intX;
    }


    /**
     *  Gets the y coordinate of the unit's position, cast as an integer, using
     *  the floor function.
     *
     * @return    The integer y coordinate.
     */
    public int getIntY() {
        return intY;
    }


    /**
     *  Gets the name of the role of the commander of the unit.
     *
     * @return    The name of the commander.
     */
    public String getCommander() {
        return commander;
    }


    /**
     *  Gets the currently executing Action.
     *
     * @return    The active action.
     */
    public Action getActiveAction() {
        return activeAction;
    }


    /**
     *  Determines if a given role is an observer of the unit.
     *
     * @param  role  The name of the role.
     * @return       true if the role is an observer, false otherwise
     */
    public boolean isObserver(String role) {
        return observers.contains(role);
    }


    /**
     *  Gets the set of observers of the unit.
     *
     * @return    The set of observers.
     */
    public Set getObservers() {
        return observers;
    }


    /**
     *  Gets the map of properties of the unit. The keys of the Map are property
     *  names, and the values are property values.
     *
     * @return    The map of properties.
     */
    public Map getProperties() {
        return properties;
    }


    /**
     *  Gets a specific property value, given the property name.
     *
     * @param  name  The property name.
     * @return       The property value.
     */
    public Object getProperty(String name) {
        return properties.getProperty(name);
    }


    /**
     *  Gets the PropertyType attribute of the Unit object
     *
     * @param  name  Description of Parameter
     * @return       The PropertyType value
     */
    public String getPropertyType(String name) {
        return properties.getPropertyType(name);
    }


    /**
     *  Gets a specific property value that is known to be a double value.
     *
     * @param  name  The property name.
     * @return       The property value.
     */
    public double getDoubleProperty(String name) {
        return properties.getDoubleProperty(name);
    }


    /**
     *  Gets a specific property value that is known to be an integer value.
     *
     * @param  name  The property name.
     * @return       The property value
     */
    public int getIntProperty(String name) {
        return properties.getIntProperty(name);
    }


    /**
     *  Gets a specific property value that is known to be a String.
     *
     * @param  name  The property name.
     * @return       The property value.
     */
    public String getStringProperty(String name) {
        return properties.getStringProperty(name);
    }


    /**
     *  Gets the map of all properties that have been updated during the current
     *  tick.
     *
     * @return    The updated properties.
     */
    public HashMap getUpdatedProperties() {
        return updatedProperties;
    }


    /**
     *  Gets the set of all actions that have been updated.
     *
     * @return    The UpdatedActions value
     */
    public Set getUpdatedActions() {
        return updatedActions;
    }


    /**
     *  Gets the set of actions that have been blocked during the last tick.
     *  That is, the execution of the actions have stopped because they could
     *  not continue, for some reason.
     *
     * @return    The set of blocked actions.
     */
    public Set getBlockedActions() {
        return blockedActions;
    }


    /**
     *  Determines if the unit has been destroyed.
     *
     * @return    True if the unit has been destroyed, false otherwise.
     */
    public boolean isDestroyed() {
        return isDestroyed;
    }


    /**
     *  Adds a new observer of the unit.
     *
     * @param  role  The name of the role that is to be an observer of the unit.
     */
    public void addObserver(String role) {
        observers.add(role);
        ReferenceHolder.getUnitManager().reportObserverForUnit(role, this);
    }


    /**
     *  Remove a role from the set of observers of the unit, so the role is no
     *  longer an observer.
     *
     * @param  role  The name of the role.
     * @return       true if the role was an observer of the unit, false
     *      otherwise.
     */
    public boolean removeObserver(String role) {
        return observers.remove(role);
    }


    /**
     *  Clears the set of updated properties.
     */
    public void resetUpdatedProperties() {
        updatedProperties.clear();
    }


    /**
     *  Clears the set of updates actions.
     */
    public void resetUpdatedActions() {
        updatedActions.clear();
    }


    /**
     *  Block an action.
     *
     * @param  actionName  The name of the action.
     */
    public void addBlockedAction(String actionName) {
        blockedActions.add(actionName);
        updatedActions.add(actionName);
    }


    /**
     *  Unblock an action.
     *
     * @param  actionName  The name of the action.
     */
    public void removeBlockedAction(String actionName) {
        blockedActions.remove(actionName);
        updatedActions.add(actionName);
    }


    /**
     *  Determines if the unit can see a specific automaton, that is, if the
     *  given position is inside the units visibility range.
     *
     * @param  _x  The x coordinate of the position.
     * @param  _y  The x coordinate of the position.
     * @return     true if the unit can see the position, false otherwise.
     */
    public boolean canSeeAutomatonAtPosition(int _x, int _y) {
        int range = unitType.getRange();
        // Check bounding box
        if (_x < (intX - range) ||
                _x > (intX + range) ||
                _y < (intY - range) ||
                _y > (intY + range)) {
            return false;
        }
        // Calculate yIndex (always >= 0 and < 2*range + 1)
        // Index range (i.e. row nr range+1) is the 'center row' of the circle.
        // So, take the center + the offset to get the correct index!
        int yIndex = (range) + (_y - intY);
        int xStart = unitType.getVisionStart()[yIndex] + intX;
        int xStop = unitType.getVisionStop()[yIndex] + intX;
        // Check if _x is contained within the interval [xStart, xStop]
        if (xStart <= _x && _x <= xStop) {
            return true;
        }
        return false;
    }


    /**
     *  Mark the unit as destroyed.
     */
    public void markAsDestroyed() {
        isDestroyed = true;
    }


    /**
     *  Determines if the unit is the same as a given unit. The effect is that
     *  the handles of the units are compared, and the units are considered
     *  equal if the handles are equal. This method obeys the rules of the
     *  Comparable interface.
     *
     * @param  o  The given object, possibly a unit.
     * @return    true if the given object is a unit, and the same unit as the
     *      current unit.
     */
    public boolean equals(Object o) {
        if (!(o instanceof Unit)) {
            return false;
        }
        return (handle == ((Unit) o).getHandle());
    }


    /**
     *  Compares the unit to a given unit. The effect is that the handles are
     *  compared. This method obeys the rules of the Comparable interface.
     *
     * @param  o  The given object, possibly a unit.
     * @return    negative if this unit is less than the given unit, zero if the
     *      units are equal, and positive if this unit is greater than the given
     *      unit.
     */
    public int compareTo(Object o) {
        if (!(o instanceof Unit)) {
            return -1;
        }
        return new Integer(handle).compareTo(new Integer(((Unit) o).getHandle()));
    }


    /**
     *  Returns a hash code of the unit.
     *
     * @return    The hash code.
     */
    public int hashCode() {
        return new Integer(handle).hashCode();
    }
}
