/* Copyright (C) 1997-2007  The Chemistry Development Kit (CDK) project
 *
 * Contact: cdk-devel@lists.sourceforge.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 * All we ask is that proper credit is given for our work, which includes
 * - but is not limited to - adding the above copyright notice to the beginning
 * of your source code files, and to any copyright notice that you may distribute
 * with programs based on this work.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.openscience.cdk.structgen;

import org.openscience.cdk.graph.ConnectivityChecker;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;
import org.openscience.cdk.tools.ILoggingTool;
import org.openscience.cdk.tools.LoggingToolFactory;
import org.openscience.cdk.tools.manipulator.BondManipulator;

/**
 * RandomGenerator is a generator of constitutional isomers. It needs to be
 * provided with a starting constitution and it makes random moves in
 * constitutional space from there.
 * This generator was first suggested by J.-L. Faulon {@cdk.cite FAU96}.
 *
 * <p>Unlike the VicinitySampler, this methods does not sample
 * the full Faulon vicinity.
 *
 * @see         org.openscience.cdk.structgen.VicinitySampler
 *
 * @cdk.githash
 * @cdk.keyword structure generator
 * @cdk.module structgen
 */
public class RandomGenerator {

    ILoggingTool           logger            = LoggingToolFactory.createLoggingTool(RandomGenerator.class);

    private IAtomContainer proposedStructure = null;
    private IAtomContainer molecule          = null;
    private IAtomContainer trial             = null;

    /**
     * Constructs a RandomGenerator with a given starting structure.
     *
     * @param   molecule  The starting structure
     */
    public RandomGenerator(IAtomContainer molecule) {
        setMolecule(molecule);
    }

    /**
     * Proposes a structure which can be accepted or rejected by an external
     * entity. If rejected, the structure is not used as a starting point
     * for the next random move in structure space.
     *
     * @return A proposed molecule
     */
    public IAtomContainer proposeStructure() {
        logger.debug("RandomGenerator->proposeStructure() Start");
        do {
            try {
                trial = molecule.clone();
            } catch (CloneNotSupportedException e) {
                throw new IllegalStateException("Could not clone IAtomContainer!" + e.getMessage());
            }
            mutate(trial);
            if (logger.isDebugEnabled()) {
                String s = "BondCounts:    ";
                for (int f = 0; f < trial.getAtomCount(); f++) {
                    s += trial.getConnectedBondsCount(trial.getAtom(f)) + " ";
                }
                logger.debug(s);
                s = "BondOrderSums: ";
                for (int f = 0; f < trial.getAtomCount(); f++) {
                    s += trial.getBondOrderSum(trial.getAtom(f)) + " ";
                }
                logger.debug(s);
            }
        } while (trial == null || !ConnectivityChecker.isConnected(trial));
        proposedStructure = trial;

        return proposedStructure;
    }

    /**
     * Tell the RandomGenerator to accept the last structure that had been proposed.
     */
    public void acceptStructure() {
        if (proposedStructure != null) {
            molecule = proposedStructure;
        }
    }

