import datetime
import os
import ee
import math
import sys
import json
from ee.ee_exception import EEException
from gee.inputs import getLandsat, getS1


########## Helper functions ##########


def initialize(ee_account='', ee_key_path=''):
    try:
        if ee_account and ee_key_path and os.path.exists(ee_key_path):
            credentials = ee.ServiceAccountCredentials(ee_account, ee_key_path)
            ee.Initialize(credentials)
        else:
            ee.Initialize()
    except Exception as e:
        print(e)


def getReducer(reducer):
    reducerName = reducer.lower()
    if(reducerName == 'min'):
        return ee.Reducer.min()
    elif (reducerName == 'max'):
        return ee.Reducer.max()
    elif (reducerName == 'mean'):
        return ee.Reducer.mean()
    elif (reducerName == 'mode'):
        return ee.Reducer.mode()
    elif (reducerName == 'first'):
        return ee.Reducer.first()
    elif (reducerName == 'last'):
        return ee.Reducer.last()
    elif (reducerName == 'sum'):
        return ee.Reducer.sum()
    else:
        return ee.Reducer.median()


def reduceIC(imageCollection, reducer):
    reducerName = reducer.lower()
    if(reducerName == 'min'):
        return imageCollection.min()
    elif (reducerName == 'max'):
        return imageCollection.max()
    elif (reducerName == 'mean'):
        return imageCollection.mean()
    elif (reducerName == 'mode'):
        return imageCollection.mode()
    elif (reducerName == 'mosaic'):
        return imageCollection.mosaic()
    elif (reducerName == 'first'):
        return imageCollection.first()
    elif (reducerName == 'sum'):
        return imageCollection.sum()
    else:
        return imageCollection.median()


def safeParseJSON(val):
    if isinstance(val,  dict):
        return val
    else:
        try:
            return json.loads(val)
        except Exception as e:
            try:
                return json.loads(val.replace("'", "\""))
            except Exception as e:
                return {}


########## Helper routes ##########


def listAvailableBands(name, assetType):
    eeImage = None
    if assetType == "imageCollection":
        eeImage = ee.ImageCollection(name).first()
    else:
        eeImage = ee.Image(name)
    return {
        'bands': eeImage.bandNames().getInfo(),
        'imageName': name
    }

########## ee.Image ##########


def imageToMapId(image, visParams):
    eeImage = ee.Image(image)
    mapId = eeImage.getMapId(visParams)
    # TODO, just return URL so the routes are easier to deduce whats being returned.
    return {
        'url': mapId['tile_fetcher'].url_format
    }

########## ee.ImageCollection ##########


def imageCollectionToMapId(assetId, visParams, reducer, startDate, endDate):
    eeCollection = ee.ImageCollection(assetId)
    if (startDate and endDate):
        eeFilterDate = ee.Filter.date(startDate, endDate)
        eeCollection = eeCollection.filter(eeFilterDate)

    reducedImage = ee.Image(reduceIC(eeCollection, reducer))
    return imageToMapId(reducedImage, visParams)

# TODO, should we allow user to select first cloud free image again?


def firstCloudFreeImageInMosaicToMapId(assetId, visParams, startDate, endDate):
    skipCloudMask = False
    eeCollection = ee.ImageCollection(assetId)
    lowerAsset = assetId.lower()
    if("b2" not in visParams["bands"].lower()):
        skipCloudMask = True
    elif ("lc8" in lowerAsset):
        skipCloudMask = False
    elif ("le7" in lowerAsset):
        skipCloudMask = False
    elif ("lt5" in lowerAsset):
        skipCloudMask = False
    else:
        skipCloudMask = True
    if (startDate and endDate):
        eeFilterDate = ee.Filter.date(startDate, endDate)
        eeCollection = eeCollection.filter(eeFilterDate)
    eeFirstImage = ee.Image(eeCollection.mosaic())
    try:
        if(skipCloudMask == False):
            sID = ''
            if ("lc8" in lowerAsset):
                sID = 'OLI_TIRS'
            elif ("le7" in lowerAsset):
                sID = 'ETM'
            elif ("lt5" in lowerAsset):
                sID = 'TM'
            scored = ee.Algorithms.Landsat.simpleCloudScore(
                eeFirstImage.set('SENSOR_ID', sID))
            mask = scored.select(['cloud']).lte(20)
            masked = eeFirstImage.updateMask(mask)
            values = imageToMapId(masked, visParams)
        else:
            values = imageToMapId(eeFirstImage, visParams)
    except EEException as ine:
        imageToMapId(eeFirstImage, visParams)
    return values

