// FILE. . . . . /home/hak/hlt/src/hlt/osfv2/util/BitCode.java
// EDIT BY . . . Hassan Ait-Kaci
// ON MACHINE. . Hak-Laptop
// STARTED ON. . Mon Sep 02 15:37:36 2013


/**
 * This class extends the class <tt><a
 * href="http://docs.oracle.com/javase/6/docs/api/java/util/BitSet.html">java.util.BitSet</a></tt>
 * with a few methods not provided by that class; mainly, static methods
 * for Boolean operations that reuse one of their arguments whenever
 * possible.
 *
 * @version	Last modified on Tue Jan 21 08:08:30 2014 by hak
 * @author      <a href="mailto:hak@acm.org">Hassan A&iuml;t-Kaci</a>
 * @copyright	&copy; <a href="http://www.hassan-ait-kaci.net/">Hassan A&iuml;t-Kaci</a>
 */

package hlt.osf.util;

import java.util.BitSet;
import java.util.HashSet;

import hlt.language.util.IntIterator;

import hlt.osf.base.Sort;
import hlt.osf.exec.Context;
import hlt.osf.exec.Taxonomy;

public class BitCode extends BitSet
{
  /**
   * Constructs a new bit code; (all bits set to <tt>false</tt>).
   */
  public BitCode ()
  {
    super();
  }
    
  /**
   * Constructs a new bit code whose initial size is large enough to
   * explicitly represent bits with indices in the range <tt>0</tt> through
   * <tt>nbits-1</tt>; (all bits set to false).
   *
   * @param     nbits the initial capacity of the bit code.
   */
  public BitCode (int nbits)
  {
    super(nbits);
  }

  /* ************************************************************************ */

  /**
   * This is used for the "set" character when displaying this as a string.
   * It is '<tt>1</tt>' by default.
   */
  private static char _ON = '1';

  /**
   * This is used for the "not set" character when displaying this as a string.
   * It is '<tt>0</tt>' by default.
   */
  private static char _OFF = '0';

  /**
   * This sets the "set" character when displaying this as a string to the specified character.
   */
  public static void setOnChar (char c)
  {
    _ON = c;
  }

  /**
   * This sets the "not set" character when displaying this as a string to the specified character.
   */
  public static void setOffChar (char c)
  {
    _OFF = c;
  }

  /* ************************************************************************ */

  /**
   * This is used as a temporary reusable BitCode for avoiding
   * unecessary multiple cloning.
   */
  private static BitCode _TEMP = new BitCode();

  /* ************************************************************************ */

  /**
   * Returns true iff the set of set of bits in this code is contained
   * in that of the specified code. That is, iff <tt>(this and code) ==
   * this</tt>.
   */
  public boolean isContainedIn (BitCode code)
  {
    _TEMP.clear();	// reset all bits to 0
    _TEMP.or(this);	// copy this into it
    _TEMP.and(code);	// intersect it with code
    return this.equals(_TEMP);    
  }

  /**
   * Returns true iff this code is strictly contained in the specified code.
   */
  public boolean isStrictlyContainedIn (BitCode code)
  {
    return this.equals(code) ? false : this.isContainedIn(code);
  }

  /**
   * Returns true iff this code is unrelated to the specified code.
   */
  public boolean isUnrelatedTo (BitCode code)
  {
    return !this.equals(code) && !this.isContainedIn(code) && !code.isContainedIn(this) ;
  }

  /* ************************************************************************ */

  /**
   * This is true iff this bitset may not be modified by the 3 boolean
   * static operations 'not', 'and', and 'or' defined below.
   */
  private boolean _isLocked = false;

  /**
   * Returns true iff this bit code is locked - which means that it will
   * not be modified by the 3 boolean static dyadic operations 'not',
   * 'and', and 'or' defined below.
   */
  public boolean isLocked ()
  {
    return _isLocked;
  }

  /**
   * Locks this bit code - which means that it will not be modified by
   * the 3 boolean static dyadic operations 'not', 'and', and 'or'
   * defined below.
   */
  public BitCode lock ()
  {
    _isLocked = true;
    return this;
  }
  
