package hypernova;

import java.util.Map;
import java.util.List;
import java.util.HashSet;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

import org.apache.log4j.Logger;

import hypernova.pilots.Pilot;
import hypernova.pilots.EmptyCockpit;

public class Ship extends Mass {
    public static final double BACK_LIMIT = -0.5;

    private static Map<String, Ship> cache = new HashMap<String, Ship>();
    private static Logger log = Logger.getLogger("Ship");

    public String name, info;

    private Weapon[] weapons;
    private Engine[] engines;
    private double enginestate;
    private boolean[] firestate = new boolean[0];
    private double turnleft, turnright;
    private Pilot pilot = new EmptyCockpit();
    private Collection<Mass> hold = new HashSet<Mass>();
    private long gold;

    /* Derived from the above. */
    private double thrust, maneuverability;
    private double mass;

    public Ship(String hullname) {
        this(Hull.get(hullname));
    }

    public Ship(Hull hull) {
        super(hull);
        weapons = new Weapon[hull.numWeapons()];
        firestate = new boolean[weapons.length];
        engines = new Engine[hull.numEngines()];
        calc();
    }

    public static Ship get(String name) {
        Ship ship = cache.get(name);
        if (ship != null) return ship.copy();
        String filename = "parts/" + name + ".ship";
        log.debug("Loading ship '" + name + "' (" + filename + ")");
        Properties props = new Properties();
        try {
            props.load(Ship.class.getResourceAsStream(filename));
        } catch (java.io.IOException e) {
            /* TODO handle this more gracefully. */
            log.error("Failed to load hull '" + name + "': " + e.getMessage());
            return null;
        }

        ship = new Ship(props.getProperty("hull"));
        ship.name = props.getProperty("name");
        ship.info = props.getProperty("info");
        String weapons = props.getProperty("weapons");
        if (weapons != null) {
            int i = 0;
            for (String w : weapons.split("\\s+")) {
                ship.setWeapon(w, i++);
            }
        }
        String engines = props.getProperty("engines");
        if (engines != null) {
            int i = 0;
            for (String e : engines.split("\\s+")) {
                ship.setEngine(e, i++);
            }
        }
        cache.put(name, ship);
        return ship.copy();
    }

    public Ship copy() {
        Ship copy = new Ship(hull.copy());
        copy.name = name;
        copy.info = info;
        for (int i = 0; i < weapons.length; i++)
            copy.setWeapon(weapons[i].copy(), i);
        for (int i = 0; i < engines.length; i++)
            copy.setEngine(engines[i], i);
        return copy;
    }

    public Ship setWeapon(String w, int slot) {
        return this.setWeapon(Weapon.get(w), slot);
    }

    public Ship setWeapon(Weapon w, int slot) {
        weapons[slot] = w;
        firestate[slot] = false;
        return this;
    }

    public Ship setEngines(boolean set) {
        enginestate = set ? thrust : 0.0;
	return this;
    }

    public Ship setEngines(double rate) {
        enginestate = Math.max(Math.min(rate, 1.0), BACK_LIMIT) * thrust;
	return this;
    }

    public Ship setEngine(String name, int slot) {
        return this.setEngine(Engine.get(name), slot);
    }

    public Ship setEngine(Engine e, int slot) {
        engines[slot] = e;
        calc();
        return this;
    }

    public Ship setPilot(Pilot pilot) {
        if (pilot != null)
            this.pilot = pilot;
        else
            pilot = new EmptyCockpit();
        return this;
    }

    public void destruct() {
        Universe u = Universe.get();
        for (Mass m : hold) {
            u.add(new Loot(this, m, 0));
        }
        if (gold > 0)
            u.add(new Loot(this, null, gold));
        super.destruct();
    }

    private void calc() {
        thrust = 0;
        maneuverability = 0;
        mass = hull.getMass();
        for (Engine e : engines) {
            if (e != null) {
                thrust += e.getThrust();
                maneuverability += e.getManeuverability();
                mass += e.getMass();
            }
        }
        for (Weapon w : weapons) {
            if (w != null) {
                mass += w.getMass();
            }
        }
        for (Mass m : hold) {
            if (m != null) {
                mass += m.getMass();
            }
        }
    }

    public void step(double t) {
        pilot.drive(t);
        x[2] = enginestate / mass * Math.cos(getA(0));
        y[2] = enginestate / mass * Math.sin(getA(0));
        a[1] = 0;
        a[1] += -turnleft;
        a[1] += turnright;

        super.step(t);
        for (int i = 0; i < weapons.length; i++) {
            if (weapons[i] != null) {
                weapons[i].step(t);
                if (firestate[i]) fire(i);
            }
        }
    }

    /** Fire a single shot.
     * @param n  weapon number
     */
    public void fire(int n) {
        Weapon w = weapons[n];
        if (w != null) w.fire(this);
    }

    /** Set a weapon as currently firing or not.
     * @param n  weapon number
     */
    public void setFire(int n, boolean set) {
        firestate[n] = set;
    }

    public void turnLeft(boolean set) {
        turnleft = set ? maneuverability : 0.0;
    }

    public void turnLeft(double set) {
        turnleft = Math.max(Math.min(set, 1.0), 0.0) * maneuverability;
    }

    public void turnRight(boolean set) {
        turnright = set ? maneuverability : 0.0;
    }

    public void turnRight(double set) {
        turnright = Math.max(Math.min(set, 1.0), 0.0) * maneuverability;
    }

    public double getMass() {
        return mass;
    }

    public double getManeuverability() {
        return maneuverability;
    }

    public double getThrust() {
        return thrust;
    }

    public Collection<Mass> getHold() {
        return hold;
    }

    public void store(Mass m) {
        hold.add(m);
        calc();
    }

    public void store(long gold) {
        this.gold += gold;
    }

    public void unstore(Mass m) {
        hold.remove(m);
        calc();
    }
}

@@ -1650,18 +1650,8 @@
 ch (
-java.io.IO
 Exce
@@ -1751,20 +1751,20 @@
 to load 
-hull
+ship
  '%22 + na
@@ -1781,21 +1781,8 @@
  + e
-.getMessage()
 );%0A 

Fix Ship log message.