// $Id: Attribute.java,v 1.9 2004/12/07 01:29:32 caron Exp $
/*
 * Copyright 1997-2004 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library 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 library 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 Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ucar.nc2;

import ucar.ma2.*;
import ucar.unidata.util.Parameter;

/**
 * An Attribute has a name and a value, used for associating arbitrary metadata with a Variable or a Group.
 * The value can be a one dimensional array of Strings or numeric values.
 *
 * @author caron
 * @version $Revision: 1.9 $ $Date: 2004/12/07 01:29:32 $
 */

public class Attribute {
  protected String name;
  protected DataType dataType;
  protected int nelems;
  protected Array values;
  protected Index ima;

  /**
   * Get the name of this Attribute.
   *  Attribute names are unique within a NetcdfFile's global set, and within a Variable's set.
   */
  public String getName() { return name; }

  /**
   * Get the data type of the Attribute value.
   */
  public DataType getDataType() { return dataType; }

  /** True if value is a String or String[]. */
  public boolean isString() { return dataType == DataType.STRING; }

  /** True if value is an array (getLength() > 1) */
  public boolean isArray() { return (getLength() > 1); }

  /**
   * Get the length of the array of values; = 1 if scaler.
   */
  public int getLength() { return nelems; }

  /**
   * Get the value as an Array.
   * @return Array of values.
   */
  public Array getValues() { return values; }

  /**
   * Retrieve String value; only call if isString() is true.
   * @return String if this is a String valued attribute, else null.
   * @see Attribute#isString
   */
  public String getStringValue(){ return getStringValue(0); }

   /**
   * Retrieve String value; only call if isString() is true.
   * @return String if this is a String valued attribute, else null.
   * @see Attribute#isString
   */
  public String getStringValue(int index) {
    if (!isString() || (index < 0) || (index >= nelems))
      return null;
    return (String) values.getObject( ima.set0(index));
  }

  /**
   * Retrieve numeric value.
   * Equivalent to <code>getNumericValue(0)</code>
   * @return the first element of the value array, or null if its a String.
   */
  public Number getNumericValue(){ return getNumericValue(0); }


  /// these deal with array-valued attributes

  /**
   * Retrieve a numeric value by index. If its a String, it will try to parse it as a double.
   * @param index the index into the value array.
   * @return Number <code>value[index]</code>, or null if its a non-parsable String or
   *  the index is out of range.
   */
  public Number getNumericValue(int index) {
    if (isString() || (index < 0) || (index >= nelems))
      return null;

    if (dataType == DataType.STRING) {
      try {  return new Double( getStringValue(index)); }
      catch (NumberFormatException e) { return null; }
    }

    if (dataType == DataType.BYTE)
      return new Byte( values.getByte( ima.set0(index)));
    else if (dataType == DataType.SHORT)
      return new Short( values.getShort( ima.set0(index)));
    else if (dataType == DataType.INT)
      return new Integer( values.getInt( ima.set0(index)));
    else if (dataType == DataType.FLOAT)
      return new Float( values.getFloat( ima.set0(index)));
    else if (dataType == DataType.DOUBLE)
      return new Double( values.getDouble( ima.set0(index)));

    return null;
  }

  /**
   * Instances which have same content are equal.
   */
  public boolean equals(Object oo) {
    if (this == oo) return true;
    if ( !(oo instanceof Attribute)) return false;
    return hashCode() == oo.hashCode();
  }

