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

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Variable represents a variable in first order logic. A variable has a name, and can either be free (unassigned) or be
 * bound (assigned or unified against) the value of another term in the language. The pure terminology of the language
 * does not cover variable assignments or substitutions, these are part of its operational semantics. Room is made
 * available for subsitutions in this class because, as stated in the comments for {@link Term}, the purpose of these
 * definitions is to provide a minimal semantic framework for evaluating first order logic as well as providing an
 * abstract syntax tree for it.
 *
 *

CRC Card

 *
 Responsibilities
 Responsibilities
 *
 Associate a name with an optional substitution.
 *

 *
 * @author Rupert Smith
 */
public class Variable extends BaseTerm implements Term, VariableBindingContext<Variable>, Comparable<Variable>
{
    /** A generator of unique variable id's. */
    private static final AtomicInteger varId = new AtomicInteger();

    /**
     * Holds the name of the variable. This is an integer that refers to an interned string that provides the textual
     * name. An integer representation is used for fast comparisons.
     */
    protected int name;

    /** The term against which this variable is currently unified. <tt>null</tt> when the variable is free. */
    protected Term substitution;

    /** Used to indicate that a variable is anonymous. */
    private final boolean anonymous;

    /** Holds a unique id for the variable. */
    protected int id = varId.getAndIncrement();

    /**
     * Creates a new variable, the name does not have to be unique and the the variable can be free by supplying a null
     * substitution.
     *
     * @param name         The name of the variable.
     * @param substitution The substitution to create the variable with, use <tt>null</tt> for a free variable.
     * @param anonymous    <tt>true</tt> if the variable is anonymous.
     */
    public Variable(int name, Term substitution, boolean anonymous)
    {
        this.name = name;
        this.substitution = substitution;
        this.anonymous = anonymous;
    }

    /**
     * Provides this variables binding context.
     *
     * @return This variables binding context.
     */
    public VariableBindingContext<Variable> getBindingContext()
    {
        return this;
    }

    /**
     * Provides the storage cell for the specified variable. Some types of variable may defer their storage onto a
     * storage cell other than themselves, other variable types may simply return themselves as their own storage cells.
     *
     * @param  variable The variable to get the storage cell for.
     *
     * @return The storage cell where the specified variable sets its bindings.
     */
    public Variable getStorageCell(Variable variable)
    {
        return this;
    }

    /**
     * Reports whether or not this term is a number.
     *
     * @return <tt>false</tt> always.
     */
    public boolean isNumber()
    {
        Term t = getValue();

        return (t != this) && t.isNumber();
    }

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

    /**
     * Reports whether or not this term is constant (contains no variables). A variable is constant if it has been
     * assigned a value which is constant, and not otherwise.
     *
     * @return <tt>true</tt> if this term is constant, <tt>false</tt> otherwise.
     */
    public boolean isConstant()
    {
        Term t = getValue();

        return (t != this) && t.isConstant();
    }

    /**
     * Reports whether or not this term is compound. A variable is compound if it has been assigned a value which is
     * compound, and not otherwise.
     *
     * @return <tt>true</tt> if this term is compound, <tt>fals</tt> otherwise.
     */
    public boolean isCompound()
    {
        Term t = getValue();

        return (t != this) && t.isCompound();
    }

    /**
     * Reports whether or not this term is an atom. A variable is an atom if it has been assigned a value which is an
     * atom and not otherwise.
     *
     * @return <tt>true</tt> if this variable has been assigned atomic value, <tt>false</tt> otherwise.
     */
    public boolean isAtom()
    {
        Term t = getValue();

        return (t != this) && t.isAtom();
    }

    /**
     * Reports whether or not this term is a ground term. A variable is a ground term if it has been assigned a value
     * which is a ground term, and not otherwise.
     *
     * @return <tt>true</tt> if this variable has been assigned a ground term, <tt>false</tt> othewise.
     */
    public boolean isGround()
    {
        Term t = getValue();

        return (t != this) && t.isGround();
    }

    /**
     * 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. When the variable is free, the variable term itself is returned.
     *
     * @return The term itself, or the assigned value of this variable.
     */
    public Term getValue()
    {
        Term result = this;
        Term assignment = this.substitution;

        // If the variable is assigned, loops down the chain of assignments until no more can be found. Whatever term
        // is found at the end of the chain of assignments is the value of this variable.
        while (assignment != null)
        {
            result = assignment;

            if (!assignment.isVar())
            {
                break;
            }
            else
            {
                assignment = ((Variable) assignment).substitution;
            }
        }

        return result;
    }

    /** Frees all assigned variables in the term, leaving them unnassigned. */
    public void free()
    {
        substitution = null;
    }

    /**
     * Reports wether or not this is an anonymous variable.
     *
     * @return <tt>true</tt> if this is an anonymous variable, <tt>false</tt> otherwise.
     */
    public boolean isAnonymous()
    {
        return anonymous;
    }

    /**
     * Reports whether or not this variable is bound to a value.
     *
     * @return <tt>true</tt> if this variable has been assigned a value, <tt>false</tt> otherwise.
     */
    public boolean isBound()
    {
        return substitution != null;
    }

    /**
     * Gets the value (if any) to which this variable has been assigned.
     *
     * @return The value to which this variable has been bound, or <tt>null</tt> if it is free.
     */
    public Term getSubstitution()
    {
        return substitution;
    }

    /**
     * Binds this variable to the specified value.
     *
     * @param term The value to bind this variable to.
     */
    public void setSubstitution(Term term)
    {
        Term termToBindTo = term;

        // When binding against a variable, always bind to its storage cell and not the variable itself.
        if (termToBindTo instanceof Variable)
        {
            Variable variableToBindTo = (Variable) term;
            termToBindTo = variableToBindTo.getStorageCell(variableToBindTo);
        }

        substitution = termToBindTo;
    }

    /**
     * Gets the name of this variable.
     *
     * @return The name of this variable.
     */
    public int getName()
    {
        return name;
    }

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

    /** {@inheritDoc} */
    public Variable acceptTransformer(TermTransformer transformer)
    {
        if (transformer instanceof VariableTransformer)
        {
            return ((VariableTransformer) transformer).transform(this);
        }
        else
        {
            return (Variable) super.acceptTransformer(transformer);
        }
    }

    /**
     * Compares this term for structural equality with another. Two terms are structurally equal if they are the same
     * functor with the same arguments, or are the same unbound variable, or the bound values of the left or right
     * variable operands are structurally equal. Structural equality is a stronger equality than unification and unlike
     * unification it does not produce any variable bindings. Two unified terms will always be structurally equal.
     *
     * @param  term The term to compare with this one for structural equality.
     *
     * @return <tt>true</tt> if the two terms are structurally eqaul, <tt>false</tt> otherwise.
     */
    public boolean structuralEquals(Term term)
    {
        Term comparator = term.getValue();
        Term value = getValue();

        // Check if this is an unbound variable in which case the comparator must be the same variable.
        if (value == this)
        {
            return this.equals(comparator);
        }
        else
        {
            return value.structuralEquals(comparator);
        }
    }

    /**
     * {@inheritDoc}
     *
     *
Orders variables by interned name.
     */
    public int compareTo(Variable o)
    {
        return (name > o.name) ? 1 : ((name < o.name) ? -1 : 0);
    }

