/*
 * 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.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
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;
import com.thesett.aima.state.restriction.MaxRestriction;
import com.thesett.aima.state.restriction.MinRestriction;
import com.thesett.aima.state.restriction.TypeRestriction;

/**
 * Implements an integer range type. This allows a sequential range of ints to be defined as a named type.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Maintain a set of named int range types.
 *
 Provide all integers within the types range.
 *
 Provide a default value that lies in the range.
 *
 Check an integer value against the range type.
 *

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

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

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

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

    /**
     * Creates an instance of an integer 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 IntRangeType(String name, int min, int max)
    {
        typeName = name;
        minValue = min;
        maxValue = max;

        restrictions = new LinkedList<TypeRestriction>();

        if (minValue > Integer.MIN_VALUE)
        {
            restrictions.add(new MinRestriction(minValue));
        }

        if (maxValue < Integer.MAX_VALUE)
        {
            restrictions.add(new MaxRestriction(maxValue));
        }
    }

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

    /**
     * Creates a new int range type with the specified name, if it does not already exist.
     *
     * @param  name The name of the int 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 int 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, int min, int 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 (INT_RANGE_TYPES)
        {
            // Add the newly created type to the map of all types.
            IntRangeType newType = new IntRangeType(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.
            IntRangeType oldType = INT_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
            {
                INT_RANGE_TYPES.put(name, newType);

                return newType;
            }
        }
    }

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

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

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

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

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

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

    /**
     * Checks an integer 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(int value)
    {
        return (value >= minValue) && (value <= maxValue);
    }

    /**
     * {@inheritDoc}
     *
     *
Beware that as this operation creates a fully expanded set of the integer range in memory, large ranges may
     * use a lot of memory. Consider using the {@link #getAllPossibleValuesIterator()} method instead, as it lazily
     * creates the values on demand.
     */
    public Set<Integer> getAllPossibleValuesSet() throws InfiniteValuesException
    {
        Set<Integer> results = new HashSet<Integer>();

        for (int i = minValue; i <= maxValue; i++)
        {
            results.add(i);
        }

        return results;
    }

    /** {@inheritDoc} */
    public Iterator<Integer> getAllPossibleValuesIterator() throws InfiniteValuesException
    {
        return new Iterator<Integer>()
            {
                int current = minValue;

                /** {@inheritDoc} */
                public boolean hasNext()
                {
                    return current <= maxValue;
                }

                /** {@inheritDoc} */
                public Integer next()
                {
                    if (hasNext())
                    {
                        int result = current;
                        current++;

                        return result;
                    }
                    else
                    {
                        throw new NoSuchElementException("No more elements less than or equal to max.");
                    }
                }

                /** {@inheritDoc} */
                public void remove()
                {
                    throw new UnsupportedOperationException("This iterator does not support 'remove'.");
                }
            };
    }

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

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

        IntRangeType that = (IntRangeType) o;

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

    /**
     * Provides a hashcode based on the name, and int range of this type.
     *
     * @return A hashcode based on the name, and int range of this type.
     */
    public int hashCode()
    {
        int result;
        result = minValue;
        result = (31 * result) + maxValue;
        result = (31 * result) + ((typeName != null) ? typeName.hashCode() : 0);

        return result;
    }
}


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

import com.thesett.aima.logic.fol.DoubleLiteral;
import com.thesett.aima.logic.fol.IntLiteral;
import com.thesett.aima.logic.fol.NumericType;
import com.thesett.aima.logic.fol.Term;

/**
 * Exponential implements the multiplication operator '**' in Prolog.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Raise one numeric value to the power of the other argument using integer or floating point arithmetic.
 *      {@link NumericType}.
 *

 *
 * @author Rupert Smith
 */
public class Exponential extends BinaryArithmeticOperator
{
    /**
     * Creates a new '**' expression operator.
     *
     * @param name      The interned name of the operator.
     * @param arguments The arguments, of which there must be two.
     */
    public Exponential(int name, Term[] arguments)
    {
        super(name, arguments);
    }

    /**
     * Evaluates the arithmetic operator on its two numeric arguments.
     *
     * @param  firstNumber  The first argument.
     * @param  secondNumber The second argument.
     *
     * @return The result of performing the arithmetic operator on its arguments.
     */
    protected NumericType evaluate(NumericType firstNumber, NumericType secondNumber)
    {
        // If either of the arguments is a real number, then use real number arithmetic, otherwise use integer arithmetic.
        if (firstNumber.isInteger() && secondNumber.isInteger())
        {
            int n1 = firstNumber.intValue();
            int n2 = secondNumber.intValue();

            int result = 1;

            for (int i = 0; i < n2; i++)
            {
                result *= n1;
            }

            return new IntLiteral(result);
        }
        else
        {
            return new DoubleLiteral(Math.pow(firstNumber.doubleValue(), secondNumber.doubleValue()));
        }
    }
}


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

import java.util.List;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.TermUtils;

/**
 * Disjunction implements the Prolog disjunction operator ';' that sets up multiple choice points potentially leading to
 * multiple solutions.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Implement the disjunction operator.
 *      {@link Functor}, {@link TermUtils}, {@link ResolutionState}.
 *

 *
 * @author Rupert Smith
 */
public class Disjunction extends BuiltInFunctor
{
    /** Holds the interned name of the operator matching this built in. */
    private int name;

    /** Holds the disjunctiv choice points that lead to states following this proof step. */
    private List<Functor> disjuncts;

    /**
     * Creates a disjunction built-in to implement the specified functor.
     *
     * @param functor The functor to implement as a built-in.
     */
    public Disjunction(Functor functor)
    {
        super(functor);
    }

    /** {@inheritDoc} */
    public boolean proofStep(ResolutionState state)
    {
        // Flatten the head goal on the disjunction operator to produce a series of choice points.
        Functor goalTerm = state.getGoalStack().poll().getFunctor();

        disjuncts = TermUtils.flattenTerm(goalTerm, Functor.class, state.getInterner().internFunctorName(";", 2));

        return true;
    }

    /** {@inheritDoc} */
    public void createContinuationStates(ResolutionState state)
    {
        for (Functor disjunct : disjuncts)
        {
            BuiltInFunctor newGoal = state.getBuiltInTransform().apply(disjunct);
            newGoal.setParentChoicePointState(state);
            state.createContinuationStatesForGoal(newGoal);
        }
    }
}


/*
Copyright (c) 2013, Groupon, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

Neither the name of GROUPON nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.groupon.novie.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.groupon.novie.SchemaDefinition;
import com.groupon.novie.engine.ColumnDataType;
import com.groupon.novie.internal.engine.schema.AbstractSqlColumn;
import com.groupon.novie.internal.engine.schema.DimensionTable;
import com.groupon.novie.internal.engine.schema.FactTable;
import com.groupon.novie.internal.engine.schema.PhysicalSqlColumn;
import com.groupon.novie.internal.engine.schema.SqlTable;

/**
 * Default implementation of {@link SchemaDefinition}
 *
 * @author thomas
 */

public final class SchemaDefinitionImpl implements SchemaDefinition {

    private static final Logger LOG = LoggerFactory.getLogger(SchemaDefinitionImpl.class);

    protected Map<String, Map<String, SqlTable>> predecessorRelation = Maps.newHashMap();

    /**
     * Map
     * <Table name, SQLTable>
     * Contains all the tables
     */
    private Map<String, SqlTable> sqlTables = Maps.newHashMap();
    /**
     * Map<DimensionName,Dimension>
     */
    private Map<String, DimensionTable> dimensions = Maps.newHashMap();
    /**
     * Map<Pair<originalDimesionName, tzName>,AlternateDimension> All
     * compatibility check must be done during the insertion.
     */
    private Map<Pair<String, String>, DimensionTable> alternateDimensions = new HashMap<Pair<String, String>, DimensionTable>();
    /**
     * Collection<DimensionName> in upper case.
     */
    private Collection<String> mandatoryDimensionNames = new HashSet<String>();

