# -*- coding: utf-8 -*-
""" Data Handler """

import plotly.graph_objs as go
from dash import html
import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash_extensions.javascript import arrow_function
from dash_extensions.javascript import Namespace
import matplotlib
from matplotlib import cm
from matplotlib.colors import ListedColormap
import numpy as np
from netCDF4 import Dataset as nc_file
import pandas as pd
import json
import orjson
from collections import OrderedDict
import os
import time
import calendar
from datetime import timedelta
from datetime import datetime
from dateutil.relativedelta import relativedelta

from utils import concat_dataframes
from utils import retrieve_timeseries
from utils import retrieve_single_point
from utils import get_colorscale

from ines_core_data_handler import MapHandler
from ines_core_data_handler import PointsFigureHandler
from ines_core_data_handler import ContourFigureHandler
from ines_core_data_handler import ShapefileFigureHandler

from pathlib import Path
from flask_caching import Cache
import uuid
import socket

DIR_PATH = os.path.dirname(os.path.realpath(__file__))
DEBUG = False

# Set up cache
CACHE = json.load(open(os.path.join(DIR_PATH, 'conf/cache.json')))
cache_config = CACHE['config']
cache_config.update({'CACHE_DIR': "/dev/shm/{}".format(str(uuid.uuid1()))})
Path(cache_config['CACHE_DIR']).mkdir(parents=True, exist_ok=True)
cache = Cache(config=cache_config)
cache_timeout = CACHE['timeout']

# Set up base url
HOSTNAME = socket.gethostbyname_ex(socket.gethostname())[0]
HOSTNAMES = json.load(open(os.path.join(DIR_PATH, 'conf/hostnames.json')))

# Get configurations
INIT = json.load(open(os.path.join(DIR_PATH, 'conf/init.json')))
DASH_STYLE = json.load(open(os.path.join(DIR_PATH, 'conf/dash_style.json')))
VARS = json.load(open(os.path.join(DIR_PATH, 'conf/vars.json')))
MODELS = json.load(open(os.path.join(DIR_PATH, 'conf/models.json')))
OBS = json.load(open(os.path.join(DIR_PATH, 'conf/obs.json')))
WAS = json.load(open(os.path.join(DIR_PATH, 'conf/was.json')))
PROB = json.load(open(os.path.join(DIR_PATH, 'conf/prob.json')))
VIS = json.load(open(os.path.join(DIR_PATH, 'conf/vis.json')))
SCORES = json.load(open(os.path.join(DIR_PATH, 'conf/scores.json')))
DATES = json.load(open(os.path.join(DIR_PATH, 'conf/dates.json')))
ALIASES = json.load(open(os.path.join(DIR_PATH, 'conf/aliases.json')))
STATS_CONF = json.load(open(os.path.join(DIR_PATH, 'conf/stats.json')))
STYLES = json.load(open(os.path.join(DIR_PATH, 'conf/map_layers.json')))
ALL_COLORS = json.load(open(os.path.join(DIR_PATH, 'conf/colors.json')))
MODEBARS = json.load(open(os.path.join(DIR_PATH, 'conf/modebars.json')))
DISCLAIMERS = json.load(open(os.path.join(DIR_PATH, 'conf/disclaimers.json')))
SATELLITE_IMAGE_SRC = json.load(open(os.path.join(DIR_PATH, 'conf/satellite_image_src.json')))

# Set up initial conditions
FREQ = INIT['frequency']
FORECAST_MAX = INIT['forecast_max']
FORECAST_FINAL_DAY = INIT['forecast_final_day']
DEFAULT_VAR = INIT['default_variable']
DEFAULT_MODEL = INIT['default_model']
START_DATE = DATES['start_date']
DELAY = DATES['delay']['delayed']
DELAY_DATE = DATES['delay']['start_date']
END_DATE = DATES['end_date']
END_DATE = END_DATE or (DELAY and (datetime.now() -
    timedelta(days=1)).strftime("%Y%m%d") or datetime.now().strftime("%Y%m%d"))

# Set up dashboard basic properties
GRAPH_HEIGHT = DASH_STYLE['graph_height']
OPACITY = DASH_STYLE['opacity']

# Set up color scheme
COLORS = ALL_COLORS['std']
COLORS_NORGB = ALL_COLORS['std_norgb']
COLORS_PROB = ALL_COLORS['prob']
COLORMAP = ListedColormap(COLORS_NORGB)
COLORMAP_PROB = ListedColormap(COLORS_PROB)

# Set up route defaults
ROUTE_DEFAULTS = json.load(open(os.path.join(DIR_PATH, 'conf/route.json')))
ROUTE_DEFAULTS.update({'date': [END_DATE]})

# Set up statistics
STATS = OrderedDict([(key, STATS_CONF[key]['name']) for key in STATS_CONF])
STATS.update({'totn': 'TOTAL CASES'})

# Set up timeseries configuration
MODEBAR_CONFIG = MODEBARS['config']
MODEBAR_CONFIG_TS = MODEBARS['config_ts']
MODEBAR_LAYOUT = MODEBARS['layout']
MODEBAR_LAYOUT_TS = MODEBARS['layout_ts']

# Set up disclaimers
DISCLAIMER_MODELS = [html.Span(html.P(DISCLAIMERS['models']), id='models-disclaimer')]
DISCLAIMER_OBS = html.P(DISCLAIMERS['observations'])

# Get templates
GEOJSON_TEMPLATE = "{}/geojson/{}/{:02d}_{}_{}.geojson"
NETCDF_TEMPLATE = "{}/netcdf/{}{}.nc"

# Get path names
if HOSTNAME in HOSTNAMES:
    PATHNAME = HOSTNAMES[HOSTNAME]
else:
    PATHNAME = HOSTNAMES['default']


