# Copyright (c) 2021, University of Washington
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the University of Washington nor the names of its
#    contributors may be used to endorse or promote products derived from this
#    software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF WASHINGTON AND CONTRIBUTORS
# “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF WASHINGTON OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import ipywidgets
import ipyleaflet
import numpy as np
from traitlets.utils.bunch import Bunch

class widgets:
    def __init__(self):
        # dropdown menu for setting asset
        self.asset = ipywidgets.Dropdown(
            options=['atlas-local', 'atlas-s3', 'nsidc-s3'],
            value='atlas-s3',
            description='Asset:',
            disabled=False,
        )

        # dropdown menu for setting data release
        self.release = ipywidgets.Dropdown(
            options=['003', '004'],
            value='004',
            description='Release:',
            disabled=False,
        )

        # dropdown menu for setting surface type
        # 0-land, 1-ocean, 2-sea ice, 3-land ice, 4-inland water
        surface_type_options = [
            'Land',
            'Ocean',
            'Sea ice',
            'Land ice',
            'Inland water'
        ]
        self.surface_type = ipywidgets.Dropdown(
            options=surface_type_options,
            value='Land',
            description='Surface Type:',
            disabled=False,
        )

        # slider for setting length of ATL06-SR segment in meters
        self.length = ipywidgets.IntSlider(
            value=40,
            min=5,
            max=200,
            step=5,
            description='Length:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='d'
        )

        # slider for setting step distance for successive segments in meters
        self.step = ipywidgets.IntSlider(
            value=20,
            min=5,
            max=200,
            step=5,
            description='Step:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='d'
        )

        # slider for setting confidence level for PE selection
        # eventually would be good to switch this to a IntRangeSlider with value=[0,4]
        self.confidence = ipywidgets.IntSlider(
            value=4,
            min=0,
            max=4,
            step=1,
            description='Confidence:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='d'
        )

        # selection for land surface classifications
        land_options = [
            'atl08_noise',
            'atl08_ground',
            'atl08_canopy',
            'atl08_top_of_canopy',
            'atl08_unclassified'
        ]
        self.land_class = ipywidgets.SelectMultiple(
            options=land_options,
            description='Land Class:',
            disabled=False
        )

        # slider for setting maximum number of iterations
        # (not including initial least-squares-fit selection)
        self.iteration = ipywidgets.IntSlider(
            value=1,
            min=0,
            max=20,
            step=1,
            description='Iterations:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='d'
        )

        # slider for setting minimum along track spread
        self.spread = ipywidgets.FloatSlider(
            value=20,
            min=1,
            max=100,
            step=0.1,
            description='Spread:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='0.1f'
        )
        # slider for setting minimum photon event (PE) count
        self.count = ipywidgets.IntSlider(
            value=10,
            min=1,
            max=50,
            step=1,
            description='PE Count:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='d'
        )

        # slider for setting minimum height of PE window in meters
        self.window = ipywidgets.FloatSlider(
            value=3,
            min=0.5,
            max=10,
            step=0.1,
            description='Window:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='0.1f'
        )

        # slider for setting maximum robust dispersion in meters
        self.sigma = ipywidgets.FloatSlider(
            value=5,
            min=1,
            max=10,
            step=0.1,
            description='Sigma:',
            disabled=False,
            continuous_update=False,
            orientation='horizontal',
            readout=True,
            readout_format='0.1f'
        )

        # dropdown menu for setting map projection for polygons
        # Global: Web Mercator (EPSG:3857)
        # North: Alaska Polar Stereographic (EPSG:5936)
        # South: Polar Stereographic South (EPSG:3031)
        projection_list = ['Global','North','South']
        self.projection = ipywidgets.Dropdown(
            options=projection_list,
            value='Global',
            description='Projection:',
            disabled=False,
        )

