""" TAB EVALUATION """
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 data_handler import DEFAULT_VAR
from data_handler import VARS
from data_handler import MODELS
from data_handler import OBS
from data_handler import DEBUG
from data_handler import END_DATE
from data_handler import MODEBAR_CONFIG_TS
from data_handler import MODEBAR_LAYOUT_TS
from data_handler import DISCLAIMER_MODELS
from data_handler import DISCLAIMER_OBS
from data_handler import cache, cache_timeout

from tabs.evaluation import tab_evaluation
from tabs.evaluation import STATS

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


SCORES = list(STATS.keys())


def extend_l(l):
    """ Estend list of lists to a flat list """
    res = []
    for x in l:
        res.extend(x)
    if not isinstance(res, list):
        res = [res]
    return res


#def register_callbacks(app, cache, cache_timeout):
#    """ Registering callbacks """

@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):
    """ Function rendering requested tab """
    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 """
    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, models, stat, network, timescale, selection):
    """ Read scores tables and show data """

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

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

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

    stat = ['model'] + stat

    logging.debug("########### %s %s %s %s %s %s", models, stat, network, timescale, selection, n)
    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), stat]
    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 stat]
    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 """

    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

def format_floats(dataframe):
    """This function takes a dataframe and changes all columns except for 'station'
    so that floats will be formatted to 2 digits after the decimal place"""
    for col in dataframe.columns:
        # check if the column is not 'station'
        if col != 'station':
            # convert the column to a string to allow for string formatting
            dataframe[col] = dataframe[col].astype(str)
            # iterate over the values in the column
            for i, val in enumerate(dataframe[col]):
                # check if the value is a float
                if '.' in val:
                    # if so, format it to have 2 decimal places
                    dataframe.at[i, col] = '{:.2f}'.format(float(val))
    return dataframe

def alphabetize_stations(dataframe):
    """ This will alphabetize the stations for each region in the aeronet stats table"""
    # Define a list of regions to sort between (in the desired order)
    regions = ['Europe', 'Mediterranean', 'MiddleEast', 'NAfrica', 'Total']
    region_dfs = []
    # Iterate over each pair of consecutive regions and sort the rows between them
    for i in range(len(regions)-1):
        # Get the indices of the current and next regions
        current_region_idx = dataframe[dataframe['station'] == regions[i]].index[0]
        next_region_idx = dataframe[dataframe['station'] == regions[i+1]].index[0]
        # Slice the dataframe to select the rows between the current and next regions
        subset_df = dataframe.loc[current_region_idx+1:next_region_idx-1]
        # Sort the rows alphabetically based on the 'station' column
        subset_df = subset_df.sort_values(by='station')
        # Create a new dataframe containing only the current region row
        current_region_row = dataframe.loc[current_region_idx].to_frame().T
        # Append the current region row and sorted subset dataframe to the list of region DataFrames
        region_dfs.append(current_region_row)
        region_dfs.append(subset_df)
    # Add the 'Total' row back to the end of the sorted dataframe
    total_row_idx = dataframe[dataframe['station'] == 'Total'].index[0]
    total_row = dataframe.loc[total_row_idx].to_frame().T
    region_dfs.append(total_row)
    # Concatenate all of the region DataFrames into a single sorted DataFrame
    sorted_df = pd.concat(region_dfs)
    return sorted_df

@dash.callback(
    extend_l([
      [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_l([[
        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, *args):
    """ Read scores tables and show data """

    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 or network != 'aeronet':
        return extend_l([[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, 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(nclicks, coords, date, obs, model):
    """ Retrieve MODIS evaluation timeseries according to station selected """
    from callback_tools import get_timeseries
    if coords is None or nclicks == 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 nclicks == 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)
    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, mapid, date, model):
    """ Manages popup info for modis """
    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], mapid + [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, mapid, stations):
    """ Manages popup info for aeronet """
    if not click_data:
        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 = mapid[-1]
    if last['type'] == 'Popup':
        mapid[-1] = marker.to_plotly_json()
    else:
        mapid.append(marker.to_plotly_json())
    logging.debug("LAST %s %s %s", type(mapid), type(last), last)
    for _, log in enumerate(mapid):
        if log is not None:
            # mapid[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, mapid, 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(nclicks, cdata, start_date, end_date, obs, model):
    """ Retrieve AERONET evaluation timeseries according to station selected """
    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 nclicks == 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]
    stat = cdata.stations.values[0]
    if idx != 0:
        figure = get_eval_timeseries(obs, start_date, end_date, DEFAULT_VAR, idx, stat, model)
        mb = MODEBAR_LAYOUT_TS
        figure.update_layout(mb)
        logging.debug('SHOW AERONET EVAL TS""""" %s %s %s %s %s', obs, idx, stat, 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, sdate, edate, obs):
    """ Update AERONET evaluation figure according to all parameters """
    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 sdate is None or edate 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: SDATE %s', str(sdate))

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

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

    stations, points_layer = get_evaluation_comparison_aeronet_figure(sdate, edate, 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, mod_div):
    """ Update MODIS evaluation figure according to all parameters """

    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 ", mod_div)
    mod_center = mod_div['props']['center']
    mod_zoom = mod_div['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')

    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 """
    from callback_tools import get_figure
    # from callback_tools import get_obs1d
    logging.debug('SERVER: calling figure from EVAL picker callback')
    # logging.debug('SERVER: interval %s', str(n))

    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