class ForecastModelsFigureHandler(ContourFigureHandler):

    def __init__(self, var=None, model=None, tstep=0, hour=None, selected_date=None):

        if isinstance(model, list):
            self.model = model[0]
        else:
            self.model = model
        self.name = self.model
        super(ForecastModelsFigureHandler, self).__init__()

        self.var = var
        self.selected_date = selected_date

        self.bounds = np.array(VARS[self.var.upper()]['bounds']).astype('float32')
        
        if self.selected_date:
            # Get NetCDF file path
            self.filepath = NETCDF_TEMPLATE.format(MODELS[self.model]['path'], selected_date, 
                                                MODELS[self.model]['template'])
            
            # Read NetCDF file and retrieve timesteps, what (hours, months, etc.) and rdatetime
            if os.path.exists(self.filepath):
                self.timesteps, self.what, self.rdatetime = self.get_time_details()
        
    def generate_var_tstep_trace(self, tstep=0):
        """ Generate trace to be added to data, per variable and timestep """
        from dash_server import app

        data_path = os.path.basename(MODELS[self.model]['path'])
        if DEBUG: print(data_path)
        geojson_url = app.get_asset_url(os.path.join('geojsons',
            GEOJSON_TEMPLATE.format(data_path, self.selected_date, tstep, self.selected_date,
                self.var)))
        
        if DEBUG: print("MODEL", self.model, "GEOJSON_URL", geojson_url)

        style = dict(weight=0, opacity=0, color='white', dashArray='', fillOpacity=OPACITY)

        # Create colorbar
        ctg = ["{:d}".format(int(cls)) if cls.as_integer_ratio()[1] == 1 else "{:.1f}".format(cls) 
               for i, cls in enumerate(self.bounds[1:-1])]
        indices = list(range(len(ctg) + 2))
        self.colorbar_info.update({'min': 0,
                                   'max': len(ctg)+1, 
                                   'classes': indices,
                                   'colorscale': COLORS,
                                   'tickValues': indices[1:-1],
                                   'tickText': ctg})
        self.colorbar = self.retrieve_colorbar()

        if DEBUG: print("BOUNDS", self.bounds)
        if DEBUG: print("CTG", ctg)

        # Geojson rendering logic, must be JavaScript as it is executed in clientside.
        namespace = Namespace("forecastTab", "forecastMaps")
        hideout = dict(colorscale=COLORS, bounds=self.bounds, style=style, colorProp="value")
       
        self.geojson = self.retrieve_geojson(geojson_url, namespace, hideout)
        
    def get_figure_layers(self, tstep=0, hour=None, aspect=(1,1)):
        """ run plot """

        if DEBUG: print('Update layout ...')
        
        if os.path.exists(self.filepath) and self.var:
            
            # Get timestep
            if hour is not None:
                tstep = int(self.hour_to_step(hour))
            else:
                tstep = int(tstep)

            # Get trace
            self.generate_var_tstep_trace(tstep)

            # Get figure title
            self.fig_title = html.P(html.B(
                [
                    item for sublist in self.get_title(varname=self.var, tstep=tstep).split('<br>') 
                    for item in [sublist, html.Br()]
                ][:-1]
            ))

        else:
            self.geojson = None
            self.colorbar = None

            # Get figure title
            if not self.var:
                self.fig_title = ""
            elif self.var and not os.path.exists(self.filepath):
                if self.model in MODELS:
                    title = "{} - DATA NOT AVAILABLE".format(MODELS[self.model]['name'])
                else:
                    title = "DATA NOT AVAILABLE"
                self.fig_title = html.P(html.B(title))

        if self.model is not None:
            # Tilt colorbar values so they won't overlap for SCONC_DUST
            if self.var == "SCONC_DUST": 
                self.colorbar.className = 'sconc_info'
            if aspect[0] > 2:
                self.info_style['fontSize'] = "{}px".format(int(self.info_style['fontSize'][:-2])-aspect[0]+ 0.3)
        
        # Get title information element
        self.info = self.retrieve_info(name=self.model)

        return [self.geojson, self.colorbar, self.info]

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

        varname = kwargs['varname']
        tstep = kwargs['tstep']

        self.base_title = " ".join([MODELS[self.model]['name'], VARS[varname]['title']])
        self.cdatetime = self.retrieve_cdatetime(tstep)
        self.step = "{:02d}".format(tstep*FREQ)
        title = super(ForecastModelsFigureHandler, self).get_title()
        
        return title


