/*
 * 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.prolog;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import com.thesett.aima.logic.fol.Clause;
import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.Resolver;
import com.thesett.aima.logic.fol.Term;
import com.thesett.aima.logic.fol.TermUtils;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.prolog.builtins.BuiltIn;
import com.thesett.aima.logic.fol.prolog.builtins.BuiltInFunctor;
import com.thesett.aima.logic.fol.prolog.builtins.BuiltInTransform;
import com.thesett.aima.logic.fol.prolog.builtins.DefaultBuiltIn;
import com.thesett.aima.logic.fol.prolog.builtins.FirstStepBuiltIn;
import com.thesett.aima.logic.fol.prolog.builtins.PrologUnifier;
import com.thesett.aima.logic.fol.prolog.builtins.ResolutionState;
import com.thesett.aima.search.GoalState;
import com.thesett.aima.search.Operator;
import com.thesett.aima.search.QueueBasedSearchMethod;
import com.thesett.aima.search.RepeatedStateFilter;
import com.thesett.aima.search.SearchNode;
import com.thesett.aima.search.SearchNotExhaustiveException;
import com.thesett.aima.search.Traversable;
import com.thesett.aima.search.TraversableState;
import com.thesett.aima.search.util.OperatorImpl;
import com.thesett.aima.search.util.Searches;
import com.thesett.aima.search.util.backtracking.DepthFirstBacktrackingSearch;
import com.thesett.aima.search.util.backtracking.ReTraversable;
import com.thesett.common.util.EmptyIterator;
import com.thesett.common.util.Filterator;
import com.thesett.common.util.Function;
import com.thesett.common.util.SimpleQueue;
import com.thesett.common.util.Sink;
import com.thesett.common.util.StackQueue;
import com.thesett.common.util.logic.UnaryPredicate;

/**
 * PrologResolver implements the prolog resolution procedure over horn clauses. This consists of unifying query functors
 * against the available clauses, in the order that they are presented.
 *
 *
Resolution is effectively the process of turning queries into variable bindings that resolve the query against
 * the domain. When a matching clause is found, every functor in its body is added to a stack of queries to be resolved,
 * or if it has no body a match is returned immediately. The process continues until the stack of queries to resolve has
 * been exhausted with succesfull bindings.
 *
 *
This Prolog resolution procedure is built on top of a {@link DepthFirstBacktrackingSearch}. The current search
 * state consists of the domain and the query stack with in-place variable bindings. The operators to produce new search
 * states consists of the possible clause heads in the domain that can be unified with the current head of the query
 * stack. When the back-tracking search establishes a search state, the unification of the current query head and the
 * clause head leading to that state takes place. The variable bindings generated by this unification, if any, are
 * noted, so that if the search has to back-track over the state the bindings can be undone. In this implementation, the
 * variable bindings take place directly in the domain and query clauses, so only one path of enquiry, from the root of
 * the query to the current search state, may have its bindings active at any one time. This is guaranteed because the
 * default {@link DepthFirstBacktrackingSearch} uses a depth first ordering, which is the standard ordering for
 * evaluating Prolog.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Resolve a query over a set of Horn clauses.
 *      {@link PrologUnifier}, {@link QueueBasedSearchMethod}, {@link DepthFirstBacktrackingSearch}
 *

 *
 * @author Rupert Smith
 * @todo   How it works. Query on goal. Take possibly matching clause. Attach stack frame to clause. Unify with clause
 *         bindings are placed in the stack frame, clause remains clean and can be re-used. Right hand side of clause
 *         added to goal stack, using 'query conversion'. Goal stack is a stack so unification proceeds depth first,
 *         left-to-right. Default variable/variable binding direction is downwards? Upon back-tracking variable bindings
 *         are undone and the goal stack is restored to its prior state.
 */
