package com.thaiopensource.validate.nrl;

import org.xml.sax.Locator;
import org.xml.sax.helpers.LocatorImpl;

import java.util.Hashtable;
import java.util.Enumeration;

import com.thaiopensource.validate.nrl.ActionSet;
import com.thaiopensource.validate.nrl.AttributeActionSet;

class Mode {
  static final String ANY_NAMESPACE = "##any";
  static final int ATTRIBUTE_PROCESSING_NONE = 0;
  static final int ATTRIBUTE_PROCESSING_QUALIFIED = 1;
  static final int ATTRIBUTE_PROCESSING_FULL = 2;
  static final Mode CURRENT = new Mode("#current", null);

  private final String name;
  private Mode baseMode;
  private boolean defined;
  private Locator whereDefined;
  private Locator whereUsed;
  private final Hashtable elementMap = new Hashtable();
  private final Hashtable attributeMap = new Hashtable();
  private int attributeProcessing = -1;

  Mode(String name, Mode baseMode) {
    this.name = name;
    this.baseMode = baseMode;
  }

  String getName() {
    return name;
  }

  Mode getBaseMode() {
    return baseMode;
  }

  void setBaseMode(Mode baseMode) {
    this.baseMode = baseMode;
  }

  ActionSet getElementActions(String ns) {
    ActionSet actions = getElementActionsExplicit(ns);
    if (actions == null) {
      actions = getElementActionsExplicit(ANY_NAMESPACE);
      // this is not correct: it breaks a derived mode that use anyNamespace
      // elementMap.put(ns, actions);
    }
    return actions;
  }

  private ActionSet getElementActionsExplicit(String ns) {
    ActionSet actions = (ActionSet)elementMap.get(ns);
    if (actions == null && baseMode != null) {
      actions = baseMode.getElementActionsExplicit(ns);
      if (actions != null) {
        actions = actions.changeCurrentMode(this);
        elementMap.put(ns, actions);
      }
    }
    return actions;
  }

  AttributeActionSet getAttributeActions(String ns) {
    AttributeActionSet actions = getAttributeActionsExplicit(ns);
    if (actions == null) {
      actions = getAttributeActionsExplicit(ANY_NAMESPACE);
      // this is not correct: it breaks a derived mode that use anyNamespace
      // attributeMap.put(ns, actions);
    }
    return actions;
  }

  private AttributeActionSet getAttributeActionsExplicit(String ns) {
    AttributeActionSet actions = (AttributeActionSet)attributeMap.get(ns);
    if (actions == null && baseMode != null) {
      actions = baseMode.getAttributeActionsExplicit(ns);
      if (actions != null)
        attributeMap.put(ns, actions);
    }
    return actions;
  }

  int getAttributeProcessing() {
    if (attributeProcessing == -1) {
      if (baseMode != null)
        attributeProcessing = baseMode.getAttributeProcessing();
      else
        attributeProcessing = ATTRIBUTE_PROCESSING_NONE;
      for (Enumeration e = attributeMap.keys(); e.hasMoreElements() && attributeProcessing != ATTRIBUTE_PROCESSING_FULL;) {
        String ns = (String)e.nextElement();
        AttributeActionSet actions = (AttributeActionSet)attributeMap.get(ns);
        if (!actions.getAttach()
            || actions.getReject()
            || actions.getSchemas().length > 0)
          attributeProcessing = ((ns.equals("") || ns.equals(ANY_NAMESPACE))
                                ? ATTRIBUTE_PROCESSING_FULL
                                : ATTRIBUTE_PROCESSING_QUALIFIED);
      }
    }
    return attributeProcessing;
  }

  Locator getWhereDefined() {
    return whereDefined;
  }

  boolean isDefined() {
    return defined;
  }

  Locator getWhereUsed() {
    return whereUsed;
  }

  void noteUsed(Locator locator) {
    if (whereUsed == null && locator != null)
      whereUsed = new LocatorImpl(locator);
  }

  void noteDefined(Locator locator) {
    defined = true;
    if (whereDefined == null && locator != null)
      whereDefined = new LocatorImpl(locator);
  }

  boolean bindElement(String ns, ActionSet actions) {
    if (elementMap.get(ns) != null)
      return false;
    elementMap.put(ns, actions);
    return true;
  }

  boolean bindAttribute(String ns, AttributeActionSet actions) {
    if (attributeMap.get(ns) != null)
      return false;
    attributeMap.put(ns, actions);
    return true;
  }

}
