# -*- coding: utf-8 -*-
""" Core """

import os
import time
import json
import copy
from dash import html
import dash_leaflet as dl
import numpy as np
from dateutil.relativedelta import relativedelta
from netCDF4 import Dataset as nc_file
from datetime import datetime as dt
import logging

DIR_PATH = os.path.dirname(os.path.realpath(__file__))
COLORBARS = json.load(open(os.path.join(DIR_PATH, 'conf/colorbars.json')))
INFO_STYLE = json.load(open(os.path.join(DIR_PATH, 'conf/info_style.json')))
MAP_LAYERS = json.load(open(os.path.join(DIR_PATH, 'conf_ines_core/map_layers.json')))
DEBUG = True

class MapHandler:
    """ Class than handles the creation of maps"""

    def __init__(self):
        """ Initialize MapHandler class """

        self.info_style = INFO_STYLE
        # TODO: Add common variables from all figure handlers here

        return None

    def get_title(self, **kwargs):
        """ Return title from base title and elements

        Returns
        -------
        title : str
            Map title
        """

        logging.debug('================= Retrieving figure title...')

        # Get selected and current datetime details
        rhour = self.rdatetime.strftime("%H")
        rday = self.rdatetime.strftime("%d")
        rmonth = self.rdatetime.strftime("%b")
        ryear = self.rdatetime.strftime("%Y")
        if hasattr(self, 'cdatetime'):
            shour = self.cdatetime.strftime("%H")
            sday = self.cdatetime.strftime("%d")
            smonth = self.cdatetime.strftime("%b")
            syear = self.cdatetime.strftime("%Y")

        # Get title elements that we want to show from base title
        # e.g. a dict with shour, sday, smonth, syear as keys
        title_elements = {}
        base_title_split = self.base_title.split("%(")
        for element in base_title_split[1:]:
            element_split = element.split(")")
            if len(element_split) > 1:
                title_element = element_split[0]
                if hasattr(self, title_element):
                    title_elements.update({title_element: getattr(self, title_element)})
                else:
                    title_elements.update({title_element: locals()[title_element]})

        # Create title from base and elements
        title = r'{}'.format(self.base_title % title_elements)

        return title
      
    def retrieve_cdatetime(self, tstep=0):
        """ Retrieve data from NetCDF file

        Parameters
        ----------
        tstep : int, optional
            Current timestep of running plot, by default 0

        Returns
        -------
        cdatetime : datetime.datetime
            Current datetime
        """

        tstep = int(tstep)
        time_attr = int(self.timesteps[tstep])

        # Calculate current datetime given the selected initial datetime and current timestep
        if self.what == 'days':
            cdatetime = self.rdatetime + relativedelta(days=time_attr)
        elif self.what == 'hours':
            cdatetime = self.rdatetime + relativedelta(hours=time_attr)
        elif self.what == 'minutes':
            cdatetime = self.rdatetime + relativedelta(minutes=time_attr)
        elif self.what == 'seconds':
            cdatetime = self.rdatetime + relativedelta(seconds=time_attr)

        return cdatetime

    def create_legend(self, colormap):
        """ Create map legend given the colors for each label

        Parameters
        ----------
        colormap : dict
            Dictionary with labels and keys and colors as values

        Returns
        -------
        legend : html.Div
            Map legend
        """

        logging.debug('================= Retrieving legend...')

        # Create categorical legend given the labels and its colors
        legend_items = []
        labels = colormap.keys()
        colors = colormap.values()

        for i, (label, color) in enumerate(zip(labels, colors)):
            legend_item = html.Div([html.Span(
                                        "",
                                        className="{}-legend-point".format(self.name),
                                        style={
                                            'backgroundColor': color,
                                            }
                                        ),
                                    html.Span(
                                        label,
                                        className="{}-legend-label".format(self.name),
                                        )
                                    ],
                                    style={ 'display': 'block' })
            legend_items.append(legend_item)

        legend = html.Div(legend_items, className="{}-legend".format(self.name))
        
        return legend

    def retrieve_info(self, name):
        """ Get information box with title 

        Parameters
        ----------
        name : str
            Map name

        Returns
        -------
        info : html.Div
            Map title
        """

        logging.debug('================= Retrieving figure title box...')

        # Box width to match colorbar width
        if hasattr(self, 'colorbar'):
            if self.colorbar is not None:
                self.info_style['width'] = str(self.colorbar.width) + "px"

        # Create figure title box
        info = html.Div(
            children=self.fig_title,
            id="{}-info".format(name),
            className="info",
            style=self.info_style
        )

        return info

    def retrieve_colorbar(self, aspect=(1,1)):
        """ Create map colorbar

        Parameters
        ----------
        aspect : tuple, optional
            Map aspect, by default (1,1)

        Returns
        -------
        colorbar : dash_leaflet.Colorbar
            Map colorbar
        """

        logging.debug('================= Retrieving colorbar...')
        
        # Create colorbar
        colorbar = dl.Colorbar(**self.colorbar_info)

        # Resize and place on top left corner
        colorbar.width = 320 - 15 * aspect[0]
        colorbar.style = {'overflow':'hidden', 'white-space':'nowrap'}
        if aspect == (3,4):
            colorbar.width = 320 - 38 * aspect[0]
        colorbar.height = 8

        return colorbar

    def get_time_details(self):
        """ Get time details

        Returns
        -------
        timesteps : list
            Steps corresponding to each datetime, e.g. [0, 3, 6, 9, 12]
        what : str
            Detects if time units are in hours, months, etc.
        rdatetime : datetime.datetime
            Selected datetime
        """

        input_file = nc_file(self.filepath)
        time_obj = input_file.variables['time']
        timesteps = time_obj[:]
        tim_units = time_obj.units.split()
        if len(tim_units) == 3:
            what, _, rdate = tim_units
            rtime = "00:00"
        elif len(tim_units) >= 4:
            what, _, rdate, rtime = tim_units[:4]
        if len(rtime) > 5:
            rtime = rtime[:5]
        rdatetime = dt.strptime("{} {}".format(rdate, rtime), "%Y-%m-%d %H:%M")

        return timesteps, what, rdatetime

    def hour_to_tstep(self, hour):
        """ Convert hour to relative tstep

        Parameters
        ----------
        hour : int
            Hour

        Returns
        -------
        tstep : int
            Timestep
        """

        cdatetime = self.rdatetime.date() + relativedelta(hours=hour)
        for tstep in range(len(self.timesteps)):
            if self.retrieve_cdatetime(tstep) == cdatetime:
                return tstep
        return 0

    def get_center(self, center=None):
        """ Returns center of map

        Parameters
        ----------
        center : list, optional
            Map center, by default None

        Returns
        -------
        center : list
            Map center
        """

        if center is None:
            if hasattr(self, 'ylat'):
                center =  [(self.ylat.max()-self.ylat.min())/2 + self.ylat.min() + (self.ylat.max()-self.ylat.min())/6,
                           (self.xlon.max()-self.xlon.min())/2 + self.xlon.min(),]
            else:
                center = [30, 15]

        return center

    def retrieve_fig(self, aspect=(1,1), center=None, selected_tiles='carto-positron', 
                     zoom=None, tag='empty', index=None, layers=None):
        """ Get figure with all the elements (legend, layers, colorbar, etc.)

        Parameters
        ----------
        aspect : tuple, optional
            Map aspect, by default (1,1)
        center : list, optional
            Map center, by default None
        selected_tiles : str, optional
            Map tiles, by default 'carto-positron'
        zoom : float, optional
            Map zoom, by default None
        tag : str, optional
            Map tag, by default 'empty'
        index : str, optional
            Map index, by default None
        layers : list, optional
            Map layers, by default None

        Returns
        -------
        fig : dash_leaflet.Map
            Figure
        """

        self.st_time = time.time()

        logging.debug('================= Retrieving figure...')
        
        # Get zoom and center if it hasn't been specified
        if center is None:
            center = self.get_center(center)
        if zoom is None:
            zoom = 3.5 -(aspect[0]-aspect[0]*0.4)
        logging.debug("ASPECT %s", aspect)
        logging.debug("ZOOM %s", zoom)
        logging.debug("CENTER %s", center)
        
        # Get index and template names
        index = str(index)
        tag_template_tile = "{}-tile-layer"
        tag_template_map = "{}-map"

        if not isinstance(layers, list):
            layers = [layers]

        logging.debug("TAG %s", tag)
        logging.debug("INDEX %s", index)

        fig = dl.Map(children=[
            dl.TileLayer(
                id=dict(
                    tag=tag_template_tile.format(tag),
                    index=index
                ),
                url=MAP_LAYERS[selected_tiles]['url'],
                attribution=MAP_LAYERS[selected_tiles]['attribution']
            ),
            dl.FullscreenControl(
                position='topright',
            )] + layers,
            zoomSnap=0.1,
            zoom=zoom,
            wheelPxPerZoomLevel=120,
            wheelDebounceTime=80,
            center=center,
            id=dict(
                tag=tag_template_map.format(tag),
                index=index
                ),
            inertia=True,
            preferCanvas=True,
            animate=False,
            minZoom=2,
            #className="graph-with-slider",
        )

        logging.debug("*** FIGURE EXECUTION TIME: {} ***".format(str(time.time() - self.st_time)))
        
        return fig

