/*
 * Copyright 1999-2006 University of Chicago
 *
 * 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.globus.rsl;

import java.util.*;

/**
 * This class provides convieniene methods for
 * accessing and manipulatig simple rsl expressions.
 * The class provides methods for retreiving and setting
 * values of specified attributes.
 */
public class RslAttributes {

    protected RslNode rslTree;

    /**
     * Creates an empty RslAttributes object.
     */
    public RslAttributes() {
	rslTree = new RslNode();
    }

    /**
     * Creates a new RslAttributes object with
     * specified rsl parse tree.
     *
     * @param rslTree the rsl parse tree.
     */
    public RslAttributes(RslNode rslTree) {
	this.rslTree = rslTree;
    }

    /**
     * Creates a new RslAttributes object from
     * specified RSL string.
     *
     * @param rsl the rsl string.
     * @exception ParseException if the rsl cannot be parsed.
     */
    public RslAttributes(String rsl)
	throws ParseException {
	rslTree = RSLParser.parse(rsl);
    }

    /**
     * Returns the rsl parse tree.
     *
     * @return the rsl parse tree.
     */
    public RslNode getRslNode() {
	return rslTree;
    }

    /**
     * Returns a string value of the specified attribute.
     * If the attribute contains multiple values the
     * first one is returned.
     *
     * @param attribute the rsl attribute to return the value of.
     * @return value of the relation. Null is returned if there is
     *         no such attribute of the attribute/value relation is
     *         not an equality relation.
     */
    public String getSingle(String attribute) {
	NameOpValue nv = rslTree.getParam(attribute);
	if (nv == null || nv.getOperator() != NameOpValue.EQ) return null;
	Object obj = nv.getFirstValue();
	if (obj != null && obj instanceof Value) {
	    return ((Value)obj).getCompleteValue();
	} else {
	    return null;
	}
    }

    /**
     * Returns a list of strings for a specified attribute.
     * For example for 'arguments' attribute.
     *
     * @param attribute the rsl attribute to return the values of.
     * @return the list of values of the relation. Each value is
     *         a string. Null is returned if there is no such
     *         attribute or the attribute/values relation is not
     *         an equality relation.
     */
    public List getMulti(String attribute) {
	NameOpValue nv = rslTree.getParam(attribute);
	if (nv == null || nv.getOperator() != NameOpValue.EQ) return null;
	List values = nv.getValues();
	List list = new LinkedList();
	Iterator iter = values.iterator();
	Object obj;
	while( iter.hasNext() ) {
	    obj = iter.next();
	    if (obj instanceof Value) {
		list.add( ((Value)obj).getCompleteValue() );
	    }
	}
	return list;
    }

    /**
     * Returns a key/value pair map for a specified attribute.
     * For example for 'environment' attribute.
     * Note: <I>Use getVariables() for rsl_substitution attribute.</I>
     *
     * @param attribute the rsl attribute to return the key/value pair map of.
     * @return a key/value pair map. Null is returned if there is no such
     *         attribute defined or if the attribute/value relation is not
     *         an equality relation.
     */
    public Map getMap(String attribute) {
	NameOpValue nv = rslTree.getParam(attribute);
	if (nv == null || nv.getOperator() != NameOpValue.EQ) return null;
	List values = nv.getValues();
	Map map = new HashMap();
	Iterator iter = values.iterator();
	Object obj;
	while( iter.hasNext() ) {
	    obj = iter.next();
	    if (obj instanceof List) {
		String key, value;
		List list = (List)obj;
		if (list.size() != 2) continue; // must have 2 values!
		obj = list.get(0);
		if (obj instanceof Value) {
		    key = ((Value)obj).getCompleteValue();
		} else {
		    continue;
		}
		obj = list.get(1);
		if (obj instanceof Value) {
		    value = ((Value)obj).getCompleteValue();
		} else {
		    continue;
		}
		map.put(key, value);
	    }
	}
	return map;
    }

    /**
     * Returns a variable name/value pair map of variable definitions.
     * Currently specified by the 'rsl_substitution' attribute.
     *
     * @param attribute the attribute that defines variables. Currently,
     *        only 'rsl_substitution' is supported.
     * @return a variable name/value pair map. Null, if there is no
     *          definitions for a specified attribute.
     *
     */
    public Map getVariables(String attribute) {
	Bindings binds = rslTree.getBindings(attribute);
	if (binds == null) return null;
        List values = binds.getValues();
        Map map = new HashMap();
        Iterator iter = values.iterator();
	Binding binding;
        while( iter.hasNext() ) {
            binding = (Binding)iter.next();
	    map.put(binding.getName(),
		    binding.getValue().getCompleteValue());
        }
        return map;
    }

