/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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 com.thesett.aima.search.util;

import java.util.ArrayList;
import java.util.Iterator;

import com.thesett.aima.search.Operator;
import com.thesett.aima.search.TraversableState;
import com.thesett.common.util.Tree;

/**
 * TreeSearchState is a {@link TraversableState} that enables {@link com.thesett.aima.search.QueueBasedSearchMethod}s to
 * be applied to {@link com.thesett.common.util.Tree} s. It does not dictate the method of searching, or the order in
 * which the nodes and leafs of the tree are visited; a free choice of search algorithm is permitted. The constructor
 * accepts a predicate over the tree element type that is used to determine whether node or leaf points in the tree
 * correspond to goal states of the search.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Translate operations into new states.
 *
 Report the cost of an operation.
 *
 Enumerate the valid operations on a state.
 *
 Provide links to successor states.
 *
 Report goal state status.
 *

 *
 * @author Rupert Smith
 */
public class TreeSearchState<E> extends TraversableState<Tree<E>>
{
    /* Used for logging. */
    /* private static final Logger log = Logger.getLogger(TreeSearchState.class.getName()); */

    /** Holds the tree to search. */
    private final Tree<E> tree;

    /**
     * Builds a tree search state over a specified tree.
     *
     * @param tree The tree to search.
     */
    public TreeSearchState(Tree<E> tree)
    {
        /*log.fine("public TreeSearchState(Tree<E> tree, UnaryPredicate<E> goalPredicate): called");*/

        // Keep the tree to search.
        this.tree = tree;
    }

    /**
     * Returns the data element in the tree associated with this search state.
     *
     * @return The data element in the tree associated with this search state.
     */
    public E getElement()
    {
        return tree.getElement();
    }

    /**
     * Returns the state obtained by applying the specified operation. If the operation is not valid then this should
     * return null.
     *
     * @param  op The operator to apply to the traversable state.
     *
     * @return The new traversable state generated by applying the specified operator.
     */
    public TreeSearchState<E> getChildStateForOperator(Operator<Tree<E>> op)
    {
        /*log.fine("public Traversable getChildStateForOperator(Operator op): called");*/

        // Extract the child tree from the operator and create a new tree search state from it.
        return new TreeSearchState<E>(op.getOp());
    }

    /**
     * Calculates the cost of applying the specified operations.
     *
     * @param  op The operator to apply.
     *
     * @return The cost of applying the specified operation.
     */
    public float costOf(Operator op)
    {
        return 1.0f;
    }

    /**
     * Gets all operators valid from this state. If the current tree to search has any children these are encoded as
     * operators to access those child trees as tree search states. If the current tree is a leaf then an empty iterator
     * is returned.
     *
     * @param  reverse <tt>true</tt> if the operators are to be presented in reverse order.
     *
     * @return An iterator over all the operators that may be applied to this state.
     */
    public Iterator<Operator<Tree<E>>> validOperators(boolean reverse)
    {
        /*log.fine("public Iterator<Operator> validOperators(): called");*/

        // Check if the tree is a leaf and return an empty iterator if so.
        if (tree.isLeaf())
        {
            /*log.fine("is leaf");*/

            return new ArrayList<Operator<Tree<E>>>().iterator();
        }

        // Generate an iterator over the child trees of the current node, encapsulating them as operators.
        else
        {
            /*log.fine("is node");*/

            Tree.Node<E> node = tree.getAsNode();

            return new TreeSearchOperatorIterator<E>(node.getChildIterator());
        }
    }

    /**
     * Translates an iterator over child trees into an iterator over operators that encapsulate those child trees. This
     * saves building an intermediate collection to hold the child trees.
     */
    public static class TreeSearchOperatorIterator<E> implements Iterator<Operator<Tree<E>>>
    {
        /** Holds the child iterator. */
        private final Iterator<Tree<E>> childIterator;

        /**
         * Creates an iterator that turns child trees into operations.
         *
         * @param childIterator The iterator over the child trees.
         */
        public TreeSearchOperatorIterator(Iterator<Tree<E>> childIterator)
        {
            this.childIterator = childIterator;
        }

        /**
         * Determines whether or not there is a next element. Delegates to the underlying child tree iterator.
         *
         * @return <tt>true</tt> if there are more elements.
         */
        public boolean hasNext()
        {
            return childIterator.hasNext();
        }

        /**
         * Gets the next child tree encapsulated as an operator on tree search states.
         *
         * @return The next child tree encapsulated as an operator on tree search states.
         */
        public Operator<Tree<E>> next()
        {
            return new OperatorImpl<Tree<E>>(childIterator.next());
        }

        /** This operation is not supported. */
        public void remove()
        {
            throw new UnsupportedOperationException("Operation 'remove' not supported.");
        }
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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 com.thesett.aima.attribute.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.thesett.aima.state.BaseType;
import com.thesett.aima.state.InfiniteValuesException;
import com.thesett.aima.state.OrdinalAttribute;
import com.thesett.aima.state.ReferencableAttribute;
import com.thesett.aima.state.Type;
import com.thesett.common.error.NotImplementedException;

/**
 * IdAttributes are used to represent properties as integers that are symbols described by arbitrary objects. The number
 * of possible values that an IdAttribute can take on is expected to be reasonably large, a 32-bit integer is used to
 * encode them. The underlying objects to be encoded, must implement equals and hashCode correctly.
 *
 *
Working with object comparisons is time consuming and this attribute class provides facilities for working
 * quickly with objects comparisons, particularly when it is known in advance what the set of objects is. The technique
 * is sometimes called 'interning', or just table lookups. An example might be a program language interpreter, where
 * functions and variables are given string names. Comparing these names by full string comparison will be slow and the
 * same string comparisons will be used often. So the strings are enumerated and can be quickly compared by their
 * enumerated values, using fast integer comparison. This class extends the notion of interning strings, over objects in
 * general.
 *
 *
Objects also consume memory space, and encoding them as integers can provide a significant saving. The benefits
 * of this may be particularly felt in communication applications, where raw data is passed over a network. A compact
 * representation may enable the network protocol to run faster, by sending less data, and requiring less decoding on
 * the receiving end (provided the lookup table on the receiving end is up-to-date, of course). Again, the savings will
 * be best where the same set of objects are used repeatedly.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Represent a single object from a set of objects.
 *
 Convert objects to compact int representations.
 *
 Restore objects from compact representations.
 *
 Provide a factory for creating object attributes.
 *
 Close object sets into finite sets from extensible open sets.
 *
 Provide fast equality comparison on objects.
 *

 *
 * @author Rupert Smith
 */
public class IdAttribute<T> implements OrdinalAttribute, ReferencableAttribute, Serializable
{
    /** Used to hold all the different named attribute classes. */
    private static final Map<String, IdClassImpl> attributeClasses = new HashMap<String, IdClassImpl>();

    /** Used to hold the byte offset into the object array. */
    int value;

    /** Holds a reference to the class of object attribute that this one belongs to. */
    IdClassImpl<T> attributeClass;

