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

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.awt.Image;
import java.util.*;

import gecco.server.core.*;

/**
 *  This class represents a unit type. A unit type encapsulates information that
 *  is common to all units of the same type, such as the image of the unit used
 *  at the clients, the event handler for units of this type, the class that
 *  implements the unit.
 *
 * @author     Henrik Bäärnhielm, Karl Hasselström, Henrik Hägerström
 * @created    April 30, 2001
 */
public class UnitType {

    /**
     *  The name of the unit type.
     */
    String name;

    /**
     *  The image used by all units of this type at the clients.
     */
    byte[] imageData;

    /**
     *  Reference to the event handler for this unit type.
     */
    EventHandler eventHandler;

    /**
     *  The class that implements this unit type.
     */
    Class unitClass;

    /**
     *  The visibility range of units of this type.
     */
    int range;

    /**
     *  The square of the visibility range. Used in calculations.
     */
    double rangeSquare;

    /**
     *  ???
     */
    int[] visionStart;
    /**
     *  Description of the Field
     */
    int[] visionStop;

    /**
     *  All ActionDefinitions of this unit type, hashed on the action name.
     */
    HashMap actionDefs;

    /**
     *  The user-defined properties of this unit type, with default values.
     */
    private TypedProperties properties;


    /**
     *  Constructor for the UnitType object
     *
     * @param  _name  The name of the unit type.
     */
    public UnitType(String _name) {
        name = _name;
        actionDefs = new HashMap();
        properties = new TypedProperties();
        eventHandler = null;
        unitClass = null;
        imageData = null;
    }


    /**
     *  Sets the ImageData attribute of the UnitType object
     *
     * @param  _imageData  The new ImageData value
     */
    public void setImageData(byte[] _imageData) {
        imageData = _imageData;
    }


    /**
     *  Sets the visibility range of the unit type.
     *
     * @param  _range  The new range
     */
    public void setRange(int _range) {
        range = _range;
        rangeSquare = range * range;
        visionStart = new int[2 * range + 1];
        visionStop = new int[2 * range + 1];
        for (int i = 0; i < visionStart.length; i++) {
            visionStart[i] = Integer.MAX_VALUE;
            visionStop[i] = Integer.MIN_VALUE;
        }
        int xc = 0;
        int yc = range;
        int r = range;
        int x = 0;
        int y = r;
        int p = 3 - 2 * r;
        while (x <= y) {
            // Set the 4 rows
            // 6 -> 5
            setRow(xc - y, xc + y, yc + x);
            // 8 -> 7
            setRow(xc - y, xc + y, yc - x);
            // 2 -> 1
            setRow(xc - x, xc + x, yc + y);
            // 4 -> 3
            setRow(xc - x, xc + x, yc - y);
            /*
             * points.add(new Point(xc + x, yc + y));
             * points.add(new Point(xc - x, yc + y));
             * points.add(new Point(xc + x, yc - y));
             * points.add(new Point(xc - x, yc - y));
             * points.add(new Point(xc + y, yc + x));
             * points.add(new Point(xc - y, yc + x));
             * points.add(new Point(xc + y, yc - x));
             * points.add(new Point(xc - y, yc - x));
             */
            if (p < 0) {
                p += 4 * x++ + 6;
            } else {
                p += 4 * (x++ - y--) + 10;
            }
        }
    }


    /**
     *  Sets the EventHandler attribute of the UnitType object
     *
     * @param  evtHandlerClassName  The new EventHandler class name.
     */
    public void setEventHandler(String evtHandlerClassName) {
        try {
            Class cl = Class.forName(evtHandlerClassName, true, ReferenceHolder.getGameClassLoader());
            Object o = cl.newInstance();
            if (!(o instanceof EventHandler)) {
                throw new IllegalArgumentException("Not an EventHandler subclass!");
            }
            // Set eventhandler
            eventHandler = (EventHandler) o;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class not found\n" + e.getMessage());
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Not allowed to instanciate class\n" + e.getMessage());
        } catch (InstantiationException e) {
            throw new RuntimeException("Couldn't instanciate class\n" + e.getMessage());
        }
    }


    /**
     *  Sets the class that implements units of this type.
     *
     * @param  unitClassName  The name of the class.
     */
    public void setUnitClass(String unitClassName) {
        try {
            Class cl = Class.forName(unitClassName, true, ReferenceHolder.getGameClassLoader());
            Object o = cl.newInstance();
            if (!(o instanceof Unit)) {
                throw new IllegalArgumentException("Not a Unit subclass!");
            }
            // Set unitclass
            unitClass = cl;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class not found\n" + e.getMessage());
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Not allowed to instanciate class\n" + e.getMessage());
        } catch (InstantiationException e) {
            throw new RuntimeException("Couldn't instanciate class\n" + e.getMessage());
        }
    }


    /**
     *  Sets the DoubleProperty attribute of the UnitType object
     *
     * @param  name   The new DoubleProperty name
     * @param  value  The new DoubleProperty value
     */
    public void setProperty(String name, double value) {
        properties.setProperty(name, value);
    }


    /**
     *  Sets the IntProperty attribute of the UnitType object
     *
     * @param  name   The new IntProperty name
     * @param  value  The new IntProperty value
     */
    public void setProperty(String name, int value) {
        properties.setProperty(name, value);
    }


