# -*- coding: utf-8 -*-
""" EVALUATION CALLBACKS """

from datetime import datetime as dt
from datetime import timedelta
from dateutil.relativedelta import relativedelta
import pandas as pd
import orjson
import os.path
from random import random
import logging
import dash
import dash_bootstrap_components as dbc
from dash import dcc
from dash import html
from dash.dependencies import Output
from dash.dependencies import Input
from dash.dependencies import State
from dash.exceptions import PreventUpdate
import dash_leaflet as dl
from ines_core_utils import extend_list

from map_handler import DEFAULT_VAR
from map_handler import VARS
from map_handler import MODELS
from map_handler import OBS
from map_handler import DEBUG
from map_handler import END_DATE
from map_handler import MODEBAR_CONFIG_TS
from map_handler import MODEBAR_LAYOUT_TS
from map_handler import DISCLAIMER_MODELS
from map_handler import DISCLAIMER_OBS
from map_handler import cache, cache_timeout
from tabs.evaluation import tab_evaluation
from tabs.evaluation import STATS
from utils import format_floats
from utils import alphabetize_stations


SCORES = list(STATS.keys())


@dash.callback(
    [Output('evaluation-tab', 'children'),
     Output('nrt-evaluation', 'style'),
     Output('scores-evaluation', 'style')],
    [Input('nrt-evaluation', 'n_clicks'),
     Input('scores-evaluation', 'n_clicks')],
    prevent_initial_call=True
)
def render_evaluation_tab(nrtbutton, scoresbutton):
    """ Render evaluation tab

    Parameters
    ----------
    nrtbutton : int
        Number of clicks on Visual Comparison button
    scoresbutton : int
        umber of clicks on Statistics button

    Returns
    -------
    dash.dcc.Tab.Tab
        Tab
    dict
        Font weight for Visual Comparison button
    dict
        Font weight for Statistics button
    """

    bold = { 'fontWeight': 'bold' }
    norm = { 'fontWeight': 'normal' }
    ctx = dash.callback_context

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id == "nrt-evaluation" and nrtbutton:
            return tab_evaluation('nrt'), bold, norm
        elif button_id == "scores-evaluation" and scoresbutton:
            return tab_evaluation('scores'), norm, bold

    return dash.no_update, bold, norm
    #raise PreventUpdate

@dash.callback(
    [Output('obs-selection-dropdown','options'),
     Output('obs-selection-dropdown','placeholder')],
    [Input('obs-timescale-dropdown', 'value')],
    [State('obs-network-dropdown', 'value')],
    prevent_initial_call=True
)
def update_time_selection(timescale, network):
    """ Update time selection among different networks

    Parameters
    ----------
    timescale : str
        Selected timescale (Monthly, Seasonal or Annual)
    network : str
        Network name

    Returns
    -------
    list
        List of dicts containing reference to dates (e.g. {'label': 'May 2022', 'value': '202205'})
    str
        Selection label
    """

    if timescale is None:
        raise PreventUpdate

    start_date = OBS[network]['start_date']

    seasons = {
           '03': 'Spring',
           '06': 'Summer',
           '09': 'Autumn',
           '12': 'Winter'
            }

    if timescale == 'seasonal':
        ret = [{
            'label' : '{} {}'.format(seasons[mon.strftime('%m')],
                mon.strftime('%Y')),
            'value' : '{}-{}'.format(
                mon.strftime('%Y%m'), (mon + relativedelta(months=2)).strftime('%Y%m'))
            }
            for mon in pd.date_range(start_date, END_DATE, freq='Q')[::-1]][1:]
        placeholder = 'Select season'
    elif timescale == 'annual':
        ret = [{
            'label': mon.strftime('%Y'),
            'value': '{year}01-{year}12'.format(year=mon.strftime('%Y')),
            } for mon in
            pd.date_range(start_date, END_DATE, freq='A')[::-1]]
        placeholder = 'Select year'
    else:   # timescale == 'monthly':
        ret = [{
            'label': mon.strftime('%B %Y'),
            'value': mon.strftime('%Y%m'),
            } for mon in
            pd.date_range(start_date, END_DATE, freq='M')[::-1]]
        placeholder = 'Select month'

    return ret, placeholder

def _no_modis_data():
    """ Return the data needed to make MODIS table output NO DATA """
    return [{'name': 'NO DATA', 'id': ''}], [], { 'display': 'block'}

