/*
 * Particle.java
 *
 * Created on December 23, 2000, 10:18 AM
 */

package pharynx;
import java.util.*;
import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.*;

/**
 *
 * @author  leon
 * @version
 */
public abstract class Particle extends java.lang.Object
implements Watcher, Kickable {
    protected Simulator sim = null;
    protected Pharynx p = null;
    protected Section s = null;
    protected double t = 0;
    protected double x = 0;
    protected double diameter;
    protected boolean caught;
    protected boolean running = false;
    protected boolean inPharynx = false;
    protected Shape shape = new Ellipse2D.Double(-5, -5, 10, 10);
    protected Color color = new Color(200, 200, 0);
    
    public Particle(Simulator sim, Pharynx p, double diameter) {
        this.sim = sim;
        this.p = p;
        this.diameter = diameter;
        tickets = new KickQueue.Ticket[KICK_TYPES];
        for(int i = 0; i < KICK_TYPES; i++) tickets[i] = null;
    }
    
    public Particle(Simulator sim, Pharynx p, double diameter, Section s,
                    double t, double x) {
        this(sim, p, diameter);
        place(s, t, x);
    }
    
    public void place(Section s, double t, double x) {
        if (!s.within(x)) {
            debugPrint("Position " + x + " is not within the Section");
            throw new IllegalArgumentException("Position " + x +
                " is not within the Section");
        }
        this.t = t;
        this.x = Math.max(s.xP(), Math.min(s.xA(), x));
        if (this.s != s) {
            if (null != this.s) this.s.removeWatcher(this);
            this.s = s;
            s.addWatcher(this);
        }
        inPharynx = true;
        caught = (s.currDiameter(t) < diameter);
    }
    
    public void place(double t, double x) {
        Section s = null;
        for(Iterator i = p.sections().iterator(); i.hasNext();) {
            Section ss = (Section) i.next();
            if (ss.within(x)) {
                s = ss;
                break;
            }
        }
        if (null == s)
            throw new IllegalArgumentException(x + " is not within the pharynx");
        place(s, t, x);
    }
    
    public void start() {
        running = true;
        setKicks();
        // checkKicks();
    }
    
    /**
     * Called just after a change takes place
     *
     * The one-argument form of consummated is called when some external call
     * changes the Object's state.
     *
     * @param o     The Object whose state changed
     */
    public void consummated(Object o) {
        // checkKicks();
        if (o != s)
            throw new CantHappenException("Message from wrong section");
        place(s, t, x);
    }
    
    /**
     * Called just before a change takes place
     *
     * @param o     The Object whose state is changing
     * @param t     Time at which change will occur
     */
    public void imminent(Object o, double t1) {
        // checkKicks();
        if (o != s)
            throw new CantHappenException("Message from wrong section");
        clearKicks();
        double x1 = x;
        if (!caught()) x1 = where(t1);
        double oldx = x;
        double oldt = t;
        // try {
        place(s, t1, x1);
        /*
        } catch(IllegalArgumentException e) {
            throw new IllegalArgumentException("oldx = " + oldx +
                                               ", oldt = " + oldt +
                                               ", t1 = " + t1 +
                                               ", x1 = " + x1 +
                                               ", caught = " + caught +
                                               ", section number = " + s.sectionNumber());
        }
         */
    }
    
    /**
     * Called just after a change takes place
     *
     * The two-argument form of consummated is called when a change occurs
     * during simulation (i.e., when the Object receives a Kick), and will
     * always be preceded by a call to imminent.
     *
     * @param o     The Object whose state changed
     * @param t     Time at which change occurred
     */
    public void consummated(Object o, double t) {
        if (o != s)
            throw new CantHappenException("Message from wrong section");
        if (t != this.t)
            throw new CantHappenException("Time changed unexpectedly");
        setKicks();
        // checkKicks();
    }
    
    protected KickQueue.Ticket[] tickets;
    protected static final int CATCHING = 0;
    protected static final int RELEASING= 1;
    protected static final int EXITING_A = 2;
    protected static final int EXITING_P = 3;
    protected static final int KICK_TYPES = 4;

    protected class PKick extends Kick {
        int type;

        PKick(double t, Kickable k, int type) {
            super(t, k);
            this.type = type;
        }
    }

    protected void clearKicks() {
        for(int i = 0; i < KICK_TYPES; i++) {
            if (null != tickets[i])
                sim.remove(tickets[i]);
            tickets[i] = null;
        }
    }

    protected void checkKickTime(double t, double kt) {
        if (kt <= t)
            System.out.println("Setting Kick at time " + kt +
                " before or at current time " + t);
        /*
            throw new CantHappenException("Setting Kick at time " + kt +
                " before or at current time " + t);
         */
    }

    protected boolean checkKicks() {
        for(int i = 0; i < tickets.length; i++) {
            if (null != tickets[i]) return true;
        }
        System.out.println("Particle: no Kicks set");
        return false;
    }

    public abstract double where(double t);
    protected abstract void setKicks();
    public abstract void kick(Kick k);
    
    public Section section() { return s; }
    public double diameter() { return diameter; }
    public boolean inPharynx() { return inPharynx; }
    public Shape shape() { return shape; }
    public void shape(Shape s) { shape = s; }
    public Color color() { return color; }
    public void color(Color c) { color = c; }
    public boolean caught() { return caught; }
    
    public void debugPrint(String s) {
        System.out.println(s);
    }

    protected boolean isGood(double t) {
        return !Double.isInfinite(t) && !Double.isNaN(t) && (t >= this.t);
    }
    
}