########## ee.FeatureCollection ##########


def getFeatureCollectionTileUrl(featureCollection, field, matchID, visParams):
    fc = ee.FeatureCollection(featureCollection)
    single = fc.filter(ee.Filter.equals(field, matchID))
    mapId = ee.Image().paint(single, 0, 2).getMapId(visParams)
    return mapId['tile_fetcher'].url_format

########## Pre defined ee.ImageCollection ##########

# Index Image Collection


def lsMaskClouds(img, cloudThresh=10):
    score = ee.Image(1.0)
    # Clouds are reasonably bright in the blue band.
    blue_rescale = img.select('blue').subtract(ee.Number(0.1)).divide(
        ee.Number(0.3).subtract(ee.Number(0.1)))
    score = score.min(blue_rescale)

    # Clouds are reasonably bright in all visible bands.
    visible = img.select('red').add(
        img.select('green')).add(img.select('blue'))
    visible_rescale = visible.subtract(ee.Number(0.2)).divide(
        ee.Number(0.8).subtract(ee.Number(0.2)))
    score = score.min(visible_rescale)

    # Clouds are reasonably bright in all infrared bands.
    infrared = img.select('nir').add(
        img.select('swir1')).add(img.select('swir2'))
    infrared_rescale = infrared.subtract(ee.Number(0.3)).divide(
        ee.Number(0.8).subtract(ee.Number(0.3)))
    score = score.min(infrared_rescale)

    # Clouds are reasonably cool in temperature.
    temp_rescale = img.select('temp').subtract(ee.Number(300)).divide(
        ee.Number(290).subtract(ee.Number(300)))
    score = score.min(temp_rescale)

    # However, clouds are not snow.
    ndsi = img.normalizedDifference(['green', 'swir1'])
    ndsi_rescale = ndsi.subtract(ee.Number(0.8)).divide(
        ee.Number(0.6).subtract(ee.Number(0.8)))
    score = score.min(ndsi_rescale).multiply(100).byte()
    mask = score.lt(cloudThresh).rename(['cloudMask'])
    img = img.updateMask(mask)
    return img.addBands(score)


def s2MaskClouds(img):
    qa = img.select('QA60')

    # Bits 10 and 11 are clouds and cirrus, respectively.
    cloudBitMask = int(math.pow(2, 10))
    cirrusBitMask = int(math.pow(2, 11))

    # clear if both flags set to zero.
    clear = qa.bitwiseAnd(cloudBitMask).eq(0).And(
        qa.bitwiseAnd(cirrusBitMask).eq(0))

    return img.divide(10000).updateMask(clear).set('system:time_start', img.get('system:time_start'))


def bandPassAdjustment(img):
    keep = img.select(['temp'])
    bands = ['blue', 'green', 'red', 'nir', 'swir1', 'swir2']
    # linear regression coefficients for adjustment
    gain = ee.Array([[0.977], [1.005], [0.982], [1.001], [1.001], [0.996]])
    bias = ee.Array([[-0.00411], [-0.00093], [0.00094],
                    [-0.00029], [-0.00015], [-0.00097]])
    # Make an Array Image, with a 2-D Array per pixel.
    arrayImage2D = img.select(bands).toArray().toArray(1)

    # apply correction factors and reproject array to geographic image
    componentsImage = ee.Image(gain).multiply(arrayImage2D).add(ee.Image(bias)) \
        .arrayProject([0]).arrayFlatten([bands]).float()

    # .set('system:time_start',img.get('system:time_start'));
    return keep.addBands(componentsImage)