    /** {@inheritDoc} */
    public String toString(VariableAndFunctorInterner interner, boolean printVarName, boolean printBindings)
    {
        Term value = null;

        String textName;

        if (isAnonymous())
        {
            textName = "_" + id;
        }
        else
        {
            textName = interner.getVariableName(name);
        }

        if (printBindings)
        {
            value = getValue();
        }

        if (!printBindings || (value == this))
        {
            return textName;
        }
        else if (printVarName)
        {
            return textName + "/" + value.toString(interner, printVarName, printBindings);
        }
        else
        {
            return value.toString(interner, printVarName, printBindings);
        }
    }

    /**
     * A unique id for this variable.
     *
     * @return The unique id for this variable.
     */
    public int getId()
    {
        return id;
    }

    /**
     * Creates a string representation of this variable, mostly used for debugging purposes.
     *
     * @return A string reprenestation of this variable.
     */
    public String toString()
    {
        return "Variable: [ name = " + name + ", substitution = " + ((substitution == null) ? "null" : substitution) +
            " ]";
    }
}


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

import com.thesett.common.error.NotImplementedException;

/**
 * ASCIIByteBufferString is an {@link ASCIIString} that holds its data in a byte buffer. It essentially provides a view
 * onto a buffer of data, through which string operations may be applied, without having to copy the data out of the
 * buffer into an intermediate buffer or array.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Represent an ASCII string as a view onto a ByteBuffer.
 *
Provide a view onto an ASCII string as a character sequence.
 *
Allow ASCII strings to be compared for ordering purposes.
 *

 *
 * @author Rupert Smith
 */
public final class ASCIIByteBufferString implements ASCIIString
{
    /** Holds the byte data that makes up the string. */
    private final ByteBuffer data;

    /** Holds the offset into the buffer of the start of the string. */
    private final int offset;

    /** Used to cache the hash code of the string. */
    private int hashCode;

    /** Used to cache the value of the string as a Java String. */
    private String stringValue;

    /** Holds the length of the string. */
    private final int length;

    /**
     * Creates an ASCII string from the specified byte buffer, taking the required number of byte to make up the length
     * of the string required.
     *
     * @param data   The underlying buffer holding the string data.
     * @param length The length of the string.
     */
    public ASCIIByteBufferString(ByteBuffer data, int length)
    {
        this.data = data;
        this.length = length;
        offset = 0;

        computeHashCode();
    }

    /**
     * Creates an ASCII string from the specified byte buffer, taking the required number of byte to make up the length
     * of the string required.
     *
     * @param data   The underlying buffer holding the string data.
     * @param start  The starting offset within the buffer of the string.
     * @param length The length of the string.
     */
    public ASCIIByteBufferString(ByteBuffer data, int start, int length)
    {
        this.data = data;
        this.length = length;
        offset = start;

        computeHashCode();
    }

    /** {@inheritDoc} */
    public int length()
    {
        return length;
    }

    /** {@inheritDoc} */
    public char charAt(int index)
    {
        return (char) data.get(offset + index);
    }

    /** {@inheritDoc} */
    public CharSequence subSequence(int start, int end)
    {
        return new ASCIIByteBufferString(data, offset + start, end - start);
    }

    /** {@inheritDoc} */
    public byte[] getBytes()
    {
        byte[] dst = new byte[length];
        data.get(dst, offset, length);

        return dst;
    }

    /** {@inheritDoc} */
    public byte get(int index)
    {
        return data.get(index + offset);
    }

    /** {@inheritDoc} */
    public int indexOf(ASCIIString str, int fromIndex)
    {
        throw new NotImplementedException();
    }

    /**
     * {@inheritDoc}
     *
     *
Two ASCII strings are equal, if they have the same length, and consist of exactly the same sequence of bytes.
     */
    public boolean equals(Object o)
    {
        if (!(o instanceof ASCIIString))
        {
            return false;
        }

        ASCIIString comparator = (ASCIIString) o;

        if (length != comparator.length())
        {
            return false;
        }

        for (int i = 0; i < length; i++)
        {
            byte b1 = get(i);
            byte b2 = comparator.get(i);

            if (b1 != b2)
            {
                return false;
            }
        }

        return true;
    }

    /** {@inheritDoc} */
    public int hashCode()
    {
        return hashCode;
    }

    /**
     * {@inheritDoc}
     *
     *
Two ASCII strings are compared using a lexicographical ordering.
     */
    public int compareTo(ASCIIString comparator)
    {
        int n = Math.min(length, comparator.length());

        for (int i = 0; i < n; i++)
        {
            byte b1 = get(i);
            byte b2 = comparator.get(i);

            if (b1 == b2)
            {
                continue;
            }

            if (b1 < b2)
            {
                return -1;
            }

            return 1;
        }

        return length - comparator.length();
    }

    /** {@inheritDoc} */
    public String toString()
    {
        if (stringValue == null)
        {
            computeStringValue();
        }

        return stringValue;
    }

    /**
     * Computes and caches in the {@link #stringValue} field, a representation of this string as a default Java String.
     */
    private void computeStringValue()
    {
        char[] chars = new char[length];

        for (int i = 0; i < length; i++)
        {
            chars[i] = (char) data.get(i + offset);
        }

        stringValue = new String(chars);
    }

    /** Computes and caches in the {@link #hashCode} field, a hash code for this string. */
    private void computeHashCode()
    {
        hashCode = 0;

        for (int i = 0; i < length; i++)
        {
            hashCode = (31 * hashCode) + data.get(i + offset);
        }
    }
}


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

import java.util.PriorityQueue;
import java.util.Queue;

import com.thesett.aima.search.SearchNode;
import com.thesett.aima.search.Traversable;

/**
 * Implements a greedy search algorithm. This is one that always follows the search node that has the highest heuristic
 * value.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Do a greedy search.
 *

 *
 * @author Rupert Smith
 */
public class GreedySearch<O, T extends Traversable<O>> extends BaseHeuristicSearch<O, T>
{
    /**
     * Creates a new GreedySearch object.
     *
     * @param heuristic The heuristic to use to compute heuristic values of search nodes with.
     */
    public GreedySearch(Heuristic<O, T> heuristic)
    {
        super(heuristic);
    }

    /**
     * Creates the correct type of queue for this search. This search uses a priority queue ordered by heuristic value.
     *
     * @return An empty priority queue with heuristic ordering.
     */
    public Queue<SearchNode<O, T>> createQueue()
    {
        return new PriorityQueue<SearchNode<O, T>>(11, new GreedyComparator());
    }
}


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

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

/**
 * SilentFailSocketAppender is a Log4J socket appender that fails silently and quickly without retrying when a logging
 * socket that it tries to connect to cannot be opened. This is extremely usefull for unit testing where a log reciever
 * may or may not be open on a socket. Running many JUnit tests in different virtual machines means that Log4J is
 * repeatedly restarted. Waiting for a connection to a non-existant socket to time out is time consuming and makes the
 * tests run far slower than if no connection is attempted. This appender fails quickly and silently allowing the tests
 * to run wihtout too much of a noticeable slow down.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Send Log4J events to a socket.
 *

 *
 * @author Rupert Smith
 */
public class SilentFailSocketAppender extends AppenderSkeleton
{
    /** The default port number of remote logging server (4560). */
    static final int DEFAULT_PORT = 4560;

    /** The default reconnection delay (30000 milliseconds or 30 seconds). */
    static final int DEFAULT_RECONNECTION_DELAY = 30000;

    /** Reset the ObjectOutputStream every 1 calls. */
    private static final int RESET_FREQUENCY = 1;

