content: /*************************************************************************
  *                                                                        *
  *  This file is part of the 20n/act project.                             *
  *  20n/act enables DNA prediction for synthetic biology/bioengineering.  *
  *  Copyright (C) 2017 20n Labs, Inc.                                     *
  *                                                                        *
  *  Please direct all queries to act@20n.com.                             *
  *                                                                        *
  *  This program is free software: you can redistribute it and/or modify  *
  *  it under the terms of the GNU General Public License as published by  *
  *  the Free Software Foundation, either version 3 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 General Public License for more details.                          *
  *                                                                        *
  *  You should have received a copy of the GNU General Public License     *
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *                                                                        *
  *************************************************************************/
  
  package com.act.biointerpretation.l2expansion;
  
  import chemaxon.calculations.clean.Cleaner;
  import chemaxon.formats.MolFormatException;
  import chemaxon.reaction.ReactionException;
  import chemaxon.struc.Molecule;
  import chemaxon.struc.MoleculeGraph;
  import com.act.analysis.chemicals.molecules.MoleculeExporter;
  import com.act.analysis.chemicals.molecules.MoleculeFormat;
  import com.act.analysis.chemicals.molecules.MoleculeFormat$;
  import com.act.biointerpretation.Utils.ReactionProjector;
  import com.act.biointerpretation.sars.Sar;
  import com.act.biointerpretation.sars.SerializableReactor;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  
  /**
   * Generates all predictions for a given PredictionSeed.
   */
  public class AllPredictionsGenerator implements PredictionGenerator {
  
    private String moleculeFormat;
  
    private static final Integer CLEAN_DIMENSION = 2;
  
    final ReactionProjector projector;
    Integer nextUid = 0; // Keeps track of the next available unique id to assign to a prediction
  
    public AllPredictionsGenerator(ReactionProjector projector) {
      this.projector = projector;
      this.moleculeFormat = MoleculeFormat.stdInchi$.MODULE$.toString();
    }
  
    // Allow for the caller to change the format.
    public AllPredictionsGenerator(ReactionProjector projector, String format) {
      this.projector = projector;
      moleculeFormat = format;
    }
  
    @Override
    public List<L2Prediction> getPredictions(PredictionSeed seed) throws IOException, ReactionException {
      List<Molecule> substrates = seed.getSubstrates();
      cleanAndAromatize(substrates);
  
      List<Sar> sars = seed.getSars();
      SerializableReactor reactor = seed.getRo();
  
      // If one or more SARs are supplied, test them before applying the reactor.
      for (Sar sar : sars) {
        if (!sar.test(substrates)) {
          return Collections.emptyList();
        }
      }
  
      Molecule[] substratesArray = substrates.toArray(new Molecule[substrates.size()]);
  
      try {
        Map<Molecule[], List<Molecule[]>> projectionMap =
            projector.getRoProjectionMap(substratesArray, reactor.getReactor());
        return getAllPredictions(projectionMap, seed.getProjectorName());
      } catch (ReactionException e) {
        StringBuilder builder = new StringBuilder();
        builder.append(e.getMessage())
            .append(": substrates, reactor: ").append(getMoleculeStrings(substratesArray))
            .append(",").append(reactor.getReactorSmarts());
        throw new ReactionException(builder.toString());
      }
    }
  
    /**
     * Returns all predictions corresponding to a given projection map
     *
     * @param projectionMap The map from substrates to products.
     * @return The list of predictions.
     * @throws IOException
     */
    private List<L2Prediction> getAllPredictions(Map<Molecule[], List<Molecule[]>> projectionMap,
                                                 String name) throws IOException {
  
      List<L2Prediction> result = new ArrayList<>();
  
      for (Molecule[] substrates : projectionMap.keySet()) {
        List<L2PredictionChemical> predictedSubstrates =
            L2PredictionChemical.getPredictionChemicals(getMoleculeStrings(substrates));
  
        for (Molecule[] products : projectionMap.get(substrates)) {
          List<L2PredictionChemical> predictedProducts =
              L2PredictionChemical.getPredictionChemicals(getMoleculeStrings(products));
  
          L2Prediction prediction = new L2Prediction(nextUid, predictedSubstrates, name, predictedProducts);
  
          result.add(prediction);
          nextUid++;
        }
      }
      return result;
    }
  
  
    /**
     * Translate an array of Chemaxon Molecules into a List of their string representations
     *
     * @param mols An array of molecules.
     * @return An array of inchis corresponding to the supplied molecules.
     */
    private List<String> getMoleculeStrings(Molecule[] mols) throws IOException {
      List<String> moleculeStrings = new ArrayList<>();
      MoleculeFormat.MoleculeFormatType moleculeFormat = MoleculeFormat$.MODULE$.getName(this.moleculeFormat);
      for (Molecule mol : mols) {
        // Grabs the string through the exporter.
        // Default is "inchi:AuxNone,SAbs,Woff", which means no aux information and absolute stereo.
        moleculeStrings.add(MoleculeExporter.exportMolecule(mol, moleculeFormat));
      }
      return moleculeStrings;
    }
  
    /**
     * Aromatizes all molecules in the input array
     *
     * @param molecules Molecules to aromatize.
     * @throws MolFormatException
     */
    private void cleanAndAromatize(List<Molecule> molecules) throws MolFormatException {
      for (Molecule mol : molecules) {
        mol.aromatize(MoleculeGraph.AROM_BASIC);
        Cleaner.clean(mol, CLEAN_DIMENSION);
      }
    }
  
  }
  