package simulate;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Component;
import java.util.Observable;
import java.util.Observer;

/**
 * A Phase collects all atoms that interact with one another; atoms in different phases do
 * not interact.  These are the important features of a Phase:<p> 
 * 
 * 1. It maintains an IteratorFactory that can be used to obtain iterators that loop over all
 * or some atoms and atom pairs in the phase.<p>
 * 
 * 2. It provides methods for addition and removal of molecules in the phase.<p>
 * 
 * 3. It holds a Boundary object, obtained from the governing Space, that defines the behavior
 * of atoms as they move into or across the boundary of the phase.<p>
 * 
 * 4. It holds a set of Meter objects that can be used to measure properties of that atoms
 * held by the phase.  An EnergyMeter is constructed by default to permit evaluation of
 * potential and kinetic energies of the atoms in the phase.<p>
 * 
 * 5. It has a Configuration object that determines the default initial configuration of the
 * atoms in the phase.<p>
 * 
 * @author David Kofke
 * @see IteratorFactory
 * @see Space.Boundary
 * @see MeterAbstract
 */
public final class Phase extends Container implements Simulation.Element, Molecule.Container {
        
    protected Space.Boundary boundary;
    private int iBoundary = -1;
    private Simulation.ElementManager meterManager;
        
    public Phase() {
        super();

        if(Simulation.space() instanceof IteratorFactory.Maker) {
            setIteratorFactory(((IteratorFactory.Maker)Simulation.space()).makeIteratorFactory(this));
        }
        else {
            if(iteratorFactory == null) setIteratorFactory(new IteratorFactory(this));
        }

        meterManager = new Simulation.ElementManager(this);
        
        atomCount = moleculeCount = 0;
        gravity = new Gravity(0.0);
        noGravity = true;

        setBoundary(Space.Boundary.DEFAULT);

        add(new ConfigurationSequential());  //default configuration
        
        //Create and register energy meter; 
        //placed to permit phase to report its potential and kinetic energies
        energy = new MeterEnergy();
        energy.setActive(false);    //default has meter inactive, so it does not keep averages or respond to integrator interval events
        register(energy);
        
        //This should be moved into DisplayConfiguration
        if(colorScheme==null) colorScheme = new Phase.ColorScheme.BySpecies(this);
        colorScheme.initialColorAtoms();

        Simulation.register(this);
        
        //Response of Phase when a Species is added to Simulation
        Simulation.speciesManager.addObserver(makeSpeciesObserver());
    }
    
    /**
     * Creates a species observer that ensures all species register an agent with this phase
     * 
     * @return A species observer that get registered with the Simulation.SpeciesManager during
     * construction of this phase
     */
    protected Observer makeSpeciesObserver() {    
        return new Observer() {
            public void update(Observable mgr, Object obj) {
                Species species = (Species)obj;
                add(species.makeAgent(Phase.this));
            }
        };
    }
    
    public void setPotential() {
        if(Simulation.space != null) potential = Simulation.space.makePotential(this);
    }
    
    public Simulation.ElementManager meterManager() {return meterManager;}
    
    public final Space.Vector randomPosition() {return boundary.randomPosition();}

    /**
     * Sets the boundary object of the phase.
     * 
     * @param b A static integer variable that codes for the boundary.  Defined by the Boundary inner
     * class of the governing Space.
     * @see Space.Boundary
     */
    public final void setBoundary(int b) {
        iBoundary = b;
        if(Simulation.space != null) {
            boundary = Simulation.space.makeBoundary(iBoundary);
            setPotential();
        }
    }

    /**
     * Indicates the Boundary object of the phase
     * 
     * @return An integer that codes for the boundary via a static variable in Space.Boundary
     */
    public final int getBoundary() {return iBoundary;}
    public final Space.Boundary boundary() {return boundary;}
    
    /**
     * Accessor of the phase's iterator factory object
     * 
     * @return The phase's iterator factory
     * @see IteratorFactory
     */
    public final IteratorFactory iteratorFactory() {return iteratorFactory;}

    /**
     * Sets the iterator factory of the phase
     * 
     * @param it
     * @see IteratorFactory
     */
    public final void setIteratorFactory(IteratorFactory it) {iteratorFactory = it;}
    
    /**
     * Accessor of the integrator governing the movement of the atoms in the phase.
     * 
     * @return The phase's integrator.
     * @see Integrator
     */
    public final Integrator integrator() {return integrator;}

    /**
     * Sets the integrator of the phase
     * 
     * @param i The new integrator.
     */
    public void setIntegrator(Integrator i) {integrator = i;}
                        