    /**
     * Holds the host name as String in addition to the resolved InetAddress so that it can be returned via getOption().
     */
    String remoteHost;

    /** The address to log to. */
    InetAddress address;

    /** The port to og to. */
    int port = DEFAULT_PORT;

    /** The output stream to write to the socket through. */
    ObjectOutputStream oos;

    /** The delay before attempting to reconnect when a socket cannot be opened. */
    int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;

    /** When set to true location information will be sent to the log event reciever on the other end of the socket. */
    boolean locationInfo;

    /** The connector to open the socket with. */
    private Connector connector;

    /**
     * Used to count the number of log events sent. The object output stream is reset on every n events as counted by
     * this counter. See {@link #RESET_FREQUENCY}.
     */
    int counter;

    /** Creates a new SilentFailSocketAppender object. */
    public SilentFailSocketAppender()
    {
    }

    /**
     * Connects to remote server at address and port.
     *
     * @param address The address to connect to.
     * @param port    The port to connect to.
     */
    public SilentFailSocketAppender(InetAddress address, int port)
    {
        this.address = address;
        this.remoteHost = address.getHostName();
        this.port = port;
        connect(address, port);
    }

    /**
     * Connects to remote server at host and port.
     *
     * @param host The address to connect to.
     * @param port The port to connect to.
     */
    public SilentFailSocketAppender(String host, int port)
    {
        this.port = port;
        this.address = getAddressByName(host);
        this.remoteHost = host;
        connect(address, port);
    }

    /** Connect to the specified RemoteHost and Port. */
    public void activateOptions()
    {
        connect(address, port);
    }

    /**
     * Close this appender.
     *
     *
This will mark the appender as closed and call then {@link #cleanUp} method.
     */
    public synchronized void close()
    {
        if (closed)
        {
            return;
        }

        this.closed = true;
        cleanUp();
    }

    /** Drop the connection to the remote host and release the underlying connector thread if it has been created. */
    public void cleanUp()
    {
        if (oos != null)
        {
            try
            {
                oos.close();
            }
            catch (IOException e)
            {
                LogLog.error("Could not close oos.", e);
            }

            oos = null;
        }

        if (connector != null)
        {
            // LogLog.debug("Interrupting the connector.");
            connector.interrupted = true;
            connector = null; // allow gc
        }
    }

    /**
     * Appends a logging event to the remote event reciever.
     *
     * @param event The logging event to send.
     */
    public void append(LoggingEvent event)
    {
        if (event == null)
        {
            return;
        }

        if (address == null)
        {
            errorHandler.error("No remote host is set for SocketAppender named \"" + this.name + "\".");

            return;
        }

        if (oos != null)
        {
            try
            {
                if (locationInfo)
                {
                    event.getLocationInformation();
                }

                oos.writeObject(event);

                // LogLog.debug("=========Flushing.");
                oos.flush();

                if (++counter >= RESET_FREQUENCY)
                {
                    counter = 0;

                    // Failing to reset the object output stream every now and
                    // then creates a serious memory leak.
                    // System.err.println("Doing oos.reset()");
                    oos.reset();
                }
            }
            catch (IOException e)
            {
                oos = null;
                LogLog.warn("Detected problem with connection: " + e);

                if (reconnectionDelay > 0)
                {
                    fireConnector();
                }
            }
        }
    }

    /**
     * The SocketAppender does not use a layout. Hence, this method returns false.
     *
     * @return Always false.
     */
    public boolean requiresLayout()
    {
        return false;
    }

    /**
     * The RemoteHost option takes a string value which should be the host name of the server where a SocketNode
     * is running.
     *
     * @param host The host to connect to.
     */
    public void setRemoteHost(String host)
    {
        address = getAddressByName(host);
        remoteHost = host;
    }

    /**
     * Returns value of the RemoteHost option.
     *
     * @return The remote host.
     */
    public String getRemoteHost()
    {
        return remoteHost;
    }

    /**
     * The Port option takes a positive integer representing the port where the server is waiting for
     * connections.
     *
     * @param port The port to connect to.
     */
    public void setPort(int port)
    {
        this.port = port;
    }

    /**
     * Returns value of the Port option.
     *
     * @return The remote port.
     */
    public int getPort()
    {
        return port;
    }

    /**
     * The LocationInfo option takes a boolean value. If true, the information sent to the remote host will
     * include location information. By default no location information is sent to the server.
     *
     * @param locationInfo Set to true to send location information to the reciever.
     */
    public void setLocationInfo(boolean locationInfo)
    {
        this.locationInfo = locationInfo;
    }

    /**
     * Returns value of the LocationInfo option.
     *
     * @return The setting of the location info flag.
     */
    public boolean getLocationInfo()
    {
        return locationInfo;
    }

    /**
     * The ReconnectionDelay option takes a positive integer representing the number of milliseconds to wait
     * between each failed connection attempt to the server. The default value of this option is 30000 which corresponds
     * to 30 seconds.
     *
     *
Setting this option to zero turns off reconnection capability.
     *
     * @param delay The time to wait between re-connection attempts.
     */
    public void setReconnectionDelay(int delay)
    {
        this.reconnectionDelay = delay;
    }

    /**
     * Returns value of the ReconnectionDelay option.
     *
     * @return The reconnection delay in force.
     */
    public int getReconnectionDelay()
    {
        return reconnectionDelay;
    }

    /**
     * Get an inet address from a host name.
     *
     * @param  host The host name to look up.
     *
     * @return An inet address for that host.
     */
    static InetAddress getAddressByName(String host)
    {
        try
        {
            return InetAddress.getByName(host);
        }
        catch (Exception e)
        {
            LogLog.error("Could not find address of [" + host + "].", e);

            return null;
        }
    }

    /**
     * Connects to remote server at address and port.
     *
     * @param address The address to connect to.
     * @param port    The port to connect to.
     */
    void connect(InetAddress address, int port)
    {
        if (this.address == null)
        {
            return;
        }

        try
        {
            // First, close the previous connection if any.
            cleanUp();

            Socket socket = new Socket();
            SocketAddress socketAddr = new InetSocketAddress(address, port);

            socket.connect(socketAddr, 10);

            oos = new ObjectOutputStream(socket.getOutputStream());
        }
        catch (IOException e)
        {
            // Silently fail and do not retry later.
            // Exception noted so can be ignored.
            e = null;

            /*
             * String msg = "Could not connect to remote log4j server at [" +address.getHostName()+"].";
             * if(reconnectionDelay > 0) { msg += " We will try again later."; fireConnector(); // fire the connector
             * thread } LogLog.error(msg, e); */
        }
    }

    /** Starts a new connector thread to do? */
    void fireConnector()
    {
        if (connector == null)
        {
            LogLog.debug("Starting a new connector thread.");
            connector = new Connector();
            connector.setDaemon(true);
            connector.setPriority(Thread.MIN_PRIORITY);
            connector.start();
        }
    }

    /**
     * The Connector will reconnect when the server becomes available again. It does this by attempting to open a new
     * connection every reconnectionDelay milliseconds.
     *
     *
It stops trying whenever a connection is established. It will restart to try reconnect to the server when
     * previpously open connection is droppped.
     *
     *

     *

     *
CRC Card

     *

     * Responsibilities
     * Collaborations
     *
     *

     * Automatically re-connect a connection if it closes.
     *

     *
     * @author Rupert Smith
     */
    class Connector extends Thread
    {
        /** Signals that the connection has been lost and needs to be re-established. */
        boolean interrupted;