    /**
     * Private constructor for an attribute. Attributes can only be created with a factory for an attribute class.
     *
     * @param value          The compact int index that is used to lookup the actual object value in the creating
     *                       factory instance.
     * @param attributeClass The factory implentation used to create this class of object attribute.
     */
    private IdAttribute(int value, IdClassImpl<T> attributeClass)
    {
        // Keep the int representation and the class.
        this.value = value;
        this.attributeClass = attributeClass;
    }

    /**
     * Generates a factory for building object attributes of the named class.
     *
     * @param  name The name of the object attribute class to get a factory for.
     * @param  <T>  The type of the attributes in the attribute class.
     *
     * @return The object attribute factory for the named object attribute class.
     */
    public static <T> IdAttributeFactory<T> getFactoryForClass(String name)
    {
        return IdClassImpl.getInstance(name);
    }

    /**
     * Returns the attribute type of this attribute.
     *
     * @return The attribute type of this attribute.
     */
    public Type<IdAttribute> getType()
    {
        return attributeClass;
    }

    /**
     * Returns an integer index for the current value of this attribute from 0 to num possible values where the number
     * of possible values is finite.
     *
     * @return An integer index for the current value of this attribute from 0 to num possible values where the number
     *         of possible values is finite.
     *
     * @throws com.thesett.aima.state.InfiniteValuesException If the set of values cannot be indexed because it is
     *                                                        infinite or cannot be ordered.
     */
    public int ordinal() throws InfiniteValuesException
    {
        return value;
    }

    /**
     * Returns the integer id of the attribute.
     *
     * @return The integer id of the attribute.
     */
    public long getId()
    {
        // Check if the attribute class has been finalized yet.
        if (attributeClass.finalized)
        {
            // Fetch the object value from the attribute class array of finalized values.
            return attributeClass.lookupValue[value].id;
        }

        // The attribute class has not been finalized yet.
        else
        {
            // Fetch the object value from the attribute class list of unfinalized values.
            return attributeClass.lookupValueList.get(value).id;
        }
    }

    /**
     * Sets the integer id of the attribute. If the attribute class is finalized this will change the value of this
     * attribute to that of the matched id, or raise an exception if no matching id exists. If the attribute class is
     * unfinalized this will change the id value of this attribute within the attribute class to the new id, provided
     * that the id has not already been assigned to another attribute value. If it has been assigned to another
     * attribute value then an exception is raised.
     *
     * @param  id The new id value.
     *
     * @throws IllegalArgumentException If the type is finalized but the id does not exist. Or if the type is
     *                                  unfinalized if the id has already been assigned to another instance of the type.
     */
    public void setId(long id)
    {
        // Find the enumeration node for this enumeration value.
        EnumerationNode<T> node = null;

        // Check if the attribute class has been finalized yet.
        if (attributeClass.finalized)
        {
            // Fetch the object value from the attribute class array of finalized values.
            node = attributeClass.lookupValue[value];
        }

        // The attribute class has not been finalized yet.
        else
        {
            // Fetch the object value from the attribute class list of unfinalized values.
            node = attributeClass.lookupValueList.get(value);
        }

        // Extract the id from it.
        long existingId = node.id;

        // Do nothing if the new id matches the existing one.
        if (id == existingId)
        {
            return;
        }

        // Check if the type is finalized.
        if (attributeClass.finalized)
        {
            // Raise an illegal argument exception if the id is not known.
            IdAttribute newValue = attributeClass.getAttributeFromId(id);

            // Otherwise, change the value of this attribute to that of the new id.
            this.value = newValue.value;
        }

        // The type is unfinalized.
        else
        {
            // Check if another instance of the type already has the id and raise an exception if so.
            EnumerationNode existingNode = attributeClass.idMap.get(id);

            if (existingNode != null)
            {
                throw new IllegalArgumentException("The id value, " + id +
                    ", cannot be set because another instance of this type with that " + "id already exists.");
            }

            // Assign it to this instance if the type is unfinalized. Also removing the old id mapping from the id
            // map and replacing it with the new one.
            node.id = id;
            attributeClass.idMap.remove(existingId);
            attributeClass.idMap.put(id, node);
        }
    }

    /**
     * Gets the object value of a object attribute.
     *
     * @return The underlying object value of this attribute.
     */
    public T getValue()
    {
        // Check if the attribute class has been finalized yet.
        if (attributeClass.finalized)
        {
            // Fetch the object value from the attribute class.
            return attributeClass.lookupValue[value].label;
        }
        else
        {
            return attributeClass.lookupValueList.get(value).label;
        }
    }

    /**
     * Sets the specified object as the value of this attribute. The value to set must be a legitimate member of this
     * attributes type, when the type has been finalized. If the type has yet to be finalized then the new value is
     * added to the set of possible values for the type.
     *
     * @param  value The new value to set.
     *
     * @throws IllegalArgumentException If the type has been finalized and the new value is not a member of the type.
     */
    public void setValue(T value) throws IllegalArgumentException
    {
        Integer b = attributeClass.lookupInt.get(value);

        // Check if the value is not already a memeber of the attribute class.
        if (b == null)
        {
            // Check if the attribute class has been finalized yet.
            if (attributeClass.finalized)
            {
                throw new IllegalArgumentException("The value to set, " + value +
                    ", is not already a member of the finalized IdType, " + attributeClass.attributeClassName + ".");
            }
            else
            {
                // Add the new value to the attribute class. Delegate to the factory to do this so that strings are
                // interned and so on.
                IdAttribute newAttribute = attributeClass.createIdAttribute(value);
                b = newAttribute.value;
            }
        }

        // Set the new value as the value of this attribute.
        this.value = b;
    }

    /**
     * Generates hashCodes unique to the value of the object value.
     *
     * @return A hashcode which is the hashcode for the object of this attributes value.
     */
    public int hashCode()
    {
        return getValue().hashCode();
    }

    /**
     * Tests if two object attributes are equal. They are equal if they have the same object value. They do not have to
     * be of the same object attribute class although the comparator must be a object attribute.
     *
     * @param  o The object to compare to.
     *
     * @return True if the comparator is a object attribute and has the same value as this one.
     */
    public boolean equals(Object o)
    {
        // The == operator can safely be used because the strings were interned.
        return getValue() == ((IdAttribute) o).getValue();
    }

    /**
     * Returns the string value.
     *
     * @return The string value.
     */
    public String toString()
    {
        return getValue().toString();
    }

    /**
     * Defines the type interface for object attributes.
     */
    public static interface IdType<T> extends Type<IdAttribute>
    {
    }

    /**
     * Defines a factory for creating object attributes of a given class.
     */
    public static interface IdAttributeFactory<T>
    {
        /**
         * Creates a object attribute of the class that this is a factory for. If the attribute class has been finalized
         * and the requested object value is not in the class then this should raise an exception.
         *
         * @param  value The value that the object attribute should have.
         *
         * @return A new object attribute with the specified value.
         */
        public IdAttribute<T> createIdAttribute(T value);