public class PrologResolver extends PrologUnifier implements Resolver<PrologCompiledClause, PrologCompiledClause>,
    QueueBasedSearchMethod<ResolutionState, ResolutionState>, Cloneable
{
    /** Used for providing user readable execution traces. */
    /* private static final Logger trace = Logger.getLogger("TRACE.Prolog." + PrologResolver.class.getSimpleName()); */

    /** Flag used as a quick check to see if trace is enabled. */
    private static final boolean TRACE = false; //log.isLoggable(Level.FINE)

    /** Holds the built-in transform function to create built-ins for functors. */
    private static BuiltInTransform builtInTransform;

    /** Holds the indenter to provide neatly indented execution traces. */
    //protected TraceIndenter indenter = new TraceIndenter(true);

    /** Holds the search over the resolution space. */
    protected QueueBasedSearchMethod<ResolutionState, ResolutionState> resolutionSearch =
        new DepthFirstBacktrackingSearch<ResolutionState, ResolutionState>();

    /** Holds the domain being queried over. */
    //protected List<PrologCompiledClause> domain = new LinkedList<PrologCompiledClause>();
    protected Map<Integer, List<PrologCompiledClause>> domain = new HashMap<Integer, List<PrologCompiledClause>>();

    /** Holds the current query if one has been set. */
    protected Clause currentQuery;

    /** Holds the stack of query functors remaining to be resolved. */
    protected Queue<BuiltInFunctor> goalStack;

    /** Holds a list of all variable bindings required to satisfy the most recent query. */
    protected StackQueue<Variable> bindings;

    /** Holds the symbol table for translating interned names, mainly for debugging purposes. */
    protected VariableAndFunctorInterner interner;

    /**
     * Creates a new Prolog resolution engine.
     *
     * @param interner The symbol table for translating interned names, to provide readable debugging output.
     */
    public PrologResolver(VariableAndFunctorInterner interner)
    {
        this.interner = interner;
        builtInTransform = new BuiltInTransform(interner);
    }

    /** {@inheritDoc} */
    public void addToDomain(PrologCompiledClause term)
    {
        List<PrologCompiledClause> predicate = domain.get(term.getHead().getName());

        if (predicate == null)
        {
            predicate = new LinkedList<PrologCompiledClause>();
            domain.put(term.getHead().getName(), predicate);
        }

        predicate.add(term);
    }

    /** {@inheritDoc} */
    public void setQuery(PrologCompiledClause query)
    {
        // Reset the search in order to initialize it.
        resolutionSearch.reset();

        // Keep a reference to the current query.
        currentQuery = query;

        // Create the starting point for the resolution, consisting of the queries to resolve as the intial goal stack,
        // and an empty list of bindings.
        goalStack = new StackQueue<BuiltInFunctor>();

        // Create a fresh list to hold the results of the resolution in.
        bindings = new StackQueue<Variable>();

        // Create the initial state of the proof search.
        ResolutionState initialState = new ResolutionStateImpl(query);
        addStartState(initialState);

        // If printing execution traces, ensure the execution indenter starts from zero.
        /*if (TRACE)
        {
            indenter.reset();
        }*/
    }

    /** {@inheritDoc} */
    public Set<Variable> resolve()
    {
        // Find all free variables in the query.
        Set<Variable> freeVars = TermUtils.findFreeNonAnonymousVariables(currentQuery);

        // Used to collect the results in.
        Set<Variable> results = null;

        // Search for the next available solution.
        SearchNode solution;

        try
        {
            solution = findGoalPath();
        }
        catch (SearchNotExhaustiveException e)
        {
            // The search may fail if the maximum number of search steps is reached. This limit is not turned on by
            // default. If this happens, null is returned, indicating that no solution was found.
            // Exception ignored as empty search results are noted and dealt with by returning null.
            e = null;
            solution = null;
            results = null;
        }

        // Check that a solution was found and return the variable bindings from it if so. Only the variable bindings
        // that were free and non-anonymous in the original query are returned.
        if (solution != null)
        {
            results = new HashSet<Variable>(freeVars);
        }

        return results;
    }

    /** {@inheritDoc} */
    public void setMaxSteps(int max)
    {
        resolutionSearch.setMaxSteps(max);
    }

    /** {@inheritDoc} */
    public void addStartState(ResolutionState startState)
    {
        resolutionSearch.addStartState(startState);
    }

    /**
     * {@inheritDoc}
     *
     *
This implementation defines its own goal predicate that cannot be replaced. Calling this method will always
     * results in an UnsupportedOperationException.
     */
    public void setGoalPredicate(UnaryPredicate<ResolutionState> goalPredicate) throws UnsupportedOperationException
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     *
     *
This also resets the entire knowledge base of this resolver, so that it is empty.
     */
    public void reset()
    {
        // Empty the knowledge base and clear the current query.
        domain = new HashMap<Integer, List<PrologCompiledClause>>();
        currentQuery = null;

        // Reset the underlying search.
        resolutionSearch.reset();

        // If printing execution traces, ensure the execution indenter starts from zero.
        /*if (TRACE)
        {
            indenter.reset();
        }*/
    }

    /** {@inheritDoc} */
    public SearchNode<ResolutionState, ResolutionState> findGoalPath() throws SearchNotExhaustiveException
    {
        return resolutionSearch.findGoalPath();
    }

    /** {@inheritDoc} */
    public ResolutionState search() throws SearchNotExhaustiveException
    {
        SearchNode<ResolutionState, ResolutionState> path = findGoalPath();

        if (path != null)
        {
            return path.getState();
        }
        else
        {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     *
     *
This implementation does not support repeated state filters, so calling this method will always result in an
     * UnsupportedOperationException.
     */
    public void setRepeatedStateFilter(RepeatedStateFilter filter)
    {
        throw new UnsupportedOperationException();
    }

    /** {@inheritDoc} */
    public Iterator<Set<Variable>> iterator()
    {
        // Find all free variables in the query.
        final Set<Variable> freeVars = TermUtils.findFreeNonAnonymousVariables(currentQuery);

        Function<ResolutionState, Set<Variable>> listFunction =
            new Function<ResolutionState, Set<Variable>>()
            {
                public Set<Variable> apply(ResolutionState state)
                {
                    return freeVars;
                }
            };

        return new Filterator<ResolutionState, Set<Variable>>(Searches.allSolutions(this), listFunction);
    }

    /**
     * Prints the contents of this resolver as a string, mainly for debugging purposes.
     *
     * @return The contents of this resolver as a string.
     */
    public String toString()
    {
        return "PrologResolver: [ domain = " + domain + ", currentQuery = " + currentQuery + ", goalStack = " +
            goalStack + ", bindings = " + bindings + " ]";
    }

    /**
     * ResolutionStateImpl models the state space of resolution of a list of queries over a domain. The state space
     * maintains a list of variable bindings, that is built up as queries are matched onto the domain. The queries
     * themselves, form a conjunction of query functors, possibly containing variables, that are to be successively
     * matched against the domain. A state with query functors in it may lead to a statPe with a query removed and
     * replaced by variable bindings that resolve the removed query functor against the domain. A state with no
     * remaining query functors in it, and variable bindings only, has been fully resolved and is a goal state.
     */
    public class ResolutionStateImpl extends TraversableState<ResolutionState> implements GoalState,
        ReTraversable<ResolutionState>, ResolutionState
    {
        /** Holds the clause acting as the choice point that immediately led to this state. */
        private PrologCompiledClause pendingClause;

        /** Holds the resulting from the proof step that immediately led to this state. */
        private BuiltInFunctor pendingGoal;

        /** Holds the parent state that is the most recent choice point that led to this state. */
        private ResolutionStateImpl lastChoicePoint;

        /** Used to indicate whether or not this state has failed to unify with its operator clause. */
        boolean failed;

        /** Holds the stack for this states bound variables. */
        private VariableBindingStackFrame stackFrame;

        /** Holds a state local view of the goal stack, which tracks and can undo goals added by this state. */
        private GoalStack localGoalStack = new GoalStack(goalStack);

        /**
         * Holds a state local view of the variable bindings, which tracks and can undo bindings added by this state.
         */
        private BindingSink localBindings = new BindingSink(bindings);

        /** Used to hold the choice points generated by this state. */
        private Queue<ResolutionState> choicePoints = new StackQueue<ResolutionState>();

        /** Flag used to indicate that this is the first step in a proof. */
        private boolean firstStep;

        /** Flag used to indicate that this state has been cut and should fail. */
        private boolean isCut;

        /**
         * Builds a new resolution start state.
         *
         * @param pendingClause The initial query clause to start the resolution from.
         */
        public ResolutionStateImpl(PrologCompiledClause pendingClause)
        {
            this.pendingClause = pendingClause;
            firstStep = true;
        }

        /**
         * Creates a new resolution state on a new clause or goal. If the state is being set up on a new clause, then
         * the clause will be prepared to unify against when the state is established by the {@link #applyOperator()}
         * method. If the state is being set up on a new goal, then the new goal will be added to the goal queue when
         * the state is established.
         *
         * @param pendingClause   The pending clause to set up for unification when this state is established.
         * @param pendingGoal     The pending goal to add to the goal queue when this state is established.
         * @param lastChoicePoint The parent state that is the most recent choice point that led to this state.
         */
        protected ResolutionStateImpl(PrologCompiledClause pendingClause, BuiltInFunctor pendingGoal,
            PrologResolver.ResolutionStateImpl lastChoicePoint)
        {
            this.pendingClause = pendingClause;
            this.pendingGoal = pendingGoal;
            this.lastChoicePoint = lastChoicePoint;
        }

        /**
         * {@inheritDoc}
         *
         *
When a state contains no queries to resolve, it is a goal state.
         */
        public boolean isGoal()
        {
            boolean isGoal = !failed && goalStack.isEmpty() && choicePoints.isEmpty();

            if (TRACE)
            {
                if (isGoal)
                {
                    /*trace.fine(indenter.generateTraceIndent() +
                        "Goal stack is empty and local choice points is empty, success!");*/
                }
                else if (failed)
                {
                    /*trace.fine(indenter.generateTraceIndent() + "Failed.");*/
                }
            }

            return isGoal;
        }

        /** {@inheritDoc} */
        public Traversable<ResolutionState> getChildStateForOperator(Operator<ResolutionState> op)
        {
            return op.getOp();
        }

        /** {@inheritDoc} */
        public float costOf(Operator op)
        {
            return 1;
        }

        /** {@inheritDoc} */
        public Iterator<Operator<ResolutionState>> validOperators(boolean reverse)
        {
            if (!failed && !choicePoints.isEmpty())
            {
                return new Filterator<ResolutionState, Operator<ResolutionState>>(choicePoints.iterator(),
                    new Function<ResolutionState, Operator<ResolutionState>>()
                    {
                        public Operator<ResolutionState> apply(ResolutionState resolutionState)
                        {
                            return new OperatorImpl<ResolutionState>(resolutionState);
                        }
                    });

            }
            else
            {
                return new EmptyIterator<Operator<ResolutionState>>();
            }
        }

        /** {@inheritDoc} */
        public void applyOperator()
        {
            // Do not establish this state if it has been cut, just fail immediately in that case.
            if (isCut)
            {
                failed = true;

                return;
            }

            // Always boost the trace indent on entering a new state.
            //indenter.generateTraceIndent(1);

            // Used to hold the original choice point clause that led to this state, so that it retained and cleaned
            // up at the end of this method, when the 'choicePointClause' field has been overwritten with the
            // query converted version.
            PrologCompiledClause originalClause = null;

            // The clause that is the choice point that led to this state needs to be 'query converted' before it can
            // be unified against, to ensure that its variables take on fresh storage slots in the stack frame for
            // this choice.
            if ((pendingClause != null) && !firstStep)
            {
                if (TRACE)
                {
                    /*trace.fine(indenter.generateTraceIndent() + "Consume choice point, " +
                        pendingClause.toString(interner, true, true) + ".");*/
                }

                // Keep a reference to the domain clause to be query converted, so that it can be cleaned up.
                originalClause = pendingClause;

                // Set up a stack frame to hold the clauses variable bindings in.
                stackFrame = new VariableBindingStackFrame(pendingClause.getStackSize());
                pendingClause.setBindingContext(stackFrame);

                // Perform query conversion on the clause to match against, to ensure that its stack variable are replaced
                // with their now allocated storage cells on the stack.
                pendingClause = (PrologCompiledClause) pendingClause.queryConversion();
            }

            // If a choice point goal led to this state, the new goal is now added to the state.
            if (pendingGoal != null)
            {
                localGoalStack.offer(pendingGoal);
            }

            // Select which built in operator is the correct one to apply to the current head goal.
            BuiltInFunctor headGoal = localGoalStack.peek();

            // Work out which built-in operator implements the head goal functor, using the default if no specialized
            // built-in is required. On the first proof step, the specail first-step built-in is always used.
            BuiltIn operation;

            if (firstStep)
            {
                operation = new FirstStepBuiltIn();
            }
            else
            {
                operation = headGoal;
            }

            // Attempt to prove the head goal. This operation should consume the goal if it is succesfull, and may
            // create more goals, variable bindings and lead to more choice points to explore.
            failed = !operation.proofStep(this);

            // Check if the successfully proved goal generated any more choice points.
            if (!failed)
            {
                operation.createContinuationStates(this);
            }

            // If the clause that is the choice point that led to this state was query converted to use this states
            // stack frame, clear that storage context from it now as it is no longer needed.
            if (originalClause != null)
            {
                // Clear the stack frame context from the domain clause, so that the clause is clear for re-use.
                // This also removes this states stack frame from being referenced by a domain clause allowing it
                // to be garbage collected.
                originalClause.setBindingContext(null);
            }
        }

        /**
         * {@inheritDoc}
         *
         *
Unbinds variables bound in the query and in the stack frame, and restores the goal stack to its initial
         * state upon entry to this state.
         */
        public void undoOperator()
        {
            // Unbind variables bound by the unification of this states query goal with the possibly matching clause.
            // These bindings are the ones on the query side of the unification.
            localBindings.undo();

            // Unbind variables bound by the unification of this states query goal with the possibly matching clause.
            // These bindings are the ones on the clause side of the unification.
            if (stackFrame != null)
            {
                stackFrame.free();
                stackFrame = null;
            }

            // Restore the goal stack to where it was before this states operator was applied. Sub-goals added to the
            // stack are removed. The goal functors consumed by this state are placed back onto the goal stack.
            localGoalStack.undo();

            // Null out everything this state refers to, to assist/allow garbage collection.
            pendingClause = null;
            pendingGoal = null;
            lastChoicePoint = null;
            stackFrame = null;
            localGoalStack = null;
            localBindings = null;
            choicePoints = null;

            // Always reduce the trace indent on backing out of a state.
            //indenter.generateTraceIndent(-1);
        }

        /** {@inheritDoc} */
        public SimpleQueue<BuiltInFunctor> getGoalStack()
        {
            return localGoalStack;
        }

        /** {@inheritDoc} */
        public Sink<Variable> getVariableBindings()
        {
            return localBindings;
        }

        /** {@inheritDoc} */
        public Function<Functor, BuiltInFunctor> getBuiltInTransform()
        {
            return builtInTransform;
        }

        /** {@inheritDoc} */
        public ResolutionStateImpl getLastChoicePoint()
        {
            return lastChoicePoint;
        }

        /** {@inheritDoc} */
        public void cut()
        {
            isCut = true;
        }

        /** {@inheritDoc} */
        public Queue<ResolutionState> getChoicePoints()
        {
            return choicePoints;
        }

        /** {@inheritDoc} */
        public Clause getCurrentClause()
        {
            return pendingClause;
        }

        /** {@inheritDoc} */
        public PrologUnifier getUnifier()
        {
            return PrologResolver.this;
        }

        /** {@inheritDoc} */
        public VariableAndFunctorInterner getInterner()
        {
            return interner;
        }

        /** {@inheritDoc} */
        public void createContinuationStatesForGoal(BuiltInFunctor goal)
        {
            if (goal != null)
            {
                if (goal instanceof DefaultBuiltIn)
                {
                    List<PrologCompiledClause> predicate = domain.get(goal.getFunctor().getName());

                    if (predicate != null)
                    {
                        for (Clause clause : predicate)
                        {
                            // Perform argument matching on the first three arguments to rule out unnecessary
                            // choice point creations.
                            boolean match = argumentMatch(goal, clause, 0);

                            if (match)
                            {
                                match = argumentMatch(goal, clause, 1);
                            }

                            if (match)
                            {
                                match = argumentMatch(goal, clause, 2);
                            }

                            if (match)
                            {
                                ResolutionState newState =
                                    new ResolutionStateImpl((PrologCompiledClause) clause, null, this);

                                if (TRACE)
                                {
                                    /*trace.fine(indenter.generateTraceIndent() + "Produce choice point, " +
                                        clause.toString(interner, true, true) + ".");*/
                                }

                                choicePoints.offer(newState);
                            }
                        }
                    }
                }
                else
                {
                    getGoalStack().poll();

                    if (TRACE)
                    {
                        /*trace.fine(indenter.generateTraceIndent() + "Produce new state for goal, " +
                            goal.getFunctor().toString(interner, true, true) + ".");*/
                    }

                    choicePoints.offer(new ResolutionStateImpl(null, goal, lastChoicePoint));
                }
            }
        }

        /** {@inheritDoc} */
        /*public TraceIndenter getTraceIndenter()
        {
            return indenter;
        }*/

        /**
         * Prints this state as a string, mainly for debugging purposes.
         *
         * @return This state as a string.
         */
        public String toString()
        {
            return "ResolutionStateImpl: [ choicePointClause = " + pendingClause + ", failed = " + failed +
                ", stackFrame = " + stackFrame + ", localGoalStack = " + localGoalStack + ", localBindings = " +
                localBindings + " ]";
        }

        /**
         * Matches the specified argument of a goal functor against the head of a clause to see if they may potentially
         * unify. If the goal argument is a free variable, or the clause head argument is, they may unify with anything
         * so the result will always be true. If the arguments are functors they are matched by their interned names to
         * see if a potential unfication is going to be possible.
         *
         * @param  goal   The goal functor to match.
         * @param  clause The clause to match the head of.
         * @param  index  The index of the argument to match.
         *
         * @return <tt>true</tt> if the goal may potentially match against the head of the clause, <tt>false</tt> if it
         *         definitely will not.
         */
        private boolean argumentMatch(BuiltInFunctor goal, Clause clause, int index)
        {
            boolean match = false;

            Term firstGoalArg = goal.getFunctor().getArgument(index);
            Term firstClauseArg = clause.getHead().getArgument(index);

            if ((firstGoalArg != null) && (firstClauseArg != null) && (firstGoalArg.getValue() instanceof Functor))
            {
                if (firstClauseArg instanceof Variable)
                {
                    match = true;
                }
                else if (((Functor) firstGoalArg.getValue()).getName() == ((Functor) firstClauseArg).getName())
                {
                    match = true;
                }
            }
            else
            {
                match = true;
            }

            return match;
        }
    }

    /**
     * GoalStack provides a state local view onto a global goal stack. This view provides a local {@link #undo()}
     * operation, that resets the global goal stack to its state at the point in time when this local view was created.
     * As goals are inserted onto the stack, the number of inserted goals is tracked. When goals are consumed from the
     * stack, the consumed goals are kept and the number consumed is counted. Whenever the state of the stack changes
     * from between adding and removing goals, a fresh count is begun with the old count pushed down into a stack. The
     * undo operation removes all added or removed counts from the stack, removing the number of goals added to the
     * stack, or re-inserting the number of goals consumed from the stack. In this way arbitrary sequences of pushes and
     * pops on the stack can be undone.
     */
    private class GoalStack implements SimpleQueue<BuiltInFunctor>
    {
        /** The goal stack to provide a local view onto. */
        private final Queue<BuiltInFunctor> goalStack;

        /** Used to hold any goals consumed by this state, so that they can be restored on undo. */
        private final Queue<BuiltInFunctor> consumedGoals = new StackQueue<BuiltInFunctor>();

        /** Flag used to indicate what state this undo stack is in, if goals are being added or removed. */
        private boolean isGrowing;

        /** Flag used to indicate when the first push or pop on this stack is called to set up the first count. */
        private boolean firstCall = true;

        /** Used to record how many goals this state added, so that they can be removed upon undo. */
        private final Queue<Integer> goalsAddedCountStack = new StackQueue<Integer>();

        /** Used to record how many goals this state consumed, so that they can be removed upon undo. */
        private final Queue<Integer> goalsConsumedCountStack = new StackQueue<Integer>();

        /**
         * Creates a state local view onto a global goal stack.
         *
         * @param goalStack The goal stack to view.
         */
        public GoalStack(Queue<BuiltInFunctor> goalStack)
        {
            this.goalStack = goalStack;

        }

        /**
         * Adds a goal to the stack, tracking how many have been added.
         *
         * @param  goal The goal to add.
         *
         * @return <tt>true</tt> if the goal was added, <tt>false</tt> otherwise.
         */
        public boolean offer(BuiltInFunctor goal)
        {
            boolean result = goalStack.offer(goal);

            if (result)
            {
                if (!isGrowing || firstCall)
                {
                    goalsAddedCountStack.offer(0);
                    firstCall = false;
                }

                int goalsAdded = goalsAddedCountStack.poll();
                goalsAddedCountStack.offer(goalsAdded + 1);
                isGrowing = true;
            }

            if (result && TRACE)
            {
                /*trace.fine(indenter.generateTraceIndent() + "Push goal " + goal + ".");*/
            }

            return result;
        }

        /**
         * Consumes a goal from the stack, locally retaining the consumed goal, so that it may be placed back onto the
         * stack when the {@link #undo()} operation is invoked.
         *
         * @return The head goal on the stack, or <tt>null</tt> if there is none.
         */
        public BuiltInFunctor poll()
        {
            BuiltInFunctor result = goalStack.poll();

            if (result != null)
            {
                if (isGrowing || firstCall)
                {
                    goalsConsumedCountStack.offer(0);
                    firstCall = false;
                }

                consumedGoals.offer(result);

                int goalsConsumed = goalsConsumedCountStack.poll();
                goalsConsumedCountStack.offer(goalsConsumed + 1);
                isGrowing = false;
            }

            if ((result != null) && TRACE)
            {
                /*trace.fine(indenter.generateTraceIndent() + "Pop goal " + result + ".");*/
            }

            return result;
        }

        /**
         * Retrieves, but does not remove, the head of this queue, returning <tt>null</tt> if this queue is empty.
         *
         * @return The head of this queue, or <tt>null</tt> if this queue is empty.
         */
        public BuiltInFunctor peek()
        {
            return goalStack.peek();
        }

        /**
         * Removes the number of added goals from the stack, and places consumed goals back onto the stack. This
         * operation resets the goal stack to its state prior to this local view being established.
         */
        private void undo()
        {
            // Restore the goal stack to where it was before this states operator was applied. Sub-goals added to
            // the stack are removed. The goal functor consumed by this state is placed back onto the goal stack.
            while (!goalsAddedCountStack.isEmpty() || !goalsConsumedCountStack.isEmpty())
            {
                if (isGrowing && !goalsAddedCountStack.isEmpty())
                {
                    int goalsAdded = goalsAddedCountStack.poll();
                    removeAddedGoals(goalsAdded);
                    isGrowing = false;
                }
                else if (!isGrowing && !goalsConsumedCountStack.isEmpty())
                {
                    int goalsConsumed = goalsConsumedCountStack.poll();
                    replaceConsumedGoals(goalsConsumed);
                    isGrowing = true;
                }
            }
        }

        /**
         * Replaces the specified number of goals from the consumed goals stack back onto the wrapped stack.
         *
         * @param goalsConsumed The number of consumed goals to replace.
         */
        private void replaceConsumedGoals(int goalsConsumed)
        {
            for (int i = 0; i < goalsConsumed; i++)
            {
                BuiltInFunctor consumedGoal = consumedGoals.poll();

                goalStack.offer(consumedGoal);

                if (TRACE)
                {
                    /*trace.fine(indenter.generateTraceIndent() + "Backtracking, restore goal " +
                        consumedGoal + ".");*/
                }
            }
        }

        /**
         * Removes the specified number of added goals from the wrapped stack.
         *
         * @param goalsAdded The number of new goals to remove.
         */
        private void removeAddedGoals(int goalsAdded)
        {
            while (goalsAdded > 0)
            {
                BuiltInFunctor functor = goalStack.poll();
                goalsAdded -= (functor != null) ? 1 : 0;
            }
        }
    }

    /**
     * BindingSink provides a local view onto the global variable bindings established by the resolution process. It
     * allows variables bindings established by this state to be added, tracking the number added, so that when the
     * {@link #undo()} operation is called, the bindings can be reset to their state prior to this local view being
     * established. The {@link #undo()} operation frees variables (see {@link Variable#free()} as they are removed.
     */
    private class BindingSink implements Sink<Variable>
    {
        /** Holds the global stack of variable bindings for the resolution. */
        private final Queue<Variable> bindings;

        /**
         * Used to record how many variable bindings this state added, so that they can be removed upon backtracking.
         */
        private int numBindingsAdded;

        /**
         * Establishes a local binding sink on the binding stack.
         *
         * @param bindings The binding stack.
         */
        public BindingSink(Queue<Variable> bindings)
        {
            this.bindings = bindings;
        }

        /**
         * Adds a variable binding to the global binding stack, counting how many are added so that they can be removed
         * by the {@link #undo()} method.
         *
         * @param  variable The bound variable to add.
         *
         * @return <tt>true</tt> if the variable binding was added, <tt>false</tt> otherwise.
         */
        public boolean offer(Variable variable)
        {
            boolean result = bindings.offer(variable);
            numBindingsAdded += result ? 1 : 0;

            return result;
        }

        /** Removes and frees all bound variables added to this local view. */
        public void undo()
        {
            // Restore the variable bindings stack to where it was before this states operator was applied.
            while (numBindingsAdded > 0)
            {
                Variable variable = bindings.poll();

                if (variable != null)
                {
                    variable.free();

                    if (TRACE)
                    {
                        /*trace.fine(indenter.generateTraceIndent() + "Backtracking, unbind " +
                            variable.toString(interner, true, true));*/
                    }

                    numBindingsAdded--;
                }
            }
        }
    }
}


/*
 * 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.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.Properties;

/**
 * PropertiesHelper defines some static methods which are useful when working with properties files.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Read properties from an input stream
 *
Read properties from a file
 *
Read properties from a URL
 *
Read properties given a path to a file
 *
Trim any whitespace from property values
 *

 *
 * @author Rupert Smith
 */
public class PropertiesHelper implements Serializable
{
    /** Used for logging. */
    /* private static final Logger log = Logger.getLogger(PropertiesHelper.class.getName()); */

    /**
     * Get properties from an input stream.
     *
     * @param  is The input stream.
     *
     * @return The properties loaded from the input stream.
     *
     * @throws IOException If the is an I/O error reading from the stream.
     */
    public static Properties getProperties(InputStream is) throws IOException
    {
        /*log.fine("getProperties(InputStream): called");*/

        // Create properties object laoded from input stream
        Properties properties = new Properties();

        properties.load(is);

        return properties;
    }

    /**
     * Get properties from a file.
     *
     * @param  file The file.
     *
     * @return The properties loaded from the file.
     *
     * @throws IOException If there is an I/O error reading from the file.
     */
    public static Properties getProperties(File file) throws IOException
    {
        /*log.fine("getProperties(File): called");*/

        // Open the file as an input stream
        InputStream is = new FileInputStream(file);

        // Create properties object loaded from the stream
        Properties properties = getProperties(is);

        // Close the file
        is.close();

        return properties;
    }

    /**
     * Get properties from a url.
     *
     * @param  url The URL.
     *
     * @return The properties loaded from the url.
     *
     * @throws IOException If there is an I/O error reading from the URL.
     */
    public static Properties getProperties(URL url) throws IOException
    {
        /*log.fine("getProperties(URL): called");*/

        // Open the URL as an input stream
        InputStream is = url.openStream();

        // Create properties object loaded from the stream
        Properties properties = getProperties(is);

        // Close the url
        is.close();

        return properties;
    }

    /**
     * Get properties from a path name. The path name may refer to either a file or a URL.
     *
     * @param  pathname The path name.
     *
     * @return The properties loaded from the file or URL.
     *
     * @throws IOException If there is an I/O error reading from the URL or file named by the path.
     */
    public static Properties getProperties(String pathname) throws IOException
    {
        /*log.fine("getProperties(String): called");*/

        // Check that the path is not null
        if (pathname == null)
        {
            return null;
        }

        // Check if the path is a URL
        if (isURL(pathname))
        {
            // The path is a URL
            return getProperties(new URL(pathname));
        }
        else
        {
            // Assume the path is a file name
            return getProperties(new File(pathname));
        }
    }

    /**
     * Trims whitespace from property values. This method returns a new set of properties the same as the properties
     * specified as an argument but with any white space removed by the {@link java.lang.String#trim} method.
     *
     * @param  properties The properties to trim whitespace from.
     *
     * @return The white space trimmed properties.
     */
    public static Properties trim(Properties properties)
    {
        Properties trimmedProperties = new Properties();

        // Loop over all the properties
        for (Object o : properties.keySet())
        {
            String next = (String) o;
            String nextValue = properties.getProperty(next);

            // Trim the value if it is not null
            if (nextValue != null)
            {
                nextValue.trim();
            }

            // Store the trimmed value in the trimmed properties
            trimmedProperties.setProperty(next, nextValue);
        }

        return trimmedProperties;
    }

    /**
     * Helper method. Guesses whether a string is a URL or not. A String is considered to be a url if it begins with
     * http:, ftp:, or uucp:.
     *
     * @param  name The string to test for being a URL.
     *
     * @return True if the string is a URL and false if not.
     */
    private static boolean isURL(String name)
    {
        return (name.toLowerCase().startsWith("http:") || name.toLowerCase().startsWith("ftp:") ||
            name.toLowerCase().startsWith("uucp:"));
    }
}


/*
 * 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.debugger.swing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.util.SortedMap;

import javax.swing.JComponent;
import javax.swing.Scrollable;
import javax.swing.event.MouseInputAdapter;

import com.thesett.aima.logic.fol.wam.debugger.text.AttributeSet;
import com.thesett.aima.logic.fol.wam.debugger.text.EnhancedTextGrid;
import com.thesett.text.api.TextGridEvent;
import com.thesett.text.api.TextGridListener;

/**
 * JTextGrid is a Swing component that renders the text in an {@link EnhancedTextGrid}.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Render a text grid. 	 {@link EnhancedTextGrid}

 *

 *
 * @author Rupert Smith
 */
public class JTextGrid extends JComponent implements Scrollable
{
    /** Flag to indicate that anti-aliasing and quality rendering should be used. */
    private final boolean useAntiAliasing = true;

    /** The text grid model to render. */
    private EnhancedTextGrid model;

    /** Indicates whether font metrics have been initialized. */
    private boolean fontMetricsInitialized;

    /** The monospaced character width. */
    private int charWidth;

    /** The monospaced character height. */
    private int charHeight;

    /** The monospaced character descent. */
    private int descent;

    /** {@inheritDoc} */
    public Dimension getPreferredSize()
    {
        return computeGridSize();
    }

    /**
     * Accepts a text grid model to render.
     *
     * @param model The text grid model.
     */
    public void setModel(EnhancedTextGrid model)
    {
        this.model = model;

        model.addTextGridListener(new ModelListener());
    }

    /** Sets up standard mouse handling, to translate mouse events from screen coordinates to text grid coordinates. */
    public void initializeStandardMouseHandling()
    {
        MouseHandler mouseHandler = new MouseHandler();
        addMouseListener(mouseHandler);
        addMouseMotionListener(mouseHandler);
    }

    /** {@inheritDoc} */
    public Dimension getPreferredScrollableViewportSize()
    {
        return getPreferredSize();
    }

    /** {@inheritDoc} */
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
    {
        return charHeight;
    }

    /** {@inheritDoc} */
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
    {
        return charHeight * 4;
    }

    /** {@inheritDoc} */
    public boolean getScrollableTracksViewportWidth()
    {
        return false;
    }

    /** {@inheritDoc} */
    public boolean getScrollableTracksViewportHeight()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     *
     *
Renders the text grid model.
     */
    protected void paintComponent(Graphics g)
    {
        Graphics2D graphics2D = (Graphics2D) g.create();
        Rectangle clipRect = (Rectangle) g.getClip();

        // Work out the area to be painted in grid coordinates against the clipping rectangle.
        int startCol = xToCol(clipRect.x);
        int startRow = yToRow(clipRect.y);
        int cols = xToCol(clipRect.x + clipRect.width);
        int rows = yToRow(clipRect.y + clipRect.height);

        graphics2D.setFont(getFont());
        graphics2D.setColor(getBackground());
        graphics2D.fillRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);

        initializeFontMetrics();

        if (useAntiAliasing)
        {
            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        }

        SortedMap<Integer, Integer> hSeps = model.getHorizontalSeparators();
        int hSepOffset = 0;

        for (int row = 0; row <= rows; row++)
        {
            Integer hNextSep = null;

            if (!hSeps.isEmpty())
            {
                hNextSep = hSeps.firstKey();
            }

            if ((hNextSep != null) && (hNextSep == row))
            {
                hSepOffset += hSeps.get(hNextSep);
                hSeps.remove(hNextSep);
            }

            SortedMap<Integer, Integer> vSeps = model.getVerticalSeparators();
            int vSepOffset = 0;

            for (int col = 0; col <= cols; col++)
            {
                Integer vNextSep = null;

                if (!vSeps.isEmpty())
                {
                    vNextSep = vSeps.firstKey();
                }

                if ((vNextSep != null) && (vNextSep == col))
                {
                    int vSepWidth = vSeps.get(vNextSep);
                    vSepOffset += vSepWidth;
                    vSeps.remove(vNextSep);

                    // If adding a vertical separator row attributes should be rendered within it to fill the space
                    // left by the separator.
                    AttributeSet attributes = model.getRowAttribute(row);

                    Color bgColor = (attributes != null) ? (Color) attributes.get(AttributeSet.BACKGROUND_COLOR) : null;
                    bgColor = (bgColor == null) ? getBackground() : bgColor;

                    graphics2D.setColor(bgColor);
                    graphics2D.fillRect(colToX(col) + vSepOffset - vSepWidth, rowToY(row) + hSepOffset,
                        colToX(col) + vSepOffset, charHeight);
                }

                // Only render if within the clip rectangle
                if ((col >= startCol) && (row >= startRow))
                {
                    char character = model.getCharAt(col, row);

                    AttributeSet attributes = model.getAttributeAt(col, row);

                    Color bgColor = (attributes != null) ? (Color) attributes.get(AttributeSet.BACKGROUND_COLOR) : null;
                    bgColor = (bgColor == null) ? getBackground() : bgColor;

                    graphics2D.setColor(bgColor);
                    graphics2D.fillRect(colToX(col) + vSepOffset, rowToY(row) + hSepOffset, charWidth, charHeight);

                    graphics2D.setColor(getForeground());
                    graphics2D.drawString(Character.toString(character), colToX(col) + vSepOffset,
                        (rowToY((row + 1))) - descent + hSepOffset);
                }
            }
        }

        graphics2D.dispose();
    }

    /**
     * Computes the rendered dimensions of the text grid model, on screen. Used for sizing this component.
     *
     * @return The on-screen dimensions of the rendered text grid model.
     */
    protected Dimension computeGridSize()
    {
        int cols = model.getWidth();
        int rows = model.getHeight();

        int horizSeparatorSize = 0;

        for (int size : model.getHorizontalSeparators().values())
        {
            horizSeparatorSize += size;
        }

        int vertSeparatorSize = 0;

        for (int size : model.getVerticalSeparators().values())
        {
            vertSeparatorSize += size;
        }

        return new Dimension(vertSeparatorSize + colToX(cols), horizSeparatorSize + rowToY(rows));
    }

    /**
     * Sets up metrics relating to the size of the font used to display the text grid. This only needs to be done once
     * but this method can be called many times as it is guarded by an initialization flag to prevent these being
     * calculated many times.
     */
    private void initializeFontMetrics()
    {
        if (!fontMetricsInitialized)
        {
            FontMetrics fontMetrics = getFontMetrics(getFont());
            charWidth = fontMetrics.charWidth(' ');
            charHeight = fontMetrics.getHeight();
            descent = fontMetrics.getDescent();
            fontMetricsInitialized = true;
        }
    }

    /**
     * Converts a column within the text grid space to an x coordinate within the component space.
     *
     * @param  col The column to convert to an x coordinate.
     *
     * @return The corresponding x coordinate within the component space.
     */
    private int colToX(int col)
    {
        return col * charWidth;
    }

    /**
     * Converts a row within the text grid space to an y coordinate within the component space.
     *
     * @param  row The row to convert to an y coordinate.
     *
     * @return The corresponding y coordinate within the component space.
     */
    private int rowToY(int row)
    {
        return row * charHeight;
    }

    /**
     * Converts an x coordinate within the component space to a column within the text grid space.
     *
     * @param  x The x coordinate to convert to a column.
     *
     * @return The corresponding column in the text grid space.
     */
    private int xToCol(int x)
    {
        return x / charWidth;
    }

    /**
     * Converts a y coordinate within the component space to a row within the text grid space.
     *
     * @param  y The y coordinate to convert to a row.
     *
     * @return The corresponding row in the text grid space.
     */
    private int yToRow(int y)
    {
        return y / charHeight;
    }

    /**
     * Listens for changes to the model, and initiates a revalidate and repaint. Revalidate is needed because the size
     * of the table may change, which will change the components preferred size. Repaint is needed to force re-rendering
     * the tables contents.
     */
    private class ModelListener implements TextGridListener
    {
        /** {@inheritDoc} */
        public void changedUpdate(TextGridEvent event)
        {
            JTextGrid.this.revalidate();
            JTextGrid.this.repaint();
        }
    }

    /**
     * MouseHandler translates mouse events on the UI component, into row/column coordinates in the text grid space.
     */
    private class MouseHandler extends MouseInputAdapter
    {
        /** The current row that the mouse has moved to. */
        int curRow = -1;

        /** The current column that the mouse has moved to. */
        int curCol = -1;

        /** Creates the mouse handler for mouse activity within the text grid. */
        private MouseHandler()
        {
            JTextGrid.this.initializeFontMetrics();
        }

        /** {@inheritDoc} */
        public void mousePressed(MouseEvent e)
        {
            model.select(yToRow(e.getY()), xToCol(e.getX()));
        }
    }
}


/*
 * 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.state;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.swing.SwingUtilities;

/**
 * BaseState defines a mechanism for beans to register property change listeners and to notify the listeners by sending
 * them a property change event on the Swing event queue.
 *
 *
This class can be extended to build any class that needs to notify property change listeners of changes to its
 * state.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Register poperty change listeners
 *
Notify property change listeners of any changes to state
 *

 *
 * @author Rupert Smith
 * @todo   Consider how this class differs from java.util.Observable. Does it really offer anything that that does not
 *         already? Perhaps make this an interface rather than a class and then have a change notifier class that the
 *         observable interface gets attached to to actually implement the propery change notifications. This class
 *         would implement the interface as an abstract base class for extending to produce observable beans. Some of
 *         its code would also be used by the notifier class to which plain implementations of the interface can be
 *         attached to take their observable implementation from without the restriction of having to extend this.
 *         Perhaps Observer is a better interface to partner this with than PropertyChangeListener as
 *         PropertyChangeListener comes from the swing package implying some sort of swing dependency of this class
 *         which is not really swing dependant at all.
 * @todo   Add in per property stuff.
 */
public abstract class BaseState
{
    /** Used for logging. */
    /* private static final Logger log = Logger.getLogger(BaseState.class.getName()); */

    /** List of property change listeners. */
    private Collection listeners = new ArrayList();

    /**
     * Adds a property changed listener to be notified of changes to the application state.
     *
     * @param l The listener.
     */
    public void addPropertyChangeListener(PropertyChangeListener l)
    {
        // Check if the listneres list has been initialized
        if (listeners == null)
        {
            // Listeneres list not intialized so create a new list
            listeners = new ArrayList();
        }

        synchronized (listeners)
        {
            // Add the new listener to the list
            listeners.add(l);
        }
    }

    /**
     * Adds a property changed listener to be notified of changes to the named property.
     *
     * @param l The listener/
     * @param p The property (ignored, notifies of changes to any properties).
     */
    public void addPropertyChangeListener(String p, PropertyChangeListener l)
    {
        // Check if the listeneres list has been initialized
        if (listeners == null)
        {
            // Listeneres list not initialized so create a new list
            listeners = new ArrayList();
        }

        synchronized (listeners)
        {
            // Add the new listener to the list
            listeners.add(l);
        }
    }

    /**
     * Removes the specified property change listener from the list of active listeners.
     *
     * @param l The property change listener to remove.
     */
    public void removePropertyChangeListener(PropertyChangeListener l)
    {
        if (listeners == null)
        {
            return;
        }

        synchronized (listeners)
        {
            listeners.remove(l);
        }
    }

    /**
     * Removes the specified property change listener from the list of active listeners.
     *
     * @param p Ignored. In future will be used to name the specific property to remove the listener for.
     * @param l The property change listener to remove.
     */
    public void removePropertyChangeListener(String p, PropertyChangeListener l)
    {
        if (listeners == null)
        {
            return;
        }

        synchronized (listeners)
        {
            listeners.remove(l);
        }
    }

    /**
     * Determines if the application state has any property change listeners.
     *
     * @param  prop Ignored but will be used in future to check for listeners for a particular property.
     *
     * @return True if there are listeners, false otherwise.
     */
    public boolean hasListeners(String prop)
    {
        // If the listeners list is not inialized then there are no listeners
        if (listeners == null)
        {
            return false;
        }

        synchronized (listeners)
        {
            // If the listeners list is empty then there are no listeners
            return listeners.isEmpty();
        }
    }

    /** Triggers notification of all property change listeners.. */
    protected void firePropertyChange()
    {
        firePropertyChange(new PropertyChangeEvent(this, null, null, null));
    }

    /**
     * Notifies all property change listeners of the given PropertyChangeEvent.
     *
     * @param evt The property change event to pass to all listeners.
     */
    protected void firePropertyChange(PropertyChangeEvent evt)
    {
        /*log.fine("firePropertyChange: called");*/

        // Take a copy of the event as a final variable so that it can be used in an inner class
        final PropertyChangeEvent finalEvent = evt;

        Iterator it;

        // Check if the list of listeners is empty
        if (listeners == null)
        {
            // There are no listeners so simply return without doing anything
            return;
        }

        // synchronize on the list of listeners to prevent comodification
        synchronized (listeners)
        {
            // Cycle through all listeners and notify them
            it = listeners.iterator();

            while (it.hasNext())
            {
                // Get the next listener from the list
                final PropertyChangeListener l = (PropertyChangeListener) it.next();

                // Notify the listener of the property change event
                Runnable r =
                    new Runnable()
                    {
                        public void run()
                        {
                            // Fire a property change event
                            l.propertyChange(finalEvent);
                        }
                    };

                // Run the property change event in the Swing event queue
                SwingUtilities.invokeLater(r);
            }
        }
    }
}


/*
 * 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.HashMap;
import java.util.Iterator;
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.RandomInstanceFactory;
import com.thesett.aima.state.Type;

/**
 * Implements a double range type. This allows an interval of doubles to be defined as a named type.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Maintain a set of named double range types.
 *
 Provide a default value that lies in the range.
 *
 Check a double value against the range type.
 *

 *
 * @author Rupert Smith
 */
public class DoubleRangeType extends BaseType<Double> implements Type<Double>, RandomInstanceFactory<Double>,
    Serializable
{
    /** Holds all of the named double range types that have been defined. */
    private static final Map<String, DoubleRangeType> DOUBLE_RANGE_TYPES = new HashMap<String, DoubleRangeType>();

    /** Holds the minimum value an instance of this type can take. */
    private final double minValue;

    /** Holds the maximum value an instance of this type can take. */
    private final double maxValue;

    /** Used to hold the name of this double range type. */
    private final String typeName;

    /**
     * Creates an instance of a double range type with the specified name and min and max values.
     *
     * @param name The name of the type.
     * @param min  The minimum value an instance of the type can take.
     * @param max  The maximum value an instance of the type can take.
     */
    private DoubleRangeType(String name, double min, double max)
    {
        typeName = name;
        minValue = min;
        maxValue = max;
    }

    /**
     * Gets the named double range type, if one exists.
     *
     * @param  name The name of the double range type to get.
     *
     * @return The named double range type, or <tt>null</tt> if none matching the name exists.
     */
    public static DoubleRangeType getInstance(String name)
    {
        synchronized (DOUBLE_RANGE_TYPES)
        {
            return DOUBLE_RANGE_TYPES.get(name);
        }
    }

    /**
     * Creates a new double range type with the specified name, if it does not already exist.
     *
     * @param  name The name of the double range type to create.
     * @param  min  The minimum value that an instance of the type can take.
     * @param  max  The maximum value that an instance of teh type can take.
     *
     * @return A new double range type.
     *
     * @throws IllegalArgumentException If min is not less than or equal to max, or the named type already exists.
     */
    public static Type createInstance(String name, double min, double max)
    {
        // Ensure that min is less than or equal to max.
        if (min > max)
        {
            throw new IllegalArgumentException("'min' must be less than or equal to 'max'.");
        }

        synchronized (DOUBLE_RANGE_TYPES)
        {
            // Add the newly created type to the map of all types.
            DoubleRangeType newType = new DoubleRangeType(name, min, max);

            // Ensure that the named type does not already exist, unless it has an identical definition already, in which
            // case the old definition can be re-used and the new one discarded.
            DoubleRangeType oldType = DOUBLE_RANGE_TYPES.get(name);

            if ((oldType != null) && !oldType.equals(newType))
            {
                throw new IllegalArgumentException("The type '" + name + "' already exists and cannot be redefined.");
            }
            else if ((oldType != null) && oldType.equals(newType))
            {
                return oldType;
            }
            else
            {
                DOUBLE_RANGE_TYPES.put(name, newType);

                return newType;
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     *
The default value of a double range type is zero when zero lies between 'min' and 'max', otherwise it is
     * 'min'.
     */
    public Double getDefaultInstance()
    {
        return ((0 >= minValue) && (0 <= maxValue)) ? 0 : minValue;
    }

    /**
     * {@inheritDoc}
     *
     *
A random value of this type lies between 'min' and 'max'.
     */
    public Double createRandomInstance()
    {
        return random.nextDouble() + (minValue * (maxValue - minValue));
    }

    /** {@inheritDoc} */
    public String getName()
    {
        return typeName;
    }

    /** {@inheritDoc} */
    public Class<Double> getBaseClass()
    {
        return Double.class;
    }

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

    /** {@inheritDoc} */
    public int getNumPossibleValues()
    {
        return -1;
    }

    /**
     * Checks a double value against this type, to determine if it is a valid instance of the type.
     *
     * @param  value The value to check.
     *
     * @return <tt>true</tt> if the value lies in the types range, <tt>false</tt> if it does not.
     */
    public boolean isInstance(double value)
    {
        return (value >= minValue) && (value <= maxValue);
    }

    /** {@inheritDoc} */
    public Set<Double> getAllPossibleValuesSet() throws InfiniteValuesException
    {
        throw new InfiniteValuesException("DoubleRangeType has too many values to enumerate.", null);
    }

    /** {@inheritDoc} */
    public Iterator<Double> getAllPossibleValuesIterator() throws InfiniteValuesException
    {
        throw new InfiniteValuesException("DoubleRangeType has too many values to enumerate.", null);
    }

    /**
     * Checks two float range types for equality, based on their name and float range.
     *
     * @param  o The object to compare to.
     *
     * @return <tt>true</tt> if the comparator is an double range type with the same name and range as this one.
     */
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }

        if (!(o instanceof DoubleRangeType))
        {
            return false;
        }

        DoubleRangeType that = (DoubleRangeType) o;

        return (Double.compare(that.maxValue, maxValue) == 0) && (Double.compare(that.minValue, minValue) == 0) &&
            !((typeName != null) ? (!typeName.equals(that.typeName)) : (that.typeName != null));
    }

    /**
     * Provides a hashcode based on the name, and double range of this type.
     *
     * @return A hashcode based on the name, and double range of this type.
     */
    public int hashCode()
    {
        long result;

        result = ((minValue != +0.0d) ? Double.doubleToLongBits(minValue) : 0);
        result = (31 * result) + ((maxValue != +0.0f) ? Double.doubleToLongBits(maxValue) : 0);
        result = (31 * result) + ((typeName != null) ? typeName.hashCode() : 0);

        return (int) (result ^ (result >>> 32));
    }
}


/*
 * 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.builtins;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.Term;
import com.thesett.aima.logic.fol.wam.compiler.DefaultBuiltIn;
import static com.thesett.aima.logic.fol.wam.compiler.SymbolTableKeys.SYMKEY_PERM_VARS_REMAINING;
import com.thesett.aima.logic.fol.wam.compiler.WAMInstruction;
import com.thesett.common.util.SizeableLinkedList;

/**
 * Conjunction implements the Prolog conjunction operator ',' that sets up multiple choice points potentially leading to
 * multiple solutions.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Implement the conjunction operator.
 *

 *
 * @author Rupert Smith
 */
public class Conjunction extends BaseBuiltIn
{
    /**
     * Creates a cut built-in to implement the specified functor.
     *
     * @param functor        The functor to implement as a built-in.
     * @param defaultBuiltIn The default built in, for standard compilation and interners and symbol tables.
     */
    public Conjunction(Functor functor, DefaultBuiltIn defaultBuiltIn)
    {
        super(functor, defaultBuiltIn);
    }

    /** {@inheritDoc} */
    public SizeableLinkedList<WAMInstruction> compileBodyArguments(Functor functor, boolean isFirstBody,
        FunctorName clauseName, int bodyNumber)
    {
        SizeableLinkedList<WAMInstruction> result = new SizeableLinkedList<WAMInstruction>();
        SizeableLinkedList<WAMInstruction> instructions;

        Term[] expressions = functor.getArguments();

        for (int i = 0; i < expressions.length; i++)
        {
            Functor expression = (Functor) expressions[i];

            Integer permVarsRemaining =
                (Integer) defaultBuiltIn.getSymbolTable().get(expression.getSymbolKey(), SYMKEY_PERM_VARS_REMAINING);

            // Select a non-default built-in implementation to compile the functor with, if it is a built-in.
            BuiltIn builtIn;

            if (expression instanceof BuiltIn)
            {
                builtIn = (BuiltIn) expression;
            }
            else
            {
                builtIn = defaultBuiltIn;
            }

            // The 'isFirstBody' parameter is only set to true, when this is the first functor of a rule.
            instructions = builtIn.compileBodyArguments(expression, false, clauseName, bodyNumber);
            result.addAll(instructions);

            // Call the body. The number of permanent variables remaining is specified for environment trimming.
            instructions = builtIn.compileBodyCall(expression, false, false, false, 0 /*permVarsRemaining*/);
            result.addAll(instructions);
        }

        return result;
    }

    /** {@inheritDoc} */
    public SizeableLinkedList<WAMInstruction> compileBodyCall(Functor expression, boolean isFirstBody,
        boolean isLastBody, boolean chainRule, int permVarsRemaining)
    {
        return new SizeableLinkedList<WAMInstruction>();
    }

    /**
     * Creates a string representation of this functor, mostly used for debugging purposes.
     *
     * @return A string representation of this functor.
     */
    public String toString()
    {
        return "Conjunction: [ arguments = " + toStringArguments() + " ]";
    }
}


/*
 * 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.debugger.uifactory.impl;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.swing.*;

import com.thesett.aima.logic.fol.wam.debugger.uifactory.ColorDelta;
import com.thesett.aima.logic.fol.wam.debugger.uifactory.ColorInterpolator;
import com.thesett.aima.logic.fol.wam.debugger.uifactory.Fader;

/**
 * FaderImpl animates color fades by feeding color changes to a {@link ColorDelta} that is under the control of a timer.
 * A FaderImpl is setup up to fade between two colors, and can then be invoked against a target to apply a timed fade.
 *
 *
Fade also supports the concepts of 'grouping' fades by name. If the group name of two fade requests match, the
 * second request will stop the first one, and re-use its timer. A group name not matching any currently running fade,
 * will run under its own timer. This scheme allows the FaderImpl to support multiple fades running in parallel or to
 * restrict fades within a group to run only one at a time.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Apply a color fade between two colors. 	 {#link ColorDelta}

 *

 *
 * @author Rupert Smith
 */
public class FaderImpl implements Fader<Color>, ActionListener
{
    /** The color to start at. */
    private final Color startColor;

    /** The color to finish at. */
    private final Color endColor;

    /** The internal fade states by group name. */
    private final Map<String, FadeState> timers = new ConcurrentHashMap<String, FadeState>();

    /**
     * Builds a color fader between two colors.
     *
     * @param startColor The color to start at.
     * @param endColor   The color to finish at.
     */
    public FaderImpl(Color startColor, Color endColor)
    {
        this.startColor = startColor;
        this.endColor = endColor;
    }

    /** {@inheritDoc} */
    public void actionPerformed(ActionEvent e)
    {
        String groupName = e.getActionCommand();
        FadeState fadeState = timers.get(groupName);

        if (fadeState.interpolator.hasNext())
        {
            Color color = fadeState.interpolator.next();

            fadeState.target.changeColor(color);
            fadeState.timer.setInitialDelay(0);
            fadeState.timer.restart();
        }
    }

    /**
     * Requests a color fade against the specified target, under a group name.
     *
     * @param target    The color delta target.
     * @param groupName The group name for the fade. See class level comment for {@link FaderImpl} for an explanation of
     *                  how the group name is used.
     */
    public void doFade(ColorDelta target, String groupName)
    {
        FadeState fadeState = timers.get(groupName);

        // Set up the color interpolator.
        Iterator<Color> interpolator = new ColorInterpolator(startColor, endColor, 8).iterator();

        if (fadeState == null)
        {
            // Create a new fade state for the target group, and a timer to run it.
            Timer timer = new Timer(20, this);

            fadeState = new FadeState(timer, target, interpolator);
            timers.put(groupName, fadeState);
        }
        else
        {
            // Kill any previous fade and replace the target with the new one.
            fadeState.timer.stop();
            fadeState.target = target;
            fadeState.interpolator = interpolator;
        }

        // Iterate to the initial color.
        Color firstColor = fadeState.interpolator.next();
        fadeState.target.changeColor(firstColor);

        // Kick off the fade timer.
        fadeState.timer.setActionCommand(groupName);
        fadeState.timer.setInitialDelay(400);
        fadeState.timer.start();
    }

    /**
     * Holds the current fade state for a fade group.
     */
    private static class FadeState
    {
        /** The timer to time the next step in the fade. */
        Timer timer;

        /** The target to apply the color change to. */
        ColorDelta target;

        /** The color interpolator to provide the sequence of colors to apply. */
        Iterator<Color> interpolator;

        /**
         * Creates a fade state.
         *
         * @param timer        The timer.
         * @param target       The color delta target.
         * @param interpolator The color interpolator.
         */
        private FadeState(Timer timer, ColorDelta target, Iterator<Color> interpolator)
        {
            this.timer = timer;
            this.target = target;
            this.interpolator = interpolator;
        }
    }
}


/*
 * 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.support.mockwebserver;

import java.nio.charset.Charset;

import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.core.CombinableMatcher;

import com.squareup.okhttp.mockwebserver.RecordedRequest;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

/**
 * Hamcrest matchers for MockWebServer.
 */
public final class MockWebServerMatchers
{
	// ----------------------------------------------------------------------------------------------------------------
	// constructors
	// ----------------------------------------------------------------------------------------------------------------

	private MockWebServerMatchers()
	{
		throw new AssertionError();
	}

	// ----------------------------------------------------------------------------------------------------------------
	// public methods
	// ----------------------------------------------------------------------------------------------------------------

	public static CombinableMatcher<RecordedRequest> get(String expectedPath)
	{
		return recordedRequest("GET", expectedPath);
	}

	public static CombinableMatcher<RecordedRequest> post(String expectedPath)
	{
		return recordedRequest("POST", expectedPath);
	}

	public static CombinableMatcher<RecordedRequest> post(String expectedPath, String expectedBody)
	{
		return post(expectedPath).and(body(expectedBody));
	}

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

	private static CombinableMatcher<RecordedRequest> recordedRequest(String expectedMethod, String expectedPath)
	{
		return Matchers.<RecordedRequest>both(hasProperty("method", is(expectedMethod)))
			.and(hasProperty("path", is(expectedPath)));
	}

	private static Matcher<RecordedRequest> body(String expectedBody)
	{
		return new FeatureMatcher<RecordedRequest, String>(is(expectedBody), "body", "body")
		{
			@Override
			protected String featureValueOf(RecordedRequest request)
			{
				return request.getBody().readString(Charset.forName("ISO-8859-1"));
			}
		};
	}
}


/*
 * 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;

/**
 * NumericLiteral extends the basic syntax of first order logic into arithmetic. Although this seems like a natural and
 * usefull extension, it significantly alters the shape of the mathematical space occupied by the language by
 * introducing the possibilities of infinity and induction. These change the language from a decidable one (in which all
 * true statement can be proven) to an undecidable one.
 *
 *
It is worth noing that the numerical types based on primitive Java or machine register types, are all of finite
 * size, therefore infinity is not really introduced by these types. A big integer type would certainly introduce it.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *

 *
 * @author Rupert Smith
 */
public abstract class NumericType extends LiteralType implements Term
{
    /**
     * Gets the value of the number converted to an int.
     *
     * @return The value of the number as int.
     */
    public abstract int intValue();

    /**
     * Gets the value of the number converted to a long.
     *
     * @return The value of the number as long.
     */
    public abstract long longValue();

    /**
     * Gets the value of the number converted to a float.
     *
     * @return The value of the number as float.
     */
    public abstract float floatValue();

    /**
     * Gets the value of the number converted to a double.
     *
     * @return The value of the number as double.
     */
    public abstract double doubleValue();

    /**
     * Determines whether a number is an integer.
     *
     * @return <tt>true</tt> if a number is an integer, <tt>false</tt> otherwise.
     */
    public boolean isInteger()
    {
        return false;
    }

    /**
     * Determines whether a number is a real.
     *
     * @return <tt>true</tt> if a number is a real, <tt>false</tt> otherwise.
     */
    public boolean isReal()
    {
        return false;
    }

    /**
     * Determines whether a number is of basic type int.
     *
     * @return <tt>true</tt> if a number is an int, <tt>false</tt> otherwise.
     */
    public boolean isInt()
    {
        return false;
    }

    /**
     * Determines whether a number is of basic type long.
     *
     * @return <tt>true</tt> if a number is a long, <tt>false</tt> otherwise.
     */
    public boolean isLong()
    {
        return false;
    }

    /**
     * Determines whether a number is of basic type float.
     *
     * @return <tt>true</tt> if a number is a float, <tt>false</tt> otherwise.
     */
    public boolean isFloat()
    {
        return false;
    }

    /**
     * Determines whether a number is of basic type double.
     *
     * @return <tt>true</tt> if a number is a double, <tt>false</tt> otherwise.
     */
    public boolean isDouble()
    {
        return false;
    }

    /**
     * Reports whether or not this term is a number.
     *
     * @return Always <tt>true</tt>.
     */
    public boolean isNumber()
    {
        return true;
    }

    /**
     * Gets the actual value of a term, which is either the term itself, or in the case of variables, the value that is
     * currently assigned to the variable.
     *
     * @return The term itself.
     */
    public Term getValue()
    {
        return this;
    }

    /**
     * Frees all assigned variables in the term, leaving them unnassigned. Numbers are not variables, so this method
     * does nothing.
     */
    public void free()
    {
    }

    /** {@inheritDoc} */
    public void accept(TermVisitor visitor)
    {
        if (visitor instanceof NumericTypeVisitor)
        {
            ((NumericTypeVisitor) visitor).visit(this);
        }
        else
        {
            super.accept(visitor);
        }
    }
}


/*
 * 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.debugger.uifactory.impl;

import java.awt.Component;
import java.awt.Dimension;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;

import com.thesett.aima.logic.fol.wam.debugger.swing.DesktopAppLayout;
import com.thesett.aima.logic.fol.wam.debugger.uifactory.ComponentFactory;
import com.thesett.aima.logic.fol.wam.debugger.uifactory.MainWindow;
import com.thesett.aima.logic.fol.wam.debugger.uifactory.PaneController;

/**
 * SwingMainWindow implements the main debugger application window as a stand-alone Swing application.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Show/hide the main window. 	 {@link ComponentFactory}

 *
 Allow UI component to be placed within the centre, left, right or console position.
 *      {@link ComponentFactory}

 *

 *
 * @author Rupert Smith
 */
public class SwingMainWindow implements MainWindow<JComponent, KeyStroke>
{
    /** The application window frame. */
    private JFrame frame;

    /** A customized Swing layout for this application. */
    private DesktopAppLayout layout;

    /** The component factory used to build UI elements. */
    private final SwingComponentFactory factory;

    /** The centre component. */
    private JComponent centreComponent;

    /** The console component. */
    private JComponent consoleComponent;

    /** The left component. */
    private JComponent leftComponent;

    /** The right component. */
    private JComponent rightComponent;

    /**
     * Creates the main debugger window, implement as a standalone Swing application window.
     *
     * @param factory The component factory used to build UI elements.
     */
    public SwingMainWindow(SwingComponentFactory factory)
    {
        this.factory = factory;
    }

    /** {@inheritDoc} */
    public void showMainWindow()
    {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setMinimumSize(new Dimension(800, 600));
        frame.setPreferredSize(new Dimension(1000, 800));

        layout = new DesktopAppLayout();
        frame.getContentPane().setLayout(layout);

        frame.setVisible(true);

        JRootPane rootPane = frame.getRootPane();
        rootPane.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "pressed");
    }

    /** {@inheritDoc} */
    public void showCentrePane(JComponent component)
    {
        frame.getContentPane().add(component, DesktopAppLayout.CENTER);
        frame.pack();

        centreComponent = component;
    }

    /** {@inheritDoc} */
    public PaneController<KeyStroke> getCentreController()
    {
        return new SwingPaneController(centreComponent);
    }

    /** {@inheritDoc} */
    public void showConsole(JComponent component)
    {
        showHorizontalBar();
        frame.getContentPane().add(component, DesktopAppLayout.CONSOLE);
        frame.pack();

        consoleComponent = component;
    }

    /** {@inheritDoc} */
    public void showLeftPane(JComponent component)
    {
        showLeftBar();
        frame.getContentPane().add(component, DesktopAppLayout.LEFT_PANE);
        frame.pack();

        leftComponent = component;
    }

    /** {@inheritDoc} */
    public void showRightPane(JComponent component)
    {
        showRightBar();
        frame.getContentPane().add(component, DesktopAppLayout.RIGHT_PANE);
        frame.pack();

        rightComponent = component;
    }

    /** {@inheritDoc} */
    public void setKeyShortcut(KeyStroke keyCombination, String actionName, Runnable action)
    {
        frame.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keyCombination, actionName);
        frame.getRootPane().getActionMap().put(actionName, new RunnableAction(action));
    }

    /** Creates a horizontal grip-able bar for adjusting the console height. */
    private void showHorizontalBar()
    {
        // Left vertical bar.
        Component bar = factory.createGripPanel(layout.getConsoleHeightResizer(), false);

        frame.getContentPane().add(bar, DesktopAppLayout.STATUS_BAR);
    }

    /** Creates a vertical grip-able bar for adjusting the left panel width. */
    private void showLeftBar()
    {
        // Left vertical bar.
        Component bar = factory.createGripPanel(layout.getLeftPaneWidthResizer(), true);

        frame.getContentPane().add(bar, DesktopAppLayout.LEFT_VERTICAL_BAR);
    }

    /** Creates a vertical grip-able bar for adjusting the right panel width. */
    private void showRightBar()
    {
        // Right vertical bar.
        Component bar = factory.createGripPanel(layout.getRightPaneWidthResizer(), true);

        frame.getContentPane().add(bar, DesktopAppLayout.RIGHT_VERTICAL_BAR);
    }
}


