""" TAB FORECAST """
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.dependencies import ALL
from dash.dependencies import MATCH
from dash.dependencies import ClientsideFunction
from dash.exceptions import PreventUpdate
import dash_leaflet as dl

from data_handler import DEFAULT_VAR
from data_handler import DEFAULT_MODEL
from data_handler import VARS 
from data_handler import WAS
from data_handler import MODELS
from data_handler import STYLES
from data_handler import FREQ
from data_handler import DEBUG
from data_handler import DATES
from data_handler import PROB 
from data_handler import GRAPH_HEIGHT 
from data_handler import MODEBAR_CONFIG_TS
from data_handler import MODEBAR_LAYOUT_TS
from utils import calc_matrix
from utils import get_graph
from tabs.forecast import tab_forecast

import requests
import pandas as pd
from datetime import datetime as dt
from datetime import timedelta
import time
from io import BytesIO
from PIL import Image
import zipfile
import tempfile
import os.path
import orjson
import math
from random import random

start_date = DATES['start_date']
end_date = DATES['end_date'] or (dt.now() - timedelta(days=1)).strftime("%Y%m%d")

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

@dash.callback(
    [Output('collapse-1', 'is_open'),
     Output('collapse-2', 'is_open'),
     Output('collapse-3', 'is_open'),
     Output('group-2-toggle', 'disabled'),
     Output('group-3-toggle', 'disabled'),
     Output('variable-dropdown-forecast','value')],
    [Input('group-1-toggle', 'n_clicks'),
     Input('group-2-toggle', 'n_clicks'),
     Input('group-3-toggle', 'n_clicks'),
     Input('variable-dropdown-forecast', 'value')],
    [State('collapse-1', 'is_open'),
     State('collapse-2', 'is_open'),
     State('collapse-3', 'is_open'),],
    prevent_initial_call=True
)
def render_forecast_tab(modbutton, probbutton, wasbutton, var, modopen, probopen, wasopen):
    """ Function rendering requested tab """
    ctx = dash.callback_context

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]

        if button_id == "group-1-toggle" and modbutton:
            if modopen is True:
                return not modopen, False, False, dash.no_update, dash.no_update, dash.no_update
            return not modopen, False, False, dash.no_update, dash.no_update, dash.no_update
        elif button_id == "group-2-toggle" and probbutton:
            if probopen is True:
                return False, not probopen, False, dash.no_update, dash.no_update, dash.no_update
            return False, not probopen, False, dash.no_update, dash.no_update, dash.no_update
        elif button_id == "group-3-toggle" and wasbutton:
            if wasopen is True:
                return False, False, not wasopen, dash.no_update, dash.no_update, 'SCONC_DUST'
            return False, False, not wasopen, dash.no_update, dash.no_update, 'SCONC_DUST'

    if var == 'SCONC_DUST':
        # raise PreventUpdate
        return modopen, probopen, wasopen, False, False, dash.no_update
    elif var == 'OD550_DUST':
        # only models and prob can be opened
        if wasopen:
            return True, False, False, False, True, dash.no_update
        return modopen, probopen, wasopen, False, True, dash.no_update
    else:
        if modopen:
            return True, False, False, True, True, dash.no_update
        return True, False, False, True, True, dash.no_update

    raise PreventUpdate

@dash.callback(
    [Output('prob-dropdown', 'options'),
     Output('prob-dropdown', 'value')],
    [Input('variable-dropdown-forecast', 'value')],
    prevent_initial_call=False
)
def update_prob_dropdown(var):
    """ Update Prob maps dropdown """
    if var in ['OD550_DUST','SCONC_DUST']:
        opt_list = PROB[var]['prob_thresh']
        units = PROB[var]['units']
        return [
                {'label': '> {} {}'.format(prob, units),
                 'value': 'prob_{}'.format(prob)}
                for prob in opt_list], 'prob_{}'.format(opt_list[0])

    raise PreventUpdate


@dash.callback(
    [  # Output('alert-models-auto', 'is_open'),
     Output('model-dropdown', 'options'),
     Output('model-dropdown', 'value'),
     Output('btn-anim-download', 'style')],
    [Input('variable-dropdown-forecast', 'value')],
    [Input('model-dropdown', 'value'),],
    prevent_initial_call=True
    )
def update_models_dropdown(variable, checked):

    btn_style = { 'display' : 'block' }
    models = VARS[variable]['models']
    if models == 'all':
        models = list(MODELS.keys())
    else:
        models = eval(models)

    options = [{
        'label': MODELS[model]['name'],
        'value': model,
        'disabled': model not in models,
        } for model in MODELS]

    checked = [c for c in models if c in checked or len(models)==1]
    if len(checked) > 1:
        btn_style = { 'display' : 'none' }
    if DEBUG: print('MODELS', models, 'OPTS', type(options), options)
