import sys
import math

import attr

from beastling.util import xml

__all__ = ['Distribution', 'Calibration']

registered_distributions = (
    ("Beta", "beast.math.distributions.Beta"),
    ("Exponential", "beast.math.distributions.Exponential"),
    ("InverseGamma", "beast.math.distributions.InverseGamma"),
    ("LogNormal", "beast.math.distributions.LogNormalDistributionModel"),
    ("Gamma", "beast.math.distributions.Gamma"),
    ("Uniform", "beast.math.distributions.Uniform"),
    ("LaplaceDistribution", "beast.math.distributions.LaplaceDistribution"),
    ("OneOnX", "beast.math.distributions.OneOnX"),
    ("Normal", "beast.math.distributions.Normal"),
)


DISTRIBUTIONS = {
    "normal": ("Normal", ("mean", "sigma")),
    "lognormal": ("LogNormal", ("M", "S")),
    "uniform": ("Uniform", ("lower", "upper")),
}
ALL_DISTRIBUTIONS = set(DISTRIBUTIONS.keys()).union(['rlognormal', 'point'])


def add_prior_density_description(compound_distribution, distribution):
    """Create a distribution of the specified type inside an ET element.

    Create an ET sub-element describing a Beast real-parameter
    distribution inside the ET element `compound_distribution`
    reflecting the properties of `distribution`.

    Parameters
    ----------
    compound_distribution: ET.Element
        The xml tag to which the distribution should be added.
    distribution: configuration.Calibration-like
        A description of the distribution.
        Must have a the offset, dist and param attributes.

    Returns
    -------
    None

    Side Effects
    ------------
    Creates a sub-element in `compound_distribution`.
    May register distributions with global map list.

    """
    dist_type, ps = DISTRIBUTIONS[distribution.dist]
    attribs = {
        "id": "DistributionFor{:}".format(compound_distribution.attrib["id"]),
        "name": "distr",
        "offset": "0.0"}
    if distribution.offset:
        attribs["offset"] = str(distribution.offset)
    for parameter, value in zip(ps, distribution.param):
        attribs[parameter] = str(value)
    getattr(xml, dist_type)(compound_distribution, attrib=attribs)


def parse_prior_string(cs, prior_name="?", is_point=False):
    """Parse a prior-describing string.

    The basic format of such a string is [offset + ]distribution
    Offset is a number, distribution can describe a probability
    density function of normal, lognormal (including rlognormal, a
    reparametrization where the mean is given in real space, not
    in log space) or uniform type in one of the following
    ways. Pseudo-densities with infinite integral are permitted.
    
    Parameters separated using `,` are directly the parameters of the
    distribution. A range separated by a `-` gives the 95% interval of
    that distribution. (This behaviour may change in the future.)

    >>> parse = parse_prior_string
    >>> # Parameters of a normal distribution
    >>> parse("0, 1")
    (0.0, 'normal', (0.0, 1.0))