    /**
     * Set<Pair<Pair<table1,col>,Pair<table2,col>>>
     */
    private Set<Pair<Pair<String, String>, Pair<String, String>>> fkRelationshipSet = Sets.newHashSet();
    private FactTable factTable;
    private String databaseName = null;
    private String endPointName;
    /**
     * Map<tzAliase,tzName>
     */
    private Map<String, String> tzNamesAliases = new HashMap<String, String>();

    private String defaultTimezone = null;

    private SchemaDefinitionImpl(String endPointName) {
        this.endPointName = endPointName;
    }

    /**
     * Entry point to create a new instance of this implementation.
     *
     * @param endPointName
     * @return a new instance of this implementation of the
     * {@link SchemaDefinition}.
     */
    public static SchemaDefinitionImpl createInstance(String endPointName) {
        return new SchemaDefinitionImpl(endPointName);
    }

    // Interface methods

    /*
     * (non-Javadoc)
     *
     * @see
     * com.groupon.novie.SchemaConfig#areLinked(com.groupon.novie.internal.engine
     * .internal.SqlTable, com.groupon.novie.internal.engine.internal.SqlTable)
     */
    @Override
    public boolean areLinked(SqlTable table1, SqlTable table2) {
        return predecessorRelation.get(table1.getTableName()).containsKey(table2.getTableName());
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.groupon.novie.SchemaConfig#isLinkedToFactTable(com.groupon.novie.
     * internal.engine.internal.SqlTable)
     */
    @Override
    public boolean isLinkedToFactTable(SqlTable table) {
        return areLinked(table, factTable);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getDimensionByName(java.lang.String,
     * java.lang.String)
     */
    @Override
    public DimensionTable getDimensionByName(String dimensionName, String tzNameAlias) {
        DimensionTable dimensionTable;
        if (StringUtils.isBlank(tzNameAlias) || (StringUtils.isNotBlank(tzNameAlias) && tzNameAlias.equals(getDefaultTimezone()))) {
            dimensionTable = dimensions.get(dimensionName.toUpperCase());
        } else {
            if (!isTimeZoneSupported(tzNameAlias)) {
                LOG.error(tzNameAlias + " TZ is not supported, do not consider it.");
                dimensionTable = dimensions.get(dimensionName.toUpperCase());
            } else {
                String tzName = tzNamesAliases.get(tzNameAlias);
                dimensionTable = alternateDimensions.get(Pair.of(dimensionName.toUpperCase(), tzName));
                if (dimensionTable == null) {
                    // No alternateTable for this dimension/tz pair
                    dimensionTable = dimensions.get(dimensionName.toUpperCase());
                }
            }

        }
        if (dimensionTable == null) {
            throw new IllegalArgumentException("Invalid SqlTable for the dimension: " + dimensionName);
        }
        return dimensionTable;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getMeasuresColumn()
     */
    @Override
    public List<AbstractSqlColumn> getMeasuresColumn() {
        return Collections.unmodifiableList(Lists.newArrayList(factTable.getMeasuresColumn().values()));
    }

    /**
     * {@inheritDoc SchemaDefinition}
     * {@link SchemaDefinition#getMeasuresByName(String)}
     */
    @Override
    public Optional<AbstractSqlColumn> getMeasuresByName(String informationName) {
        for (AbstractSqlColumn column : factTable.getMeasuresColumn().values()) {
            if (column.getBusinessName().equalsIgnoreCase(informationName)) {
                return Optional.of(column);
            }
        }
        return Optional.absent();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getDatabaseName()
     */
    @Override
    public String getDatabaseName() {
        return databaseName;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getEndPointName()
     */
    @Override
    public String getEndPointName() {
        return endPointName;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * com.groupon.novie.SchemaConfig#getInformationColumn(com.groupon.novie
     * .internal.engine.internal.DimensionTable, java.lang.String)
     */
    @Override
    public AbstractSqlColumn getInformationColumn(DimensionTable dimension, String informationName) {
        final AbstractSqlColumn sqlTableColumn = dimension.getSqlTableColumnByInformationName(informationName);
        if (sqlTableColumn == null) {
            throw new IllegalArgumentException("Invalid sqlTableColumn for the informationName: " + informationName);
        }
        return sqlTableColumn;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#isValidDimension(java.lang.String)
     */
    @Override
    public boolean isValidDimension(String dimensionName) {
        return isValidDimension(dimensionName, null);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#isValidDimension(java.lang.String,
     * java.lang.String)
     */
    @Override
    public boolean isValidDimension(String dimensionName, String informationName) {
        if (StringUtils.isNotBlank(dimensionName)) {
            DimensionTable dim = dimensions.get(dimensionName.toUpperCase());
            if (dim == null) {
                return false;
            }
            if (StringUtils.isBlank(informationName)) {
                return true;
            }
            if (dim.getSqlTableColumnByInformationName(informationName.toUpperCase()) != null) {
                return true;
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     * @see com.groupon.novie.SchemaDefinition#isMandatoryDimension(java.lang.String)
     */
    @Override
    public boolean isMandatoryDimension(String dimensionName) {
        if (isValidDimension(dimensionName, null)) {
            return mandatoryDimensionNames.contains(dimensionName.toUpperCase(Locale.ENGLISH));
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getInformationType(java.lang.String,
     * java.lang.String)
     */
    @Override
    public ColumnDataType getInformationType(String dimensionName, String informationName) {
        if (StringUtils.isNotBlank(dimensionName)) {
            DimensionTable dim = dimensions.get(dimensionName.toUpperCase());
            if (dim == null) {
                throw new IllegalArgumentException("Unknown dimension named " + dimensionName + ".");
            }
            if (StringUtils.isBlank(informationName)) {
                return dim.getDefaultSearchColumn().getColumnType();
            }
            AbstractSqlColumn information = dim.getSqlTableColumnByInformationName(informationName);
            if (information == null) {
                throw new IllegalArgumentException("Unknown information named " + informationName + " for dimension " + dimensionName + ".");
            }
            return information.getColumnType();
        }
        throw new IllegalArgumentException("Dimension name can't be null or blank");
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#isTimeZoneSupported(java.lang.String)
     */
    @Override
    public boolean isTimeZoneSupported(String tzName) {
        return tzNamesAliases.containsKey(tzName);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getDefaultTimezone()
     */
    @Override
    public String getDefaultTimezone() {
        return defaultTimezone;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.groupon.novie.SchemaConfig#getMandatoryDimension()
     */
    @Override
    public Collection<String> getMandatoryDimension() {
        return Collections.unmodifiableCollection(mandatoryDimensionNames);
    }

    // #######

    public void setDatabaseName(String dbName) {
        this.databaseName = dbName;
    }

    public void addSqlForeignKey(Pair<String, String> column1, Pair<String, String> column2) {
        fkRelationshipSet.add(ImmutablePair.of(column1, column2));
    }

    private void buildSqlForeignKey(PhysicalSqlColumn column1, PhysicalSqlColumn column2) {
        if (column1 == null || column2 == null) {
            throw new IllegalArgumentException("Can't link null columns.");
        }
        column1.addForeignKey(column2);
        column2.addForeignKey(column1);
        Map<String, SqlTable> stringSqlTableMap = predecessorRelation.get(column1.getSqlTable().getSqlString());
        if (stringSqlTableMap == null) {
            stringSqlTableMap = Maps.newHashMap();
            predecessorRelation.put(column1.getSqlTable().getSqlString(), stringSqlTableMap);
        }

        stringSqlTableMap.put(column2.getSqlTable().getTableName(), column2.getSqlTable());

        stringSqlTableMap = predecessorRelation.get(column2.getSqlTable().getSqlString());
        if (stringSqlTableMap == null) {
            stringSqlTableMap = Maps.newHashMap();
            predecessorRelation.put(column2.getSqlTable().getSqlString(), stringSqlTableMap);
        }

        stringSqlTableMap.put(column1.getSqlTable().getTableName(), column1.getSqlTable());
    }

    /**
     * Add a new supported tz. Add one aliase with the same name
     *
     * @param tzName
     */
    public void addSupportedTZ(String tzName) {
        if (!StringUtils.isBlank(tzName) && !tzNamesAliases.containsKey(tzName.trim())) {
            tzNamesAliases.put(tzName.trim(), tzName.trim());
            if (LOG.isInfoEnabled()) {
                LOG.info("Endpoint " + this.getEndPointName() + " - add support of TZ: " + tzName);
            }
        }
    }

    public void addTZAliases(String tzName, String... tzAliases) {
        addSupportedTZ(tzName);
        if (tzAliases != null && !StringUtils.isBlank(tzName)) {
            for (String tzAlias : tzAliases) {
                if (StringUtils.isNotBlank(tzAlias)) {
                    String timedTzAlias = tzAlias.trim();
                    if (tzNamesAliases.containsKey(timedTzAlias)) {
                        LOG.warn("Timezone alias \"" + timedTzAlias + "\" already assigned to \"" + tzNamesAliases.get(timedTzAlias)
                                + "\". Replaced by " + tzName);
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Endpoint " + this.getEndPointName() + " - for TZ " + tzName + ", add support of Alias: " + timedTzAlias);
                    }
                    tzNamesAliases.put(timedTzAlias, tzName);
                }
            }
        }
    }

    // Timezone is case sensitive
    public void addTZAlternateDimension(String orignalDimensionName, DimensionTable alternateDimension, String tzName) {
        addSupportedTZ(tzName);
        if (tzNamesAliases.containsValue(tzName)) {
            sqlTables.put(alternateDimension.getTableName(), alternateDimension);
            alternateDimensions.put(Pair.of(orignalDimensionName.toUpperCase(), tzName), alternateDimension);
        } else {
            LOG.error("Unsuported timezone: " + tzName);
        }
    }

    /**
     * Add a dimension as mandatory.
     * Mandatory dimension names are stored in upper case.
     *
     * @param table     The dimension table
     * @param mandatory Add if true, remove otherwise
     */
    public void addDimension(DimensionTable table, boolean mandatory) {
        sqlTables.put(table.getTableName(), table);
        dimensions.put(table.getDimensionName().toUpperCase(), table);
        if (mandatory) {
            mandatoryDimensionNames.add(table.getDimensionName().toUpperCase(Locale.ENGLISH));
        }
    }

    public void setFactTable(FactTable factTable) throws IllegalArgumentException {
        if (this.factTable != null) {
            throw new IllegalArgumentException("Fact table already setted");
        }
        this.factTable = factTable;
        sqlTables.put(factTable.getTableName(), factTable);
    }

    public void setDefaultTimezone(String defaultTimezone) {
        if (StringUtils.isBlank(defaultTimezone)) {
            throw new IllegalArgumentException("Default timezone can't be null.");
        }
        this.defaultTimezone = defaultTimezone;
        addSupportedTZ(defaultTimezone);
    }

    /**
     * Build the configuration links (ie Foreigns key). Checks the validity of
     * the configuration.
     *
     * @throws Exception
     */
    public Collection<Error> buildAndValidateConfig() {
        Collection<Error> returnValue = new ArrayList<Error>();

        // Check default TZ
        if (defaultTimezone == null) {
            returnValue.add(new Error("No default Timezone configured"));
        }

        for (Pair<Pair<String, String>, Pair<String, String>> fkRelationship : fkRelationshipSet) {
            Optional<PhysicalSqlColumn> left = getColumnByPair(fkRelationship.getLeft());
            Optional<PhysicalSqlColumn> right = getColumnByPair(fkRelationship.getRight());
            if (!left.isPresent()) {
                returnValue.add(new Error("Column " + fkRelationship.getLeft().getRight() + " in table " + fkRelationship.getLeft().getLeft()
                        + " doesn't exist."));
            } else if (!right.isPresent()) {
                returnValue.add(new Error("Column " + fkRelationship.getRight().getRight() + " in table " + fkRelationship.getRight().getLeft()
                        + " doesn't exist."));
            } else {
                buildSqlForeignKey(left.get(), right.get());
            }
        }
        // Check Alternate Tables
        for (Entry<Pair<String, String>, DimensionTable> entry : alternateDimensions.entrySet()) {
            DimensionTable dim = dimensions.get(entry.getKey().getLeft());
            if (dim == null) {
                returnValue.add(new Error("Original dimension " + entry.getKey().getLeft() + " does not exist."));
            } else {
                if (!dim.isEquivalent(entry.getValue(), returnValue)) {
                    returnValue.add(new Error("Dimension " + entry.getValue().getDimensionName() + " is not equivalent to " + dim.getDimensionName()
                            + "."));
                }
            }

        }

        return returnValue;
    }

    private Optional<PhysicalSqlColumn> getColumnByPair(Pair<String, String> columnName) {
        SqlTable sqlTable = sqlTables.get(columnName.getLeft());
        if (sqlTable == null) {
            LOG.error("Table with name " + columnName.getLeft() + " not presents in the schema.");
            return Optional.absent();
        }
        Optional<AbstractSqlColumn> rValue = sqlTable.getColumnByName(columnName.getRight());
        if (rValue.isPresent() && PhysicalSqlColumn.class.isAssignableFrom(rValue.get().getClass())) {
            return Optional.of((PhysicalSqlColumn) rValue.get());
        }
        return Optional.absent();
    }

    /**
     * {@inheritDoc SchemaDefinition}
     * {@link SchemaDefinition#isValidMeasure(String)}
     */
    @Override
    public boolean isValidMeasure(String name) {
        for (AbstractSqlColumn column : factTable.getMeasuresColumn().values()) {
            if (column.getBusinessName().equals(name)) {
                return true;
            }
        }
        return false;
    }

    public String toString() {
        Set<String> unicTZ = new HashSet<String>(tzNamesAliases.values());
        StringBuilder returnValue = new StringBuilder();
        returnValue.append(this.getEndPointName());
        returnValue.append(" [fact: ");
        returnValue.append(this.factTable.getTableName());
        returnValue.append(" ,dim (");
        returnValue.append(this.dimensions.size());
        returnValue.append("): ");
        for (DimensionTable dim : dimensions.values()) {
            returnValue.append(dim.getTableName());
            returnValue.append("(");
            returnValue.append(dim.getDimensionName());
            returnValue.append(") ");
        }
        returnValue.append(",TZ (");
        returnValue.append(this.getDefaultTimezone());
        returnValue.append("): ");
        returnValue.append(unicTZ.size());
        returnValue.append("-");
        returnValue.append(tzNamesAliases.size());
        returnValue.append("]");
        return returnValue.toString();
    }
}


/*
 * 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.LinkedList;
import java.util.List;
import java.util.Set;

import com.thesett.aima.search.QueueBasedSearchMethod;
import com.thesett.aima.search.util.Searches;
import com.thesett.aima.search.util.uninformed.DepthFirstSearch;
import com.thesett.common.parsing.SourceCodeException;

/**
 * TermUtils provides some convenient static utility methods for working with terms in first order logic.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Find all free variables in a term.
 *      {@link DepthFirstSearch}, {@link FreeVariablePredicate}, {@link Searches}
 *
 Find all free and non-anonymous variables in a term.
 *      {@link DepthFirstSearch}, {@link FreeNonAnonymousVariablePredicate}, {@link Searches}.
 *
 Flatten comma seperated lists of term.
 *
 Convert a term into a clause.
 *

 *
 * @author Rupert Smith
 */
public class TermUtils
{
    /**
     * Calculates the set of free variables in a term.
     *
     * @param  query The term to calculate the free non-anonymous variable set from.
     *
     * @return A set of variables that are free and non-anonymous in the term.
     */
    public static Set<Variable> findFreeVariables(Term query)
    {
        QueueBasedSearchMethod<Term, Term> freeVarSearch = new DepthFirstSearch<Term, Term>();
        freeVarSearch.reset();
        freeVarSearch.addStartState(query);
        freeVarSearch.setGoalPredicate(new FreeVariablePredicate());

        return (Set<Variable>) (Set) Searches.setOf(freeVarSearch);
    }

    /**
     * Calculates the set of free and non-anonymous variables in a term. This is the set of variables that a user query
     * usually wants to be made aware of.
     *
     * @param  query The term to calculate the free non-anonymous variable set from.
     *
     * @return A set of variables that are free and non-anonymous in the term.
     */
    public static Set<Variable> findFreeNonAnonymousVariables(Term query)
    {
        QueueBasedSearchMethod<Term, Term> freeVarSearch = new DepthFirstSearch<Term, Term>();
        freeVarSearch.reset();
        freeVarSearch.addStartState(query);
        freeVarSearch.setGoalPredicate(new FreeNonAnonymousVariablePredicate());

        return (Set<Variable>) (Set) Searches.setOf(freeVarSearch);
    }

    /**
     * Flattens a sequence of terms as a symbol seperated argument list. Terms that have been parsed as a bracketed
     * expressions will not be flattened. All of the terms in the list must sub sub-classes of a specified super class.
     * This is usefull, for example, when parsing a sequence of functors in a clause body, in order to check that all of
     * the body members really are functors and not just terms.
     *
     *
For example, 'a, b, c' is broken into the list { a, b, c } on the symbol ','. The example, 'a, (b, c), d' is
     * broken into the list { a, (b, c), d} on the symbol ',' and so on.
     *
     * @param  <T>               The type of the class that all flattened terms must extend.
     * @param  term              The term to flatten.
     * @param  superClass        The root class that all flattened terms must extend.
     * @param  symbolToFlattenOn A symbol of fixity 2 to flatten on.
     * @param  interner          The functor and variable interner for the namespace the term to flatten is in.
     *
     * @return A sequence of terms parsed as a term, then flattened back into a list seperated on commas.
     *
     * @throws SourceCodeException If any of the extracted terms encountered do not extend the superclass.
     */
    public static <T extends Term> List<T> flattenTerm(Term term, Class<T> superClass, String symbolToFlattenOn,
        VariableAndFunctorInterner interner) throws SourceCodeException
    {
        List<T> terms = new LinkedList<T>();

        // Used to hold the next term to examine as operators are flattened.
        Term nextTerm = term;

        // Used to indicate when there are no more operators to flatten.
        boolean mayBeMoreCommas = true;

        // Get the functor name of the symbol to flatten on.
        int symbolName = interner.internFunctorName(symbolToFlattenOn, 2);

        // Walk down the terms matching symbols and flattening them into a list of terms.
        while (mayBeMoreCommas)
        {
            if (!nextTerm.isBracketed() && (nextTerm instanceof Functor) &&
                    (symbolName == (((Functor) nextTerm).getName())))
            {
                Functor op = (Functor) nextTerm;
                Term termToExtract = op.getArgument(0);

                if (superClass.isInstance(termToExtract))
                {
                    terms.add(superClass.cast(termToExtract));
                    nextTerm = op.getArgument(1);
                }
                else
                {
                    throw new SourceCodeException("The term " + termToExtract + " is expected to extend " + superClass +
                        " but does not.", null, null, null, termToExtract.getSourceCodePosition());
                }
            }
            else
            {
                if (superClass.isInstance(nextTerm))
                {
                    terms.add(superClass.cast(nextTerm));
                    mayBeMoreCommas = false;
                }
                else
                {
                    throw new SourceCodeException("The term " + nextTerm + " is expected to extend " + superClass +
                        " but does not.", null, null, null, nextTerm.getSourceCodePosition());
                }
            }
        }

        return terms;
    }

    /**
     * Flattens a sequence of terms as a symbol seperated argument list. Terms that have been parsed as a bracketed
     * expressions will not be flattened. All of the terms in the list must sub sub-classes of a specified super class.
     * This is usefull, for example, when parsing a sequence of functors in a clause body, in order to check that all of
     * the body members really are functors and not just terms.
     *
     *
For example, 'a, b, c' is broken into the list { a, b, c } on the symbol ','. The example, 'a, (b, c), d' is
     * broken into the list { a, (b, c), d} on the symbol ',' and so on.
     *
     * @param  <T>          The type of the class that all flattened terms must extend.
     * @param  term         The term to flatten.
     * @param  superClass   The root class that all flattened terms must extend.
     * @param  internedName The interned name of the symbol to flatten on.
     *
     * @return A sequence of terms parsed as a term, then flattened back into a list seperated on commas.
     */
    public static <T extends Term> List<T> flattenTerm(Term term, Class<T> superClass, int internedName)
    {
        List<T> terms = new LinkedList<T>();

        // Used to hold the next term to examine as operators are flattened.
        Term nextTerm = term;

        // Used to indicate when there are no more operators to flatten.
        boolean mayBeMore = true;

        // Walk down the terms matching symbols and flattening them into a list of terms.
        while (mayBeMore)
        {
            if (!nextTerm.isBracketed() && (nextTerm instanceof Functor) &&
                    (internedName == (((Functor) nextTerm).getName())))
            {
                Functor op = (Functor) nextTerm;
                Term termToExtract = op.getArgument(0);

                if (superClass.isInstance(termToExtract))
                {
                    terms.add(superClass.cast(termToExtract));
                    nextTerm = op.getArgument(1);
                }
                else
                {
                    throw new IllegalStateException("The term " + termToExtract + " is expected to extend " + superClass +
                        " but does not.");
                }
            }
            else
            {
                if (superClass.isInstance(nextTerm))
                {
                    terms.add(superClass.cast(nextTerm));
                    mayBeMore = false;
                }
                else
                {
                    throw new IllegalStateException("The term " + nextTerm + " is expected to extend " + superClass +
                        " but does not.");
                }
            }
        }

        return terms;
    }

    /**
     * Converts a term into a clause. The term must be a functor. If it is a functor corresponding to the ':-' symbol it
     * is a clause with a head and a body. If it is a functor corresponding to the '?-' symbol it is a query clause with
     * no head but must have a body. If it is neither but is a functor it is interpreted as a program clause ':-' with
     * no body, that is, a fact.
     *
     * @param  term     The term to convert to a top-level clause.
     * @param  interner The functor and variable name interner for the namespace the term to convert is in.
     *
     * @return A clause for the term, or <tt>null</tt> if it cannot be converted.
     *
     * @throws SourceCodeException If the term to convert to a clause does not form a valid clause.
     */
    public static Clause convertToClause(Term term, VariableAndFunctorInterner interner) throws SourceCodeException
    {
        // Check if the top level term is a query, an implication or neither and reduce the term into a clause
        // accordingly.
        if (term instanceof OpSymbol)
        {
            OpSymbol symbol = (OpSymbol) term;

            if (":-".equals(symbol.getTextName()))
            {
                List<Functor> flattenedArgs = flattenTerm(symbol.getArgument(1), Functor.class, ",", interner);

                return new Clause<Functor>((Functor) symbol.getArgument(0),
                    flattenedArgs.toArray(new Functor[flattenedArgs.size()]));
            }
            else if ("?-".equals(symbol.getTextName()))
            {
                List<Functor> flattenedArgs = flattenTerm(symbol.getArgument(0), Functor.class, ",", interner);

                return new Clause<Functor>(null, flattenedArgs.toArray(new Functor[flattenedArgs.size()]));
            }
        }

        if (term instanceof Functor)
        {
            return new Clause<Functor>((Functor) term, null);
        }
        else
        {
            throw new SourceCodeException("Only functors can for a clause body, not " + term + ".", null, null, null,
                term.getSourceCodePosition());
        }
    }
}


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

import com.thesett.aima.search.SearchNode;

/**
 * GreedyComparator is a Comparator that compares the heuristic values of heuristic search nodes. This can be used to
 * order the search nodes in a greedy search.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Order heuristic search nodes by heuristic value.
 *

 *
 * @author Rupert Smith
 */
public final class GreedyComparator implements Comparator<SearchNode>
{
    /**
     * Compares two heuristic search nodes by their heuristic values.
     *
     * @param  object1 The first search node to compare.
     * @param  object2 The second search node to compare.
     *
     * @return 1 if the first search node has a larger heuristic value than the second, -1 if it has a smaller heuristic
     *         value and 0 if they are the same.
     */
    public int compare(SearchNode object1, SearchNode object2)
    {
        float h1 = ((HeuristicSearchNode) object1).getH();
        float h2 = ((HeuristicSearchNode) object2).getH();

        return (h1 > h2) ? 1 : ((h1 < h2) ? -1 : 0);
    }
}


/*
 * 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.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * FileUtils provides some simple helper methods for working with files. It follows the convention of wrapping all
 * checked exceptions as runtimes, so code using these methods is free of try-catch blocks but does not expect to
 * recover from errors.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Read a text file as a string.
 *

 *
 * @author Rupert Smith
 */
public class FileUtils
{
    /**
     * Reads a text file as a string.
     *
     * @param  filename The name of the file.
     *
     * @return The contents of the file.
     */
    public static String readFileAsString(String filename)
    {
        BufferedInputStream is = null;

        try
        {
            is = new BufferedInputStream(new FileInputStream(filename));
        }
        catch (FileNotFoundException e)
        {
            throw new IllegalStateException(e);
        }

        return readStreamAsString(is);
    }

    /**
     * Reads a text file as a string.
     *
     * @param  file The file.
     *
     * @return The contents of the file.
     */
    public static String readFileAsString(File file)
    {
        BufferedInputStream is = null;

        try
        {
            is = new BufferedInputStream(new FileInputStream(file));
        }
        catch (FileNotFoundException e)
        {
            throw new IllegalStateException(e);
        }

        return readStreamAsString(is);
    }

    /**
     * Writes an object, using its 'toString' method, to the named file. The object may optionally be appended to the
     * file, or may overwrite it.
     *
     * @param outputFileName The name of the file to write the output to.
     * @param toWrite        The object to write to the file.
     * @param append         <tt>true</tt> to append to the file, <tt>false</tt> to overwrite.
     */
    public static void writeObjectToFile(String outputFileName, Object toWrite, boolean append)
    {
        // Open the output file.
        Writer resultWriter;

        try
        {
            resultWriter = new FileWriter(outputFileName, append);
        }
        catch (IOException e)
        {
            throw new IllegalStateException("Unable to open the output file '" + outputFileName + "' for writing.", e);
        }

        // Write the object into the output file.
        try
        {
            resultWriter.write(toWrite.toString());
            resultWriter.flush();
            resultWriter.close();
        }
        catch (IOException e)
        {
            throw new IllegalStateException("There was an error whilst writing to the output file '" + outputFileName + "'.",
                e);
        }
    }

    /**
     * Reads the contents of a reader, one line at a time until the end of stream is encountered, and returns all
     * together as a string.
     *
     * @param  is The reader.
     *
     * @return The contents of the reader.
     */
    private static String readStreamAsString(BufferedInputStream is)
    {
        try
        {
            byte[] data = new byte[4096];

            StringBuffer inBuffer = new StringBuffer();

            int read;

            while ((read = is.read(data)) != -1)
            {
                String s = new String(data, 0, read);
                inBuffer.append(s);
            }

            return inBuffer.toString();
        }
        catch (IOException e)
        {
            throw new IllegalStateException(e);
        }
    }
}


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

import java.util.Collection;
import java.util.LinkedList;

/**
 * SizeableLinkedList is a list of sizeables that tracks the size of its elements to generate a total size for the list.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Keep track of the size of a list of sizeables.
 *  {@link Sizeable}
 *

 *
 * @author Rupert Smith
 */
public class SizeableLinkedList<T extends Sizeable> extends LinkedList<T> implements SizeableList<T>
{
    /** Holds the byte size of the listing. */
    protected long sizeOf;

    /**
     * Calculates the size of this object in bytes.
     *
     * @return The size of this object in bytes.
     */
    public long sizeof()
    {
        return sizeOf;
    }

    /**
     * Appends the specified element to the end of this list (optional operation).
     *
     *
Lists that support this operation may place limitations on what elements may be added to this list. In
     * particular, some lists will refuse to add null elements, and others will impose restrictions on the type of
     * elements that may be added. List classes should clearly specify in their documentation any restrictions on what
     * elements may be added.
     *
     * @param  o element to be appended to this list.
     *
     * @return <tt>true</tt> (as per the general contract of the <tt>Collection.add</tt> method).
     *
     * @throws UnsupportedOperationException if the <tt>add</tt> method is not supported by this list.
     * @throws ClassCastException            if the class of the specified element prevents it from being added to this
     *                                       list.
     * @throws NullPointerException          if the specified element is null and this list does not support null
     *                                       elements.
     * @throws IllegalArgumentException      if some aspect of this element prevents it from being added to this list.
     */
    public boolean add(T o)
    {
        boolean result = super.add(o);

        sizeOf += (result) ? o.sizeof() : 0;

        return result;
    }

    /**
     * Removes the first occurrence in this list of the specified element (optional operation). If this list does not
     * contain the element, it is unchanged. More formally, removes the element with the lowest index i such that <tt>
     * (o==null ? get(i)==null : o.equals(get(i)))</tt> (if such an element exists).
     *
     * @param  o element to be removed from this list, if present.
     *
     * @return <tt>true</tt> if this list contained the specified element.
     *
     * @throws ClassCastException            if the type of the specified element is incompatible with this list
     *                                       (optional).
     * @throws NullPointerException          if the specified element is null and this list does not support null
     *                                       elements (optional).
     * @throws UnsupportedOperationException if the <tt>remove</tt> method is not supported by this list.
     */
    public boolean remove(Object o)
    {
        boolean result = super.remove(o);

        sizeOf -= ((o instanceof Sizeable) && result) ? ((Sizeable) o).sizeof() : 0;

        return result;
    }

    /**
     * Removes all of the elements from this list (optional operation). This list will be empty after this call returns
     * (unless it throws an exception).
     *
     * @throws UnsupportedOperationException if the <tt>clear</tt> method is not supported by this list.
     */
    public void clear()
    {
        super.clear();
        sizeOf = 0;
    }

    /**
     * Inserts the specified element at the specified position in this list (optional operation). Shifts the element
     * currently at that position (if any) and any subsequent elements to the right (adds one to their indices).
     *
     * @param  index   index at which the specified element is to be inserted.
     * @param  element element to be inserted.
     *
     * @throws UnsupportedOperationException if the <tt>add</tt> method is not supported by this list.
     * @throws ClassCastException            if the class of the specified element prevents it from being added to this
     *                                       list.
     * @throws NullPointerException          if the specified element is null and this list does not support null
     *                                       elements.
     * @throws IllegalArgumentException      if some aspect of the specified element prevents it from being added to
     *                                       this list.
     * @throws IndexOutOfBoundsException     if the index is out of range (index < 0 || index > size()).
     */
    public void add(int index, T element)
    {
        super.add(index, element);
        sizeOf += element.sizeof();
    }

    /**
     * Removes the element at the specified position in this list (optional operation). Shifts any subsequent elements
     * to the left (subtracts one from their indices). Returns the element that was removed from the list.
     *
     * @param  index the index of the element to removed.
     *
     * @return the element previously at the specified position.
     *
     * @throws UnsupportedOperationException if the <tt>remove</tt> method is not supported by this list.
     * @throws IndexOutOfBoundsException     if the index is out of range (index < 0 || index >= size()).
     */
    public T remove(int index)
    {
        T result = super.remove(index);

        sizeOf += (result != null) ? result.sizeof() : 0;

        return result;
    }

    /**
     * Appends all of the elements in the specified collection to the end of this list, in the order that they are
     * returned by the specified collection's iterator (optional operation). The behavior of this operation is
     * unspecified if the specified collection is modified while the operation is in progress. (Note that this will
     * occur if the specified collection is this list, and it's nonempty.)
     *
     * @param  c collection whose elements are to be added to this list.
     *
     * @return <tt>true</tt> if this list changed as a result of the call.
     *
     * @throws UnsupportedOperationException if the <tt>addAll</tt> method is not supported by this list.
     * @throws ClassCastException            if the class of an element in the specified collection prevents it from
     *                                       being added to this list.
     * @throws NullPointerException          if the specified collection contains one or more null elements and this
     *                                       list does not support null elements, or if the specified collection is <tt>
     *                                       null</tt>.
     * @throws IllegalArgumentException      if some aspect of an element in the specified collection prevents it from
     *                                       being added to this list.
     *
     * @see    #add(Object)
     */
    public boolean addAll(Collection<? extends T> c)
    {
        if (c instanceof Sizeable)
        {
            sizeOf += ((Sizeable) c).sizeof();
        }

        return super.addAll(c);
    }
}


/*
 * 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.Clause;
import com.thesett.aima.logic.fol.Predicate;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.compiler.PositionalTermTraverser;
import com.thesett.aima.logic.fol.wam.compiler.WAMCompiledPredicate;
import com.thesett.aima.logic.fol.wam.compiler.WAMCompiledQuery;
import com.thesett.aima.logic.fol.wam.compiler.WAMInstruction;
import com.thesett.aima.logic.fol.wam.compiler.WAMOptimizeableListing;
import com.thesett.common.util.doublemaps.SymbolTable;
import com.thesett.text.api.model.TextTableModel;

/**
 * ByteCodePrinter prints the compiled bytecode in its unoptimzed state.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Print the unoptimized compiled byte code.
 *

 *
 * @author Rupert Smith
 */
public class UnoptimizedByteCodePrinter 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 UnoptimizedByteCodePrinter(VariableAndFunctorInterner interner,
        SymbolTable<Integer, String, Object> symbolTable, PositionalTermTraverser traverser, int column,
        TextTableModel table)
    {
        super(interner, symbolTable, traverser, column, table);
    }

    /** {@inheritDoc} */
    protected void enterClause(Clause clause)
    {
        if (clause instanceof WAMCompiledQuery)
        {
            WAMOptimizeableListing query = (WAMCompiledQuery) clause;

            for (WAMInstruction instruction : query.getUnoptimizedInstructions())
            {
                addLineToRow(instruction.toString());
                nextRow();
            }
        }
    }

    /** {@inheritDoc} */
    protected void enterPredicate(Predicate predicate)
    {
        if (predicate instanceof WAMCompiledPredicate)
        {
            WAMOptimizeableListing compiledPredicate = (WAMCompiledPredicate) predicate;

            for (WAMInstruction instruction : compiledPredicate.getUnoptimizedInstructions())
            {
                addLineToRow(instruction.toString());
                nextRow();
            }
        }
    }
}


/*
 * 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 com.thesett.aima.search.SearchNode;
import com.thesett.aima.search.Traversable;
import com.thesett.aima.search.impl.BaseQueueSearch;

/**
 * Provides a base class for implementation queue-based heuristic searched.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Do a heauristic search.
 *

 *
 * @author Rupert Smith
 */
public abstract class BaseHeuristicSearch<O, T extends Traversable<O>> extends BaseQueueSearch<O, T>
{
    /** Used to hold the heuristic function. */
    Heuristic<O, T> heuristic;

    /**
     * Creates a base heuristic search using the specified heuristic to guide the search.
     *
     * @param heuristic The heursitic to use to guide the search.
     */
    public BaseHeuristicSearch(Heuristic<O, T> heuristic)
    {
        this.heuristic = heuristic;
    }

    /**
     * Creates the correct type of search nodes for this search. This search uses heuristic search nodes.
     *
     * @param  state The search space state to create a search node for.
     *
     * @return The state encapsulated in a heuristic search node.
     */
    public SearchNode<O, T> createSearchNode(T state)
    {
        return new HeuristicSearchNode<O, T>(state, heuristic);
    }
}


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

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

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.common.util.Function;
import com.thesett.common.util.ReflectionUtils;

/**
 * BuiltInTransform implements a compilation transformation over term syntax trees, that substitutes for functors that
 * map onto Prolog built-ins, an extension of the functor type that implements the built-in.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Transform functors to built in functors where appropriate.
 *      {@link BuiltInFunctor}, {@link VariableAndFunctorInterner}.
 *

 *
 * @author Rupert Smith
 */
public class BuiltInTransform implements Function<Functor, BuiltInFunctor>
{
    /** Holds a mapping from functor names to built-in implementations. */
    private final Map<FunctorName, Class<? extends BuiltInFunctor>> builtIns =
        new HashMap<FunctorName, Class<? extends BuiltInFunctor>>();

    /** Used to extract functor names to match. */
    private final VariableAndFunctorInterner interner;

    /**
     * Initializes the built-in transformation by population the the table of mappings of functors onto their built-in
     * implementations.
     *
     * @param interner The interner to use to extract functor names to match as built-ins.
     */
    public BuiltInTransform(VariableAndFunctorInterner interner)
    {
        this.interner = interner;

        builtIns.put(new FunctorName("true", 0), True.class);
        builtIns.put(new FunctorName("fail", 0), Fail.class);
        builtIns.put(new FunctorName("!", 0), Cut.class);
        builtIns.put(new FunctorName("=", 2), Unifies.class);
        builtIns.put(new FunctorName("\\=", 2), NonUnifies.class);
        builtIns.put(new FunctorName("is", 2), Is.class);
        builtIns.put(new FunctorName(";", 2), Disjunction.class);
        builtIns.put(new FunctorName("call", 1), Call.class);
        builtIns.put(new FunctorName(">", 2), GreaterThan.class);
        builtIns.put(new FunctorName(">=", 2), GreaterThanOrEqual.class);
        builtIns.put(new FunctorName("<", 2), LessThan.class);
        builtIns.put(new FunctorName("=<", 2), LessThanOrEqual.class);
        builtIns.put(new FunctorName("integer", 1), IntegerCheck.class);
        builtIns.put(new FunctorName("float", 1), FloatCheck.class);
    }

    /**
     * Applies a built-in replacement transformation to functors. If the functor matches built-in, a
     * {@link BuiltInFunctor} is created with a mapping to the functors built-in implementation, and the functors
     * arguments are copied into this new functor. If the functor does not match a built-in, it is returned unmodified.
     *
     * @param  functor The functor to attempt to map onto a built-in.
     *
     * @return The functor umodified, or a {@link BuiltInFunctor} replacement for it.
     */
    public BuiltInFunctor apply(Functor functor)
    {
        FunctorName functorName = interner.getFunctorFunctorName(functor);

        Class<? extends BuiltInFunctor> builtInClass;

        if (builtIns.containsKey(functorName))
        {
            builtInClass = builtIns.get(functorName);
        }
        else
        {
            builtInClass = DefaultBuiltIn.class;
        }

        return ReflectionUtils.newInstance(ReflectionUtils.getConstructor(builtInClass, new Class[] { Functor.class }),
            new Object[] { functor });
    }
}


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

/**
 * TraceIndenter generates indents as spaces, to assist with pretty printing trace execution code that benefits from
 * using a nested indentation layout.
 *
 *

CRC Card

 *
ResponsibilitiesCollaborations
 *
Generate indents and maintain a current indent count.
 *

 *
 * @author Rupert Smith
 */
public class TraceIndenter
{
    /** Used to maintain a nesting index on method call traces, to neatly pad the output with spaces by. */
    private int traceIndent;

    /** Flag to indicate whether or not the indent should actually be generated. */
    private final boolean useIndent;

    /** Holds the current stack of indents to the current position. */
    private final Stack<Integer> indentStack = new LifoStack<Integer>();

    /**
     * Creates a trace indenter, the flag specifies whether or not to actually generate the indents.
     *
     * @param useIndent <tt>true</tt> to generate indents.
     */
    public TraceIndenter(boolean useIndent)
    {
        this.useIndent = useIndent;
    }

    /** Resets the indentation to zero. */
    public void reset()
    {
        traceIndent = 0;
    }

    /** Provides the current latest delta to the current position. */
    public int getLastDelta()
    {
        return indentStack.peek();
    }

    /**
     * Generates a sequence of spaces to indent debugging output with, without applying a delta to the indentation
     * level.
     *
     * @return A sequence of spaces to use for nesting call traces in debug output with.
     */
    public String generateTraceIndent()
    {
        return generateTraceIndent(0);
    }

    /**
     * Generates a sequence of spaces to indent debugging output with.
     *
     * @param  delta The amount to change the current trace indent by. If this is negative it is subtracted before the
     *               sequence of spaces is generated, if it is positive it is added after.
     *
     * @return A sequence of spaces to use for nesting call traces in debug output with.
     */
    public String generateTraceIndent(int delta)
    {
        if (!useIndent)
        {
            return "";
        }
        else
        {
            if (delta >= 1)
            {
                indentStack.push(delta);
            }
            else if (delta < 0)
            {
                indentStack.pop();
            }

            StringBuffer result = new StringBuffer();

            traceIndent += (delta < 0) ? delta : 0;

            for (int i = 0; i < traceIndent; i++)
            {
                result.append(" ");
            }

            traceIndent += (delta > 0) ? delta : 0;

            return result.toString();
        }
    }
}


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

import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.FunctorTermPredicate;
import com.thesett.aima.logic.fol.Term;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.bytecode.BaseMachine;
import com.thesett.aima.logic.fol.wam.builtins.BuiltIn;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.REG_ADDR;
import com.thesett.aima.search.QueueBasedSearchMethod;
import com.thesett.aima.search.SearchMethod;
import com.thesett.aima.search.util.Searches;
import com.thesett.aima.search.util.uninformed.BreadthFirstSearch;
import com.thesett.aima.search.util.uninformed.PostFixSearch;
import com.thesett.common.util.SizeableLinkedList;
import com.thesett.common.util.doublemaps.SymbolTable;

/**
 * DefaultBuiltIn implements the standard WAM Prolog compilation for normal Prolog programs. Splitting this out into
 * DefaultBuiltIn which supplies the {@link BuiltIn} interface, allows different compilations to be used for built in
 * predicates that behave differently to the normal compilation.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Generate instructions to set up the arguments to a call to a built-in functor.

 *
 Generate instructions to call to a built-in functor.

 *

 *
 * @author Rupert Smith
 */
public class DefaultBuiltIn extends BaseMachine implements BuiltIn
{
    /** Used for debugging. */
    /* private static final Logger log = Logger.getLogger(DefaultBuiltIn.class.getName()); */

    /** Enumerates the possible ways in which a variable can be introduced to a clause. */
    protected enum VarIntroduction
    {
        /** Introduced by a get instruction. */
        Get,

        /** Introduced by a put instruction. */
        Put,

        /** Introduced by a set instruction. */
        Set,

        /** Introduced by a unify instruction. */
        Unify
    }

    /** This is used to keep track of registers as they are seen. */
    protected Collection<Integer> seenRegisters = new TreeSet<Integer>();

    /** Used to keep track of the temporary register assignment across multiple functors within a clause. */
    protected int lastAllocatedTempReg;

    /**
     * Creates a built-in, with the specified symbol table and name interner.
     *
     * @param symbolTable The symbol table for the compiler and machine.
     * @param interner    The name interner for the compiler and machine.
     */
    public DefaultBuiltIn(SymbolTable<Integer, String, Object> symbolTable, VariableAndFunctorInterner interner)
    {
        super(symbolTable, interner);
    }

    /** {@inheritDoc} */
    public SizeableLinkedList<WAMInstruction> compileBodyCall(Functor expression, boolean isFirstBody,
        boolean isLastBody, boolean chainRule, int permVarsRemaining)
    {
        // Used to build up the results in.
        SizeableLinkedList<WAMInstruction> instructions = new SizeableLinkedList<WAMInstruction>();

        // Generate the call or tail-call instructions, followed by the call address, which is f_n of the
        // called program.
        if (isLastBody)
        {
            // Deallocate the stack frame at the end of the clause, but prior to calling the last
            // body predicate.
            // This is not required for chain rules, as they do not need a stack frame.
            if (!chainRule)
            {
                instructions.add(new WAMInstruction(WAMInstruction.WAMInstructionSet.Deallocate));
            }

            instructions.add(new WAMInstruction(WAMInstruction.WAMInstructionSet.Execute,
                    interner.getFunctorFunctorName(expression)));
        }
        else
        {
            instructions.add(new WAMInstruction(WAMInstruction.WAMInstructionSet.Call,
                    (byte) (permVarsRemaining & 0xff), interner.getFunctorFunctorName(expression)));
        }

        return instructions;
    }

    /** {@inheritDoc} */
    public SizeableLinkedList<WAMInstruction> compileBodyArguments(Functor expression, boolean isFirstBody,
        FunctorName clauseName, int bodyNumber)
    {
        // Used to build up the results in.
        SizeableLinkedList<WAMInstruction> instructions = new SizeableLinkedList<WAMInstruction>();

        // Allocate argument registers on the body, to all functors as outermost arguments.
        // Allocate temporary registers on the body, to all terms not already allocated.
        /*if (!isFirstBody)
        {
            lastAllocatedRegister = 0;
        }*/

        allocateArgumentRegisters(expression);
        allocateTemporaryRegisters(expression);

        // Loop over all of the arguments to the outermost functor.
        int numOutermostArgs = expression.getArity();

        for (int j = 0; j < numOutermostArgs; j++)
        {
            Term nextOutermostArg = expression.getArgument(j);
            int allocation =
                (Integer) symbolTable.get(nextOutermostArg.getSymbolKey(), SymbolTableKeys.SYMKEY_ALLOCATION);

            byte addrMode = (byte) ((allocation & 0xff00) >> 8);
            byte address = (byte) (allocation & 0xff);

            // On the first occurrence of a variable output a put_var.
            // On a subsequent variable occurrence output a put_val.
            if (nextOutermostArg.isVar() && !seenRegisters.contains(allocation))
            {
                seenRegisters.add(allocation);

                // The variable has been moved into an argument register.
                //varNames.remove((byte) allocation);
                //varNames.put((byte) j, ((Variable) nextOutermostArg).getName());

                /*log.fine("PUT_VAR " + ((addrMode == REG_ADDR) ? "X" : "Y") + address + ", A" + j);*/

                WAMInstruction instruction =
                    new WAMInstruction(WAMInstruction.WAMInstructionSet.PutVar, addrMode, address, (byte) (j & 0xff));
                instructions.add(instruction);

                // Record the way in which this variable was introduced into the clause.
                symbolTable.put(nextOutermostArg.getSymbolKey(), SymbolTableKeys.SYMKEY_VARIABLE_INTRO,
                    VarIntroduction.Put);
            }
            else if (nextOutermostArg.isVar())
            {
                // Check if this is the last body functor in which this variable appears, it does so only in argument
                // position, and this is the first occurrence of these conditions. In which case, an unsafe put is to
                // be used.
                if (isLastBodyTermInArgPositionOnly((Variable) nextOutermostArg, expression) &&
                        (addrMode == WAMInstruction.STACK_ADDR))
                {
                    /*log.fine("PUT_UNSAFE_VAL " + ((addrMode == REG_ADDR) ? "X" : "Y") + address + ", A" +
                        j);*/

                    WAMInstruction instruction =
                        new WAMInstruction(WAMInstruction.WAMInstructionSet.PutUnsafeVal, addrMode, address,
                            (byte) (j & 0xff));
                    instructions.add(instruction);

                    symbolTable.put(nextOutermostArg.getSymbolKey(), SymbolTableKeys.SYMKEY_VAR_LAST_ARG_FUNCTOR, null);
                }
                else
                {
                    /*log.fine("PUT_VAL " + ((addrMode == REG_ADDR) ? "X" : "Y") + address + ", A" + j);*/

                    WAMInstruction instruction =
                        new WAMInstruction(WAMInstruction.WAMInstructionSet.PutVal, addrMode, address,
                            (byte) (j & 0xff));
                    instructions.add(instruction);
                }
            }

            // When a functor is encountered, output a put_struc.
            else if (nextOutermostArg.isFunctor())
            {
                Term nextFunctorArg = (Functor) nextOutermostArg;

                // Heap cells are to be created in an order such that no heap cell can appear before other cells that it
                // refers to. A postfix traversal of the functors in the term to compile is used to achieve this, as
                // child functors in a head will be visited first.
                // Walk over the query term in post-fix order, picking out just the functors.
                QueueBasedSearchMethod<Term, Term> postfixSearch = new PostFixSearch<Term, Term>();
                postfixSearch.reset();
                postfixSearch.addStartState(nextFunctorArg);
                postfixSearch.setGoalPredicate(new FunctorTermPredicate());

                Iterator<Term> treeWalker = Searches.allSolutions(postfixSearch);

                // For each functor encountered: put_struc.
                while (treeWalker.hasNext())
                {
                    Functor nextFunctor = (Functor) treeWalker.next();
                    allocation =
                        (Integer) symbolTable.get(nextFunctor.getSymbolKey(), SymbolTableKeys.SYMKEY_ALLOCATION);
                    addrMode = (byte) ((allocation & 0xff00) >> 8);
                    address = (byte) (allocation & 0xff);

                    // Ouput a put_struc instuction, except on the outermost functor.
                    /*log.fine("PUT_STRUC " + interner.getFunctorName(nextFunctor) + "/" + nextFunctor.getArity() +
                        ((addrMode == REG_ADDR) ? ", X" : ", Y") + address);*/

                    WAMInstruction instruction =
                        new WAMInstruction(WAMInstruction.WAMInstructionSet.PutStruc, addrMode, address,
                            interner.getDeinternedFunctorName(nextFunctor.getName()), nextFunctor);
                    instructions.add(instruction);

                    // For each argument of the functor.
                    int numArgs = nextFunctor.getArity();

                    for (int i = 0; i < numArgs; i++)
                    {
                        Term nextArg = nextFunctor.getArgument(i);
                        allocation =
                            (Integer) symbolTable.get(nextArg.getSymbolKey(), SymbolTableKeys.SYMKEY_ALLOCATION);
                        addrMode = (byte) ((allocation & 0xff00) >> 8);
                        address = (byte) (allocation & 0xff);

                        // If it is new variable: set_var or put_var.
                        // If it is variable or functor already seen: set_val or put_val.
                        if (nextArg.isVar() && !seenRegisters.contains(allocation))
                        {
                            seenRegisters.add(allocation);

                            /*log.fine("SET_VAR " + ((addrMode == REG_ADDR) ? "X" : "Y") + address);*/
                            instruction =
                                new WAMInstruction(WAMInstruction.WAMInstructionSet.SetVar, addrMode, address, nextArg);

                            // Record the way in which this variable was introduced into the clause.
                            symbolTable.put(nextArg.getSymbolKey(), SymbolTableKeys.SYMKEY_VARIABLE_INTRO,
                                VarIntroduction.Set);
                        }
                        else
                        {
                            // Check if the variable is 'local' and use a local instruction on the first occurrence.
                            VarIntroduction introduction =
                                (VarIntroduction) symbolTable.get(nextArg.getSymbolKey(),
                                    SymbolTableKeys.SYMKEY_VARIABLE_INTRO);

                            if (isLocalVariable(introduction, addrMode))
                            {
                                /*log.fine("SET_LOCAL_VAL " + ((addrMode == REG_ADDR) ? "X" : "Y") +
                                    address);*/

                                instruction =
                                    new WAMInstruction(WAMInstruction.WAMInstructionSet.SetLocalVal, addrMode, address,
                                        nextArg);

                                symbolTable.put(nextArg.getSymbolKey(), SymbolTableKeys.SYMKEY_VARIABLE_INTRO, null);
                            }
                            else
                            {
                                /*log.fine("SET_VAL " + ((addrMode == REG_ADDR) ? "X" : "Y") + address);*/
                                instruction =
                                    new WAMInstruction(WAMInstruction.WAMInstructionSet.SetVal, addrMode, address,
                                        nextArg);
                            }
                        }

                        instructions.add(instruction);
                    }
                }
            }
        }

        return instructions;
    }

    /**
     * For a predicate of arity n, the first n registers are used to receive its arguments in.
     *
     *
Non-variable arguments appearing directly within a functor are allocated to argument registers. This means
     * that they are directly referenced from the argument registers that pass them to predicate calls, or directly
     * referenced from the argument registers that are used to read them as call arguments.
     *
     *
Variables appearing as functor arguments are not allocated in this way, but are kept in registers with
     * positions higher than the number of arguments. The reason for this, is that a variable can appear multiple times
     * in an expression; it may not always be the same argument. Variables are assigned to other registers, then copied
     * into the argument registers as needed.
     *
     *
Argument registers are allocated by argument position within the functor. This means that gaps will be left
     * in the numbering for variables to be copied in as needed.
     *
     * @param expression The clause head functor to allocate argument registers to.
     */
    protected void allocateArgumentRegisters(Functor expression)
    {
        // Assign argument registers to functors appearing directly in the argument of the outermost functor.
        // Variables are never assigned directly to argument registers.
        int reg = 0;

        for (; reg < expression.getArity(); reg++)
        {
            Term term = expression.getArgument(reg);

            if (term instanceof Functor)
            {
                /*log.fine("X" + lastAllocatedTempReg + " = " + interner.getFunctorFunctorName((Functor) term));*/

                int allocation = (reg & 0xff) | (REG_ADDR << 8);
                symbolTable.put(term.getSymbolKey(), SymbolTableKeys.SYMKEY_ALLOCATION, allocation);
            }
        }
    }

    /**
     * Allocates terms within a functor expression to registers. The outermost functor itself is not assigned to a
     * register in WAM (only in l0). Functors already directly assigned to argument registers will not be re-assigned by
     * this. Variables as arguments will be assigned but not as argument registers.
     *
     * @param expression The expression to walk over.
     */
    protected void allocateTemporaryRegisters(Term expression)
    {
        // Need to assign registers to the whole syntax tree, working in from the outermost functor. The outermost
        // functor itself is not assigned to a register in l3 (only in l0). Functors already directly assigned to
        // argument registers will not be re-assigned by this, variables as arguments will be assigned.
        SearchMethod outInSearch = new BreadthFirstSearch<Term, Term>();
        outInSearch.reset();
        outInSearch.addStartState(expression);

        Iterator<Term> treeWalker = Searches.allSolutions(outInSearch);

        // Discard the outermost functor from the variable allocation.
        treeWalker.next();

        // For each term encountered: set X++ = term.
        while (treeWalker.hasNext())
        {
            Term term = treeWalker.next();

            if (symbolTable.get(term.getSymbolKey(), SymbolTableKeys.SYMKEY_ALLOCATION) == null)
            {
                int allocation = (lastAllocatedTempReg++ & 0xff) | (REG_ADDR << 8);
                symbolTable.put(term.getSymbolKey(), SymbolTableKeys.SYMKEY_ALLOCATION, allocation);
            }
        }
    }

    /**
     * Determines whether a variable is local, that is, it may only exist on the stack. When variables are introduced
     * into clauses, the way in which they are introduced is recorded using the {@link VarIntroduction} enum. When a
     * variable is being written to the heap for the first time, this check may be used to see if a 'local' variant of
     * an instruction is needed, in order to globalize the variable on the heap.
     *
     *
The conditions for a variable being deemed local are:
     *
     *

     *
For a permanent variable, not initialized in this clause with set_var or unify_var instruction.

     *
For a temporary variable, not initialized in this clause with set_var or unify_var or put_var instruction.
     *

     *

     *
     * @param  introduction The type of instruction that introduced the variable into the clause.
     * @param  addrMode     The addressing mode of the variable, permanent variables are stack allocated.
     *
     * @return <tt>true</tt> iff the variable is unsafe.
     */
    protected boolean isLocalVariable(VarIntroduction introduction, byte addrMode)
    {
        if (WAMInstruction.STACK_ADDR == addrMode)
        {
            return (introduction == VarIntroduction.Get) || (introduction == VarIntroduction.Put);
        }
        else
        {
            return introduction == VarIntroduction.Get;
        }
    }

    /**
     * Checks if a variable is appearing within the last body functor in which it occurs, and only does so within
     * argument position.
     *
     * @param  var  The variable to check.
     * @param  body The current body functor being processed.
     *
     * @return <tt>true</tt> iff this is the last body functor that the variable appears in and does so only in argument
     *         position.
     */
    private boolean isLastBodyTermInArgPositionOnly(Term var, Functor body)
    {
        return body == symbolTable.get(var.getSymbolKey(), SymbolTableKeys.SYMKEY_VAR_LAST_ARG_FUNCTOR);
    }
}