import copy
import time
import webbrowser
from pathlib import Path
from threading import Thread
from typing import cast

import dash_cytoscape as cyto
from dash import Dash, Input, Output, State, dcc, html
from dash.exceptions import PreventUpdate

import lang_main.io
from lang_main.analysis import graphs

target = '../results/test_20240529/Pipe-Token_Analysis_Step-1_build_token_graph.pkl'
p = Path(target).resolve()
ret = lang_main.io.load_pickle(p)
tk_graph = cast(graphs.TokenGraph, ret[0])
tk_graph_filtered = graphs.filter_graph_by_edge_weight(tk_graph, 150, None)
tk_graph_filtered = graphs.filter_graph_by_node_degree(tk_graph_filtered, 1, None)
cyto_data_base, weight_data = graphs.convert_graph_to_cytoscape(tk_graph_filtered)

MIN_WEIGHT = weight_data['min']
MAX_WEIGHT = weight_data['max']


cyto.load_extra_layouts()
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = Dash(__name__, external_stylesheets=external_stylesheets)

cose_layout = {
    'name': 'cose',
    'nodeOverlap': 500,
    'refresh': 20,
    'fit': True,
    'padding': 20,
    'randomize': False,
    'componentSpacing': 1.2,
    'nodeRepulsion': 1000,
    'edgeElasticity': 1000,
    'idealEdgeLength': 100,
    'nestingFactor': 1.2,
    'gravity': 50,
    'numIter': 3000,
    'initialTemp': 2000,
    'coolingFactor': 0.7,
    'minTemp': 1.0,
    'nodeDimensionsIncludeLabels': True,
}

cose_bilkent_layout = {
    'name': 'cose-bilkent',
    'nodeDimensionsIncludeLabels': True,
    'idealEdgeLength': 100,
    'edgeElasticity': 0.45,
    'nodeRepulsion': 10000,
    'nestingFactor': 0.1,
    'gravity': 0.25,
    'numIter': 2500,
    'initialTemp': 1000,
    'coolingFactor': 0.95,
    'minTemp': 1.0,
}

cola_layout = {
    'name': 'cola',
    'nodeDimensionsIncludeLabels': True,
    'nodeSpacing': 30,
    'edgeLength': 45,
    'animate': True,
    'centerGraph': True,
    'randomize': False,
}

my_stylesheet = [
    # Group selectors
    {
        'selector': 'node',
        'style': {
            'shape': 'circle',
            'content': 'data(label)',
            'background-color': '#B10DC9',
            'border-width': 2,
            'border-color': 'black',
            'border-opacity': 1,
            'opacity': 1,
            'color': 'black',
            'text-opacity': 1,
            'font-size': 12,
            'z-index': 9999,
        },
    },
    {
        'selector': 'edge',
        'style': {
            #'width': f'mapData(weight, {MIN_WEIGHT}, {MAX_WEIGHT}, 1, 10)',
            # 'width': """function(ele) {
            #     return ele.data('weight');
            # """,
            'curve-style': 'bezier',
            'line-color': 'grey',
            'line-style': 'solid',
            'line-opacity': 1,
        },
    },
    # Class selectors
    # {'selector': '.red', 'style': {'background-color': 'red', 'line-color': 'red'}},
    # {'selector': '.triangle', 'style': {'shape': 'triangle'}},
]

layout = html.Div(
    [
        html.Button('Trigger JS Weight', id='test_js_weight'),
        html.Div(id='output'),
        html.Div(
            [
                html.H2('Token Graph', style={'margin': 0}),
                html.Button(
                    'Reset Default',
                    id='bt-reset',
                    style={
                        'marginLeft': 'auto',
                        'width': '300px',
                    },
                ),
            ],
            style={
                'display': 'flex',
                'marginBottom': '1em',
            },
        ),
        html.H3('Layout'),
        dcc.Dropdown(
            id='layout_choice',
            options=[
                'cose',
                'cola',
                'euler',
                'random',
            ],
            value='cose',
            clearable=False,
        ),
        html.Div(
            [
                html.H3('Graph Filter'),
                dcc.Input(
                    id='weight_min',
                    type='number',
                    min=MIN_WEIGHT,
                    max=MAX_WEIGHT,
                    step=1,
                    placeholder=f'Minimum edge weight: {MIN_WEIGHT} - {MAX_WEIGHT}',
                    debounce=True,
                    style={'width': '40%'},
                ),
                dcc.Input(
                    id='weight_max',
                    type='number',
                    min=MIN_WEIGHT,
                    max=MAX_WEIGHT,
                    step=1,
                    placeholder=f'Maximum edge weight: {MIN_WEIGHT} - {MAX_WEIGHT}',
                    debounce=True,
                    style={'width': '40%'},
                ),
                html.H3('Graph'),
                html.Button('Re-Layout', id='trigger_relayout'),
                html.Div(
                    [
                        cyto.Cytoscape(
                            id='cytoscape-graph',
                            style={'width': '100%', 'height': '600px'},
                            layout=cose_layout,
                            stylesheet=my_stylesheet,
                            elements=cyto_data_base,
                            zoom=1,
                        ),
                    ],
                    style={
                        'border': '3px solid black',
                        'borderRadius': '25px',
                        'marginTop': '1em',
                        'marginBottom': '2em',
                        'padding': '7px',
                    },
                ),
            ],
            style={'marginTop': '1em'},
        ),
    ],
    style={'margin': '2em'},
)