class ForecastModelsTimeSeriesHandler:
    """ Class to handle forecast time series """

    def __init__(self, model, date, variable):
        if isinstance(model, str):
            model = [model]
        self.model = model
        self.variable = variable
        self.fpaths = []
        try:
            self.month = datetime.strptime(date, "%Y%m%d").strftime("%Y%m")
            self.currdate = datetime.strptime(date, "%Y%m%d").strftime("%Y%m%d")
        except:
            self.month = datetime.strptime(date, "%Y-%m-%d").strftime("%Y%m")
            self.currdate = datetime.strptime(date, "%Y-%m-%d").strftime("%Y%m%d")

    def retrieve_single_point(self, tstep, lat, lon, model=None):

        if not model:
            model = self.model[0]

        if DEBUG: print("----------", model)

        method = 'netcdf'
        if (MODELS[model]['start'] == 12 and not DELAY and DELAY_DATE and (datetime.strptime(self.currdate, "%Y%m%d") >= datetime.strptime(DELAY_DATE, "%Y%m%d"))) or \
                (MODELS[model]['start'] == 12 and not DELAY and not DELAY_DATE):
            mod_date = (datetime.strptime(self.currdate, "%Y%m%d") -
                    timedelta(days=1)).strftime("%Y%m%d")
        else:
            mod_date = self.currdate
        path_template = '{}{}.nc'.format(mod_date,
                                         MODELS[model]['template'],
                                         self.variable)

        fpath = os.path.join(MODELS[model]['path'], method, path_template)

        return retrieve_single_point(fpath, tstep, lat, lon,
                                     self.variable)

    def retrieve_timeseries(self, lat, lon, model=None, method='netcdf', forecast=False):

        if not model:
            model = self.model

        # if DEBUG: print("----------", model)

        obs_eval = model[0] not in MODELS and model[0] in OBS
        if obs_eval:
            all_models = [model[0]] + list(MODELS.keys())
        else:
            all_models = list(MODELS.keys())

        for mod in all_models:
            if obs_eval:
                filedir = OBS[model[0]]['path']
                path_tpl = '{}-{}-{}_interp.ft'  # 202010-median-OD550_DUST_interp.ft
            else:   # if mod in MODELS:
                filedir = MODELS[mod]['path']
                path_tpl = '{}-{}-{}.ft'  # 202010-median-OD550_DUST_interp.ft

            if method == 'feather':
                path_template = path_tpl.format(self.month, mod, self.variable)
            elif method == 'netcdf':
                path_template = '{}*{}.nc'.format(self.month, MODELS[mod]['template'], self.variable)

            if forecast:
                method = 'netcdf'
                if (MODELS[mod]['start'] == 12 and not DELAY and DELAY_DATE and (datetime.strptime(self.currdate, "%Y%m%d") >= datetime.strptime(DELAY_DATE, "%Y%m%d"))) or \
                        (MODELS[mod]['start'] == 12 and not DELAY and not DELAY_DATE):
                    mod_date = (datetime.strptime(self.currdate, "%Y%m%d") -
                            timedelta(days=1)).strftime("%Y%m%d")
                else:
                    mod_date = self.currdate
                path_template = '{}{}.nc'.format(mod_date, MODELS[mod]['template'], self.variable)

            fpath = os.path.join(filedir,
                                 method,
                                 path_template)
            self.fpaths.append(fpath)

        title = "{} @ lat = {} and lon = {}".format(
            VARS[self.variable]['name'], round(lat, 2), round(lon, 2)
        )

        mul = VARS[self.variable]['mul']

        fig = go.Figure()

        for mod, fpath in zip(all_models, self.fpaths):
            # print(mod, fpath)
            if mod not in MODELS and mod in OBS:
                variable = OBS[mod]['obs_var']
            else:
                variable = self.variable

            if not os.path.exists(fpath):
                if DEBUG: print("NOT retrieving", fpath, "File doesn't exist.")
                continue

            if DEBUG: print('Retrieving *** FPATH ***', fpath)
            try:
                ts_lat, ts_lon, ts_index, ts_values = retrieve_timeseries(
                        fpath, lat, lon, variable, method=method, forecast=forecast)
            except Exception as e:
                if DEBUG: print("NOT retrieving", fpath, "ERROR:", str(e))
                continue

            if forecast is True or mod in ('cams', 'ema-regcm4'):
                ts = pd.Series(index=ts_index, data=ts_values)
            else:
                date_index = pd.date_range('{}01'.format(self.month),
                        '{}01'.format((datetime.strptime(self.month, '%Y%m') +
                            relativedelta(days=31)).strftime('%Y%m')), freq=f'{FREQ}H')
                ts = pd.Series(index=ts_index, data=ts_values).reindex(date_index)

            if isinstance(ts_lat, np.ndarray):
                ts_lat = float(ts_lat)
                ts_lon = float(ts_lon)
            if isinstance(ts.values, np.ndarray):
                ts_values = (ts.values*mul).round(2)
            else:
                ts_values = round((ts.values*mul), 2)

            if obs_eval and mod == model[0]:
                sc_mode = 'markers'
                marker = {'size': 12, 'symbol': "triangle-up-dot", 'color': '#f0b450'}
                line = {}
                visible = True
                name = "{}".format(mod.upper())
            elif obs_eval:
                sc_mode = 'lines'
                marker = {}
                line = { 'color': MODELS[mod]['color'] }
                if mod in model:
                    visible = True
                else:
                    visible = 'legendonly'
                name = "{}".format(MODELS[mod]['name'])
            else:
                sc_mode = 'lines'
                marker = {}
                line = { 'color': MODELS[mod]['color'] }
                if mod in model:
                    visible = True
                else:
                    visible = 'legendonly'
                name = "{} ({}, {})".format(
                        MODELS[mod]['name'], round(ts_lat, 2), round(ts_lon, 2))

            if mod == 'median':
                line['dash'] = 'dash'
            else:
                line['dash'] = 'solid'

            fig.add_trace(dict(
                    type='scatter',
                    name=name,
                    x=ts.index,
                    y=ts_values,
                    mode=sc_mode,
                    marker=marker,
                    line=line,
                    visible=visible,
                )
            )

        fig.update_layout(
            title=dict(text=title, x=0.45, y=.99),
            yaxis=dict(exponentformat="none"),
            # uirevision=True,
            autosize=True,
            showlegend=True,
            plot_bgcolor='#F9F9F9',
            font_size=12,
            # hovermode="closest",        # highlight closest point on hover
            hovermode="x",        # highlight closest point on hover
            margin={"r": 10, "t": 35, "l": 10, "b": 10},
        )

        return fig


class ForecastProbFigureHandler(ContourFigureHandler):
    """ Class to manage the figure creation """

    def __init__(self, var=None, prob=None, selected_date=None):
        """ Initialization with variable, prob and date """

        self.name = 'prob'
        super(ForecastProbFigureHandler, self).__init__()

        if var is None:
            self.var = DEFAULT_VAR
        else:
            self.var = var

        probs = PROB[self.var]['prob_thresh']
        if prob is None:
            prob = probs[0]

        self.prob = prob
        self.selected_date = selected_date

        self.bounds = np.arange(0, 110, 10)

        geojson_path = PROB[self.var]['geojson_path']
        geojson_file = PROB[self.var]['geojson_template']
        netcdf_path = PROB[self.var]['netcdf_path']
        netcdf_file = PROB[self.var]['netcdf_template']

        # Get GeoJSON file path
        self.geojsonpath = os.path.join(geojson_path, geojson_file).format(prob=prob, 
                                                                           date=self.selected_date, 
                                                                           var=self.var)
        # Get NetCDF file path
        self.filepath = os.path.join(netcdf_path, netcdf_file).format(prob=prob, 
                                                                      date=self.selected_date, 
                                                                      var=self.var)

        # Read NetCDF file and retrieve timesteps, what (hours, months, etc.) and rdatetime
        if os.path.exists(self.filepath):
            self.timesteps, self.what, self.rdatetime = self.get_time_details()

    def generate_var_tstep_trace(self, tstep=0):
        """ Generate trace to be added to data, per variable and timestep """
        from dash_server import app

        if DEBUG: print("##############", tstep)

        geojson_file = self.geojsonpath.format(step=tstep)
        geojson_url = app.get_asset_url(geojson_file.replace('/data/daily_dashboard', 'geojsons'))

        name = VARS[self.var]['name']
        bounds = self.bounds
        colorscale = COLORS_PROB

        if DEBUG: print("GEOJSON_URL", geojson_url)

        style = dict(weight=0, opacity=0, color='white', dashArray='', fillOpacity=OPACITY)

        # Create colorbar
        ctg = ["{:.1f}".format(cls) if '.' in str(cls) else "{:d}".format(cls)
                for i, cls in enumerate(bounds)]
        indices = list(range(len(ctg)))
        self.colorbar_info.update({'min': -0.1,
                                   'max': len(ctg)-.7,
                                   'classes': indices,
                                   'colorscale': colorscale,
                                   'tickValues': indices,
                                   'tickText': ctg})
        self.colorbar = self.retrieve_colorbar()

        # Geojson rendering logic, must be JavaScript as it is executed in clientside.
        namespace = Namespace("forecastTab", "forecastMaps")
        hideout = dict(colorscale=colorscale, bounds=bounds, style=style, colorProp="value")
        self.geojson = self.retrieve_geojson(geojson_url, namespace, hideout)

        return [self.geojson, self.colorbar]

    def get_title(self, **kwargs):
        """ Return title from base title and elements """
        
        tstep = kwargs['tstep']

        self.members = 0
        for model in MODELS:
            if model == 'median':
                continue
            path_template = '{}{}.nc'.format(self.rdatetime.strftime("%Y%m%d"), MODELS[model]['template'])
            fpath = os.path.join(MODELS[model]['path'], 'netcdf', path_template)
            if os.path.exists(fpath):
                self.members += 1

        self.base_title = PROB[self.var]['title']
        self.cdatetime = self.rdatetime + relativedelta(days=tstep)

        title = super(ForecastProbFigureHandler, self).get_title()

        return title

    def get_figure_layers(self, day=0):
        """ run plot """
        
        if DEBUG: print("***", self.var, day, self.geojsonpath, self.filepath)
        
        if DEBUG: print('Update layout ...')

        if self.var and os.path.exists(self.filepath):
            
            # Get timestep
            tstep = int(day)

            # Get trace
            if DEBUG: print('Adding contours...')
            self.generate_var_tstep_trace(tstep)

            # Get figure title
            self.fig_title = html.P(html.B(
                [
                    item for sublist in self.get_title(varname=self.var, tstep=tstep).split('<br>') 
                    for item in [sublist, html.Br()]
                ][:-1]
            ))
        else:
            self.geojson = None
            self.colorbar = None

            # Get figure title
            if not self.var:
                self.fig_title = ""
            else:
                self.fig_title = html.P(html.B("DATA NOT AVAILABLE"))

        # Get title information element
        self.info = self.retrieve_info(self.name)
        
        return [self.geojson, self.colorbar, self.info]