#        if len(checked) >= 16:
#            options = [{
#                'label': MODELS[model]['name'],
#                'value': model,
#                'disabled': model not in checked,
#                } for model in MODELS]
#
#            return True, options, checked

    return options, checked, btn_style

@dash.callback(
             Output('caret1', 'style'),
             [Input('collapse-1', 'is_open')],
)
def rotate_models_caret(collapse_open):
    rotate_caret = {
      'top':'.05rem',
      'transform': 'rotate(180deg)',
      '-ms-transform': 'rotate(180deg)',
      '-webkit-transform': 'rotate(180deg)'
    }
    if not collapse_open:
        return rotate_caret

@dash.callback(
             Output('caret2', 'style'),
             [Input('collapse-2', 'is_open')],
)
def rotate_prob_caret(collapse_open):
    rotate_caret = {
      'transform': 'rotate(0deg)',
      '-ms-transform': 'rotate(0deg)',
      '-webkit-transform': 'rotate(0deg)'
    }
    if collapse_open:
        return rotate_caret

@dash.callback(
             Output('caret3', 'style'),
             [Input('collapse-3', 'is_open')],
)
def rotate_was_caret(collapse_open):
    rotate_caret = {
      'transform': 'rotate(0deg)',
      '-ms-transform': 'rotate(0deg)',
      '-webkit-transform': 'rotate(0deg)'
    }
    if collapse_open:
        return rotate_caret

@dash.callback(
    [Output('info-collapse', 'is_open'),
     Output('download-collapse', 'is_open')],
    [Input('info-button', 'n_clicks'),
     Input('download-button', 'n_clicks')],
    [State('info-collapse', 'is_open'),
     State('download-collapse', 'is_open')],
    prevent_initial_call=True
)
def sidebar_bottom(n_info, n_download, open_info, open_download):
    ctx = dash.callback_context
    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]

    if button_id == 'info-button':
        if DEBUG: print('clicked INFO', not open_info, False)
        return not open_info, False
    elif button_id == 'download-button':
        if DEBUG: print('clicked DOWN', False, not open_download)
        return False, not open_download

    if DEBUG: print('clicked NONE', False, False)
    raise PreventUdate


#    @dash.callback(
#        [Output('login-modal', 'is_open'),
#         Output('alert-login-error', 'is_open'),
#         Output('alert-login-wrong', 'is_open'),
#         Output('netcdf-download', 'data')],
#        [Input('btn-netcdf-download', 'n_clicks'),
#         Input('submit-login', 'n_clicks')],
#        [State('input_username', 'value'),
#         State('input_password', 'value'),
#         State('model-dropdown', 'value'),
#         State('model-date-picker', 'date')],
#        prevent_initial_call=True,
#    )
#    def download_netcdf(btn_download, btn_login, username, password, models, date):
#        ctx = dash.callback_context
#
#        if ctx.triggered:
#            button_id = ctx.triggered[0]["prop_id"].split(".")[0]
#            if DEBUG: print(':::::', ctx.triggered)
#            if DEBUG: print(':::::', button_id)
#            if button_id == 'btn-netcdf-download' and btn_download > 0:
#                return True, False, False, dash.no_update
#
#            if button_id == 'submit-login':
#                res = requests.get('http://bscesdust03.bsc.es/@users/{}'.format(username),
#                        headers={'Accept': 'application/json'},
#                        auth=(username, password))
#                if res.status_code != 200:
#                    return True, True, False, dash.no_update
#
#                user_data = res.json()
#                if DEBUG: print('USER_DATA', user_data)
#                roles = user_data['roles']
#
#                if DEBUG: print('NC', btn_login, models, date)
#                try:
#                    curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
#                except:
#                    curdate = date
#
#                if curdate == (dt.today() - timedelta(days=1)).strftime('%Y%m%d') and not any(r in roles for r in ('Restricted data user', 'Manager')):
#                    return True, False, True, dash.no_update
#                    
#                if len(models) == 1:
#                    model = models[0]
#                    tpl = MODELS[model]['template']
#                    mod_path = MODELS[model]['path']
#                    final_path = os.path.join(mod_path, 'netcdf', '{date}{tpl}.nc'.format(date=curdate, tpl=tpl))
#                    if DEBUG: print('DOWNLOAD', final_path)
#                    return False, False, False, dcc.send_file(
#                            final_path,
#                            filename=os.path.basename(final_path),
#                            type='application/x-netcdf')
#
#                with tempfile.NamedTemporaryFile() as fp:
#                    with zipfile.ZipFile(
#                            fp, mode="w",
#                            compression=zipfile.ZIP_DEFLATED) as zf:
#
#                        for model in models:
#                            tpl = MODELS[model]['template']
#                            mod_path = MODELS[model]['path']
#                            final_path = os.path.join(
#                                    mod_path,
#                                    'netcdf',
#                                    '{date}{tpl}.nc'.format(date=curdate, tpl=tpl))
#                            if DEBUG: print('ZIPPING', final_path)
#                            fname = os.path.basename(final_path)
#                            zf.write(final_path, fname)
#                    if DEBUG: print('DOWNLOAD', fp.name)
#                    return False, False, False, dcc.send_file(
#                            fp.name,
#                            filename='{}_DUST_MODELS.zip'.format(date),
#                            type='application/zip')
#
#        raise PreventUpdate

