/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * 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.
 * 
 * $Id$
 */

package net.noderunner.exml;

import java.util.Collection;
import java.util.Iterator;

/**
 * Contains DTD rules for an attribute.
 * <p>
 * @author Elias Ross
 * @version 1.0
 */
public class AttributeRule 
{
	/** Name of the attribute */
	private String name;
	/** One of the types in AttributeValueType interface */
	private int valueType; 
	/** True if attribute is required to be specified */
	private boolean required;
	/** True if attribute is fixed */
	private boolean fixed;
	/** If fixed, the value it must have, otherwise its default value */
	private String value;
	/** If NAME_GROUP or NOTATION then the value must belong to this collection of strings */
	private Collection<String> enumeration;

	/**
	 * Constructs a new AttributeRule for an
	 * {@link AttributeValueType}.
	 */
	public AttributeRule(int valueType) {
		this(valueType, (Collection<String>)null);
	}

	/**
	 * Constructs a new AttributeRule for an
	 * {@link AttributeValueType} with an attribute name.
	 * @see AttributeValueType
	 */
	public AttributeRule(int valueType, String name) {
		this(valueType, (Collection<String>)null);
		setName(name);
	}

	/**
	 * Constructs a new AttributeRule for an
	 * <code>AttributeValueType</code> along with an enumeration, a
	 * collection of possible string values.
	 * @see AttributeValueType
	 */
	public AttributeRule(int valueType, Collection<String> enumeration) 
	{
		this.valueType = valueType;
		this.enumeration = enumeration;
		this.fixed = false;
		this.required = false;
		this.value = null;
	}
	
	/**
	 * Returns a new rule where attribute can contain "CDATA".
	 */
	public static AttributeRule cdata(String name) {
	    return new AttributeRule(AttributeValueType.CDATA, name);
	}

	/**
	 * Sets the name of the attribute.
	 * TODO:  Set up namespace correctly.
	 */
	public void setName(String name)
	{
		this.name = name;
	}

	/**
	 * Returns the name of this attribute.
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * Returns true if the name matches the given attribute.
	 */
	public boolean matches(Attribute a)
	{
		return name.equals(a.getName());
	}

	/**
	 * Returns true if the attribute value is fixed.
	 */
	public boolean isFixed() {
		return fixed;
	}

	/**
	 * Returns true if the attribute is required to be declared.
	 */
	public boolean isRequired() {
		return required;
	}

	/**
	 * Returns true if the attribute has a default or fixed value.
	 */
	public boolean isDefault() {
		return value != null;
	}

	/**
	 * Returns the default or fixed value.
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Sets this attribute to have a fixed value.
	 */
	public void setFixed(String value)
		throws AttributeRuleException
	{
		if (valueType == AttributeValueType.NAME_GROUP || valueType == AttributeValueType.NOTATION) {
			if (!enumeration.contains(value))
				throw new AttributeRuleException("FIXED value not in enumeration " + value, this);
		}
		this.value = value;
		fixed = true;
	}

	/**
	 * Sets this attribute to be required.
	 */
	public void setRequired() {
		required = true;
	}

	/**
	 * Sets the default value for this attribute.
	 */
	public void setDefault(String value) {
		this.value = value;
	}

	/**
	 * Returns true if the value is allowed.
	 */
	public boolean allowedValue(String value) {
		if (fixed)
			return this.value.equals(value);
		if (valueType == AttributeValueType.NAME_GROUP || valueType == AttributeValueType.NOTATION)
			return enumeration.contains(value);
		return true;
	}

	/**
	 * Returns the string form of a value, from the interface
	 * <code>AttributeValueType</code>.
	 * @see AttributeValueType
	 */
	public static String valueTypeToString(int value) {
		switch (value) {
			case AttributeValueType.CDATA: return XmlTags.ST_CDATA;
			case AttributeValueType.NMTOKEN: return XmlTags.TT_NMTOKEN;
			case AttributeValueType.NMTOKENS: return XmlTags.TT_NMTOKENS;
			case AttributeValueType.ENTITY: return XmlTags.TT_ENTITY;
			case AttributeValueType.ENTITIES: return XmlTags.TT_ENTITIES;
			case AttributeValueType.ID: return XmlTags.TT_ID;
			case AttributeValueType.IDREF: return XmlTags.TT_IDREF;
			case AttributeValueType.IDREFS: return XmlTags.TT_IDREFS;
			case AttributeValueType.NOTATION: return XmlTags.ET_NOTATION;
			case AttributeValueType.NAME_GROUP: return "name group";
			default: return "unknown type " + value;
		}
	}

	/**
	 * Creates a string formed by a set of Objects as a concatenation.
	 * The concatenation consists of their <code>Object.toString</code>
	 * values in the form:
	 * <pre>( obj1 | obj2 | obj3 )</pre>
	 */
	public static String collectionToString(Collection<String> c) {
		Iterator<String> i = c.iterator();
		StringBuffer sb = new StringBuffer(16 * c.size());
		sb.append("(");
		while (i.hasNext()) {
			sb.append(i.next());
			if (i.hasNext())
				sb.append(" | ");
		}
		sb.append(")");
		return sb.toString();
	}

	/**
	 * Constructs a debug string which should match the DTD declaration
	 * used to construct this object.
	 */
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(getName()).append(' ');
		if (valueType == AttributeValueType.NAME_GROUP)
			sb.append(collectionToString(enumeration));
		else if (valueType == AttributeValueType.NOTATION)
			sb.append("NOTATION ").append(collectionToString(enumeration));
		else sb.append(valueTypeToString(valueType));
		if (required)
			sb.append(' ').append(XmlTags.REQUIRED_BEGIN);
		else {
			if (value == null)
				sb.append(' ').append(XmlTags.IMPLIED_BEGIN);
		}
		if (fixed)
			sb.append(' ').append(XmlTags.FIXED_BEGIN);
		if (value != null)
			sb.append(" \"").append(value).append('"');
		return sb.toString();
	}
}