        /**
         * Converts a compact int representation of a object attribute into its object representation. If the int is not
         * valid then this should raise an exception.
         *
         * @param  b The compact int index that a object attribute should be created from.
         *
         * @return A object attribute looked up by its int index.
         */
        public IdAttribute<T> getAttributeFromInt(int b);

        /** Finalizes an attribute class. This prevents any new attribute value from being added to the class. */
        public void finalizeAttribute();

        /** Drops an attribute class. The attribute class is explicitly deleted. */
        public void dropAttributeClass();
    }

    /**
     * Defines the class of enumerated object attributes. Class = Type + Factory.
     */
    public static interface IdClass<T> extends IdType<T>, IdAttributeFactory<T>
    {
    }

    /**
     * Used to hold the lookup Object values and the number of possible values for a object attribute class.
     */
    private static class IdClassImpl<T> extends BaseType<IdAttribute> implements IdClass<T>
    {
        /** The number of possible values this attribute can take on. Defaults to infinity. */
        int numValues = -1;

        /** The Object lookup array for converting ints back into Strings for this class. */
        EnumerationNode<T>[] lookupValue;

        /** A map from Strings to ints for use when creating new attributes with strings that already exist. */
        Map<T, Integer> lookupInt = new HashMap<T, Integer>();

        /** Used to hold a collection of values for this attribute until the attribute is finalized. */
        List<EnumerationNode<T>> lookupValueList = new ArrayList<EnumerationNode<T>>();

        /**
         * Holds a map from referencable ids to enumeration nodes. This is used to resolve ids into instances of the
         * attribute type. It is a map rather than an array because the ids do not have to be sequentially assigned from
         * 0 but could be a scattered range of values.
         */
        Map<Long, EnumerationNode> idMap = new HashMap<Long, EnumerationNode>();

        /** Used to indicate whether the class has been finalized yet. */
        boolean finalized;

        /** Used to hold the name of the attribute class that this is a factory for. */
        String attributeClassName;

        /**
         * Builds a new factory for a given class.
         *
         * @param name The name that identifies this class of object attributes.
         */
        private IdClassImpl(String name)
        {
            // Keep a reference to the attribute class and name.
            this.attributeClassName = name;
        }

        /**
         * Gets an instance of this factory implementation for specified class of sting attribute. If the attribute
         * class is a new one, then a new factory instance is created, otherwise an existing factory for the class is
         * used.
         *
         * @param  name The name that identifies this class of object attributes.
         * @param  <T>  The type of the attributes in the attribute class.
         *
         * @return A factory implementation for the named object attribute class.
         */
        public static <T> IdAttributeFactory<T> getInstance(String name)
        {
            // Try to get the attribute class from the map of those that have already been created.
            IdClassImpl<T> attributeClass = attributeClasses.get(name);

            // Check if this is a new class and create it if so.
            if (attributeClass == null)
            {
                attributeClass = new IdClassImpl<T>(name);
                attributeClasses.put(name, attributeClass);
            }

            return attributeClass;
        }

        /**
         * Gets a new default instance of the type. The types value will be set to its default uninitialized value.
         *
         * @return A new default instance of the type.
         */
        public IdAttribute<T> getDefaultInstance()
        {
            throw new NotImplementedException();
        }

        /**
         * Creates a object attribute of the class that this is a factory for.
         *
         * @param  value The object value to create an attribute instance for.
         *
         * @return A new object attribute with the specified value.
         *
         * @throws IllegalArgumentException If the type has been finalized but the value to create an attribute for if
         *                                  not a member of the type.
         */
        public IdAttribute<T> createIdAttribute(T value) throws IllegalArgumentException
        {
            // Check if the value is already in the attribute class.
            Integer i = lookupInt.get(value);

            if (i != null)
            {
                return new IdAttribute<T>(i, this);
            }

            // Check if the attribute class has been finalized yet.
            if (finalized)
            {
                // The value is not already in the attribute class and it is finalized so an attribute cannot be
                // created.
                // return null;
                throw new IllegalArgumentException("The value, " + value +
                    ", is not a member of the finalized IdAttribute class, " + attributeClassName + ".");
            }

            // The attribute class is not finalized yet so work with the list.
            else
            {
                // Work out what position in the list the new value will be at.
                int position = lookupValueList.size();

                // Create a new enumeration node to insert.
                EnumerationNode node = new EnumerationNode();
                node.label = value;
                node.value = position;
                node.id = node.value;

                // Add the new value at the end of the list.
                lookupValueList.add(node);

                // Also add the new value to the map of values.
                lookupInt.put(value, position);

                // Also add the new value to the id map.
                idMap.put(node.id, node);

                // Create a new object attribute from a int representation of the position.
                return new IdAttribute<T>(position, this);
            }
        }

        /**
         * Converts a compact int representation of a object attribute into its object representation. If the int is not
         * valid then this returns null.
         *
         * @param  b The compact int index that a object attribute should be created from.
         *
         * @return A object attribute looked up by its int index.
         */
        public IdAttribute<T> getAttributeFromInt(int b)
        {
            // Check if the attribute class has already been finalized in which case the int is only valid if it is
            // less than num possible values.
            if (finalized && (b >= numValues))
            {
                return null;
            }

            // If the attribute is not finalized the int is only valid if it is less than the attibute list size.
            else if (b >= lookupValueList.size())
            {
                return null;
            }

            return new IdAttribute<T>(b, this);
        }

        /**
         * Converts an id representation of an enumerated attribute into its object representation. If the id is not
         * valid then this will raise an exception.
         *
         * @param  id The id of the enumerated attribute value to fetch.
         *
         * @return An enumerated attribute looked up by its id.
         */
        public IdAttribute<T> getAttributeFromId(long id)
        {
            // The id is only valid if it is in the id map.
            EnumerationNode matchedNode = idMap.get(id);

            // Check that the value exists.
            if (matchedNode == null)
            {
                throw new IllegalArgumentException("The enumerated attribute cannot be generated from the id, " + id +
                    ", because that does not represent an existing value.");
            }

            return new IdAttribute<T>(matchedNode.value, this);
        }

        /** Finalizes an attribute class. This prevents any new attribute value from being added to the class. */
        public void finalizeAttribute()
        {
            // Check if the attribute class has already been finalized.
            if (finalized)
            {
                // Its already been finalized. Do nothing.
                return;
            }

            // Count how many values there are and set this as the possible value count.
            int count = lookupValueList.size();

            numValues = count;

            // Convert the List of values into an array and set the List reference to null.
            lookupValue = lookupValueList.toArray(new EnumerationNode[count]);
            lookupValueList = null;

            // Set the finalized flag.
            finalized = true;
        }

        /** Drops an attribute class. The attribute class is explicitly deleted. */
        public void dropAttributeClass()
        {
            attributeClasses.remove(attributeClassName);
        }

        /**
         * Returns the name of this object attribute class.
         *
         * @return The name of this object attribute class.
         */
        public String getName()
        {
            return attributeClassName;
        }

        /**
         * Gets the underlying Java class that implements the type.
         *
         * @return The underlying Java class that implements the type.
         */
        public Class<IdAttribute> getBaseClass()
        {
            return IdAttribute.class;
        }