@dash.callback(
    Output('btn-anim-download', 'href'),
#         Output('btn-all-frame-download', 'href'),
#         Output('btn-all-anim-download', 'href')],
    [#Input('btn-anim-download', 'n_clicks'),
     Input('model-dropdown', 'value'),
     Input('variable-dropdown-forecast', 'value'),
     Input('model-date-picker', 'date'),
     Input('slider-graph', 'value')],
     prevent_initial_call=False,
)
def download_anim_link(models, variable, date, tstep):
    """ Download PNG frame """
    # from tools import get_figure
    from tools import download_image_link

#        ctx = dash.callback_context
#
#        if ctx.triggered:
#            button_id = ctx.triggered[0]["prop_id"].split(".")[0]
#            if button_id == 'btn-anim-download':
    if DEBUG: print('GIF', models, variable, date)
    try:
        curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
    except:
        curdate = date
    anim = download_image_link(models, variable, curdate, anim=True)
    #all_frame = download_image_link(['all',], variable, curdate, tstep=int(tstep/3), anim=False)
    #all_anim = download_image_link(['all',], variable, curdate, anim=True)
    if DEBUG: print('DOWNLOAD LINK', anim)
    return anim  #, all_frame, all_anim


#    @dash.callback(
#        Output('anim-download', 'data'),
#        [Input('btn-anim-download', 'n_clicks')],
#        [State('model-dropdown', 'value'),
#         State('variable-dropdown-forecast', 'value'),
#         State('model-date-picker', 'date')],
#         prevent_initial_call=True,
#    )
#    def download_anim(btn, models, variable, date):
#        """ Download PNG frame """
#        # from tools import get_figure
#        from tools import download_image
#
#        ctx = dash.callback_context
#
#        if ctx.triggered:
#            button_id = ctx.triggered[0]["prop_id"].split(".")[0]
#            if button_id == 'btn-anim-download':
#                if DEBUG: print('GIF', btn, models, variable, date)
#                try:
#                    curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
#                except:
#                    curdate = date
#                data = download_image(models, variable, curdate, anim=True)
#                if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
#                return data
#
#        raise PreventUpdate
#

@dash.callback(
    Output('all-frame-download', 'data'),
    [Input('btn-all-frame-download', 'n_clicks')],
    [State('variable-dropdown-forecast', 'value'),
     State('model-date-picker', 'date'),
     State('slider-graph', 'value')
     ],
     prevent_initial_call=True,
)
def download_all_frame(btn, models, variable, date, tstep):
    """ Download PNG frame """
    # from tools import get_figure
    from tools import download_image

    ctx = dash.callback_context

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id == 'btn-all-frame-download':
            if DEBUG: print('GIF', btn, models, variable, date)
            try:
                curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
            except:
                curdate = date
            data = download_image(models, variable, curdate, tstep=tstep, anim=False)
            if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
            return data

    raise PreventUpdate

@dash.callback(
    Output('all-anim-download', 'data'),
    [Input('btn-all-anim-download', 'n_clicks')],
    [State('variable-dropdown-forecast', 'value'),
     State('model-date-picker', 'date')],
     prevent_initial_call=True,
)
def download_all_anim(btn, variable, date):
    """ Download PNG frame """
    # from tools import get_figure
    from tools import download_image

    ctx = dash.callback_context

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id == 'btn-all-anim-download':
            if DEBUG: print('GIF', btn, variable, date)
            try:
                curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
            except:
                curdate = date
            data = download_image(['all'], variable, curdate, anim=True)
            if DEBUG: print('DATA', type(data), data.keys(), [data[k] for k in data if k != 'content'])
            return data

    raise PreventUpdate



#    app.clientside_callback(
#        ClientsideFunction(
#            namespace='clientside',
#            function_name='download_png_frame'
#        ),
#        Output('frame-download', 'data'),
#        Input('btn-frame-download', 'n_clicks'),
#        prevent_initial_call=True,
#    )

