///////////////////////////////////////////////////////////////////////////////
// For information as to what this class does, see the Javadoc, below.       //
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,       //
// 2007, 2008, 2009, 2010, 2014, 2015 by Peter Spirtes, Richard Scheines, Joseph   //
// Ramsey, and Clark Glymour.                                                //
//                                                                           //
// This program is free software; you can redistribute it and/or modify      //
// it under the terms of the GNU General Public License as published by      //
// the Free Software Foundation; either version 2 of the License, or         //
// (at your option) any later version.                                       //
//                                                                           //
// This program is distributed in the hope that it will be useful,           //
// but WITHOUT ANY WARRANTY; without even the implied warranty of            //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             //
// GNU General Public License for more details.                              //
//                                                                           //
// You should have received a copy of the GNU General Public License         //
// along with this program; if not, write to the Free Software               //
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA //
///////////////////////////////////////////////////////////////////////////////

package edu.cmu.tetrad.search;

/**
 * A function from n binary variables to a binary variable.
 *
 * @author Joseph Ramsey
 */
public class BinaryFunction {

    /**
     * The function index.
     */
    private long functionIndex;

    /**
     * The number of arguments to the function.
     */
    private int numArgs;

    /**
     * Represents the function column of the truth table.
     */
    int[] functionColumn;

    //=============================CONSTRUCTORS=========================//

    /**
     * Constructs a binary function by giving the number of arguments and the function index, from which the function
     * column will be calculated.
     *
     * @param numArgs       The number of arguments to the function. The order of these is T-F, left to right in
     *                      increasing period of alternation.
     * @param functionIndex Represents the function column of the truth table. Should be a number from 0 to 2 ^ numArgs
     *                      - 1. The value for a given row in the truth table is calculated by the getValue() method.
     */
    public BinaryFunction(int numArgs, long functionIndex) {
        this.numArgs = numArgs;
        if (functionIndex > getNumFunctions()) {
            throw new IllegalArgumentException("Function index out of range " +
                    "for " + numArgs + " arguments.");
        }
        functionColumn = new int[getNumRows()];
        resetFunction(functionIndex);
    }

    /**
     * Constructs a binary function by giving the number of arguments and the function column itself.
     *
     * @param numArgs        The number of arguments to the function. The order of these is T-F, left to right in
     *                       increasing period of alternation.
     * @param functionColumn The function column.
     */
    public BinaryFunction(int numArgs, boolean[] functionColumn) {
        this.numArgs = numArgs;
        if (functionColumn.length > getNumRows()) {
            throw new IllegalArgumentException("Function column does not have " +
                    "the right number of rows for " + numArgs + " argument: " +
                    functionColumn.length);
        }
        this.functionColumn = new int[getNumRows()];
        resetFunction(getIndex(functionColumn));
    }

    //=============================PUBLIC METHODS=========================//

    public void resetFunction(long functionIndex) {
        this.functionIndex = functionIndex;

        for (int i = 0; i < getNumRows(); i++) {
            functionColumn[getNumRows() - i - 1] = (int) functionIndex % 2;
            functionIndex /= 2;
        }
    }

    public long getOppositeFunction() {
        long functionIndex = 0;

        for (int i = 0; i < getNumRows(); i++) {
            functionIndex *= 2;
            functionIndex = getFunctionIndex(i, functionIndex);
        }

        return functionIndex;
    }

    public long getSymmetricFunction() {
        long functionIndex = 0;

        for (int i = 0; i < getNumRows(); i++) {
            functionIndex *= 2;
            functionIndex += functionColumn[getNumRows() - i - 1];
        }

        return functionIndex;
    }

    public long getSymmetricOppositeFunction() {
        long functionIndex = 0;

        for (int i = 0; i < getNumRows(); i++) {
            functionIndex *= 2;
            functionIndex += 1 - functionColumn[getNumRows() - i - 1];
        }

        return functionIndex;
    }

//    public long switchColsBinary(int col1, int col2) {
//        int[] functionColumn = new int[getNumRows()];
//
//        for (int i = 0; i < getNumRows(); i++) {
//            boolean[] row = getRow(i);
//
//            boolean temp = row[col1];
//            row[col1] = row[col2];
//            row[col2] = temp;
//
//            boolean b = getValue(row);
//            functionColumn[i] = b ? 1 : 0;
//        }
//
//        return getIndex(functionColumn);
//    }

    public long switchColsFull(int[] permutation) {
        int[] functionColumn = new int[getNumRows()];

        for (int i = 0; i < getNumRows(); i++) {
            boolean[] row = getRow(i);
            boolean[] newRow = new boolean[row.length];

            for (int j = 0; j < row.length; j++) {
                newRow[permutation[j]] = row[j];
            }

            boolean b = getValue(newRow);
            functionColumn[i] = b ? 1 : 0;
        }

        return getIndex(functionColumn);
    }

    public boolean getValue(boolean[] values) {
        if (values.length != numArgs) throw new IllegalArgumentException();
        return functionColumn[getRowIndex(values)] == 1;
    }

    public boolean getValue(int row) {
        return functionColumn[row] == 1;
    }

    public boolean[] getRow(int rowIndex) {
        boolean[] values = new boolean[numArgs];

        for (int i = 0; i < numArgs; i++) {
            values[i] = (rowIndex % 2) == 1;
            rowIndex /= 2;
        }

        return values;
    }

    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append("\n");

        for (int j = 0; j < numArgs; j++) {
            buf.append("v").append(j + 1).append("   \t");
        }

        buf.append("G");

        buf.append("\n");

        for (int i = 0; i < (getNumRows()); i++) {
            boolean[] argumentVals = getRow(i);

            for (int j = 0; j < numArgs; j++) {
                buf.append(argumentVals[j]).append("\t");
            }

            buf.append(getValue(argumentVals));

            buf.append("\n");
        }

        buf.append("\n");
        return buf.toString();
    }

    public boolean equals(Object o) {
        return o instanceof BinaryFunction && functionIndex == ((BinaryFunction) o).getFunctionIndex();
    }

    public int getNumRows() {
        int numRows = 1;

        for (int i = 0; i < getNumArgs(); i++) {
            numRows *= 2;
        }
        return numRows;
    }

    public int getNumArgs() {
        return numArgs;
    }

    public long getFunctionIndex() {
        return functionIndex;
    }

    public long getNumFunctions() {
        long n = 1;

        for (int i = 0; i < getNumRows(); i++) {
            n *= 2;
        }

        return n;
    }

    //=============================PRIVATE METHODS=========================//

    private long getIndex(int[] functionColumn) {
        long functionIndex = 0;

        for (int i = 0; i < getNumRows(); i++) {
            functionIndex *= 2;
            functionIndex += functionColumn[i];
        }

        return functionIndex;
    }

    private long getIndex(boolean[] functionColumn) {
        long functionIndex = 0;

        for (int i = 0; i < getNumRows(); i++) {
            functionIndex *= 2;
            functionIndex += functionColumn[i] ? 1 : 0;
        }

        return functionIndex;
    }

    private long getFunctionIndex(int i, long functionIndex) {
        functionIndex += 1 - functionColumn[i];
        return functionIndex;
    }

    private int getRowIndex(boolean[] values) {
        int rowIndex = 0;

        for (int i = 0; i < numArgs; i++) {
            rowIndex *= 2;
            rowIndex += values[i] ? 1 : 0;
        }

        return rowIndex;
    }
}




