# Source: molecularformulae.js

    /**
     * Code to calculate distribution based on molecular formula
     */
    class MolecularFormulae{
    	constructor(){
    		this.averagine = "C4.9384H7.7583N1.3577O1.4773S0.0417" ;
    		this.avrgMass = 111.0543055992;
    		this.atomList = ["C","H","N","O","S"]
    		this.carbonCnt = 4.9384;
    		this.hydrogenCnt = 7.7583;
    		this.nitrogenCnt = 1.3577;
    		this.oxygenCnt = 1.4773
    		this.sulphurCnt = 0.0417;
    		this.minintensity = 0.0001;
    		this.protonMass = 1.00727647  ;
    		this.eachAtomCount;
    		this.toleraceMassDiff = 0.01;
    		this.mininte = 1;
    		this.intensityTolerance = 1 ;
    	}
    	/**
    	 * Function to calculate the emass distrubution fo the given sequence
    	 * @param {Double} mass - Contains the mass at that position of the sequence
    	 * @param {Array} peakDataList - Contains the peak list provided by the user
    	 * @param {Float} charge - Contains the chrage of the ion
    	 */
    	emass(mass,charge,peakDataList){
    		//Give the count of each atom in molecule
    		this.eachAtomCount = mass/this.avrgMass ;
    		let calculatedMass;
    		let atomCountList ;
    		[atomCountList,calculatedMass] = this.getMolecularFormula() ;
    		let massError = mass - calculatedMass ;
    		let len = atomCountList.length;
    		let totDistributionList = [] ;
    		let numOfAtoms = this.atomList.length ;
    		for(let i=0;i<len;i++)
    		{
    			for(let j=0;j<atomCountList[i].count;j++)
    			{
    				let atomdist = getIsotopicMassRef(atomCountList[i].atom);
    				totDistributionList = this.getMassAndIntensity(totDistributionList,atomdist);
    			}
    		}
    		for(let k=0;k<totDistributionList.length;k++)
    		{
    			for(let i = 0; i < numOfAtoms ; i++)
    			{
    				let IsotopicMass = getIsotopicMassOfAtom(atomCountList[i].atom);
    				totDistributionList[k].mass = totDistributionList[k].mass + (IsotopicMass[0].mass * atomCountList[i].count) ;
    			}
    		}
    		totDistributionList = this.getMZwithHighInte(totDistributionList,charge,massError,peakDataList);
    		totDistributionList = this.getNormalizedIntensity(totDistributionList,peakDataList);
    		return totDistributionList ;
    	}
    	/**
    	 * Logic to calculate distribution
    	 * @param {Array} totDistributionList - Array with current total distribution
    	 * @param {Array} aminoAcidDist - Array with existing calculated distribution of amino acid
    	 */
    	getMassAndIntensity(totDistributionList,atomdist){
    		let maxintensity = 0 ;
    		if(totDistributionList.length == 0)
    		{
    			return atomdist ;
    		}
    		else
    		{
    			let len = totDistributionList.length + atomdist.length - 1;
    	  		let completeDistributionList = new Array(len).fill(0) ;
    	  		for(let i=0;i<totDistributionList.length;i++)
    			{
    				for(let j=0;j<atomdist.length;j++)
    				{
    					let intensity = 0;
    					let index = i+j ;
    					let mass = totDistributionList[i].mass+atomdist[j].mass ;
    					intensity = totDistributionList[i].intensity * atomdist[j].intensity ;
    					if(completeDistributionList[index] != 0) intensity = intensity + completeDistributionList[index].intensity ;
    					completeDistributionList[index] = {mass:mass,intensity:intensity};
    					if(intensity > maxintensity) maxintensity = intensity ;
    				}
    			}
    	  		let completeDistributionList_temp = [];
    			for(let i = 0; i<completeDistributionList.length; i++)
    			{
    				let tempintensityVal = (completeDistributionList[i].intensity/maxintensity)*100;
    				if( tempintensityVal > this.minintensity)
    				{
    					completeDistributionList[i].intensity = Math.round(tempintensityVal * 1000000) / 1000000;//tempintensityVal//Math.round(tempintensityVal * 1000000) / 1000000; //parseFloat(((tempDistributionList[i].intensity/maxintensity)*100).toFixed(6)) ;
    					completeDistributionList_temp.push(completeDistributionList[i]);
    				}
    			}
    			completeDistributionList = completeDistributionList_temp ;
    			return completeDistributionList ;
    		}
    	}
    	/**
    	 * Code to get the molecular formular based on the mass.
    	 * Number of atoms of each atom in the averagine * number of
    	 * atoms of each atom calculated from mass gives the total number of atoms.
    	 */
    	getMolecularFormula(){
    		let atomListwithCnt = new Array(5) ;
    		let len = this.atomList.length;
    		let mass = 0;
    		for(let i=0;i<len;i++)
    		{
    			let count ;
    			if(this.atomList[i] == "C"){
    				count = parseInt(this.eachAtomCount * this.carbonCnt) ;
    				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
    			}
    			else if(this.atomList[i] == "H"){
    				count = parseInt(this.eachAtomCount * this.hydrogenCnt) ;
    				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
    			}
    			else if(this.atomList[i] == "N"){
    				count = parseInt(this.eachAtomCount * this.nitrogenCnt) ;
    				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
    			}
    			else if(this.atomList[i] == "O"){
    				count = parseInt(this.eachAtomCount * this.oxygenCnt) ;
    				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
    			}
    			else if(this.atomList[i] == "S"){
    				count = parseInt(this.eachAtomCount * this.sulphurCnt) ;
    				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
    			}
    			atomListwithCnt[i] = {atom:this.atomList[i],count:count};
    		}
    		return [atomListwithCnt,mass] ;
    	}
    	/**
    	 * Code to calculate MZ(mass/charge) and remove low intensity values
    	 * @param {Array} totDistributionList - Array with total distribution
    	 * @param {Float} charge - Float from mass list
    	 * @param {Float} massError - Calculated massError needed to be added
    	 * as we taken int values of number of atoms eleminating mass from the float values.
    	 * @param {Array} peakDataList - Array of peak list from user
    	 */
    	getMZwithHighInte(totDistributionList,charge,massError,peakDataList)
    	{
    		let len = totDistributionList.length;
    		let newtotDistributionList = [];
    		let overaAllMaxIntensity = 0 ;
    		let onePerctInte = 0;
    		let peakListLen = peakDataList.length;
    		for(let k=0;k<peakListLen ; k++)
    		{
    			if(peakDataList[k].intensity > overaAllMaxIntensity) overaAllMaxIntensity = peakDataList[k].intensity ;
    		}
    		onePerctInte = overaAllMaxIntensity/100 ;
    		for(let i=0;i<len;i++)
    		{
    			let intensity = totDistributionList[i].intensity ;
    			intensity = overaAllMaxIntensity * intensity/100 ;
    			if(intensity > onePerctInte)
    			{
    				let mz = (totDistributionList[i].mass+massError)/charge + this.protonMass;
    				let intensity = totDistributionList[i].intensity ;
    				//Converting mass variable to mz(mass/charge)
    				let tempdistObj = {mz:mz,intensity:intensity};
    				newtotDistributionList.push(tempdistObj);
    			}
    		}
    		return newtotDistributionList ;
    	}
    	/**
    	 * Code to normalize the Intensity.
    	 * Take the average of intensity from the peaks entered by the user.
    	 * Take the average of the calculated distribution for each Array element in the Array.
    	 * Make both of them equal and calculating the rest of the
    	 * distribution intensity based on the avg value from the peak list.
    	 * @param {Array} totDistributionList - Total distribution calculated
    	 * @param {Array} peakDataList - Peak Data entered by the user
    	 */
    	getNormalizedIntensity(totDistributionList,peakDataList)
    	{
    		let len = totDistributionList.length;
    		let peakListLen = peakDataList.length;
    		let intensity = 0;
    		let count = 0 ;
    		let distributionInte = 0;
    		let maxinte = 0;
    		let mininte = 100;

    		let peakMaxinte = 0;
    		let peakMininte = 10000000;

    		let maxMz = 0;
    		let minMz = 10000000;
    		for(let i=0;i<len;i++)
    		{
    			for(let j=0;j<peakListLen;j++)
    			{
    				if((totDistributionList[i].mz - peakDataList[j].mz) <= this.toleraceMassDiff
    					&& (totDistributionList[i].mz - peakDataList[j].mz) >= 0-this.toleraceMassDiff )
    				{
    					if(maxMz < totDistributionList[i].mz){
    						maxMz = totDistributionList[i].mz;
    					}
    					if(minMz > totDistributionList[i].mz){
    						minMz = totDistributionList[i].mz;
    					}
    					count = count + 1;
    				}
    			}
    		}
    		maxMz = maxMz + this.toleraceMassDiff;
    		minMz = minMz - this.toleraceMassDiff;
    		for(let i=0;i<len;i++)
    		{
    			if(minMz <= totDistributionList[i].mz &&  totDistributionList[i].mz <= maxMz)
    			{
    				if(maxinte < totDistributionList[i].intensity){
    					maxinte = totDistributionList[i].intensity;
    				}
    				if(mininte > totDistributionList[i].intensity){
    					mininte = totDistributionList[i].intensity;
    				}
    			}
    		}
    		for(let j=0;j<peakListLen;j++)
    		{
    			if(peakDataList[j].mz >= minMz && peakDataList[j].mz <= maxMz)
    			{
    				if(peakMaxinte < peakDataList[j].intensity){
    					peakMaxinte = peakDataList[j].intensity;
    				}
    				if(peakMininte > peakDataList[j].intensity){
    					peakMininte = peakDataList[j].intensity;
    				}
    			}
    		}
    		if(count !=0 )
    		{
    			let avg ;
    			let distributionAvgInte;

    			avg = (peakMaxinte + peakMininte)/2;
    			distributionAvgInte = (maxinte+mininte)/2;
    			for(let i=0;i<len;i++)
    			{
    				totDistributionList[i].intensity = (avg * totDistributionList[i].intensity)/distributionAvgInte ;
    			}
    		}
    		return totDistributionList ;
    	}
    }