/*  $RCSfile$
 *  $Author$
 *  $Date$
 *  $Revision$
 *
 *  Copyright (C) 2005-2007  Christian Hoppe <chhoppe@users.sf.net>
 *
 *  Contact: cdk-devel@list.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.
 *
 *  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.charges;


import java.util.Iterator;

import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.interfaces.IAtom;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IBond;

/**
 * <p>The calculation of the Gasteiger Marsili (PEOE) partial charges is based on 
 * {@cdk.cite GM80}. This class only implements the original method which only
 * applies to &sigma;-bond systems.</p> 
 * 
 *
 * @author      chhoppe
 * @author      rojas
 * 
 * @cdk.module  charges
 * @cdk.githash
 * @cdk.created 2004-11-03
 * @cdk.keyword partial atomic charges
 * @cdk.keyword charge distribution
 * @cdk.keyword electronegativities, partial equalization of orbital
 * @cdk.keyword PEOE
 */
@TestClass("org.openscience.cdk.charges.GasteigerMarsiliPartialChargesTest")
public class GasteigerMarsiliPartialCharges implements IChargeCalculator {

    private double DEOC_HYDROGEN = 20.02;
    private double MX_DAMP = 0.5;
    private double MX_ITERATIONS = 20;
    private int STEP_SIZE = 5;
    /** Flag is set if the formal charge of a chemobject is changed due to resonance.*/


    /**
     *  Constructor for the GasteigerMarsiliPartialCharges object
     */
    public GasteigerMarsiliPartialCharges() { }


    /**
    *  Sets chi_cat value for hydrogen, because H poses a special problem due to lack of possible second ionisation
     *
     *@param  chiCat  The new DEOC_HYDROGEN value
     */
    @TestMethod("testSetChiCatHydrogen_Double")
    public void setChiCatHydrogen(double chiCat) {
        DEOC_HYDROGEN = chiCat;
    }


    /**
     *  Sets the maxGasteigerDamp attribute of the GasteigerMarsiliPartialCharges
     *  object
     *
     *@param  damp  The new maxGasteigerDamp value
     */
    @TestMethod("testSetMaxGasteigerDamp_Double")
    public void setMaxGasteigerDamp(double damp) {
        MX_DAMP = damp;
    }


    /**
     *  Sets the maxGasteigerIters attribute of the GasteigerMarsiliPartialCharges
     *  object
     *
     *@param  iters  The new maxGasteigerIters value
     */
    @TestMethod("testSetMaxGasteigerIters_Double")
    public void setMaxGasteigerIters(double iters) {
        MX_ITERATIONS = iters;
    }

    /**
     *  Gets chi_cat value for hydrogen, because H poses a special problem due to lack of possible second ionisation
      *
      * @return  The new DEOC_HYDROGEN value
      */
    @TestMethod("testGetChiCatHydrogen")
     public double getChiCatHydrogen() {
         return DEOC_HYDROGEN;
     }


     /**
      *  Gets the maxGasteigerDamp attribute of the GasteigerMarsiliPartialCharges
      *  object
      *
      * @return  The new maxGasteigerDamp value
      */
     @TestMethod("testGetMaxGasteigerDamp")
     public double getMaxGasteigerDamp() {
         return MX_DAMP;
     }


     /**
      *  Gets the maxGasteigerIters attribute of the GasteigerMarsiliPartialCharges
      *  object
      *
      * @return  The new maxGasteigerIters value
      */
     @TestMethod("testGetMaxGasteigerIters")
     public double getMaxGasteigerIters() {
         return MX_ITERATIONS;
     }