    /**
     *  Sets the StringProperty attribute of the UnitType object
     *
     * @param  name   The new IntProperty name
     * @param  value  The new IntProperty value
     */
    public void setProperty(String name, String value) {
        properties.setProperty(name, value);
    }


    /**
     *  Gets the Name attribute of the UnitType object
     *
     * @return    The Name value
     */
    public String getName() {
        return name;
    }


    /**
     *  Gets the ImageData attribute of the UnitType object
     *
     * @return    The ImageData value
     */
    public byte[] getImageData() {
        return imageData;
    }


    /**
     *  Gets the visibility range
     *
     * @return    The visibility range
     */
    public int getRange() {
        return range;
    }


    /**
     *  Gets the square of the visibility range.
     *
     * @return    The square of the range.
     */
    public double getRangeSquare() {
        return rangeSquare;
    }


    /**
     *  Gets the VisionStart attribute of the UnitType object
     *
     * @return    The VisionStart value
     */
    public int[] getVisionStart() {
        return visionStart;
    }


    /**
     *  Gets the VisionStop attribute of the UnitType object
     *
     * @return    The VisionStop value
     */
    public int[] getVisionStop() {
        return visionStop;
    }


    /**
     *  Gets the EventHandler attribute of the UnitType object
     *
     * @return    The EventHandler value
     */
    public EventHandler getEventHandler() {
        return eventHandler;
    }


    /**
     *  Gets the UnitClass attribute of the UnitType object
     *
     * @return    The UnitClass value
     */
    public Class getUnitClass() {
        return unitClass;
    }


    /**
     *  Gets the names of all actions associated with this unit type.
     *
     * @return    The set of names of actions.
     */
    public Set getActionNames() {
        return actionDefs.keySet();
    }


    /**
     *  Gets the definitions of all actions associated with this unit type.
     *
     * @return    The collection of action definitions.
     */
    public Collection getActionDefinitions() {
        return actionDefs.values();
    }


    /**
     *  Gets the ActionDefinition associated with a given action.
     *
     * @param  actionName  The name of the action.
     * @return             The ActionDefinition.
     */
    public ActionDefinition getActionDefinition(String actionName) {
        return (ActionDefinition) actionDefs.get(actionName);
    }


    /**
     *  Determines if a given action associated with this unit type is
     *  instantaneous. The action MUST be associated with this unit type.
     *
     * @param  actionName  The name of the action.
     * @return             true if the given action is instantaneous, false
     *      otherwise.
     */
    public boolean isActionInstantaneous(String actionName) {
        return ((ActionDefinition) actionDefs.get(actionName)).isInstantaneous;
    }


    /**
     *  Gets the class implementing a given class, associated with this unit
     *  type.
     *
     * @param  actionName  The name of the action.
     * @return             The class of the Action.
     */
    public Class getActionClass(String actionName) {
        return (Class) ((ActionDefinition) actionDefs.get(actionName)).actionClass;
    }


    /**
     *  Gets the description of an action associated with this unit type.
     *
     * @param  actionName  The name of the action.
     * @return             The description of the action.
     */
    public String getActionDescription(String actionName) {
        return (String) ((ActionDefinition) actionDefs.get(actionName)).description;
    }


    /**
     *  Gets the argument type of an action associated with this unit type.
     *
     * @param  actionName  The name of the action.
     * @return             The argument type of the action.
     */
    public String getActionArgumentType(String actionName) {
        return (String) ((ActionDefinition) actionDefs.get(actionName)).argumentType;
    }


    /**
     *  Gets the Properties attribute of the Unit object
     *
     * @return    The Properties value
     */
    public TypedProperties getProperties() {
        return properties;
    }


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


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


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


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


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


    /**
     *  Adds an action to the unit type.
     *
     * @param  name                    The name of the action.
     * @param  description             The description of the action.
     * @param  argumentType            The argument type of the action.
     * @param  actionHandlerClassName  The name of the class that implements the
     *      action.
     */
    public void addAction(String name,
            String description,
            String argumentType,
            String actionHandlerClassName) {
        if (actionDefs.containsKey(name)) {
            throw new IllegalArgumentException("Duplicate definition of action " + name + " found.");
        }
        try {
            Class cl = Class.forName(actionHandlerClassName, true, ReferenceHolder.getGameClassLoader());
            Object o = cl.newInstance();
            if (!(o instanceof Action)) {
                throw new IllegalArgumentException("Not a subclass of Action");
            }

            // Put class in HashMap (not instance of...)
            ActionDefinition actionDef = new ActionDefinition(name, cl, description, argumentType);
            actionDefs.put(name, actionDef);

        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class not found\n" + e.getMessage());
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Not allowed to instanciate class\n" + e.getMessage());
        } catch (InstantiationException e) {
            throw new RuntimeException("Couldn't instanciate class\n" + e.getMessage());
        }
    }


    /**
     *  Sets the Row attribute of the UnitType object
     *
     * @param  start  The new Row value
     * @param  stop   The new Row value
     * @param  y      The new Row value
     */
    private void setRow(int start, int stop, int y) {
        if (visionStart[y] > start) {
            visionStart[y] = start;
        }
        if (visionStop[y] < stop) {
            visionStop[y] = stop;
        }
    }

}