/*
 * 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.jsoup;

import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.jsoup.Connection.Response;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.FormElement;
import org.jsoup.select.Elements;

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 jsoup {@code Document}.
 */
class JsoupMicrodataDocument extends AbstractMicrodataDocument
{
	// ----------------------------------------------------------------------------------------------------------------
	// constants
	// ----------------------------------------------------------------------------------------------------------------

	private static final Map<String, String> NO_COOKIES = Collections.<String, String>emptyMap();

	private static final String DEFAULT_BASE_URI = "microbrowser://";

	// ----------------------------------------------------------------------------------------------------------------
	// fields
	// ----------------------------------------------------------------------------------------------------------------

	private final Map<String, String> cookies;

	private final Document document;

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

	JsoupMicrodataDocument()
	{
		this(NO_COOKIES, new Document(DEFAULT_BASE_URI));
	}

	JsoupMicrodataDocument(Map<String, String> cookies, Document document)
	{
		this.cookies = checkNotNull(cookies, "cookies");
		this.document = sanitize(checkNotNull(document, "document"));
	}

	JsoupMicrodataDocument(Map<String, String> cookies, Response response) throws IOException
	{
		this(union(cookies, response.cookies()), response.parse());
	}

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

	public List<MicrodataItem> getItems(String type)
	{
		Elements elements = document.select(byItemType(newUrl(type)));

		return Lists.transform(elements, new Function<Element, MicrodataItem>()
		{
			public MicrodataItem apply(Element element)
			{
				return new JsoupMicrodataItem(JsoupMicrodataDocument.this, element);
			}
		});
	}