# calculate centroid of polygon
def centroid(x,y):
    npts = len(x)
    area,cx,cy = (0.0,0.0,0.0)
    for i in range(npts-1):
        SA = x[i]*y[i+1] - x[i+1]*y[i]
        area += SA
        cx += (x[i] + x[i+1])*SA
        cy += (y[i] + y[i+1])*SA
    cx /= 3.0*area
    cy /= 3.0*area
    return (cx,cy)

# determine if polygon winding is counter-clockwise
def winding(x,y):
    npts = len(x)
    wind = np.sum([(x[i+1] - x[i])*(y[i+1] + y[i]) for i in range(npts - 1)])
    return wind

# extract coordinates from a sliderule region
def coordinates(polygon):
    npts = len(polygon)
    x = np.zeros((npts))
    y = np.zeros((npts))
    for i,p in enumerate(polygon):
        x[i] = p['lon']
        y[i] = p['lat']
    return (x,y)

# define projections for ipyleaflet tiles
projections = Bunch(
    # Alaska Polar Stereographic (WGS84)
    EPSG5936=dict(
        name='EPSG5936',
        custom=True,
        proj4def="""+proj=stere +lat_0=90 +lat_ts=90 +lon_0=-150 +k=0.994
            +x_0=2000000 +y_0=2000000 +datum=WGS84 +units=m +no_defs""",
        origin=[-2.8567784109255e+07, 3.2567784109255e+07],
        resolutions=[
            238810.813354,
            119405.406677,
            59702.7033384999,
            29851.3516692501,
            14925.675834625,
            7462.83791731252,
            3731.41895865639,
            1865.70947932806,
            932.854739664032,
            466.427369832148,
            233.213684916074,
            116.60684245803701,
            58.30342122888621,
            29.151710614575396,
            14.5758553072877,
            7.28792765351156,
            3.64396382688807,
            1.82198191331174,
            0.910990956788164,
            0.45549547826179,
            0.227747739130895,
            0.113873869697739,
            0.05693693484887,
            0.028468467424435
        ],
        bounds=[
            [-2623285.8808999992907047,-2623285.8808999992907047],
            [6623285.8803000003099442,6623285.8803000003099442]
        ]
    )
    ,
    # Polar Stereographic South (WGS84)
    EPSG3031=dict(
        name='EPSG3031',
        custom=True,
        proj4def="""+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1
            +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs""",
        origin=[-3.06361E7, 3.0636099999999993E7],
        resolutions=[
            67733.46880027094,
            33866.73440013547,
            16933.367200067736,
            8466.683600033868,
            4233.341800016934,
            2116.670900008467,
            1058.3354500042335,
            529.1677250021168,
            264.5838625010584,
        ],
        bounds=[
            [-4524583.19363305,-4524449.487765655],
            [4524449.4877656475,4524583.193633042]
        ]
    )
)

# attributions for the different basemaps
glims_attribution = """
Imagery reproduced from GLIMS and NSIDC (2005, updated 2018):
Global Land Ice Measurements from Space glacier database. (doi:10.7265/N5V98602)
"""
esri_attribution = """
Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC,
USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan,
METI, Esri China (Hong Kong), and the GIS User Community
"""
noaa_attribution = """
Imagery provided by NOAA National Centers for Environmental Information (NCEI);
International Bathymetric Chart of the Southern Ocean (IBCSO);
General Bathymetric Chart of the Oceans (GEBCO).
"""

# define background ipyleaflet tiles
basemaps = Bunch(
    Esri = Bunch(
        ArcticOceanBase=dict(name='Esri.ArcticOceanBase',crs=projections.EPSG5936,attribution=esri_attribution,
            url='http://server.arcgisonline.com/ArcGIS/rest/services/Polar/Arctic_Ocean_Base/MapServer/tile/{z}/{y}/{x}',
        ),
        ArcticOceanReference=dict(name='Esri.ArcticOceanReference',crs=projections.EPSG5936,attribution=esri_attribution,
            url='http://server.arcgisonline.com/ArcGIS/rest/services/Polar/Arctic_Ocean_Reference/MapServer/tile/{z}/{y}/{x}',
        ),
        AntarcticBasemap=dict(name='Esri.AntarcticBasemap',crs=projections.EPSG3031,attribution=noaa_attribution,
            url='https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/Antarctic_Basemap/MapServer/tile/{z}/{y}/{x}',
        )
    ),
    GLIMS = Bunch(
        glaciers = ipyleaflet.WMSLayer(
            attribution=glims_attribution,
            layers='GLIMS_GLACIERS',
            format='image/png',
            url='https://www.glims.org/mapservice'
        )
    )
)