class ForecastWasFigureHandler(ShapefileFigureHandler):
    """ Class to manage the figure creation """

    def __init__(self, was='burkinafaso', model='median', variable='SCONC_DUST', selected_date=None):
        """ Initialize WasFigureHandler with shapefile and netCDF data """
        
        self.name = 'was'
        super(ForecastWasFigureHandler, self).__init__()

        self.model = model
        self.was = was
        self.variable = variable
        self.selected_date = selected_date

        self.colormap = WAS[self.was]['colormap']
        self.hideout_style = WAS[self.was]['hideout_style']
        self.hideout_style.update({'fillOpacity': OPACITY})
        self.hover_style = WAS[self.was]['hover_style']
        self.hover_style.update({'fillOpacity': OPACITY})

        if (self.model in MODELS) and self.selected_date:
            
            # Get NetCDF file path
            self.filepath = NETCDF_TEMPLATE.format(
                MODELS[self.model]['path'],
                selected_date,
                MODELS[self.model]['template']
            )
            
            if os.path.exists(self.filepath):
                # Read NetCDF file and retrieve timesteps, what (hours, months, etc.) and rdatetime
                self.timesteps, self.what, self.rdatetime = self.get_time_details()
            
                # Correct timesteps to show first timestep of each day ([0, 24, 48, ...])
                self.timesteps = self.timesteps[::8]

    def get_regions_data(self, day=0):
        input_dir = WAS[self.was]['path'].format(was=self.was, format='h5', date=self.selected_date)
        input_file = WAS[self.was]['template'].format(date=self.selected_date, var=self.variable, format='h5')

        input_path = os.path.join(input_dir, input_file)

        if DEBUG: print("INPUT PATH", input_path, self.selected_date)
        if not os.path.exists(input_path):
            return [], [], []

        df = pd.read_hdf(input_path, 'was_{}'.format(self.selected_date)).set_index('day')

        names, colors, definitions = df.loc['Day{}'.format(day)].values.T
        return names, colors, definitions

    def get_geojson_url(self, day=0):
        from dash_server import app
        geojsons_dir = WAS[self.was]['path'].format(was=self.was, format='geojson', 
                                                    date=self.selected_date)
        geojson_file = WAS[self.was]['template'].format(date=self.selected_date, var=self.variable, 
                                                        format='geojson').replace('.geojson', 
                                                        '_{}.geojson'.format(day))
        geojson_filepath = os.path.join(geojsons_dir, geojson_file)
        if not os.path.exists(geojson_filepath):
            if DEBUG: print("ERROR: WAS GEOJSON URL", geojson_filepath)
            return None

        pathlist = os.path.normpath(geojsons_dir).split(os.sep)
        geojsons_dir_cleaned = os.sep.join(pathlist[pathlist.index('was'):])

        geojson_path = os.path.join(geojsons_dir_cleaned, geojson_file)
        geojson_url = app.get_asset_url(os.path.join('geojsons', geojson_path))

        if DEBUG: print("WAS GEOJSON URL", geojson_url)

        return geojson_url

    def generate_var_tstep_trace(self, day=0):
        """ Generate trace to be added to data, per variable and timestep """

        if DEBUG: print('Adding contours ...')

        day = int(day)
        names, colors, definitions = self.get_regions_data(day=day)
        colors = np.array(colors)
        if DEBUG: print("::::::::::", names, colors, definitions)

        # Create legend
        self.legend = self.create_legend(self.colormap)

        # Geojson rendering logic, must be JavaScript as it is executed in clientside.
        namespace = Namespace("forecastTab", "wasMaps")
        geojson_url = self.get_geojson_url(day=day)
        hideout = dict(colorscale=list(self.colormap.values()),
                       bounds=[i for i in range(len(self.colormap.values()))],
                       style=self.hideout_style,
                       colorProp="value")
        hover_style = arrow_function(self.hover_style)
        self.geojson = self.retrieve_geojson(geojson_url, namespace, hideout, hover_style)

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

        tstep = kwargs['day']
        self.base_title = WAS[self.was]['title']
        self.cdatetime = self.retrieve_cdatetime(tstep)
        title = super(ForecastWasFigureHandler, self).get_title()

        return title

    def get_figure_layers(self, day=0):
        """ run plot """

        if DEBUG: print('Update layout ...')
        
        if os.path.exists(self.filepath):
            # Get trace
            self.generate_var_tstep_trace(day)

            # Get figure title
            self.fig_title = html.P(html.B(
                [
                    item
                    for sublist in self.get_title(day=day).split('<br>')
                    for item in [sublist, html.Br()]
                ][:-1]
            ))
        else:
            self.geojson = None
            self.legend = None

            # Get figure title
            self.fig_title = html.P(html.B("DATA NOT AVAILABLE"))

        # Get title information element
        self.info = self.retrieve_info(self.name)

        return [self.geojson, self.legend, self.info]