    /**
     *  Main method which assigns Gasteiger Marisili partial sigma charges
     *
     *@param  ac             AtomContainer
     *@param setCharge   	 The Charge
     *@return                AtomContainer with partial charges
     *@exception  Exception  Possible Exceptions
     */
    @TestMethod("testAssignGasteigerMarsiliSigmaPartialCharges_IAtomContainer_Boolean")
    public IAtomContainer assignGasteigerMarsiliSigmaPartialCharges(IAtomContainer ac, boolean setCharge) throws Exception {

//		if (setCharge) {
//			atomTypeCharges.setCharges(ac); // not necessary initial charge
//		}
        /*add the initial charge to 0. According results of Gasteiger*/
        for(int i = 0; i < ac.getAtomCount(); i++)
            ac.getAtom(i).setCharge(0.0);
        double[] gasteigerFactors = assignGasteigerSigmaMarsiliFactors(ac);//a,b,c,deoc,chi,q
        double alpha = 1.0;
        double q;
        double deoc;

        IAtom[] atoms = null;
        int atom1 = 0;
        int atom2 = 0;

        double[] q_old = new double[ac.getAtomCount()];
        for(int i = 0 ; i < q_old.length ; i++)
            q_old[0] = 20.0;

        out:
        for (int i = 0; i < MX_ITERATIONS; i++) {
            alpha *= MX_DAMP;
            boolean isDifferent = false;
            for (int j = 0; j < ac.getAtomCount(); j++) {
                q = gasteigerFactors[STEP_SIZE * j + j + 5];
                double difference = Math.abs(q_old[j])-Math.abs(q);
                if(Math.abs(difference) > 0.001)
                    isDifferent = true;
                q_old[j] = q;

                gasteigerFactors[STEP_SIZE * j + j + 4] = gasteigerFactors[STEP_SIZE * j + j + 2] * q * q + gasteigerFactors[STEP_SIZE * j + j + 1] * q + gasteigerFactors[STEP_SIZE * j + j];
//				logger.debug("g4: "+gasteigerFactors[STEP_SIZE * j + j + 4]);
            }
            if(!isDifferent)/* automatically break the maximum iterations*/
                break out;

//            bonds = ac.getBonds();
            Iterator bonds = ac.bonds().iterator();
            while (bonds.hasNext()) {
                IBond bond = (IBond) bonds.next();
                
                atom1 = ac.getAtomNumber(bond.getAtom(0));
                atom2 = ac.getAtomNumber(bond.getAtom(1));

                if (gasteigerFactors[STEP_SIZE * atom1 + atom1 + 4] >= gasteigerFactors[STEP_SIZE * atom2 + atom2 + 4]) {
                    if (ac.getAtom(atom2).getSymbol().equals("H")) {
                        deoc = DEOC_HYDROGEN;
                    } else {
                        deoc = gasteigerFactors[STEP_SIZE * atom2 + atom2 + 3];
                    }
                } else {
                    if (ac.getAtom(atom1).getSymbol().equals("H")) {
                        deoc = DEOC_HYDROGEN;
                    } else {
                        deoc = gasteigerFactors[STEP_SIZE * atom1 + atom1 + 3];
                    }
                }

                q = (gasteigerFactors[STEP_SIZE * atom1 + atom1 + 4] - gasteigerFactors[STEP_SIZE * atom2 + atom2 + 4]) / deoc;
//				logger.debug("qq: "+q);
                gasteigerFactors[STEP_SIZE * atom1 + atom1 + 5] -= (q*alpha);
                gasteigerFactors[STEP_SIZE * atom2 + atom2 + 5] += (q*alpha);
            }
        }

        for (int i = 0; i < ac.getAtomCount(); i++) {
        	ac.getAtom(i).setCharge(gasteigerFactors[STEP_SIZE * i + i + 5]);
        }
        return ac;
    }

    @TestMethod("testCalculateCharges_IAtomContainer")
    public void calculateCharges(IAtomContainer container) throws CDKException {
    	try {
	        this.assignGasteigerMarsiliSigmaPartialCharges(container, true);
        } catch (Exception exception) {
	        throw new CDKException(
	        	"Could not calculate Gasteiger-Marsili sigma charges: " +
	        	exception.getMessage(), exception
	        );
        }
    }

    /**
     *  Get the StepSize attribute of the GasteigerMarsiliPartialCharges
     *  object
     *
     *@return STEP_SIZE
     */
    @TestMethod("testGetStepSize")
    public int getStepSize(){
        return STEP_SIZE;
    }
    /**
     *  Set the StepSize attribute of the GasteigerMarsiliPartialCharges
     *  object
     *
     *@param step
     */
    @TestMethod("testSetStepSize")
    public void setStepSize(int step){
        STEP_SIZE = step;
    }


