/*
 *
 *
 *    Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
 *    See the NOTICE file distributed with this work for additional information regarding copyright ownership.
 *    The ASF licenses this file to You 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 eu.amidst.core.exponentialfamily;

import eu.amidst.core.distribution.UnivariateDistribution;
import eu.amidst.core.utils.ArrayVector;
import eu.amidst.core.utils.Vector;
import eu.amidst.core.variables.Assignment;
import eu.amidst.core.variables.Variable;
import org.apache.commons.math3.special.Gamma;

import java.util.ArrayList;
import java.util.Random;

/**
 *
 * This class extends the abstract class {@link EF_UnivariateDistribution} and defines a Gamma distribution in exponential family canonical form.
 *
 * <p> For further details about how exponential family models are considered in this toolbox take a look at the following paper:
 * <i>Representation, Inference and Learning of Bayesian Networks as Conjugate Exponential Family Models. Technical Report.</i>
 * (<a href="http://amidst.github.io/toolbox/docs/ce-BNs.pdf">pdf</a>) </p>
 */
public class EF_Gamma extends EF_UnivariateDistribution {

    public static final int LOGX = 0;
    public static final int INVX = 1;

    public static double PRECISION_LIMIT = 1000000;

    /**
     * Creates a new EF_Gamma distribution, i.e., Gamma(1,1), for a given {@link Variable} object.
     * @param var1 a {@link Variable} object with a Gamma distribution type.
     */
    public EF_Gamma(Variable var1) {

        if (!var1.isGammaParameter())
            throw new IllegalArgumentException("The variable is not a Gamma parameter!");

        this.parents = new ArrayList();

        this.var = var1;
        this.naturalParameters = this.createZeroNaturalParameters();
        this.momentParameters = this.createZeroMomentParameters();
        double alpha = 1;
        double beta = 1;
        this.naturalParameters.set(0, alpha -1 );
        this.naturalParameters.set(1, -beta);
        this.setNaturalParameters(naturalParameters);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double computeLogBaseMeasure(double val) {
        return 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SufficientStatistics getSufficientStatistics(double val) {
        SufficientStatistics vec = this.createZeroSufficientStatistics();
        vec.set(LOGX, Math.log(val));
        vec.set(INVX, val);
        return vec;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public EF_UnivariateDistribution deepCopy(Variable var) {
        EF_Gamma copy = new EF_Gamma(var);
        copy.getNaturalParameters().copy(this.getNaturalParameters());
        copy.getMomentParameters().copy(this.getMomentParameters());

        return copy;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public EF_UnivariateDistribution randomInitialization(Random random) {

        double randomVar = random.nextDouble()*2 + 1 + 0.01;

        double alpha = 10;
        double beta = randomVar*alpha ;

        this.getNaturalParameters().set(0, alpha - 1);
        this.getNaturalParameters().set(1, -beta);

        this.fixNumericalInstability();
        this.updateMomentFromNaturalParameters();

        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E extends UnivariateDistribution> E toUnivariateDistribution() {
        throw new UnsupportedOperationException("Gamma is not included yet in the Distributions package.");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updateNaturalFromMomentParameters() {
        throw new UnsupportedOperationException("Not Implemented");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updateMomentFromNaturalParameters() {
        double alpha = this.naturalParameters.get(0) + 1;
        double beta = -this.naturalParameters.get(1);
        this.momentParameters.set(0, Gamma.digamma(alpha) - Math.log(beta));
        this.momentParameters.set(1, alpha / beta);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SufficientStatistics getSufficientStatistics(Assignment data) {
        return this.getSufficientStatistics(data.getValue(this.var));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int sizeOfSufficientStatistics() {
        return 2;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double computeLogNormalizer() {
        double alpha = this.naturalParameters.get(0) + 1;
        double beta = -this.naturalParameters.get(1);
        return Gamma.logGamma(alpha) - alpha * Math.log(beta);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Vector createZeroVector() {
        return new ArrayVector(2);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Vector getExpectedParameters() {

        Vector vec = new ArrayVector(1);
        vec.set(0, -(this.naturalParameters.get(0)+1)/this.naturalParameters.get(1));
        return vec;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void fixNumericalInstability() {
        /*double alpha = this.naturalParameters.get(0) + 1;
        double beta = -this.naturalParameters.get(1);


        if ((alpha/beta)>PRECISION_LIMIT){
            double K = (alpha/beta)/PRECISION_LIMIT;
            double alphaPrime = alpha/K;
            this.naturalParameters.set(0,alphaPrime-1);
        }*/

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SufficientStatistics createInitSufficientStatistics() {

        ArrayVector vector = new ArrayVector(this.sizeOfSufficientStatistics());

        double alpha = 1;
        double beta = 1;
        vector.set(0, Gamma.digamma(alpha) - Math.log(beta));
        vector.set(1, alpha / beta);

        return vector;
    }
}
@@ -1865,21 +1865,16 @@
 MIT = 10
-00000
 ;%0A%0A    /
@@ -5726,18 +5726,16 @@
         
-/*
 double a
@@ -6034,18 +6034,16 @@
        %7D
-*/
 %0A%0A    %7D%0A

Update EF_Gamma