class EvaluationGroundFigureHandler(PointsFigureHandler):
    """ Class which handles AERONET observations data """

    def __init__(self, sdate=None, edate=None, obs=None, var=None):
        
        self.name = obs
        super(EvaluationGroundFigureHandler, self).__init__()
        self.circle_options = OBS[obs]['circle_options']

        fday = sdate[:-2] + '01'
        lday = edate[:-2] + str(calendar.monthrange(int(edate[:4]), int(edate[4:6]))[1])
        date_range = pd.date_range(fday, lday, freq='M')
        months = [d.strftime("%Y%m") for d in date_range.to_pydatetime()]
        filepaths = ["{}.nc".format(os.path.join(OBS[obs]['path'], 'netcdf',
            OBS[obs]['template'].format(OBS[obs]['obs_var'], month))) for month
            in months]
        self.input_files = [nc_file(filepath) for filepath in filepaths if os.path.exists(filepath)]
        
        if self.input_files:

            if 'longitude' in self.input_files[0].variables:
                lon_var = 'longitude'
                lat_var = 'latitude'
            else:
                lon_var = 'lon'
                lat_var = 'lat'

            lon = self.input_files[0].variables[lon_var][:]
            lat = self.input_files[0].variables[lat_var][:]
            self.var = [var for var in self.input_files[0].variables if var == OBS[obs]['obs_var']][0]
            if DEBUG: print('VARNAME', self.var)

            sites_path = os.path.join(DIR_PATH, 'conf/', OBS[obs]['sites'])
            if os.path.exists(sites_path):
                sites = pd.read_csv(sites_path)
                idxs, self.station_names = np.array([[idx, st_name[~st_name.mask].tobytes().decode('utf-8')]
                                    for idx, st_name in
                                    enumerate(self.input_files[0].variables['station_name'][:])
                                    if st_name[~st_name.mask].tobytes().decode('utf-8').upper() in map(str.upper, sites['SITE'])]
                                    ).T

                if DEBUG: print('IDXS', idxs)
                if DEBUG: print('ST_NAMES', self.station_names)

            self.clon = lon[idxs.astype(int)]
            self.clat = lat[idxs.astype(int)]

            self.values = {
                self.var: np.concatenate([input_file.variables[self.var][:, idxs.astype(int)] 
                                         for input_file in self.input_files])
            }

    def generate_var_tstep_trace(self):
        """ Generate trace to be added to data, per variable and timestep """
        
        val = np.ma.masked_where(self.values[self.var]<0., self.values[self.var])
        notnan = (np.array([i for i in range(val.shape[1]) if not val[:, i].mask.all()]),)
        clon = self.clon[notnan]
        clat = self.clat[notnan]
        cstations = self.station_names[notnan]

        # Create dataframe
        self.df = pd.DataFrame({
            'lon': clon.round(2),
            'lat': clat.round(2),
            'stations': cstations
            })  # .T, columns=['lon', 'lat', 'station'])
        dicts = self.df.to_dict('records')

        # Create geojson
        # Note: It must be JavaScript as it is executed in clientside
        geojson_data = dlx.dicts_to_geojson(dicts, lon="lon")
        namespace = Namespace("evaluationTab", "evaluationMaps")
        self.geojson = self.retrieve_geojson(geojson_data, namespace)

    def get_figure_layers(self):
        
        if self.input_files:
            # Get trace
            self.generate_var_tstep_trace()

        else:
            self.geojson = None
            self.df = pd.DataFrame([])

        return self.df, [self.geojson]