#    @dash.callback(
#        Output('frame-download', 'data'),
#        [Input('btn-frame-download', 'n_clicks')],
#        [State('model-dropdown', 'value'),
#         State('variable-dropdown-forecast', 'value'),
#         State('model-date-picker', 'date'),
#         State('slider-graph', 'value')],
#         prevent_initial_call=True,
#    )
#    def download_frame(btn, models, variable, date, tstep):
#        """ Download PNG frame """
#        from tools import get_figure
#        from tools import download_image
#
#        ctx = dash.callback_context
#
#        if ctx.triggered:
#            button_id = ctx.triggered[0]["prop_id"].split(".")[0]
#            if button_id == 'btn-frame-download':
#                if tstep is None:
#                    tstep = 0
#                if DEBUG: print('PNG', btn, models, variable, date, tstep)
#                try:
#                    curdate = dt.strptime(date, '%Y-%m-%d').strftime('%Y%m%d')
#                except:
#                    curdate = date
#
#                return download_image(models, variable, curdate, tstep=int(tstep/FREQ))

@dash.callback(
    [Output('was-graph', 'children'),
     Output('was-previous', 'data')],
    [Input('was-apply', 'n_clicks'),
     Input('was-date-picker', 'date'),
     Input('was-slider-graph', 'value')],
    [State('was-dropdown', 'value'),
     State('variable-dropdown-forecast', 'value'),
     State('was-previous', 'data'),
     State({'tag': 'view-style', 'index': ALL}, 'active'),
     State({'tag': 'empty-map', 'index': ALL}, 'zoom'),
     State({'tag': 'empty-map', 'index': ALL}, 'center')],
    prevent_initial_call=True
    )
@cache.memoize(timeout=cache_timeout)
def update_was_figure(n_clicks, date, day, was, var, previous, view, zoom, center):
    """ Update Warning Advisory Systems maps """
    if DEBUG: print('WAS', n_clicks, date, day, was, previous, view, zoom, center)
    ctx = dash.callback_context
    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id != 'was-apply' and date is None and day is None:
            raise PreventUpdate

    from tools import get_was_figure
    from tools import get_figure
    if not zoom or previous != was:
        zoom = WAS[was]['zoom']
    else:
        zoom = zoom[0]
    if not center or previous != was:
        center = WAS[was]['center']
    else:
        center = center[0]

    previous = was

    print('WAS', was)
    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: print('SERVER: callback date {}'.format(date))
    else:
        date = end_date

    if DEBUG: print("WAS figure " + date, was, day)
    if was:
        view = list(STYLES.keys())[view.index(True)]
        geojson, legend, info = get_was_figure(was, day, selected_date=date)
        fig = get_figure(model=None, var=var, layer=[geojson, legend, info], view=view, zoom=zoom, center=center)
        return fig, previous

    raise PreventUpdate 


@dash.callback(
    Output('prob-graph', 'children'),
    [Input('prob-apply', 'n_clicks'),
     Input('prob-date-picker', 'date'),
     Input('prob-slider-graph', 'value')],
    [State('prob-dropdown', 'value'),
     State('variable-dropdown-forecast', 'value'),
     State({'tag': 'view-style', 'index': ALL}, 'active'),
     State({'tag': 'empty-map', 'index': ALL}, 'zoom'),
     State({'tag': 'empty-map', 'index': ALL}, 'center')],
    prevent_initial_call=False
)
@cache.memoize(timeout=cache_timeout)
def update_prob_figure(n_clicks, date, day, prob, var, view, zoom, center):
    """ Update Warning Advisory Systems maps """
    from tools import get_prob_figure
    from tools import get_figure
    if not zoom:
        zoom = None
    else:
        zoom = zoom[0]
    if not center:
        center = None
    else:
        center = center[0]
    if DEBUG: print('PROB', date, day, var, prob, var, view, zoom, center)
    ctx = dash.callback_context
    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id != 'prob-apply' and var is None and day is None:
            raise PreventUpdate

    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: print('SERVER: callback date {}'.format(date))
    else:
        date = end_date

    if prob:
        prob = prob.replace('prob_', '')
        view = list(STYLES.keys())[view.index(True)]
        geojson, colorbar, info = get_prob_figure(var, prob, day, selected_date=date)
        fig = get_figure(model=None, var=var, layer=[geojson, colorbar, info], view=view, zoom=zoom, center=center)
        if DEBUG: print("FIG", fig)
        return fig
#            return html.Div(
#                fig,
#                id='prob-graph',
#                className='graph-with-slider'
#                )
    raise PreventUpdate