        /** {@inheritDoc} */
        public String getBaseClassName()
        {
            return getBaseClass().getName();
        }

        /**
         * Gets the number of possible values that attributes of this object class can take on.
         *
         * @return the number of possible values that an instance of this attribute can take on. If the value is -1 then
         *         this is to be interpreted as infinity.
         */
        public int getNumPossibleValues()
        {
            // Fetch the number of possible values that this attribute class can take.
            return numValues;
        }

        /**
         * Returns all the different values that a object attribute of this class can take on. If the class is finalized
         * this will list all the possible strings in the class. If the class is not yet finalized the set of strings is
         * still open and potentially infinite so this will throw an infinite values exception.
         *
         * @return All the objects that this class of object attribute can take on.
         *
         * @throws InfiniteValuesException If the set of values cannot be listed because the object attribute class is
         *                                 unfinalized.
         */
        public Set<IdAttribute> getAllPossibleValuesSet() throws InfiniteValuesException
        {
            // Check if the attribute class is not yet finalized so an infinte values exception must be thrown.
            if (!finalized)
            {
                throw new InfiniteValuesException("The object attribute class is not finalized yet, " +
                    "so can have infinite values.", null);
            }

            // The attribute class is finalized so return the array of possible values as a set.
            else
            {
                Set<IdAttribute> result = new HashSet<IdAttribute>();

                for (int i = 0; i < numValues; i++)
                {
                    result.add(createIdAttribute(lookupValue[i].label));
                }

                return result;
            }
        }

        /**
         * Returns an iterator over all the different values that a object attribute of this class can take on. If the
         * class is finalized this will list all the possible strings in the class. If the class is not yet finalized
         * the set of strings is still open and potentially infinite so this will throw an infinite values exception.
         *
         * @return An iterator over all the object that this class of object attribute can take on.
         *
         * @throws InfiniteValuesException If the set of values cannot be listed because the object attribute class is
         *                                 unfinalized.
         */
        public Iterator<IdAttribute> getAllPossibleValuesIterator() throws InfiniteValuesException
        {
            return getAllPossibleValuesSet().iterator();
        }
    }

    /**
     * An EnumerationNode captures all the relevant information about a label within an enumeration; its label, its
     * enumerated compact int representation, its referenceable id value.
     */
    private static class EnumerationNode<T> implements Serializable
    {
        /** The label of the node. */
        public T label;

        /** The compact int value of the node. */
        public int value;

        /** The referencable id of the value. */
        public long id;

        /**
         * Outputs all values as a string for debugging.
         *
         * @return All values as a string for debugging.
         */
        public String toString()
        {
            return "IdAttribute: [ label = " + label + ", value = " + value + ", id = " + id + "]";
        }
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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 com.thesett.common.util;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * StringUtils provides some commonly re-used functions on strings.
 *
 *
There are methods to convert between delimited string and array representations. For example, comma seperated
 * strings such as "1,2,3" can be converted to array representations { "1", "2", "3" } and vica versa.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Convert between delimited strings and arrays.
 *

 *
 * @author Rupert Smith
 */
public class StringUtils
{
    /**
     * Converts a string listing sub-strings seperated by a delimeter into an array of strings.
     *
     *
For example, suppose the string, "one,two,three" is presented with the delimter ",", then this will be
     * converted into the string array { "one", "two", "three" }.
     *
     * @param  value The string to convert.
     * @param  delim The delimeter.
     *
     * @return The string converted into an array of strings.
     */
    public static String[] listToArray(String value, String delim)
    {
        List<String> result = new ArrayList<String>();
        StringTokenizer tokenizer = new StringTokenizer(value, delim);

        while (tokenizer.hasMoreTokens())
        {
            result.add(tokenizer.nextToken());
        }

        return result.toArray(new String[result.size()]);
    }

    /**
     * Converts a string listing sub-strings seperated by a comma delimeter into an array of strings.
     *
     * @param  value The string to convert.
     *
     * @return The comma seperated string converted into an array of strings.
     */
    public static String[] commaListToArray(String value)
    {
        return listToArray(value, ",");
    }

    /**
     * Converts an array of strings into a delimeter seperated string.
     *
     * @param  array The array to convert.
     * @param  delim The delimeter to use.
     *
     * @return The array of strings converted into a delimeter seperated string.
     */
    public static String arrayToList(String[] array, String delim)
    {
        String result = "";

        for (int i = 0; i < array.length; i++)
        {
            result += array[i] + ((i == (array.length - 1)) ? "" : delim);
        }

        return result;
    }

    /**
     * Converts an array of strings into a comma seperated string.
     *
     * @param  array The array to convert.
     *
     * @return The array of strings converted into a comma seperated string.
     */
    public static String arrayToCommaList(String[] array)
    {
        return arrayToList(array, ",");
    }

    /**
     * Converts a string to camel case.
     *
     * @param  name The string to convert to camel case.
     *
     * @return The string in camel case.
     */
    public static String toCamelCase(String name)
    {
        String[] parts = name.split("_");
        String result = parts[0];

        for (int i = 1; i < parts.length; i++)
        {
            if (parts[i].length() > 0)
            {
                result += upperFirstChar(parts[i]);
            }
        }

        return result;
    }

    /**
     * Converts a string to camel case with the first letter in uppercase.
     *
     * @param  name The string to convert to camel case.
     *
     * @return The string in camel case.
     */
    public static String toCamelCaseUpper(String name)
    {
        return convertCase(name, "", true, true);
    }

    /**
     * Converts a string to camel case with the first letter in lowercase.
     *
     * @param  name The string to convert to camel case.
     *
     * @return The string in camel case.
     */
    public static String toCamelCaseLower(String name)
    {
        return convertCase(name, "", false, true);
    }

    /**
     * Converts a string to snake case with the first letter in uppercase.
     *
     * @param  name The string to convert to snake case.
     *
     * @return The string in snake case.
     */
    public static String toSnakeCaseUpper(String name)
    {
        return convertCase(name, "_", true, true);
    }

    /**
     * Converts a string to snake case with the first letter in lowercase.
     *
     * @param  name The string to convert to snake case.
     *
     * @return The string in snake case.
     */
    public static String toSnakeCaseLower(String name)
    {
        return convertCase(name, "_", false, false);
    }

    /**
     * Converts a string to kebab case with the first letter in uppercase.
     *
     * @param  name The string to convert to kebab case.
     *
     * @return The string in kebab case.
     */
    public static String toKebabCaseUpper(String name)
    {
        return convertCase(name, "-", true, true);
    }

    /**
     * Converts a string to kebab case with the first letter in lowercase.
     *
     * @param  name The string to convert to kebab case.
     *
     * @return The string in kebab case.
     */
    public static String toKebabCaseLower(String name)
    {
        return convertCase(name, "-", false, false);
    }

    /** Used to track the state of the machine that extracts words from variable names. */
    private enum WordMachineState {
        Initial,
        StartWord,
        ContinueWordCaps,
        ContinueWordLower
    }