class EvaluationGroundTimeSeriesHandler:
    """ Class to handle evaluation time series """

    def __init__(self, obs, start_date, end_date, variable, models=None):
        self.obs = obs
        if models is None:
            models = list(MODELS.keys())
        self.model = models
        self.variable = variable
        self.dataframe = []
        if DEBUG: print("ObsTimeSeries", start_date, end_date)
        self.date_range = pd.date_range(start_date, end_date, freq='D')

        fname_tpl = os.path.join(OBS[obs]['path'],
                                'feather',
                                '{{}}-{dat}-{{}}_interp.ft')

        months = np.unique([d.strftime("%Y%m") for d in self.date_range.to_pydatetime()])

        self.date_index = pd.date_range('{}01'.format(months[0]),
                '{}01'.format((datetime.strptime(months[-1], '%Y%m') +
                    relativedelta(days=31)).strftime('%Y%m')), freq=f'{FREQ}H')

        if DEBUG:
            print('MONTHS', months)
        if DEBUG:
            print('DATE_INDEX', self.date_index)

        fname_obs = fname_tpl.format(dat=obs)
        notnans, obs_df = concat_dataframes(fname_obs, months, variable,
            rename_from=OBS[obs]['obs_var'])
        self.dataframe.append(obs_df)

        if 'flt_var' in OBS[obs]:
            fname_flt = fname_tpl.format(dat='{}_{}'.format(obs, OBS[obs]['flt_var']))
            _, flt_df = concat_dataframes(fname_flt, months, variable,
                rename_from=OBS[obs]['flt_var'], notnans=notnans)
            self.dataframe.append(flt_df)

        for mod in self.model:
            fname_mod = fname_tpl.format(dat=mod)
            _, mod_df = concat_dataframes(fname_mod, months, variable,
                    rename_from=None, notnans=notnans)
            self.dataframe.append(mod_df)


    def retrieve_timeseries(self, idx, st_name, model):
        """ Return traces with timeseries of observations, filtering variable and
        all models """
        
        #FIXME pop filtering variable for now
        self.dataframe.pop(1)
        old_indexes = self.dataframe[0]['station'].unique()
        new_indexes = np.arange(old_indexes.size)
        dict_idx = dict(zip(new_indexes, old_indexes))
        if DEBUG: print("RETRIEVE TS", idx, dict_idx[idx], st_name, model)
        fig = go.Figure()
        for mod, df in zip([self.obs]+self.model, self.dataframe):
            if df is None:
                continue
            if DEBUG: print("MOD", mod, "COLS", df.columns)
            if df.columns[-1].upper() == self.variable:
                df = df.rename(columns = { df.columns[-1]: self.variable })

            if DEBUG: print("BUILDING TIME-SERIES")
            try:
                tmp_df = df[df['station']==dict_idx[idx]].set_index('time')
                tmp_df.drop_duplicates(keep='first')
                timeseries = \
                   tmp_df.reindex(self.date_index)
                if timeseries[self.variable].isnull().all():
                    continue
            except Exception as e:
                if DEBUG: print("ERROR timeseries", str(e))
                continue

            if DEBUG: print("SELECTING COORDS")
            if 'lat' in df.columns:
                lat_col = 'lat'
                lon_col = 'lon'
            else:
                lat_col = 'latitude'
                lon_col = 'longitude'

            if mod == self.obs:
                if DEBUG: print("OBSERVATION")
                sc_mode = 'markers'
                marker = {'size': 10, 'symbol': "triangle-up-dot", 'color': '#f0b450'}
                line = {}
                visible = True
                name = mod.upper()
            else:
                if DEBUG: print("MODEL", mod)
                try:
                    sc_mode = 'lines'
                    marker = {}
                    line = { 'color': MODELS[mod]['color'] }
                    visible = (mod == model) and True or 'legendonly'
                    name = "{}".format(MODELS[mod]['name'])
                    cur_lat = round(timeseries[lat_col].dropna()[0], 2)
                    cur_lon = round(timeseries[lon_col].dropna()[0], 2)
                except Exception as e:
                    if DEBUG: print("ERROR MODEL", mod, str(e), timeseries[lat_col].dropna())
                    continue

            if mod == 'median':
                line['dash'] = 'dash'

            if DEBUG: print("ADD TRACE")
            fig.add_trace(dict(
                type='scatter',
                name=name,
                x=timeseries.index,
                y=timeseries[self.variable].round(2),
                mode=sc_mode,
                marker=marker,
                line=line,
                visible=visible,
                connectgaps=False
                )
            )

        title = "{} @ {} (lat = {:.2f}, lon = {:.2f})".format(
            VARS[self.variable]['name'], st_name, cur_lat, cur_lon,
        )

        fig.update_layout(
            title=dict(text=title, x=0.45, y=0.99),
            # uirevision=True,
            autosize=True,
            showlegend=True,
            plot_bgcolor='#F9F9F9',
            font_size=12,
            hovermode="x",        # highlight closest point on hover
            margin={"r": 10, "t": 35, "l": 10, "b": 10},
        )
        fig.update_xaxes(
            range=[self.date_range[0], self.date_range[-1]],
            rangeslider_visible=True,
            rangeselector=dict(
                buttons=list([
                    dict(step="all", label="all"),
                    dict(count=14, label="2w",
                         step="day", stepmode="backward"),
                    dict(count=7, # label="1w",
                         step="day", stepmode="backward"),
                ])
            )
        )

        if DEBUG: print('FIG TYPE', type(fig))
        return fig


class EvaluationSatelliteFigureHandler(ContourFigureHandler):

    def __init__(self, var, obs, tstep, selected_date):
        
        self.obs = obs
        self.name = self.obs
        super(EvaluationSatelliteFigureHandler, self).__init__()
        
        self.var = var
        self.selected_date = selected_date

        self.bounds = np.array(VARS[self.var.upper()]['bounds']).astype('float32')

        if self.selected_date:
            # Get NetCDF file path
            filedir = OBS[self.obs]['path']
            filetpl = OBS[self.obs]['template'].format(OBS[self.obs]['obs_var'], self.selected_date) + '.nc'
            self.filepath = os.path.join(filedir, 'netcdf', filetpl)

            if os.path.exists(self.filepath):
                # Read NetCDF file and retrieve timesteps, what (hours, months, etc.) and rdatetime
                self.timesteps, self.what, self.rdatetime = self.get_time_details()

    def generate_var_tstep_trace(self, tstep):

        from dash_server import app

        data_path = os.path.basename(OBS[self.obs]['path'][:-1])
        
        geojson_url = app.get_asset_url(os.path.join('geojsons',
            GEOJSON_TEMPLATE.format(data_path, self.selected_date, tstep, self.selected_date,
                OBS[self.obs]['obs_var'])))
                
        if DEBUG: print("OBS", self.obs, "GEOJSON_URL", geojson_url)

        style = dict(weight=0, opacity=0, color='white', dashArray='', fillOpacity=OPACITY)

        # Create colorbar
        ctg = ["{:d}".format(int(cls)) if cls.as_integer_ratio()[1] == 1 else "{:.1f}".format(cls) 
               for i, cls in enumerate(self.bounds[1:-1])]
        indices = list(range(len(ctg) + 2))
        self.colorbar_info.update({'min': 0,
                                   'max': len(ctg)+1, 
                                   'classes': indices,
                                   'colorscale': COLORS,
                                   'tickValues': indices[1:-1],
                                   'tickText': ctg})
        self.colorbar = self.retrieve_colorbar()

        if DEBUG: print("BOUNDS", self.bounds)
        if DEBUG: print("CTG", ctg)

        # Geojson rendering logic, must be JavaScript as it is executed in clientside.
        namespace = Namespace("forecastTab", "forecastMaps")
        hideout = dict(colorscale=COLORS, bounds=self.bounds, style=style, colorProp="value")
        self.geojson = self.retrieve_geojson(geojson_url, namespace, hideout)

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

        varname = kwargs['varname']
        tstep = kwargs['tstep']

        self.base_title = " ".join([OBS[self.obs]['name'], OBS[self.obs]['title']])
        self.cdatetime = self.retrieve_cdatetime(tstep)
        self.step = "{:02d}".format(tstep*FREQ)
        title = super(EvaluationSatelliteFigureHandler, self).get_title()
        
        return title

    def get_figure_layers(self, tstep=0):
        """ Run plot """

        if DEBUG: print('Update layout ...')

        if self.var and not os.path.exists(self.filepath):
            self.geojson = None
            self.colorbar = None

            # Get figure title
            if self.obs in OBS[self.obs]:
                title = "{} - DATA NOT AVAILABLE".format(OBS[self.obs]['name'])
            else:
                title = "DATA NOT AVAILABLE"
            self.fig_title = html.P(html.B("DATA NOT AVAILABLE"))
        else:
            # Get trace
            self.generate_var_tstep_trace(tstep)

            # Get figure title
            self.fig_title = html.P(html.B(
                [
                    item for sublist in self.get_title(varname=self.var, tstep=tstep).split('<br>') 
                    for item in [sublist, html.Br()]
                ][:-1]
            ))

        # Get title information element
        self.info = self.retrieve_info(self.name)
        
        return [self.geojson, self.colorbar, self.info]


