package soot.jimple.spark.sets;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2002 Ondrej Lhotak
 * %%
 * 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 soot.Type;
import soot.jimple.spark.pag.Node;
import soot.jimple.spark.pag.PAG;
import soot.util.BitVector;

/**
 * Implementation of points-to set using a sorted array.
 *
 * @author Ondrej Lhotak
 */
public final class SortedArraySet extends PointsToSetInternal {
  public SortedArraySet(Type type, PAG pag) {
    super(type);
    this.pag = pag;
  }

  /** Returns true if this set contains no run-time objects. */
  public final boolean isEmpty() {
    return size == 0;
  }

  /**
   * Adds contents of other into this set, returns true if this set changed.
   */
  public final boolean addAll(final PointsToSetInternal other, final PointsToSetInternal exclude) {
    boolean ret = false;
    BitVector typeMask = (pag.getTypeManager()).get(type);
    if (other instanceof SortedArraySet) {
      SortedArraySet o = (SortedArraySet) other;
      Node[] mya = nodes;
      Node[] oa = o.nodes;
      int osize = o.size;
      Node[] newa = new Node[size + osize];
      int myi = 0;
      int oi = 0;
      int newi = 0;
      for (;;) {
        if (myi < size) {
          if (oi < osize) {
            int myhc = mya[myi].getNumber();
            int ohc = oa[oi].getNumber();
            if (myhc < ohc) {
              newa[newi++] = mya[myi++];
            } else if (myhc > ohc) {
              if ((type == null || typeMask == null || typeMask.get(ohc))
                  && (exclude == null || !exclude.contains(oa[oi]))) {
                newa[newi++] = oa[oi];
                ret = true;
              }
              oi++;
            } else {
              newa[newi++] = mya[myi++];
              oi++;
            }
          } else { // oi >= osize
            newa[newi++] = mya[myi++];
          }
        } else { // myi >= size
          if (oi < osize) {
            int ohc = oa[oi].getNumber();
            if ((type == null || typeMask == null || typeMask.get(ohc)) && (exclude == null || !exclude.contains(oa[oi]))) {
              newa[newi++] = oa[oi];
              ret = true;
            }
            oi++;
          } else {
            break;
          }
        }
      }
      nodes = newa;
      size = newi;
      return ret;
    }
    return super.addAll(other, exclude);
  }

  /** Calls v's visit method on all nodes in this set. */
  public final boolean forall(P2SetVisitor v) {
    for (int i = 0; i < size; i++) {
      v.visit(nodes[i]);
    }
    return v.getReturnValue();
  }

  /** Adds n to this set, returns true if n was not already in this set. */
  public final boolean add(Node n) {
    if (pag.getTypeManager().castNeverFails(n.getType(), type)) {
      if (contains(n)) {
        return false;
      }
      int left = 0;
      int right = size;
      int mid;
      int hc = n.getNumber();
      while (left < right) {
        mid = (left + right) / 2;
        int midhc = nodes[mid].getNumber();
        if (midhc < hc) {
          left = mid + 1;
        } else if (midhc > hc) {
          right = mid;
        } else {
          break;
        }
      }
      if (nodes == null) {
        nodes = new Node[size + 4];
      } else if (size == nodes.length) {
        Node[] newNodes = new Node[size + 4];
        System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
        nodes = newNodes;
      }
      System.arraycopy(nodes, left, nodes, left + 1, size - left);
      nodes[left] = n;
      size++;
      return true;
    }
    return false;
  }

  /** Returns true iff the set contains n. */
  public final boolean contains(Node n) {
    int left = 0;
    int right = size;
    int hc = n.getNumber();
    while (left < right) {
      int mid = (left + right) / 2;
      int midhc = nodes[mid].getNumber();
      if (midhc < hc) {
        left = mid + 1;
      } else if (midhc > hc) {
        right = mid;
      } else {
        return true;
      }
    }
    return false;
  }

  public final static P2SetFactory getFactory() {
    return new P2SetFactory() {
      public final PointsToSetInternal newSet(Type type, PAG pag) {
        return new SortedArraySet(type, pag);
      }
    };
  }

  /* End of public methods. */
  /* End of package methods. */

  private Node[] nodes = null;
  private int size = 0;
  private PAG pag = null;
}