@dash.callback(
    [Output({'tag': 'empty-tile-layer', 'index': ALL}, 'url'),
     Output({'tag': 'empty-tile-layer', 'index': ALL}, 'attribution')],
    [Input({'tag': 'view-style', 'index': ALL}, 'n_clicks')],
    [State({'tag': 'view-style', 'index': ALL}, 'active'),
     State({'tag': 'empty-tile-layer', 'index': ALL}, 'url')],
    prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def update_styles_button(*args):
    """ Function updating styles button """
    ctx = dash.callback_context
    if ctx.triggered:
        button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
        if DEBUG: print("BUTTON ID", str(button_id), type(button_id))
        if button_id['index'] in STYLES:
            active = args[-2]
            graphs = args[-1]
            num_graphs = len(graphs)
            # if DEBUG: print("CURRENT ARGS", str(args))
            # if DEBUG: print("NUM GRAPHS", num_graphs)

            res = [False for i in active]
            st_idx = list(STYLES.keys()).index(button_id['index'])
            if active[st_idx] is False:
                res[st_idx] = True
            url = [STYLES[button_id['index']]['url'] for x in range(num_graphs)]
            attr = [STYLES[button_id['index']]['attribution'] for x in range(num_graphs)]
            if DEBUG:
                print(res, url, attr)
            return url, attr
            # return [True if i == button_id['index'] else False for i in active]

    if DEBUG: print('NOTHING TO DO')
    raise PreventUpdate


@dash.callback(
    [Output({'tag': 'model-tile-layer', 'index': ALL}, 'url'),
     Output({'tag': 'model-tile-layer', 'index': ALL}, 'attribution'),
     Output({'tag': 'view-style', 'index': ALL}, 'active')],
    [Input({'tag': 'view-style', 'index': ALL}, 'n_clicks')],
    [State({'tag': 'view-style', 'index': ALL}, 'active'),
     State({'tag': 'model-tile-layer', 'index': ALL}, 'url')],
    prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def update_models_styles_button(*args):
    """ Function updating styles button """
    ctx = dash.callback_context
    if ctx.triggered:
        button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
        if DEBUG: print("BUTTON ID", str(button_id), type(button_id))
        if button_id['index'] in STYLES:
            active = args[-2]
            graphs = args[-1]
            num_graphs = len(graphs)
            # if DEBUG: print("CURRENT ARGS", str(args))
            # if DEBUG: print("NUM GRAPHS", num_graphs)

            res = [False for i in active]
            st_idx = list(STYLES.keys()).index(button_id['index'])
            if active[st_idx] is False:
                res[st_idx] = True
            url = [STYLES[button_id['index']]['url'] for x in range(num_graphs)]
            attr = [STYLES[button_id['index']]['attribution'] for x in range(num_graphs)]
            if DEBUG:
                print(res, url, attr)
            return url, attr, res
            # return [True if i == button_id['index'] else False for i in active]

    if DEBUG: print('NOTHING TO DO')
    raise PreventUpdate


#    @dash.callback(
#        [Output({'tag': 'model-tile-layer', 'index': MATCH}, 'url'),
#         Output({'tag': 'model-tile-layer', 'index': MATCH}, 'attribution')],
#        [Input('airports', 'n_clicks')] +
#        [Input({'tag': 'view-style', 'index': MATCH}, 'n_clicks')],
#        [State({'tag': 'view-style', 'index': MATCH}, 'active')],
#        prevent_initial_call=True
#    )
#    # @cache.memoize(timeout=cache_timeout)
#    def update_styles(*args):
#        """ Function updating map layout cartography """
#        ctx = dash.callback_context
#        active = args[-1]
#        # urls, attributions = args[-2], args[-1]
#        if DEBUG: print("CURRENT STYLES 2", str(active))
#
#        if ctx.triggered:
#            button_id = ctx.triggered[0]["prop_id"].split(".")[0]
#            if button_id in STYLES:
#                if DEBUG:
#                    print("STYLE", button_id)
#                url = STYLES[button_id]['url']
#                attr = STYLES[button_id]['attribution']
#                return url, attr
#            elif button_id == 'airports':
#                traces_list = [trace for trace in figures['data']]
#                for trace in figures['data']:
#                    if trace['name'] == 'Airports':
#                        figures['data'].remove(trace)
#                        return figures
#
#                fname = "/data/daily_dashboard/obs/airports/airports.dat"
#                df = pd.read_csv(fname)
#                clon = df['Longitude']
#                clat = df['Latitude']
#                calt = df['Altitude']
#                cname = df['Name']
#                cicao = df['ICAO']
#                ccity = df['City']
#                ccountry = df['Country']
#                figures['data'].append(
#                    dict(
#                        type='scattermapbox',
#                        name='Airports',
#                        below='',
#                        lon=clon,
#                        lat=clat,
#                        text=cname,
#                        customdata=cicao,
#                        #name='{} ({})'.format(cname, cicao),
#                        mode='markers',
#                        hovertemplate="lon: %{lon:.2f}<br>" +
#                                      "lat: %{lat:.2f}<br>" +
#                                      "name: %{text} (%{customdata})",
#                        opacity=0.6,
#                        showlegend=False,
#                        marker=dict(
#                            # autocolorscale=True,
#                            # symbol='square',
#                            color='#2B383E',
#                            opacity=0.6,
#                            size=10,
#                            showscale=False,
#                        )
#                    ),
#                )
#                return figures
#
#        raise PreventUpdate


@dash.callback(
    [Output('model-clicked-coords', 'data'),
     Output('current-popups-stored', 'data'),
     Output(dict(tag='model-map', index=ALL, n_clicks=ALL), 'children')],
    [Input(dict(tag='model-map', index=ALL, n_clicks=ALL), 'click_lat_lng')],
    [State(dict(tag='model-map', index=ALL, n_clicks=ALL), 'id'),
     State(dict(tag='model-map', index=ALL, n_clicks=ALL), 'children'),
     State('model-date-picker', 'date'),
     State('slider-graph', 'value'),
     State('variable-dropdown-forecast', 'value'),
     State('model-clicked-coords', 'data'),
     State('current-popups-stored', 'data')],
    prevent_initial_call=False
)
def models_popup(click_data, map_ids, res_list, date, tstep, var, coords, popups):
    from tools import get_single_point
    if DEBUG: print("CLICK:", str(click_data))
    if click_data.count(None) == len(click_data):
        raise PreventUpdate

    if DEBUG: print("MAPID:", str(map_ids), type(map_ids))
    # if DEBUG: print("RESLIST:", str(res_list), type(res_list))

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

    if popups is None:
        popups = {}

    trigger = orjson.loads(ctxt)
    if DEBUG: print('TRIGGER', trigger, type(trigger))

    curr_models = [m['index'] for m in map_ids]
    res = res_list
    if trigger in map_ids:
        model = trigger['index']
        mod_idx = map_ids.index(trigger)
        click = click_data[mod_idx]
        if tstep is None:
            tstep = 0
        else:
            tstep = int(tstep/FREQ)

        if DEBUG: print("MODEL", model, "CLICK", click, "DATE", date, "STEP", tstep, "MODIDX", mod_idx)

        if click is not None and model is not None:
            lat, lon = click
            try:
                selected_date = dt.strptime(date, '%Y%m%d')
            except:
                selected_date = dt.strptime(date, '%Y-%m-%d')

            if model in MODELS and MODELS[model]['start'] == 12:
                if tstep < 4:
                    date = (selected_date - timedelta(days=1)).strftime("%Y%m%d")
                    tstep = 4 + int(tstep)
                else:
                    date = selected_date.strftime("%Y%m%d")
                    tstep = int(tstep) - 4
                valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=(tstep+4)*FREQ)
            else:
                date = selected_date.strftime("%Y%m%d")
                valid_dt = dt.strptime(date, '%Y%m%d') + timedelta(hours=tstep*FREQ)

            if DEBUG: print("MODEL", model, "CLICK", click, "DATE", date, "STEP", tstep)
            value = get_single_point(model, date, int(tstep), var, lat, lon)
            if DEBUG: print("VALUE:", str(value))

            marker = dl.Popup(
                children=[
                    html.Div([
                        html.Span(html.P(
                            '{:.2f}'.format(value*VARS[var]['mul'])),
                            className='popup-map-value',
                        ),
                        html.Span([
                            html.B("Lat {:.2f}, Lon {:.2f}".format(lat, lon)), 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=dict(
                                    tag='ts-button',
                                    index=model,
                                    random=int(random()*100)
                                ),
                                n_clicks=0,
                                className='popup-ts-button'
                            )],
                            className='popup-map-body',
                        )],
                    )
                ],
                position=[lat, lon],
                autoClose=True, 
                closeOnEscapeKey=True,
                closeOnClick=False,
                closeButton=True,
                className='popup-map-point',
            )

            # if DEBUG: print("||||", res, "\n", res[mod_idx], type(res[mod_idx]))
            if DEBUG: print("||||", res[mod_idx][-1], type(res[mod_idx][-1]))
            if not res[mod_idx] or res[mod_idx] is None:
                if DEBUG: print("*** 1 ***")
                res[mod_idx] = marker
            elif res[mod_idx][-1] is None:
                if DEBUG: print("*** 2 ***")
                res[mod_idx][-1] = marker