class EvaluationStatisticsFigureHandler(PointsFigureHandler):
    """ Class to manage the figure creation """

    def __init__(self, network=None, statistic=None, model=None, selection=None):

        self.name = 'scores'
        super(EvaluationStatisticsFigureHandler, self).__init__()

        # self.extent = SCORES['extent']
        self.model = model
        self.network = network
        self.statistic = statistic
        self.selection = selection

        if self.network and self.model and self.statistic and self.selection:

            # Define markers style
            self.bounds = np.array(STATS_CONF[self.statistic]['bounds'])
            self.cmap = cm.get_cmap(STATS_CONF[self.statistic]['cmap'], len(self.bounds))
            self.colormap = {}
            for i in range(self.cmap.N):
                self.colormap[i] = matplotlib.colors.rgb2hex(self.cmap(i))
            self.labels = list(self.colormap.keys())
            self.colors = list(self.colormap.values())
            self.circle_options = SCORES['circle_options']

            # Get stations data
            if network == 'aeronet':
                self.sites = pd.read_csv(os.path.join(DIR_PATH, 'conf/', OBS[network]['sites']))
                self.circle_options.update({'radius': 6})
            else:
                self.sites = None
                self.circle_options.update({'radius': 2})
            
            # Get selected month / year
            # Set day to first date of the month to transform into datetime and avoid errors creating title
            year = int(self.selection[0:4])
            month = int(self.selection[4:6])
            self.rdatetime = datetime(year, month, 1)

            # Read data
            self.read_data()

    def read_data(self):
        
        # Get HDF5 file
        filedir = OBS[self.network]['path']
        filename = "{}_{}.h5".format(self.selection, self.statistic)
        tab_name = "{}_{}".format(self.statistic, self.selection)
        self.filepath = os.path.join(filedir, "h5", filename)

        # Read data
        if os.path.exists(self.filepath):
            self.data = pd.read_hdf(self.filepath, tab_name)
            if DEBUG: print('SCORES FILEPATH', self.filepath, 'SELECTION', self.selection, 'TAB', tab_name)

    def select_data(self):

        # Get coordinates for each site
        if self.sites is not None:
            for station in self.sites['SITE']:
                self.data.loc[self.data['station'] == station, 'lon'] = \
                    self.sites.loc[self.sites['SITE'] == station, 'LONGITUDE'].values[0]
                self.data.loc[self.data['station'] == station, 'lat'] = \
                    self.sites.loc[self.sites['SITE'] == station, 'LATITUDE'].values[0]
    
        # Transform empty values into np.nan
        data = self.data.replace('-', np.nan)

        # Remove continents and stations without coordinates / model score
        data = data.dropna(subset=['lat', 'lon', self.model])

        # Transform into numeric (if needed)
        for column in ['lat', 'lon', self.model]:
            data[column] = pd.to_numeric(data[column])

        # Get coordinates / scores / stations for selected model
        lon = np.array(data['lon'])
        lat = np.array(data['lat'])
        scores = np.array(data[self.model])
        if self.sites is not None:
            stations = np.array(data['station'])
        else:
            stations = None
            
        # Assign color index in colormap to scores
        res = np.zeros((len(lon)))
        for point_n, score in enumerate(scores):
            for label_n, label in enumerate(self.labels):
                # Skip last position
                if label_n < self.labels[-1]:
                    # Below minimum limit, get first color in cmap
                    if float(score) < self.bounds[0]:
                        res[point_n] = self.labels[0]
                        break
                    # Above maximum limit, get last color in cmap
                    elif float(score) >= self.bounds[-1]:
                        res[point_n] = self.labels[-1]
                        break
                    # In between, find cmap color in range
                    elif (float(score) >= self.bounds[label_n]) and (float(score) < self.bounds[label_n+1]):
                        res[point_n] = self.labels[label_n]
                        break

        return lon, lat, stations, scores, res

    def get_figure_layers(self):
        """ Run plot """

        if DEBUG: print('Update layout ...')

        if (os.path.exists(self.filepath)) and (self.model in self.data.columns):
            # Get data
            lon, lat, stations, scores, res = self.select_data()

            # Get trace
            self.generate_var_tstep_trace(lon, lat, stations, scores, res)

            if DEBUG: print('Adding one point ...')
            # Get figure title
            self.fig_title = html.P(html.B(
                [
                    item for sublist in self.get_title().split('<br>') 
                    for item in [sublist, html.Br()]
                ][:-1]
            ))
        else:
            self.geojson = None
            self.colorbar = None
            self.fig_title = html.P(html.B("DATA NOT AVAILABLE"))

        # Get title information element
        self.info = self.retrieve_info(self.name)

        return [self.geojson, self.colorbar, self.info]

    def generate_var_tstep_trace(self, lon, lat, stations, scores, res):
        """ Generate trace to be added to data, per variable and timestep """

        # Create dataframe
        df = pd.DataFrame({
            'lon': np.array(lon).round(2),
            'lat': np.array(lat).round(2),
            'score': scores.round(2),
            'value': res
            })
        if stations is not None:
            df['station'] = stations
            var_list=['station', 'lon', 'lat', 'score']
        else:
            var_list=['lon', 'lat', 'score']
        
        # Add tooltips (hover information) to map
        data_dict = self.get_tooltip(df, var_list=var_list)

        # Create colorbar
        bounds = STATS_CONF[self.statistic]['bounds']
        ctg = ["{}".format(cls) if '.' in str(cls) else "{:d}".format(cls)
                for i, cls in enumerate(bounds)]
        indices = list(range(len(bounds)))
        self.colorbar_info.update({'min': bounds[0],
                                   'max': bounds[-1],
                                   'classes': len(bounds)-1,
                                   'colorscale': self.colors,
                                   'tickValues': self.bounds,
                                   'tickText': ctg})
        self.colorbar = self.retrieve_colorbar()

        # Create geojson
        geojson_data = dlx.dicts_to_geojson(data_dict, lon="lon")
        namespace = Namespace("evaluationTab", "evaluationMaps")
        self.geojson = self.retrieve_geojson(geojson_data, namespace)

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

        self.model_name = MODELS[self.model]['name']
        self.network_name = OBS[self.network]['name']
        self.statistic_name = STATS_CONF[self.statistic]['name']
        self.base_title = SCORES['title']
        title = super(EvaluationStatisticsFigureHandler, self).get_title()
        
        return title