    public final Space.Vector dimensions() {return boundary.dimensions();}
    public final double volume() {return boundary.volume();}  //infinite volume unless using PBC

    public void inflate(double scale) {
        boundary.inflate(scale);
        for(Molecule m=firstMolecule(); m!=null; m=m.nextMolecule()) {
            m.inflate(scale);
        }
    }
    public void reflate(double scale) {
        boundary.inflate(1.0/scale);
        for(Molecule m=firstMolecule(); m!=null; m=m.nextMolecule()) {
            m.replace();
        }
    }
    
    public final int atomCount() {return atomCount;}
    public final int moleculeCount() {return moleculeCount;}
    public final Atom firstAtom() {
        Molecule m = firstMolecule();
        return (m != null) ? m.firstAtom() : null;
    }
    public final Atom lastAtom() {
        Molecule m = lastMolecule();
        return (m != null) ? m.lastAtom() : null;
    }
    public final Molecule firstMolecule() {
        for(Species.Agent s=firstSpecies; s!=null; s=s.nextSpecies()) {
            Molecule m = s.firstMolecule();
            if(m != null) {return m;}
        }
        return null;
    }
    public final Molecule lastMolecule() {
        for(Species.Agent s=lastSpecies; s!=null; s=s.previousSpecies()) {
            Molecule m = s.lastMolecule();
            if(m != null) {return m;}
        }
        return null;
    }
    public final Species.Agent firstSpecies() {return firstSpecies;}
    public final Species.Agent lastSpecies() {return lastSpecies;}
    public final Phase.ColorScheme colorScheme() {return colorScheme;}
          
    public final double getG() {return gravity.getG();}
    public void setG(double g) {
        gravity.setG(g);
        noGravity = (g == 0.0);
    }
          
    /**
    * Returns the temperature (in simulation units) of this phase as computed via the equipartition
    * theorem from the kinetic energy summed over all (atomic) degrees of freedom
    */  
    public double kineticTemperature() {
        return MeterTemperature.value(this);
    }

    public void add(Configuration c){
        c.parentPhase = this;
        configuration = c;
        for(Species.Agent s=firstSpecies; s!=null; s=s.nextSpecies()) {
            configuration.add(s);
        }
        iteratorFactory.reset();
    }
            
    //add method is needed to use as a bean.  Would like to deprecate this in favor of register method
	public void add(MeterAbstract m) {
	    register(m);
	}
	
	public void register(MeterAbstract m) {
	    m.setPhase(this);
	    meterManager.addElement(m);
	}
	
    //add method is needed to use as a bean.  Would like to deprecate this in favor of set method
	public void add(IteratorFactory it) {setIteratorFactory(it);}
        	
    public void add(Species.Agent species) {
        //set internal configuration of molecule
        if(species.parentSpecies().moleculeConfiguration != null) species.parentSpecies().moleculeConfiguration.initializeCoordinates(this);
        //add to linked list of species agents
        if(lastSpecies != null) {lastSpecies.setNextSpecies(species);}
        else {firstSpecies = species;}
        lastSpecies = species;
        //update molecule and atom counts
        for(Molecule m=species.firstMolecule(); m!=null; m=m.nextMolecule()) {moleculeCount++;}
        for(Atom a=species.firstAtom(); a!=null; a=a.nextAtom()) {atomCount++;}
        //add species to configuration for this phase and notify iteratorFactory
        configuration.add(species);
        iteratorFactory.reset();  
    }
      
            // 5-15-00  method marked for deletion
//    public void add(Species s) {  //add species to phase if it doesn't appear in another phase
//        Species.Agent agent = s.makeAgent(this);
//        agent.setNMolecules(20);
//        add(agent);
//    }
    
    public void add(Phase.ColorScheme csp) {
        colorScheme = csp;
        csp.setPhase(this);
        csp.initialColorAtoms();
    }
                        
    public void updateCounts() {
        moleculeCount = 0;
        atomCount = 0;
        for(Molecule m=firstMolecule(); m!=null; m=m.nextMolecule()) {moleculeCount++;}
        for(Atom a=firstAtom(); a!=null; a=a.nextAtom()) {atomCount++;}
    }
    
    public void addMolecule(Molecule m) {
        addMolecule(m, m.parentSpecies().getAgent(this));
    }
    public void addMolecule(Molecule m, Species.Agent s) {
        m.container().removeMolecule(m);
        m.setParentPhase(this);
        moleculeCount++;
        atomCount += m.atomCount;
        s.addMolecule(m);
        iteratorFactory.addMolecule(m);
    }
    