def getLandSatMergedCollection():
    sensorBandDictLandsatTOA = {'L8': [1, 2, 3, 4, 5, 9, 6],
                                'L7': [0, 1, 2, 3, 4, 5, 7],
                                'L5': [0, 1, 2, 3, 4, 5, 6],
                                'L4': [0, 1, 2, 3, 4, 5, 6],
                                'S2': [1, 2, 3, 7, 11, 10, 12]}
    bandNamesLandsatTOA = ['blue', 'green',
                           'red', 'nir', 'swir1', 'temp', 'swir2']
    metadataCloudCoverMax = 100
    lt4 = ee.ImageCollection('LANDSAT/LT4_L1T_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L4'], bandNamesLandsatTOA).map(lsMaskClouds)
    lt5 = ee.ImageCollection('LANDSAT/LT5_L1T_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L5'], bandNamesLandsatTOA).map(lsMaskClouds)
    le7 = ee.ImageCollection('LANDSAT/LE7_L1T_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L7'], bandNamesLandsatTOA).map(lsMaskClouds)
    lc8 = ee.ImageCollection('LANDSAT/LC08/C01/T1_TOA') \
        .filterMetadata('CLOUD_COVER', 'less_than', metadataCloudCoverMax) \
        .select(sensorBandDictLandsatTOA['L8'], bandNamesLandsatTOA).map(lsMaskClouds)
    s2 = ee.ImageCollection('COPERNICUS/S2') \
        .filterMetadata('CLOUDY_PIXEL_PERCENTAGE', 'less_than', metadataCloudCoverMax) \
        .map(s2MaskClouds).select(sensorBandDictLandsatTOA['S2'], bandNamesLandsatTOA) \
        .map(bandPassAdjustment)
    return ee.ImageCollection(lt4.merge(lt5).merge(le7).merge(lc8).merge(s2))