        /** The main thread. */
        public void run()
        {
            Socket socket;

            while (!interrupted)
            {
                try
                {
                    sleep(reconnectionDelay);
                    LogLog.debug("Attempting connection to " + address.getHostName());
                    socket = new Socket(address, port);

                    synchronized (this)
                    {
                        oos = new ObjectOutputStream(socket.getOutputStream());
                        connector = null;
                        LogLog.debug("Connection established. Exiting connector thread.");

                        break;
                    }
                }
                catch (InterruptedException e)
                {
                    LogLog.debug("Connector interrupted. Leaving loop.");

                    // Exception noted so can be ignored.
                    e = null;

                    return;
                }
                catch (java.net.ConnectException e)
                {
                    LogLog.debug("Remote host " + address.getHostName() + " refused connection.", e);
                }
                catch (IOException e)
                {
                    LogLog.debug("Could not connect to " + address.getHostName() + ". Exception is " + e);
                }
            }
            // LogLog.debug("Exiting Connector.run() method.");
        }
    }
}


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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.thesett.aima.logic.fol.AllTermsVisitor;
import com.thesett.aima.logic.fol.Clause;
import com.thesett.aima.logic.fol.DelegatingAllTermsVisitor;
import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.PositionalTermVisitor;
import com.thesett.aima.logic.fol.Predicate;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.compiler.PositionalTermTraverser;
import com.thesett.aima.logic.fol.wam.compiler.InstructionCompiler;
import com.thesett.common.util.doublemaps.SymbolTable;
import com.thesett.text.api.model.TextTableModel;
import com.thesett.text.impl.model.TextTableImpl;

/**
 * WAMCompiledTermsPrintingVisitor assists with pretty printing queries and predicates compiled by the
 * {@link InstructionCompiler}.
 *
 *
This class is abstract because it implements a default behaviour for all terms, which is to apply a stack of
 * column printers to them. Depending on the entry point of this pretty printer, the top-level term being printed should
 * have its visit method overridden to set up the stack of printers on entry, and to finalize the output on exit. The
 * {@link #initializePrinters()} and {@link #printTable()} methods are provided to do this.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Pretty print compiled terms internal information about the compilation.
 *

 *
 * @author Rupert Smith
 */
public abstract class WAMCompiledTermsPrintingVisitor extends DelegatingAllTermsVisitor implements PositionalTermVisitor
{
    /** The positional traverser used to traverse the clause being printed. */
    protected PositionalTermTraverser traverser;

    /** The name interner. */
    private final VariableAndFunctorInterner interner;

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

    /** Holds the string buffer to pretty print the results into. */
    private final StringBuffer result;

    /** Holds a list of all column printers to apply. */
    Collection<AllTermsVisitor> printers = new ArrayList<AllTermsVisitor>();

    /** Holds the table model to render the output to. */
    TextTableModel printTable = new TextTableImpl();

    /**
     * Creates a pretty printing visitor for clauses being compiled in WAM.
     *
     * @param interner    The symbol name table.
     * @param symbolTable The symbol table for the compilation.
     * @param result      A string buffer to place the results in.
     */
    public WAMCompiledTermsPrintingVisitor(VariableAndFunctorInterner interner,
        SymbolTable<Integer, String, Object> symbolTable, StringBuffer result)
    {
        super(null);
        this.interner = interner;
        this.result = result;
        this.symbolTable = symbolTable;

    }

    /** {@inheritDoc} */
    public void setPositionalTraverser(PositionalTermTraverser traverser)
    {
        this.traverser = traverser;
    }

    /** {@inheritDoc} */
    public void visit(Predicate predicate)
    {
        for (AllTermsVisitor printer : printers)
        {
            printer.visit(predicate);
        }

        super.visit(predicate);
    }

    /** {@inheritDoc} */
    public void visit(Clause clause)
    {
        for (AllTermsVisitor printer : printers)
        {
            printer.visit(clause);
        }

        super.visit(clause);
    }

    /** {@inheritDoc} */
    public void visit(Functor functor)
    {
        for (AllTermsVisitor printer : printers)
        {
            printer.visit(functor);
        }

        super.visit(functor);
    }

    /** {@inheritDoc} */
    public void visit(Variable variable)
    {
        for (AllTermsVisitor printer : printers)
        {
            printer.visit(variable);
        }

        super.visit(variable);
    }

    /** Sets up the stack of column printers. */
    protected void initializePrinters()
    {
        int maxColumns = 0;

        printers.add(new SourceClausePrinter(interner, symbolTable, traverser, maxColumns++, printTable));
        printers.add(new PositionPrinter(interner, symbolTable, traverser, maxColumns++, printTable));
        printers.add(new UnoptimizedLabelPrinter(interner, symbolTable, traverser, maxColumns++, printTable));
        printers.add(new UnoptimizedByteCodePrinter(interner, symbolTable, traverser, maxColumns++, printTable));
        printers.add(new LabelPrinter(interner, symbolTable, traverser, maxColumns++, printTable));
        printers.add(new ByteCodePrinter(interner, symbolTable, traverser, maxColumns++, printTable));
    }

    /**
     * Assembles the accumulated output in all rows and columns into a table. The table is appended onto {@link #result}
     */
    protected void printTable()
    {
        for (int i = 0; i < printTable.getRowCount(); i++)
        {
            for (int j = 0; j < printTable.getColumnCount(); j++)
            {
                String valueToPrint = printTable.get(j, i);
                valueToPrint = (valueToPrint == null) ? "" : valueToPrint;
                result.append(valueToPrint);

                Integer maxColumnSize = printTable.getMaxColumnSize(j);
                int padding = ((maxColumnSize == null) ? 0 : maxColumnSize) - valueToPrint.length();
                padding = (padding < 0) ? 0 : padding;

                for (int s = 0; s < padding; s++)
                {
                    result.append(" ");
                }

                result.append(" % ");
            }

            result.append("\n");
        }
    }
}


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

import java.net.MalformedURLException;
import java.util.List;

import org.hobsoft.microbrowser.CookieNotFoundException;
import org.hobsoft.microbrowser.Form;
import org.hobsoft.microbrowser.FormNotFoundException;
import org.hobsoft.microbrowser.Link;
import org.hobsoft.microbrowser.LinkNotFoundException;
import org.hobsoft.microbrowser.MicrodataDocument;
import org.hobsoft.microbrowser.MicrodataItem;
import org.hobsoft.microbrowser.MicrodataItemNotFoundException;
import org.junit.Test;

import com.squareup.okhttp.mockwebserver.MockResponse;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hobsoft.microbrowser.tck.support.MicrobrowserMatchers.item;
import static org.hobsoft.microbrowser.tck.support.MicrobrowserMatchers.link;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.url;
import static org.junit.Assert.assertThat;

/**
 * TCK for {@code MicrodataDocument}.
 */
public abstract class MicrodataDocumentTck extends AbstractMicrobrowserTest
{
	// ----------------------------------------------------------------------------------------------------------------
	// getItem tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getItemWhenItemReturnsItem() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""));

		MicrodataItem actual = newBrowser().get(url(server()))
			.getItem("http://x");

