/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/

package jessrobocode;

import java.io.*;

import java.util.Vector;
import java.util.Enumeration;

import jess.*;

import robocode.*;
import robocode.security.RobocodeClassLoader;

/**
 * <p>
 * Title: JessRobot
 * </p>
 * <p>
 * Description: An adapting interface between Robocode and Jess
 * </p>
 * <p>
 * Universita` degli Studi di Pavia
 * </p>
 * 
 * @author Marco Piastra
 * @version 2006-6-16
 */


abstract public class JessRobot extends AdvancedRobot {

    // Template filename
    public static final String TemplateFilename = "JessRobot.clp";

    protected Rete rete = null;

    protected Vector facts = null;

    // Templated methods
    abstract public String jessRobotFilename();

    // Called by robocode
    public void run() {

        // Initialize
        initialize();

        while (true) {

            try {
                Vector oldFacts;

                // Cleanup
                resetVariables();

                // Add default event
                facts.addElement(newFact("NewTurn"));

                // Add sensor readings
                getValues(facts);

                // Assert all facts
                Enumeration enume = facts.elements();

                while (enume.hasMoreElements()) {
                    Fact fact = (Fact) enume.nextElement();

                    // out.println("Asserting: " + fact);
                    rete.assertFact(fact);
                }

                oldFacts = facts;
                facts = new Vector();

                // Run rete
                rete.run();

                // Transfer action requests to Robocode
                setValues();

                // Tell Robocode to execute all actions
                execute();

                // Retract all facts
                enume = oldFacts.elements();

                while (enume.hasMoreElements()) {
                    Fact fact = (Fact) enume.nextElement();

                    // out.println("Retracting: " + fact);
                    rete.retract(fact);
                }
            } catch (JessException e) {
                e.printStackTrace(out);

                throw new RuntimeException("Internal error: " + e);
            }
        }
    }

    /**
     * initialize: Create Jess Rete and initialize it.
     */
    public void initialize() {

        try {
            Value result;
            String dir = ((RobocodeClassLoader) getClass().getClassLoader())
                    .getClassDirectory();
            String filename = null;

            rete = new Rete();
            facts = new Vector();

            rete.addOutputRouter("ROBOCODEOUT", new PrintWriter(out, true));
            
            filename = dir + File.separator + TemplateFilename;

            out.println("Loading file " + filename);

            if (File.separator.equals("\\")) {
                filename = filename.replaceAll("\\\\", "\\\\\\\\");
            }
            result = rete.executeCommand("(batch \"" + filename + "\")");

            out.println("Jess result: " + result);

            filename = dir + File.separator + jessRobotFilename();

            out.println("Loading file " + filename);

            if (File.separator.equals("\\")) {
                filename = filename.replaceAll("\\\\", "\\\\\\\\");
            }
            result = rete.executeCommand("(batch \"" + filename + "\")");

            out.println("Jess result: " + result);

            reset();
        } catch (JessException e) {
            out.println("Whoops! Could not initialize Jess");

            e.printStackTrace();
            throw new RuntimeException("Internal error: " + e);
        }
    }

    /**
     * resetVariables: restore variables to their null value
     */
    public void resetVariables() throws JessException {

        // Read AND Write Variables
        rete.store("AdjustGunForRobotTurn", null);
        rete.store("AdjustRadarForGunTurn", null);
        rete.store("AdjustRadarForRobotTurn", null);

        // Write only Variables (AdvancedRobot)
        rete.store("Ahead", null);
        rete.store("Back", null);
        rete.store("Fire", null);
        rete.store("MaxTurnRate", null);
        rete.store("MaxVelocity", null);
        rete.store("Resume", null);
        rete.store("Stop", null);
        rete.store("TurnGunLeft", null);
        rete.store("TurnGunRight", null);
        rete.store("TurnLeft", null);
        rete.store("TurnRadarLeft", null);
        rete.store("TurnRadarRight", null);
        rete.store("TurnRight", null);
    }