    /**
     * Converts string between various case forms such as camel case, snake case or kebab case.
     *
     *
Characters in the string are characterized as whitespace, upper case, or other by the following rules.
     *
     *

     *
 An upper case letter is marked as upper case (U).

     *
 A lower case letter or digit is marked as lower case (L).

     *
 Any other character is marked as whitespace (W).

     *

     *
     *
The string is processed into words by these rules.
     *
     *

     *
 W at the start is discarded.

     *
 U or L begins a word.

     *
 U after the initial character of a word continues the same word, so long as no W or L is encountered.

     *
 L after U or L continues the word, so long as no W is encountered.

     *
 U after L begins a new word.

     *
 W after U or L is discarded and ends the current word.

     *

     *
     * @param value                  The string to convert.
     * @param separator              The separator to place between words.
     * @param firstLetterUpper       <tt>true</tt> iff the first letter should be in capitals.
     * @param firstLetterOfWordUpper <tt>true</tt> iff the first letter of subsequent words should be in capitals.
     *
     * @return The input converted to lower case words separated by spaces, as per the rules described above.
     */
    public static String convertCase(String value, String separator, boolean firstLetterUpper, boolean firstLetterOfWordUpper) {
        final StringBuffer result = new StringBuffer();

        boolean firstWord = true;
        boolean firstLetter = true;
        boolean upper = false;

        WordMachineState state = WordMachineState.Initial;

        Function2<Character, Boolean, StringBuffer> writeChar = new Function2<Character, Boolean, StringBuffer>() {
            public StringBuffer apply(Character nextChar, Boolean upper) {
                if (upper)
                    result.append(Character.toUpperCase(nextChar));
                else
                    result.append(Character.toLowerCase(nextChar));

                return result;
            }
        };

        for (int i = 0; i < value.length(); i++) {
            char nextChar = value.charAt(i);

            if (Character.isUpperCase(nextChar)) {
                switch (state) {
                    case Initial:
                        state = WordMachineState.StartWord;
                        upper = firstLetterOfWordUpper;
                        if (!firstWord) {
                            result.append(separator);
                        }
                        firstWord = false;
                        break;
                    case StartWord:
                    case ContinueWordCaps:
                        state = WordMachineState.ContinueWordCaps;
                        upper = false;
                        break;
                    case ContinueWordLower:
                        state = WordMachineState.StartWord;
                        upper = firstLetterOfWordUpper;
                        result.append(separator);
                        break;
                }

                writeChar.apply(nextChar, (!firstLetter && upper) || (firstLetter & firstLetterUpper));
                firstLetter = false;
            } else if (Character.isLetterOrDigit(nextChar)) {
                switch (state) {
                    case Initial:
                        state = WordMachineState.StartWord;
                        upper = firstLetterOfWordUpper;
                        if (!firstWord) {
                            result.append(separator);
                        }
                        firstWord = false;
                        break;
                    case StartWord:
                    case ContinueWordLower:
                    case ContinueWordCaps:
                        state = WordMachineState.ContinueWordLower;
                        upper = false;
                        break;
                }

                writeChar.apply(nextChar, (!firstLetter && upper) || (firstLetter & firstLetterUpper));
                firstLetter = false;
            } else {
                switch (state) {
                    case Initial:
                        state = WordMachineState.Initial;
                        break;
                    case StartWord:
                    case ContinueWordCaps:
                    case ContinueWordLower:
                        state = WordMachineState.Initial;
                        break;
                }

                upper = false;
            }
        }

        return result.toString();
    }

    /**
     * Converts the first character of a string to upper case.
     *
     * @param  name The string to convert the first character of.
     *
     * @return The string with its first character in upper case.
     */
    public static String upperFirstChar(String name)
    {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    /**
     * Converts the first character of a string to lower case.
     *
     * @param  name The string to convert the first character of.
     *
     * @return The string with its first character in lower case.
     */
    public static String lowerFirstChar(String name)
    {
        return name.substring(0, 1).toLowerCase() + name.substring(1);
    }

    /**
     * Converts a string to camel case.
     *
     * @param  name The string to convert to camel case.
     *
     * @return The string in camel case.
     */
    public String toCamelCaseFunc(String name)
    {
        return toCamelCase(name);
    }

    /**
     * Converts a string to camel case with the first letter in uppercase.
     *
     * @param  name The string to convert to camel case.
     *
     * @return The string in camel case.
     */
    public String toCamelCaseUpperFunc(String name)
    {
        return toCamelCaseUpper(name);
    }

    /**
     * Converts the first character of a string to upper case.
     *
     * @param  name The string to convert the first character of.
     *
     * @return The string with its first character in upper case.
     */
    public String upperFirstCharFunc(String name)
    {
        return upperFirstChar(name);
    }
}


/*
 * 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.hobsoft.microbrowser.selenium;

import java.net.URL;
import java.util.List;

import org.hobsoft.microbrowser.CookieNotFoundException;
import org.hobsoft.microbrowser.Form;
import org.hobsoft.microbrowser.FormNotFoundException;
import org.hobsoft.microbrowser.Link;
import org.hobsoft.microbrowser.MicrodataItem;
import org.hobsoft.microbrowser.spi.AbstractMicrodataDocument;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

import static org.hobsoft.microbrowser.spi.Urls.newUrl;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * {@code MicrodataDocument} adapter to a Selenium {@code WebDriver}.
 */
class SeleniumMicrodataDocument extends AbstractMicrodataDocument
{
	// ----------------------------------------------------------------------------------------------------------------
	// fields
	// ----------------------------------------------------------------------------------------------------------------

	private final WebDriver driver;

	// ----------------------------------------------------------------------------------------------------------------
	// constructors
	// ----------------------------------------------------------------------------------------------------------------

