import ee
import openet.core.common

from . import utils
# import utils


def lazy_property(fn):
    """Decorator that makes a property lazy-evaluated

    https://stevenloria.com/lazy-properties/
    """
    attr_name = '_lazy_' + fn.__name__

    @property
    def _lazy_property(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)
    return _lazy_property


class Image():
    """GEE based model for computing NDVI"""

    def __init__(
        self,
        image,
    ):
        """Earth Engine based NDVI image object

        Parameters
        ----------
        image : ee.Image
            Required band: ndvi
            Required properties: system:time_start, system:index, system:id

        """
        self.image = image

        # Get system properties from the input image
        self._id = self.image.get('system:id')
        self._index = self.image.get('system:index')
        self._time_start = self.image.get('system:time_start')
        self._properties = {
            'system:index': self._index,
            'system:time_start': self._time_start,
            'image_id': self._id,
        }

        # Build date properties from the system:time_start
        self._date = ee.Date(self._time_start)
        self._year = ee.Number(self._date.get('year'))
        self._start_date = ee.Date(utils.date_0utc(self._date).millis())
        self._end_date = self._start_date.advance(1, 'day')

    def calculate(self, variables=['ndvi']):
        """Return a multiband image of calculated variables

        Parameters
        ----------
        variables : list

        Returns
        -------
        ee.Image

        """
        output_images = []
        for v in variables:
            if v.lower() == 'ndvi':
                output_images.append(self.ndvi.float())
            elif v.lower() == 'mask':
                output_images.append(self.mask)
            elif v.lower() == 'time':
                output_images.append(self.time)
            else:
                raise ValueError(f'unsupported variable: {v}')

        return ee.Image(output_images).set(self._properties)

    @lazy_property
    def mask(self):
        """Mask of all active pixels based on the final Kc

        Using Kc here to capture any masking that might be in the crop_type

        Returns
        -------
        ee.Image

        """
        return (
            self.ndvi.multiply(0).add(1).updateMask(1)
            .rename(['mask']).set(self._properties).uint8()
        )

    @lazy_property
    def ndvi(self):
        """Normalized difference vegetation index (NDVI)

        Returns
        -------
        ee.Image

        """
        return self.image.select(['ndvi']).set(self._properties)

    @lazy_property
    def time(self):
        """Image of the 0 UTC time (in milliseconds)

        Returns
        -------
        ee.Image

        """
        return (
            self.mask
            .double().multiply(0).add(utils.date_0utc(self._date).millis())
            .rename(['time']).set(self._properties)
        )

    @classmethod
    def from_image_id(cls, image_id, **kwargs):
        """Construct an NDVI Image instance from an image ID

        Parameters
        ----------
        image_id : str
            A full earth engine image ID.
            (i.e. 'LANDSAT/LC08/C02/T1_L2/LC08_044033_20170716')
        kwargs : dict
            Keyword arguments to pass through to model init.

        Returns
        -------
        new instance of Image class

        Raises
        ------
        ValueError for an unsupported collection ID.

        """
        collection_methods = {
            'LANDSAT/LC09/C02/T1_L2': 'from_landsat_c2_sr',
            'LANDSAT/LC08/C02/T1_L2': 'from_landsat_c2_sr',
            'LANDSAT/LE07/C02/T1_L2': 'from_landsat_c2_sr',
            'LANDSAT/LT05/C02/T1_L2': 'from_landsat_c2_sr',
            'LANDSAT/LT04/C02/T1_L2': 'from_landsat_c2_sr',
        }

        try:
            method_name = collection_methods[image_id.rsplit('/', 1)[0]]
        except KeyError:
            raise ValueError(f'unsupported collection ID: {image_id}')
        except Exception as e:
            raise Exception(f'unhandled exception: {e}')

        method = getattr(Image, method_name)

        return method(ee.Image(image_id), **kwargs)

    @classmethod
    def from_landsat_c2_sr(cls, l2_image, cloudmask_args={}, **kwargs):
        """Construct an NDVI Image instance from a Landsat C02 level 2 (SR) image

        Parameters
        ----------
        input_img : ee.Image, str
            A raw Landsat Collection 2 level 2 (SR) image or image ID.
        cloudmask_args : dict
            keyword arguments to pass through to cloud mask function
        kwargs : dict
            Keyword arguments to pass through to model init.

        Returns
        -------
        new instance of Image class

        """
        l2_image = ee.Image(l2_image)

        # Default the cloudmask flags to True if they were not
        # Eventually these will probably all default to True in openet.core
        if 'cirrus_flag' not in cloudmask_args.keys():
            cloudmask_args['cirrus_flag'] = True
        if 'dilate_flag' not in cloudmask_args.keys():
            cloudmask_args['dilate_flag'] = True
        if 'shadow_flag' not in cloudmask_args.keys():
            cloudmask_args['shadow_flag'] = True
        if 'snow_flag' not in cloudmask_args.keys():
            cloudmask_args['snow_flag'] = True
        if 'cloud_score_flag' not in cloudmask_args.keys():
            cloudmask_args['cloud_score_flag'] = False
        if 'cloud_score_pct' not in cloudmask_args.keys():
            cloudmask_args['cloud_score_pct'] = 100
        if 'filter_flag' not in cloudmask_args.keys():
            cloudmask_args['filter_flag'] = False
        if 'saturated_flag' not in cloudmask_args.keys():
            cloudmask_args['saturated_flag'] = False

        cloud_mask = openet.core.common.landsat_c2_sr_cloud_mask(l2_image, **cloudmask_args)

        # Build the input image
        sr_image = openet.core.landsat.c02_l2_sr(l2_image).select(['red', 'nir'])
        input_image = openet.core.landsat.c02_sr_ndvi(sr_image)
        #input_image = ee.Image([openet.core.landsat.c02_sr_ndvi(prep_image)])

        # Apply the cloud mask and add properties
        input_image = (
            input_image
            .updateMask(cloud_mask)
            .set({
                'system:index': l2_image.get('system:index'),
                'system:time_start': l2_image.get('system:time_start'),
                'system:id': l2_image.get('system:id'),
            })
        )

        return cls(input_image, **kwargs)
