content: /*
   * Copyright (c) 1997 - 2016
   * Actelion Pharmaceuticals Ltd.
   * Gewerbestrasse 16
   * CH-4123 Allschwil, Switzerland
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice, this
   *    list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   * 3. Neither the name of the the copyright holder nor the
   *    names of its contributors may be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * @author Thomas Sander
   */
  
  package com.actelion.research.chem;
  
  import com.actelion.research.chem.io.CompoundFileParser;
  import com.actelion.research.chem.io.DWARFileParser;
  import com.actelion.research.chem.io.SDFileParser;
  
  import java.io.*;
  import java.nio.charset.StandardCharsets;
  import java.util.TreeMap;
  import java.util.TreeSet;
  
  
  public class AtomTypeList {
      private static final String VERSION_STRING = "AtomTypeList v1.1";
  
      private static final int DEFAULT_MIN_ATOMS = 10;
  	private static final int DEFAULT_MAX_ATOMS = 50;
  
      private TreeMap<Long,Integer>	mCountList;
  	private TreeMap<Long,Double>	mProbabilityList;
  	private float[]	            	mRingSizeAdjust;
  	private int                 	mAtomTypeMode,mMaxAtoms,mMinAtoms;
  
  	/**
  	 * Creates an empty AtomTypeList, which must be populated by multiply calling processMolecule()
  	 * and finally calling finalizeProcessMolecules() once.
  	 * @param mode
  	 */
      public AtomTypeList(int mode) {
          mRingSizeAdjust = new float[8];
          mCountList = new TreeMap<Long,Integer>();
  		mAtomTypeMode = mode;
  		mMinAtoms = DEFAULT_MIN_ATOMS;
  		mMaxAtoms = DEFAULT_MAX_ATOMS;
          }
  
  
  	/**
  	 * Creates a new AtomTypeList from a given file using the given mode.
  	 * If the filename references a .typ file, then the mode is checked, whether it matches the file's content.
  	 * If the filename references a compound file, then the molecules are parsed and a new AtomTypeList is created
  	 * reflecting the all contained atom types.
  	 * @param filename either .typ file or a .dwar or .sdf compound file
  	 * @param mode
  	 * @throws Exception
  	 */
  	public AtomTypeList(String filename, int mode) throws Exception {
          this(mode);
  
          if (filename.endsWith(".typ")) {
  	        BufferedReader theReader = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream(filename), StandardCharsets.UTF_8));
  	        String version =theReader.readLine();
  	        if (!VERSION_STRING.equals(version)) {
  	            throw new Exception("Outdated atom type list file.");
  	            }
  
  	        mAtomTypeMode = Integer.parseInt(theReader.readLine());
  	        if (mAtomTypeMode != mode) {
  	            throw new Exception("Incompatible atom type mode.");
  	            }
  
  	        for (int i=0; i<8; i++)
  	            mRingSizeAdjust[i] = Float.parseFloat(theReader.readLine());
  
  	        while (true) {
  	            String theLine = theReader.readLine();
  	            if (theLine == null)
  	                break;
  
  	            int tab = theLine.indexOf('\t');
  	            mCountList.put(Long.valueOf(Long.parseLong(theLine.substring(0, tab))),
  	                      Integer.valueOf(Integer.parseInt(theLine.substring(tab+1))));
  	            }
  	        theReader.close();
  	        return;
  	        }
  
          CompoundFileParser parser = filename.endsWith(".dwar") ? new DWARFileParser(filename)
  								  : filename.endsWith(".sdf")  ? new SDFileParser(filename) : null;
  
  		if (parser != null) {
  			TreeSet<String> moleculeCache = new TreeSet<String>();
  			try {
  				while (parser.next())
  					processMolecule(parser.getMolecule(), moleculeCache);
  				}
  			catch (Exception e) {
  				e.printStackTrace();
  				}
  
  			finalizeProcessMolecules();
  			}
  		}
  
  
  	public void finalizeProcessMolecules() {
  		float ringSum = 0.0f;
  		for (int i=0; i<8; i++)
  			ringSum += mRingSizeAdjust[i];
  		if (ringSum != 0)
  			for (int i=0; i<8; i++)
  				mRingSizeAdjust[i] /= ringSum;
  		}
  
  	public synchronized void calculateProbabilities() {
  		if (mProbabilityList == null) {
  			int averageCount = 0;
  			for (Integer count: mCountList.values())
  				averageCount += count;
  			averageCount /= mCountList.size();
  
  			mProbabilityList = new TreeMap<>();
  			for (Long type: mCountList.keySet())
  				mProbabilityList.put(type, (double)mCountList.get(type)/averageCount);
  			}
  		}
  
  
  	public void writeTypeFile(String filename) {
  		try {
  			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), StandardCharsets.UTF_8));
  			writer.write(VERSION_STRING);
  			writer.newLine();
  
  			writer.write(""+mAtomTypeMode);
  			writer.newLine();
  
  			for (int i=0; i<8; i++) {
  				writer.write(""+mRingSizeAdjust[i]);
  				writer.newLine();
  				}
  
  			for (Long type: mCountList.keySet()) {
  				writer.write(type.toString()+"\t"+ mCountList.get(type).toString());
  				writer.newLine();
  				}
  			writer.close();
  			}
  		catch (Exception e) {
  			e.printStackTrace();
  			}
  		}
  
  
  	/**
  	 * Writes this AtomTypeList into a TAB-delimited text file in human readable form.
  	 * Atom type frequency and all properties making up an atom type are written into separate columns.
  	 * @param textfilename
  	 * @param mode
  	 */
  	public void writeTextFile(String textfilename, int mode) {
  		try {
  			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(textfilename), StandardCharsets.UTF_8));
  			writer.write("AtomType\tFrequency\t"+AtomTypeCalculator.getHeaderString(mode));
  			writer.newLine();
  
  			for (Long type: mCountList.keySet()) {
  				writer.write(Long.toString(type)+"\t"+ mCountList.get(type).toString()+"\t"+AtomTypeCalculator.getTypeString(type, mode));
  				writer.newLine();
  				}
  			writer.close();
  			}
  		catch (Exception e) {
  			e.printStackTrace();
  			}
  		}
  
  
  	public TreeMap<Long,Integer> getAtomTypeList() {
  	    return mCountList;
  	    }
  
  
  	/**
  	 *
  	 * @param mol
  	 * @param moleculeCache
  	 */
  	public void processMolecule(StereoMolecule mol, TreeSet<String> moleculeCache) {
  		if (mol != null) {
  			mol.stripIsotopInfo();
  			mol.stripSmallFragments();
  			mol.stripStereoInformation();
  
  			mol.ensureHelperArrays(Molecule.cHelperNeighbours);
  
  			boolean containsMetal = false;
  			for (int atom=0; atom<mol.getAtoms(); atom++) {
  				if (mol.isMetalAtom(atom)) {
  					containsMetal = true;
  					break;
  					}
  				}
  
  			if (!containsMetal && mol.getAtoms() >= mMinAtoms && mol.getAtoms() <= mMaxAtoms) {
  				String idcode = new Canonizer(mol).getIDCode();
  				if (!moleculeCache.contains(idcode)) {
  					moleculeCache.add(idcode);
  
  					for(int atom=0;atom<mol.getAtoms();atom++)
  						try {
  							addAtomType(AtomTypeCalculator.getAtomType(mol, atom, mAtomTypeMode));
  							} catch (Exception e) {}
  
  //for(int atom=0;atom<mol.getAtoms();atom++) { long type = AtomTypeCalculator.getAtomType(mol, atom, mAtomTypeMode); if (type == 2689028 || type == 11077636) System.out.println(idcode); };
  
  					RingCollection ringSet = mol.getRingSet();
  					for (int ring=0; ring<ringSet.getSize(); ring++)
  						mRingSizeAdjust[ringSet.getRingSize(ring)]++;
  					}
  				}
  			}
  		}
  
  
  	private void addAtomType(Long atomType) {
  	    Integer count = mCountList.get(atomType);
  	    if (count == null)
  	        mCountList.put(atomType, 1);
  	    else
              mCountList.put(atomType, count+1);
  		}
  
  
  	public int getCountFromType(long type){
  	    return mCountList.get(Long.valueOf(type)).intValue();
  		}
  
  
  	public double getProbabilityFromType(long type){
  		return mProbabilityList.get(type);
  	}
  
  
  	public float getRingSizeAdjust(int ringSize) {
  		return mRingSizeAdjust[ringSize];
  		}
  	}
  