	public String getCookie(String name)
	{
		String value = cookies.get(name);

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

		return value;
	}

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

	public List<Link> getLinks(String rel)
	{
		Elements elements = document.select(byLink(rel));

		return Lists.transform(elements, new Function<Element, Link>()
		{
			public Link apply(Element element)
			{
				return new JsoupLink(JsoupMicrodataDocument.this, element);
			}
		});
	}

	public Form getForm(String name)
	{
		Elements elements = document.select(byForm(name));

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

		return new JsoupForm(this, (FormElement) elements.first());
	}

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

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

		return type.cast(document);
	}

	// ----------------------------------------------------------------------------------------------------------------
	// public methods
	// ----------------------------------------------------------------------------------------------------------------

	public Map<String, String> getCookies()
	{
		return cookies;
	}

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

	private static Document sanitize(Document document)
	{
		for (FormElement form : document.getAllElements().forms())
		{
			sanitizeRadioControls(form);
		}

		return document;
	}

	/**
	 * Ensures that radio controls are mutually exclusive within control groups.
	 */
	private static void sanitizeRadioControls(FormElement form)
	{
		Map<String, Element> controlsByName = new HashMap<String, Element>();

		for (Element control : form.elements())
		{
			// cannot use Element.select since Element.hashCode collapses like elements
			if ("radio".equals(control.attr("type")) && control.hasAttr("checked"))
			{
				String name = control.attr("name");

				if (controlsByName.containsKey(name))
				{
					controlsByName.get(name).attr("checked", false);
				}

				controlsByName.put(name, control);
			}
		}
	}

	private static <K, V> Map<K, V> union(Map<K, V> map1, Map<K, V> map2)
	{
		Map<K, V> union = new HashMap<K, V>(map1);
		union.putAll(map2);
		return union;
	}

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

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

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