package org.openscience.cdk.formula;

import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;


/**
 * This class gives a score hit of similarity between two different
 * isotope abundance pattern.
 * 
 * @author Miguel Rojas Cherto
 * 
 * @cdk.module  formula
 */
@TestClass("org.openscience.cdk.formula.IsotopePatternSimilarityTest")
public class IsotopePatternSimilarity{

	private double chargeToAdd;
	
	private double tolerance_ppm = 1;
	
	private static double massE = 0.0005485;
    
	/**
     *  Constructor for the IsotopePatternSimilarity object.
     */
    public IsotopePatternSimilarity() {
    }
    
    /**
     * Set the tolerance of the mass accuracy.
     * 
     * @param tolerance  The tolerance value
     */
    @TestMethod("testSeTolerance_double")
	public void seTolerance(double tolerance) {
    	tolerance_ppm = tolerance;
    }
    /**
     * Get the tolerance of the mass accuracy.
     * 
     * @return The tolerance value
     */
    @TestMethod("testGetTolerance")
    public double getTolerance(){
    	return tolerance_ppm;
    }
    /**
     * Compare the IMolecularFormula with a isotope
     * abundance pattern.  
     * 
     *
     * @param  isoto1  The Isotope pattern reference (predicted)
     * @param  isoto2  The Isotope pattern reference (detected)
     * @return         The hit score of similarity
     */
    @TestMethod("testCompare_IsotopePattern_IsotopePattern")
    public double compare(IsotopePattern isoto1, IsotopePattern isoto2){
    	
    	IsotopePattern iso1 = IsotopePatternManipulator.sortAndNormalizedByIntensity(isoto1);
    	IsotopePattern iso2 = IsotopePatternManipulator.sortAndNormalizedByIntensity(isoto2);
    	
    	/*charge to add*/
    	if(isoto1.getCharge() == 1)
    		chargeToAdd = massE;
    	else if(isoto1.getCharge() == -1)
    		chargeToAdd = -massE;
    	else
    		chargeToAdd = 0;
    	
    	for(IsotopeContainer isoC: iso1.getIsotopes()){
    		double mass = isoC.getMass();
    		isoC.setMass(mass+chargeToAdd);
    	}
    	
    	double diffMass, diffAbun, factor, totalFactor = 0d;
		double score = 0d, tempScore;
		// Maximum number of isotopes to be compared according predicted isotope
		// pattern. It is assumed that this will have always more isotopeContainers
		int length = iso1.getNumberOfIsotopes();

		for (int i = 0 ; i< length; i++){
			IsotopeContainer isoContainer = iso1.getIsotopes().get(i); 
			factor = isoContainer.getIntensity();
			totalFactor += factor;
			
			// Search for the closest isotope in the second pattern (detected) to the
			// current isotope (predicted pattern)
			int closestDp = getClosestDataDiff(isoContainer, iso2);
			if (closestDp == -1)
				continue;
			
			diffMass = isoContainer.getMass() - iso2.getIsotopes().get(closestDp).getMass();
			diffMass = Math.abs(diffMass);

			diffAbun = 1.0d - (isoContainer.getIntensity() / iso2.getIsotopes().get(closestDp).getIntensity());
			diffAbun = Math.abs(diffAbun);
			
			tempScore = 1 - (diffMass + diffAbun);
			
			if (tempScore < 0)
				tempScore = 0;

			score += (tempScore * factor);

		}

		return score / totalFactor;
    }

    /**
     * Search and find the closest difference in an array in terms of mass and
	 * intensity. Always return the position in this List.
	 * 
     * @param diffValue  The difference to look for
     * @param normMass   A List of normalized masses
     * @return           The position in the List
     */
    private int getClosestDataDiff(IsotopeContainer isoContainer, IsotopePattern pattern) {
    	double diff = 100;
    	int posi = -1;
		for(int i = 0 ; i < pattern.getNumberOfIsotopes(); i++) {
			double tempDiff = Math.abs((isoContainer.getMass()) - pattern.getIsotopes().get(i).getMass());
			if (tempDiff <= (tolerance_ppm/isoContainer.getMass()) && tempDiff < diff){
				diff = tempDiff;
				posi = i;
			}
		}
		
		return posi;
	}
}