	SeleniumMicrodataDocument(WebDriver driver)
	{
		this.driver = checkNotNull(driver, "driver");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// MicrodataDocument methods
	// ----------------------------------------------------------------------------------------------------------------

	public List<MicrodataItem> getItems(String type)
	{
		List<WebElement> elements = driver.findElements(byItemType(newUrl(type)));

		return Lists.transform(elements, new Function<WebElement, MicrodataItem>()
		{
			public MicrodataItem apply(WebElement element)
			{
				return new SeleniumMicrodataItem(driver, element);
			}
		});
	}

	public String getCookie(String name)
	{
		Cookie cookie = driver.manage().getCookieNamed(name);

		if (cookie == null)
		{
			throw new CookieNotFoundException(name);
		}

		return cookie.getValue();
	}

	// ----------------------------------------------------------------------------------------------------------------
	// Hypermedia methods
	// ----------------------------------------------------------------------------------------------------------------

	public List<Link> getLinks(String rel)
	{
		List<WebElement> elements = driver.findElements(byLink(rel));

		return Lists.transform(elements, new Function<WebElement, Link>()
		{
			public Link apply(WebElement element)
			{
				return new SeleniumLink(driver, element);
			}
		});
	}

	public Form getForm(String name)
	{
		List<WebElement> elements = driver.findElements(byForm(name));

		if (elements.isEmpty())
		{
			throw new FormNotFoundException(name);
		}

		return new SeleniumForm(driver, elements.get(0));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// Unwrappable methods
	// ----------------------------------------------------------------------------------------------------------------

	public <T> T unwrap(Class<T> type)
	{
		checkArgument(WebDriver.class.equals(type), "Cannot unwrap to: %s", type);

		return type.cast(driver);
	}

	// ----------------------------------------------------------------------------------------------------------------
	// private methods
	// ----------------------------------------------------------------------------------------------------------------

	private static By byItemType(URL itemType)
	{
		return By.cssSelector(String.format("[itemscope][itemtype='%s']", itemType));
	}

	private static By byLink(String rel)
	{
		return By.cssSelector(String.format("a[rel='%1$s'], link[rel='%1$s']", rel));
	}

	private static By byForm(String name)
	{
		return By.cssSelector(String.format("form[name='%s']", name));
	}
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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 com.thesett.util.log4j;

import java.io.File;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Logger;

/**
 * LoggingDiagnostic provides a diagnostic string, containing information about the java.util.logging configuration.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Provide a java.util.logging diagnostic string.
 *

 *
 * @author Rupert Smith
 */
public class LoggingDiagnostic
{
    /**
     * Provides a string containing information about the configured logging set up.
     *
     * @return A string containing information about the configured logging set up.
     */
    public static String currentConfiguration()
    {
        StringBuffer rtn = new StringBuffer(1024);

        String loggingConfigClass = System.getProperty("java.util.logging.config.class");
        String loggingConfigFile = System.getProperty("java.util.logging.config.file");
        boolean configClassOK = false;

        if (loggingConfigClass == null)
        {
            rtn.append("No java.util.logging.config.class class is set.\n");
        }
        else
        {
            rtn.append("java.util.logging.config.class is set to '").append(loggingConfigClass).append("'\n");

            try
            {
                Class c = Class.forName(loggingConfigClass);
                c.newInstance();
                rtn.append("This class was loaded and a new instance was sucessfully created.\n");
                configClassOK = true;
            }
            catch (ClassNotFoundException e)
            {
                e = null;
                rtn.append(loggingConfigClass).append(" could not be found.");
            }
            catch (InstantiationException e)
            {
                e = null;
                rtn.append(loggingConfigClass).append(" could not be instantiated.");
            }
            catch (IllegalAccessException e)
            {
                e = null;
                rtn.append(loggingConfigClass).append(" could not be accessed.");
            }
        }

        if (loggingConfigFile == null)
        {
            rtn.append("No java.util.logging.config.file file is set.\n");
        }
        else
        {
            rtn.append("java.util.logging.config.file is set to '").append(loggingConfigFile).append("'\n");

            File loggingFile = new File(loggingConfigFile);
            rtn.append(loggingFile.getAbsolutePath()).append("\n");

            if (!loggingFile.exists() || !loggingFile.isFile())
            {
                rtn.append("This file does NOT EXIST.\n");
            }

            if (loggingConfigClass != null)
            {
                if (configClassOK)
                {
                    rtn.append("This file is ignored because java.util.logging.config.class is set.\n");
                }
            }
        }

        Handler[] handlers = Logger.getLogger("").getHandlers();
        listHandlers(handlers, rtn);

        return rtn.toString();
    }

    /**
     * Lists information about logging handlers.
     *
     * @param  handlers The handlers.
     * @param  buffer   A string buffer to build up the listing in.
     *
     * @return The string buffer to build up the listing in.
     */
    private static StringBuffer listHandlers(Handler[] handlers, StringBuffer buffer)
    {
        for (Handler handler : handlers)
        {
            Class<? extends Handler> handlerClass = handler.getClass();
            Formatter formatter = handler.getFormatter();

            buffer.append("Handler:").append(handlerClass.getName()).append("\n");
            buffer.append("Level:").append(handler.getLevel().toString()).append("\n");

            if (formatter != null)
            {
                buffer.append("Formatter:").append(formatter.getClass().getName()).append("\n");
            }
        }

        return buffer;
    }
}


/*
 * 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.hobsoft.microbrowser.tck;

import org.hobsoft.microbrowser.Control;
import org.hobsoft.microbrowser.ControlGroup;
import org.junit.Test;

import com.squareup.okhttp.mockwebserver.MockResponse;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.url;
import static org.junit.Assert.assertThat;

/**
 * TCK for {@code Control}.
 */
public abstract class ControlTck extends AbstractMicrobrowserTest
{
	// ----------------------------------------------------------------------------------------------------------------
	// getName tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getNameReturnsName()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='text' name='x'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("x")
			.getName();

		assertThat("form control name", actual, is("x"));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getValueWhenHiddenControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='hidden' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("x"));
	}

	@Test
	public void getValueWhenTextControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='text' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("x"));
	}

	@Test
	public void getValueWhenPasswordControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='password' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("x"));
	}

	@Test
	public void getValueWhenCheckedCheckboxControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' checked/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("on"));
	}

	@Test
	public void getValueWhenUncheckedCheckboxControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenValuedCheckedCheckboxControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' value='x' checked/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("x"));
	}

	@Test
	public void getValueWhenValuedUncheckedCheckboxControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenCheckedRadioControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' checked/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("on"));
	}

	@Test
	public void getValueWhenUncheckedRadioControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenValuedCheckedRadioControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' value='x' checked/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("x"));
	}

	@Test
	public void getValueWhenValuedUncheckedRadioControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, isEmptyString());
	}

	@Test
	public void getValueWhenTextAreaControlReturnsInitialValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<textarea name='c'>x</textarea>"
			+ "</form>"
			+ ""));

		String actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c")
			.getValue();

		assertThat("form control value", actual, is("x"));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// setValue tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void setValueWhenHiddenControlThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='hidden' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Cannot set hidden control value: c");

		control.setValue("y");
	}

	@Test
	public void setValueWhenTextControlSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='text' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("y");

		assertThat("form control value", control.getValue(), is("y"));
	}

	@Test
	public void setValueWhenPasswordControlSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='password' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("y");

		assertThat("form control value", control.getValue(), is("y"));
	}

	@Test
	public void setValueWhenCheckboxControlWithCheckedSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("on");

		assertThat("form control value", control.getValue(), is("on"));
	}

	@Test
	public void setValueWhenCheckboxControlWithUncheckedUnsetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' checked/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("");

		assertThat("form control value", control.getValue(), isEmptyString());
	}

	@Test
	public void setValueWhenCheckboxControlWithInvalidValueThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Invalid checkbox value: x");

		control.setValue("x");
	}

	@Test
	public void setValueWhenValuedCheckboxControlWithCheckedSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("x");

		assertThat("form control value", control.getValue(), is("x"));
	}

	@Test
	public void setValueWhenValuedCheckboxControlWithUncheckedUnsetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' value='x' checked/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("");

		assertThat("form control value", control.getValue(), isEmptyString());
	}

	@Test
	public void setValueWhenValuedCheckboxControlWithInvalidValueThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='checkbox' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Invalid checkbox value: y");

		control.setValue("y");
	}

	@Test
	public void setValueWhenRadioControlWithCheckedSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("on");

		assertThat("form control value", control.getValue(), is("on"));
	}

	@Test
	public void setValueWhenRadioControlWithUncheckedThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' checked/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Cannot uncheck radio control");

		control.setValue("");
	}

	@Test
	public void setValueWhenRadioControlWithInvalidValueThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Invalid checkbox value: x");

		control.setValue("x");
	}

	@Test
	public void setValueWhenValuedRadioControlWithCheckedSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("x");

		assertThat("form control value", control.getValue(), is("x"));
	}

	@Test
	public void setValueWhenValuedRadioControlWithCheckedUnsetsOtherValues()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' value='x' checked/>"
			+ "<input type='radio' name='c' value='y'/>"
			+ "</form>"
			+ ""));

		ControlGroup group = newBrowser().get(url(server()))
			.getForm("f")
			.getControlGroup("c");
		group.getControl("y")
			.setValue("y");

		assertThat("form control value", group.getControl("x").getValue(), isEmptyString());
	}

	@Test
	public void setValueWhenValuedRadioControlWithUncheckedThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' value='x' checked/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Cannot uncheck radio control");

		control.setValue("");
	}

	@Test
	public void setValueWhenValuedRadioControlWithInvalidValueThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='radio' name='c' value='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Invalid checkbox value: y");

		control.setValue("y");
	}

	@Test
	public void setValueWhenTextAreaControlSetsValue()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<textarea name='c'>x</textarea>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("c");
		control.setValue("y");

		assertThat("form control value", control.getValue(), is("y"));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// unwrap tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void unwrapWithUnknownTypeThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='text' name='x'/>"
			+ "</form>"
			+ ""));

		Control control = newBrowser().get(url(server()))
			.getForm("f")
			.getControl("x");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Cannot unwrap to: class java.lang.Void");

		control.unwrap(Void.class);
	}
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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 com.thesett.aima.logic.fol;