def filteredImageNDVIToMapId(startDate, endDate):
    def calcNDVI(img):
        return img.expression('(i.nir - i.red) / (i.nir + i.red)',  {'i': img}).rename(['NDVI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = 'c9c0bf,435ebf,eee8aa,006400'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcNDVI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageEVIToMapId(startDate, endDate):
    def calcEVI(img):
        return img.expression('2.5 * (i.nir - i.red) / (i.nir + 6.0 * i.red - 7.5 * i.blue + 1)',  {'i': img}).rename(['EVI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = 'F5F5F5,E6D3C5,C48472,B9CF63,94BF3D,6BB037,42A333,00942C,008729,007824,004A16'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcEVI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageEVI2ToMapId(startDate, endDate):
    def calcEVI2(img):
        return img.expression('2.5 * (i.nir - i.red) / (i.nir + 2.4 * i.red + 1)',  {'i': img}).rename(['EVI2']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = 'F5F5F5,E6D3C5,C48472,B9CF63,94BF3D,6BB037,42A333,00942C,008729,007824,004A16'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcEVI2).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageNDMIToMapId(startDate, endDate):
    def calcNDMI(img):
        return img.expression('(i.nir - i.swir1) / (i.nir + i.swir1)',  {'i': img}).rename(['NDMI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = '0000FE,2E60FD,31B0FD,00FEFE,50FE00,DBFE66,FEFE00,FFBB00,FF6F00,FE0000'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcNDMI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageNDWIToMapId(startDate, endDate):
    def calcNDWI(img):
        return img.expression('(i.green - i.nir) / (i.green + i.nir)',  {'i': img}).rename(['NDWI']) \
            .set('system:time_start', img.get('system:time_start'))

    eeCollection = getLandSatMergedCollection().filterDate(startDate, endDate)
    colorPalette = '505050,E8E8E8,00FF33,003300'
    visParams = {'opacity': 1, 'max': 1,
                 'min': -1, 'palette': colorPalette}
    eviImage = ee.Image(eeCollection.map(calcNDWI).mean())
    return imageToMapId(eviImage, visParams)


def filteredImageByIndexToMapId(startDate, endDate, index):
    lowerIndex = index.lower()
    if (lowerIndex == 'ndvi'):
        return filteredImageNDVIToMapId(startDate, endDate)
    elif (lowerIndex == 'evi'):
        return filteredImageEVIToMapId(startDate, endDate)
    elif (lowerIndex == 'evi2'):
        return filteredImageEVI2ToMapId(startDate, endDate)
    elif (lowerIndex == 'ndmi'):
        return filteredImageNDMIToMapId(startDate, endDate)
    elif (lowerIndex == 'ndwi'):
        return filteredImageNDWIToMapId(startDate, endDate)


def filteredImageCompositeToMapId(assetId, visParams, startDate, endDate, metadataCloudCoverMax, simpleCompositeVariable):
    eeCollection = ee.ImageCollection(assetId)
    if (startDate and endDate):
        eeCollection = eeCollection.filterDate(startDate, endDate)
    eeCollection.filterMetadata(
        'CLOUD_COVER',
        'less_than',
        metadataCloudCoverMax
    )
    eeMosaicImage = ee.Algorithms.Landsat.simpleComposite(
        eeCollection,
        simpleCompositeVariable,
        10,
        40,
        True
    )
    return imageToMapId(eeMosaicImage, visParams)


def filteredSentinelComposite(visParams, startDate, endDate, metadataCloudCoverMax):
    def cloudScore(img):
        def rescale(img, exp, thresholds):
            return img.expression(exp, {'img': img}).subtract(thresholds[0]).divide(thresholds[1] - thresholds[0])

        score = ee.Image(1.0)
        score = score.min(rescale(img, 'img.B2', [0.1, 0.3]))
        score = score.min(rescale(img, 'img.B4 + img.B3 + img.B2', [0.2, 0.8]))
        score = score.min(
            rescale(img, 'img.B8 + img.B11 + img.B12', [0.3, 0.8]))
        ndsi = img.normalizedDifference(['B3', 'B11'])
        return score.min(rescale(ndsi, 'img', [0.8, 0.6]))

    def cloudScoreS2(img):
        rescale = img.divide(10000)
        score = cloudScore(rescale).multiply(100).rename('cloudscore')
        return img.addBands(score)

    sentinel2 = ee.ImageCollection('COPERNICUS/S2')
    f2017s2 = sentinel2.filterDate(startDate, endDate).filterMetadata(
        'CLOUDY_PIXEL_PERCENTAGE', 'less_than', metadataCloudCoverMax)
    m2017s2 = f2017s2.map(cloudScoreS2)
    m2017s3 = m2017s2.median()
    return imageToMapId(m2017s3, visParams)


def filteredSentinelSARComposite(visParams, startDate, endDate):
    def toNatural(img):
        return ee.Image(10).pow(img.divide(10))

    def addRatioBands(img):
        # not using angle band
        vv = img.select('VV')
        vh = img.select('VH')
        vv_vh = vv.divide(vh).rename('VV/VH')
        vh_vv = vh.divide(vv).rename('VH/VV')
        return vv.addBands(vh).addBands(vv_vh).addBands(vh_vv)

    sentinel1 = ee.ImageCollection('COPERNICUS/S1_GRD')
    sentinel1 = sentinel1.filterDate(startDate, endDate) \
        .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV')) \
        .filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH')) \
        .filter(ee.Filter.eq('instrumentMode', 'IW'))

    sentinel1 = sentinel1.map(toNatural)
    sentinel1 = sentinel1.map(addRatioBands)
    median = sentinel1.median()
    return imageToMapId(median, visParams)

########## Time Series ##########


def getTimeSeriesByCollectionAndIndex(assetId, indexName, scale, coords, startDate, endDate, reducer):
    geometry = None
    indexCollection = None
    if isinstance(coords[0], list):
        geometry = ee.Geometry.Polygon(coords)
    else:
        geometry = ee.Geometry.Point(coords)
    if indexName != None:
        indexCollection = ee.ImageCollection(assetId).filterDate(
            startDate, endDate).select(indexName)
    else:
        indexCollection = ee.ImageCollection(
            assetId).filterDate(startDate, endDate)

    def getIndex(image):
        theReducer = getReducer(reducer)
        if indexName != None:
            indexValue = image.reduceRegion(
                theReducer, geometry, scale).get(indexName)
        else:
            indexValue = image.reduceRegion(theReducer, geometry, scale)
        date = image.get('system:time_start')
        indexImage = ee.Image().set(
            'indexValue', [ee.Number(date), indexValue])
        return indexImage

    def getClipped(image):
        return image.clip(geometry)

    clippedcollection = indexCollection.map(getClipped)
    indexCollection1 = clippedcollection.map(getIndex)
    indexCollection2 = indexCollection1.aggregate_array('indexValue')
    return indexCollection2.getInfo()


def getTimeSeriesByIndex(indexName, scale, coords, startDate, endDate, reducer):
    bandsByCollection = {
        'LANDSAT/LC08/C01/T1_TOA': ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
        'LANDSAT/LC08/C01/T2_TOA': ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
        'LANDSAT/LE07/C01/T1_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LE07/C01/T2_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT05/C01/T1_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT05/C01/T2_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT04/C01/T1_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7'],
        'LANDSAT/LT04/C01/T2_TOA': ['B1', 'B2', 'B3', 'B4', 'B5', 'B7']
    }
    indexes = {
        'NDVI': '(nir - red) / (nir + red)',
        'EVI': '2.5 * (nir - red) / (nir + 6.0 * red - 7.5 * blue + 1)',
        'EVI2': '2.5 * (nir - red) / (nir + 2.4 * red + 1)',
        'NDMI': '(nir - swir1) / (nir + swir1)',
        'NDWI': '(green - nir) / (green + nir)',
        'NBR': '(nir - swir2) / (nir + swir2)',
        'LSAVI': '((nir - red) / (nir + red + 0.5)) * (1 + 0.5)'
    }

    def create(name):
        def maskClouds(image):
            def isSet(types):
                """ https://landsat.usgs.gov/collectionqualityband """
                typeByValue = {
                    'badPixels': 15,
                    'cloud': 16,
                    'shadow': 256,
                    'snow': 1024,
                    'cirrus': 4096
                }
                anySet = ee.Image(0)
                for Type in types:
                    anySet = anySet.Or(image.select(
                        'BQA').bitwiseAnd(typeByValue[Type]).neq(0))
                return anySet
            return image.updateMask(isSet(['badPixels', 'cloud', 'shadow', 'cirrus']).Not())

        def toIndex(image):
            bands = bandsByCollection[name]
            return image.expression(indexes[indexName], {
                'blue': image.select(bands[0]),
                'green': image.select(bands[1]),
                'red': image.select(bands[2]),
                'nir': image.select(bands[3]),
                'swir1': image.select(bands[4]),
                'swir2': image.select(bands[5]),
            }).clamp(-1, 1).rename(['index'])

        def toIndexWithTimeStart(image):
            time = image.get('system:time_start')
            image = maskClouds(image)
            return toIndex(image).set('system:time_start', time)
        #
        if startDate and endDate:
            return ee.ImageCollection(name).filterDate(startDate, endDate).filterBounds(geometry).map(toIndexWithTimeStart, True)
        else:
            return ee.ImageCollection(name).filterBounds(geometry).map(toIndexWithTimeStart, True)

    def reduceRegion(image):
        theReducer = getReducer(reducer)
        reduced = image.reduceRegion(
            theReducer, geometry=geometry, scale=scale, maxPixels=1e6)
        return ee.Feature(None, {
            'index': reduced.get('index'),
            'timeIndex': [image.get('system:time_start'), reduced.get('index')]
        })

    geometry = None
    if isinstance(coords[0], list) or isinstance(coords[0], tuple):
        geometry = ee.Geometry.Polygon(coords)
    else:
        geometry = ee.Geometry.Point(coords)
    collection = ee.ImageCollection([])
    for name in bandsByCollection:
        collection = collection.merge(create(name))
    return ee.ImageCollection(ee.ImageCollection(collection).sort('system:time_start').distinct('system:time_start')) \
        .map(reduceRegion) \
        .filterMetadata('index', 'not_equals', None) \
        .aggregate_array('timeIndex') \
        .getInfo()


########## Degradation##########

def getDegradationTileUrlByDateS1(geometry, date, visParams):
    imDate = datetime.datetime.strptime(date, "%Y-%m-%d")
    befDate = imDate - datetime.timedelta(days=1)
    aftDate = imDate + datetime.timedelta(days=1)

    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)

    sentinel1Data = getS1({
        "targetBands": ['VV', 'VH', 'VV/VH'],
        'region': geometry})

    start = befDate.strftime('%Y-%m-%d')
    end = aftDate.strftime('%Y-%m-%d')

    selectedImage = sentinel1Data.filterDate(start, end).first()

    selectedImage = ee.Image(selectedImage)
    mapparams = selectedImage.getMapId(visParams)
    return mapparams['tile_fetcher'].url_format


def getDegradationPlotsByPointS1(geometry, start, end):
    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)

    sentinel1Data = getS1({
        "targetBands": ['VV', 'VH', 'VV/VH'],
        'region': geometry
    }).filterDate(start, end)

    def myimageMapper(img):
        theReducer = ee.Reducer.mean()
        indexValue = img.reduceRegion(theReducer, geometry, 30)
        date = img.get('system:time_start')
        visParams = {'bands': ['VV', 'VH', 'ratioVVVH'],
                     'min': [-15, -25, .40], 'max': [0, -10, 1], 'gamma': 1.6}
        indexImage = ee.Image().set(
            'indexValue', [ee.Number(date), indexValue])
        return indexImage
    lsd = sentinel1Data.map(myimageMapper, True)
    indexCollection2 = lsd.aggregate_array('indexValue')
    values = indexCollection2.getInfo()
    return values


def getDegradationTileUrlByDate(geometry, date, visParams):
    imDate = datetime.datetime.strptime(date, "%Y-%m-%d")
    startDate = imDate - datetime.timedelta(days=1)
    endDate = imDate + datetime.timedelta(days=1)
    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)
    landsatData = getLandsat({
        "start": startDate.strftime('%Y-%m-%d'),
        "end": endDate.strftime('%Y-%m-%d'),
        "targetBands": ['RED', 'GREEN', 'BLUE', 'SWIR1', 'NIR'],
        "region": geometry,
        "sensors": {"l4": False, "l5": False, "l7": False, "l8": True}
    })

    selectedImage = landsatData.first()
    unmasked = ee.Image(selectedImage).multiply(10000).toInt16().unmask()
    mapparams = unmasked.getMapId(visParams)
    return mapparams['tile_fetcher'].url_format