		assertThat("item", actual, is(item("http://y")));
	}

	@Test
	public void getItemWhenItemsReturnsFirstItem() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ ""));

		MicrodataItem actual = newBrowser().get(url(server()))
			.getItem("http://x");

		assertThat("item", actual, is(item("http://y")));
	}

	@Test
	public void getItemWithUnknownTypeThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""));

		MicrodataDocument document = newBrowser().get(url(server()));

		thrown().expect(MicrodataItemNotFoundException.class);
		thrown().expectMessage("http://x");

		document.getItem("http://x");
	}

	@Test
	public void getItemWithInvalidTypeThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""));

		MicrodataDocument document = newBrowser().get(url(server()));

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("x");

		document.getItem("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getItems tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getItemsWhenItemReturnsItem() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ ""));

		List<MicrodataItem> actual = newBrowser().get(url(server()))
			.getItems("http://x");

		assertThat("items", actual, contains(item("http://y")));
	}

	@Test
	public void getItemsWhenItemsReturnsItems() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "
"
			+ "
"
			+ ""));

		List<MicrodataItem> actual = newBrowser().get(url(server()))
			.getItems("http://x");

		assertThat("items", actual, contains(
			item("http://y"),
			item("http://z")
		));
	}

	@Test
	public void getItemsWithUnknownTypeReturnsEmptyList()
	{
		server().enqueue(new MockResponse().setBody(""));

		List<MicrodataItem> actual = newBrowser().get(url(server()))
			.getItems("http://x");

		assertThat("items", actual, is(empty()));
	}

	@Test
	public void getItemsWithInvalidTypeThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""));

		MicrodataDocument document = newBrowser().get(url(server()));

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("x");

		document.getItems("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getLink tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getLinkWhenAnchorReturnsLink() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ ""
			+ ""));

		Link actual = newBrowser().get(url(server()))
			.getLink("x");

		assertThat("link", actual, is(link("x", "http://y/")));
	}

	@Test
	public void getLinkWhenAnchorsReturnsFirstLink() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ ""
			+ ""
			+ ""));

		Link actual = newBrowser().get(url(server()))
			.getLink("x");

		assertThat("link", actual, is(link("x", "http://y/")));
	}

	@Test
	public void getLinkWhenLinkReturnsLink() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<link rel='x' href='http://y/'/>"
			+ ""));

		Link actual = newBrowser().get(url(server()))
			.getLink("x");

		assertThat("link", actual, is(link("x", "http://y/")));
	}

	@Test
	public void getLinkWhenLinksReturnsFirstLink() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<link rel='x' href='http://y/'/>"
			+ "<link rel='x' href='http://z/'/>"
			+ ""));

		Link actual = newBrowser().get(url(server()))
			.getLink("x");

		assertThat("link", actual, is(link("x", "http://y/")));
	}

	@Test
	public void getLinkWithUnknownRelThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""));

		MicrodataDocument document = newBrowser().get(url(server()));

		thrown().expect(LinkNotFoundException.class);
		thrown().expectMessage("x");

		document.getLink("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getLinks tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getLinksWhenAnchorReturnsLink() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ ""
			+ ""));

		List<Link> actual = newBrowser().get(url(server()))
			.getLinks("x");

		assertThat("links", actual, contains(link("x", "http://y/")));
	}

	@Test
	public void getLinksWhenAnchorsReturnsLinks() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ ""
			+ ""
			+ ""));

		List<Link> actual = newBrowser().get(url(server()))
			.getLinks("x");

		assertThat("links", actual, contains(
			link("x", "http://y/"),
			link("x", "http://z/")
		));
	}

	@Test
	public void getLinksWhenLinkReturnsLink() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<link rel='x' href='http://y/'/>"
			+ ""));

		List<Link> actual = newBrowser().get(url(server()))
			.getLinks("x");

		assertThat("links", actual, contains(link("x", "http://y/")));
	}

	@Test
	public void getLinksWhenLinksReturnsLinks() throws MalformedURLException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<link rel='x' href='http://y/'/>"
			+ "<link rel='x' href='http://z/'/>"
			+ ""));

		List<Link> actual = newBrowser().get(url(server()))
			.getLinks("x");

		assertThat("links", actual, contains(
			link("x", "http://y/"),
			link("x", "http://z/")
		));
	}

	@Test
	public void getLinksWithUnknownRelReturnsEmptyList()
	{
		server().enqueue(new MockResponse().setBody(""));

		List<Link> actual = newBrowser().get(url(server()))
			.getLinks("x");

		assertThat("links", actual, is(empty()));
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getForm tests
	// ----------------------------------------------------------------------------------------------------------------

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

		Form actual = newBrowser().get(url(server()))
			.getForm("x");

		assertThat("form", actual.getName(), is("x"));
	}

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

		MicrodataDocument document = newBrowser().get(url(server()));
		document.getForm("f")
			.setControlValue("c", "x");

		assertThat("form control value", document.getForm("f").getControlValue("c"), is("x"));
	}

	@Test
	public void getFormWithUnknownNameThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""));

		MicrodataDocument document = newBrowser().get(url(server()));

		thrown().expect(FormNotFoundException.class);
		thrown().expectMessage("x");

		document.getForm("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getCookie tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void getCookieReturnsValue()
	{
		server().enqueue(new MockResponse().addHeader("Set-Cookie", "x=y"));

		String actual = newBrowser().get(url(server()))
			.getCookie("x");

		assertThat("cookie", actual, is("y"));
	}

	@Test
	public void getCookieWithUnknownNameThrowsException()
	{
		server().enqueue(new MockResponse());

		MicrodataDocument document = newBrowser().get(url(server()));

		thrown().expect(CookieNotFoundException.class);
		thrown().expectMessage("x");

		document.getCookie("x");
	}

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

	@Test
	public void unwrapWithUnknownTypeThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""));

		MicrodataDocument document = newBrowser().get(url(server()));

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

		document.unwrap(Void.class);
	}
}


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

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

import com.squareup.okhttp.mockwebserver.MockResponse;

