/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      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.IOException;
import java.io.StreamTokenizer;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.webdav.IResponse;

/** 
 * A ConditionTerm represents some state configuration of a resource that must be
 * satisfied in order for the associated request to be valid. The ConditionFactors in
 * a ConditionTerm must all match with states of the resource, i.e., they are AND'ed
 * together. ConditionTerms are contained in a Condition which is used in the Precondition
 * of a WebDAV If header.
 */
public class ConditionTerm {

    private Vector conditionFactors = new Vector();

    /**
     * Construct a Condition with no associated Resource URI.
     */
    public ConditionTerm() {
        super();
    }

    /**
     * Add a ConditionFactor to a ConditionTerm.
     *
     * @param factor the factor to add
     * @exception WebDAVException thrown if the term already contains the factor
     */
    public void addConditionFactor(ConditionFactor factor) throws WebDAVException {
        if (conditionFactors.contains(factor))
            throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseDuplicateEntry")); //$NON-NLS-1$
        conditionFactors.addElement(factor);
    }

    /**
     * Does this ConditionTerm contain the given ConditionFactor?
     *
     * @param factor the factor to check for
     * @return true if the term contains the given factor
     */
    public boolean contains(ConditionFactor factor) {
        return conditionFactors.contains(factor);
    }

    /**
     * Create a ConditionTerm by parsing the given If header as defined by
     * section 9.4 in the WebDAV spec.
     *
     * @param tokenizer a StreamTokenizer on the contents of a WebDAV If header
     * @return the parsed ConditionTerm
     * @exception WebDAVException thrown if there is a syntax error in the If header
     */
    public static ConditionTerm create(StreamTokenizer tokenizer) throws WebDAVException {
        ConditionTerm term = new ConditionTerm();
        try {
            int token = tokenizer.ttype;
            if (token == '(')
                token = tokenizer.nextToken();
            else
                throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseMissing", String.valueOf(token), "(")); //$NON-NLS-1$ //$NON-NLS-2$

            while (token == StreamTokenizer.TT_WORD || token == '<' || token == '[') {
                term.addConditionFactor(ConditionFactor.create(tokenizer));
                token = tokenizer.ttype;
            }
            if (token == ')')
                token = tokenizer.nextToken();
            else
                throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseMissing", String.valueOf(token), ")")); //$NON-NLS-1$ //$NON-NLS-2$
        } catch (IOException exc) {
            // ignore or log?
        }
        if (!term.getConditionFactors().hasMoreElements())
            throw new WebDAVException(IResponse.SC_BAD_REQUEST, Policy.bind("error.parseMissingStateOrEntity")); //$NON-NLS-1$
        return term;
    }

    /**
     * Get all the ConditionFactors in this Condition. The ConditionFactors in
     * a Condition must all match with states of the resource, i.e., they are AND'ed
     * together. ConditionTerms are contained in a Condition which is used in the
     * Precondition of a WebDAV If header.
     *
     * @return an Enumeration of ConditionFactors
     */
    public Enumeration getConditionFactors() {
        return conditionFactors.elements();
    }

    /**
     * See if this ConditionTerm matches the given ConditionTerm. This is an
     * AND operation. All the factors in the ConditionTerm must match.
     *
     * @param conditionTerm the term to match
     * @return true if all the factors in the term match those in this term
     */
    public boolean matches(ConditionTerm conditionTerm) {
        int numberOfItemsToMatch = 0;
        boolean match = true;
        Enumeration factors = getConditionFactors();
        while (match && factors.hasMoreElements()) {
            ConditionFactor factor = (ConditionFactor) factors.nextElement();
            if (factor.not()) {
                match = !conditionTerm.contains(factor);
            } else {
                match = conditionTerm.contains(factor);
                numberOfItemsToMatch++;
            }
        }
        match = match && numberOfItemsToMatch == conditionTerm.numberOfFactors();
        return match;
    }

    /**
     * Get the number of ConditionFactors in this ConditionTerm.
     *
     * @return the number of factors in this term
     */
    public int numberOfFactors() {
        return conditionFactors.size();
    }

    /**
     * Return a String representation of this ConditionTerm as defined by section 9.4
     * of the WebDAV Spec.
     *
     * @return a string representation of this term
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append('(');
        Enumeration factors = getConditionFactors();
        while (factors.hasMoreElements()) {
            ConditionFactor factor = (ConditionFactor) factors.nextElement();
            buffer.append(factor.toString());
            if (factors.hasMoreElements())
                buffer.append(' ');
        }
        buffer.append(')');
        return buffer.toString();
    }
}