def getDegradationPlotsByPoint(geometry, start, end, band):
    if isinstance(geometry[0], list):
        geometry = ee.Geometry.Polygon(geometry)
    else:
        geometry = ee.Geometry.Point(geometry)
    landsatData = getLandsat({
        "start": start,
        "end": end,
        "targetBands": [band],
        "region": geometry,
        "sensors": {"l4": True, "l5": True, "l7": True, "l8": True}
    })

    def myImageMapper(img):
        theReducer = ee.Reducer.mean()
        indexValue = img.reduceRegion(theReducer, geometry, 30)
        date = img.get('system:time_start')
        indexImage = ee.Image().set(
            'indexValue',
            [ee.Number(date), indexValue]
        )
        return indexImage
    lsd = landsatData.map(myImageMapper, True)
    indexCollection2 = lsd.aggregate_array('indexValue')
    values = indexCollection2.getInfo()
    return values


########## Stats ##########

def getStatistics(extent):
    extentGeom = ee.Geometry.Polygon(extent)
    elev = ee.Image('USGS/GTOPO30')
    minmaxElev = elev.reduceRegion(
        ee.Reducer.minMax(), extentGeom, 1000, maxPixels=500000000)
    minElev = minmaxElev.get('elevation_min').getInfo()
    maxElev = minmaxElev.get('elevation_max').getInfo()
    ciesinPopGrid = ee.Image('CIESIN/GPWv4/population-count/2020')
    popDict = ciesinPopGrid.reduceRegion(
        ee.Reducer.sum(), extentGeom, maxPixels=500000000)
    pop = popDict.get('population-count').getInfo()
    pop = int(pop)
    return {
        'minElev': minElev,
        'maxElev': maxElev,
        'pop': pop
    }

