package simulate;

public final class AtomPair {
    public Atom atom1, atom2;
    public final Space.CoordinatePair cPair;
//    public Potential potential;
//AtomPair must be associated with a phase because it contains a coordinatePair, which requires a phase.boundary
//   for its complete definition
    public AtomPair(Phase phase) {
        cPair = Simulation.space().makeCoordinatePair(phase.boundary());
    }
    public AtomPair(Atom a1, Atom a2) {  //Assumes a1 and a2 are in same phase
        cPair = Simulation.space().makeCoordinatePair(a1.parentPhase().boundary());
        reset(a1, a2);
    }
    public AtomPair(Atom a1, Atom a2, Space.CoordinatePair c) {atom1 = a1; atom2 = a2; cPair = c;}
    
    /**
     * Clones this atomPair without cloning the atoms or their coordinates
     * The returned atomPair refers to the same pair of atoms as the original
     * This can be used to make a working copy of an atomPair that is returned by an atomPair iterator
     * Method is called "copy" instead of "clone" because whole object tree isn't cloned
     */
    public AtomPair copy() {
        return new AtomPair(atom1, atom2, cPair.copy());  //cannot use super.clone() because cPair (being final) cannot then be changed to a clone of cPair
    }

    public void reset(Atom a1, Atom a2) {
        atom1 = a1; 
        atom2 = a2;
        reset();
    }
    public void reset() {
        cPair.reset(atom1.coordinate(), atom2.coordinate());
    }
    public final double r2() {return cPair.r2();}
    public final double v2() {return cPair.v2();}
    public final double vDotr() {return cPair.vDotr();}
    public final Space.Vector dr() {return cPair.dr();}
    public final double dr(int i) {return cPair.dr(i);}
    public final double dv(int i) {return cPair.dv(i);}
    public final Atom atom1() {return atom1;}
    public final Atom atom2() {return atom2;}
    
    /**
     * Sorts by separation distance all the atom pairs produced by an atomPair iterator
     * Returns the first element of a linked list of atomPair(Linker)s, sorted by increasing distance
     * Perhaps better to do this using java.util.Collections (in java 1.2 API)
     */
    public static AtomPair.Linker distanceSort(AtomPair.Iterator api) {
        if(!api.hasNext()) return null;
        AtomPair.Linker firstLink = new AtomPair.Linker(api.next().copy());
        while(api.hasNext()) {                      //loop through all pairs generated by api
            AtomPair nextPair = api.next().copy();  //make a copy of pair for use in ordered list
            //Insert pair into ordered list in proper location
            Linker previous = null;  //holds value from previous iteration of this for-loop
            boolean inserted = false;
            for(Linker link=firstLink; link!=null; link=link.next()) {
                if(nextPair.r2() < link.pair().r2()) {  //insert nextPair before current pair
                    if(previous == null) {firstLink = new Linker(nextPair,firstLink);} //nextPair is new firstPair, to be followed by old firstPair
                    else {previous.setNext(new Linker(nextPair,link));}  //place nextPair between last and pair
                    inserted = true;
                    break;  //break out of for-loop
                }
                previous = link;
            }  //end of for loop
            if(!inserted) //reached end of list without inserting;
                previous.setNext(new Linker(nextPair));   //insert after last link
        }
        return firstLink;
    }
    
    /** 
     * Class used to define an action on an AtomPair.
     * Can be passed to allPairs method of an AtomPair iterator to perform the action on 
     * all pairs generated by the iterator
     */
    public interface Action {
        public void action(AtomPair pair);
    }
    /**
     * Wrapper class that makes an AtomPair.Action suitable for input to an Atom.Iterator.
     * This is needed to enable an AtomPair iterator to perform an AtomPair.Action on all its pairs.
     * Class is an Atom.Action that contains the desired AtomPair.Action. The first Atom is set
     *   externally before being fed into an Atom.Iterator, which iterates values for the
     *   second Atom of the pair; the pair is sent to the wrapped AtomPair.Action on each iteration.
     */
    public static final class ActionWrapper implements Atom.Action {
        Atom atom1;
        AtomPair.Action pairAction;
        final AtomPair pair;
        public ActionWrapper(AtomPair p) {pair = p;}
        public void action(Atom a) {
            pair.atom2 = a;
            pair.reset();
            pairAction.action(pair);
        }
    }
    
