package com.thaiopensource.validate.nvdl;

import com.thaiopensource.util.Equal;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 * Keeps modes depending on context.
 * The structure of the context map is
 * 
 * stores the mode for 
 *  /  in rootValue
 *  "" in otherValue (this is for relative paths)
 * stores a hash with the last path elements as key and
 * ContextMap objects as values.
 * 
 * A path like a/b and mode x
 * will be represented by 3 ContextMap objects
 * ContextMap b ---> ContextMap a ---> ContextMap otherValue=x
 * 
 * Addind also /a/b and mode y will give
 * 
 * ContextMap b ---> ContextMap a ---> ContextMap (otherValue=x, rootValue=y)
 * 
 * Adding a2/b and mode w will give
 * 
 *  ContextMap b ---> ContextMap a ---> ContextMap (otherValue=x, rootValue=y)
 *                              a2 ---> ContextMap otherValue=w
 */
class ContextMap {
  /**
   * Stores the mode associated with an absolute path.
   */
  private Object rootValue;
  /**
   * Stores a mode associated with a relative path.
   */
  private Object otherValue;
  /**
   * Stores a hash map with with the key the last local name and 
   * as values other ContextMap objects.
   */
  private final Hashtable nameTable = new Hashtable();

  /**
   * Get the mode matching a list of local names.
   * A root more returned means an exact matching of the given local names 
   * with the local names from the context map. Otherwise we can get either 
   * a mode stored as otherValue or null if the given context does not match 
   * any of the stored paths.
   * @param context The list of local names that represent a section context 
   * (path from root local element names from the same namespace).
   * @return A mode or null.
   */  
  Object get(Vector context) {
    return get(context, context.size());
  }
  
  /**
   * Adds a single path (isRoot, names) and a mode to be used for this path = context.
   * @param isRoot True if the path starts with /
   * @param names The local names that form the path.
   * @param value The mode.
   * @return true if there is no duplicate path, false otherwise.
   */
  boolean put(boolean isRoot, Vector names, Object value) {
    return put(isRoot, names, names.size(), value);
  }

  /**
   * Get the mode matching a list of local names.
   * A root more returned means an exact matching of the given local names 
   * with the local names from the context map. Otherwise we can get either 
   * a mode stored as otherValue or null if the given context does not match 
   * any of the stored paths.
   * @param context The list of local names that represent a section context 
   * (path from root local element names from the same namespace).
   * @param len The length we should take from the list.
   * @return A mode or null.
   */
  private Object get(Vector context, int len) {
    if (len > 0) {
      ContextMap nestedMap = (ContextMap)nameTable.get(context.elementAt(len - 1));
      if (nestedMap != null) {
        Object value = nestedMap.get(context, len - 1);
        if (value != null)
          return value;
      }
    }
    if (rootValue != null && len == 0)
      return rootValue;
    return otherValue;
  }

  /**
   * Adds a single path (isRoot, names) and a mode to be used for this path = context.
   * @param isRoot True if the path starts with /
   * @param names The local names that form the path.
   * @param len The length if the names vector.
   * @param value The mode.
   * @return true if there is no duplicate path, false otherwise.
   */
  private boolean put(boolean isRoot, Vector names, int len, Object value) {
    if (len == 0) {
      // if we have only /
      if (isRoot) {
        if (rootValue != null)
          return false;
        rootValue = value;
      }
      // We followed all the paths, it is not root, 
      // then we store the mode as the other value.
      else {
        if (otherValue != null)
          return false;
        otherValue = value;
      }
      return true;
    }
    else {
      // get the last local name from the path
      Object name = names.elementAt(len - 1);
      // Get the context map mapped in nameTable to that name.
      ContextMap nestedMap = (ContextMap)nameTable.get(name);
      // Not preset then create it.
      if (nestedMap == null) {
        nestedMap = new ContextMap();
        nameTable.put(name, nestedMap);
      }
      // Add the rest of the path names in the nested context map.
      return nestedMap.put(isRoot, names, len - 1, value);
    }
  }

  /**
   * Chek that this context map is equals with
   * a specified context map.
   */
  public boolean equals(Object obj) {
    if (!(obj instanceof ContextMap))
      return false;
    ContextMap other = (ContextMap)obj;
    if (!Equal.equal(this.rootValue, other.rootValue)
        || !Equal.equal(this.otherValue, other.otherValue))
      return false;
    // We want jing to work with JDK 1.1 so we cannot use Hashtable.equals
    if (this.nameTable.size() != other.nameTable.size())
      return false;
    for (Enumeration e = nameTable.keys(); e.hasMoreElements();) {
      Object key = e.nextElement();
      if (!nameTable.get(key).equals(other.nameTable.get(key)))
        return false;
    }
    return true;
  }

  /**
   * Get a hashcode for this context map.
   */
  public int hashCode() {
    int hc = 0;
    if (rootValue != null)
      hc ^= rootValue.hashCode();
    if (otherValue != null)
      hc ^= otherValue.hashCode();
    for (Enumeration e = nameTable.keys(); e.hasMoreElements();) {
      Object key = e.nextElement();
      hc ^= key.hashCode();
      hc ^= nameTable.get(key).hashCode();
    }
    return hc;
  }

  /**
   * Creates an Enumeration implementation that enumerates all the 
   * modes stored in this context map and in the nested context maps.
   */
  static private class Enumerator implements Enumeration {
    /**
     * Store this context map root value.
     */
    private Object rootValue;
    
    /**
     * Store this context map other value.
     */
    private Object otherValue;
    
    /**
     * Stores the enumeration of modes of the current subMap.
     */
    private Enumeration subMapValues;
    
    /**
     * Stores the ContextMap objects from the nameTable.
     */
    private final Enumeration subMaps;

    private Enumerator(ContextMap map) {
      rootValue = map.rootValue;
      otherValue = map.otherValue;
      subMaps = map.nameTable.elements();
    }

    /**
     * Advance to the next context map values
     * in subMapValues and to the next element 
     * in subMap enumeration, if needed.
     */
    private void prep() {
      while ((subMapValues == null || !subMapValues.hasMoreElements()) && subMaps.hasMoreElements())
        subMapValues = ((ContextMap)subMaps.nextElement()).values();
    }

    /**
     * True if we have more elements.
     */
    public boolean hasMoreElements() {
      prep();
      return rootValue != null || otherValue != null || (subMapValues != null && subMapValues.hasMoreElements());
    }

    /**
     * Get the next element (mode in this case).
     */
    public Object nextElement() {
      if (rootValue != null) {
        Object tem = rootValue;
        rootValue = null;
        return tem;
      }
      if (otherValue != null) {
        Object tem = otherValue;
        otherValue = null;
        return tem;
      }
      prep();
      if (subMapValues == null)
        throw new NoSuchElementException();
      return subMapValues.nextElement();
    }
  }

  /**
   * Get an enumeration with all the modes in this context map.
   * @return An enumeration containing Mode objects.
   */
  Enumeration values() {
    return new Enumerator(this);
  }
}