    /**
     * getValues: get all Robocode values and make them visibile through Jess
     * facts
     */
    public void getValues(Vector facts) throws JessException {

        // Once-only Facts (Robot)
        facts.addElement(newFact("BattleFieldHeight", getBattleFieldHeight()));
        facts.addElement(newFact("BattleFieldWidth", getBattleFieldWidth()));

        // Facts (Robot)
        facts.addElement(newFact("Energy", getEnergy()));
        facts.addElement(newFact("GunCoolingRate", getGunCoolingRate()));
        facts.addElement(newFact("GunHeading", getGunHeading()));
        facts.addElement(newFact("GunHeat", getGunHeat()));
        facts.addElement(newFact("Heading", getHeading()));
        facts.addElement(newFact("Height", getHeight()));
        facts.addElement(newFact("Name", getName()));
        facts.addElement(newFact("NumRounds", getNumRounds()));
        facts.addElement(newFact("Others", getOthers()));
        facts.addElement(newFact("RadarHeading", getRadarHeading()));
        facts.addElement(newFact("RoundNum", getRoundNum()));
        facts.addElement(newFact("Time", getTime()));
        facts.addElement(newFact("Velocity", getVelocity()));
        facts.addElement(newFact("Width", getWidth()));
        facts.addElement(newFact("X", getX()));
        facts.addElement(newFact("Y", getY()));

        // Facts (AdvancedRobot)
        facts.addElement(newFact("DistanceRemaining", getDistanceRemaining()));
        facts.addElement(newFact("GunTurnRemaining", getGunTurnRemaining()));
        facts
                .addElement(newFact("RadarTurnRemaining",
                        getRadarTurnRemaining()));
        facts.addElement(newFact("TurnRemaining", getTurnRemaining()));

        // Read AND Write Variables
        facts.addElement(newFact("AdjustGunForRobotTurn",
                isAdjustGunForRobotTurn()));
        facts.addElement(newFact("AdjustRadarForGunTurn",
                isAdjustRadarForGunTurn()));
        facts.addElement(newFact("AdjustRadarForRobotTurn",
                isAdjustRadarForRobotTurn()));
    }

