/*
* File BitFlipOperator.java
*
* Copyright (C) 2010 Joseph Heled jheled@gmail.com
* This file is part of BEAST2.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
*  BEAST 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA  02110-1301  USA
*/

package beast.base.inference.operator;

import beast.base.core.Description;
import beast.base.core.Input;
import beast.base.core.Input.Validate;
import beast.base.inference.Operator;
import beast.base.inference.parameter.BooleanParameter;
import beast.base.inference.util.InputUtil;
import beast.base.util.Randomizer;

/**
 * @author Joseph Heled
 */

@Description("Flip one bit in an array of boolean bits. The hastings ratio is designed so that all subsets of vectors with the" +
        " same number of 'on' bits are equiprobable.")
public class BitFlipOperator extends Operator {
    final public Input<Boolean> uniformInput = new Input<>("uniform", "when on, total probability of combinations with k" +
            " 'on' bits is equal. Otherwise uniform on all combinations (default true)", true);

    final public Input<BooleanParameter> parameterInput = new Input<>("parameter", "the parameter to operate a flip on.", Validate.REQUIRED);

    private boolean usesPriorOnSum = true;

    @Override
	public void initAndValidate() {
        final Boolean b = uniformInput.get();
        if (b != null) {
            usesPriorOnSum = b;
        }
    }

    /**
     * Change the parameter and return the hastings ratio.
     * Flip (Switch a 0 to 1 or 1 to 0) for a random bit in a bit vector.
     * Return the hastings ratio which makes all subsets of vectors with the same number of 1 bits
     * equiprobable, unless !usesPriorOnSum , then all configurations are equiprobable
     */

    @Override
    public double proposal() {

        final BooleanParameter p = (BooleanParameter) InputUtil.get(parameterInput , this);

        final int dim = p.getDimension();

        double sum = 0.0;
        if (usesPriorOnSum) {
            for (int i = 0; i < dim; i++) {
                if (p.getValue(i)) sum += 1;
            }
        }

        final int pos = Randomizer.nextInt(dim);

        final boolean value = p.getValue(pos);

        double logq = 0.0;
        if (!value) {
            p.setValue(pos, true);

            if (usesPriorOnSum) {
                logq = -Math.log((dim - sum) / (sum + 1));
            }

        } else {
            //assert value;

            p.setValue(pos, false);
            if (usesPriorOnSum) {
                logq = -Math.log(sum / (dim - sum + 1));
            }
        }
        return logq;
    }
}

