package com.jclark.xsl.om;

import java.util.Hashtable;

public class NameTableImpl implements NameTable {

  private final
  class NamespacePrefixMapImpl implements NamespacePrefixMap {
    final private String[] bindings;
    final private String defaultNS;

    NamespacePrefixMapImpl() {
      this.bindings = new String[0];
      this.defaultNS = null;
    }

    private
    NamespacePrefixMapImpl(String[] bindings, String defaultNS) {
      this.bindings = bindings;
      this.defaultNS = defaultNS;
    }

    public final
    int getSize() {
      return bindings.length >> 1;
    }

    public final
    String getPrefix(int i) {
      return bindings[i << 1];
    }

    public final
    String getNamespace(int i) {
      return bindings[(i << 1) | 1];
    }

    public final
    String getPrefix(String namespace) {
      for (int i = 1; i < bindings.length; i += 2) {
	if (namespace.equals(bindings[i]))
	  return bindings[i - 1];
      }
      return null;
    }

    public final
    String getNamespace(String prefix) {
      for (int i = 0; i < bindings.length; i += 2) {
	if (prefix.equals(bindings[i]))
	  return bindings[i + 1];
      }
      return null;
    }

    public final
    String getDefaultNamespace() {
      return defaultNS;
    }

    public
    Name expandAttributeName(String name, Node node) throws XSLException {
      int i = name.indexOf(':');
      if (i == -1)
	return createName(name);
      if (i == 3 && name.regionMatches(0, "xml", 0, 3))
	return createName(name, Name.XML_NAMESPACE);
      for (int j = 0; j < bindings.length; j += 2) {
	String prefix = bindings[j];
	if (prefix.length() == i && name.regionMatches(0, prefix, 0, i))
	  return createName(name, bindings[j + 1]);
      }
      throw new XSLException("no such prefix \"" + name.substring(0, i) + '"', node);
    }

    public
    Name expandElementTypeName(String name, Node node) throws XSLException {
      int i = name.indexOf(':');
      if (i == -1) {
	if (defaultNS != null)
	  return createName(name, defaultNS);
	else
	  return createName(name);
      }
      if (i == 3 && name.regionMatches(0, "xml", 0, 3))
	return createName(name, Name.XML_NAMESPACE);
      for (int j = 0; j < bindings.length; j += 2) {
	String prefix = bindings[j];
	if (prefix.length() == i && name.regionMatches(0, prefix, 0, i))
	  return createName(name, bindings[j + 1]);
      }
      throw new XSLException("no such prefix \"" + name.substring(0, i) + '"', node);
    }

    public
    NamespacePrefixMap unbind(String prefix) {
      for (int i = 0; i < bindings.length; i += 2) {
	if (prefix.equals(bindings[i])) {
	  String[] newBindings = new String[bindings.length - 2];
	  System.arraycopy(bindings, 0, newBindings, 0, i);
	  System.arraycopy(bindings, i + 2, newBindings, i, bindings.length - i - 2);
	  return intern(new NamespacePrefixMapImpl(newBindings, defaultNS));
	}
      }
      return this;
    }

    public
    NamespacePrefixMap bind(String prefix, String namespace) {
      int i;
      for (i = 0; i < bindings.length; i += 2) {
	int cmp = prefix.compareTo(bindings[i]);
	if (cmp < 0)
	  break;
	if (cmp == 0) {
	  if (namespace.equals(bindings[i + 1]))
	    return this;
	  String[] newBindings = (String[])bindings.clone();
	  newBindings[i + 1] = namespace;
	  return intern(new NamespacePrefixMapImpl(newBindings, defaultNS));
	}
      }
      String[] newBindings = new String[bindings.length + 2];
      System.arraycopy(bindings, 0, newBindings, 0, i);
      newBindings[i] = prefix;
      newBindings[i + 1] = namespace;
      System.arraycopy(bindings, i, newBindings, i + 2, bindings.length - i);
      return intern(new NamespacePrefixMapImpl(newBindings, defaultNS));
    }