    public static class Iterator {
        private AtomPair pair;  //want final, but derived class MP somehow prevents compiler from doing this
        protected Atom.Iterator ai1, ai2;
        protected ActionWrapper actionWrapper;   // want final too //wrapper inner class defined below
        protected boolean hasNext, needUpdate1;
        private Atom atom1;
        public Iterator(Phase p) {
            pair = new AtomPair(p); 
            actionWrapper = new AtomPair.ActionWrapper(pair);
            hasNext = false;
        }
        public Iterator(Phase p, ActionWrapper wrap) {
            pair = new AtomPair(p); 
            actionWrapper = wrap;
            hasNext = false;
        }
        public Iterator(Phase p, Atom.Iterator iter1, Atom.Iterator iter2) {
            pair = new AtomPair(p);
            actionWrapper = new AtomPair.ActionWrapper(pair);
            hasNext = false;
            ai1 = iter1;
            ai2 = iter2;
        }
        public final boolean hasNext() {return hasNext;}
        public void reset() {
            reset(null);
        }
        public void reset(Atom a1) {
            if(a1==null) ai1.reset();
            else ai1.reset(a1);
            do {                  //advance up list of atoms until a pair is found
                if(ai1.hasNext()) {
                    atom1 = ai1.next();
                    ai2.reset(atom1);
                }  
                else {hasNext = false; return;}}   //end of list of atoms
            while(!ai2.hasNext());
            needUpdate1 = true;
            hasNext = true;
        }
        public void reset(Atom a1, Atom a2) {
            ai1.reset(a1);
            ai2.reset(a2);
            pair.atom1 = ai1.next();
            needUpdate1 = false;
            hasNext = ai1.hasNext() && ai2.hasNext();
        }
            
        public AtomPair next() {
            if(needUpdate1) {pair.atom1 = atom1; needUpdate1 = false;}  //ai1 was advanced
            pair.atom2 = ai2.next();
            pair.reset();
            while(!ai2.hasNext()) {     //ai2 is done for this atom1, loop until it is prepared for next
                if(ai1.hasNext()) {     //ai1 has another atom1...
                    atom1 = ai1.next();     //...get it
                    ai2.reset(atom1);       //...reset ai2
                    needUpdate1 = true;     //...flag update of pair.atom1 for next time
                }
                else {hasNext = false; break;} //ai1 has no more; all done with pairs
            }
            return pair;
        }
        
        public void allPairs(AtomPair.Action act) {  
            reset();
            ai1.reset();  //this shouldn't be here, in general; need to consider it more carefully
            actionWrapper.pairAction = act;
            while(ai1.hasNext()) {
                pair.atom1 = ai1.next();
                ai2.reset(pair.atom1);
                ai2.allAtoms(actionWrapper);
            }
        }
        
        // The following are convenience extensions of AtomPair.Iterator that
        // handle some common iteration needs
        
        /**
         * Iterator for all atom pairs in a phase
         * Default is to do inter and intra pairs; this may be overridden using reset method to do
         * only intermolecular pairs
         * Uses atom iterator and atomPair iterator given by the phase.iterator class.
         */
         public static final class All extends Iterator {
            public All(Phase p) {
                super(p);
                ai1 = p.iteratorFactory.makeAtomIteratorUp();
                ai2 = p.iteratorFactory.makeAtomIteratorUpNeighbor();
                this.reset();
            }
         }
         