import com.thesett.aima.logic.fol.compiler.PositionalTermTraverser;
import com.thesett.common.util.doublemaps.SymbolTable;

/**
 * BasePositionalVisitor is an {@link AllTermsVisitor} that is being driven by a {@link PositionalTermTraverser}. It is
 * used as a base class for implementing visitors that need to understand the positional context during visitation.
 *
 *
It uses positional context information from a {@link PositionalTermTraverser}, to determine whether terms are
 * being entered or left, and splits these down into calls on appropriate enter/leave methods. Default no-op
 * implementations of these methods are supplied by this base class and may be extended by specific implementations as
 * needed to figure out the positional context during visitation.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Provide default enter/leave methods for every part of a term.
 *

 *
 * @author Rupert Smith
 */
public class BasePositionalVisitor implements AllTermsVisitor
{
    /** The name interner. */
    protected VariableAndFunctorInterner interner;

    /** The symbol table. */
    protected SymbolTable<Integer, String, Object> symbolTable;

    /** The positional context. */
    protected PositionalTermTraverser traverser;

    /**
     * Creates a positional visitor.
     *
     * @param interner    The name interner.
     * @param symbolTable The compiler symbol table.
     * @param traverser   The positional context traverser.
     */
    public BasePositionalVisitor(VariableAndFunctorInterner interner, SymbolTable<Integer, String, Object> symbolTable,
        PositionalTermTraverser traverser)
    {
        this.interner = interner;
        this.symbolTable = symbolTable;
        this.traverser = traverser;
    }

    /** {@inheritDoc} */
    public void visit(Term term)
    {
        if (traverser.isEnteringContext())
        {
            enterTerm(term);
        }
        else if (traverser.isLeavingContext())
        {
            leaveTerm(term);
            term.setTermTraverser(null);
        }
    }

    /** {@inheritDoc} */
    public void visit(Functor functor)
    {
        if (traverser.isEnteringContext())
        {
            enterFunctor(functor);
        }
        else if (traverser.isLeavingContext())
        {
            leaveFunctor(functor);
            functor.setTermTraverser(null);
        }
    }

    /** {@inheritDoc} */
    public void visit(Variable variable)
    {
        if (traverser.isEnteringContext())
        {
            enterVariable(variable);
        }
        else if (traverser.isLeavingContext())
        {
            leaveVariable(variable);
            variable.setTermTraverser(null);
        }
    }

    /** {@inheritDoc} */
    public void visit(Predicate predicate)
    {
        if (traverser.isEnteringContext())
        {
            enterPredicate(predicate);
        }
        else if (traverser.isLeavingContext())
        {
            leavePredicate(predicate);
            predicate.setTermTraverser(null);
        }
    }

    /** {@inheritDoc} */
    public void visit(Clause clause)
    {
        if (traverser.isEnteringContext())
        {
            enterClause(clause);
        }
        else if (traverser.isLeavingContext())
        {
            leaveClause(clause);
            clause.setTermTraverser(null);
        }
    }

    /** {@inheritDoc} */
    public void visit(IntegerType literal)
    {
        if (traverser.isEnteringContext())
        {
            enterIntLiteral(literal);
        }
        else if (traverser.isLeavingContext())
        {
            leaveIntLiteral(literal);
            literal.setTermTraverser(null);
        }
    }

    /** {@inheritDoc} */
    public void visit(LiteralType literal)
    {
        if (traverser.isEnteringContext())
        {
            enterLiteral(literal);
        }
        else if (traverser.isLeavingContext())
        {
            leaveLiteral(literal);
            literal.setTermTraverser(null);
        }
    }

    /**
     * Called when a term is entered during the visitation.
     *
     * @param term The term being entered.
     */
    protected void enterTerm(Term term)
    {
    }

    /**
     * Called when a term is being left during the visitation.
     *
     * @param term The term being left.
     */
    protected void leaveTerm(Term term)
    {
    }

    /**
     * Called when a functor is entered during the visitation.
     *
     * @param functor The functor being entered.
     */
    protected void enterFunctor(Functor functor)
    {
    }

    /**
     * Called when a functor is being left during the visitation.
     *
     * @param functor The functor being left.
     */
    protected void leaveFunctor(Functor functor)
    {
    }

    /**
     * Called when a variable is entered during the visitation.
     *
     * @param variable The variable being entered.
     */
    protected void enterVariable(Variable variable)
    {
    }

    /**
     * Called when a variable is being left during the visitation.
     *
     * @param variable The variable being left.
     */
    protected void leaveVariable(Variable variable)
    {
    }

    /**
     * Called when a predicate is entered during the visitation.
     *
     * @param predicate The predicate being entered.
     */
    protected void enterPredicate(Predicate predicate)
    {
    }

    /**
     * Called when a predicate is being left during the visitation.
     *
     * @param predicate The predicate being left.
     */
    protected void leavePredicate(Predicate predicate)
    {
    }

    /**
     * Called when a clause is entered during the visitation.
     *
     * @param clause The clause being entered.
     */
    protected void enterClause(Clause clause)
    {
    }

    /**
     * Called when a clause is being left during the visitation.
     *
     * @param clause The clause being left.
     */
    protected void leaveClause(Clause clause)
    {
    }