    /**
     * Adds a new variable definition to the specified variable definitions
     * attribute.
     *
     * @param attribute the variable definitions attribute - rsl_subsititution.
     * @param varName the variable name to add.
     * @param value the value of the variable to add.
     */
    public void addVariable(String attribute, String varName, String value) {
	Bindings binds = rslTree.getBindings(attribute);
	if (binds == null) {
	    binds = new Bindings(attribute);
	    rslTree.put(binds);
	}
	binds.add(new Binding(varName, value));
    }

    /**
     * Removes a specific variable definition given a variable name.
     *
     * @param attribute the attribute that defines variable definitions.
     * @param varName the name of the variable to remove.
     * @return true if the variable was successfully removed. Otherwise,
     *         returns false,
     */
    public boolean removeVariable(String attribute, String varName) {
	Bindings binds = rslTree.getBindings(attribute);
        if (binds == null) return false;
	return binds.removeVariable(varName);
    }

    /**
     * Removes a specific attribute from attribute/value relations.
     *
     * @param attribute the attribute name to remove.
     */
    public void remove(String attribute) {
	rslTree.removeParam(attribute);
    }

    /**
     * Removes a specific value from a list of values of the specified
     * attribute.
     *
     * @param attribute the attribute from which to remote the value from.
     * @param value the specific value to remove.
     * @return true if the value was successfully removed. Otherwise,
     *         returns false,
     */
    public boolean remove(String attribute, String value) {
	NameOpValue nv = rslTree.getParam(attribute);
	if (nv == null || nv.getOperator() != NameOpValue.EQ) return false;
	return nv.remove(new Value(value));
    }

    /**
     * Removes a specific key from a list of values of the specified
     * attribute. The attribute values must be in the right form. See
     * the 'environment' rsl attribute.
     *
     * @param attribute the attribute to remove the key from.
     * @param key the key to remove.
     * @return true if the key was successfully removed. Otherwise,
     *         returns false.
     */
    public boolean removeMap(String attribute, String key) {
	NameOpValue nv = rslTree.getParam(attribute);
	if (nv == null || nv.getOperator() != NameOpValue.EQ) return false;
	List values = nv.getValues();
	Iterator iter = values.iterator();
	Object obj;
	int i=0;
	int found = -1;
	while( iter.hasNext() ) {
	    obj = iter.next();
	    if (obj instanceof List) {
		List vr = (List)obj;
		if (vr.size() > 0) {
		    Object var = vr.get(0);
		    if (var instanceof Value &&
			((Value)var).getValue().equals(key)) {
			found = i;
			break;
		    }
		}
	    }
	    i++;
	}
	if (found != -1) {
	    values.remove(found);
	    return true;
	} else {
	    return false;
	}
    }

    protected NameOpValue getRelation(String attribute) {
	NameOpValue nv = rslTree.getParam(attribute);
	if (nv == null) {
	    nv = new NameOpValue(attribute, NameOpValue.EQ);
	    rslTree.put(nv);
	}
	return nv;
    }

    /**
     * Sets the attribute value to the given value.
     * All previous values are removed first.
     *
     * @param attribute the attribute to set the value of.
     * @param value the value to add.
     */
    public void set(String attribute, String value) {
	NameOpValue nv = getRelation(attribute);
	nv.clear();
	nv.add(new Value(value));
    }

    /**
     * Adds a simple value to the list of values of a given
     * attribute.
     *
     * @param attribute the attribute to add the value to.
     * @param value the value to add.
     */
    public void add(String attribute, String value) {
	NameOpValue nv = getRelation(attribute);
	nv.add(new Value(value));
    }

    /**
     * Sets the attribute value to the given list of values.
     * The list of values is added as a single value.
     *
     * @param attribute the attribute to set the value of.
     * @param values the list of values to add.
     */
    public void setMulti(String attribute, String [] values) {
	NameOpValue nv = getRelation(attribute);
	nv.clear();
	List list = new LinkedList();
	for (int i=0;i<values.length;i++) {
	    list.add(new Value(values[i]));
	}
	nv.add(list);
    }

    /**
     * Adds a list of values as a single value to the specified
     * attribute.
     *
     * @param attribute the attribute to add the list of values to.
     * @param values the values to add.
     */
    public void addMulti(String attribute, String [] values) {
	NameOpValue nv = getRelation(attribute);

	List list = new LinkedList();
	for (int i=0;i<values.length;i++) {
	    list.add(new Value(values[i]));
	}
	nv.add(list);
    }

    // --- Compatibility API with old RslAttributes class ---

    /**
     * Returns the first value of a specified attribute.
     *
     * @return the first value of the attribute.
     * @deprecated use getSingle() instead.
     */
    public String getFirstValue(String attribute) {
	return getSingle(attribute);
    }

    /**
     * Returns the values for a specified attribute.
     *
     * @return the list of values.
     * @deprecated use getMulti() instead.
     */
    public List get(String attribute) {
        return getMulti(attribute);
    }

    /**
     * Returns the rsl.
     *
     * @return the rsl.
     */
    public String toRSL() {
	return rslTree.toRSL(true);
    }

}
