/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 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 Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * StringLocator.java
 * Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.core;

import java.io.Serializable;
import java.util.Vector;
import java.util.BitSet;

/**
 * This class locates and records the indices of a certain type of attributes, 
 * recursively in case of Relational attributes.
 * 
 * @author fracpete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 8034 $
 * @see Attribute#RELATIONAL
 */
public class AttributeLocator 
  implements Serializable, Comparable<AttributeLocator>, RevisionHandler {
  
  /** for serialization */
  private static final long serialVersionUID = -2932848827681070345L;

  /** the attribute indices that may be inspected */
  protected int[] m_AllowedIndices = null;
  
  /** contains the attribute locations, either true or false Boolean objects */
  protected Vector<Boolean> m_Attributes = null;
  
  /** contains the attribute locations, either true or false (efficient replacement) */
  protected BitSet m_AttributesEfficient = null;
  
  /** contains the locator locations, either null or a AttributeLocator reference */
  protected Vector<AttributeLocator> m_Locators = null;

  /** the type of the attribute */
  protected int m_Type = -1;
  
  /** the referenced data */
  protected Instances m_Data = null;

  /** the indices */
  protected int[] m_Indices = null;

  /** the indices of locator objects */
  protected int[] m_LocatorIndices = null;
  
  /**
   * Initializes the AttributeLocator with the given data for the specified
   * type of attribute. Checks all attributes.
   * 
   * @param data	the data to work on
   * @param type	the type of attribute to locate
   */
  public AttributeLocator(Instances data, int type) {
    this(data, type, 0, data.numAttributes() - 1);
  }
  
  /**
   * Initializes the AttributeLocator with the given data for the specified
   * type of attribute. Checks only the given range.
   * 
   * @param data	the data to work on
   * @param type	the type of attribute to locate
   * @param fromIndex	the first index to inspect (including)
   * @param toIndex	the last index to check (including)
   */
  public AttributeLocator(Instances data, int type, int fromIndex, int toIndex) {
    super();

    int[] indices = new int[toIndex - fromIndex + 1];
    for (int i = 0; i < indices.length; i++)
      indices[i] = fromIndex + i;
    
    initialize(data, type, indices);
  }
  
  /**
   * initializes the AttributeLocator with the given data for the specified
   * type of attribute. Checks only the given attribute indices.
   * 
   * @param data	the data to work on
   * @param type	the type of attribute to locate
   * @param indices	the attribute indices to check
   */
  public AttributeLocator(Instances data, int type, int[] indices) {
    super();

    initialize(data, type, indices);
  }
  
  /**
   * initializes the AttributeLocator
   * 
   * @param data	the data to base the search for attributes on
   * @param type	the type of attribute to look for
   * @param indices	the indices that are allowed to check
   */
  protected void initialize(Instances data, int type, int[] indices) {
    m_Data = new Instances(data, 0);
    m_Type = type;
    
    m_AllowedIndices = new int[indices.length];
    System.arraycopy(indices, 0, m_AllowedIndices, 0, indices.length);
    
    locate();

    m_Indices        = find(true);
    m_LocatorIndices = find(false);
  }
  
  /**
   * returns the type of attribute that is located
   * 
   * @return		the type of attribute
   */
  public int getType() {
    return m_Type;
  }
  
  /**
   * returns the indices that are allowed to check for the attribute type
   * 
   * @return 		the indices that are checked for the attribute type
   */
  public int[] getAllowedIndices() {
    return m_AllowedIndices;
  }
  
  /**
   * sets up the structure
   */
  protected void locate() {
    int         i;
    
    m_Attributes = null;
    m_AttributesEfficient = new BitSet(m_AllowedIndices.length);
    m_Locators   = new Vector<AttributeLocator>();
    
    for (i = 0; i < m_AllowedIndices.length; i++) {
      if (m_Data.attribute(m_AllowedIndices[i]).type() == Attribute.RELATIONAL)
	m_Locators.add(new AttributeLocator(m_Data.attribute(m_AllowedIndices[i]).relation(), getType()));
      else
	m_Locators.add(null);
      
      m_AttributesEfficient.set(i, m_Data.attribute(m_AllowedIndices[i]).type() == getType());
    }
  }
  
  /**
   * returns the underlying data
   * 
   * @return      the underlying Instances object
   */
  public Instances getData() {
    return m_Data;
  }
  
  /**
   * returns the indices of the searched-for attributes (if TRUE) or the indices
   * of AttributeLocator objects (if FALSE)
   * 
   * @param findAtts      if true the indices of attributes are located,
   *                      otherwise the ones of AttributeLocator objects
   * @return              the indices of the attributes or the AttributeLocator objects
   */
  protected int[] find(boolean findAtts) {
    int		i;
    int[]	result;
    Vector<Integer>	indices;

    if (m_AttributesEfficient == null) 
      moveFromBooleanVectorToBitSet();

    // determine locations
    indices = new Vector<Integer>();
    if (findAtts) {
      for (i = 0; i < m_AttributesEfficient.size(); i++) {
	if (((Boolean) m_AttributesEfficient.get(i)).booleanValue())
	  indices.add(new Integer(i));
      }
    }
    else {
      for (i = 0; i < m_Locators.size(); i++) {
	if (m_Locators.get(i) != null)
	  indices.add(new Integer(i));
      }
    }
    
    // fill array
    result = new int[indices.size()];
    for (i = 0; i < indices.size(); i++)
      result[i] = ((Integer) indices.get(i)).intValue();
    
    return result;
  }

  /**
   * returns actual index in the Instances object.
   * 
   * @param index	the index in the m_AllowedIndices array
   * @return		the actual index in the instances object
   */
  public int getActualIndex(int index) {
    return m_AllowedIndices[index];
  }
  
  /**
   * Returns the indices of the attributes. These indices are referring
   * to the m_AllowedIndices array, not the actual indices in the Instances
   * object.
   * 
   * @return	the indices of the attributes
   * @see	#getActualIndex(int)
   */
  public int[] getAttributeIndices() {
    return m_Indices;
  }
  
  /**
   * Returns the indices of the AttributeLocator objects.  These indices are 
   * referring to the m_AllowedIndices array, not the actual indices in the 
   * Instances object.
   * 
   * @return	the indices of the AttributeLocator objects
   * @see	#getActualIndex(int)
   */
  public int[] getLocatorIndices() {
    return m_LocatorIndices;
  }
  
  /**
   * Returns the AttributeLocator at the given index. This index refers to
   * the index of the m_AllowedIndices array, not the actual Instances object.
   * 
   * @param index   the index of the locator to retrieve
   * @return        the AttributeLocator at the given index
   */
  public AttributeLocator getLocator(int index) {
    return (AttributeLocator) m_Locators.get(index);
  }
  
  /**
   * Compares this object with the specified object for order. Returns a 
   * negative integer, zero, or a positive integer as this object is less 
   * than, equal to, or greater than the specified object. Only type and
   * indices are checked.
   * 
   * @param o		the object to compare with
   * @return		-1 if less than, 0 if equal, +1 if greater than the 
   * 			given object
   */
  public int compareTo(AttributeLocator o) {
    int		result;
    int		i;
    
    result = 0;
    
    // 1. check type
    if (this.getType() < o.getType()) {
      result = -1;
    }
    else if (this.getType() > o.getType()) {
      result = 1;
    }
    else {
      // 2. check indices
      if (this.getAllowedIndices().length < o.getAllowedIndices().length) {
	result = -1;
      }
      else if (this.getAllowedIndices().length > o.getAllowedIndices().length) {
	result = 1;
      }
      else {
	for (i = 0; i < this.getAllowedIndices().length; i++) {
	  if (this.getAllowedIndices()[i] < o.getAllowedIndices()[i]) {
	    result = -1;
	    break;
	  }
	  else if (this.getAllowedIndices()[i] > o.getAllowedIndices()[i]) {
	    result = 1;
	    break;
	  }
	  else {
	    result = 0;
	  }
	}
      }
    }
    
    return result;
  }
  
  /**
   * Indicates whether some other object is "equal to" this one. Only type
   * and indices are checked.
   * 
   * @param o		the AttributeLocator to check for equality
   * @return		true if the AttributeLocators have the same type and 
   * 			indices
   */
  public boolean equals(Object o) {
    return (compareTo((AttributeLocator) o) == 0);
  }
  
  /**
   * returns a string representation of this object
   * 
   * @return 		a string representation
   */
  public String toString() {

    if (m_AttributesEfficient == null) 
      moveFromBooleanVectorToBitSet();

    return m_AttributesEfficient.toString();
  }
  
  /**
   * Moves data from Vector<Boolean> to Bitset. Creates bitset first. Sets vector to null.
   */
  private void moveFromBooleanVectorToBitSet() {

    m_AttributesEfficient = new BitSet(m_Attributes.size());
    
    for (int i = 0; i < m_Attributes.size(); i++) {
      if (((Boolean) m_Attributes.get(i)).booleanValue())
        m_AttributesEfficient.set(i, true);
    }
    
    m_Attributes = null;
  }

  /**
   * Returns the revision string.
   * 
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 8034 $");
  }
}
