package simulate;

import java.awt.*;
import java.util.*;
import simulate.units.UnitSystem;

public class Simulation extends Panel {

    public static int D;  //dimension (2-D, 3-D, etc;)
    public static Space space;
    private static UnitSystem unitSystem;  //default unit system for I/O (internal calculations are all done in simulation units)

    /**
     * A static instance of a Simulation, suitable as a default Container of graphical simulation elements.
     * The add method of Simulation (which is inherited from Panel) is not
     * static, but it can be invoked for this static instance of Simulation.  In this way
     * graphical elements can be collected in this instance by default.  This instance
     * can then be added to an applet or application.
     */
    public static Simulation instance = new Simulation(new Space2D());
    
    public static ElementManager controllerManager = new ElementManager();
    public static ElementManager phaseManager = new ElementManager();
    public static ElementManager speciesManager = new ElementManager();
    public static ElementManager displayManager = new ElementManager();
    public static ElementManager deviceManager = new ElementManager();
    public static ElementManager integratorManager = new ElementManager();
    public static ElementManager potential1Manager = new ElementManager();
    public static ElementManager potential2Manager = new ElementManager();
    public static ElementManager meterManager = new ElementManager();
    
    protected Simulation(Space s) {
        space = s;
        D = space.D();
        setUnitSystem(new UnitSystem.Sim()); 
        setSize(400,300);
        setLayout(null);
    }
    
    public static final UnitSystem unitSystem() {return unitSystem;}
    public static final void setUnitSystem(UnitSystem us) {unitSystem = us;}
              
    public static final Space space() {return space;}
    
    public static void register(Controller c) {
        controllerManager.addElement(c);
    }
                
    public static void register(Display d) {
        displayManager.addElement(d);
    }
    
    public static void register(Device d) {
        deviceManager.addElement(d);
    }
              
    public static void register(Phase p) {
        phaseManager.addElement(p);
    }
    
    public static void register(Integrator i) {
        integratorManager.addElement(i);
    }
    
    public static void register(MeterAbstract m) {
        meterManager.addElement(m);
    }
                  
    public static void register(Species species) {
        //register with manager
        speciesManager.addElement(species);
        
        //update linked list of species
        if(lastSpecies != null) {lastSpecies.setNextSpecies(species);}
        else {firstSpecies = species;}
        lastSpecies = species;
        
        //set index of species
        species.setSpeciesIndex(speciesCount);
        setSpeciesCount(speciesCount+1);
//        speciesCount++;
        
//        if(species.getSpeciesIndex() > speciesCount-1) {setSpeciesCount(species.getSpeciesIndex()+1);}
    }
                
    /* Resizes potential arrays, keeping all elements already filled in, and
    setting to p1Null or p2IdealGas the newly formed elements
     
     ** All activities involving setSpeciesCount are under revision (05-16-00)
    */
    private static void setSpeciesCount(int n) {
        Potential1 p1[] = new Potential1[n];
        Potential2 p2[][] = new Potential2[n][n];
        for(int i=0; i<speciesCount; i++) {
            p1[i] = potential1[i];
            p2[i][i] = potential2[i][i];
            for(int j=i+1; j<speciesCount; j++) {        //could use system arraycopy
                p2[i][j] = p2[j][i] = potential2[i][j];
                            
            }
        }
        for(int i=speciesCount; i<n; i++) {
            p1[i] = p1Null;
            p2[i][i] = p2IdealGas;
            for(int j=0; j<n; j++) {
                p2[i][j] = p2[j][i] = p2IdealGas;
            }
        }
        potential1 = p1;
        potential2 = p2;
        speciesCount = n;
    }
              
    public static void register(Potential1 p1) {
        potential1Manager.addElement(p1);
//        if(p1.speciesIndex+1 > speciesCount) {setSpeciesCount(p1.speciesIndex+1);}
        if(potential1 == null) potential1 = new Potential1[1];
        potential1[p1.speciesIndex] = p1;
    }
                
    public static void register(Potential2 p2) {
        potential2Manager.addElement(p2);
        int idx = Math.max(p2.species1Index,p2.species2Index);
 //       if(idx+1 > speciesCount) {setSpeciesCount(idx+1);}
        if(potential2 == null) potential2 = new Potential2[1][1];
        potential2[p2.species1Index][p2.species2Index] = p2;
        potential2[p2.species2Index][p2.species1Index] = p2;
    }
                