  /** Override Object.hashCode() to implement equals. */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      result = 37*result + getName().hashCode();
      result = 37*result + getDataType().hashCode();
      for (int i=0; i<getLength(); i++) {
        int h = isString() ? getStringValue(i).hashCode() : getNumericValue(i).hashCode();
        result = 37*result + h;
      } 
      hashCode = result;
    }
    return hashCode;
  }
  private volatile int hashCode = 0;

  /** String representation */
  public String toString() {
    StringBuffer buff = new StringBuffer();
    buff.append(getName());
    if (isString()) {
      buff.append(" = ");
      for (int i=0; i<getLength(); i++) {
        if (i != 0) buff.append(", ");
        buff.append("\""+getStringValue(i)+"\"");
      }
    } else {
      buff.append(" = ");
      for (int i=0; i<getLength(); i++) {
        if (i != 0) buff.append(", ");
        buff.append(getNumericValue(i));
      }
    }
    return buff.toString();
  }


  ///////////////////////////////////////////////////////////////////////////////
    // for subclasses
  protected Attribute() {}

  /** Constructor. Must also set value */
  public Attribute( String name) {
    this.name = name;
  }

  /** Copy constructor */
  public Attribute( String name, Attribute from) {
    this.name = name;
    this.dataType = from.dataType;
    this.nelems = from.nelems;
    this.values = from.values;
    this.ima = from.ima;
  }

  /**
   * Create a String-valued Attribute.
   */
  public Attribute( String name, String val) {
    this.name = name;
    setStringValue( val);
  }

  /**
   * Create a scalar numeric-valued Attribute.
   */
  public Attribute( String name, Number val) {
    this.name = name;
    int[] shape = new int[1];
    shape[0] = 1;
    DataType dt = DataType.getType(val.getClass());
    Array vala = Array.factory( dt.getPrimitiveClassType(), shape);
    Index ima = vala.getIndex();
    vala.setDouble( ima.set0(0), val.doubleValue());
    setValues( vala);
  }

  /**
   * Construct attribute with list of values.
   * @param name name of attribute
   * @param values array of values.
   */
  public Attribute( String name, Array values) {
    this.name = name;
    setValues( values);
  }

  public void setName( String name) {
    this.name = name;
  }

  /**
   * Create an Attribute with a String or a 1-dimensional array of primitives.
   * @deprecated use setValues( Array val)
   */
  protected void setValueOld( Object val) {
    if (val instanceof String)
      setStringValue( (String) val);
    else {
      int n = java.lang.reflect.Array.getLength( val);
      int[] shape = new int[] {n};
      Class elemType = val.getClass().getComponentType();
      Array vala = Array.factory( elemType, shape, val);
      setValues( vala);
    }
  }

  /** set the value as a String, trimming trailing zeroes */
  public void setStringValue( String val) {
       // get rid of trailing zeroes
    int len = val.length();
    while ((len > 0) && (val.charAt( len-1) == 0))
      len--;

    if (len != val.length())
      val = val.substring(0, len);
    values = Array.factory(String.class, new int[] {1});
    values.setObject( values.getIndex(), val);
    setValues( values);
  }

  /** set the values from an Array */
  public void setValues( Array arr) {
    if (DataType.getType( arr.getElementType()) == null)
      throw new IllegalArgumentException("Cant set Attribute with type "+arr.getElementType());

    if (arr.getElementType() == char.class) { // turn CHAR into STRING
      ArrayChar carr = (ArrayChar) arr;
      arr = carr.make1DStringArray();
    }
    if (arr.getRank() != 1)
      arr = arr.reshape(new int[] { (int) arr.getSize() } ); // make sure 1D

    this.values = arr;
    this.nelems = (int) arr.getSize();
    this.dataType = DataType.getType(arr.getElementType());
    ima = values.getIndex();
  }

  // ucar.unidata.util.Parameter : need to do this do ucar.unidata.geoloc stuff
  // doesnt depend on ucar.nc2 library


  public Attribute( ucar.unidata.util.Parameter param) {
    this.name = param.getName();

    if (param.isString()) {
      setStringValue( param.getStringValue());
      
    } else {
      double[] values = param.getNumericValues();
      int n = values.length;
      Array vala = Array.factory( DataType.DOUBLE.getPrimitiveClassType(), new int[] {n}, values);
      setValues( vala);
    }
  }

}

/* Change History:
   $Log: Attribute.java,v $
   Revision 1.9  2004/12/07 01:29:32  caron
   redo convention parsing, use _Coordinate encoding.

   Revision 1.8  2004/10/29 00:14:12  caron
   no message

   Revision 1.7  2004/10/06 19:03:44  caron
   clean up javadoc
   change useV3 -> useRecordsAsStructure
   remove id, title, from NetcdfFile constructors
   add "in memory" NetcdfFile

   Revision 1.6  2004/09/09 22:47:39  caron
   station updates

   Revision 1.5  2004/08/19 20:48:55  edavis
   Make public a number of methods for building an Attribute.

   Revision 1.4  2004/08/16 20:53:44  caron
   2.2 alpha (2)

   Revision 1.3  2004/07/12 23:40:16  caron
   2.2 alpha 1.0 checkin

   Revision 1.2  2004/07/06 19:28:08  caron
   pre-alpha checkin

   Revision 1.1.1.1  2003/12/04 21:05:27  caron
   checkin 2.2

 */