#                 elif res[mod_idx][-1]['type'] == 'Popup':
#                     if DEBUG: print("*** 3 ***")
#                     res[mod_idx].pop(-1)
#                     res[mod_idx].append(marker)
            else:
                if DEBUG: print("*** 3 ***")
                res[mod_idx].append(marker)
            if not coords or coords is None:
                coords = { model: [lat, lon] }
            else:
                coords[model] = [lat, lon]

            popups[model] = sum([res[mod_idx][i]['type'] == 'Popup' and 1 or 0
                    for i in range(len(res[mod_idx])) if 'type' in
                    res[mod_idx][i]]) + 1

            if DEBUG: print("COORDS:", str(coords))
            if DEBUG: print("POPUPS:", str(popups))
            return coords, popups, res

    return {}, {}, dash.no_update
    # raise PreventUpdate


# retrieve timeseries according to coordinates selected
@dash.callback(
    [Output('ts-modal', 'children'),
     Output('ts-modal', 'is_open'),
     Output({ 'tag': 'ts-button', 'index': ALL, 'random': ALL }, 'n_clicks')],
    [Input({ 'tag': 'ts-button', 'index': ALL, 'random': ALL }, 'n_clicks')],
    [State('model-dropdown', 'value'),
     State('model-date-picker', 'date'),
     State('variable-dropdown-forecast', 'value'),
     State('model-clicked-coords', 'data'),
     State('current-popups-stored', 'data')],
     prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def show_timeseries(ts_button, mod, date, variable, coords, popups):
    """ Renders model comparison timeseries """
    from tools import get_timeseries

    ctx = dash.callback_context
    if ctx.triggered:
        button_id = orjson.loads(ctx.triggered[0]["prop_id"].split(".")[0])
        if DEBUG: print("BUTTONID:", mod, str(button_id), str(ts_button))
        if DEBUG: print('SHOW TS COORDS:', coords, type(coords), mod)
        ts_popups = 0
        for m in popups:
            if DEBUG: print("___", m, "___")
            ts_popups += popups[m]
            if m == button_id['index']:
                break

        if ts_popups == 0:
            ts_popups = 1

        if button_id['tag'] == 'ts-button' and ts_button[ts_popups-1] >= 1:
            lat, lon = coords[button_id['index']]

            if lat is None or lon is None:
                raise PreventUpdate

            if DEBUG: print('SHOW TS """""', mod, lat, lon)
            figure = get_timeseries(mod, date, variable, lat, lon, forecast=True)
            figure.update_layout(MODEBAR_LAYOUT_TS)
            ts_body = dbc.ModalBody(
                dcc.Graph(
                    id='timeseries-modal',
                    figure=figure,
                    config=MODEBAR_CONFIG_TS
                )
            )

            return ts_body, True, [0 for _ in ts_button]
        return dash.no_update, False, [0 for _ in ts_button]
    return dash.no_update, False, [0 for _ in ts_button]
    # raise PreventUpdate


# start/stop animation
@dash.callback(
    [Output('slider-interval', 'disabled'),
     Output('slider-interval', 'n_intervals'),
     Output('open-timeseries', 'style'),
     #Output('div-collection', 'children'),
     ],
    [Input('btn-play', 'n_clicks'),
     Input('btn-stop', 'n_clicks')],
    [State('slider-interval', 'disabled'),
     State('slider-graph', 'value')],
    prevent_initial_call=True
    )
def start_stop_autoslider(n_play, n_stop, disabled, value):
    """ Play/Pause map animation """
    ctx = dash.callback_context
    if DEBUG: print("VALUE", value)
    if not value:
        value = 0

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id == 'btn-play' and disabled:
            ts_style = { 'display': 'none' }
            return not disabled, int(value/FREQ), ts_style
        elif button_id == 'btn-stop' and not disabled:
            ts_style = { 'display': 'block' }
            return not disabled, int(value/FREQ), ts_style

    raise PreventUpdate


@dash.callback(
    Output('slider-graph', 'value'),
    [Input('slider-interval', 'n_intervals')],
    prevent_initial_call=True
)
@cache.memoize(timeout=cache_timeout)
def update_slider(n):
    """ Update slider value according to the number of intervals """
    if DEBUG: print('SERVER: updating slider-graph ' + str(n))
    if not n:
        return
    if n >= 24:
        tstep = int(round(24*math.modf(n/24)[0], 0))
    else:
        tstep = int(n)
    if DEBUG: print('SERVER: updating slider-graph ' + str(tstep*FREQ))
    return tstep*FREQ


@dash.callback(
    Output('forecast-tab', 'children'),
    [Input('models-apply', 'n_clicks'),
     Input('prob-apply', 'n_clicks'),
     Input('was-apply', 'n_clicks')],
    [State({'tag': 'tab-name', 'index': ALL}, 'id')],
    prevent_initial_call=True
    )
@cache.memoize(timeout=cache_timeout)
def update_tab_content(models_clicks, prob_clicks, was_clicks, curtab):
    ctx = dash.callback_context

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id not in ('models-apply', 'prob-apply', 'was-apply'):
            raise PreventUpdate

        if DEBUG: print("::::::::::::", len(curtab), curtab[0]['index'])
        curtab_name = curtab[0]['index']

        nexttab_name = button_id.replace('-apply', '')
        if DEBUG: print("::::::::::::", curtab_name, nexttab_name)
        if curtab_name != nexttab_name:
            return tab_forecast(nexttab_name)

        raise PreventUpdate

    raise PreventUpdate


#    app.clientside_callback(
#        """
#        function((figures, models, variable, date, tstep)) {
#            return figures;
#        }
#        """,
#
#         Output('graph-collection', 'children'),
#         Input('clientside-graph-collection', 'data'),
#    )


@dash.callback(
    Output('graph-collection', 'children'),
    [Input('models-apply', 'n_clicks'),
     Input('slider-graph', 'value'),
     Input('model-date-picker', 'date')],
    [State('model-dropdown', 'value'),
     State('variable-dropdown-forecast', 'value'),
     State('slider-interval', 'disabled'),
     State({'tag': 'view-style', 'index': ALL}, 'active'),
     State({'tag': 'model-map', 'index': ALL}, 'zoom'),
     State({'tag': 'model-map', 'index': ALL}, 'center'),
     State({'tag': 'model-map', 'index': ALL}, 'id'),
     ],
    prevent_initial_call=False
    )
@cache.memoize(timeout=cache_timeout)
def update_models_figure(n_clicks, tstep, date, model, variable, static, view, zoom, center, ids):
    """ Update mosaic of maps figures according to all parameters """
    ctx = dash.callback_context

    if ctx.triggered:
        button_id = ctx.triggered[0]["prop_id"].split(".")[0]
        if button_id != 'models-apply' and tstep is None and date is None:
            raise PreventUpdate
    elif tstep is None and date is None:
        raise PreventUpdate

    from tools import get_figure
    if DEBUG: print('SERVER: calling figure from picker callback')

    st_time = time.time()

    # if DEBUG: print('SERVER: interval ' + str(n))
    if DEBUG: print('SERVER: tstep ' + str(tstep))

    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: print('SERVER: callback date {}'.format(date))
    else:
        date = end_date

    if model is None:
        model = DEFAULT_MODEL

    if variable is None:
        variable = DEFAULT_VAR

    if tstep is not None:
        tstep = int(tstep/FREQ)
    else:
        tstep = 0

    if DEBUG: print('SERVER: tstep calc ' + str(tstep))
    if DEBUG: print('#### IDS, ZOOM, CENTER:', ids, zoom, center)

    curr_mods = { i['index']: { 'zoom': z, 'center': c }
            for i, z, c in zip(ids, zoom, center) }

    figures = []

    ncols, nrows = calc_matrix(len(model))

    if not zoom:
        zoom = [None]
    if not center:
        center = [None]

    if len(model) != len(zoom):
        if DEBUG: print("##############", len(model), len(zoom), "**********")
        zoom = [None for item in model]
        center = [None for item in model]

    if DEBUG: print('#### ZOOM, CENTER:', zoom, center, model, ncols, nrows)
    view = list(STYLES.keys())[view.index(True)]
#         for mod in model:
#             mod_zoom = mod in curr_mods and curr_mods[mod]['zoom'] or None
#             mod_center = mod in curr_mods and curr_mods[mod]['center'] or None
    for mod, mod_zoom, mod_center in zip(model, zoom, center):
        if DEBUG: print("MOD", mod, "ZOOM", mod_zoom, "CENTER", mod_center, 'VIEW', view)

        figure = get_figure(mod, variable, date, tstep,
                static=static, aspect=(nrows, ncols), view=view,
                center=mod_center, zoom=mod_zoom)

        figure.style = {
            'height': '{}vh'.format(int(GRAPH_HEIGHT/nrows)),
        }

        # add n_clicks to id dict to reset the state of map, for centering
        figure.id['n_clicks'] = n_clicks
        # if DEBUG: print('FIGURE KEYS 2', figure.id)
        if DEBUG: print('STATIC', static, int(GRAPH_HEIGHT)/nrows)
        figures.append(figure)

    if DEBUG:
        for f in figures:
            print("************", f.id, f.style, "**************")

    res = [
        dbc.Row(
            [
                dbc.Col(
                    figures[row+col+(row*(ncols-1))],
                    width=int(12/ncols),
                )
                for col in range(ncols)
                if len(figures) > row+col+(row*(ncols-1))
            ],
            align="start",
            no_gutters=True,
        ) for row in range(nrows)
    ]
    if DEBUG: print(ncols, nrows, len(res), [(type(i), len(i.children)) for i in res])
    if DEBUG: print("**** REQUEST TIME", str(time.time() - st_time))
    return res