@dash.callback(
    [Output('modis-scores-table', 'columns'),
     Output('modis-scores-table', 'data'),
     Output('modis-scores-table', 'style_table')],
    [Input('scores-apply', 'n_clicks')],
    [State('obs-models-dropdown', 'value'),
     State('obs-statistics-dropdown', 'value'),
     State('obs-network-dropdown', 'value'),
     State('obs-timescale-dropdown', 'value'),
     State('obs-selection-dropdown', 'value')],
    prevent_initial_call=True
)
def modis_scores_tables_retrieve(n_clicks, models, score, network, timescale, selection):
    """ Read scores tables and show data for MODIS

    Parameters
    ----------
    n_clicks : int
        Number of clicks on APPLY button
    models : str, list
        Model names
    score : str
        Statistic
    network : str
        Network name
    timescale : str
        Timescale
    selection : str
        Period selection

    Returns
    -------
    list
        Column names
    pandas.core.frame.DataFrame
        Dataframe with statistics information
    """

    if not n_clicks or network != 'modis':
        return dash.no_update, dash.no_update, { 'display': 'none' }

    if isinstance(models, str):
        models = [models]

    if isinstance(score, str):
        score = [score]

    score = ['model'] + score

    logging.debug("########### %s %s %s %s %s %s", models, score, network, timescale, selection, n_clicks)
    filedir = OBS[network]['path']
    filename = f"{selection}_scores.h5"
    tab_name = f"total_{selection}"
    filepath = os.path.join(filedir, "h5", filename)
    if not os.path.exists(filepath):
        return _no_modis_data()
    df = pd.read_hdf(filepath, tab_name)
    ret = df.loc[df['model'].isin(models), score]
    ret['model'] = ret['model'].map({k:MODELS[k]['name'] for k in MODELS})
    logging.debug('--- %s', ret.columns)
    logging.debug('--- %s', ret.to_dict('records'))
    # Check if there is any data in returned table, return No Data if not
    if len(ret.to_dict('records')) < 1:
        return _no_modis_data()
    columns = [{'name': i in SCORES and
        STATS[i] or '', 'id': i} for
        i in score]
    return columns, ret.replace('_', ' ', regex=True).to_dict('records'), { 'display': 'block' }


@dash.callback(
    [Output('scores-map-modalbody', 'children'),
     Output('scores-map-modal', 'is_open'),
     Output('obs-models-dropdown-modal', 'value'),
     Output('obs-statistics-dropdown-modal', 'value')],
    [Input('scores-map-apply', 'n_clicks'),
     Input('obs-models-dropdown-modal', 'value'),
     Input('obs-statistics-dropdown-modal', 'value'),],
    [State('obs-network-dropdown', 'value'),
     State('obs-selection-dropdown', 'value'),
     State('obs-models-dropdown', 'value'),
     State('obs-statistics-dropdown', 'value')],
)
@cache.memoize(timeout=cache_timeout)
def scores_maps_retrieve(n_clicks, model, score, network, selection, orig_model, orig_stats):
    """ Read scores tables and plot maps

    Parameters
    ----------
    n_clicks : int
        Number of clicks on APPLY button
    model : str
        Model name
    score : str
        Statistic
    network : str
        Network name
    selection : str
        Period selection
    orig_model : list
        Selected model
    orig_stats : list
        Selected statistics

    Returns
    -------
    dash_leaflet.Map
        Figure
    bool
        Indicates whether the modal window is shown or not
    str
        Model name
    str
        Score name
    """

    from callback_tools import get_evaluation_statistics_figure
    from callback_tools import get_figure

    mb = MODEBAR_LAYOUT_TS

    ctx = dash.callback_context

    logging.debug('::: %s %s %s %s %s', n_clicks, model, score, network, selection)
    if ctx.triggered:

        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id != "scores-map-apply":
            if model is not None and score is not None:
                logging.debug('::: 1 :::')
                layers = get_evaluation_statistics_figure(network, model, score, selection)
                fig = get_figure(layers=layers)
                return fig, True, model, score

            raise PreventUpdate

        if orig_model and orig_stats:
            logging.debug('::: %s %s :::', orig_model, orig_stats)
            curr_model = [mod for mod in MODELS if mod in orig_model][0]
            curr_score  = [sc for sc in SCORES if sc in orig_stats][0]
            layers = get_evaluation_statistics_figure(network, curr_model, curr_score, selection)
            fig = get_figure(layers=layers)

            return fig, True, curr_model, curr_score
        else:
            logging.debug('::: 2.5 :::')
            layers = get_evaluation_statistics_figure(network, model, score, selection)
            fig = get_figure(layers=layers)
            return fig, True, model, score

    return dash.no_update, False, dash.no_update, dash.no_update  # PreventUpdate