[(96, 'ee.Image', 'ee.Image', False, 'import ee\n'), (107, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (120, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (160, 'ee.FeatureCollection', 'ee.FeatureCollection', False, 'import ee\n'), (171, 'ee.Image', 'ee.Image', False, 'import ee\n'), (224, 'ee.Array', 'ee.Array', False, 'import ee\n'), (225, 'ee.Array', 'ee.Array', False, 'import ee\n'), (346, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (354, 'ee.Algorithms.Landsat.simpleComposite', 'ee.Algorithms.Landsat.simpleComposite', False, 'import ee\n'), (382, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (402, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (525, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (538, 'datetime.datetime.strptime', 'datetime.datetime.strptime', False, 'import datetime\n'), (547, 'gee.inputs.getS1', 'getS1', False, 'from gee.inputs import getLandsat, getS1\n'), (556, 'ee.Image', 'ee.Image', False, 'import ee\n'), (588, 'datetime.datetime.strptime', 'datetime.datetime.strptime', False, 'import datetime\n'), (614, 'gee.inputs.getLandsat', 'getLandsat', False, 'from gee.inputs import getLandsat, getS1\n'), (640, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (641, 'ee.Image', 'ee.Image', False, 'import ee\n'), (646, 'ee.Image', 'ee.Image', False, 'import ee\n'), (28, 'ee.Reducer.min', 'ee.Reducer.min', False, 'import ee\n'), (86, 'ee.Image', 'ee.Image', False, 'import ee\n'), (109, 'ee.Filter.date', 'ee.Filter.date', False, 'import ee\n'), (133, 'ee.Filter.date', 'ee.Filter.date', False, 'import ee\n'), (161, 'ee.Filter.equals', 'ee.Filter.equals', False, 'import ee\n'), (210, 'math.pow', 'math.pow', False, 'import math\n'), (211, 'math.pow', 'math.pow', False, 'import math\n'), (369, 'ee.Image', 'ee.Image', False, 'import ee\n'), (406, 'ee.Filter.eq', 'ee.Filter.eq', False, 'import ee\n'), (420, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (422, 'ee.Geometry.Point', 'ee.Geometry.Point', False, 'import ee\n'), (522, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (524, 'ee.Geometry.Point', 'ee.Geometry.Point', False, 'import ee\n'), (539, 'datetime.timedelta', 'datetime.timedelta', False, 'import datetime\n'), (540, 'datetime.timedelta', 'datetime.timedelta', False, 'import datetime\n'), (543, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (545, 'ee.Geometry.Point', 'ee.Geometry.Point', False, 'import ee\n'), (563, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (565, 'ee.Geometry.Point', 'ee.Geometry.Point', False, 'import ee\n'), (573, 'ee.Reducer.mean', 'ee.Reducer.mean', False, 'import ee\n'), (589, 'datetime.timedelta', 'datetime.timedelta', False, 'import datetime\n'), (590, 'datetime.timedelta', 'datetime.timedelta', False, 'import datetime\n'), (592, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (594, 'ee.Geometry.Point', 'ee.Geometry.Point', False, 'import ee\n'), (611, 'ee.Geometry.Polygon', 'ee.Geometry.Polygon', False, 'import ee\n'), (613, 'ee.Geometry.Point', 'ee.Geometry.Point', False, 'import ee\n'), (623, 'ee.Reducer.mean', 'ee.Reducer.mean', False, 'import ee\n'), (643, 'ee.Reducer.minMax', 'ee.Reducer.minMax', False, 'import ee\n'), (648, 'ee.Reducer.sum', 'ee.Reducer.sum', False, 'import ee\n'), (16, 'os.path.exists', 'os.path.exists', False, 'import os\n'), (17, 'ee.ServiceAccountCredentials', 'ee.ServiceAccountCredentials', False, 'import ee\n'), (18, 'ee.Initialize', 'ee.Initialize', False, 'import ee\n'), (20, 'ee.Initialize', 'ee.Initialize', False, 'import ee\n'), (30, 'ee.Reducer.max', 'ee.Reducer.max', False, 'import ee\n'), (70, 'json.loads', 'json.loads', False, 'import json\n'), (174, 'ee.Number', 'ee.Number', False, 'import ee\n'), (181, 'ee.Number', 'ee.Number', False, 'import ee\n'), (188, 'ee.Number', 'ee.Number', False, 'import ee\n'), (193, 'ee.Number', 'ee.Number', False, 'import ee\n'), (199, 'ee.Number', 'ee.Number', False, 'import ee\n'), (567, 'gee.inputs.getS1', 'getS1', False, 'from gee.inputs import getLandsat, getS1\n'), (32, 'ee.Reducer.mean', 'ee.Reducer.mean', False, 'import ee\n'), (84, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (173, 'ee.Number', 'ee.Number', False, 'import ee\n'), (174, 'ee.Number', 'ee.Number', False, 'import ee\n'), (180, 'ee.Number', 'ee.Number', False, 'import ee\n'), (181, 'ee.Number', 'ee.Number', False, 'import ee\n'), (187, 'ee.Number', 'ee.Number', False, 'import ee\n'), (188, 'ee.Number', 'ee.Number', False, 'import ee\n'), (192, 'ee.Number', 'ee.Number', False, 'import ee\n'), (193, 'ee.Number', 'ee.Number', False, 'import ee\n'), (198, 'ee.Number', 'ee.Number', False, 'import ee\n'), (199, 'ee.Number', 'ee.Number', False, 'import ee\n'), (392, 'ee.Image', 'ee.Image', False, 'import ee\n'), (405, 'ee.Filter.listContains', 'ee.Filter.listContains', False, 'import ee\n'), (427, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (438, 'ee.Image', 'ee.Image', False, 'import ee\n'), (439, 'ee.Number', 'ee.Number', False, 'import ee\n'), (483, 'ee.Image', 'ee.Image', False, 'import ee\n'), (578, 'ee.Image', 'ee.Image', False, 'import ee\n'), (579, 'ee.Number', 'ee.Number', False, 'import ee\n'), (626, 'ee.Image', 'ee.Image', False, 'import ee\n'), (628, 'ee.Number', 'ee.Number', False, 'import ee\n'), (34, 'ee.Reducer.mode', 'ee.Reducer.mode', False, 'import ee\n'), (162, 'ee.Image', 'ee.Image', False, 'import ee\n'), (36, 'ee.Reducer.first', 'ee.Reducer.first', False, 'import ee\n'), (404, 'ee.Filter.listContains', 'ee.Filter.listContains', False, 'import ee\n'), (424, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (38, 'ee.Reducer.last', 'ee.Reducer.last', False, 'import ee\n'), (247, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (250, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (253, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (256, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (509, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (604, 'ee.Image', 'ee.Image', False, 'import ee\n'), (40, 'ee.Reducer.sum', 'ee.Reducer.sum', False, 'import ee\n'), (42, 'ee.Reducer.median', 'ee.Reducer.median', False, 'import ee\n'), (231, 'ee.Image', 'ee.Image', False, 'import ee\n'), (259, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (507, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n'), (231, 'ee.Image', 'ee.Image', False, 'import ee\n'), (528, 'ee.ImageCollection', 'ee.ImageCollection', False, 'import ee\n')]