# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Units and Quantity related helper functions."""

import logging
from math import floor
import numpy as np
import astropy.units as u

__all__ = ["standardise_unit", "unit_from_fits_image_hdu", "wrap_at"]

log = logging.getLogger(__name__)


def wrap_at(value, min_value, max_value):
    """Wrap a value to be within the range [min_value, max_value).

    Parameters
    ----------
    value : float or `~astropy.units.Unit`
        The value to wrap
    min_value : float or `~astropy.units.Unit`
        The minimum value of the range (inclusive).
    max_value : float or `~astropy.units.Unit`
        The maximum value of the range (exclusive).

    Returns
    -------
    wrapped_val : float or `~astropy.units.Unit`
        Wrapped value
    """
    # Note: Astropy wrap_at currently works only for wrapping
    # between [0, 360] and [-180, 180]
    range_size = max_value - min_value
    wrapped_val = ((value - min_value) % range_size) + min_value
    return wrapped_val


def standardise_unit(unit):
    """Standardise unit.

    Changes applied by this function:

    * Drop "photon" == "ph" from the unit
    * Drop "count" == "ct" from the unit

    Parameters
    ----------
    unit : `~astropy.units.Unit` or str
        Any old unit.

    Returns
    -------
    unit : `~astropy.units.Unit`
        Shiny new, standardised unit.

    Examples
    --------
    >>> from gammapy.utils.units import standardise_unit
    >>> standardise_unit('ph cm-2 s-1')
    Unit("1 / (s cm2)")
    >>> standardise_unit('ct cm-2 s-1')
    Unit("1 / (s cm2)")
    >>> standardise_unit('cm-2 s-1')
    Unit("1 / (s cm2)")
    """
    unit = u.Unit(unit)
    bases, powers = [], []
    for base, power in zip(unit.bases, unit.powers):
        if str(base) not in {"ph", "ct"}:
            bases.append(base)
            powers.append(power)

    return u.CompositeUnit(scale=unit.scale, bases=bases, powers=powers)


def unit_from_fits_image_hdu(header):
    """Read unit from a FITS image HDU.

    - The ``BUNIT`` key is used.
    - `astropy.units.Unit` is called.
      If the ``BUNIT`` value is invalid, a log warning
      is emitted and the empty unit is used.
    - `standardise_unit` is called
    """
    unit = header.get("BUNIT", "")

    try:
        u.Unit(unit)
    except ValueError:
        log.warning(f"Invalid value BUNIT={unit!r} in FITS header. Setting empty unit.")
        unit = ""

    return standardise_unit(unit)


def energy_unit_format(E):
    """Format energy quantities to a string representation that is more comfortable to read.

    This is done by switching to the most relevant unit (keV, MeV, GeV, TeV) and changing the float precision.

    Parameters
    ----------
    E: `~astropy.units.Quantity`
        Quantity or list of quantities.

    Returns
    -------
    str : str
        A string or tuple of strings with energy unit formatted.
    """
    try:
        iter(E)
    except TypeError:
        pass
    else:
        return tuple(map(energy_unit_format, E))

    i = floor(np.log10(E.to_value(u.eV)) / 3)  # a new unit every 3 decades
    unit = (u.eV, u.keV, u.MeV, u.GeV, u.TeV, u.PeV)[i] if i < 5 else u.PeV

    v = E.to_value(unit)
    i = floor(np.log10(v))
    prec = (2, 1, 0)[i] if i < 3 else 0

    return f"{v:0.{prec}f} {unit}"
