package soot.jimple.spark.ondemand;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2007 Manu Sridharan
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.util.HashSet;
import java.util.Set;

import soot.PointsToSet;
import soot.Type;
import soot.jimple.ClassConstant;
import soot.jimple.spark.ondemand.genericutil.ArraySet;
import soot.jimple.spark.ondemand.genericutil.ImmutableStack;
import soot.jimple.spark.pag.AllocNode;
import soot.jimple.spark.pag.ClassConstantNode;
import soot.jimple.spark.pag.Node;
import soot.jimple.spark.pag.StringConstantNode;
import soot.jimple.spark.sets.EqualsSupportingPointsToSet;
import soot.jimple.spark.sets.P2SetVisitor;
import soot.jimple.spark.sets.PointsToSetInternal;

public final class AllocAndContextSet extends ArraySet<AllocAndContext> implements EqualsSupportingPointsToSet {

  public boolean hasNonEmptyIntersection(PointsToSet other) {
    if (other instanceof AllocAndContextSet) {
      return nonEmptyHelper((AllocAndContextSet) other);
    } else if (other instanceof WrappedPointsToSet) {
      return hasNonEmptyIntersection(((WrappedPointsToSet) other).getWrapped());
    } else if (other instanceof PointsToSetInternal) {
      return ((PointsToSetInternal) other).forall(new P2SetVisitor() {

        @Override
        public void visit(Node n) {
          if (!returnValue) {
            for (AllocAndContext allocAndContext : AllocAndContextSet.this) {
              if (n.equals(allocAndContext.alloc)) {
                returnValue = true;
                break;
              }
            }
          }
        }

      });
    }
    throw new UnsupportedOperationException("can't check intersection with set of type " + other.getClass());
  }

  private boolean nonEmptyHelper(AllocAndContextSet other) {
    for (AllocAndContext otherAllocAndContext : other) {
      for (AllocAndContext myAllocAndContext : this) {
        if (otherAllocAndContext.alloc.equals(myAllocAndContext.alloc)) {
          ImmutableStack<Integer> myContext = myAllocAndContext.context;
          ImmutableStack<Integer> otherContext = otherAllocAndContext.context;
          if (myContext.topMatches(otherContext) || otherContext.topMatches(myContext)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  public Set<ClassConstant> possibleClassConstants() {
    Set<ClassConstant> res = new HashSet<ClassConstant>();
    for (AllocAndContext allocAndContext : this) {
      AllocNode n = allocAndContext.alloc;
      if (n instanceof ClassConstantNode) {
        res.add(((ClassConstantNode) n).getClassConstant());
      } else {
        return null;
      }
    }
    return res;
  }

  public Set<String> possibleStringConstants() {
    Set<String> res = new HashSet<String>();
    for (AllocAndContext allocAndContext : this) {
      AllocNode n = allocAndContext.alloc;
      if (n instanceof StringConstantNode) {
        res.add(((StringConstantNode) n).getString());
      } else {
        return null;
      }
    }
    return res;
  }

  public Set<Type> possibleTypes() {
    Set res = new HashSet<Type>();
    for (AllocAndContext allocAndContext : this) {
      res.add(allocAndContext.alloc.getType());
    }
    return res;
  }

  /**
   * Computes a hash code based on the contents of the points-to set. Note that hashCode() is not overwritten on purpose.
   * This is because Spark relies on comparison by object identity.
   */
  public int pointsToSetHashCode() {
    final int PRIME = 31;
    int result = 1;
    for (AllocAndContext elem : this) {
      result = PRIME * result + elem.hashCode();
    }
    return result;
  }

  /**
   * Returns <code>true</code> if and only if other holds the same alloc nodes as this. Note that equals() is not overwritten
   * on purpose. This is because Spark relies on comparison by object identity.
   */
  public boolean pointsToSetEquals(Object other) {
    if (this == other) {
      return true;
    }
    if (!(other instanceof AllocAndContextSet)) {
      return false;
    }
    AllocAndContextSet otherPts = (AllocAndContextSet) other;

    // both sets are equal if they are supersets of each other
    return superSetOf(otherPts, this) && superSetOf(this, otherPts);
  }

  /**
   * Returns <code>true</code> if <code>onePts</code> is a (non-strict) superset of <code>otherPts</code>.
   */
  private boolean superSetOf(AllocAndContextSet onePts, final AllocAndContextSet otherPts) {
    return onePts.containsAll(otherPts);
  }
}