  /**
   * Unlocks this bit code - which means that it may be modified by the
   * 3 boolean static dyadic operations 'not', 'and', and 'or' defined
   * below.
   */
  public BitCode unlock ()
  {
    _isLocked = false;
    return this;
  }
  
  /* ************************************************************************ */

  /**
   * Adds the specified index to this BitCode (i.e., it sets it to true),
   * and returns this BitCode.
   */
  public BitCode add (int index) throws LockedBitCodeException
  {
    if (_isLocked)
      throw new LockedBitCodeException("Cannot add to a locked BitCode");

    set(index);
    return this;
  }
  
  /* ************************************************************************ */

  /**
   * Removes the specified index from this BitCode (i.e., it sets it to false),
   * and returns this BitCode.
   */
  public BitCode remove (int index) throws LockedBitCodeException
  {
    if (_isLocked)
      throw new LockedBitCodeException("Cannot remove from a locked BitCode");

    set(index,false);
    return this;
  }
  
  /* ************************************************************************ */

  /**
   * Removes all the true bits from this BitCode (i.e., all set to false),
   * and returns this BitCode.
   */
  public BitCode erase () throws LockedBitCodeException
  {
    if (_isLocked)
      throw new LockedBitCodeException("Cannot clear a locked BitCode");

    super.clear();
    return this;
  }
  
  /* ************************************************************************ */

  /**
   * Returns a bit code that is the logical <tt>not</tt> of its bit code
   * argument up to the sort code size. The bit code argument is left
   * unchanged if locked. If the bit code argument is not locked, the
   * returned bit code is the argument modified in place. Otherwise, a
   * new bit code is returned with its bits set to their appropriate
   * values.
   */
  public static BitCode not (BitCode c)
  {
    if (!c.isLocked())
      {
	c.flip(0,Sort.codeSize());
	return c;
      }
    
    BitCode newcode = c.copy();
    newcode.flip(0,Sort.codeSize());
    return newcode;
  }

  /* ************************************************************************ */

  /**
   * Returns a bit code that is the logical <tt>and</tt> of its
   * arguments, which are left unchanged if locked. If the first
   * argument is not locked, the returned bit code is the first argument
   * modified in place.  Otherwise, if the second argument is not
   * locked, the returned bit code is the second argument modified in
   * place. Otherwise, a new bit code is returned with its bits set to
   * their appropriate values.
   */
  public static BitCode and (BitCode c1, BitCode c2)
  {
    if (!c1.isLocked())
      {
	c1.and(c2);
	return c1;
      }
    
    if (!c2.isLocked())
      {
	c2.and(c1);
	return c2;
      }
    
    BitCode newcode = c1.copy();
    newcode.and(c2);
    return newcode;
  }

  /* ************************************************************************ */

  /**
   * Returns the position on the least true bit (or -1 if there is none).  
   */
  public final int first ()
  {
    return nextSetBit(0);
  }

  /**
   * Returns the position on the highest true bit (or -1 if there is none).  
   */
  public final int last ()
  {
    return length()-1;
  }

  /* ************************************************************************ */

  /**
   * Returns a bit code that is the logical <tt>or</tt> of its
   * arguments, which are left unchanged if locked. If the first
   * argument is not locked, the returned bit code is the first argument
   * modified in place.  Otherwise, if the second argument is not
   * locked, the returned bit code is the second argument modified in
   * place. Otherwise, a new bit code is returned with its bits set to
   * their appropriate values.
   */
  public static BitCode or (BitCode c1, BitCode c2)
  {
    if (!c1.isLocked())
      {
	c1.or(c2);
	return c1;
      }
    
    if (!c2.isLocked())
      {
	c2.or(c1);
	return c2;
      }
    
    BitCode newcode = c1.copy();
    newcode.or(c2);
    return newcode;
  }

  /* ************************************************************************ */
  
  /**
   * Returns a new unlocked BitCode that is an equal copy of this one.
   */
  public BitCode copy ()
  {
    if (Context.isTracing())
      Context.tallyCodeCopy();
    return ((BitCode)super.clone()).unlock();
  }
  
  /* ************************************************************************ */