@dash.callback(
    extend_list([
      [Output(f'aeronet-scores-table-{score}', 'columns'),
       Output(f'aeronet-scores-table-{score}', 'data'),
       Output(f'aeronet-scores-table-{score}', 'style_table'),
       Output(f'aeronet-scores-table-{score}', 'selected_cells'),
       Output(f'aeronet-scores-table-{score}', 'active_cell')]
        for score in SCORES]),
    [Input('scores-apply', 'n_clicks'),
    *[Input(f'aeronet-scores-table-{score}', 'active_cell')
        for score in SCORES]],
    [State('obs-models-dropdown', 'value'),
     State('obs-statistics-dropdown', 'value'),
     State('obs-network-dropdown', 'value'),
     State('obs-timescale-dropdown', 'value'),
     State('obs-selection-dropdown', 'value')] +
    extend_list([[
        State(f'aeronet-scores-table-{score}', 'columns'),
        State(f'aeronet-scores-table-{score}', 'data'),
        State(f'aeronet-scores-table-{score}', 'style_table')]
        for score in SCORES]),
    prevent_initial_call=True
)
def aeronet_scores_tables_retrieve(n_clicks, *args):
    """ Read scores tables and show data for AERONET

    Parameters
    ----------
    n_clicks : int
        Number of clicks on APPLY button

    Returns
    -------
    list
        Dataframe columns, data, tables style, selected cells and active cells
    """

    ctx = dash.callback_context
    active_cells = list(args[:len(SCORES)])
    tables = list(args[-len(SCORES)*3:])
    logging.debug("ACTIVES %s", active_cells)

    logging.debug("########### %s", args[len(SCORES):-len(SCORES)*3])
    models, stat, network, timescale, selection = args[len(SCORES):-len(SCORES)*3]

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        logging.debug("BUTTON %s", button_id)
        if button_id not in ['scores-apply'] + [f'aeronet-scores-table-{score}'
                                                for score in SCORES]:
            raise PreventUpdate

    if not n_clicks or network != 'aeronet':
        return extend_list([[dash.no_update, dash.no_update, { 'display': 'none' },
                          dash.no_update, dash.no_update] for _ in SCORES])

    # ORDER is IMPORTANT
    areas = ['Europe', 'Mediterranean', 'MiddleEast', 'NAfrica', 'Total']

    if isinstance(models, str):
        models = [models]

    if isinstance(stat, str):
        stat = [stat]

    models = ['station'] + models

    logging.debug("@@@@@@@@@@@ %s %s %s %s %s %s %s", models, stat, network, timescale, selection, n_clicks, len(tables))
    filedir = OBS[network]['path']

    stat_idxs = [SCORES.index(st) for st in stat]

    # 5 outputs per score
    # 3 inputs (states) per score
    ret_tables = [None for _ in range(len(SCORES)*5)]
    for table_idx in range(int(len(tables)/3)):
        obj_idx = table_idx * 3
        ret_idx = table_idx * 5
        ret_tables[ret_idx] = tables[obj_idx]
        ret_tables[ret_idx+1] = tables[obj_idx+1]
        ret_tables[ret_idx+2] = tables[obj_idx+2]
        # curr_columns = tables[obj_idx]
        curr_data = tables[obj_idx+1]
        if active_cells[table_idx] is not None and \
              active_cells[table_idx]['column_id'] == 'station':
            curr_active_cell = active_cells[table_idx]
        else:
            curr_active_cell = None
        if table_idx in stat_idxs:
            filename = "{}_{}.h5".format(selection, SCORES[table_idx])
            tab_name = "{}_{}".format(SCORES[table_idx], selection)
            filepath = os.path.join(filedir, "h5", filename)
            if not os.path.exists(filepath):
                logging.debug ("TABLES 0 %s", tables)
                #Build a no_data list of values to return
                no_data = []
                for _ in range(len(SCORES)):
                    no_data += [[], [], {'display': 'block'}, dash.no_update, None]
                    # Add no data update for UI output only for 1 output
                    no_data[0].append({'name':['NO DATA'], 'id':'station'})
                logging.debug ("TABLES 1 %s", tables)
                logging.debug ("No Data Table %s", no_data)
                return no_data

            df = pd.read_hdf(filepath, tab_name)
            df = format_floats(df)
            df = alphabetize_stations(df)

            ret_tables[ret_idx] = [{'name': i in MODELS and
                [STATS[SCORES[table_idx]], MODELS[i]['name']] or
                [STATS[SCORES[table_idx]], ''], 'id': i} for
                i in models]
            # replace "tables" data
            if curr_active_cell is not None:
                logging.debug("ACTIVE %s", curr_active_cell)
                curr_data = tables[obj_idx+1]
                if not curr_data:
                    continue
                row_number = curr_active_cell['row']
                # 1st case:
                logging.debug('CURRDATA %s', curr_data)
                logging.debug('ROWNUMBER %s', row_number)
                value = curr_data[row_number]['station']
                if value not in areas[:-1]:
                    raise PreventUpdate
                val_idx = df.loc[df['station']==value].index[0]
                # check following data
                if row_number < len(curr_data)-1:
                    foll_val = curr_data[row_number+1]['station']
                    if foll_val in areas:
                        foll_idx = df.loc[df['station']==foll_val].index[0]
                        tables[obj_idx+1] = [table_row
                                for table_row in curr_data
                                        if curr_data.index(table_row) < row_number] + \
                                        df.iloc[val_idx:foll_idx-1][models].to_dict('records') + \
                                        [table_row for table_row in curr_data
                                         if curr_data.index(table_row) > row_number]
                    else:
                        foll_area = areas[areas.index(value)+1]
                        logging.debug("''' %s", curr_data)
                        logging.debug("--- %s", foll_area)
                        foll_idx = curr_data.index([row for row in curr_data
                                                    if row['station'] == foll_area][0])
                        tables[obj_idx+1] = [table_row
                                for table_row in curr_data
                                        if curr_data.index(table_row) <= row_number] + \
                                        [table_row for table_row in curr_data
                                         if curr_data.index(table_row) >= foll_idx]
                ret_tables[ret_idx+1] = tables[obj_idx+1]
                ret_tables[ret_idx+2] = { 'display': 'block' }
                ret_tables[ret_idx+3] = []
                ret_tables[ret_idx+4] = None
            else:
                # ret_tables[ret_idx+1] = df.loc[df['station'].isin(areas), models]
                # .to_dict('records')
                ret_tables[ret_idx+1] = df[df['station'].isin(areas)].reindex(
                        columns=models).to_dict('records')
                ret_tables[ret_idx+2] = { 'display': 'block' }
                ret_tables[ret_idx+3] = dash.no_update
                ret_tables[ret_idx+4] = dash.no_update

        else:
            ret_tables[ret_idx] = []
            ret_tables[ret_idx+1] = []
            ret_tables[ret_idx+2] = { 'display': 'block' }
            ret_tables[ret_idx+3] = dash.no_update
            ret_tables[ret_idx+4] = dash.no_update


    logging.debug('LEN %s', len(ret_tables))
    logging.debug ("TABLES RET %s", ret_tables)

    return ret_tables