    /**
     * Randomly chooses four atoms and alters the bonding
     * pattern between them according to rules described
     * in "Faulon, JCICS 1996, 36, 731".
     */
    public void mutate(IAtomContainer ac) {
        logger.debug("RandomGenerator->mutate() Start");
        int nrOfAtoms = ac.getAtomCount();
        int x1 = 0, x2 = 0, y1 = 0, y2 = 0;
        double a11 = 0, a12 = 0, a22 = 0, a21 = 0;
        double b11 = 0, lowerborder = 0, upperborder = 0;

        IAtom ax1 = null, ax2 = null, ay1 = null, ay2 = null;
        IBond b1 = null, b2 = null, b3 = null, b4 = null;
        int[] choices = new int[3];
        int choiceCounter = 0;
        /* We need at least two non-zero bonds in order to be successful */
        int nonZeroBondsCounter = 0;
        do {
            do {
                nonZeroBondsCounter = 0;
                /* Randomly choose four distinct atoms */
                do {
                    // this yields numbers between 0 and (nrOfAtoms - 1)
                    x1 = (int) (Math.random() * nrOfAtoms);
                    x2 = (int) (Math.random() * nrOfAtoms);
                    y1 = (int) (Math.random() * nrOfAtoms);
                    y2 = (int) (Math.random() * nrOfAtoms);
                    logger.debug("RandomGenerator->mutate(): x1, x2, y1, y2: " + x1 + ", " + x2 + ", " + y1 + ", " + y2);
                } while (!(x1 != x2 && x1 != y1 && x1 != y2 && x2 != y1 && x2 != y2 && y1 != y2));
                ax1 = ac.getAtom(x1);
                ay1 = ac.getAtom(y1);
                ax2 = ac.getAtom(x2);
                ay2 = ac.getAtom(y2);
                /* Get four bonds for these four atoms */

                b1 = ac.getBond(ax1, ay1);
                if (b1 != null) {
                    a11 = BondManipulator.destroyBondOrder(b1.getOrder());
                    nonZeroBondsCounter++;
                } else {
                    a11 = 0;
                }

                b2 = ac.getBond(ax1, ay2);
                if (b2 != null) {
                    a12 = BondManipulator.destroyBondOrder(b2.getOrder());
                    nonZeroBondsCounter++;
                } else {
                    a12 = 0;
                }

                b3 = ac.getBond(ax2, ay1);
                if (b3 != null) {
                    a21 = BondManipulator.destroyBondOrder(b3.getOrder());
                    nonZeroBondsCounter++;
                } else {
                    a21 = 0;
                }

                b4 = ac.getBond(ax2, ay2);
                if (b4 != null) {
                    a22 = BondManipulator.destroyBondOrder(b4.getOrder());
                    nonZeroBondsCounter++;
                } else {
                    a22 = 0;
                }
                logger.debug("RandomGenerator->mutate()->The old bond orders: a11, a12, a21, a22: " + +a11 + ", " + a12
                        + ", " + a21 + ", " + a22);
            } while (nonZeroBondsCounter < 2);

            /* Compute the range for b11 (see Faulons formulae for details) */
            double[] cmax = {0, a11 - a22, a11 + a12 - 3, a11 + a21 - 3};
            double[] cmin = {3, a11 + a12, a11 + a21, a11 - a22 + 3};
            lowerborder = MathTools.max(cmax);
            upperborder = MathTools.min(cmin);
            /* Randomly choose b11 != a11 in the range max > r > min */
            logger.debug("*** New Try ***");
            logger.debug("a11 = ", a11);
            logger.debug("upperborder = ", upperborder);
            logger.debug("lowerborder = ", lowerborder);
            choiceCounter = 0;
            for (double f = lowerborder; f <= upperborder; f++) {
                if (f != a11) {
                    choices[choiceCounter] = (int) f;
                    choiceCounter++;
                }
            }
            if (choiceCounter > 0) {
                b11 = choices[(int) (Math.random() * choiceCounter)];
            }

            logger.debug("b11 = " + b11);
        } while (!(b11 != a11 && (b11 >= lowerborder && b11 <= upperborder)));

        double b12 = a11 + a12 - b11;
        double b21 = a11 + a21 - b11;
        double b22 = a22 - a11 + b11;

        if (b11 > 0) {
            if (b1 == null) {
                b1 = ac.getBuilder().newInstance(IBond.class, ax1, ay1, BondManipulator.createBondOrder(b11));
                ac.addBond(b1);
            } else {
                b1.setOrder(BondManipulator.createBondOrder(b11));
            }
        } else if (b1 != null) {
            ac.removeBond(b1);
        }

        if (b12 > 0) {
            if (b2 == null) {
                b2 = ac.getBuilder().newInstance(IBond.class, ax1, ay2, BondManipulator.createBondOrder(b12));
                ac.addBond(b2);
            } else {
                b2.setOrder(BondManipulator.createBondOrder(b12));
            }
        } else if (b2 != null) {
            ac.removeBond(b2);
        }

        if (b21 > 0) {
            if (b3 == null) {
                b3 = ac.getBuilder().newInstance(IBond.class, ax2, ay1, BondManipulator.createBondOrder(b21));
                ac.addBond(b3);
            } else {
                b3.setOrder(BondManipulator.createBondOrder(b21));
            }
        } else if (b3 != null) {
            ac.removeBond(b3);
        }

        if (b22 > 0) {
            if (b4 == null) {
                b4 = ac.getBuilder().newInstance(IBond.class, ax2, ay2, BondManipulator.createBondOrder(b22));
                ac.addBond(b4);
            } else {
                b4.setOrder(BondManipulator.createBondOrder(b22));
            }
        } else if (b4 != null) {
            ac.removeBond(b4);
        }

        logger.debug("a11 a12 a21 a22: " + a11 + " " + a12 + " " + a21 + " " + a22);
        logger.debug("b11 b12 b21 b22: " + b11 + " " + b12 + " " + b21 + " " + b22);
    }

    /**
     * Assigns a starting structure to this generator.
     *
     * @param   molecule  a starting structure for this generator
     */
    public void setMolecule(IAtomContainer molecule) {
        this.molecule = molecule;
    }

    /**
     * Returns the molecule which reflects the current state of this
     * stochastic structure generator.
     *
     * @return The molecule
     */
    public IAtomContainer getMolecule() {
        return this.molecule;
    }

}