class PointsFigureHandler(MapHandler):
    """ Class that handles the generation of maps with points """

    def __init__(self):
        """ Initialize PointsFigureHandler class """

        super(PointsFigureHandler, self).__init__()

        if self.name in COLORBARS:
            self.colorbar_info = COLORBARS[self.name]
        
        # TODO: Add common variables from:
        # VisFigureHandler, EvaluationGroundFigureHandler and EvaluationStatisticsFigureHandler

        return None

    def retrieve_geojson(self, geojson_data, namespace):
        """ Get points layer as GeoJSON

        Parameters
        ----------
        geojson_data : dict
            Data to generate GeoJSON
        namespace : dash_extensions.javascript.Namespace
            Namespace

        Returns
        -------
        geojson : dash_leaflet.GeoJSON
            GeoJSON layer
        """

        logging.debug('================= Retrieving geojson...')

        # Get points properties
        hideout = {"circleOptions": self.circle_options}
        if 'fillColor' not in hideout["circleOptions"].keys():
            hideout.update({'colorProp': "value",
                            'colorscale': self.colors})

        # Get geojson
        point_to_layer = namespace("pointToLayer")
        geojson = dl.GeoJSON(data=geojson_data,
                             options=dict(pointToLayer=point_to_layer,),
                             hideout=hideout)

        return geojson

    def get_tooltip(self, df, var_list):
        """ Get tooltip on hover

        Parameters
        ----------
        df : dict
            Data to generate tooltip
        var_list : list
            Variables to be shown in the tooltip if available in df

        Returns
        -------
        data_dict : dict
            Data dictionary with tooltips
        """

        logging.debug('================= Retrieving tooltip...')

        data_dict = df.to_dict('records')
        for item in data_dict:
            logging.debug("------------- %s -----------------", item)
            tooltip = """<span class='popup-map-body'>"""
            tooltip_keys = [key for key in var_list if key in item.keys()]
            for key in tooltip_keys:
                # Check for nans, empty and false numbers
                if (item[key] in [False, '']) or (item[key] != item[key]):
                    continue
                if key == 'station':
                    tooltip += """<b>{0}</b><br>""".format(item[key])
                else:
                    tooltip += """{0}: <b>{1}</b><br>""".format(key.capitalize(), item[key])
            tooltip += """</span>"""
            item["tooltip"] = tooltip

        return data_dict


