# # # This source code is subject to the license referenced at
# # # https://github.com/NRLMMD-GEOIPS.

"""Data manipulation steps for standard "single_channel" algorithm.

Generalized algorithm to apply data manipulation steps in a standard order
to apply corrections to a single channel output product.
"""

import logging

from geoips.interfaces.class_based.algorithms import BaseAlgorithmPlugin

LOG = logging.getLogger(__name__)


class SingleChannelAlgorithmPlugin(BaseAlgorithmPlugin):
    """Plugin class for the single channel algorithm.

    Applies a series of masking and data manipulation to a series of arrays containing
    data from a single channel.
    """

    KtoC_conversion = -273.15

    interface = "algorithms"
    family = "list_numpy_to_numpy"
    name = "single_channel_cb"

    def call(
        self,
        arrays,
        output_data_range=None,
        input_units=None,
        output_units=None,
        min_outbounds="crop",
        max_outbounds="crop",
        norm=False,
        inverse=False,
        sun_zen_correction=False,
        mask_night=False,
        max_day_zen=None,
        mask_day=False,
        min_night_zen=None,
        gamma_list=None,
        scale_factor=None,
        satellite_zenith_angle_cutoff=None,
    ):
        """Apply data range and requested corrections to a single channel product.

        Data manipulation steps for applying a data range and requested corrections
        to a single channel product.

        Parameters
        ----------
        arrays : list of numpy.ndarray
            * list of numpy.ndarray or numpy.MaskedArray of channel data
            * MUST be length one for single_channel algorithm.
            * If applying solar zenith corrections and diurnal masking, the second index
              in the arrays list must be the solar zenith angle array. This is
              controlled in product YAML.
            * If applying a satellite zenith angle cutoff, the final index in the arrays
              list must be the satellite zenith angle array. This is controlled in the
              product YAML.
        output_data_range : list of float, default=None
            * list of min and max value for output data product.
            * This is applied LAST after all other corrections/adjustments
            * If None, use data min and max.
        input_units : str, default=None
            * Units of input data, for applying necessary conversions
            * If None, no conversion
        output_units : str, default=None
            * Units of output data, for applying necessary conversions
            * If None, no conversion
        min_outbounds : str, default='crop'
            * Method to use when applying bounds.  Valid values are:

                * retain: keep all pixels as is
                * mask: mask all pixels that are out of range
                * crop: set all out of range values to either min_val or max_val
                  as appropriate
        max_outbounds : str, default='crop'
            * Method to use when applying bounds.  Valid values are:

                * retain: keep all pixels as is
                * mask: mask all pixels that are out of range
                * crop: set all out of range values to either min_val or max_val
                  as appropriate
        norm : bool, default=False
            * Boolean flag indicating whether to normalize (True) or not (False)

                * If True, returned data will be in the range from 0 to 1
                * If False, returned data will be in the range from min_val to max_val
        inverse : bool, default=False
            * Boolean flag indicating whether to inverse (True) or not (False)

                * If True, returned data will be inverted
                * If False, returned data will not be inverted
        sun_zenith_correction : bool, default=False
            * Boolean flag indicating whether to apply solar zenith correction
              (True) or not (False)

                * If True, returned data will have solar zenith correction applied
                  (see data_manipulations.corrections.apply_solar_zenith_correction)
                * If False, returned data will not be modified based on solar zenith
                  angle
        satellite_zenith_angle_cutoff : float, default=None
            * Cutoff for masking data where satellite zenith angle exceeds threshold
            * If None, no masking

        Notes
        -----
        Order of operations, based on the passed arguments, is:

        1. Mask night
        2. Mask day
        3. Apply satellite zenith angle cutoff
        4. Apply solar zenith correction
        5. Apply gamma values
        6. Apply scale factor
        7. Convert units
        8. Apply data range.

        NOTE: If "norm=True" is specified, the "output_data_range" will NOT
        match the actual range of the returned data, since the normalized data
        will be returned between 0 and 1.

        If you require a different order of operations than that specified within
        "single_channel" algorithm, please create a new algorithm for your desired
        order of operations.

        Returns
        -------
        numpy.ndarray
            numpy.ndarray or numpy.MaskedArray of appropriately scaled channel data,
            in units "output_units".
        """
        data = arrays[0]
        if output_data_range is None:
            output_data_range = [data.min(), data.max()]

        # Mask everything greater than max_day_zen
        # day zenith angles are less than 90
        # night zenith angles are greater than 90
        if max_day_zen and len(arrays) >= 2:
            from geoips.data_manipulations.info import percent_unmasked
            from geoips.data_manipulations.corrections import mask_night

            sun_zenith = arrays[1]
            LOG.info("Percent unmasked day/night %s", percent_unmasked(data))
            data = mask_night(data, sun_zenith, max_day_zen)
            LOG.info("Percent unmasked day only %s", percent_unmasked(data))

        # Mask everything less than min_night_zen
        # day zenith angles are less than 90
        # night zenith angles are greater than 90
        if mask_day and min_night_zen and len(arrays) >= 2:
            from geoips.data_manipulations.info import percent_unmasked
            from geoips.data_manipulations.corrections import mask_day

            sun_zenith = arrays[1]
            LOG.info("Percent unmasked day/night %s", percent_unmasked(data))
            data = mask_day(data, sun_zenith, min_night_zen)
            LOG.info("Percent unmasked night only %s", percent_unmasked(data))

        if satellite_zenith_angle_cutoff is not None and len(arrays) >= 2:
            from geoips.data_manipulations.info import percent_unmasked
            from geoips.data_manipulations.corrections import (
                apply_satellite_zenith_cutoff,
            )

            sat_zenith = arrays[-1]
            data = apply_satellite_zenith_cutoff(
                data, sat_zenith, satellite_zenith_angle_cutoff
            )
            LOG.info(
                "Percent unmasked after zenith angle cutoff %s", percent_unmasked(data)
            )

        if sun_zen_correction and len(arrays) >= 2:
            sun_zenith = arrays[1]
            from geoips.data_manipulations.corrections import (
                apply_solar_zenith_correction,
            )

            data = apply_solar_zenith_correction(data, sun_zenith)

        if gamma_list is not None:
            from geoips.data_manipulations.corrections import apply_gamma

            for gamma in gamma_list:
                data = apply_gamma(data, gamma)

        if scale_factor is not None:
            from geoips.data_manipulations.corrections import apply_scale_factor

            data = apply_scale_factor(data, scale_factor)

        from geoips.data_manipulations.conversions import unit_conversion

        data = unit_conversion(data, input_units, output_units)

        from geoips.data_manipulations.corrections import apply_data_range

        data = apply_data_range(
            data,
            min_val=output_data_range[0],
            max_val=output_data_range[1],
            min_outbounds=min_outbounds,
            max_outbounds=max_outbounds,
            norm=norm,
            inverse=inverse,
        )

        return data


PLUGIN_CLASS = SingleChannelAlgorithmPlugin
