/*
 * Copyright 2000-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jetbrains.java.decompiler.modules.decompiler.sforms;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;

import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent;
import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent;
import org.jetbrains.java.decompiler.modules.decompiler.sforms.FlattenStatementsHelper.FinallyPathWrapper;
import org.jetbrains.java.decompiler.modules.decompiler.stats.CatchAllStatement;
import org.jetbrains.java.decompiler.modules.decompiler.stats.CatchStatement;
import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement;
import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement;
import org.jetbrains.java.decompiler.modules.decompiler.stats.SynchronizedStatement;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionEdge;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionNode;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair;
import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionsGraph;
import org.jetbrains.java.decompiler.struct.StructMethod;
import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
import org.jetbrains.java.decompiler.util.FastSparseSetFactory;
import org.jetbrains.java.decompiler.util.FastSparseSetFactory.FastSparseSet;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.SFormsFastMapDirect;
import org.jetbrains.java.decompiler.util.VBStyleCollection;

public class SSAUConstructorSparseEx {

  // node id, var, version
  private final HashMap<String, SFormsFastMapDirect> inVarVersions = new HashMap<>();
  //private HashMap<String, HashMap<Integer, FastSet<Integer>>> inVarVersions = new HashMap<String, HashMap<Integer, FastSet<Integer>>>();

  // node id, var, version (direct branch)
  private final HashMap<String, SFormsFastMapDirect> outVarVersions = new HashMap<>();
  //private HashMap<String, HashMap<Integer, FastSet<Integer>>> outVarVersions = new HashMap<String, HashMap<Integer, FastSet<Integer>>>();

  // node id, var, version (negative branch)
  private final HashMap<String, SFormsFastMapDirect> outNegVarVersions = new HashMap<>();
  //private HashMap<String, HashMap<Integer, FastSet<Integer>>> outNegVarVersions = new HashMap<String, HashMap<Integer, FastSet<Integer>>>();

  // node id, var, version
  private final HashMap<String, SFormsFastMapDirect> extraVarVersions = new HashMap<>();
  //private HashMap<String, HashMap<Integer, FastSet<Integer>>> extraVarVersions = new HashMap<String, HashMap<Integer, FastSet<Integer>>>();

  // (var, version), version
  private final HashMap<VarVersionPair, HashSet<Integer>> phi = new HashMap<>();

  // var, version
  private final HashMap<Integer, Integer> lastversion = new HashMap<>();

  // version, protected ranges (catch, finally)
  private final HashMap<VarVersionPair, Integer> mapVersionFirstRange = new HashMap<>();

  // version, version
  private final HashMap<VarVersionPair, VarVersionPair> phantomppnodes = new HashMap<>(); // ++ and --

  // node.id, version, version
  private final HashMap<String, HashMap<VarVersionPair, VarVersionPair>> phantomexitnodes = new HashMap<>(); // finally exits

  // versions memory dependencies
  private final VarVersionsGraph ssuversions = new VarVersionsGraph();

  // field access vars (exprent id, var id)
  private final HashMap<Integer, Integer> mapFieldVars = new HashMap<>();

  // field access counter
  private int fieldvarcounter = -1;

  // set factory
  private FastSparseSetFactory<Integer> factory;

  public void splitVariables(RootStatement root, StructMethod mt) {

    FlattenStatementsHelper flatthelper = new FlattenStatementsHelper();
    DirectGraph dgraph = flatthelper.buildDirectGraph(root);

    HashSet<Integer> setInit = new HashSet<>();
    for (int i = 0; i < 64; i++) {
      setInit.add(i);
    }
    factory = new FastSparseSetFactory<>(setInit);

    extraVarVersions.put(dgraph.first.id, createFirstMap(mt, root));

    setCatchMaps(root, dgraph, flatthelper);

    //		try {
    //			DotExporter.toDotFile(dgraph, new File("c:\\Temp\\gr12_my.dot"));
    //		} catch(Exception ex) {ex.printStackTrace();}

    HashSet<String> updated = new HashSet<>();
    do {
      //			System.out.println("~~~~~~~~~~~~~ \r\n"+root.toJava());
      ssaStatements(dgraph, updated, false);
      //			System.out.println("~~~~~~~~~~~~~ \r\n"+root.toJava());
    } while (!updated.isEmpty());

    ssaStatements(dgraph, updated, true);

    ssuversions.initDominators();
  }

  private void ssaStatements(DirectGraph dgraph, HashSet<String> updated, boolean calcLiveVars) {

    for (DirectNode node : dgraph.nodes) {

      updated.remove(node.id);
      mergeInVarMaps(node, dgraph);

      SFormsFastMapDirect varmap = new SFormsFastMapDirect(inVarVersions.get(node.id));

      SFormsFastMapDirect[] varmaparr = new SFormsFastMapDirect[] { varmap, null };

      if (node.exprents != null) {
        for (Exprent expr : node.exprents) {
          processExprent(expr, varmaparr, node.statement, calcLiveVars);
        }
      }

      if (varmaparr[1] == null) {
        varmaparr[1] = varmaparr[0];
      }

      // quick solution: 'dummy' field variables should not cross basic block borders (otherwise problems e.g. with finally loops - usage without assignment in a loop)
      // For the full solution consider adding a dummy assignment at the entry point of the method
      boolean allow_field_propagation = node.succs.isEmpty() || (node.succs.size() == 1 && node.succs.get(0).preds.size() == 1);

      if (!allow_field_propagation && varmaparr[0] != null) {
        varmaparr[0].removeAllFields();
        varmaparr[1].removeAllFields();
      }

      boolean this_updated = !mapsEqual(varmaparr[0], outVarVersions.get(node.id))
          || (outNegVarVersions.containsKey(node.id) && !mapsEqual(varmaparr[1], outNegVarVersions.get(node.id)));

      if (this_updated) {

        outVarVersions.put(node.id, varmaparr[0]);
        if (dgraph.mapNegIfBranch.containsKey(node.id)) {
          outNegVarVersions.put(node.id, varmaparr[1]);
        }

        for (DirectNode nd : node.succs) {
          updated.add(nd.id);
        }
      }
    }
  }

  private void processExprent(Exprent expr, SFormsFastMapDirect[] varmaparr, Statement stat, boolean calcLiveVars) {

    if (expr == null) {
      return;
    }

    VarExprent varassign = null;
    boolean finished = false;

    switch (expr.type) {
    case Exprent.EXPRENT_ASSIGNMENT:
      AssignmentExprent assexpr = (AssignmentExprent) expr;
      if (assexpr.getCondType() == AssignmentExprent.CONDITION_NONE) {
        Exprent dest = assexpr.getLeft();
        if (dest.type == Exprent.EXPRENT_VAR) {
          varassign = (VarExprent) dest;
        }
      }
      break;
    case Exprent.EXPRENT_FUNCTION:
      FunctionExprent func = (FunctionExprent) expr;
      switch (func.getFuncType()) {
      case FunctionExprent.FUNCTION_IIF:
        processExprent(func.getLstOperands().get(0), varmaparr, stat, calcLiveVars);

        SFormsFastMapDirect varmapFalse;
        if (varmaparr[1] == null) {
          varmapFalse = new SFormsFastMapDirect(varmaparr[0]);
        } else {
          varmapFalse = varmaparr[1];
          varmaparr[1] = null;
        }

        processExprent(func.getLstOperands().get(1), varmaparr, stat, calcLiveVars);

        SFormsFastMapDirect[] varmaparrNeg = new SFormsFastMapDirect[] { varmapFalse, null };
        processExprent(func.getLstOperands().get(2), varmaparrNeg, stat, calcLiveVars);

        mergeMaps(varmaparr[0], varmaparrNeg[0]);
        varmaparr[1] = null;

        finished = true;
        break;
      case FunctionExprent.FUNCTION_CADD:
        processExprent(func.getLstOperands().get(0), varmaparr, stat, calcLiveVars);

        SFormsFastMapDirect[] varmaparrAnd = new SFormsFastMapDirect[] { new SFormsFastMapDirect(varmaparr[0]), null };

        processExprent(func.getLstOperands().get(1), varmaparrAnd, stat, calcLiveVars);

        // false map
        varmaparr[1] = mergeMaps(varmaparr[varmaparr[1] == null ? 0 : 1], varmaparrAnd[varmaparrAnd[1] == null ? 0 : 1]);
        // true map
        varmaparr[0] = varmaparrAnd[0];

        finished = true;
        break;
      case FunctionExprent.FUNCTION_COR:
        processExprent(func.getLstOperands().get(0), varmaparr, stat, calcLiveVars);

        SFormsFastMapDirect[] varmaparrOr = new SFormsFastMapDirect[] { new SFormsFastMapDirect(varmaparr[varmaparr[1] == null ? 0 : 1]), null };

        processExprent(func.getLstOperands().get(1), varmaparrOr, stat, calcLiveVars);

        // false map
        varmaparr[1] = varmaparrOr[varmaparrOr[1] == null ? 0 : 1];
        // true map
        varmaparr[0] = mergeMaps(varmaparr[0], varmaparrOr[0]);

        finished = true;
      }
    }

    if (!finished) {
      List<Exprent> lst = expr.getAllExprents();
      lst.remove(varassign);

      for (Exprent ex : lst) {
        processExprent(ex, varmaparr, stat, calcLiveVars);
      }
    }

    SFormsFastMapDirect varmap = varmaparr[0];

    // field access
    if (expr.type == Exprent.EXPRENT_FIELD) {

      int index;
      if (mapFieldVars.containsKey(expr.id)) {
        index = mapFieldVars.get(expr.id);
      } else {
        index = fieldvarcounter--;
        mapFieldVars.put(expr.id, index);

        // ssu graph
        ssuversions.createNode(new VarVersionPair(index, 1));
      }

      setCurrentVar(varmap, index, 1);
    } else if (expr.type == Exprent.EXPRENT_INVOCATION
        || (expr.type == Exprent.EXPRENT_ASSIGNMENT && ((AssignmentExprent) expr).getLeft().type == Exprent.EXPRENT_FIELD)
        || (expr.type == Exprent.EXPRENT_NEW && ((NewExprent) expr).getNewType().type == CodeConstants.TYPE_OBJECT)
        || expr.type == Exprent.EXPRENT_FUNCTION) {

      boolean ismmpp = true;

      if (expr.type == Exprent.EXPRENT_FUNCTION) {

        ismmpp = false;

        FunctionExprent fexpr = (FunctionExprent) expr;
        if (fexpr.getFuncType() >= FunctionExprent.FUNCTION_IMM && fexpr.getFuncType() <= FunctionExprent.FUNCTION_PPI) {
          if (fexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FIELD) {
            ismmpp = true;
          }
        }
      }

      if (ismmpp) {
        varmap.removeAllFields();
      }
    }

    if (varassign != null) {

      Integer varindex = varassign.getIndex();

      if (varassign.getVersion() == 0) {
        // get next version
        Integer nextver = getNextFreeVersion(varindex, stat);

        // set version
        varassign.setVersion(nextver);

        // ssu graph
        ssuversions.createNode(new VarVersionPair(varindex, nextver));

        setCurrentVar(varmap, varindex, nextver);
      } else {
        if (calcLiveVars) {
          varMapToGraph(new VarVersionPair(varindex.intValue(), varassign.getVersion()), varmap);
        }
        setCurrentVar(varmap, varindex, varassign.getVersion());
      }
    } else if (expr.type == Exprent.EXPRENT_FUNCTION) { // MM or PP function
      FunctionExprent func = (FunctionExprent) expr;

      switch (func.getFuncType()) {
      case FunctionExprent.FUNCTION_IMM:
      case FunctionExprent.FUNCTION_MMI:
      case FunctionExprent.FUNCTION_IPP:
      case FunctionExprent.FUNCTION_PPI:

        if (func.getLstOperands().get(0).type == Exprent.EXPRENT_VAR) {
          VarExprent var = (VarExprent) func.getLstOperands().get(0);
          Integer varindex = var.getIndex();
          VarVersionPair varpaar = new VarVersionPair(varindex.intValue(), var.getVersion());

          // ssu graph
          VarVersionPair phantomver = phantomppnodes.get(varpaar);
          if (phantomver == null) {
            // get next version
            Integer nextver = getNextFreeVersion(varindex, null);
            phantomver = new VarVersionPair(varindex, nextver);
            //ssuversions.createOrGetNode(phantomver);
            ssuversions.createNode(phantomver);

            VarVersionNode vernode = ssuversions.nodes.getWithKey(varpaar);

            FastSparseSet<Integer> vers = factory.spawnEmptySet();
            if (vernode.preds.size() == 1) {
              vers.add(vernode.preds.iterator().next().source.version);
            } else {
              for (VarVersionEdge edge : vernode.preds) {
                vers.add(edge.source.preds.iterator().next().source.version);
              }
            }
            vers.add(nextver);
            createOrUpdatePhiNode(varpaar, vers, stat);
            phantomppnodes.put(varpaar, phantomver);
          }
          if (calcLiveVars) {
            varMapToGraph(varpaar, varmap);
          }
          setCurrentVar(varmap, varindex.intValue(), var.getVersion());
        }
      }
    } else if (expr.type == Exprent.EXPRENT_VAR) {

      VarExprent vardest = (VarExprent) expr;

      Integer varindex = vardest.getIndex();
      Integer current_vers = vardest.getVersion();

      FastSparseSet<Integer> vers = varmap.get(varindex);

      int cardinality = vers.getCardinality();
      if (cardinality == 1) { // size == 1
        if (current_vers.intValue() != 0) {
          if (calcLiveVars) {
            varMapToGraph(new VarVersionPair(varindex, current_vers), varmap);
          }
          setCurrentVar(varmap, varindex, current_vers);
        } else {
          // split last version
          Integer usever = getNextFreeVersion(varindex, stat);

          // set version
          vardest.setVersion(usever);
          setCurrentVar(varmap, varindex, usever);

          // ssu graph
          Integer lastver = vers.iterator().next();
          VarVersionNode prenode = ssuversions.nodes.getWithKey(new VarVersionPair(varindex, lastver));
          VarVersionNode usenode = ssuversions.createNode(new VarVersionPair(varindex, usever));
          VarVersionEdge edge = new VarVersionEdge(VarVersionEdge.EDGE_GENERAL, prenode, usenode);
          prenode.addSuccessor(edge);
          usenode.addPredecessor(edge);
        }
      } else if (cardinality == 2) { // size > 1

        if (current_vers.intValue() != 0) {
          if (calcLiveVars) {
            varMapToGraph(new VarVersionPair(varindex, current_vers), varmap);
          }
          setCurrentVar(varmap, varindex, current_vers);
        } else {
          // split version
          Integer usever = getNextFreeVersion(varindex, stat);
          // set version
          vardest.setVersion(usever);

          // ssu node
          ssuversions.createNode(new VarVersionPair(varindex, usever));

          setCurrentVar(varmap, varindex, usever);

          current_vers = usever;
        }

        createOrUpdatePhiNode(new VarVersionPair(varindex, current_vers), vers, stat);
      } // vers.size() == 0 means uninitialized variable, which is impossible
    }
  }

  private void createOrUpdatePhiNode(VarVersionPair phivar, FastSparseSet<Integer> vers, Statement stat) {

    FastSparseSet<Integer> versCopy = vers.getCopy();
    HashSet<Integer> phiVers = new HashSet<>();

    // take into account the corresponding mm/pp node if existing
    int ppvers = phantomppnodes.containsKey(phivar) ? phantomppnodes.get(phivar).version : -1;

    // ssu graph
    VarVersionNode phinode = ssuversions.nodes.getWithKey(phivar);
    List<VarVersionEdge> lstPreds = new ArrayList<>(phinode.preds);
    if (lstPreds.size() == 1) {
      // not yet a phi node
      VarVersionEdge edge = lstPreds.get(0);
      edge.source.removeSuccessor(edge);
      phinode.removePredecessor(edge);
    } else {
      for (VarVersionEdge edge : lstPreds) {
        int verssrc = edge.source.preds.iterator().next().source.version;
        if (!vers.contains(verssrc) && verssrc != ppvers) {
          edge.source.removeSuccessor(edge);
          phinode.removePredecessor(edge);
        } else {
          versCopy.remove(verssrc);
          phiVers.add(verssrc);
        }
      }
    }

    List<VarVersionNode> colnodes = new ArrayList<>();
    List<VarVersionPair> colpaars = new ArrayList<>();

    for (Integer ver : versCopy) {

      VarVersionNode prenode = ssuversions.nodes.getWithKey(new VarVersionPair(phivar.var, ver.intValue()));

      Integer tempver = getNextFreeVersion(phivar.var, stat);

      VarVersionNode tempnode = new VarVersionNode(phivar.var, tempver.intValue());

      colnodes.add(tempnode);
      colpaars.add(new VarVersionPair(phivar.var, tempver.intValue()));

      VarVersionEdge edge = new VarVersionEdge(VarVersionEdge.EDGE_GENERAL, prenode, tempnode);

      prenode.addSuccessor(edge);
      tempnode.addPredecessor(edge);

      edge = new VarVersionEdge(VarVersionEdge.EDGE_GENERAL, tempnode, phinode);
      tempnode.addSuccessor(edge);
      phinode.addPredecessor(edge);

      phiVers.add(tempver);
    }

    ssuversions.addNodes(colnodes, colpaars);

    // update phi node
    phi.put(phivar, phiVers);
  }

  private void varMapToGraph(VarVersionPair varpaar, SFormsFastMapDirect varmap) {

    VBStyleCollection<VarVersionNode, VarVersionPair> nodes = ssuversions.nodes;

    VarVersionNode node = nodes.getWithKey(varpaar);

    node.live = new SFormsFastMapDirect(varmap);
  }

  private Integer getNextFreeVersion(Integer var, Statement stat) {

    Integer nextver = lastversion.get(var);

    if (nextver == null) {
      nextver = new Integer(1);
    } else {
      nextver = new Integer(nextver.intValue() + 1);
    }
    lastversion.put(var, nextver);

    // save the first protected range, containing current statement
    if (stat != null) { // null iff phantom version
      Integer firstRangeId = getFirstProtectedRange(stat);
      if (firstRangeId != null) {
        mapVersionFirstRange.put(new VarVersionPair(var, nextver), firstRangeId);
      }
    }

    return nextver;
  }

  private void mergeInVarMaps(DirectNode node, DirectGraph dgraph) {

    SFormsFastMapDirect mapNew = new SFormsFastMapDirect();

    for (DirectNode pred : node.preds) {
      SFormsFastMapDirect mapOut = getFilteredOutMap(node.id, pred.id, dgraph, node.id);
      if (mapNew.isEmpty()) {
        mapNew = mapOut.getCopy();
      } else {
        mergeMaps(mapNew, mapOut);
      }
    }

    if (extraVarVersions.containsKey(node.id)) {
      SFormsFastMapDirect mapExtra = extraVarVersions.get(node.id);
      if (mapNew.isEmpty()) {
        mapNew = mapExtra.getCopy();
      } else {
        mergeMaps(mapNew, mapExtra);
      }
    }

    inVarVersions.put(node.id, mapNew);
  }

  private SFormsFastMapDirect getFilteredOutMap(String nodeid, String predid, DirectGraph dgraph, String destid) {

    SFormsFastMapDirect mapNew = new SFormsFastMapDirect();

    boolean isFinallyExit = dgraph.mapShortRangeFinallyPaths.containsKey(predid);

    if (nodeid.equals(dgraph.mapNegIfBranch.get(predid))) {
      if (outNegVarVersions.containsKey(predid)) {
        mapNew = outNegVarVersions.get(predid).getCopy();
      }
    } else if (outVarVersions.containsKey(predid)) {
      mapNew = outVarVersions.get(predid).getCopy();
    }

    if (isFinallyExit) {

      SFormsFastMapDirect mapNewTemp = mapNew.getCopy();

      SFormsFastMapDirect mapTrueSource = new SFormsFastMapDirect();

      String exceptionDest = dgraph.mapFinallyMonitorExceptionPathExits.get(predid);
      boolean isExceptionMonitorExit = (exceptionDest != null && !nodeid.equals(exceptionDest));

      HashSet<String> setLongPathWrapper = new HashSet<>();
      for (List<FinallyPathWrapper> lstwrapper : dgraph.mapLongRangeFinallyPaths.values()) {
        for (FinallyPathWrapper finwraplong : lstwrapper) {
          setLongPathWrapper.add(finwraplong.destination + "##" + finwraplong.source);
        }
      }

      for (FinallyPathWrapper finwrap : dgraph.mapShortRangeFinallyPaths.get(predid)) {
        SFormsFastMapDirect map;

        boolean recFinally = dgraph.mapShortRangeFinallyPaths.containsKey(finwrap.source);

        if (recFinally) {
          // recursion
          map = getFilteredOutMap(finwrap.entry, finwrap.source, dgraph, destid);
        } else {
          if (finwrap.entry.equals(dgraph.mapNegIfBranch.get(finwrap.source))) {
            map = outNegVarVersions.get(finwrap.source);
          } else {
            map = outVarVersions.get(finwrap.source);
          }
        }

        // false path?
        boolean isFalsePath = true;

        if (recFinally) {
          isFalsePath = !finwrap.destination.equals(nodeid);
        } else {
          isFalsePath = !setLongPathWrapper.contains(destid + "##" + finwrap.source);
        }

        if (isFalsePath) {
          mapNewTemp.complement(map);
        } else {
          if (mapTrueSource.isEmpty()) {
            if (map != null) {
              mapTrueSource = map.getCopy();
            }
          } else {
            mergeMaps(mapTrueSource, map);
          }
        }
      }

      if (isExceptionMonitorExit) {

        mapNew = mapTrueSource;
      } else {

        mapNewTemp.union(mapTrueSource);
        mapNew.intersection(mapNewTemp);

        if (!mapTrueSource.isEmpty() && !mapNew.isEmpty()) { // FIXME: what for??

          // replace phi versions with corresponding phantom ones
          HashMap<VarVersionPair, VarVersionPair> mapPhantom = phantomexitnodes.get(predid);
          if (mapPhantom == null) {
            mapPhantom = new HashMap<>();
          }

          SFormsFastMapDirect mapExitVar = mapNew.getCopy();
          mapExitVar.complement(mapTrueSource);

          for (Entry<Integer, FastSparseSet<Integer>> ent : mapExitVar.entryList()) {
            for (Integer version : ent.getValue()) {

              Integer varindex = ent.getKey();
              VarVersionPair exitvar = new VarVersionPair(varindex, version);
              FastSparseSet<Integer> newSet = mapNew.get(varindex);

              // remove the actual exit version
              newSet.remove(version);

              // get or create phantom version
              VarVersionPair phantomvar = mapPhantom.get(exitvar);
              if (phantomvar == null) {
                Integer newversion = getNextFreeVersion(exitvar.var, null);
                phantomvar = new VarVersionPair(exitvar.var, newversion.intValue());

                VarVersionNode exitnode = ssuversions.nodes.getWithKey(exitvar);
                VarVersionNode phantomnode = ssuversions.createNode(phantomvar);
                phantomnode.flags |= VarVersionNode.FLAG_PHANTOM_FINEXIT;

                VarVersionEdge edge = new VarVersionEdge(VarVersionEdge.EDGE_PHANTOM, exitnode, phantomnode);
                exitnode.addSuccessor(edge);
                phantomnode.addPredecessor(edge);

                mapPhantom.put(exitvar, phantomvar);
              }

              // add phantom version
              newSet.add(phantomvar.version);
            }
          }

          if (!mapPhantom.isEmpty()) {
            phantomexitnodes.put(predid, mapPhantom);
          }
        }
      }
    }

    return mapNew;
  }

  private static SFormsFastMapDirect mergeMaps(SFormsFastMapDirect mapTo, SFormsFastMapDirect map2) {

    if (map2 != null && !map2.isEmpty()) {
      mapTo.union(map2);
    }

    return mapTo;
  }

  private static boolean mapsEqual(SFormsFastMapDirect map1, SFormsFastMapDirect map2) {

    if (map1 == null) {
      return map2 == null;
    } else if (map2 == null) {
      return false;
    }

    if (map1.size() != map2.size()) {
      return false;
    }

    for (Entry<Integer, FastSparseSet<Integer>> ent2 : map2.entryList()) {
      if (!InterpreterUtil.equalObjects(map1.get(ent2.getKey()), ent2.getValue())) {
        return false;
      }
    }

    return true;
  }

  private void setCurrentVar(SFormsFastMapDirect varmap, Integer var, Integer vers) {
    FastSparseSet<Integer> set = factory.spawnEmptySet();
    set.add(vers);
    varmap.put(var, set);
  }

  private void setCatchMaps(Statement stat, DirectGraph dgraph, FlattenStatementsHelper flatthelper) {

    SFormsFastMapDirect map;

    switch (stat.type) {
    case Statement.TYPE_CATCHALL:
    case Statement.TYPE_TRYCATCH:

      List<VarExprent> lstVars;
      if (stat.type == Statement.TYPE_CATCHALL) {
        lstVars = ((CatchAllStatement) stat).getVars();
      } else {
        lstVars = ((CatchStatement) stat).getVars();
      }

      for (int i = 1; i < stat.getStats().size(); i++) {
        int varindex = lstVars.get(i - 1).getIndex();
        int version = getNextFreeVersion(varindex, stat); // == 1

        map = new SFormsFastMapDirect();
        setCurrentVar(map, varindex, version);

        extraVarVersions.put(dgraph.nodes.getWithKey(flatthelper.getMapDestinationNodes().get(stat.getStats().get(i).id)[0]).id, map);
        //ssuversions.createOrGetNode(new VarVersionPair(varindex, version));
        ssuversions.createNode(new VarVersionPair(varindex, version));
      }
    }

    for (Statement st : stat.getStats()) {
      setCatchMaps(st, dgraph, flatthelper);
    }
  }

  private SFormsFastMapDirect createFirstMap(StructMethod mt, RootStatement root) {
    boolean thisvar = !mt.hasModifier(CodeConstants.ACC_STATIC);

    MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor());

    int paramcount = md.params.length + (thisvar ? 1 : 0);

    int varindex = 0;
    SFormsFastMapDirect map = new SFormsFastMapDirect();
    for (int i = 0; i < paramcount; i++) {
      int version = getNextFreeVersion(varindex, root); // == 1

      FastSparseSet<Integer> set = factory.spawnEmptySet();
      set.add(version);
      map.put(varindex, set);
      ssuversions.createNode(new VarVersionPair(varindex, version));

      if (thisvar) {
        if (i == 0) {
          varindex++;
        } else {
          varindex += md.params[i - 1].stackSize;
        }
      } else {
        varindex += md.params[i].stackSize;
      }
    }

    return map;
  }

  private static Integer getFirstProtectedRange(Statement stat) {

    while (true) {
      Statement parent = stat.getParent();

      if (parent == null) {
        break;
      }

      if (parent.type == Statement.TYPE_CATCHALL || parent.type == Statement.TYPE_TRYCATCH) {
        if (parent.getFirst() == stat) {
          return parent.id;
        }
      } else if (parent.type == Statement.TYPE_SYNCRONIZED) {
        if (((SynchronizedStatement) parent).getBody() == stat) {
          return parent.id;
        }
      }

      stat = parent;
    }

    return null;
  }

  public HashMap<VarVersionPair, HashSet<Integer>> getPhi() {
    return phi;
  }

  public VarVersionsGraph getSsuversions() {
    return ssuversions;
  }

  public SFormsFastMapDirect getLiveVarVersionsMap(VarVersionPair varpaar) {

    VarVersionNode node = ssuversions.nodes.getWithKey(varpaar);
    if (node != null) {
      return node.live;
    }

    return null;
  }

  public HashMap<VarVersionPair, Integer> getMapVersionFirstRange() {
    return mapVersionFirstRange;
  }

  public HashMap<Integer, Integer> getMapFieldVars() {
    return mapFieldVars;
  }
}