    /**
     * setValues: set all Robocode values according to the corresponding Jess
     * variables
     */
    public void setValues() throws JessException {

        Value value = null;
        boolean temp;

        // Read AND Write Variables
        value = rete.fetch("AdjustGunForRobotTurn");

        if (value != null && value.type() == RU.ATOM) {
            temp = new Boolean(value.atomValue(rete.getGlobalContext()))
                    .booleanValue();

            setAdjustGunForRobotTurn(temp);
        }

        value = rete.fetch("AdjustRadarForGunTurn");

        if (value != null && value.type() == RU.ATOM) {
            temp = new Boolean(value.atomValue(rete.getGlobalContext()))
                    .booleanValue();

            setAdjustRadarForGunTurn(temp);
        }

        value = rete.fetch("AdjustRadarForRobotTurn");

        if (value != null && value.type() == RU.ATOM) {
            temp = new Boolean(value.atomValue(rete.getGlobalContext()))
                    .booleanValue();

            setAdjustRadarForRobotTurn(temp);
        }

        // Write only Variables (AdvancedRobot)
        value = rete.fetch("Ahead");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setAhead(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("Back");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setBack(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("Fire");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setFire(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("MaxTurnRate");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setMaxTurnRate(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("MaxVelocity");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setMaxVelocity(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("Resume");

        if (value != null && value.type() == RU.ATOM) {
            temp = new Boolean(value.atomValue(rete.getGlobalContext()))
                    .booleanValue();

            if (temp)
                setResume();
        }

        value = rete.fetch("Stop");

        if (value != null && value.type() == RU.ATOM) {
            temp = new Boolean(value.atomValue(rete.getGlobalContext()))
                    .booleanValue();

            if (temp)
                setStop();
        }

        value = rete.fetch("TurnGunLeft");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setTurnGunLeft(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("TurnGunRight");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setTurnGunRight(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("TurnLeft");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setTurnLeft(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("TurnRadarLeft");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setTurnRadarLeft(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("TurnRadarRight");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setTurnRadarRight(value.floatValue(rete.getGlobalContext()));
        }

        value = rete.fetch("TurnRight");

        if (value != null
                && (value.type() == RU.FLOAT || value.type() == RU.INTEGER)) {
            setTurnRight(value.floatValue(rete.getGlobalContext()));
        }

    }

    /* Event Handling */

    public void onBulletHit(BulletHitEvent event) {
        try {
            Fact fact = newFact("BulletHitEvent");

            setSlotValue(fact, "Energy", event.getEnergy());
            setSlotValue(fact, "Name", event.getName());

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onBulletHitBullet(BulletHitBulletEvent event) {
        try {
            Fact fact = newFact("BulletHitBulletEvent");

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onBulletMissed(BulletMissedEvent event) {
        try {
            Fact fact = newFact("BulletMissedEvent");

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onHitByBullet(HitByBulletEvent event) {
        try {
            Fact fact = newFact("HitByBulletEvent");

            setSlotValue(fact, "Bearing", event.getBearing());
            setSlotValue(fact, "BearingRadians", event.getBearingRadians());
            setSlotValue(fact, "Heading", event.getHeading());
            setSlotValue(fact, "HeadingRadians", event.getHeadingRadians());
            setSlotValue(fact, "Name", event.getName());
            setSlotValue(fact, "Power", event.getPower());
            setSlotValue(fact, "Velocity", event.getVelocity());

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onHitRobot(HitRobotEvent event) {
        try {
            Fact fact = newFact("HitRobotEvent");

            setSlotValue(fact, "Bearing", event.getBearing());
            setSlotValue(fact, "BearingRadians", event.getBearingRadians());
            setSlotValue(fact, "Energy", event.getEnergy());
            setSlotValue(fact, "MyFault", event.isMyFault());
            setSlotValue(fact, "Name", event.getName());

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onHitWall(HitWallEvent event) {

        // Duplicates are rare but possible
        if (containsEvent("HitWallEvent", facts))
            return;

        try {
            Fact fact = newFact("HitWallEvent");

            setSlotValue(fact, "Bearing", event.getBearing());
            setSlotValue(fact, "BearingRadians", event.getBearingRadians());

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onRobotDeath(RobotDeathEvent event) {
        try {
            Fact fact = newFact("RobotDeathEvent");

            setSlotValue(fact, "Name", event.getName());

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onScannedRobot(ScannedRobotEvent event) {
        try {
            Fact fact = newFact("ScannedRobotEvent");

            setSlotValue(fact, "Bearing", event.getBearing());
            setSlotValue(fact, "BearingRadians", event.getBearingRadians());
            setSlotValue(fact, "Energy", event.getEnergy());
            setSlotValue(fact, "Distance", event.getDistance());
            setSlotValue(fact, "Heading", event.getHeading());
            setSlotValue(fact, "HeadingRadians", event.getHeadingRadians());
            setSlotValue(fact, "Name", event.getName());
            setSlotValue(fact, "Velocity", event.getVelocity());

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onSkippedTurn(SkippedTurnEvent event) {

        // Duplicates are rare but possible
        if (containsEvent("SkippedTurnEvent", facts))
            return;

        try {
            Fact fact = newFact("SkippedTurnEvent");

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onWin(WinEvent event) {
        try {
            Fact fact = newFact("WinEvent");

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    public void onDeath(DeathEvent event) {
        try {
            Fact fact = newFact("DeathEvent");

            facts.addElement(fact);
        } catch (JessException e) {

            // Should never occur
            e.printStackTrace(out);
        }
    }

    /**
     * reset: reset Jess Rete.
     */
    public void reset() {

        try {

            // Reset rete
            rete.reset();
        } catch (JessException e) {
            throw new RuntimeException("Internal error: " + e);
        }
    }

    /**
     * See if a given vector contains an event type
     */
    public boolean containsEvent(String eventType, Vector vector) {
        Fact fact;

        try {
            for (int i = 0; i < vector.size(); i++) {
                fact = (Fact) vector.elementAt(i);

                // out.println("Fact: " + fact + " get(0): " + fact.get(0));
                if (fact.get(0).equals(eventType)) {

                    return true;
                }
            }
        } catch (JessException e) {
            throw new RuntimeException("Internal error: " + e);
        }

        return false;
    }

    /**
     * Create a new fact, with just a name
     */
    public Fact newFact(String name) throws JessException {
        return new Fact(name, rete);
    }

    /**
     * Create a new fact, with a name and an int value
     */
    public Fact newFact(String name, int i) throws JessException {
        Fact fact = new Fact(name, rete);
        ValueVector vv = new ValueVector();
        vv.add(new Value(i, RU.INTEGER));
        fact.setSlotValue("__data", new Value(vv, RU.LIST));

        return fact;
    }

    /**
     * Create a new fact, with a name and a double value
     */
    public Fact newFact(String name, double v) throws JessException {
        Fact fact = new Fact(name, rete);
        ValueVector vv = new ValueVector();
        vv.add(new Value(v, RU.FLOAT));
        fact.setSlotValue("__data", new Value(vv, RU.LIST));

        return fact;
    }

    /**
     * Create a new fact, with a name and a boolean value
     */
    public Fact newFact(String name, boolean b) throws JessException {
        return newFact(name, String.valueOf(b));
    }

    /**
     * Create a new fact, with a name and a String (as ATOM)
     */
    public Fact newFact(String name, String s) throws JessException {
        Fact fact = new Fact(name, rete);
        ValueVector vv = new ValueVector();
        vv.add(new Value(s, RU.ATOM));
        fact.setSlotValue("__data", new Value(vv, RU.LIST));

        return fact;
    }

    /**
     * Add slot value, with a name and an int value
     */
    public void setSlotValue(Fact fact, String name, int i)
            throws JessException {
        fact.setSlotValue(name, new Value(i, RU.INTEGER));
    }

    /**
     * Add slot value, with a name and a double value
     */
    public void setSlotValue(Fact fact, String name, double v)
            throws JessException {
        fact.setSlotValue(name, new Value(v, RU.FLOAT));
    }

    /**
     * Add slot value, with a name and a boolean value
     */
    public void setSlotValue(Fact fact, String name, boolean b)
            throws JessException {
        setSlotValue(fact, name, String.valueOf(b));
    }

    /**
     * Add slot value, with a name and a String (as ATOM)
     */
    public void setSlotValue(Fact fact, String name, String s)
            throws JessException {
        fact.setSlotValue(name, new Value(s, RU.ATOM));
    }

}