import static org.hamcrest.CoreMatchers.is;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerMatchers.get;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.takeRequest;
import static org.hobsoft.microbrowser.tck.support.mockwebserver.MockWebServerUtils.url;
import static org.junit.Assert.assertThat;

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

	public void getNameReturnsName()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='x'/>"
			+ ""));

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

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

	// ----------------------------------------------------------------------------------------------------------------
	// getControl tests
	// ----------------------------------------------------------------------------------------------------------------

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

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

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

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

		Form form = newBrowser().get(url(server()))
			.getForm("f");

		thrown().expect(ControlNotFoundException.class);
		thrown().expectMessage("x");

		form.getControl("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getControlValue tests
	// ----------------------------------------------------------------------------------------------------------------

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

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

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

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

		Form form = newBrowser().get(url(server()))
			.getForm("f");

		thrown().expect(ControlNotFoundException.class);
		thrown().expectMessage("x");

		form.getControlValue("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// setControlValue tests
	// ----------------------------------------------------------------------------------------------------------------

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

		Form form = newBrowser().get(url(server()))
			.getForm("f");
		form.setControlValue("x", "y");

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

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

		Form form = newBrowser().get(url(server()))
			.getForm("f");

		thrown().expect(ControlNotFoundException.class);
		thrown().expectMessage("x");

		form.setControlValue("x", "y");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// getControlGroup tests
	// ----------------------------------------------------------------------------------------------------------------

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

		ControlGroup actual = newBrowser().get(url(server()))
			.getForm("f")
			.getControlGroup("x");

		assertThat("form control group", actual.getName(), is("x"));
	}

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

		Form form = newBrowser().get(url(server()))
			.getForm("f");

		thrown().expect(ControlNotFoundException.class);
		thrown().expectMessage("x");

		form.getControlGroup("x");
	}

	// ----------------------------------------------------------------------------------------------------------------
	// submit tests
	// ----------------------------------------------------------------------------------------------------------------

	@Test
	public void submitWhenSubmitInputSubmitsRequest() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f' action='/x'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ ""));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()).getPath(), is("/x"));
	}

	@Test
	public void submitWhenSubmitButtonSubmitsRequest() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f' action='/x'>"
			+ "<button type='submit'/>"
			+ "</form>"
			+ ""));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()).getPath(), is("/x"));
	}

	@Test
	public void submitWhenDefaultButtonSubmitsRequest() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f' action='/x'>"
			+ "<button/>"
			+ "</form>"
			+ ""));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()).getPath(), is("/x"));
	}

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

		Form form = newBrowser().get(url(server()))
			.getForm("f");

		thrown().expect(IllegalStateException.class);
		thrown().expectMessage("Missing form submit button");

		form.submit();
	}

	@Test
	public void submitWhenNoMethodSubmitsGetRequest() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f' action='/x'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ ""));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server()))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()), is(get("/x")));
	}

	@Test
	public void submitWhenNoActionSubmitsRequest() throws InterruptedException
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ ""));
		server().enqueue(new MockResponse());

		newBrowser().get(url(server(), "/x"))
			.getForm("f")
			.submit();

		server().takeRequest();
		assertThat("request", takeRequest(server()).getPath(), is("/x"));
	}

	@Test
	public void submitWhenInvalidActionThrowsException()
	{
		server().enqueue(new MockResponse().setBody(""
			+ "<form name='f' action='x:/a'>"
			+ "<input type='submit'/>"
			+ "</form>"
			+ ""));

		Form form = newBrowser().get(url(server()))
			.getForm("f");

		thrown().expect(IllegalArgumentException.class);
		thrown().expectMessage("Invalid action: x:/a");

		form.submit();
	}

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

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

		Form form = newBrowser().get(url(server()))
			.getForm("x");

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

		form.unwrap(Void.class);
	}
}


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

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.compiler.PositionalTermTraverser;
import com.thesett.common.util.doublemaps.SymbolTable;
import com.thesett.text.api.model.TextTableModel;

/**
 * SourceClausePrinter prints the source of a compiled clause in a vertically spread out way, so that each predicate
 * call and argument appears in its own row. This leaves enough space vertically to print the compiled byte code
 * alongside.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Print each predicate call and argument in its own row.
 *

 *
 * @author Rupert Smith
 */
public class SourceClausePrinter extends BasePrinter
{
    /**
     * Creates a printer.
     *
     * @param interner    The name interner.
     * @param symbolTable The compiler symbol table.
     * @param traverser   The positional context traverser.
     * @param column      The column to print to.
     * @param table       The table to inform of cell sizes and positions.
     */
    public SourceClausePrinter(VariableAndFunctorInterner interner, SymbolTable<Integer, String, Object> symbolTable,
        PositionalTermTraverser traverser, int column, TextTableModel table)
    {
        super(interner, symbolTable, traverser, column, table);
    }

    /** {@inheritDoc} */
    protected void enterFunctor(Functor functor)
    {
        String showContextAs = interner.getFunctorName(functor) + (functor.isAtom() ? "" : "(");

        int delta = showContextAs.length();

        addLineToRow(indent + showContextAs);
        nextRow();

        indent = indenter.generateTraceIndent(delta);
    }

    /** {@inheritDoc} */
    protected void leaveFunctor(Functor functor)
    {
        String toAppend = indent;
        boolean addData = false;

        if (functor.isCompound())
        {
            toAppend += ")";
            addData = true;
        }

        if (!traverser.isInHead() && !traverser.isLastBodyFunctor() && traverser.isTopLevel())
        {
            toAppend += ",";
            addData = true;
        }

        if (traverser.isInHead() && traverser.isTopLevel())
        {
            toAppend += " :-";
            addData = true;
        }

        if (addData)
        {
            addLineToRow(toAppend);
            nextRow();
        }

        indent = indenter.generateTraceIndent(-indenter.getLastDelta());
    }

    /** {@inheritDoc} */
    protected void enterVariable(Variable variable)
    {
        String showContextAs = interner.getVariableName(variable);

        int delta = showContextAs.length();

        addLineToRow(indent + showContextAs);
        nextRow();

        indent = indenter.generateTraceIndent(delta);
    }
}


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

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

import org.hobsoft.microbrowser.Control;
import org.hobsoft.microbrowser.ControlGroup;
import org.hobsoft.microbrowser.ControlNotFoundException;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

/**
 * Default {@code ControlGroup} implementation.
 */
public class DefaultControlGroup implements ControlGroup
{
	// ----------------------------------------------------------------------------------------------------------------
	// constants
	// ----------------------------------------------------------------------------------------------------------------

	private static final String UNCHECKED_VALUE = "";

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

	private final List<Control> controls;

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

	public DefaultControlGroup(List<Control> controls)
	{
		this.controls = unmodifiableList(controls);
	}

	// ----------------------------------------------------------------------------------------------------------------
	// Controls methods
	// ----------------------------------------------------------------------------------------------------------------

	public String getName()
	{
		return controls.get(0).getName();
	}

	public List<Control> getControls()
	{
		return controls;
	}

	public Control getControl(String value)
	{
		for (Control control : controls)
		{
			if (value.equals(getCheckedValue(control)))
			{
				return control;
			}
		}

		throw new ControlNotFoundException(getName(), value);
	}

	public List<String> getValues()
	{
		List<String> values = new ArrayList<String>();

		for (Control control : controls)
		{
			String value = control.getValue();

			if (!value.isEmpty())
			{
				values.add(value);
			}
		}

		return values;
	}

	public void setValues(String... values)
	{
		List<String> valuesList = new ArrayList<String>(asList(values));

		for (Control control : controls)
		{
			if (control instanceof CheckableControl)
			{
				CheckableControl checkableControl = (CheckableControl) control;
				String checkedValue = checkableControl.getCheckedValue();
				boolean check = valuesList.remove(checkedValue);

				if (check)
				{
					control.setValue(checkedValue);
				}
				else if (checkableControl.isUncheckable())
				{
					control.setValue(UNCHECKED_VALUE);
				}
			}
		}

		if (!valuesList.isEmpty())
		{
			throw new IllegalArgumentException("Invalid control values: " + valuesList);
		}
	}

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

	private static String getCheckedValue(Control control)
	{
		if (control instanceof CheckableControl)
		{
			return ((CheckableControl) control).getCheckedValue();
		}

		return control.getValue();
	}
}


/*
 * 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.wam.compiler.DefaultBuiltIn;
import com.thesett.aima.logic.fol.wam.compiler.WAMInstruction;
import com.thesett.common.util.SizeableLinkedList;

/**
 * Implements the true atom. This always succeeds without generating any choice points.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Always succeed, consuming the 'true' goal.
 *

 *
 * @author Rupert Smith
 */
public class True extends BaseBuiltIn
{
    /**
     * Creates a true 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 True(Functor functor, DefaultBuiltIn defaultBuiltIn)
    {
        super(functor, defaultBuiltIn);
    }

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

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

        if (isLastBody)
        {
            if (!chainRule)
            {
                result.add(new WAMInstruction(WAMInstruction.WAMInstructionSet.Deallocate));
            }

            result.add(new WAMInstruction(WAMInstruction.WAMInstructionSet.Proceed));
        }

        return result;
    }

    /**
     * Creates a string representation of this functor, mostly used for debugging purposes.
     *
     * @return A string representation of this functor.
     */
    public String toString()
    {
        return "True";
    }
}


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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.thesett.common.error.NotImplementedException;

