/*
 * $Id: EnumType.java,v 1.28 2012/03/03 10:41:19 agoubard Exp $
 *
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.common.types;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.xins.common.MandatoryArgumentChecker;

/**
 * Abstract base class for enumeration types. An enumeration type only accepts
 * a defined set of values.
 *
 * @version $Revision: 1.28 $ $Date: 2012/03/03 10:41:19 $
 * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
 *
 * @since XINS 1.0.0
 *
 * @see EnumItem
 */
public abstract class EnumType extends Type {

   /**
    * Map that links symbolic names to enumeration values.
    */
   private final Map<String,String> _namesToValues;

   /**
    * Map that links enumeration values to their symbolic names.
    */
   private final Map<String,String> _valuesToNames;

   /**
    * Map that links symbolic names to enumeration item objects.
    */
   protected final Map<String,EnumItem> _namesToItems;

   /**
    * Map that links enumeration values to enumeration item objects.
    */
   protected final Map<String,EnumItem> _valuesToItems;

   /**
    * List of the <code>EnumItem</code> instances.
    */
   private final List<EnumItem> _items;

   /**
    * The list of accepted values.
    */
   private final String[] _values;

   /**
    * Creates a new <code>EnumType</code> instance. The name of the type needs
    * to be specified. The value class (see {@link Type#getValueClass()}) is
    * set to {@link String String.class}.
    *
    * <p />The items this type accepts should be passed. If
    * <code>items == null</code>, then this type will contain no items. This
    * is the same as passing a zero-size {@link EnumItem} array.
    *
    * <p />Note that the <code>items</code> array may contain
    * <code>null</code> values. These will be ignored.
    *
    * @param name
    *    the name of the type, not <code>null</code>.
    *
    * @param items
    *    the items for the type, or <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>name == null</code>.
    */
   public EnumType(String name, EnumItem[] items)
   throws IllegalArgumentException {
      super(name, EnumItem.class);

      _namesToValues = new HashMap<String,String>();
      _valuesToNames = new HashMap<String,String>();
      _namesToItems  = new HashMap<String,EnumItem>();
      _valuesToItems = new HashMap<String,EnumItem>();
      _items         = new ArrayList<EnumItem>();

      int count = items == null ? 0 : items.length;
      String[] values = new String[count];
      int actualItems = 0;
      for (int i = 0; i < count; i++) {
         EnumItem item = items[i];
         if (item != null) {
            String itemName  = item.getName();
            String itemValue = item.getValue();

            _namesToValues.put(itemName,  itemValue);
            _valuesToNames.put(itemValue, itemName);
            values[actualItems++] = itemValue;
            _namesToItems.put(itemName,   item);
            _valuesToItems.put(itemValue, item);
            _items.add(item);
         }
      }

      _values = new String[actualItems];
      System.arraycopy(values, 0, _values, 0, actualItems);
   }

   /**
    * Actually checks if the specified value is valid for this type. This
    * method is called from {@link #isValidValue(String)}. It is guaranteed that
    * the argument is not <code>null</code>.
    *
    * @param value
    *    the value that should be checked for validity, never
    *    <code>null</code>.
    *
    * @return
    *    <code>true</code> if and only if the specified value is valid,
    *    <code>false</code> otherwise.
    */
   protected final boolean isValidValueImpl(String value) {
      for (int i = 0; i < _values.length; i++) {
         if (_values[i].equals(value)) {
            return true;
         }
      }
      return false;
   }

   /**
    * Converts the specified <code>EnumItem</code> to a string.
    *
    * @param value
    *    the value to convert, can be <code>null</code>.
    *
    * @return
    *    the textual representation of the value, or <code>null</code> if and
    *    only if <code>value == null</code>.
    */
   public String toString(EnumItem value) {

      // Short-circuit if the argument is null
      if (value == null) {
         return null;
      } else {
         return value.getValue();
      }
   }

   /**
    * Generates a string representation of the specified value for this type.
    * The specified value must be an instance of the value class for this type
    * (see {@link #getValueClass()}). Also, it has to fall within the range of
    * valid values.
    *
    * @param value
    *    the value, cannot be <code>null</code>.
    *
    * @return
    *    the string representation of the specified value for this type,
    *    cannot be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>value == null</code>.
    *
    * @throws ClassCastException
    *    if <code>getValueClass().isInstance(value) == false</code>.
    *
    * @throws TypeValueException
    *    if the specified value is not in the allowed range.
    */
   public final String toString(Object value)
   throws IllegalArgumentException, ClassCastException, TypeValueException {
      MandatoryArgumentChecker.check("value", value);
      EnumItem item = (EnumItem) value;
      return item.getValue();
   }

   /**
    * Gets the value matching the specified name.
    *
    * @param name
    *    the name to match a corresponding value by, cannot be
    *    <code>null</code>.
    *
    * @return
    *    the corresponding value, or <code>null</code> if there is none.
    *
    * @throws IllegalArgumentException
    *    if <code>name == null</code>.
    */
   public final String getValueByName(String name)
   throws IllegalArgumentException {

      // Check preconditions
      MandatoryArgumentChecker.check("name", name);

      return (String) _namesToValues.get(name);
   }

   /**
    * Gets the name matching the specified value.
    *
    * @param value
    *    the value to match a corresponding name by, cannot be
    *    <code>null</code>.
    *
    * @return
    *    the corresponding name, or <code>null</code> if there is none.
    *
    * @throws IllegalArgumentException
    *    if <code>value == null</code>.
    */
   public final String getNameByValue(String value)
   throws IllegalArgumentException {

      // Check preconditions
      MandatoryArgumentChecker.check("value", value);

      return (String) _valuesToNames.get(value);
   }

   /**
    * Get the list of the EnumItem included in this <code>EnumType</code>.
    *
    * @return
    *    the list of {@link EnumItem} included in this <code>EnumType</code>.
    */
   public final List<EnumItem> getEnumItems() {
      return _items;
   }
}