@dash.callback(
    [Output('ts-eval-modis-modal', 'children'),
     Output('ts-eval-modis-modal', 'is_open')],
    [Input('ts-eval-modis-button', 'n_clicks')],
    [State('modis-clicked-coords', 'data'),
     State('eval-date-picker', 'date'),
     State('obs-dropdown', 'value'),
     State('obs-mod-dropdown', 'value')],
    prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def show_eval_modis_timeseries(n_clicks, coords, date, obs, model):
    """ Retrieve MODIS evaluation timeseries according to station selected

    Parameters
    ----------
    n_clicks : int
        Number of clicks on EXPLORE TIMESERIES button
    coords : list
        Latitude, longitude and value on coordinates
    date : str
        Selected date
    obs : str
        Observations name
    model : str
        Model name

    Returns
    -------
    dash_bootstrap_components._components.ModalBody.ModalBody
        Timeseries modal window
    bool
        Indicates whether the modal window is shown or not
    """

    from callback_tools import get_timeseries
    if coords is None or n_clicks == 0:
        raise PreventUpdate

    ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    logging.debug("CTXT %s %s", ctxt, type(ctxt))
    if not ctxt or ctxt is None or ctxt != 'ts-eval-modis-button':  #  or n_clicks == 0:P:
        raise PreventUpdate

    logging.debug('TRIGGER %s %s', ctxt, type(ctxt))
    lat, lon, val = coords
    logging.debug(" %s %s ", coords, date)
    models = [obs, model]  # [model for model in MODELS]
    logging.debug('SHOW MODIS EVAL TS""""" %s', coords)
    figure = get_timeseries(models, date, DEFAULT_VAR, lat, lon)
    mb = MODEBAR_LAYOUT_TS
    figure.update_layout(mb)
    print(type(dbc.ModalBody(
        dcc.Graph(
            id='timeseries-eval-modal',
            figure=figure,
            config=MODEBAR_CONFIG_TS
        ))))
    return dbc.ModalBody(
        dcc.Graph(
            id='timeseries-eval-modal',
            figure=figure,
            config=MODEBAR_CONFIG_TS
        )
    ), True


@dash.callback(
    [Output('modis-clicked-coords', 'data'),
     Output(dict(tag='modis-map', index='modis'), 'children')],
    [Input(dict(tag='modis-map', index='modis'), 'click_lat_lng')],
    [State(dict(tag='modis-map', index='modis'), 'children'),
     State('eval-date-picker', 'date'),
     State('obs-mod-dropdown', 'value')],
)
def modis_popup(click_data, figure, date, model):
    """ Manage popup info for MODIS

    Parameters
    ----------
    click_data : list
        Coordinates
    figure : list
        Figure elements
    date : str
        Selected date
    model : str
        Model name

    Returns
    -------
    list
        Latitude, longitude and value on coordinates
    list
        Figure elements with map marker
    """
    
    from callback_tools import get_single_point

    logging.debug("CLICK: %s", str(click_data))
    if not click_data:
        raise PreventUpdate
    ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    ctxt = orjson.loads(dash.callback_context.triggered[0]["prop_id"].split(".")[0])
    logging.debug("CTXT %s %s", ctxt, type(ctxt))
    if not ctxt or ctxt is None or ctxt != {'index': 'modis', 'tag': 'modis-map'}:
        raise PreventUpdate

    lat, lon = click_data
    value = get_single_point(model, date, 0, DEFAULT_VAR, lat, lon)
    logging.debug("VALUE %s", value)

    if not value:
        raise PreventUpdate

    logging.debug("VALUE %s", value)
    try:
        valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=12)
    except:
        valid_dt = dt.strptime(date, '%Y-%m-%d') + timedelta(hours=12)

    marker = dl.Popup(
        children=[
            html.Div([
                    html.Span(html.P(
                        '{:.2f}'.format(value*VARS[DEFAULT_VAR]['mul'])),
                        className='popup-map-value',
                    ),
                    html.Span([
                        html.B(f"Lat {lat:.2f}, Lon {lon:.2f}"), html.Br(),
                        "DATE {:02d} {} {} {:02d}UTC".format(valid_dt.day,
                                                             dt.strftime(valid_dt, '%b'),
                                                             valid_dt.year, valid_dt.hour),
                        html.Br(),
                        html.Button("EXPLORE TIMESERIES",
                            id='ts-eval-modis-button',
                            n_clicks=0,
                            className='popup-ts-button'
                        )],
                        className='popup-map-body',
                    )],
                )
        ],
        id='modis-map-point',
        position=[lat, lon],
        autoClose=True,
        closeOnEscapeKey=True,
        closeOnClick=True,
        closeButton=True,
        className='popup-map-point'
    )

    return [lat, lon, value], figure + [marker]