    public
    NamespacePrefixMap bindDefault(String namespace) {
      if (namespace.equals(defaultNS))
	return this;
      return intern(new NamespacePrefixMapImpl(bindings, namespace));
    }

    public
    NamespacePrefixMap unbindDefault() {
      if (defaultNS == null)
	return this;
      return intern(new NamespacePrefixMapImpl(bindings, null));
    }

    public NameTable getNameTable() {
      return NameTableImpl.this;
    }

    public
    int hashCode() {
      int h = defaultNS != null ? defaultNS.hashCode() : 0;
      for (int i = 0; i < bindings.length; i++)
	h ^= bindings[i].hashCode();
      return h;
    }

    public
    boolean equals(Object obj) {
      if (obj == null || !(obj instanceof NamespacePrefixMapImpl))
	return false;
      NamespacePrefixMapImpl map = (NamespacePrefixMapImpl)obj;
      if (defaultNS == null) {
	if (map.defaultNS != null)
	  return false;
      }
      else if (!defaultNS.equals(map.defaultNS))
	return false;
      if (bindings.length != map.bindings.length)
	return false;
      for (int i = 0; i < bindings.length; i++) {
	if (!bindings[i].equals(map.bindings[i]))
	  return false;
      }
      return true;
    }
  }

  private
  class NameImpl implements Name {
    private String qName;
    private String namespace;
    private NameImpl canon;

    NameImpl(String qName, String namespace) {
      this.qName = qName;
      this.namespace = namespace;
      this.canon = this;
    }

    NameImpl(String qName, String namespace, NameImpl canon) {
      this.qName = qName;
      this.namespace = namespace;
      this.canon = canon;
    }

    public String getNamespace() {
      return namespace;
    }
    public String getLocalPart() {
      return canon.qName;
    }
    public String getPrefix() {
      int i = qName.indexOf(':');
      if (i < 0)
	return null;
      return qName.substring(0, i);
    }
    public boolean equals(Object obj) {
      if (obj != null && (obj instanceof NameImpl))
	return ((NameImpl)obj).canon == canon;
      return false;
    }
    public String toString() {
      return qName;
    }
    public int hashCode() {
      return System.identityHashCode(canon);
    }

    public Object getCreator() {
      return NameTableImpl.this;
    }
  }

  public Name createName(String qName, String namespace) {
    Hashtable ns;
    synchronized (namespaces) {
      ns = (Hashtable)namespaces.get(namespace);
      if (ns == null) {
	ns = new Hashtable();
	namespaces.put(namespace, ns);
      }
    }
    return createName(ns, qName, namespace);
  }

  public Name createName(String nonQName) {
    return createName(docNamespace, nonQName, null);
  }

  NameImpl createName(Hashtable ns, String qName, String namespace) {
    synchronized (ns) {
      NameImpl nm = (NameImpl)ns.get(qName);
      if (nm == null) {
	int i = qName.indexOf(':');
	if (i == -1)
	  nm = new NameImpl(qName, namespace);
	else
	  nm = new NameImpl(qName,
			    namespace,
			    createName(ns, qName.substring(i + 1), namespace));
	ns.put(qName, nm);
      }
      return nm;
    }
  }

  public NamespacePrefixMap getEmptyNamespacePrefixMap() {
    return emptyMap;
  }

  NamespacePrefixMap intern(NamespacePrefixMap prefixMap) {
    synchronized (prefixMaps) {
      Object obj = prefixMaps.get(prefixMap);
      if (obj != null)
	return (NamespacePrefixMap)obj;
      prefixMaps.put(prefixMap, prefixMap);
      return prefixMap;
    }
  }

  public NameTableImpl() {
    prefixMaps.put(emptyMap, emptyMap);
  }

  private NamespacePrefixMap emptyMap = new NamespacePrefixMapImpl();
  private Hashtable prefixMaps = new Hashtable();
  private Hashtable namespaces = new Hashtable();
  private Hashtable docNamespace = new Hashtable();
}
