/*
 * LinearBiasModel.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * 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 dr.evomodel.substmodel;

import dr.evolution.datatype.Microsatellite;
import dr.inference.model.Parameter;
import dr.inference.model.Variable;

import java.util.ArrayList;

/**
 * @author Chieh-Hsi Wu
 *
 * Implements the LinearBiasModel of microsatellites.
 */
public class LinearBiasModel extends OnePhaseModel{

    /*
     *Parameters for setting up the infinitesimal rate matrix.
     */
    private Parameter biasConst;
    private Parameter biasLin;
    private ArrayList<Variable<Double>> submodelParameters = null;
    private boolean estimateSubmodelParams = false;
    private boolean updateSubmodelRates = false;
    private boolean inLogitSpace = false;

    public static final double delta = 1e-15;

    public static final String LINEAR_BIAS_MODEL = "LINEARBIASModel";


    /**
     * Constructor
     *
     * @param microsatellite            microsatellite data type
     * @param freqModel                 user specified initial equilibrium frequencies
     * @param submodel                  submodel of this linear bias model
     * @param biasConst                 bias constant parameter
     * @param biasLinear                bias linear parameter
     * @param inLogitSpace              indicates whether the bias parameters are in the logit space
     * @param estimateSubmodelParams    inidicate whether the parameters of submodel will be estimated
     * @param isSubmodel                inidicate whether this model is a submodel of another microsatellite model
     */
    public LinearBiasModel(
            Microsatellite microsatellite,
            FrequencyModel freqModel,
            OnePhaseModel submodel,
            Parameter biasConst,
            Parameter biasLinear,
            boolean inLogitSpace,
            boolean estimateSubmodelParams,
            boolean isSubmodel){

        super(LINEAR_BIAS_MODEL, microsatellite, freqModel, null);

        isNested = isSubmodel;
        this.subModel = submodel;
        this.estimateSubmodelParams = estimateSubmodelParams;
        if(this.estimateSubmodelParams){
            submodelParameters = new ArrayList<Variable<Double>>();
            for(int i = 0; i < subModel.getNestedParameterCount(); i++){

                if(isNested){
                    addVariable(subModel.getNestedParameter(i));
                }

                addParam(subModel.getNestedParameter(i));
                submodelParameters.add(subModel.getNestedParameter(i));
            }
            updateSubmodelRates = true;
        }



        //The default setting of the parameters gives infinitesimal rates with no directional bias.
        if(biasConst != null){
            this.biasConst = biasConst;
        }else{
            this.biasConst = new Parameter.Default(0.5);
        }


        if(biasLinear != null){
            biasLin = biasLinear;
        }else{
            biasLin = new Parameter.Default(0.0);
        }
        addParam(this.biasConst);
        addParam(this.biasLin);

        this.inLogitSpace = inLogitSpace;

        //printDetails();

        setupInfinitesimalRates();

        if(freqModel == null){
            useStationaryFreqs = true;
            computeStationaryDistribution();
        }else{
            this.freqModel = freqModel;

        }
        addModel(this.freqModel);

    }
    protected void handleVariableChangedEvent(Variable variable, int index, Parameter.ChangeType type) {
        if(submodelParameters !=null && submodelParameters.indexOf((Parameter)variable) != -1){
            updateSubmodelRates = true;
        }
        updateMatrix = true;

    }

     public void setupInfinitesimalRates(){
        if(updateSubmodelRates){
            subModel.setupInfinitesimalRates();
            updateSubmodelRates = false;
        }

        double biasConst = this.biasConst.getParameterValue(0);
        double biasLin = this.biasLin.getParameterValue(0);
        setupInfinitesimalRates(
                infinitesimalRateMatrix,
                subModel.getInfinitesimalRates(),
                biasConst,
                biasLin,
                stateCount,
                inLogitSpace
        );

    }


    public static void setupInfinitesimalRates(
            double[][] rates,
            double[][] subModelRateMatrix,
            double biasConst,
            double biasLin,
            int stateCount,
            boolean inLogitSpace){
        double rowSum;
        double expansionProb = 0.5;
        for(int i = 0; i < stateCount;i++){
            rowSum = 0.0;
            expansionProb = computeExpansionProb(biasConst,biasLin,i, inLogitSpace);
            if(expansionProb < delta){
                System.out.println("changing expan prob from " + expansionProb+ " to " + delta
                +"\nbiasConst: "+biasConst+", biasLin: "+biasLin);
                expansionProb = delta;

            }else if (expansionProb > (1.0-delta)){

                System.out.println("changing expan prob from " + expansionProb+ " to " + (1.0-delta)
                        +"\nbiasConst: "+biasConst+", biasLin: "+biasLin);
                expansionProb = 1.0-delta;
            }
            if(i - 1 > -1){
                rates[i][i - 1] = subModelRateMatrix[i][i-1]*(1.0 - expansionProb);
                rowSum = rowSum+rates[i][i - 1];
            }
            if(i + 1 < stateCount){
                rates[i][i + 1] = subModelRateMatrix[i][i+1]*expansionProb;
                rowSum = rowSum + rates[i][i + 1];
            }

            rates[i][i] = rowSum*-1;

        }
    }

    public static double computeExpansionProb(double biasConst, double biasLin, int length, boolean inLogitSpace){
        double expanProb = 0.5;
        if(inLogitSpace){
            double numerator = Math.exp(biasConst+biasLin*length);
            expanProb = numerator/(1+numerator);
        }else{
            expanProb = biasConst+biasLin*length;
        }
        return  expanProb;

    }


    public Parameter getBiasConstant(){
        return biasConst;
    }

    public Parameter getBiasLinearPercent(){
        return biasLin;
    }

    public boolean isEstimatingSubmodelParams(){
        return estimateSubmodelParams;
    }

    public boolean isInLogitSpace(){
        return inLogitSpace;
    }

    public void printDetails(){
        System.out.println("Details of the Linear Bias Model and its paramters:");
        System.out.println("a submodel:                     "+isNested);
        System.out.println("in logit space:                 "+inLogitSpace);
        System.out.println("has submodel:                   "+hasSubmodel());
        if(hasSubmodel()){
            System.out.println("submodel class:                 "+subModel.getClass());
        }
        System.out.println("esitmating submodel parameters: "+estimateSubmodelParams);
        System.out.println("bias constant:                  "+biasConst.getParameterValue(0));
        System.out.println("bias linear coefficient:        "+biasLin.getParameterValue(0));
    }

}