app.layout = layout


@app.callback(
    Output('cytoscape-graph', 'layout', allow_duplicate=True),
    Input('layout_choice', 'value'),
    prevent_initial_call=True,
)
def update_layout_internal(layout_choice):
    # return {'name': layout_choice}
    return cose_layout
    # return cose_bilkent_layout
    # return cola_layout


@app.callback(
    Output('cytoscape-graph', 'zoom'),
    Output('cytoscape-graph', 'elements', allow_duplicate=True),
    Output('weight_min', 'value'),
    Output('weight_max', 'value'),
    Input('bt-reset', 'n_clicks'),
    prevent_initial_call=True,
)
def reset_layout(n_clicks):
    return (1, cyto_data_base, None, None)


# update edge weight
@app.callback(
    Output('cytoscape-graph', 'elements', allow_duplicate=True),
    Input('weight_min', 'value'),
    Input('weight_max', 'value'),
    prevent_initial_call=True,
)
def update_edge_weight(weight_min, weight_max):
    if not any([weight_min, weight_max]):
        return cyto_data_base

    if weight_min is None:
        weight_min = MIN_WEIGHT
    if weight_max is None:
        weight_max = MAX_WEIGHT
    tk_graph_filtered = graphs.filter_graph_by_edge_weight(
        tk_graph,
        weight_min,
        weight_max,
    )
    tk_graph_filtered = graphs.filter_graph_by_node_degree(
        tk_graph_filtered,
        1,
        None,
    )
    cyto_data, _ = graphs.convert_graph_to_cytoscape(tk_graph_filtered)
    return cyto_data


# app.clientside_callback(
#     """
#     function(n_clicks, layout) {
#         let threshold = 1000;
#         layout.edgeLength = function(edge) {
#             let weight = edge.data().weight;
#             let length;
#             if (weight > threshold) {
#                 length = 10;
#             } else {
#                 length = 1000 / edge.data().weight;
#                 length = Math.max(20, length);
#             }
#             return length;
#         };
#         cy.layout(layout).run();
#         return layout;
#     }
#     """,
#     Output('cytoscape-graph', 'layout', allow_duplicate=True),
#     Input('test_js', 'n_clicks'),
#     State('cytoscape-graph', 'layout'),
#     prevent_initial_call=True,
# )

app.clientside_callback(
    """
    function(n_clicks, layout) {
        layout.edgeElasticity = function(edge) {
            return edge.data().weight * 0.05;
        };
        layout.idealEdgeLength = function(edge) {
            return edge.data().weight * 0.4;
        };
        cy.layout(layout).run();
        return layout;
    }
    """,
    Output('cytoscape-graph', 'layout', allow_duplicate=True),
    Input('trigger_relayout', 'n_clicks'),
    State('cytoscape-graph', 'layout'),
    prevent_initial_call=True,
)

app.clientside_callback(
    """
    function(n_clicks, stylesheet) {
        function edge_weight(ele) {
            let threshold = 1000;
            let weight = ele.data('weight');
            if (weight > threshold) {
                weight = 12;
            } else {
                weight = weight / threshold * 10;
                weight = Math.max(1, weight);
            }
            return weight;
        }
        stylesheet[1].style.width = edge_weight;
        cy.style(stylesheet).update();
        return stylesheet;
    }
    """,
    Output('cytoscape-graph', 'stylesheet'),
    Input('test_js_weight', 'n_clicks'),
    State('cytoscape-graph', 'stylesheet'),
    prevent_initial_call=False,
)


def _start_webbrowser():
    host = '127.0.0.1'
    port = '8050'
    adress = f'http://{host}:{port}/'
    time.sleep(2)
    webbrowser.open_new(adress)


def main():
    webbrowser_thread = Thread(target=_start_webbrowser, daemon=True)
    webbrowser_thread.start()
    app.run(debug=True)


if __name__ == '__main__':
    main()