  /**
   * Returns this bit code of length equal to the sort code size as a
   * string of ON and OFF characters (which default to <tt>0</tt> and
   * <tt>1</tt>). These can be set to different characters using the
   * static methods <tt>setOnChar</tt> and <tt>setOffChar</tt>. All bits
   * at index higher or equal to that size are always false.
   */
  public String toBitString (int size)
  {
    String s = "";
    for (int i=size; i-->0;)
      s += get(i) ? _ON : _OFF;
    return s;
  }

  public String toBitString ()
  {
    return toBitString(Sort.codeSize());
  }

  public String toTrueBitString ()
  {
    return toBitString(length());
  }

  /**
   * Returns this bit code as a bit string followed by a marker whether
   * it is locked or not.
   */
  public String toString ()
  {
    return toBitString()+" "+(_isLocked ? "[]" : "][");
  }

  /* ************************************************************************ */

      /**
     * This returns a <tt>String</tt> form of this <tt>BitCode</tt> as a
     * space-separated sequence of pairs of integers, each number
     * corresponding to the next true index followed by the next false
     * index, going from the lowest to the highest. For example, the bitcode:
     * <pre>
     * 000011111001111000000110000
     * </pre>
     * corresponds to the string:
     * <pre>
     * "4 6 12 16 18 23"
     * </pre>
     * If there are no true bits (<i>i.e.</i>, all the bits are
     * <tt>false</tt>), this returns the empty string.
     */
  public String toSaveFormatString ()
  {
    StringBuffer result = new StringBuffer();

    int trueIndex = nextSetBit(0);

    while (trueIndex != -1)
      {
	int falseIndex = nextClearBit(trueIndex);
	result = result.append(trueIndex+" "+falseIndex+" ");
	trueIndex = nextSetBit(falseIndex);
      }

    return result.toString();
  }

  /* ************************************************************************ */

  /**
   * Returns a HashSet containing the set of sorts denoted by this BitCode.
   */
  public HashSet toHashSet (Taxonomy taxonomy)
  {
    if (isEmpty())
      return null;
    
    HashSet hset = new HashSet(3*length());	// allocate three times the number of actual bits

    for (IntIterator it=iterator(); it.hasNext();)
      hset.add(taxonomy.getSort(it.next()));

    return hset;
  }

  /* ************************************************************************ */

  /**
   * Returns an iterator over the <tt>1</tt> bits of this bitcode.
   */
  public IntIterator iterator ()
    {
      return new BitCodeIterator(this);
    }

  private class BitCodeIterator implements IntIterator
    {
      private BitCode _code;
      private boolean _getNext = true;
      private int _next = -1;

      BitCodeIterator (BitCode code)
        {
          _code = code;
        }

      public final boolean hasNext ()
        {
	  if (_getNext)
	    {
	      _next = _code.nextSetBit(_next+1);
	      _getNext = false;
	    }

          return _next >= 0;
        }

      public final int next ()
        {
	  _getNext = true;
          return _next;
        }
      
    }

  /* ************************************************************************ */

  /**
   * Returns an iterator over the <tt>0</tt> bits of this bitcode up to
   * its length (exclusive).
   */
  public IntIterator zeroIterator ()
    {
      return new BitCodeZeroIterator(this,length()-1);
    }

  /**
   * Returns an iterator over the <tt>0</tt> bits of this bitcode up to,
   * but excluding, position <tt>max</tt>.
   */
  public IntIterator zeroIterator (int max)
    {
      return new BitCodeZeroIterator(this,max);
    }

  private class BitCodeZeroIterator implements IntIterator
    {
      private BitCode _code;
      private int _stop;
      private boolean _getNext = true;
      private int _next = -1;

      BitCodeZeroIterator (BitCode code, int max)
        {
          _code = code;
	  _stop = max-1;
        }

      public final boolean hasNext ()
        {
	  if (_next >= _stop)
	    return false;
	  
  	  if (_getNext)
  	    {
  	      _next = _code.nextClearBit(_next+1);
  	      _getNext = false;
  	    }

          return _next >= 0;
        }

      public final int next ()
        {
  	  _getNext = true;
          return _next;
        }
      
    }

  /* ************************************************************************ */
}