    /**
     *  Method which stores and assigns the factors a,b,c and CHI+
     *
     *@param  ac  AtomContainer
     *@return     Array of doubles [a1,b1,c1,denom1,chi1,q1...an,bn,cn...] 1:Atom 1-n in AtomContainer
     */
    @TestMethod("testAssignGasteigerSigmaMarsiliFactors_IAtomContainer")
    public double[] assignGasteigerSigmaMarsiliFactors(IAtomContainer ac) {
        //a,b,c,denom,chi,q
        double[] gasteigerFactors = new double[(ac.getAtomCount() * (STEP_SIZE+1))];
        String AtomSymbol = "";
        double[] factors = new double[]{0.0, 0.0, 0.0};
        for (int i = 0; i < ac.getAtomCount(); i++) {
            factors[0] = 0.0;
            factors[1] = 0.0;
            factors[2] = 0.0;
            AtomSymbol = ac.getAtom(i).getSymbol();
            if (AtomSymbol.equals("H")) {
                factors[0] = 7.17;
                factors[1] = 6.24;
                factors[2] = -0.56;
            } else if (AtomSymbol.equals("C")) {
                if ((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.SINGLE)&&
                (ac.getAtom(i).getFormalCharge() != -1)){
                    factors[0] = 7.98;
                    factors[1] = 9.18;
                    factors[2] = 1.88;
                } else if (ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.DOUBLE
                        ||((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.SINGLE)
                           && ac.getAtom(i).getFormalCharge() == -1)) {
                    factors[0] = 8.79;/*8.79*//*8.81*/
                    factors[1] = 9.32;/*9.32*//*9.34*/
                    factors[2] = 1.51;/*1.51*//*1.52*/
                } else if (ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.TRIPLE ||
                		ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.QUADRUPLE) {
                    factors[0] = 10.39;/*10.39*/
                    factors[1] = 9.45;/*9.45*/
                    factors[2] = 0.73;
                }
            } else if(AtomSymbol.equals("N")){
                if((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.SINGLE) &&
                (ac.getAtom(i).getFormalCharge() != -1)) {
                    factors[0] = 11.54;
                    factors[1] = 10.82;
                    factors[2] = 1.36;
                } else if ((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.DOUBLE)
                        ||((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.SINGLE)&& 
                        	ac.getAtom(i).getFormalCharge() == -1)) {
                    factors[0] = 12.87;
                    factors[1] = 11.15;
                    factors[2] = 0.85;
                } else if (ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.TRIPLE
                    || ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.QUADRUPLE) {
                    factors[0] = 17.68;/*15.68*/
                    factors[1] = 12.70;/*11.70*/
                    factors[2] = -0.27;/*-0.27*/
                }
            } else if (AtomSymbol.equals("O")) {
                if ((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.SINGLE) &&
                (ac.getAtom(i).getFormalCharge() != -1)){
                    factors[0] = 14.18;
                    factors[1] = 12.92;
                    factors[2] = 1.39;
                } else if((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.DOUBLE)
                    ||((ac.getMaximumBondOrder(ac.getAtom(i)) == IBond.Order.SINGLE)
                    	&& ac.getAtom(i).getFormalCharge() == -1)){
                    factors[0] = 17.07;/* paramaters aren'T correct parametrized. */
                    factors[1] = 13.79;
                    factors[2] = 0.47;/*0.47*/
                }
            } else if (AtomSymbol.equals("Si")) {// <--not correct
                factors[0] = 8.10;// <--not correct
                factors[1] = 7.92;// <--not correct
                factors[2] = 1.78;// <--not correct
            } else if (AtomSymbol.equals("P")) {
                factors[0] = 8.90;
                factors[1] = 8.32;
                factors[2] = 1.58;
            } else if (AtomSymbol.equals("S") /*&& ac.getMaximumBondOrder(ac.getAtomAt(i)) == 1*/) {
                factors[0] = 10.14;/*10.14*/
                factors[1] = 9.13;/*9.13*/
                factors[2] = 1.38;/*1.38*/
            } else if (AtomSymbol.equals("F")) {
                factors[0] = 14.66;
                factors[1] = 13.85;
                factors[2] = 2.31;
            } else if (AtomSymbol.equals("Cl")) {
                factors[0] = 12.31;/*11.0*//*12.31*/
                factors[1] = 10.84;/*9.69*//*10.84*/
                factors[2] = 1.512;/*1.35*//*1.512*/
            } else if (AtomSymbol.equals("Br")) {
                factors[0] = 11.44;/*10.08*//*11.2*/
                factors[1] = 9.63;/*8.47*//*9.4*/
                factors[2] = 1.31;/*1.16*//*1.29*/
            } else if (AtomSymbol.equals("I")) {
                factors[0] = 9.88;/*9.90*/
                factors[1] = 7.95;/*7.96*/
                factors[2] = 0.945;/*0.96*/
            }

            gasteigerFactors[STEP_SIZE * i + i] = factors[0];
            gasteigerFactors[STEP_SIZE * i + i + 1] = factors[1];
            gasteigerFactors[STEP_SIZE * i + i + 2] = factors[2];
            gasteigerFactors[STEP_SIZE * i + i + 5] = ac.getAtom(i).getCharge();
            if (factors[0] == 0 && factors[1] == 0 && factors[2] == 0) {
                gasteigerFactors[STEP_SIZE * i + i + 3] = 1;
            } else {
                gasteigerFactors[STEP_SIZE * i + i + 3] = factors[0] + factors[1] + factors[2];
            }
        }
        return gasteigerFactors;
    }
}