@dash.callback(
    [Output('stations-clicked-coords', 'data'),
     Output(dict(tag='empty-map', index='None'), 'children'),
     Output(dict(tag='empty-map', index='None'), 'click_lat_lng')],
    [Input(dict(tag='empty-map', index='None'), 'click_lat_lng')],
    [State(dict(tag='empty-map', index='None'), 'children'),
     State('stations-dataframe', 'data')],
    prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def stations_popup(click_data, figure, stations):
    """ Manage popup info for AERONET

    Parameters
    ----------
    click_data : list
        Coordinates
    figure : list
        Figure elements
    stations : str
        Stations

    Returns
    -------
    dict
        Dataframe with stations information
    list
        Figure elements with map marker
    None
        Cleared coordinates
    """

    if not click_data or not stations:
        raise PreventUpdate
    
    logging.debug("CLICK: %s", str(click_data))

    ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    logging.debug("CTXT %s %s", ctxt, type(ctxt))
    if not ctxt or ctxt is None:
        raise PreventUpdate

    trigger = orjson.loads(ctxt)
    logging.debug('TRIGGER %s %s', trigger, type(trigger))

    if trigger != {'index': 'None', 'tag': 'empty-map'}:
        raise PreventUpdate

    df_stations = pd.DataFrame(stations)

    lat, lon = click_data
    curr_station = df_stations[(df_stations['lon'].round(2) == round(lon, 2)) & \
        (df_stations['lat'].round(2) == round(lat, 2))]['stations'].values

    logging.debug("CURR_STATION %s", curr_station)

    if not curr_station:
        raise PreventUpdate

    curr_station = curr_station[0]

    marker = dl.Popup(
        children=[
            html.Div([
                html.Span([
                    html.B(f"Lat {lat:.2f}, Lon {lon:.2f}"), html.Br(),
                    "STATION: ", html.B(f"{curr_station}"), html.Br(),
                    html.Button("EXPLORE TIMESERIES",
                        id='ts-eval-button',
                        n_clicks=0,
                        className='popup-ts-button'
                    )],
                    className='popup-map-eval-body',
                    style={ 'z-index': '10001' }
                )],
            )
        ],
        id=dict(
            tag='empty-map-point',
            index=int(random()*100)
            ),
        position=[lat, lon],
        autoClose=True,
        closeOnEscapeKey=True,
        closeOnClick=True,
        closeButton=True,
        className='popup-map-point',
    )

    curr_data = df_stations[(df_stations['lon'].round(2) == round(lon, 2)) & \
        (df_stations['lat'].round(2) == round(lat, 2))].to_dict()

    logging.debug("MARKER %s", marker.to_plotly_json())
    last = figure[-1]
    if last['type'] == 'Popup':
        figure[-1] = marker.to_plotly_json()
    else:
        figure.append(marker.to_plotly_json())
    logging.debug("LAST %s %s %s", type(figure), type(last), last)
    for _, log in enumerate(figure):
        if log is not None:
            # figure[pos]['id']['random'] =
            if DEBUG:
                logging.debug("******** %s %s", type(log), log.keys())
                # logging.debug(log['type'])
    # Clear the click_data so that repeat clicks continue to call the callback
    click_data = None
    return curr_data, figure, click_data


@dash.callback(
    [Output('ts-eval-modal', 'children'),
     Output('ts-eval-modal', 'is_open')],
    [Input('ts-eval-button', 'n_clicks')],
    [State('stations-clicked-coords', 'data'),
     State('eval-date-picker', 'start_date'),
     State('eval-date-picker', 'end_date'),
     State('obs-dropdown', 'value'),
     State('obs-mod-dropdown', 'value')],
    prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def show_eval_aeronet_timeseries(n_clicks, cdata, start_date, end_date, obs, model):
    """ Retrieve AERONET evaluation timeseries according to selected station

    Parameters
    ----------
    n_clicks : int
        Number of clicks on EXPLORE TIMESERIES button
    cdata : str
        Current station data
    start_date : str
        Start date
    end_date : str
        End date
    obs : str
        Observations name
    model : str
        Model name

    Returns
    -------
    dash_bootstrap_components._components.ModalBody.ModalBody
        Timeseries modal window
    bool
        Indicates whether the modal window is shown or not
    """

    from callback_tools import get_eval_timeseries
    ctxt = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    logging.debug("CTXT %s %s", ctxt, type(ctxt))
    if not ctxt or ctxt is None:
        raise PreventUpdate

    if ctxt != 'ts-eval-button' or n_clicks == 0:
        raise PreventUpdate

    if not cdata:
        raise PreventUpdate
    
    logging.debug(" %s %s %s %s %s", start_date, end_date, obs, model, cdata)
    logging.debug('EVAL AERONET CLICKDATA %s', cdata)
    cdata = pd.DataFrame(cdata)
    idx = int(cdata.index.values[0])
#        lon = cdata.lon.round(2).values[0]
#        lat = cdata.lat.round(2).values[0]
    station_name = cdata.stations.values[0]
    if idx != 0:
        figure = get_eval_timeseries(obs, start_date, end_date, DEFAULT_VAR, idx, station_name, model)
        mb = MODEBAR_LAYOUT_TS
        figure.update_layout(mb)
        logging.debug('SHOW AERONET EVAL TS""""" %s %s %s %s %s', obs, idx, station_name, start_date, end_date)
        return dbc.ModalBody(
            dcc.Graph(
                id='timeseries-eval-modal',
                figure=figure,
                config=MODEBAR_CONFIG_TS
            )
        ), True

    raise PreventUpdate


@dash.callback(
    [Output('stations-dataframe', 'data'),
     Output('graph-eval-aeronet', 'children')],
    [Input('eval-apply', 'n_clicks')],
    [State('eval-date-picker', 'start_date'),
     State('eval-date-picker', 'end_date'),
     State('obs-dropdown', 'value')],
    prevent_initial_call=True)
@cache.memoize(timeout=cache_timeout)
def update_eval_aeronet(n_clicks, start_date, end_date, obs):
    """ Update AERONET evaluation figure according to all parameters

    Parameters
    ----------
    n_clicks : int
        Number of clicks on APPLY button
    start_date : str
        Start date
    end_date : str
        End date
    obs : str
        Observations name

    Returns
    -------
    dict
        Dict with stations positions and names
    dash_leaflet.Map
        Figure
    """

    ctx = dash.callback_context
    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        logging.debug("BUTTON %s", button_id)
        if button_id != 'eval-apply':
            raise PreventUpdate
    else:
        raise PreventUpdate

    if start_date is None or end_date is None or obs != 'aeronet':
        raise PreventUpdate

    from callback_tools import get_figure
    from callback_tools import get_evaluation_comparison_aeronet_figure

    logging.debug('SERVER: calling figure from EVAL picker callback')
    logging.debug('SERVER: start date %s', str(start_date))

    start_date = start_date.split()[0]
    try:
        start_date = dt.strptime(
            start_date, "%Y-%m-%d").strftime("%Y%m%d")
    except:
        start_date = END_DATE
        pass
    if DEBUG:
        logging.debug('SERVER: callback start_date  %s', start_date)

    if end_date is not None:
        end_date = end_date.split()[0]
        try:
            end_date = dt.strptime(
                end_date, "%Y-%m-%d").strftime("%Y%m%d")
        except:
            pass
        if DEBUG:
            logging.debug('SERVER: callback end date %s', end_date)
    else:
        end_date = END_DATE

    stations, points_layer = get_evaluation_comparison_aeronet_figure(start_date, end_date, obs, DEFAULT_VAR)
    fig = get_figure(layers=points_layer)
    
    return stations.to_dict(), fig


@dash.callback(
   [Output('graph-eval-modis-obs', 'children'),
    Output('graph-eval-modis-mod', 'children')],
   [Input('eval-apply', 'n_clicks')],
   [State('eval-date-picker', 'date'),
    State('obs-mod-dropdown', 'value'),
    State('obs-dropdown', 'value'),
    State('graph-eval-modis-mod', 'children')],
    prevent_initial_call=True)
@cache.memoize(timeout=cache_timeout)
def update_eval_modis(n_clicks, date, mod, obs, model_figure):
    """ Update MODIS evaluation figure according to all parameters

    Parameters
    ----------
    n_clicks : int
        Number of clicks on APPLY button
    date : str
        Selected date
    mod : str
        Model name
    obs : str
        Observations name
    model_figure : dict
        Model figure properties

    Returns
    -------
    dash_leaflet.Map
        Figure with MODIS sensor data
    dash_leaflet.Map
        Figure with model data
    """

    ctx = dash.callback_context
    if not ctx.triggered:
        raise PreventUpdate

    button_id = ctx.triggered[0]["prop_id"].split(".")[0]
    logging.debug("BUTTON %s", button_id)
    if button_id != 'eval-apply':
        raise PreventUpdate

    if date is None or mod is None or obs != 'modis':
        raise PreventUpdate

    from callback_tools import get_model_figure
    from callback_tools import get_evaluation_comparison_modis_figure
    from callback_tools import get_figure

    logging.debug('SERVER: calling figure from EVAL picker callback')
    logging.debug(" %s ", model_figure)

    mod_center = model_figure['props']['center']
    mod_zoom = model_figure['props']['zoom']

    if date is not None:
        date = date.split()[0]
        try:
            date = dt.strptime(
                date, "%Y-%m-%d").strftime("%Y%m%d")
        except:
            pass
        if DEBUG:
            logging.debug('SERVER: callback date %s', date)
    else:
        date = END_DATE

    if DEBUG:
        logging.debug("ZOOM %s %s", mod_zoom, mod_center)
    if MODELS[mod]['start'] == 12:
        tstep = 4
    else:
        tstep = 0

    logging.debug("================================ %s %s", date, tstep)
    # Get model figure
    layers = get_model_figure(var=DEFAULT_VAR, model=mod, tstep=tstep, selected_date=date, 
                              hour=12)
    fig_mod = get_figure(center=mod_center, zoom=mod_zoom, layers=layers)

    # Get sensor figure
    layers = get_evaluation_comparison_modis_figure(var=DEFAULT_VAR, obs='modis', tstep=0,
                                                    selected_date=date)
    fig_obs = get_figure(center=mod_center, zoom=mod_zoom, layers=layers, tag='modis', index='modis')

    return fig_obs, fig_mod


@dash.callback(
    [Output('eval-date', 'children'),
     Output('eval-graph', 'children'),
     Output('obs-dropdown', 'value'),
     Output('obs-mod-dropdown-span', 'style')],
    [Input('obs-dropdown', 'value')],
     prevent_initial_call=True)
@cache.memoize(timeout=cache_timeout)
def update_eval(obs):
    """ Update evaluation figure according to all parameters

    Parameters
    ----------
    obs : str
        Observations name

    Returns
    -------
    list
        Date picker properties
    list
        Figure with one map for AERONET and two maps for MODIS (MODIS and model)
    str
        Observations name
    dict
        Figure style
    """

    from callback_tools import get_figure
    
    logging.debug('SERVER: calling figure from EVAL picker callback')

    start_date = OBS[obs]['start_date']

    if obs == 'aeronet':

        eval_date = [
          html.Label("Date range"),
          dcc.DatePickerRange(
            id='eval-date-picker',
            min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
            max_date_allowed=dt.strptime(END_DATE, "%Y%m%d"),
            initial_visible_month=dt.strptime(END_DATE, "%Y%m%d"),
            display_format='DD MMM YYYY',
            # end_date=end_date,
            updatemode='bothdates',
        )]

        eval_graph = html.Div([
                    html.Div(
                        get_figure(),
                        id='graph-eval-aeronet'
                    ),
                    html.Div(
                        DISCLAIMER_OBS,
                        id='eval-aeronet-disclaimer',
                        className='disclaimer'
                    ),
                ])

        style = { 'display': 'none' }

    elif obs == 'modis':

        eval_date = [
          html.Label("Date"),
          dcc.DatePickerSingle(
            id='eval-date-picker',
            min_date_allowed=dt.strptime(start_date, "%Y%m%d"),
            max_date_allowed=dt.strptime(END_DATE, "%Y%m%d"),
            initial_visible_month=dt.strptime(END_DATE, "%Y%m%d"),
            display_format='DD MMM YYYY',
            # date=end_date,
            # with_portal=True,
        )]

        fig_mod = get_figure()
        fig_obs = get_figure(tag='modis')

        graph_obs = html.Div([
            html.Div(
                fig_obs,
                id='graph-eval-modis-obs',
                ),
            html.Div(DISCLAIMER_OBS,
                className='disclaimer')
            ],
            )
        graph_mod = html.Div([
            html.Div(
                fig_mod,
                id='graph-eval-modis-mod',
                ),
            html.Div(DISCLAIMER_MODELS,
                className='disclaimer',
                id='eval-vis-modis-disclaimer'
            )
            ],
            )
        eval_graph = [dbc.Row([
                dbc.Col(graph_obs, width=6),
                dbc.Col(graph_mod, width=6)
            ],
            align='start',
            # no_gutters is replaced with the className because of bootstrap update
            # no_gutters=True,
            className='g-0',
            )]

        style = { 'display': 'table-cell' }

    else:
        raise PreventUpdate

    return eval_date, eval_graph, obs, style
