import sys
import logging

import scipy
import numpy as np
import emcee

from robo.acquisition_functions.log_ei import LogEI
from robo.acquisition_functions.base_acquisition import BaseAcquisitionFunction
from robo.util import epmgp

sq2 = np.sqrt(2)
l2p = np.log(2) + np.log(np.pi)
eps = np.finfo(np.float32).eps

logger = logging.getLogger(__name__)


class InformationGain(BaseAcquisitionFunction):

    def __init__(self, model, lower, upper,
                 Nb=50, Np=400, sampling_acquisition=None,
                 sampling_acquisition_kw={"par": 0.0},
                 rng=None, **kwargs):

        """
        The Information Gain acquisition_functions function for Entropy Search [1].
        In a nutshell entropy search approximates the
        distribution pmin of the global minimum and tries to decrease its
        entropy. See Hennig and Schuler[1] for a more detailed view.

        [1] Hennig and C. J. Schuler
            Entropy search for information-efficient global optimization
            Journal of Machine Learning Research, 13, 2012

        Parameters
        ----------
        model: Model object
            A model that implements at least
                 - predict(X)
                 - predict_variances(X1, X2)
            If you want to calculate derivatives than it should also support
                 - predictive_gradients(X)
        lower: np.ndarray (D)
            Lower bounds of the input space
        upper: np.ndarray (D)
            Upper bounds of the input space
        Nb: int
            Number of representer points to define pmin.
        Np: int
            Number of hallucinated values to compute the innovations
            at the representer points
        sampling_acquisition: function
            Proposal measurement from which the representer points will
            be samples
        sampling_acquisition_kw: dist
            Additional keyword parameters that are passed to the
            acquisition_functions function
        """

        self.Nb = Nb
        super(InformationGain, self).__init__(model)
        self.lower = lower
        self.upper = upper
        self.D = self.lower.shape[0]
        self.sn2 = None

        if sampling_acquisition is None:
            sampling_acquisition = LogEI
        self.sampling_acquisition = sampling_acquisition(
            model, **sampling_acquisition_kw)

        self.Np = Np