       /**
        * Iterates over pairs formed by given atom and all atoms from other molecules above it in list
        * If given atom is not in phase, it is considered the last atom, and no iterations are performed
        */
         public static final class Up extends Iterator {
            public Up(Phase p) {
                super(p);
                ai1 = new Atom.Iterator.Singlet();
                ai2 = p.iteratorFactory.makeAtomIteratorUpNeighbor();
                this.reset();
            }
            public Up(Phase p, Atom a) {
                super(p);
                ai1 = new Atom.Iterator.Singlet(a);
                ai2 = p.iteratorFactory.makeAtomIteratorUpNeighbor();
                this.reset();
            }
         }
         
       /**
        * Iterates over pairs formed by given atom and all atoms from other molecules below it in list
        * If given atom is not in phase, it is considered the last atom, and iterations are performed
        * over pairs formed from it and all atoms in phase
        */
         public static final class Down extends Iterator {
            public Down(Phase p) {
                super(p);
                ai1 = new Atom.Iterator.Singlet();
                ai2 = p.iteratorFactory.makeAtomIteratorDownNeighbor();
                this.reset();
            }
            public Down(Phase p, Atom a) {
                super(p);
                ai1 = new Atom.Iterator.Singlet(a);
                ai2 = p.iteratorFactory.makeAtomIteratorDownNeighbor();
                this.reset();
            }
         }
        
        /**
         * Iterator for all atoms in a molecule with all atoms in a phase
         * The molecule may or may not be in the phase
         * Intramolecular pairs are not generated
         */
         // Needs to be fixed to handle multi-atom molecules
         public static class MP extends Iterator {
            private boolean upDone;
            private final Atom.Iterator aiUp, aiDown;
            private Atom.Iterator apiCurrent;
            private Molecule molecule;
            public MP(Phase p) {
                super(p);
                aiUp = p.iteratorFactory.makeAtomIteratorUpNeighbor();
                aiDown = p.iteratorFactory.makeAtomIteratorDownNeighbor();
        //        aiUp.setIntra(false);
        //        aiDown.setIntra(false);    (need to implement these methods in Atom.Iterator)
            }
            public MP(Phase p, Molecule m) {
                super(p);
                aiUp = p.iteratorFactory.makeAtomIteratorUpNeighbor();
                aiDown = p.iteratorFactory.makeAtomIteratorDownNeighbor();
        //        aiUp.setIntra(false);
        //        aiDown.setIntra(false);    (need to implement these methods in Atom.Iterator)
                reset(m);
            }
            public void reset() {reset(molecule);}
            public void reset(Molecule m) {
                molecule = m;
                ai1 = m.atomIterator;
                aiUp.reset(m.lastAtom());
                aiDown.reset(m.firstAtom());
                ai2 = aiUp;
                super.reset();
                if(hasNext) {
                    upDone = false;}
                else {
                    ai2 = aiDown;
                    super.reset();
                    upDone = true;
                }
            }
            public AtomPair next() {  //not handling intra/inter in well defined way
                AtomPair dummy = super.next();
                if(!hasNext && !upDone) {
                    ai2 = aiDown;
                    super.reset();
                    upDone = true;
                }
                return pair;   //this was set to proper value when super.next() was called
            }
            public void allPairs(Action act) {
                actionWrapper.pairAction = act;
                while(ai1.hasNext()) {
                    pair.atom1 = ai1.next();
                    aiUp.reset(molecule.lastAtom());
                    aiDown.reset(molecule.firstAtom());
                    aiUp.allAtoms(actionWrapper);
                    aiDown.allAtoms(actionWrapper);
                }
            }
        }  //end of MP 
    }  //end of class Iterator
    
    
    /**
     * Class for making linked lists of AtomPairs
     */
    public static final class Linker {
        private AtomPair pair;
        private AtomPair.Linker next;
        public Linker() {}
        public Linker(AtomPair p) {pair = p;}
        public Linker(AtomPair p, Linker l) {pair = p; next = l;}
        public final AtomPair pair() {return pair;}
        public final AtomPair.Linker next() {return next;}
        public final void setNext(AtomPair.Linker l) {next = l;}
        public final void setPair(AtomPair p) {pair = p;}
    } //end of Linker
    
}  //end of  AtomPair