/*******************************************************************************
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.ibm.wala.util.intset;

import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.UnimplementedError;

/**
 * Utilities for dealing with {@link IntSet}s
 */
public class IntSetUtil {

  public static final String INT_SET_FACTORY_CONFIG_PROPERTY_NAME = "com.ibm.wala.mutableIntSetFactory";

  private static MutableIntSetFactory<?> defaultIntSetFactory;

  static {
    MutableIntSetFactory<?> defaultFactory = new MutableSharedBitVectorIntSetFactory();
    if (System.getProperty(INT_SET_FACTORY_CONFIG_PROPERTY_NAME) != null) {
      try {
        Class<?> intSetFactoryClass = Class.forName(System.getProperty(INT_SET_FACTORY_CONFIG_PROPERTY_NAME));
        MutableIntSetFactory<?> intSetFactory = (MutableIntSetFactory<?>) intSetFactoryClass.newInstance();
        setDefaultIntSetFactory(intSetFactory);
      } catch (Exception e) {
        System.err.println(("Cannot use int set factory " + System.getProperty(INT_SET_FACTORY_CONFIG_PROPERTY_NAME)));
        setDefaultIntSetFactory(defaultFactory);
      }
    } else {
      setDefaultIntSetFactory(defaultFactory);
    }
    assert defaultIntSetFactory != null;
  }

  public static MutableIntSet make() {
    return defaultIntSetFactory.make();
  }

  public static MutableIntSet make(int[] initial) {
	    return defaultIntSetFactory.make(initial);
	  }

  private final static boolean DEBUG = false;

  // there's no reason to instantiate this class
  private IntSetUtil() {
  }
  
  /**
   * This method constructs an appropriate mutable copy of set.
   * 
   * @return a new {@link MutableIntSet} object with the same value as set
   * @throws UnimplementedError if we haven't supported the set type yet.
   * @throws IllegalArgumentException if set == null
   */
  public static MutableIntSet makeMutableCopy(IntSet set) throws IllegalArgumentException, UnimplementedError {
    if (set == null) {
      throw new IllegalArgumentException("set == null");
    }
    if (set instanceof SparseIntSet) {
      return MutableSparseIntSet.make(set);
    } else if (set instanceof BitVectorIntSet) {
      return new BitVectorIntSet(set);
    } else if (set instanceof BimodalMutableIntSet) {
      return BimodalMutableIntSet.makeCopy(set);
    } else if (set instanceof MutableSharedBitVectorIntSet) {
      return new MutableSharedBitVectorIntSet((MutableSharedBitVectorIntSet) set);
    } else if (set instanceof SemiSparseMutableIntSet) {
      return new SemiSparseMutableIntSet((SemiSparseMutableIntSet) set);
    } else if (set instanceof DebuggingMutableIntSet) {
      MutableIntSet pCopy = makeMutableCopy(((DebuggingMutableIntSet) set).primaryImpl);
      MutableIntSet sCopy = makeMutableCopy(((DebuggingMutableIntSet) set).secondaryImpl);
      return new DebuggingMutableIntSet(pCopy, sCopy);
    } else if (set instanceof EmptyIntSet) {
      return IntSetUtil.make();
    } else {
      Assertions.UNREACHABLE(set.getClass().toString());
      return null;
    }
  }

  /**
   * Compute the asymmetric difference of two sets, a \ b.
   */
  public static IntSet diff(IntSet A, IntSet B) {
    if (A == null) {
      throw new IllegalArgumentException("null A");
    }
    if (B == null) {
      throw new IllegalArgumentException("null B");
    }
    return diff(A, B, IntSetUtil.getDefaultIntSetFactory());
  }

  private static IntSet defaultSlowDiff(IntSet A, IntSet B, MutableIntSetFactory<?> factory) {
    // TODO: this is slow ... optimize please.
    MutableIntSet result = factory.makeCopy(A);
    if (DEBUG) {
      System.err.println(("initial result " + result + " " + result.getClass()));
    }
    for (IntIterator it = B.intIterator(); it.hasNext();) {
      int I = it.next();
      result.remove(I);
      if (DEBUG) {
        System.err.println(("removed " + I + " now is " + result));
      }
    }
    if (DEBUG) {
      System.err.println(("return " + result));
    }
    return result;
  }