    /**
     * Called when a integer literal is entered during the visitation.
     *
     * @param literal The integer literal being entered.
     */
    protected void enterIntLiteral(IntegerType literal)
    {
    }

    /**
     * Called when a integer literal is being left during the visitation.
     *
     * @param literal The integer literal being left.
     */
    protected void leaveIntLiteral(IntegerType literal)
    {
    }

    /**
     * Called when a literal is entered during the visitation.
     *
     * @param literal The literal being entered.
     */
    protected void enterLiteral(LiteralType literal)
    {
    }

    /**
     * Called when a literal is being left during the visitation.
     *
     * @param literal The literal being left.
     */
    protected void leaveLiteral(LiteralType literal)
    {
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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 com.thesett.aima.logic.fol.wam.machine;

import java.util.HashMap;
import java.util.Map;

import com.thesett.aima.logic.fol.LinkageException;
import com.thesett.aima.logic.fol.VariableAndFunctorInternerImpl;
import com.thesett.aima.logic.fol.wam.compiler.WAMCallPoint;
import com.thesett.aima.logic.fol.wam.compiler.WAMCompiledPredicate;
import com.thesett.aima.logic.fol.wam.compiler.WAMCompiledQuery;
import com.thesett.aima.logic.fol.wam.compiler.WAMReservedLabel;
import com.thesett.common.util.doublemaps.SymbolTable;

/**
 * WAMBaseMachine provides the basic services common to all WAM machines. This consists of managing the interning name
 * tables for functors and variables, and managing the call table for the entry addresses of procedures.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Provide symbol table for functors names.
 *
 Provide symbol table for variable names.
 *
 Store and retrieve the entry points to byte code procedures.
 *

 *
 * @author Rupert Smith
 */
public abstract class WAMBaseMachine extends VariableAndFunctorInternerImpl implements WAMMachine, WAMCodeView
{
    /** Used for debugging. */
    /* private static final Logger log = Logger.getLogger(WAMBaseMachine.class.getName()); */

    /** The symbol table key for call points. */
    protected static final String SYMKEY_CALLPOINTS = "call_points";

    /** Holds the symbol table. */
    protected SymbolTable<Integer, String, Object> symbolTable;

    /** Holds the reverse symbol table to look up names by addresses. */
    protected Map<Integer, Integer> reverseTable = new HashMap<Integer, Integer>();

    /**
     * Creates the base machine, providing variable and functor symbol tables.
     *
     * @param symbolTable The symbol table.
     */
    protected WAMBaseMachine(SymbolTable<Integer, String, Object> symbolTable)
    {
        super("WAM_Variable_Namespace", "WAM_Functor_Namespace");
        this.symbolTable = symbolTable;
    }

    /** {@inheritDoc} */
    public abstract void emmitCode(WAMCompiledPredicate predicate) throws LinkageException;

    /** {@inheritDoc} */
    public abstract void emmitCode(WAMCompiledQuery query) throws LinkageException;

    /** {@inheritDoc} */
    public abstract void emmitCode(int offset, int address);

    /**
     * Extracts the raw byte code from the machine for a given call table entry.
     *
     * @param  callPoint The call table entry giving the location and length of the code.
     *
     * @return The byte code at the specified location.
     */
    public abstract byte[] retrieveCode(WAMCallPoint callPoint);

    /**
     * Looks up the offset of the start of the code for the named functor.
     *
     * @param  functorName The interned name of the functor to find the start address of the code for.
     *
     * @return The call table entry of the functors code within the code area of the machine, or an invalid address if
     *         the functor is not known to the machine.
     */
    public WAMCallPoint resolveCallPoint(int functorName)
    {
        /*log.fine("public WAMCallPoint resolveCallPoint(int functorName): called");*/

        WAMCallPoint result = (WAMCallPoint) symbolTable.get(functorName, SYMKEY_CALLPOINTS);

        if (result == null)
        {
            result = new WAMCallPoint(-1, 0, functorName);
        }

        return result;
    }

    /** {@inheritDoc} */
    public void reserveReferenceToLabel(int labelName, int offset)
    {
        // Create call point with label name if it does not already exist.
        WAMReservedLabel label = (WAMReservedLabel) symbolTable.get(labelName, SYMKEY_CALLPOINTS);

        if (label == null)
        {
            label = new WAMReservedLabel(labelName);
            symbolTable.put(labelName, SYMKEY_CALLPOINTS, label);
        }

        // Add to the mapping from the label to referenced from addresses to fill in later.
        label.referenceList.add(offset);
    }

    /** {@inheritDoc} */
    public void resolveLabelPoint(int labelName, int address)
    {
        // Create the label with resolved address, if it does not already exist.
        WAMReservedLabel label = (WAMReservedLabel) symbolTable.get(labelName, SYMKEY_CALLPOINTS);

        if (label == null)
        {
            label = new WAMReservedLabel(labelName);
            symbolTable.put(labelName, SYMKEY_CALLPOINTS, label);
        }

        label.entryPoint = address;

        // Fill in all references to the label with the correct value. This does nothing if the label was just created.
        for (Integer offset : label.referenceList)
        {
            emmitCode(offset, label.entryPoint);
        }

        // Keep a reverse lookup from address to label name.
        reverseTable.put(address, labelName);
    }

    /** {@inheritDoc} */
    public Integer getNameForAddress(int address)
    {
        return reverseTable.get(address);
    }

    /**
     * Resets the machine, to its initial state. This should clear any programs from the machine, and clear all of its
     * stacks and heaps.
     */
    public void reset()
    {
        // Clear the entire symbol table.
        symbolTable.clear();
        reverseTable.clear();
    }

    /**
     * Records the offset of the start of the code for the named functor.
     *
     * @param  functorName The interned name of the functor to find the start address of the code for.
     * @param  offset      The offset of the start of the functors code within the code area.
     * @param  length      The size of the code to set the address for.
     *
     * @return The call table entry for the functors code within the code area of the machine.
     */
    protected WAMCallPoint setCodeAddress(int functorName, int offset, int length)
    {
        WAMCallPoint entry = new WAMCallPoint(offset, length, functorName);
        symbolTable.put(functorName, SYMKEY_CALLPOINTS, entry);

        // Keep a reverse lookup from address to functor name.
        reverseTable.put(offset, functorName);

        return entry;
    }

    /**
     * Records the id of an internal function for the named functor. The method name uses the word 'address' but this is
     * not really accurate, the address field is used to hold an id of the internal function to be invoked. This method
     * differs from {@link #setCodeAddress(int, int, int)}, as it does not set the reverse mapping from the address to
     * the functor name, since an address is not really being used.
     *
     * @param  functorName The interned name of the functor to find the start address of the code for.
     * @param  id          The offset of the start of the functors code within the code area.
     *
     * @return The call table entry for the functors code within the code area of the machine.
     */
    protected WAMCallPoint setInternalCodeAddress(int functorName, int id)
    {
        WAMCallPoint entry = new WAMCallPoint(id, 0, functorName);
        symbolTable.put(functorName, SYMKEY_CALLPOINTS, entry);

        return entry;
    }
}