    /**
     * Removes molecule from phase.  
     * Should be called only by an addMolecule method of another container
     * Use deleteMolecule to remove molecule while not adding it to another phase (adds it to species reservoir)
     */
    public void removeMolecule(Molecule m) {
        removeMolecule(m, m.parentSpecies().getAgent(this));
    }
    public void removeMolecule(Molecule m, Species.Agent s) {
        m.setParentPhase(null);        
        moleculeCount--;
        atomCount -= m.atomCount;
        s.deleteMolecule(m);
        iteratorFactory.deleteMolecule(m);
    }
// deletes molecule by adding it to reservoir

public void deleteMolecule(Molecule m) {
        m.parentSpecies().reservoir().addMolecule(m);
    }
    /**
    * Synchronized version of deleteMolecule.  
    * Useful if molecules are being deleted by GUI events, rather than by integrator 
    */
    public final synchronized void deleteMoleculeSafely(Molecule m) {  //will this make deleteMolecule synchronized?
        deleteMolecule(m);
    }
    
    /**
    * Synchronized version of addMolecule
    * Useful if molecules are being added by GUI events, rather than by integrator 
    */
    public final synchronized void addMoleculeSafely(Molecule m) {
        addMolecule(m);
    }
              
    Potential potential;
    public Potential potential() {return potential;}
            
    /**
    * Object used to describe presence and magnitude of constant gravitational acceleration
    */
    public Gravity gravity;
    public boolean noGravity = true;
            
    /**
    * First species in the linked list of species in this phase.
    */
    private Species.Agent firstSpecies;
         
    /**
    * Last species in the linked list of species in this phase.
    */
    Species.Agent lastSpecies;
          
    /**
    * Total number of atoms in this phase
    */
    public int atomCount;
         
    /**
    * Total number of molecules in this phase
    *
    * @see Species#addMolecule
    * @see Species#deleteMolecule
    */
    public int moleculeCount;
             
    public Configuration configuration;
          
    private Integrator integrator;
    
    protected IteratorFactory iteratorFactory;
          
    public MeterEnergy energy;
    
    private Phase.ColorScheme colorScheme;
 
    /**
    * ColorScheme for application to all atoms in the phase
    * @author David Kofke
    *
    */
     
    public static class ColorScheme extends Component {

        public Color baseColor = Color.black;
        protected Phase phase;
        protected Atom.Iterator iterator;
        
        public ColorScheme() {
            this(Color.black);
        }
        public ColorScheme(Color c) {
            setBaseColor(c);
        }
        public ColorScheme(Phase p) {
            this();
            setPhase(p);
        }
        public ColorScheme(Phase p, Color c) {
            this(c);
            setPhase(p);
        }
        
        public Phase phase() {return phase;}
        public void setPhase(Phase p) {
            phase = p;
            iterator = phase.iteratorFactory().makeAtomIteratorUp();
            initialColorAtoms();
        }
        
        public void initialColorAtoms() {
            if(iterator == null) return;
            iterator.reset();
            while(iterator.hasNext()) {iterator.next().setColor(baseColor);}
        }
        public void colorAtoms() {}
        
        public final void setBaseColor(Color c) {baseColor = c; initialColorAtoms();}
        public final Color getBaseColor() {return baseColor;}
        
        /**
        * Phase color scheme that defers entirely to the species colorschemes
        */
        public static class BySpecies extends Phase.ColorScheme {
            
            public BySpecies() {super();}
            public BySpecies(Phase p) {super(p);}
            
            public void initialColorAtoms() {
                if(phase == null) return;
                for(Species.Agent s=phase.firstSpecies(); s!=null; s=s.nextSpecies()) {
                    if(s.firstAtom() == null) {continue;}
                    simulate.ColorScheme cs = s.colorScheme();
                    Atom nextSpeciesAtom = s.terminationAtom();
                    for(Atom a=s.firstAtom(); a!=nextSpeciesAtom; a=a.nextAtom()) {
                        cs.initializeAtomColor(a);
                    }
                }
            }

            public void colorAtoms() {  //should have a species atomIterator for this
                for(Species.Agent s=phase.firstSpecies(); s!=null; s=s.nextSpecies()) {
                    if(s.firstAtom() == null) {continue;}
                    simulate.ColorScheme cs = s.colorScheme();
                    Atom nextSpeciesAtom = s.terminationAtom();
                    for(Atom a=s.firstAtom(); a!=nextSpeciesAtom; a=a.nextAtom()) {
                        cs.setAtomColor(a);
                    }
                }
            } //end of colorAtoms
        } //end of BySpecies
    } //end of ColorScheme
    
} //end of Phase
        