class ContourFigureHandler(MapHandler):
    """ Class that handles the generation of maps with countours """

    def __init__(self):
        """ Initialize ContourFigureHandler class """

        super(ContourFigureHandler, self).__init__()

        # TODO: Add common variables from old FigureHandler and ProbFigureHandler

        if self.name in COLORBARS:
            self.colorbar_info = COLORBARS[self.name]
        else:
            self.colorbar_info = COLORBARS['model']

        return None

    def retrieve_geojson(self, geojson_path, namespace):
        """ Get contour layer as GeoJSON

        Parameters
        ----------
        geojson_path : str
            Directory with data to generate GeoJSON
        namespace : dash_extensions.javascript.Namespace
            Namespace

        Returns
        -------
        geojson : dash_leaflet.GeoJSON
            GeoJSON layer
        """

        from dash_server import app

        logging.debug('================= Retrieving geojson...')
        
        # Get contour properties
        hideout = dict(colorscale=self.colorscale, bounds=self.bounds, style=self.style, 
                       colorProp="value")

        # Get geojson
        geojson_url = app.get_asset_url(geojson_path)
        style_handle = namespace("styleHandle")
        geojson = dl.GeoJSON(url=geojson_url,
                             options=dict(style=style_handle),
                             hideout=hideout)

        logging.info("GEOJSON URL %s", geojson_url)

        return geojson


class ShapefileFigureHandler(MapHandler):
    """ Class that handles the generation of maps with shapefiles """

    def __init__(self):
        """ Initialize ShapefileFigureHandler class """

        super(ShapefileFigureHandler, self).__init__()

        # TODO: Add variables from ForecastWasFigureHandler that could be reused in other figures

        return None

    def retrieve_geojson(self, geojson_path, namespace, hideout, hover_style):
        """ Get shapefile layer as GeoJSON

        Parameters
        ----------
        geojson_path : str
            Directory with data to generate GeoJSON
        namespace : dash_extensions.javascript.Namespace
            Namespace
        hideout : dict
            Shapefile style
        hover_style : dict
            Shapefile hover style

        Returns
        -------
        geojson : dash_leaflet.GeoJSON
            GeoJSON layer
        """

        from dash_server import app

        logging.debug('================= Retrieving geojson...')
        
        # Get geojson
        geojson_url = app.get_asset_url(geojson_path)
        style_handle = namespace("styleHandle")
        geojson = dl.GeoJSON(url=geojson_url,
                             options=dict(style=style_handle),
                             hoverStyle=hover_style,
                             hideout=hideout)

        logging.info("GEOJSON URL %s", geojson_url)

        return geojson