/**
 * GlobalWriteLockWithWriteBehindTxMethod provides a global read-write lock based scheme, with write-behind operations,
 * for managing transactional updates to a resource.
 *
 *
Exclusive write locks are taken to ensure that only transaction at a time may commit its write-behind operations.
 * The write-behind operations are held in a map, per transaction id, and this map is a ConcurrentHashMap, so that
 * mutliple transactions can create their write-behind operations in parallel.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Apply pending write behind operations.
 *
Forget pending write behind operations.
 *
Take record of and synchronize updating operations on a transactional resource.
 *
Take record of and synchronize reading operations on a transactional resource.
 *

 *
 * @author Rupert Smith
 * @todo   When transaction ids are discarded, are their write-behind caches cleaned up? Caches are cleaned up on commit
 *         or rollback. Dangling transactions will cause leaks, need to think about how to handle them.
 * @todo   Need to decide what to do if the commit is interrupted? If commit thread is interrupted presumably that means
 *         that it is deliberately being brought down immediately, with no intention to complete the transaction. It
 *         will be possible to retry, but that does not seem to be the intention of interrupting a commit. I think an
 *         exception needs to be added to the commit signature to allow for commit failures. For the moment it gets
 *         rethrown as a runtime.
 */
public class GlobalWriteLockWithWriteBehindTxMethod<E> implements TxMethod
{
    /** Holds the isolation level of the resource. */
    private IsolationLevel isolationLevel = IsolationLevel.ReadCommitted;

    /** Holds the global write lock that ensures that only one writer at a time is allowed to alter the resource. */
    private final ReadWriteLock globalLock = new ReentrantReadWriteLock();

    /** A condition on the global write lock that is used to signal when it becomes unowned by any transaction. */
    private final Condition globalWriteLockFree = globalLock.writeLock().newCondition();

    /** Holds the transaction id of the only transaction that is allowed to use the global write lock. */
    private TxId globalWriteLockTxId;

    /** Holds the write-behind cache of changes made by transactions. */
    private final Map<TxId, List<TxOperation>> txWrites = new ConcurrentHashMap<TxId, List<TxOperation>>();

    /**
     * When operating in transactional mode causes any changes since the last commit to be made visible to the search
     * method.
     */
    public void commit()
    {
        TxId txId = null;

        // Check if in a higher transactional mode than none, otherwise commit does nothing.
        if (!getIsolationLevel().equals(IsolationLevel.None))
        {
            // Extract the current transaction id.
            txId = TxManager.getTxIdFromThread();

            // Wait until the global write lock can be acquired by this transaction.
            try
            {
                acquireGlobalWriteLock(txId);
            }
            catch (InterruptedException e)
            {
                // The commit was interrupted, so cannot succeed.
                throw new IllegalStateException("Interrupted whilst commit is waiting for global write lock.", e);
            }

            // Check that this transaction has made changes to be committed.
            List<TxOperation> alterations = txWrites.get(txId);

            try
            {
                if (alterations != null)
                {
                    // Loop through all the writes that the transaction wants to apply to the resource.
                    for (TxOperation nextAlteration : alterations)
                    {
                        // Apply the change and update the term resource.
                        nextAlteration.execute();
                    }

                    // Clear the write behind cache for this transaction as its work has been completed.
                    txWrites.remove(txId);
                }
            }
            finally
            {
                // Release the global write lock.
                releaseGlobalWriteLock();
            }
        }
    }

    /**
     * When operation in transactional mode causes any changes since the last commit to be dropped and never made
     * visible to the search method.
     */
    public void rollback()
    {
        TxId txId = null;

        // Check if in a higher transactional mode than none, otherwise commit does nothing.
        if (!getIsolationLevel().equals(IsolationLevel.None))
        {
            // Extract the current transaction id.
            txId = TxManager.getTxIdFromThread();

            // Check that this transaction has made changes to be rolled back.
            List<TxOperation> alterations = txWrites.get(txId);

            if (alterations != null)
            {
                // Loop through all the writes that the transaction wants to apply to the resource.
                for (TxOperation nextAlteration : alterations)
                {
                    // Cancel the operation.
                    nextAlteration.cancel(false);
                }
            }

            // Discard all the changes that the transaction was going to make.
            txWrites.remove(txId);
        }
    }

    /**
     * Requests an operation that alters the transactional resource. This may be blocked until an appropriate lock can
     * be acquired, delayed until commit time, or actioned upon a copy of the data structure private to a transaction
     * branch.
     *
     *
This stores the requested operation for execution at commit time, unless running in non-transactional mode in
     * which case it is run immediately.
     *
     * @param op The write operation as a future.
     */
    public void requestWriteOperation(TxOperation op)
    {
        // Check if in a higher transactional mode than none and capture the transaction id if so.
        TxId txId = null;

        if (getIsolationLevel().compareTo(IsolationLevel.None) > 0)
        {
            // Extract the current transaction id.
            txId = TxManager.getTxIdFromThread();

            // Ensure that this resource is enlisted with the current session.
            enlistWithSession();

        }

        // For non-transactional isolation levels, apply the requested operation immediately.
        if (getIsolationLevel().equals(IsolationLevel.None))
        {
            op.execute();
        }

        // Add the operation to the transaction write-behind cache for the transaction id, if using transactional
        // isolation, to defer the operation untill commit time.
        else
        {
            addCachedOperation(txId, op);
        }
    }

    /**
     * Requests an operation that only reads from the transactional resource. If neccessary locks may be acquired, or
     * the operation may otherwise be delayed, or read from a copy of the data, as appropriate.
     *
     * @param  op The read operation as a future.
     *
     * @return The value read from the resource.
     */
    public E requestReadOperation(TxOperation op)
    {
        throw new NotImplementedException();

        // Ensure that this resource is enlisted with the current session.
        // enlistWithSession();

        // Acquire a read lock.
        // Apply the operation.
        // Release the read lock.
    }

    /**
     * Gets this transactional methods isolation level.
     *
     * @return This transactional methods isolation level.
     */
    public IsolationLevel getIsolationLevel()
    {
        return isolationLevel;
    }

    /**
     * Sets this transactional methods isolation level.
     *
     * @param isolationLevel This transactional methods new isolation level.
     */
    public void setIsolationLevel(IsolationLevel isolationLevel)
    {
        this.isolationLevel = isolationLevel;
    }

    /**
     * Adds a transactional operation to the transactional write-behind cache for the specified transaction. If no cache
     * exists for the specified transaction id, a new one is created.
     *
     * @param txId                 The transaction id to store the operation against.
     * @param cachedWriteOperation The operation to store.
     */
    private void addCachedOperation(TxId txId, TxOperation cachedWriteOperation)
    {
        List<TxOperation> writeCache = txWrites.get(txId);

        if (writeCache == null)
        {
            writeCache = new ArrayList<TxOperation>();
            txWrites.put(txId, writeCache);
        }

        writeCache.add(cachedWriteOperation);
    }

    /**
     * Waits until a global read lock can be acquired by the specified transaction.
     *
     * @param txId The transaction id to acquite a global read lock for.
     */
    private void acquireGlobalReadLock(TxId txId)
    {
        // Get the global read lock to ensure only one thread at a time can execute this code.
        globalLock.readLock().lock();
    }