class VisFigureHandler(PointsFigureHandler):
    """ Class to manage the figure creation """

    def __init__(self, selected_date=None):
        
        self.name = 'vis'
        super(VisFigureHandler, self).__init__()

        self.selected_date = selected_date

        self.path_tpl = VIS['path']
        self.xlon = np.array(VIS['xlon'])
        self.ylat = np.array(VIS['ylat'])
        self.ec = VIS['ec']
        self.size = VIS['size']
        self.freq = VIS['freq']
        self.colormap = VIS['colormap']
        self.colors = list(VIS['colormap'].values())
        self.labels = list(VIS['colormap'].keys())
        self.values = VIS['values']
        self.circle_options = VIS['circle_options']

        if self.selected_date:
            self.rdatetime = datetime.strptime(self.selected_date, '%Y%m%d')

    def read_data(self, tstep):
        
        # Get CSV file
        tstep0 = tstep
        tstep1 = tstep + self.freq
        year = datetime.strptime(self.selected_date, '%Y%m%d').strftime('%Y')
        month = datetime.strptime(self.selected_date, '%Y%m%d').strftime('%m')
        day = datetime.strptime(self.selected_date, '%Y%m%d').strftime('%d')
        self.filepath = self.path_tpl.format(year=year, month=month, day=day, tstep0=tstep0, 
                                             tstep1=tstep1)
        
        # Read data
        if os.path.exists(self.filepath):
            self.data = pd.read_table(self.filepath, na_filter=False)
            if DEBUG: print("VIS FILEPATH", self.filepath)

    def select_data(self, tstep=0):
        """ Set time dependent data """

        # Uncertain
        cx = np.where((self.data['WW'].astype(str) == "HZ") | (self.data['WW'].astype(str) == "5")| (self.data['WW'].astype(str) == "05"))

        # Visibility <= 1km
        c0t = np.where((self.data['VV'] <= 1000))[0]
        c0x = np.where([c0t == i for i in cx[0]])[-1]
        c0 = (np.delete(c0t, c0x),)

        # Visibility 1km <= 2km
        c1t = np.where((self.data['VV'] > 1000) & (self.data['VV'] <= 2000))[0]
        c1x = np.where([c1t == i for i in cx[0]])[-1]
        c1 = (np.delete(c1t, c1x),)

        # Visibility 2km <= 5km
        c2t = np.where((self.data['VV'] > 2000) & (self.data['VV'] <= 5000))[0]
        c2x = np.where([c2t == i for i in cx[0]])[-1]
        c2 = (np.delete(c2t, c2x),)

        xlon = self.data['LON'].values
        ylat = self.data['LAT'].values
        stations = self.data['STATION'].values
        visibility = self.data['VV'] if 'VV' in self.data.columns else []
        humidity = self.data['HUMIDITY'] if 'HUMIDITY' in self.data.columns else []

        # Make sure that the dtype is not object, this appears in files with missing values
        humidity = pd.to_numeric(humidity, errors='coerce')
        
        # Replace np.nan by -999
        # In the case of humidity, we want to show the values as int and cannot do so with nan
        humidity = humidity.replace(float('nan'), -999)

        return xlon, ylat, stations, np.array(visibility), np.array(humidity), (c0, c1, c2, cx)

    def generate_var_tstep_trace(self, xlon, ylat, stations, visibility, humidity, values, color, 
                                 labels, tstep=0):
        """ Generate trace to be added to data, per variable and timestep """

        if list(xlon) and list(ylat) and list(stations) and list(visibility) and list(humidity):

            # Create legend
            self.legend = self.create_legend(self.colormap)

            # Assign colors to values
            n_points = len(xlon)
            res = np.zeros((n_points))
            for i, (value, label) in enumerate(zip(values, labels)):
                res[value] = i

            # Create dataframe
            df = pd.DataFrame({
                'station': stations,
                'lon': xlon.round(2),
                'lat': ylat.round(2),
                'visibility': (visibility/1e3).round(2),
                'humidity': humidity.astype(int),
                'value': res
                })
        
            # Replace -999 by np.nan to revert previous change
            df['humidity'] = df['humidity'].replace(-999, float('nan'))

            # Add tooltips (hover information) to map
            data_dict = self.get_tooltip(df, 
                var_list=['station', 'lon', 'lat', 'visibility', 'humidity'])

            # Create geojson
            geojson_data = dlx.dicts_to_geojson(data_dict, lon="lon")
            namespace = Namespace("observationsTab", "observationsMaps")
            self.geojson = self.retrieve_geojson(geojson_data, namespace)

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

        tstep = None if 'tstep' not in kwargs else kwargs['tstep']
        if tstep is None:
            return "NO DATA AVAILABLE"
        else:
            self.base_title = VIS['title']
        
        self.tstep0 = "{:02d}".format(tstep)
        self.tstep1 = "{:02d}".format(tstep + self.freq)
        title = super(VisFigureHandler, self).get_title()

        return title

    def get_figure_layers(self, tstep=0, hour=None):
        """ run plot """

        # Read data
        if self.selected_date:
            self.read_data(tstep)

        if os.path.exists(self.filepath):
            tstep = int(tstep)
            xlon, ylat, stations, visibility, humidity, values = self.select_data(tstep)
            self.generate_var_tstep_trace(xlon, ylat, stations, visibility, humidity, values, 
                                          self.colors, self.labels, tstep)
            self.fig_title = html.P(html.B(
                [
                    item for sublist in self.get_title(tstep=tstep).split('<br>') 
                    for item in [sublist, html.Br()]
                ][:-1]
            ))
        else:
            self.geojson = None
            self.legend = None
            self.fig_title = "NO DATA AVAILABLE"

        # Get title information element
        self.info = self.retrieve_info(self.name)

        return [self.geojson, self.info, self.legend]