  /**
   * Compute the asymmetric difference of two sets, a \ b.
   */
  public static IntSet diff(IntSet A, IntSet B, MutableIntSetFactory<?> factory) {
    if (factory == null) {
      throw new IllegalArgumentException("null factory");
    }
    if (A == null) {
      throw new IllegalArgumentException("null A");
    }
    if (B == null) {
      throw new IllegalArgumentException("null B");
    }
    if (A instanceof SparseIntSet && B instanceof SparseIntSet) {
      return SparseIntSet.diff((SparseIntSet) A, (SparseIntSet) B);
    } else if (A instanceof SemiSparseMutableIntSet && B instanceof SemiSparseMutableIntSet) {
      IntSet d = SemiSparseMutableIntSet.diff((SemiSparseMutableIntSet) A, (SemiSparseMutableIntSet) B);
      return d;
    } else {
      return defaultSlowDiff(A, B, factory);
    }
  }

  /**
   * Subtract two sets, i.e. a = a \ b.
   * 
   * @throws IllegalArgumentException if B == null
   */
  public static MutableIntSet removeAll(MutableIntSet A, IntSet B) throws IllegalArgumentException {
    if (A == null) {
      throw new IllegalArgumentException("A == null");
    }
    if (B == null) {
      throw new IllegalArgumentException("B == null");
    }
    if (A instanceof SemiSparseMutableIntSet && B instanceof SemiSparseMutableIntSet) {
      if (DEBUG) {
        System.err.println("call SemiSparseMutableIntSet.removeAll");
      }
      return ((SemiSparseMutableIntSet) A).removeAll((SemiSparseMutableIntSet) B);
    } else {
      for (IntIterator it = B.intIterator(); it.hasNext();) {
        int I = it.next();
        A.remove(I);
        if (DEBUG) {
          System.err.println(("removed " + I + " now is " + A));
        }
      }
      if (DEBUG) {
        System.err.println(("return " + A));
      }
      return A;
    }
  }

  /**
   * @return index \in [low,high] s.t. data[index] = key, or -1 if not found
   */
  public static int binarySearch(int[] data, int key, int low, int high) throws IllegalArgumentException {
    if (data == null) {
      throw new IllegalArgumentException("null array");
    }
    if (data.length == 0) {
      return -1;
    }
    if (low <= high && (low < 0 || high < 0)) {
      throw new IllegalArgumentException("can't search negative indices " + low + " " + high);
    }
    if (high > data.length - 1) {
      high = data.length - 1;
    }
    if (low <= high) {
      int mid = (low + high) / 2;
      int midValue = data[mid];
      if (midValue == key) {
        return mid;
      } else if (midValue > key) {
        return binarySearch(data, key, low, mid - 1);
      } else {
        return binarySearch(data, key, mid + 1, high);
      }
    } else {
      return -1;
    }
  }

  /**
   * @return Returns the defaultIntSetFactory.
   */
  public static MutableIntSetFactory<?> getDefaultIntSetFactory() {
    return defaultIntSetFactory;
  }

  /**
   * @param defaultIntSetFactory The defaultIntSetFactory to set.
   */
  public static void setDefaultIntSetFactory(MutableIntSetFactory<?> defaultIntSetFactory) {
    if (defaultIntSetFactory == null) {
      throw new IllegalArgumentException("null defaultIntSetFactory");
    }
    IntSetUtil.defaultIntSetFactory = defaultIntSetFactory;
  }

  /**
   * @return a new sparse int set which adds j to s
   * @throws IllegalArgumentException if s == null
   */
  public static IntSet add(IntSet s, int j) throws IllegalArgumentException {
    if (s == null) {
      throw new IllegalArgumentException("s == null");
    }
    if (s instanceof SparseIntSet) {
      SparseIntSet sis = (SparseIntSet) s;
      return SparseIntSet.add(sis, j);
    } else {
      // really slow. optimize as needed.
      MutableSparseIntSet result = MutableSparseIntSet.make(s);
      result.add(j);
      return result;
    }
  }
  
  public static int[] toArray(IntSet s) {
    int i = 0;
    int[] result = new int[ s.size() ];
    IntIterator x = s.intIterator();
    while (x.hasNext()) {
      result[i++] = x.next();
    }
    assert ! x.hasNext();
    return result;
  }
}