    /** Releases the global write lock from being assigned to a transaction. */
    private void releaseGlobalReadLock()
    {
        // Get the global write lock to ensure only one thread at a time can execute this code.
        globalLock.readLock().unlock();
    }

    /**
     * Waits until the global write lock can be acquired by the specified transaction.
     *
     * @param  txId The transaction id to acquite the global write lock for.
     *
     * @throws InterruptedException If interrupted whilst waiting for the global write lock.
     */
    private void acquireGlobalWriteLock(TxId txId) throws InterruptedException
    {
        // Get the global write lock to ensure only one thread at a time can execute this code.
        globalLock.writeLock().lock();

        // Use a try block so that the corresponding finally block guarantees release of the thread lock.
        try
        {
            // Check that this transaction does not already own the lock.
            if (!txId.equals(globalWriteLockTxId))
            {
                // Wait until the write lock becomes free.
                while (globalWriteLockTxId != null)
                {
                    globalWriteLockFree.await();
                }

                // Assign the global write lock to this transaction.
                globalWriteLockTxId = txId;
            }
        }
        finally
        {
            // Ensure that the thread lock is released once assignment of the write lock to the transaction is complete.
            globalLock.writeLock().unlock();
        }
    }

    /** Releases the global write lock from being assigned to a transaction. */
    private void releaseGlobalWriteLock()
    {
        // Get the global write lock to ensure only one thread at a time can execute this code.
        globalLock.writeLock().lock();

        // Use a try block so that the corresponding finally block guarantees release of the thread lock.
        try
        {
            // Release the global write lock, assigning it to no transaction.
            globalWriteLockTxId = null;

            // Signal that the write lock is now free.
            globalWriteLockFree.signal();
        }

        // Ensure that the thread lock is released once assignment of the write lock to the transaction is complete.
        finally
        {
            globalLock.writeLock().unlock();
        }
    }

    /**
     * Enlists this transactional resource with the current session. If no session exists this will fail.
     *
     * @throws IllegalStateException If this resource is transactional and accessed outside of a transaction.
     */
    private void enlistWithSession()
    {
        TxSession session = TxSessionImpl.getCurrentSession();

        // Ensure that this resource is being used within a session.
        if (session == null)
        {
            throw new IllegalStateException("Cannot access transactional resource outside of a session.");
        }

        // Ensure that this resource is enlisted with the session.
        session.enlist(this);
    }
}


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

import com.bazaarvoice.jless.ast.node.MediaQueryNode;
import com.bazaarvoice.jless.ast.node.RuleSetNode;
import com.bazaarvoice.jless.ast.node.ScopeNode;
import com.bazaarvoice.jless.ast.node.SelectorGroupNode;
import com.bazaarvoice.jless.ast.node.SelectorNode;
import com.bazaarvoice.jless.ast.node.SelectorSegmentNode;
import com.bazaarvoice.jless.ast.node.SpacingNode;
import com.bazaarvoice.jless.ast.node.WhiteSpaceCollectionNode;
import com.bazaarvoice.jless.ast.util.NodeTreeUtils;

import java.util.List;

public class NestedMediaQueries extends InclusiveNodeVisitor {

    /**
     * Locates nested MediaQueryNode inside RuleSetNode,
     * separates RuleSetNode and MediaQueryNode
     *
     * If MediaQueryNode has nested RuleSetNodes than to every
     * nested selectors will added selector of input RuleSetNode
     *
     * If MediaQueryNode has other nodes except WhiteSpaceCollectionNode and
     * RuleSetNode than all of that will be wrapped with new RuleSetNode with
     * the same selectors as has input RuleSetNode
     */
    @Override
    public boolean enter(RuleSetNode ruleSetNode) {

        ScopeNode scopeNode = NodeTreeUtils.getFirstChild(ruleSetNode, ScopeNode.class);
        SelectorGroupNode selectorGroupNode = NodeTreeUtils.getFirstChild(ruleSetNode, SelectorGroupNode.class);

        if (selectorGroupNode == null) {
            return true;
        }

        List<SelectorNode> selectorNodes = NodeTreeUtils.getChildren(selectorGroupNode, SelectorNode.class);

        if (selectorNodes.size() < 0) {
            return true;
        }

        List<MediaQueryNode> mediaQueryNodes = NodeTreeUtils.getAndRemoveChildren(scopeNode, MediaQueryNode.class);

        for (MediaQueryNode mediaQueryNode : mediaQueryNodes) {
            ScopeNode mediaScopeNode = NodeTreeUtils.getFirstChild(mediaQueryNode, ScopeNode.class);

            List<RuleSetNode> nestedRuleSets = NodeTreeUtils.getAndRemoveChildren(mediaScopeNode, RuleSetNode.class);

            // if scope node for media query has anything more but whitespaces and rule sets than wrap it with rule set with the same selector group as outer rule set has
            if (mediaScopeNode.getChildren().size() > NodeTreeUtils.getChildren(mediaScopeNode, WhiteSpaceCollectionNode.class).size()) {
                RuleSetNode newRuleSetNode = new RuleSetNode();
                ScopeNode newScopeNode = new ScopeNode();
                newRuleSetNode.addChild(selectorGroupNode.clone());
                newRuleSetNode.addChild(newScopeNode);

                NodeTreeUtils.moveChildren(mediaScopeNode, newScopeNode);

                mediaScopeNode.clearChildren();
                mediaScopeNode.addChild(newRuleSetNode);
            }

            // adding outer selectors to every nested selectors
            for (RuleSetNode nestedRuleSet : nestedRuleSets) {
                List<SelectorGroupNode> nestedSelectorGroupNodes = NodeTreeUtils.getChildren(nestedRuleSet, SelectorGroupNode.class);

                for (SelectorGroupNode nestedSelectorGroupNode : nestedSelectorGroupNodes) {
                    List<SelectorNode> nestedSelectorNodes = NodeTreeUtils.getAndRemoveChildren(nestedSelectorGroupNode, SelectorNode.class);
                    NodeTreeUtils.getAndRemoveChildren(nestedSelectorGroupNode, SpacingNode.class);

                    for (SelectorNode selectorNode : selectorNodes) {
                        for (SelectorNode nestedSelectorNode : nestedSelectorNodes) {
                            if (nestedSelectorNode.getChildren().get(0) != null) {
                                if (nestedSelectorNode.getChildren().get(0) instanceof SelectorSegmentNode) {
                                    SelectorSegmentNode selectorSegmentNode = (SelectorSegmentNode) nestedSelectorNode.getChildren().get(0);
                                    selectorSegmentNode.setCombinator(" ");
                                }
                            }

                            for (int j = selectorNode.getChildren().size() - 1; j >= 0; j--) {
                                if (selectorNode.getChildren().get(j) instanceof SelectorSegmentNode) {
                                    SelectorSegmentNode selectorSegmentNode = (SelectorSegmentNode) selectorNode.getChildren().get(j).clone();

                                    nestedSelectorNode.addChild(0, selectorSegmentNode);
                                }
                            }

                            nestedSelectorGroupNode.addChild(nestedSelectorNode);
                            nestedSelectorGroupNode.addChild(new SpacingNode(" "));
                        }
                    }
                }
                mediaScopeNode.addChild(nestedRuleSet);
            }

            if (ruleSetNode.getParent() != null) {
                ruleSetNode.getParent().addChild(new SpacingNode("\n"));
                ruleSetNode.getParent().addChild(mediaQueryNode);
            }
        }

        return true;
    }
}