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

/**
 * @version     Last modified on Fri Aug 23 12:39:36 2019 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.util.Decoded;
import hlt.osf.exec.Context;
import hlt.osf.exec.Taxonomy;
import hlt.osf.exec.LockedCodeArrayException;

/**
 * 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.
 */
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);
  }

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

  /**
   * The <tt>Decoded</tt> object corresponding to this <tt>BitCode</tt>.
   */
  private Decoded _decoded;

  public BitCode setDecoded (Decoded decoded)
  {
    _decoded = decoded;
    return this;
  }

  public Decoded decoded ()
  {
    return _decoded;
  }

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

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

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

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

  /**
   * This sets the "off" 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 sets of bits in this code is contained
   * in or equal to 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 related to the specified code.
   */
  public boolean isRelatedTo (BitCode code)
  {
    return this.equals(code) || this.isContainedIn(code) || code.isContainedIn(this) ;
  }

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

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

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

  /**
   * 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 of the least true bit (or -1 if there is none).  
   */
  public final int first ()
  {
    return nextSetBit(0);
  }

  /**
   * Returns the position of 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 <tt>HashSet</tt> containing the set of sorts denoted by
   * this <tt>BitCode</tt> in the specified taxonomy, or <tt>null</tt>
   * if this is an empty <tt>BitSet</tt>.
   */
  public HashSet toHashSet (Taxonomy taxonomy) throws LockedCodeArrayException
  {
    if (!taxonomy.isLocked())
      throw new LockedCodeArrayException("Attempt to perform an operation requiring prior sort encoding");

    if (isEmpty())
      return null;

    HashSet hset = new HashSet(size());

    // iterate through this code's 1s adding the sorts their denote in
    // the taxonomy to hset one at a time:
    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
   * the maximal code size (exclusive).
   */
  public IntIterator zeroIterator ()
    {
      return new BitCodeZeroIterator(this,Sort.codeSize());
    }

  /**
   * 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;
        }
      
    }

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