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.reachables;
  
  import act.shared.helpers.P;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  public class OutdatedWavefrontExpansion extends SteppedTask {
    Set<Long> R;
    HashMap<Long, List<Long>> rxn_needs;
    HashMap<Integer, Set<Long>> R_by_layers;
    int currentLayer;
  
    public OutdatedWavefrontExpansion() {
      this.R = new HashSet<Long>();
      this.R_by_layers = new HashMap<Integer, Set<Long>>();
      this.rxn_needs = computeRxnNeeds();
      this.currentLayer = 0;
    }
  
    private HashMap<Long, List<Long>> computeRxnNeeds() {
      HashMap<Long, List<Long>> needs = new HashMap<Long, List<Long>>();
      for (Long r : ActData.instance().rxnSubstrates.keySet()) {
        needs.put(r, new ArrayList<Long>(ActData.instance().rxnSubstrates.get(r)));
      }
      return needs;
    }
  
    protected Set<Long> productsOf(Set<Long> enabledRxns) {
      Set<Long> P = new HashSet<Long>();
      for (Long r : enabledRxns) {
        P.addAll(ActData.instance().rxnProducts.get(r));
      }
      return P;
    }
  
    @Override
    public double percentDone() {
      return anyEnabledReactions(null) ? 0 : 100;
    }
  
    @Override
    public void doMoreWork() {
      pushWaveFront(null, true /* do increment layer counter */);
    }
  
    private void addAllHostMetabolites(long hostID) {
      pushWaveFront(hostID, false /* do not increment layer counter */);
    }
  
    private static String _fileloc = "com.act.reachables.ConditionalReachable";
    private static void logProgress(String format, Object... args) {
      if (!GlobalParams.LOG_PROGRESS)
        return;
  
      System.err.format(_fileloc + ": " + format, args);
    }
  
    private static void logProgress(String msg) {
      if (!GlobalParams.LOG_PROGRESS)
        return;
  
      System.err.println(_fileloc + ": " + msg);
    }
  
    private void pushWaveFront(Long orgID, boolean incrementLayer) {
      Set<Long> enabledRxns = extractEnabledRxns(orgID);
      if (orgID != null)
        logProgress("Org: %d, num enabled rxns: %d\n", orgID, enabledRxns.size());
      Set<Long> newReachables = productsOf(enabledRxns);
  
      {
        // this is work done for layout, i.e., setting the layers, and first reach reactions
        Set<Long> uniqNew = new HashSet<Long>(newReachables);
        uniqNew.removeAll(R);
        if (uniqNew.size() > 0) {
          addToLayers(uniqNew, this.currentLayer, true /* add to existing layer */);
          if (incrementLayer)
            this.currentLayer++;
        }
        tagActivatingRxns(enabledRxns, uniqNew);
      }
  
      R.addAll(newReachables);
      if (orgID != null)
        logProgress("Org: %d, num newReachables in layer %d: %d\n", orgID, this.currentLayer-1, newReachables.size());
      updateEnabled(newReachables);
    }
  
    private void tagActivatingRxns(Set<Long> rxns, Set<Long> newChems) {
  
      for (Long rxn : rxns) {
        Set<Long> activated_products = new HashSet<Long>();
        for (Long p : ActData.instance().rxnProducts.get(rxn))
          if (newChems.contains(p))
            activated_products.add(p);
        if (activated_products.size() > 0) {
          // tag it as such
          for (Long s : ActData.instance().rxnSubstrates.get(rxn)) {
            for (Long p : activated_products) {
              Edge e = ActData.instance().rxnsInAct.get(new P<Long, Long>(s, p));
              Edge.setAttribute(e, "activates_product", true);
            }
          }
        }
      }
    }
  
    protected boolean anyEnabledReactions(Long orgID) {
      for (Long r : this.rxn_needs.keySet()) {
        if (orgID == null || ActData.instance().rxnOrganisms.get(r).contains(orgID))
          if (this.rxn_needs.get(r).isEmpty())
            return true;
      }
      return false;
    }
  
    protected Set<Long> extractEnabledRxns(Long orgID) {
      Set<Long> enabled = new HashSet<Long>();
      for (Long r : this.rxn_needs.keySet())
        if (this.rxn_needs.get(r).isEmpty()) {
          // if no orgID specified: add all rxns from any organism,
          // if orgID is specified: only if the reaction happens in the org
          if (orgID == null || ActData.instance().rxnOrganisms.get(r).contains(orgID))
            enabled.add(r);
        }
      for (Long r : enabled)
        this.rxn_needs.remove(r);
      return enabled;
    }
  
    protected void updateEnabled(Set<Long> newReachables) {
      for (Long r : this.rxn_needs.keySet()) {
        List<Long> needs = new ArrayList<Long>();
        for (Long l : this.rxn_needs.get(r)) {
          if (!newReachables.contains(l))
            needs.add(l);
        }
        this.rxn_needs.put(r, needs);
      }
    }
  
    @Override
    public void init() {
      for (Long c : ActData.instance().cofactors)
        R.add(c);
      for (Long n : ActData.instance().natives)
         R.add(n);
  
      addToLayers(R, this.currentLayer++, false /* add to new layer */);
      updateEnabled(R);
      // add all host organism reachables
      while (anyEnabledReactions(GlobalParams.gethostOrganismID()))
        addAllHostMetabolites(GlobalParams.gethostOrganismID());
    }
  
    private void addToLayers(Set<Long> nodes, int layer, boolean addToExisting) {
      Set<Long> addNodes = new HashSet<Long>(nodes);
      if (this.R_by_layers.containsKey(layer)) {
        if (addToExisting)
          addNodes.addAll(this.R_by_layers.get(layer));
        else
          logProgress("ERR: Layer nodeMapping already installed and addToExisting not requested. How did new nodeMapping appear at the same later!?");
      }
      for (Long c : nodes) {
        Node n = ActData.instance().chemsInAct.get(c);
        if (n != null)
          Node.setAttribute(n.getIdentifier(), "reachable_layer", layer);
      }
      this.R_by_layers.put(layer, addNodes);
    }
  
    @Override
    public void finalize(TaskMonitor tm) {
      int N = R.size();
      int i = 0;
      tm.setStatus("Reachable: " +  N + " nodeMapping. Setting isReachable, and selecting nodeMapping");
      Set<Node> reach1 = new HashSet<Node>();
      for (Long r : R) {
        tm.setPercentCompleted((int)(100 * ((double)(i++)/N)));
        if (!ActData.instance().chemsInAct.containsKey(r))
          continue; // in cases where the native is also a cofactor, it would not have a node.
  
        // set the attributes in the act network
        Long n1 = ActData.instance().chemsInAct.get(r).getIdentifier();
        Node.setAttribute(n1, "isReachable", true);
        reach1.add(ActData.instance().chemsInAct.get(r));
      }
      ActData.instance().Act.unselectAllNodes();
      ActData.instance().Act.setSelectedNodeState(reach1, true);
    }
  
  }
  