    public static final Species firstSpecies() {return firstSpecies;}
    public static final Species lastSpecies() {return lastSpecies;}
              
    public static final Potential getPotential(AtomPair pair) {
        Atom a1 = pair.atom1();
        Atom a2 = pair.atom2();
        return getPotential(a1,a2);
    }
    //This can be made much more clever
    public static final Potential getPotential(Atom a1, Atom a2) {
        if(a1 == a2) {
            return a1.parentPhase().potential();}  //should rewrite AtomPair to hold phase
        else if(a1.parentMolecule() == a2.parentMolecule()) {
            return potential1[a1.speciesIndex()].getPotential(a1,a2);}
        else {
            return potential2[a2.speciesIndex()][a1.speciesIndex()].getPotential(a1,a2);
        }
    }
    
    /**
    * Total number of species contained in this phase.
    */
    static int speciesCount=0;
      
    /**
    * Symmetric array of all two-body potentials.  Potentials are associated with species, and each species
    * is assigned a unique index to idenfity it.  Potential2[i][j] is the two-body potential
    * for Species indexed i and j, respectively.  The potential for i=j is merely the one describing the 
    * molecular interactions for that species.
    * 
    * @see Species#speciesIndex
    * @see Potential2
    */
    public static Potential2[][] potential2;
      
    /**
    * Array of all one-body potentials.  Potentials are associated with species, and each species
    * is assigned a unique index to idenfity it.  Potential1[i] is the one-body potential
    * for Species indexed i.
    * 
    * @see Species#speciesIndex
    * @see Potential1
    */
    public static Potential1[] potential1;
      
    /**
    * First species in the linked list of species in this phase.
    */
    public static Species firstSpecies;
     
    /**
    * Last species in the linked list of species in this phase.
    */
    public static Species lastSpecies;
            
    private static Potential1 p1Null = new P1Null();
    private static Potential2 p2IdealGas = new P2IdealGas();
    
    
    /**
     * Class to collect all simulation elements and notify registered observers when new elements are added.
     * A ElementManager instance is created for each of the major types of simulation
     * elements (Phase, Species, etc.).  Any time a new simulation element is
     * instantiated it registers itself with the corresponding ElementManager, which
     * is accessed as a static field of Simulation.  Any elements interested in knowing
     * when other element types are instantiated can list themselves as an observer
     * of the corresponding ElementManager.
     */
    public static class ElementManager extends Observable {
        
        /**
         * A linked-list collection of the simulation components
         */
        public final LinkedList list = new LinkedList();
        
        /**
         * The simulation element that is using this element manager
         * Value is null if used by Simulation
         */
        public Element parent;

        /**
         * Default constructor
         */
        public ElementManager() {this(null);} //constructor
        public ElementManager(Element e) {parent = e;}
        
        /**
         * Registers an observer of this ElementManager.
         * Any existing elements managed by the ElementManager will be identified to
         * the observer immediately (via the update method of the Observer interface), 
         * and the observer will be notified of subsequent additions
         * 
         * @param o The observer of this ElementManager
         */
        public void addObserver(Observer o) {
            super.addObserver(o);
            for(java.util.Iterator i=list.iterator(); i.hasNext(); ) {
                o.update(this,i.next());
            }
        }

        /**
         * Adds a simulation element to the collection held by this ElementManager.
         * Any registered observers are notified of the addition of the element.
         * 
         * @param component The simulation component that is being added to the collection
         * @param element
         */
        public void addElement(Simulation.Element element) {
            list.add(element);  //add component to collection
            if(element instanceof GraphicalElement && parent == null) {
                Simulation.instance.add(((GraphicalElement)element).graphic(null));
            }
            setChanged();
            notifyObservers(element);
        }
    }
    
    /**
     * A marker interface to indicate that the class is an element of a Simulation
     */
    public interface Element {
    }
    
    /**
     * Interface for a simulation element that can make a graphical component
     */
    public interface GraphicalElement extends Element {

        /**
         * Interface for a Simulation element that would be used in a simulation graphical user interface (GUI)
         * 
         * @param obj An object that might be used to specify the graphic that the GraphicalElement is to return.
         * In most cases the GraphicalElement ignores this parameter, and it can be set to null.
         * @return A Component that can be used in the GUI of a graphical simulation
         * @see Device
         * @see Display
         */
        public java.awt.Component graphic(Object obj);
    }
            
}