# draw ipyleaflet map
class leaflet:
    def __init__(self, projection, **kwargs):
        # set default keyword arguments
        kwargs.setdefault('zoom',False)
        kwargs.setdefault('scale',True)
        kwargs.setdefault('cursor',True)
        kwargs.setdefault('color','green')
        # create basemap in projection
        if (projection == 'Global'):
            self.map = ipyleaflet.Map(center=(39,-108), zoom=9, max_zoom=15,
                basemap=ipyleaflet.basemaps.Esri.WorldTopoMap)
            self.map.add_layer(basemaps.GLIMS.glaciers)
        elif (projection == 'North'):
            self.map = ipyleaflet.Map(center=(90,0), zoom=5, max_zoom=24,
                basemap=basemaps.Esri.ArcticOceanBase,
                crs=projections.EPSG5936)
            self.map.add_layer(basemaps.Esri.ArcticOceanReference)
        elif (projection == 'South'):
            self.map = ipyleaflet.Map(center=(-90,0), zoom=2, max_zoom=9,
                basemap=basemaps.Esri.AntarcticBasemap,
                crs=projections.EPSG3031)
        # add control for zoom
        if kwargs['zoom']:
            zoom_slider = ipywidgets.IntSlider(description='Zoom level:',
                min=self.map.min_zoom, max=self.map.max_zoom, value=self.map.zoom)
            ipywidgets.jslink((zoom_slider, 'value'), (self.map, 'zoom'))
            zoom_control = ipyleaflet.WidgetControl(widget=zoom_slider,
                position='topright')
            self.map.add_control(zoom_control)
        # add scale bar
        if kwargs['scale']:
            scale_control = ipyleaflet.ScaleControl(position='topright')
            self.map.add_control(scale_control)
        # add label for cursor position
        if kwargs['cursor']:
            self.cursor = ipywidgets.Label()
            label_control = ipyleaflet.WidgetControl(widget=self.cursor,
                position='bottomright')
            self.map.add_control(label_control)
            # keep track of cursor position
            self.map.on_interaction(self.handle_interaction)
        # add control for drawing polygons or bounding boxes
        draw_control = ipyleaflet.DrawControl(polyline={},circlemarker={})
        shapeOptions = {'color':kwargs['color'],'fill_color':kwargs['color']}
        draw_control.rectangle = dict(shapeOptions=shapeOptions)
        draw_control.polygon = dict(shapeOptions=shapeOptions)
        # create regions
        self.regions = []
        draw_control.on_draw(self.handle_draw)
        self.map.add_control(draw_control)

    # handle cursor movements for label
    def handle_interaction(self, **kwargs):
        if (kwargs.get('type') == 'mousemove'):
            self.cursor.value = u"""Latitude: {d[0]:8.4f}\u00B0,
                Longitude: {d[1]:8.4f}\u00B0""".format(d=kwargs.get('coordinates'))

    # keep track of rectangles and polygons drawn on map
    def handle_draw(self, obj, action, geo_json):
        lon,lat = np.transpose(geo_json['geometry']['coordinates'])
        cx,cy = centroid(lon,lat)
        wind = winding(lon,lat)
        # set winding to counter-clockwise
        if (wind > 0):
            lon = lon[::-1]
            lat = lat[::-1]
        # create sliderule region from list
        region = [{'lon':ln,'lat':lt} for ln,lt in np.c_[lon,lat]]
        # append coordinates to list
        if (action == 'created'):
            self.regions.append(region)
        elif (action == 'deleted'):
            self.regions.remove(region)
        return self
