/**
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.eclipse.webdav.internal.kernel;

import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.webdav.IResponse;

/** 
 * A Precondition represents some condition or collection of conditions
 * representing states of a resource. If the state of the resource does not
 * match any of the specified states in any of the conditions, then the 
 * method must fail. Conditions in a Precondition are OR'd together while
 * ConditionFactors in a ConditionTerm are AND'ed.
 */
public class Precondition {

	private Vector conditions = new Vector();

	/** 
	 * Construct an empty Precondition. The client must add Conditions.
	 */
	public Precondition() {
		super();
	}

	/** 
	 * Construct a Precondition by parsing the given If header as defined by
	 * section 8.4 in the WebDAV spec.
	 * 
	 * @param ifHeader the contents of a WebDAV If header
	 */
	public Precondition(String ifHeader) throws WebDAVException {
		StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(ifHeader));
		// URI characters
		tokenizer.wordChars('!', '/');
		tokenizer.wordChars(':', '@');
		tokenizer.ordinaryChar('(');
		tokenizer.ordinaryChar(')');
		tokenizer.ordinaryChar('<');
		tokenizer.ordinaryChar('>');
		tokenizer.ordinaryChar('[');
		tokenizer.ordinaryChar(']');
		tokenizer.quoteChar('"');
		int token = 0;
		try {
			token = tokenizer.nextToken();
			switch (token) {
				case '<' :
					while (token == '<') {
						addCondition(Condition.create(tokenizer));
						token = tokenizer.ttype;
					}
					break;
				case '(' :
					while (token == '(') {
						addCondition(Condition.create(tokenizer));
						token = tokenizer.ttype;
					}
					break;
			}
			if (token != StreamTokenizer.TT_EOF)
				throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseMissing", String.valueOf(token), "EOF")); //$NON-NLS-1$ //$NON-NLS-2$

			if (!getConditions().hasMoreElements())
				throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseEmptyList", ifHeader)); //$NON-NLS-1$

		} catch (IOException exc) {
			// ignore or log?
		}
	}

	/**
	 * Add a Condition to this Precondition. Conditions are OR'd together to 
	 * check for a matching resource.
	 *
	 * @param condition the Condition to add
	 * @exception WebDAVException thrown if the precondition already contains this condition
	 */
	public void addCondition(Condition condition) throws WebDAVException {
		// a Resource URI can only be specified once in a Precondition
		Enumeration conditions = getConditions();
		if (condition.getResourceURI() != null) {
			while (conditions.hasMoreElements()) {
				Condition existingCondition = (Condition) conditions.nextElement();
				if (existingCondition.getResourceURI() != null && existingCondition.getResourceURI().equals(condition.getResourceURI()))
					throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseMultipleSpecification", condition.getResourceURI())); //$NON-NLS-1$
			}
		}
		this.conditions.addElement(condition);
	}

	/** 
	 * Add a condition created from the given URI and state token. This is a
	 * convenience method used primarily to create preconditions for lock tokens
	 * that must be provided in the resource context for methods that update
	 * the resource.
	 *
	 * @param resourceURI the URI of the resource the state token applies to. Null
	 *     implicitly specifies the resource processing the request
	 * @param stateToken the state token to match
	 */
	public void addStateTokenCondition(String resourceURI, String stateToken) throws WebDAVException {
		Condition condition = new Condition(resourceURI);
		ConditionTerm term = new ConditionTerm();
		term.addConditionFactor(new StateToken(stateToken));
		condition.addConditionTerm(term);
		addCondition(condition);
	}

	/** 
	 * Construct a Precondition by parsing the given If header as defined by
	 * section 9.4 in the WebDAV spec.
	 *
	 * @param ifHeader the contents of a WebDAV If header
	 * @return the parser If header
	 * @exception WebDAVException thrown if there is a syntax error in the If header
	 */
	public static Precondition create(String ifHeader) throws WebDAVException {
		return new Precondition(ifHeader);
	}

	/** 
	 * Get the Conditions contained in this Precondition. At least one must match
	 * in order for a valid match to occur.
	 *
	 * @return an Enumeration of Conditions
	 */
	public Enumeration getConditions() {
		return conditions.elements();
	}

	/**
	 * See if this Precondition contains a matching Condition.
	 *
	 * @param condition the condition to match
	 * @return true if this precondition contains atleast one condition matching the given condition
	 */
	public boolean matches(Condition condition) {
		boolean match = false;
		Enumeration conditions = getConditions();
		while (!match && conditions.hasMoreElements()) {
			Condition existingCondition = (Condition) conditions.nextElement();
			match = existingCondition.matches(condition);
		}
		return match;
	}

	/** 
	 * Return a String representation of this Precondition as defined by section 9.4
	 * of the WebDAV Spec. The string is the value of an If header.
	 *
	 * @return a string representation of this precondition
	 */
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		Enumeration conditions = getConditions();
		while (conditions.hasMoreElements()) {
			Condition condition = (Condition) conditions.nextElement();
			buffer.append(condition.toString());
			if (conditions.hasMoreElements())
				buffer.append(' ');
		}
		return buffer.toString();
	}
}
