import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
import dash_daq as daq
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from ..api.sqlalchemy_declarative import stravaSummary, stravaSamples, stravaBestSamples, athlete, withings
from ..api.database import engine
from ..app import app
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from ..utils import config, stryd_credentials_supplied
from sqlalchemy import func, or_
import math
from ..api.strydAPI import get_training_distribution

# pre_style = {"backgroundColor": "#ddd", "fontSize": 20, "padding": "10px", "margin": "10px"}
hidden_style = {"display": "none"}
hidden_inputs = html.Div(id="hidden-inputs", style=hidden_style, children=[])

transition = int(config.get('dashboard', 'transition'))

white = config.get('oura', 'white')
teal = config.get('oura', 'teal')
light_blue = config.get('oura', 'light_blue')
dark_blue = config.get('oura', 'dark_blue')
orange = config.get('oura', 'orange')
ftp_color = 'rgb(100, 217, 236)'

def create_power_curve_kpis(interval, all, L90D, l6w, last, pr):
    return \
        html.Div(className='row align-items-center text-center', children=[
            ### Interval KPI ###
            html.Div(className='col-auto', children=[
                html.H4(id='power-curve-title', className='mb-0',
                        children='Power Curve {}'.format(timedelta(seconds=interval))),
            ]),
            dbc.Tooltip(
                '''A high power output for short periods of time (10 seconds) can contribute to improved performance across your entire Power Duration Curve. To improve musle power, focus on VO2 Max Intervals, Hill / Track Repeats and Supplemental Training.

                Fatigue resistance directly reflects your ability to run at close to maximal effort for your goal race distance. To improve fatigue fesistance, focus on Long Runs, High Volume Easy Runs, and Aerobic Threshold Tempo Runs.

                Building up your endurance with longer runs helps improve your body's ability to sustain efforts for long durations. To improve endurance, focus on Aerobic Threshold Tempo Runs, Race Specific Training and Long Runs.''',
                target="power-curve-title"),

            ### All KPI ###
            html.Div(id='all-kpi', className='col-lg-12 mb-0', children=[
                html.H5('All Time {}'.format(all),
                        style={'display': 'inline-block',  # 'fontWeight': 'bold',
                               'color': white, 'backgroundColor': dark_blue, 'marginTop': '0',
                               'marginBottom': '0',
                               'borderRadius': '.3rem'}),
            ]),
            ### L90D KPI ###
            html.Div(id='L90D-kpi', className='col-lg-12 mb-0', children=[
                html.H5('L90D {}'.format(L90D if pr == '' else pr),
                        style={'display': 'inline-block',  # 'fontWeight': 'bold',
                               'color': 'rgb(46,46,46)', 'backgroundColor': white if pr == '' else orange,
                               'marginTop': '0',
                               'marginBottom': '0',
                               'borderRadius': '.3rem'}),
            ]),
            ### L6W KPI ###
            html.Div(id='l6w-kpi', className='col-lg-12 mb-0', children=[
                html.H5('L6W {}'.format(l6w),
                        style={'display': 'inline-block',  # 'fontWeight': 'bold',
                               'color': white, 'backgroundColor': light_blue, 'marginTop': '0',
                               'marginBottom': '0',
                               'borderRadius': '.3rem'}),
            ]),
            ### Last KPI ###
            html.Div(id='last-kpi', className='col-lg-12 mb-0', children=[
                html.H5('L30D {}'.format(last),
                        style={'display': 'inline-block',  # 'fontWeight': 'bold',
                               'color': 'rgb(46,46,46)', 'backgroundColor': teal, 'marginTop': '0',
                               'marginBottom': '0',
                               'borderRadius': '.3rem'}),
            ]),

        ]),

def get_workout_title(activity_id=None):
    min_non_warmup_workout_time = app.session.query(athlete).filter(
        athlete.athlete_id == 1).first().min_non_warmup_workout_time
    activity_id = app.session.query(stravaSummary.activity_id).filter(stravaSummary.type.ilike('%ride%'),
                                                                      stravaSummary.elapsed_time > min_non_warmup_workout_time).order_by(
        stravaSummary.start_date_utc.desc()).first()[0] if not activity_id else activity_id
    df_samples = pd.read_sql(
        sql=app.session.query(stravaSamples).filter(stravaSamples.activity_id == activity_id).statement,
        con=engine,
        index_col=['timestamp_local'])

    app.session.remove()

    return [html.H6(datetime.strftime(df_samples['date'][0], "%A %b %d, %Y"), style={'height': '50%'}),
            html.H6(df_samples['act_name'][0], style={'height': '50%'})]

def power_profiles(interval, activity_type='ride', power_unit='mmp', group='M'):
    activity_type = '%' + activity_type + '%'

    # Filter on interval passed
    df_best_samples = pd.read_sql(
        sql=app.session.query(stravaBestSamples).filter(stravaBestSamples.type.ilike(activity_type),
                                                        stravaBestSamples.interval == interval).statement, con=engine,
        index_col=['timestamp_local'])

    app.session.remove()
    if len(df_best_samples) < 1:
        return {}

    # Create columns for x-axis
    df_best_samples['power_profile_dategroup'] = df_best_samples.index.to_period(group).to_timestamp()

    df = df_best_samples[['activity_id', power_unit, 'power_profile_dategroup', 'interval']]
    df = df.loc[df.groupby('power_profile_dategroup')[power_unit].idxmax()]

    figure = {
        'data': [
            go.Bar(
                x=df['power_profile_dategroup'],
                y=df[power_unit],
                customdata=[
                    '{}_{}_{}'.format(df.loc[x]['activity_id'], df.loc[x]['interval'].astype('int'),
                                      interval) for x in df.index],
                # add fields to text so data can go through clickData
                text=[
                    '{:.2f} W/kg'.format(x) if power_unit == 'watts_per_kg' else '{:.0f} W'.format(
                        x)
                    for x in
                    df[power_unit]],
                hoverinfo='x+text',
                marker=dict(
                    color=[orange if x == df[power_unit].max() else light_blue for x in
                           df[power_unit]],
                )
            )
        ],
        'layout': go.Layout(
            # transition=dict(duration=transition),
            font=dict(
                size=10,
                color=white
            ),
            height=400,
            xaxis=dict(
                showticklabels=True,
                tickformat="%b '%y"
            ),
            yaxis=dict(
                showgrid=True,
                gridcolor='rgb(73, 73, 73)',
            ),
            margin={'l': 25, 'b': 25, 't': 5, 'r': 20},

        )
    }

    return figure

def stryd_training_distributions():
    current_ftp_w = app.session.query(stravaSummary).filter(stravaSummary.type.ilike('run')).order_by(
        stravaSummary.start_date_utc.desc()).first().ftp

    # Data points for Power Curve Training Disribution
    TD_df_L90D = pd.read_sql(
        sql=app.session.query(
            func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
            stravaBestSamples.interval, stravaBestSamples.time_interval,
            stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
        ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.type.ilike('run'),
                                                      stravaBestSamples.timestamp_local >= (
                                                              datetime.now() - timedelta(days=90))
                                                      ).statement, con=engine)

    app.session.remove()

    ### STRYD TRAINING DISTRIBUTION USES CURRENT WEIGHT WHEN CALCULATING W/KG ###
    ### TRAINING DIST BARS WILL DO THE SAME TO BETTER ALIGN WITH PERCENTILES ###
    ### ACTUAL DATA SHOWN IN POWER CURVE WILL BE BASED ON FTP AT THE TIME OF RECORDING FOR BETTER ACCURACY ###

    # Make room on canvas for training dist bars
    td = get_training_distribution()
    data = [
        # Fitness
        go.Bar(
            xaxis='x', yaxis='y', customdata=['ignore'], x=[100],
            y=['Fitness'],
            # width=.5,
            marker=dict(color=light_blue),
            showlegend=False,
            text=[
                'Fitness: {:.2f} W/kg
Stryd Avg: {:.2f} W/kg
Percentile: {:.0%}'.format(
                    td["attr"]["fitness"],  # Use value directly from stryd api call
                    td["percentile"]["median_fitness"],
                    td["percentile"]["fitness"])
            ],
            hoverinfo='text',
            orientation='h'),

        # Muscle Power
        go.Bar(
            xaxis='x', yaxis='y', customdata=['ignore'], x=[100],
            y=['Muscle Power'],
            # width=.5,
            marker=dict(color=light_blue),
            showlegend=False,
            text=[
                'Max 10 Sec Power: {:.2f} W/kg
Stryd Avg: {:.2f} W/kg
Percentile: {:.0%}'.format(
                    td["attr"]["muscle_power"],  # Use value directly from stryd api call
                    td["percentile"]["median_muscle_power"],
                    td["percentile"]["muscle_power"]),
            ],
            hoverinfo='text',
            orientation='h'),

        # Fatigue Resistance
        go.Bar(
            xaxis='x', yaxis='y', customdata=['ignore'], x=[100],
            y=['Fatigue'],
            # width=.5,
            marker=dict(color=light_blue),
            showlegend=False,
            text=[
                'Longest 100% CP: {}
Stryd Avg: {}
Percentile: {:.0%}'.format(
                    timedelta(seconds=int(td["attr"]["fatigue_resistance"])),  # Use value directly from stryd api call
                    timedelta(seconds=int(td["percentile"]["median_fatigue_resistance"])),
                    td["percentile"]["fatigue_resistance"]),
            ],
            hoverinfo='text',
            orientation='h'),
        # Endurance
        go.Bar(
            xaxis='x', yaxis='y', customdata=['ignore'], x=[100],
            y=['Endurance'],
            # width=.5,
            marker=dict(color=light_blue),
            showlegend=False,
            text=[
                'Longest 50% CP: {}
Stryd Avg: {}
Percentile: {:.0%}'.format(
                    timedelta(seconds=int(td["attr"]["endurance"])),  # Use value directly from stryd api call
                    timedelta(seconds=int(td["percentile"]["median_endurance"])),
                    td["percentile"]["endurance"])
            ],
            hoverinfo='text',
            orientation='h'),
    ]

    shapes = [
        # Training distribution charts lines
        (dict(type='line', xref='x', yref='y', x0=50, x1=50, y0=0.05, y1=.35,
              line=dict(color=white, width=1, dash="dot", ), ),
         dict(type='line', xref='x', yref='y', x0=td['percentile']['fitness'] * 100,
              x1=td['percentile']['fitness'] * 100, y0=0, y1=.4,
              line=dict(color=white, width=2, ), )
         ),

        (dict(type='line', xref='x', yref='y', x0=50, x1=50, y0=0.05, y1=.35,
              line=dict(color=white, width=1, dash="dot", ), ),
         dict(type='line', xref='x', yref='y', x0=td['percentile']['muscle_power'] * 100,
              x1=td['percentile']['muscle_power'] * 100, y0=0, y1=.4,
              line=dict(color=white, width=2, ), )),

        (dict(type='line', xref='x', yref='y', x0=50, x1=50, y0=0.05, y1=.35,
              line=dict(color=white, width=1, dash="dot", ), ),
         dict(type='line', xref='x', yref='y', x0=td['percentile']['fatigue_resistance'] * 100,
              x1=td['percentile']['fatigue_resistance'] * 100, y0=0, y1=.4,
              line=dict(color=white, width=2, ), )),

        (dict(type='line', xref='x', yref='y', x0=50, x1=50, y0=0.05, y1=.35,
              line=dict(color=white, width=1, dash="dot", ), ),

         dict(type='line', xref='x', yref='y', x0=td['percentile']['endurance'] * 100,
              x1=td['percentile']['endurance'] * 100, y0=0, y1=.4,
              line=dict(color=white, width=2)
              ))
    ]
    annotations = [
        go.layout.Annotation(
            font={'size': 12, 'color': white}, x=50, y=.7, xref="x", yref="y",
            text='Fitness: {:.0f} W'.format(current_ftp_w),
            showarrow=False,
        ),

        go.layout.Annotation(
            font={'size': 12, 'color': white}, x=50, y=.7, xref="x", yref="y",
            text='Muscle Power: {:.0f} W'.format(TD_df_L90D.loc[10]['mmp']),
            showarrow=False,
        ),

        go.layout.Annotation(
            font={'size': 12, 'color': white}, x=50, y=.7, xref="x", yref="y",
            text='Fatigue Resistance: {}'.format(timedelta(seconds=int(td["attr"]["fatigue_resistance"]))),

            showarrow=False,
        ),
        go.layout.Annotation(
            font={'size': 12, 'color': white}, x=50, y=.7, xref="x", yref="y",
            text='Endurance: {}'.format(timedelta(seconds=int(td["attr"]["endurance"]))),
            showarrow=False,
        ),

    ]
    layouts = []
    for i in range(4):
        layouts.append(
            go.Layout(
                font=dict(size=10, color=white),
                shapes=[shapes[i][0], shapes[i][1]],
                annotations=[annotations[i]],
                height=100,
                xaxis=dict(showgrid=False, showline=False, zeroline=False, showticklabels=False, range=[0, 100]),
                margin={'l': 0, 'b': 20, 't': 0, 'r': 0},
                showlegend=False,
                autosize=True,
                hovermode='x',
                yaxis=dict(showgrid=False, showline=False, zeroline=False, showticklabels=False, range=[0, 1]),
            )
        )

    graphs = []
    for i in range(4):
        graphs.append(
            html.Div(className='col-lg-12', style={'paddingRight': 0, 'paddingLeft': 0}, children=[
                dcc.Graph(
                    config={
                        'displayModeBar': False
                    },
                    style={'height': '100%'},
                    figure={
                        'data': [data[i]],
                        'layout': layouts[i]}
                )
            ])
        )

    return graphs

def power_curve(activity_type='ride', power_unit='mmp', last_id=None, height=400, time_comparison=None,
                intensity='all'):
    # TODO: Add power curve model once sweatpy has been finished
    # https://sweatpy.gssns.io/features/Power%20duration%20modelling/#comparison-of-power-duration-models
    activity_type = '%' + activity_type + '%'

    max_interval = app.session.query(
        func.max(stravaBestSamples.interval).label('interval')).filter(
        stravaBestSamples.type.ilike(activity_type)).first()[0]

    act_dict = pd.read_sql(
        sql=app.session.query(stravaBestSamples.activity_id, stravaBestSamples.act_name).distinct().statement,
        con=engine,
        index_col='activity_id').to_dict()

    # Data points for Power Curve Training Disribution
    # Join in weight at the time of workout for calculating FTP_W/kg at point in time (of workout)
    TD_df_L90D = pd.read_sql(
        sql=app.session.query(
            func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
            stravaBestSamples.interval, stravaBestSamples.time_interval,
            stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
            stravaSummary.weight
        ).join(stravaSummary, stravaBestSamples.activity_id == stravaSummary.activity_id, isouter=True).group_by(
            stravaBestSamples.interval).filter(stravaBestSamples.type.ilike(activity_type),
                                               stravaBestSamples.timestamp_local >= (
                                                       datetime.now() - timedelta(days=90))
                                               ).statement, con=engine, index_col='interval')

    # Don't show TD date when plotting a small chart ( <400 height)
    td_data_exists = len(TD_df_L90D) > 0 and height >= 400
    # If training distribution data exists
    if td_data_exists:
        TD_df_L90D['act_name'] = TD_df_L90D['activity_id'].map(act_dict['act_name'])
        TD_df_L90D['ftp_wkg'] = TD_df_L90D['ftp'] / (TD_df_L90D['weight'] * 0.453592)

        ### Calculations for L90D workouts based on todays weights for stryd comparisons ###
        # Stryd uses "Current FTP" and "Current Weight" across all workouts for last 90 days for their power curve
        # To align with their percentiles, we use these metrics (pulled from api)

        # For our actual power curve chart, we will use ftp as of when the workout was done for a more accurate chart
        fatigue_df = TD_df_L90D.loc[TD_df_L90D[
            TD_df_L90D[power_unit] > TD_df_L90D['ftp_wkg' if power_unit == 'watts_per_kg' else 'ftp']].index.max()]
        fatigue_ftp = fatigue_df.ftp_wkg if power_unit == 'watts_per_kg' else fatigue_df.ftp
        endurance_df = TD_df_L90D.loc[TD_df_L90D[TD_df_L90D[power_unit] > (
                TD_df_L90D['ftp_wkg' if power_unit == 'watts_per_kg' else 'ftp'] / 2)].index.max()]
        endurance_ftp = endurance_df.ftp_wkg / 2 if power_unit == 'watts_per_kg' else endurance_df.ftp / 2

        # Muscle power is just best 10 second power
        muscle_power = TD_df_L90D.loc[10][power_unit]

        TD_df_at = pd.read_sql(
            sql=app.session.query(
                func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
                stravaBestSamples.interval, stravaBestSamples.time_interval,
                stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
            ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.type.ilike(activity_type),
                                                          or_(stravaBestSamples.interval == 10,
                                                              stravaBestSamples.interval == int(fatigue_df.name),
                                                              stravaBestSamples.interval == int(endurance_df.name)),
                                                          ).statement, con=engine, index_col='interval')

        muscle_power_best = True if TD_df_at.loc[10][power_unit] == muscle_power else False
        endurance_best = True if TD_df_at.loc[endurance_df.name][power_unit] == endurance_df[power_unit] else False
        fatigue_best = True if TD_df_at.loc[fatigue_df.name][power_unit] == fatigue_df[power_unit] else False

    # 1 second intervals from 0-60 seconds
    interval_lengths = [i for i in range(1, 61)]
    # 5 second intervals from 1:15 - 20:00 mins
    interval_lengths += [i for i in range(65, 1201, 5)]
    # 30 second intervals for everything after 20 mins
    interval_lengths += [i for i in range(1230, (int(math.floor(max_interval / 10.0)) * 10) + 1, 30)]

    all_best_interval_df = pd.read_sql(
        sql=app.session.query(
            func.max(stravaBestSamples.mmp).label('mmp'),
            stravaBestSamples.activity_id, stravaBestSamples.interval, stravaBestSamples.time_interval,
            stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
        ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.interval.in_(interval_lengths),
                                                      stravaBestSamples.type.ilike(activity_type)).statement,
        con=engine, index_col='interval')
    all_best_interval_df['act_name'] = all_best_interval_df['activity_id'].map(act_dict['act_name'])

    L90D_best_interval_df = pd.read_sql(
        sql=app.session.query(
            func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
            stravaBestSamples.interval, stravaBestSamples.time_interval,
            stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
        ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.interval.in_(interval_lengths),
                                                      stravaBestSamples.type.ilike(activity_type),
                                                      stravaBestSamples.timestamp_local >= (
                                                              datetime.now() - timedelta(days=90))
                                                      ).statement, con=engine, index_col='interval')

    L90D_best_interval_df['act_name'] = L90D_best_interval_df['activity_id'].map(act_dict['act_name'])

    L6W_best_interval_df = pd.read_sql(
        sql=app.session.query(
            func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id,
            stravaBestSamples.interval, stravaBestSamples.time_interval,
            stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
        ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.interval.in_(interval_lengths),
                                                      stravaBestSamples.type.ilike(activity_type),
                                                      stravaBestSamples.timestamp_local >= (
                                                              datetime.now() - timedelta(days=42))
                                                      ).statement, con=engine, index_col='interval')
    L6W_best_interval_df['act_name'] = L6W_best_interval_df['activity_id'].map(act_dict['act_name'])

    L30D_best_interval_df = pd.read_sql(
        sql=app.session.query(
            func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id,
            stravaBestSamples.interval, stravaBestSamples.time_interval,
            stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
        ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.interval.in_(interval_lengths),
                                                      stravaBestSamples.type.ilike(activity_type),
                                                      stravaBestSamples.timestamp_local >= (
                                                              datetime.now() - timedelta(days=30))
                                                      ).statement, con=engine, index_col='interval')
    L30D_best_interval_df['act_name'] = L30D_best_interval_df['activity_id'].map(act_dict['act_name'])

    if last_id:
        recent_best_interval_df = pd.read_sql(
            sql=app.session.query(
                func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id,
                stravaBestSamples.interval, stravaBestSamples.time_interval,
                stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
            ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.activity_id == last_id,
                                                          stravaBestSamples.interval.in_(interval_lengths),
                                                          ).statement, con=engine, index_col='interval')
        recent_best_interval_df['act_name'] = recent_best_interval_df['activity_id'].map(act_dict['act_name'])

    if time_comparison:
        if intensity == 'all':
            time_comparison_best_interval_df = pd.read_sql(
                sql=app.session.query(
                    func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
                    stravaBestSamples.interval, stravaBestSamples.time_interval,
                    stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
                ).group_by(stravaBestSamples.interval).filter(stravaBestSamples.interval.in_(interval_lengths),
                                                              stravaBestSamples.type.ilike(activity_type),
                                                              stravaBestSamples.timestamp_local >= (
                                                                      datetime.now() - timedelta(days=time_comparison))
                                                              ).statement, con=engine, index_col='interval')
            time_comparison_best_interval_df['act_name'] = time_comparison_best_interval_df['activity_id'].map(
                act_dict['act_name'])
        else:
            # Join in intensity data from strava summary

            pd.read_sql(
                sql=app.session.query(stravaSummary.activity_id, stravaSummary.workout_intensity).statement,
                con=engine)

            time_comparison_best_interval_df = pd.read_sql(
                sql=app.session.query(
                    func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
                    stravaBestSamples.interval, stravaBestSamples.time_interval,
                    stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
                ).join(stravaSummary, stravaBestSamples.activity_id == stravaSummary.activity_id,
                       isouter=True).group_by(stravaBestSamples.interval).filter(
                    stravaSummary.workout_intensity == intensity,
                    stravaBestSamples.interval.in_(interval_lengths),
                    stravaBestSamples.type.ilike(activity_type),
                    stravaBestSamples.timestamp_local >= (
                            datetime.now() - timedelta(days=time_comparison))
                ).statement, con=engine, index_col='interval')
            time_comparison_best_interval_df['act_name'] = time_comparison_best_interval_df['activity_id'].map(
                act_dict['act_name'])

            all_best_interval_df = pd.read_sql(
                sql=app.session.query(
                    func.max(stravaBestSamples.mmp).label('mmp'), stravaBestSamples.activity_id, stravaBestSamples.ftp,
                    stravaBestSamples.interval, stravaBestSamples.time_interval,
                    stravaBestSamples.date, stravaBestSamples.timestamp_local, stravaBestSamples.watts_per_kg,
                ).join(stravaSummary, stravaBestSamples.activity_id == stravaSummary.activity_id,
                       isouter=True).group_by(stravaBestSamples.interval).filter(
                    stravaSummary.workout_intensity == intensity,
                    stravaBestSamples.interval.in_(interval_lengths),
                    stravaBestSamples.type.ilike(activity_type)
                ).statement, con=engine, index_col='interval')
            all_best_interval_df['act_name'] = all_best_interval_df['activity_id'].map(
                act_dict['act_name'])

    app.session.remove()

    if len(all_best_interval_df) < 1:
        return {}

    ## Initial hoverdata if showing KPIs in header
    # hoverData = {'points': [
    #     {'x': 60,
    #      'y': ((all_best_interval_df.loc[60]['watts_per_kg']) if len(all_best_interval_df) > 0 else 0),
    #      'customdata': 'x_x_at'},
    #     {'y': ((L90D_best_interval_df.loc[60]['watts_per_kg']) if len(
    #         L90D_best_interval_df) > 0 else 0), 'customdata': 'x_x_L90D'},
    #     {'y': ((L6W_best_interval_df.loc[60]['watts_per_kg']) if len(
    #         L6W_best_interval_df) > 0 else 0), 'customdata': 'x_x_l6w'},
    #     {'y': ((L30D_best_interval_df.loc[60]['watts_per_kg']) if len(
    #         L30D_best_interval_df) > 0 else 0), 'customdata': 'x_x_w'}
    # ]} if power_unit == 'watts_per_kg' else {'points': [
    #     {'x': 60,
    #      'y': (round(all_best_interval_df.loc[60]['mmp']) if len(all_best_interval_df) > 0 else 0),
    #      'customdata': 'x_x_at'},
    #     {'y': (round(L90D_best_interval_df.loc[60]['mmp']) if len(
    #         L90D_best_interval_df) > 0 else 0), 'customdata': 'x_x_L90D'},
    #     {'y': (round(L6W_best_interval_df.loc[60]['mmp']) if len(
    #         L6W_best_interval_df) > 0 else 0), 'customdata': 'x_x_l6w'},
    #     {'y': (round(L30D_best_interval_df.loc[60]['mmp']) if len(
    #         L30D_best_interval_df) > 0 else 0), 'customdata': 'x_x_w'}
    # ]}

    # On Main chart, we only want to show 1 line with all different colors, so loop through each df and remove points where not max
    # Replace all_time with l90D
    if not time_comparison:
        for i in all_best_interval_df.index:
            if i in L90D_best_interval_df.index:
                if L90D_best_interval_df.at[i, power_unit] >= all_best_interval_df.at[i, power_unit]:
                    all_best_interval_df.at[i, power_unit] = None
                else:
                    L90D_best_interval_df.at[i, power_unit] = None
        # Replace L90D with L6W
        for i in L90D_best_interval_df.index:
            if i in L6W_best_interval_df.index:
                if L6W_best_interval_df.at[i, power_unit] >= L90D_best_interval_df.at[i, power_unit]:
                    L90D_best_interval_df.at[i, power_unit] = None
                else:
                    L6W_best_interval_df.at[i, power_unit] = None
        # Replace L6W with L30D
        for i in L6W_best_interval_df.index:
            if i in L30D_best_interval_df.index:
                if L30D_best_interval_df.at[i, power_unit] >= L6W_best_interval_df.at[i, power_unit]:
                    L6W_best_interval_df.at[i, power_unit] = None
                else:
                    L30D_best_interval_df.at[i, power_unit] = None

    tooltip = '''{}
{}
{}
{:.2f} W/kg''' if power_unit == 'watts_per_kg' else '''{}
{}
{}
{:.0f} W'''
    data = [
        go.Scatter(
            name='All',
            x=all_best_interval_df.index,
            y=all_best_interval_df[power_unit],
            mode='lines',
            text=[
                tooltip.format(
                    timedelta(seconds=i),
                    all_best_interval_df.loc[i]['act_name'],
                    all_best_interval_df.loc[i].date,
                    all_best_interval_df.loc[i][power_unit])
                for i in all_best_interval_df.index],

            customdata=[
                '{}_{}_at'.format(all_best_interval_df.loc[x]['activity_id'], int(x))
                for x in all_best_interval_df.index],  # add fields to text so data can go through clickData
            hoverinfo='text',
            line={'shape': 'spline', 'color': 'rgba(220,220,220,.5)'}
        )
    ]
    if not time_comparison:
        data.extend([
            go.Scatter(
                name='L90D',
                x=L90D_best_interval_df.index,
                y=L90D_best_interval_df[power_unit],
                mode='lines',
                # text=['{:.0f}'.format(L90D_best_interval_df.loc[i]['mmp']) for i in
                #       L90D_best_interval_df.index],
                text=[
                    tooltip.format(
                        timedelta(seconds=i),
                        L90D_best_interval_df.loc[i]['act_name'],
                        L90D_best_interval_df.loc[i].date,
                        L90D_best_interval_df.loc[i][power_unit])
                    for i in L90D_best_interval_df.index],
                customdata=[
                    '{}_{}_L90D'.format(L90D_best_interval_df.loc[x]['activity_id'], int(x))
                    for x in L90D_best_interval_df.index],
                hoverinfo='text',
                line={'shape': 'spline', 'color': white},
            ),
            go.Scatter(
                name='L6W',
                x=L6W_best_interval_df.index,
                y=L6W_best_interval_df[power_unit],
                mode='lines',
                # text=['{:.0f}'.format(L90D_best_interval_df.loc[i]['mmp']) for i in
                #       L90D_best_interval_df.index],
                text=[
                    tooltip.format(
                        timedelta(seconds=i),
                        L6W_best_interval_df.loc[i]['act_name'],
                        L6W_best_interval_df.loc[i].date,
                        L6W_best_interval_df.loc[i][power_unit])
                    for i in L6W_best_interval_df.index],
                customdata=[
                    '{}_{}_l6w'.format(L6W_best_interval_df.loc[x]['activity_id'], int(x))
                    for x in L6W_best_interval_df.index],
                hoverinfo='text',
                line={'shape': 'spline', 'color': light_blue},
            ),
            go.Scatter(
                name='L30D',
                x=L30D_best_interval_df.index,
                y=L30D_best_interval_df[power_unit],
                mode='lines',
                # text=['{:.0f}'.format(L90D_best_interval_df.loc[i]['mmp']) for i in
                #       L90D_best_interval_df.index],
                text=[
                    tooltip.format(
                        timedelta(seconds=i),
                        L30D_best_interval_df.loc[i]['act_name'],
                        L30D_best_interval_df.loc[i].date,
                        L30D_best_interval_df.loc[i][power_unit])
                    for i in L30D_best_interval_df.index],
                customdata=[
                    '{}_{}_w'.format(L30D_best_interval_df.loc[x]['activity_id'], int(x))
                    for x in L30D_best_interval_df.index],
                hoverinfo='text',
                line={'shape': 'spline', 'color': teal},
            )
        ])
    else:
        data.append(
            go.Scatter(
                name={99999: 'All', int(datetime.now().strftime('%j')): 'YTD', 90: 'L90D', 42: 'L6W', 30: 'L30D'}[
                    time_comparison],
                x=time_comparison_best_interval_df.index,
                y=time_comparison_best_interval_df[power_unit],
                mode='lines',
                # text=['{:.0f}'.format(L90D_best_interval_df.loc[i]['mmp']) for i in
                #       L90D_best_interval_df.index],
                text=[
                    tooltip.format(
                        timedelta(seconds=i),
                        time_comparison_best_interval_df.loc[i]['act_name'],
                        time_comparison_best_interval_df.loc[i].date,
                        time_comparison_best_interval_df.loc[i][power_unit])
                    for i in time_comparison_best_interval_df.index],
                customdata=[
                    '{}_{}_w'.format(time_comparison_best_interval_df.loc[x]['activity_id'], int(x))
                    for x in time_comparison_best_interval_df.index],
                hoverinfo='text',
                line={'shape': 'spline', 'color': teal},
            )
        )
    if last_id:
        data.append(
            go.Scatter(
                name='Workout',
                x=recent_best_interval_df.index,
                y=recent_best_interval_df[power_unit],
                mode='lines',
                text=[
                    tooltip.format(
                        timedelta(seconds=i),
                        recent_best_interval_df.loc[i]['act_name'],
                        recent_best_interval_df.loc[i].date,
                        recent_best_interval_df.loc[i][power_unit])
                    for i in recent_best_interval_df.index],
                customdata=[
                    '{}_{}_w'.format(recent_best_interval_df.loc[x]['activity_id'], int(x))
                    for x in recent_best_interval_df.index],
                hoverinfo='text',
                line={'shape': 'spline', 'color': orange},
            )
        )
    annotations = [
        # Muscle Power
        go.layout.Annotation(
            font={'size': 10, 'color': orange if muscle_power_best else white},
            x=1,
            y=muscle_power,
            xref="x",
            yref="y",
            text='''Max 10 Sec Power (L90D): {:.2f} W/kg'''.format(
                muscle_power) if power_unit == 'watts_per_kg' else '''Max 10 Sec Power (L90D): {:.0f} W'''.format(
                muscle_power),
            showarrow=False,
            arrowhead=1,
            arrowcolor='Grey',
            bgcolor='rgba(81,89,95,.5)',
            ay=-10,
        ),
        # Fatigue Resistance
        go.layout.Annotation(
            font={'size': 10, 'color': orange if fatigue_best else white},
            x=.1,
            y=fatigue_ftp,
            xref="x",
            yref="y",
            text='''100% CP (L90D): {:.2f} W/kg'''.format(
                fatigue_ftp) if power_unit == 'watts_per_kg' else '''100% CP (L90D): {:.0f} W'''.format(
                fatigue_ftp),
            showarrow=True,
            arrowhead=1,
            arrowcolor='rgba(0,0,0,0)',
            bgcolor='rgba(81,89,95,.5)',
            ax=75,
            ay=-10,
        ),

        go.layout.Annotation(
            font={'size': 10, 'color': orange if endurance_best else white},
            x=.1,
            y=endurance_ftp,
            xref="x",
            yref="y",
            text='''50% CP (L90D): {:.2f} W/kg'''.format(
                endurance_ftp) if power_unit == 'watts_per_kg' else '''50% CP (L90D): {:.0f} W'''.format(
                endurance_ftp),
            showarrow=True,
            arrowhead=1,
            arrowcolor='rgba(0,0,0,0)',
            bgcolor='rgba(81,89,95,.5)',
            ax=75,
            ay=-10,
        ),
    ] if td_data_exists else []

    shapes = [
        # Power Curve Lines
        # Muscle Power
        dict(
            type='line', y0=0, y1=muscle_power, xref='x', yref='y', x0=10, x1=10,
            line=dict(
                color="Grey",
                width=1,
                dash="dot",
            ),
        ),
        # Fatigue Resistance
        dict(
            type='line', y0=fatigue_ftp, y1=fatigue_ftp, xref='x', yref='y', x0=.9,
            line=dict(
                color="Grey",
                width=1,
                dash="dot",
            ),
        ),
        # Endurance
        dict(
            type='line', y0=endurance_ftp, y1=endurance_ftp, xref='x', yref='y', x0=.9,
            line=dict(
                color="Grey",
                width=1,
                dash="dot",
            ),
        )
    ] if td_data_exists else []

    layout = go.Layout(
        # transition=dict(duration=transition),
        title='Power Curve' if height < 400 else '',
        font=dict(
            size=10,
            color=white
        ),
        height=height,
        shapes=shapes,
        annotations=annotations,
        xaxis=dict(
            showgrid=False,
            # tickformat="%H:%M:%S",
            # range=[best_interval_df.index.min(),best_interval_df.index.max()],
            # range=[np.log10(best_interval_df.index.min()), np.log10(best_interval_df.index.max())],
            type='log',
            tickangle=45 if height < 400 else 0,
            tickvals=[1, 2, 5, 10, 30, 60, 120, 5 * 60, 10 * 60, 20 * 60, 60 * 60, 60 * 120],
            ticktext=['1s', '2s', '5s', '10s', '30s', '1m', '2m', '5m', '10m', '20m', '1h', '2h'],
        ),

        yaxis=dict(
            showgrid=True,
            zeroline=False,
            # range=[best_interval_df['mmp'].min(), best_interval_df['mmp'].max()],
            gridcolor='rgb(73, 73, 73)'
        ),
        margin={'l': 40, 'b': 25, 't': 20 if height < 400 else 5, 'r': 0},
        legend=dict(x=.5, y=1, bgcolor='rgba(127, 127, 127, 0)', xanchor='center',
                    orientation='h', ) if height >= 400 else
        dict(x=.85, bgcolor='rgba(127, 127, 127, 0)', xanchor='center'),
        autosize=True,
        hovermode='x',
        # paper_bgcolor='rgb(66,66,66)',
        # plot_bgcolor='rgba(0,0,0,0)',

    )

    figure = {
        'data': data,
        'layout': layout
    }

    # if not last_id:
    #     # Pull CY for scatter bubbles
    #     cy_best_df_bubbles = df_best_samples[
    #         df_best_samples['timestamp_local'] >= datetime.strptime(str(datetime.now().year) + '-01-01', "%Y-%m-%d")]
    #     data.append(go.Scatter(
    #         name='CY bubbles',
    #         x=cy_best_df_bubbles.index,
    #         y=cy_best_df_bubbles[power_unit],
    #         mode='markers',
    #         customdata=['____' for x in cy_best_df_bubbles.index],
    #         hoverinfo='none',
    #         line={'color': 'rgba(56, 128, 139,.05)'},
    #         showlegend=False
    #     ))

    return figure  # , hoverData

def create_ftp_chart(activity_type='ride', power_unit='watts'):
    activity_type = '%' + activity_type + '%'

    df_ftp = pd.read_sql(
        sql=app.session.query(stravaSummary).filter(stravaSummary.type.ilike(activity_type),
                                                    stravaSummary.start_date_utc >= (
                                                            datetime.utcnow() - relativedelta(months=12))
                                                    ).statement, con=engine,
        index_col='start_day_local')[['activity_id', 'ftp', 'weight']]

    app.session.remove()

    if len(df_ftp) < 1:
        return None, {}

    df_ftp.set_index(pd.DatetimeIndex(df_ftp.index), inplace=True)

    # Get latest FTP of each month (instead of max in case ftp decreases)
    df_ftp = df_ftp.groupby(df_ftp.index.month).apply(pd.Series.tail, 1).reset_index(level=0,
                                                                                drop=True).sort_index().resample(
        'M').max().ffill().interpolate()

    # Filter summary table on activities that have a different FTP from the previous activity
    # df_ftp['previous_ftp'] = df_ftp['ftp'].shift(1)
    # df_ftp = df_ftp[df_ftp['previous_ftp'] != df_ftp['ftp']]

    df_ftp['watts_per_kg'] = df_ftp['ftp'] / (df_ftp['weight'] / 2.20462)
    metric = 'ftp' if power_unit == 'ftp' else 'watts_per_kg'
    tooltip = '{:.0f} W {}' if metric == 'ftp' else '{:.2f} W/kg {}'
    title = 'Current FTP {:.0f} W (L12M)' if metric == 'ftp' else 'Current FTP {:.2f} W/kg (L12M)'

    df_ftp['ftp_%'] = ['{}{:.0f}%'.format('+' if x > 0 else '', x) if x != 0 else '' for x in
                       (((df_ftp[metric] - df_ftp[metric].shift(1)) / df_ftp[metric].shift(1)) * 100).fillna(0)]

    df_ftp_tooltip = [tooltip.format(x, y) for x, y in
                      zip(df_ftp[metric], df_ftp['ftp_%'])]

    df_ftp = df_ftp.reset_index()

    ftp_current = title.format(df_ftp.loc[df_ftp.index.max()][metric])

    figure = {
        'data': [
            go.Bar(
                name='FTP',
                x=df_ftp.index,
                y=df_ftp[metric],
                text=df_ftp['ftp_%'],
                textfont=dict(
                    size=10,
                ),
                textposition='none',
                marker=dict(
                    color=[orange if x == df_ftp[metric].max() else light_blue for x in
                           df_ftp[metric]],
                ),
                hovertext=df_ftp_tooltip,
                hoverinfo='text+x',
                opacity=0.7,
            ),

        ],
        'layout': go.Layout(
            font=dict(
                size=10,
                color=white
            ),
            height=400,
            transition=dict(duration=transition),
            xaxis=dict(
                showticklabels=True,
                tickvals=df_ftp.index,
                ticktext=df_ftp['start_day_local'].apply(lambda x: datetime.strftime(x, "%b")),
                showgrid=False
            ),
            yaxis=dict(
                showticklabels=True,
                showgrid=True,
                gridcolor='rgb(73, 73, 73)',
                # range=[df_ftp['ftp'].min() * .90, df_ftp['ftp'].max() * 1.25],
            ),
            showlegend=False,
            margin={'l': 25, 'b': 25, 't': 5, 'r': 20},
        )
    }

    return ftp_current, figure

def zone_chart(activity_id=None, sport='run', metrics=['power_zone', 'hr_zone'], chart_id='zone-chart', days=90,
               height=400, intensity='all'):
    # If activity_id passed, filter only that workout, otherwise show distribution across last 6 weeks

    if activity_id:
        df_samples = pd.read_sql(
            sql=app.session.query(stravaSamples).filter(stravaSamples.activity_id == activity_id).statement,
            con=engine,
            index_col=['timestamp_local'])

    else:
        if intensity == 'all':
            df_samples = pd.read_sql(
                sql=app.session.query(stravaSamples).filter(stravaSamples.type.like(sport),
                                                            stravaSamples.timestamp_local >= (
                                                                    datetime.now() - timedelta(
                                                                days=days))).statement,
                con=engine,
                index_col=['timestamp_local'])
        else:
            # Join intensity from strava summary to samples and filter on intensity if passed as an argument
            df_samples = pd.read_sql(
                sql=app.session.query(stravaSamples).filter(stravaSamples.type.like(sport),
                                                            stravaSamples.timestamp_local >= (
                                                                    datetime.now() - timedelta(
                                                                days=days))).statement,
                con=engine,
                index_col=['timestamp_local'])
            df_samples = df_samples.merge(pd.read_sql(
                sql=app.session.query(stravaSummary.activity_id, stravaSummary.workout_intensity).statement,
                con=engine), how='left', left_on='activity_id', right_on='activity_id')

            df_samples = df_samples[df_samples['workout_intensity'] == intensity]

    app.session.remove()
    data = []
    for metric in metrics:
        zone_df = df_samples.groupby(metric).size().reset_index(name='counts')
        zone_df['seconds'] = zone_df['counts']
        zone_df['Percent of Total'] = (zone_df['seconds'] / zone_df['seconds'].sum())

        # zone_map = {1: 'Active Recovery', 2: 'Endurance', 3: 'Tempo', 4: 'Threshold', 5: 'VO2 Max',
        #             6: 'Anaerobic', 7: 'Neuromuscular'}

        zone_map = {1: 'Zone 1', 2: 'Zone 2', 3: 'Zone 3', 4: 'Zone 4', 5: 'Zone 5',
                    6: 'Zone 6', 7: 'Zone 7'}

        zone_df[metric] = zone_df[metric].map(zone_map)
        zone_df = zone_df.sort_values(by=metric, ascending=False)

        label = [
            'Time: ' + '{}'.format(timedelta(seconds=seconds)) + '
' + '% of Total: ' + '{0:.0f}'.format(
                percentage * 100) + '%'
            for seconds, percentage in zip(list(zone_df['seconds']), list(zone_df['Percent of Total']))]

        per_low = zone_df[zone_df[metric].isin(
            ['Zone 1', 'Zone 2', 'Zone 3'] if sport == 'Ride' and metric == 'power_zone' else ['Zone 1', 'Zone 2'])][
            'Percent of Total'].sum()

        if metric == 'hr_zone':
            colors = [
                'rgb(174, 18, 58)',
                'rgb(204, 35, 60)',
                'rgb(227, 62, 67)',
                'rgb(242, 98, 80)',
                'rgb(248, 130, 107)',
                'rgb(252, 160, 142)',
                'rgb(255, 190, 178)'
            ]
        elif metric == 'power_zone':
            colors = [
                'rgb(44, 89, 113)',
                'rgb(49, 112, 151)',
                'rgb(53, 137, 169)',
                'rgb(69, 162, 185)',
                'rgb(110, 184, 197)',
                'rgb(147, 205, 207)',
                'rgb(188, 228, 216)'
            ]

        data.append(
            go.Bar(
                name='HR ({:.0f}% Low)'.format(per_low * 100) if metric == 'hr_zone' else 'Power ({:.0f}% Low)'.format(
                    per_low * 100),
                y=zone_df[metric],
                x=zone_df['Percent of Total'],
                orientation='h',
                text=['{0:.0f}'.format(percentage * 100) + '%' for percentage in list(zone_df['Percent of Total'])],
                hovertext=label,
                hoverinfo='text',
                textposition='auto',
                width=.4,
                marker={'color': colors},
            )
        )

    return dcc.Graph(
        id=chart_id, style={'height': '100%'},
        config={
            'displayModeBar': False
        },
        figure={
            'data': data,
            'layout': go.Layout(
                title='Time in Zones' if height < 400 else '',
                font=dict(
                    size=10,
                    color=white
                ),
                height=height,
                # annotations=[
                #     dict(
                #         text="Power Zones",
                #         font=dict(size=16),
                #         xref="paper",
                #         yref="paper",
                #         yanchor="bottom",
                #         xanchor="center",
                #         align="center",
                #         x=0.5,
                #         y=1,
                #         showarrow=False
                #     )],
                autosize=True,
                xaxis=dict(
                    hoverformat=".1%",
                    tickformat="%",
                    showgrid=False,
                    # zerolinecolor='rgb(238, 238, 238)',
                ),
                yaxis=dict(
                    autorange='reversed',
                    showgrid=False,
                    categoryarray=['Zone 5', 'Zone 4', 'Zone 3', 'Zone 2', 'Zone 1'] if sport == 'run' else [
                        'Zone 7', 'Zone 6', 'Zone 5', 'Zone 4', 'Zone 3', 'Zone 2', 'Zone 1'],
                ),
                showlegend=True,
                hovermode='closest',
                legend=dict(x=.5, y=1.1, bgcolor='rgba(127, 127, 127, 0)', xanchor='center',
                            orientation='h', ) if height >= 400 else
                dict(x=.85, bgcolor='rgba(127, 127, 127, 0)', xanchor='center'),
                margin={'l': 45, 'b': 0, 't': 20, 'r': 0},
                # margin={'l': 40, 'b': 0, 't': 20 if height < 400 else 5, 'r': 0},

            )
        }
    )

@app.callback(
    [Output('power-curve-container', 'className'),
     Output('power-curve-chart', 'figure'),
     Output('stryd-distributions', 'children')],
    [Input('activity-type-toggle', 'value'),
     Input('power-unit-toggle', 'value')]
)
def update_power_curve(activity_type, power_unit):
    power_unit = 'watts_per_kg' if power_unit else 'mmp'
    activity_type = 'ride' if activity_type else 'run'

    if stryd_credentials_supplied and activity_type == 'run':
        classname = 'col-lg-9'
        stryd = stryd_training_distributions()
    else:
        classname = 'col-lg-12'
        stryd = []

    figure = power_curve(activity_type, power_unit)
    return classname, figure, stryd

# # Callbacks to figure out which is the latest chart that was clicked
# @app.callback(
#     Output('power-curve-chart-timestamp', 'children'),
#     [Input('power-curve-chart', 'clickData')])
# def power_curve_chart_timestamp(dummy):
#     return datetime.utcnow()
#
#
# @app.callback(
#     Output('power-profile-5-chart-timestamp', 'children'),
#     [Input('power-profile-5-chart', 'clickData')])
# def power_profile_5_chart_timestamp(dummy):
#     return datetime.utcnow()
#
#
# @app.callback(
#     Output('power-profile-60-chart-timestamp', 'children'),
#     [Input('power-profile-60-chart', 'clickData')])
# def power_profile_60_chart_timestamp(dummy):
#     return datetime.utcnow()
#
#
# @app.callback(
#     Output('power-profile-300-chart-timestamp', 'children'),
#     [Input('power-profile-300-chart', 'clickData')])
# def power_profile_300_chart_timestamp(dummy):
#     return datetime.utcnow()
#
#
# @app.callback(
#     Output('power-profile-1200-chart-timestamp', 'children'),
#     [Input('power-profile-1200-chart', 'clickData')])
# def power_profile_1200_chart_timestamp(dummy):
#     return datetime.utcnow()

# # Store last clicked data into div for consumption by action callbacks
# @app.callback(
#     Output('last-clicked', 'children'),
#     [Input('power-curve-chart-timestamp', 'children'),
#      Input('power-profile-5-chart-timestamp', 'children'),
#      Input('power-profile-60-chart-timestamp', 'children'),
#      Input('power-profile-300-chart-timestamp', 'children'),
#      Input('power-profile-1200-chart-timestamp', 'children')],
#     [State('power-curve-chart', 'clickData'),
#      State('power-profile-5-chart', 'clickData'),
#      State('power-profile-60-chart', 'clickData'),
#      State('power-profile-300-chart', 'clickData'),
#      State('power-profile-1200-chart', 'clickData')]
#
# )
# def update_last_clicked(power_curve_chart_timestamp, power_profile_5_chart_timestamp, power_profile_60_chart_timestamp,
#                         power_profile_300_chart_timestamp, power_profile_1200_chart_timestamp,
#                         power_curve_chart_clickData, power_profile_5_chart_clickData, power_profile_60_chart_clickData,
#                         power_profile_300_chart_clickData, power_profile_1200_chart_clickData):
#     timestamps = {'power': power_curve_chart_timestamp,
#                   '5': power_profile_5_chart_timestamp,
#                   '60': power_profile_60_chart_timestamp,
#                   '300': power_profile_300_chart_timestamp,
#                   '1200': power_profile_1200_chart_timestamp,
#                   }
#
#     if power_curve_chart_timestamp or power_profile_5_chart_timestamp or power_profile_60_chart_timestamp or power_profile_300_chart_timestamp or power_profile_1200_chart_timestamp:
#
#         latest = max(timestamps.items(), key=operator.itemgetter(1))[0]
#
#         if latest == 'power':
#             clickData = power_curve_chart_clickData
#         elif latest == '5':
#             clickData = power_profile_5_chart_clickData
#         elif latest == '60':
#             clickData = power_profile_60_chart_clickData
#         elif latest == '300':
#             clickData = power_profile_300_chart_clickData
#         elif latest == '1200':
#             clickData = power_profile_1200_chart_clickData
#
#         return json.dumps(clickData)

# ## Action for workout title ##
# @app.callback(
#     Output('workout-title', 'children'),
#     [Input('last-clicked', 'children')],
# )
# def update_workout_title(clickdata):
#     last_clickdata = json.loads(clickdata)
#     try:
#         activity_id, end_seconds, interval = last_clickdata['points'][0]['customdata'].split('_')
#         return get_workout_title(activity_id=activity_id)
#     except:
#         return get_workout_title()

# ## Action for workout Trends ##
# @app.callback(
#     Output('workout-trends', 'children'),
#     [Input('last-clicked', 'children')],
# )
# def update_workout_trends(last_clickdata):
#     last_clickdata = json.loads(last_clickdata)
#     try:
#         activity_id, end_seconds, interval = last_clickdata['points'][0]['customdata'].split('_')
#         return workout_details(activity_id=activity_id, start_seconds=(int(end_seconds) - int(interval)),
#                                end_seconds=int(end_seconds))
#     except:
#         return workout_details()

# ## Action for workout Trends ##
# @app.callback(
#     Output('power-zone', 'children'),
#     [Input('last-clicked', 'children')],
# )
# def update_power_zones(last_clickdata):
#     last_clickdata = json.loads(last_clickdata)
#     try:
#         activity_id, end_seconds, interval = last_clickdata['points'][0]['customdata'].split('_')
#         return zone_chart(activity_id=activity_id)
#     except:
#         return zone_chart()

# Color icons
@app.callback(
    [Output('bicycle-icon', 'style'),
     Output('running-icon', 'style')],
    [Input('activity-type-toggle', 'value')]
)
def update_icon(value):
    if value:
        return {'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle', 'color': teal}, {
            'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle'}
    else:
        return {'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle'}, {
            'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle', 'color': teal}

@app.callback(
    [Output('bolt-icon', 'style'),
     Output('weight-icon', 'style')],
    [Input('power-unit-toggle', 'value')]
)
def update_icon(value):
    if value:
        return {'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle'}, {
            'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle', 'color': teal}
    else:
        return {'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle', 'color': teal}, {
            'fontSize': '2rem', 'display': 'inline-block', 'vertical-align': 'middle'}

# FTP Chart
@app.callback(
    [Output('ftp-current', 'children'),
     Output('ftp-chart', 'figure')],
    [Input('activity-type-toggle', 'value'),
     Input('power-unit-toggle', 'value')]
)
def ftp_chart(activity_type, power_unit):
    power_unit = 'watts_per_kg' if power_unit else 'ftp'
    activity_type = 'ride' if activity_type else 'run'
    current, figure = create_ftp_chart(activity_type=activity_type, power_unit=power_unit)
    return current, figure

# Group power profiles
@app.callback([Output('power-profile-5-chart', 'figure'),
               Output('power-profile-60-chart', 'figure'),
               Output('power-profile-300-chart', 'figure'),
               Output('power-profile-1200-chart', 'figure'),
               Output('day-button', 'style'),
               Output('week-button', 'style'),
               Output('month-button', 'style'),
               Output('year-button', 'style'), ],
              [Input('activity-type-toggle', 'value'),
               Input('power-unit-toggle', 'value'),
               Input('day-button', 'n_clicks'),
               Input('week-button', 'n_clicks'),
               Input('month-button', 'n_clicks'),
               Input('year-button', 'n_clicks')]
              )
def update_power_profiles(activity_type, power_unit, day_n_clicks, week_n_clicks, month_n_clicks, year_n_clicks):
    latest_dict = {'day-button': 'D', 'week-button': 'W', 'month-button': 'M', 'year-button': 'Y'}
    style = {'Y': {'marginRight': '1%'}, 'M': {'marginRight': '1%'}, 'W': {'marginRight': '1%'},
             'D': {'marginRight': '1%'}}

    ctx = dash.callback_context
    if not ctx.triggered:
        latest = 'M'
    else:
        if ctx.triggered[0]['prop_id'].split('.')[0] != 'power-unit-toggle' and ctx.triggered[0]['prop_id'].split('.')[
            0] != 'activity-type-toggle':
            latest = latest_dict[ctx.triggered[0]['prop_id'].split('.')[0]]
        else:
            latest = 'M'

    style[latest] = {'marginRight': '1%', 'color': '#64D9EC', 'borderColor': '#64D9EC'}

    power_unit = 'watts_per_kg' if power_unit else 'mmp'
    activity_type = 'ride' if activity_type else 'run'

    return power_profiles(interval=5, group=latest, power_unit=power_unit, activity_type=activity_type), \
           power_profiles(interval=60, group=latest, power_unit=power_unit, activity_type=activity_type), \
           power_profiles(interval=300, group=latest, power_unit=power_unit, activity_type=activity_type), \
           power_profiles(interval=1200, group=latest, power_unit=power_unit, activity_type=activity_type), \
           style['D'], style['W'], style['M'], style['Y']

# # Main Dashboard Generation Callback
# @app.callback(
#     Output('power-layout', 'children'),
#     [Input('activity-type-toggle', 'value'),
#      Input('power-unit-toggle', 'value')],
# )
# def performance_dashboard(dummy, activity_type, power_unit):
#     return generate_power_dashboard()

# @app.callback(
#     Output('power-curve-kpis', 'children'),
#     [Input('power-curve-chart', 'hoverData')],
#     [State('power-unit-toggle', 'value')])
# def update_fitness_kpis(hoverData, power_unit):
#     interval, at, L90D, l6w, last, pr = 0, '', '', '', '', ''
#     if hoverData is not None and hoverData['points'][0]['customdata'] != 'ignore':
#         interval = hoverData['points'][0]['x']
#         for x in hoverData['points']:
#             if x['customdata'].split('_')[2] == 'at':
#                 at = '{:.1f} W/kg'.format(x['y']) if power_unit else '{:.0f} W'.format(x['y'])
#             elif x['customdata'].split('_')[2] == 'L90D':
#                 L90D = '{:.1f} W/kg'.format(x['y']) if power_unit else '{:.0f} W'.format(x['y'])
#             elif x['customdata'].split('_')[2] == 'l6w':
#                 l6w = '{:.1f} W/kg'.format(x['y']) if power_unit else '{:.0f} W'.format(x['y'])
#             elif x['customdata'].split('_')[2] == 'pr':
#                 pr = '{:.1f} W/kg'.format(x['y']) if power_unit else '{:.0f} W'.format(x['y'])
#             elif x['customdata'].split('_')[2] == 'w':
#                 last = '{:.1f} W/kg'.format(x['y']) if power_unit else '{:.0f} W'.format(x['y'])
#
#     return create_power_curve_kpis(interval, at, L90D, l6w, last, pr)

def get_layout(**kwargs):
    athlete_info = app.session.query(athlete).filter(athlete.athlete_id == 1).first()
    use_power = True if athlete_info.use_run_power or athlete_info.use_cycle_power else False
    app.session.remove()

    if not use_power:
        return html.H1('Power data currently disabled', className='text-center')
    else:
        return html.Div([

            html.Div(className='row align-items-start text-center', children=[
                html.Div(id='power-dashboard-header-container', className='col-12 mt-2 mb-2', children=[

                    html.I(id='running-icon', className='fa fa-running',
                           style={'fontSize': '2rem', 'display': 'inline-block'}),
                    daq.ToggleSwitch(id='activity-type-toggle', className='mr-2 ml-2',
                                     style={'display': 'inline-block'}),

                    html.I(id='bicycle-icon', className='fa fa-bicycle',
                           style={'fontSize': '2rem', 'display': 'inline-block', 'color': teal}),
                    dbc.Tooltip('Analyze cycling activities', target="bicycle-icon"),
                    dbc.Tooltip('Toggle activity type', target="activity-type-toggle"),
                    dbc.Tooltip('Analyze running activities', target="running-icon"),

                    html.I(style={'fontSize': '2rem', 'display': 'inline-block', 'paddingLeft': '1%',
                                  'paddingRight': '1%'}),

                    html.I(id='bolt-icon', className='fa fa-bolt',
                           style={'fontSize': '2rem', 'display': 'inline-block',
                                  'color': teal}),

                    daq.ToggleSwitch(id='power-unit-toggle', className='mr-2 ml-2', style={'display': 'inline-block'},
                                     value=False),

                    html.I(id='weight-icon', className='fa fa-weight',
                           style={'fontSize': '2rem', 'display': 'inline-block'}),

                    dbc.Tooltip('Show watts', target="bolt-icon"),
                    dbc.Tooltip('Toggle power unit', target="power-unit-toggle"),
                    dbc.Tooltip('Show watts/kg', target="weight-icon"),

                ]),
            ]),

            html.Div(id='power-curve-and-zone', className='row mt-2 mb-2',
                     children=[
                         html.Div(className='col-lg-8', children=[
                             dbc.Card(children=[
                                 dbc.CardHeader(id='power-curve-kpis',
                                                children=[html.H4('Power Duration Curve', className='mb-0')]),
                                 dbc.Spinner(color='info', children=[
                                     dbc.CardBody(
                                         html.Div(className='row', children=[
                                             html.Div(id='power-curve-container', className='col-lg-9', children=[
                                                 dcc.Graph(id='power-curve-chart', config={'displayModeBar': False},
                                                           style={'height': '100%'})
                                             ]),
                                             html.Div(id='stryd-distributions', className='col-lg-3',
                                                      style={'paddingLeft': 0}),
                                         ]),

                                     )
                                 ]),
                             ]),
                         ]),
                         html.Div(className='col-lg-4', children=[
                             dbc.Card(children=[
                                 dbc.CardHeader(html.H4(id='ftp-current', className='mb-0')),
                                 dbc.Tooltip(
                                     'Functional Threshold Power (FTP) is the highest average power you can sustain for 1 hour, measured in watts. FTP is used to determine training zones when using a power meter and to measure improvement.',
                                     target="ftp-current", ),
                                 dbc.Spinner(color='info', children=[
                                     dbc.CardBody(dcc.Graph(id='ftp-chart', config={'displayModeBar': False},
                                                            style={'height': '100%'}, ))
                                 ]),
                             ]
                             ),

                         ]),
                     ]),
            html.Div(id='power-profile-header',
                     className='row align-items-center text-center mt-2 mb-2', children=[
                    html.Div(className='col', children=[
                        html.H6('Power Profiles by'),
                        html.Div(id='power-profile-buttons', className='col', children=[
                            dbc.Button('Day', id='day-button', color='primary', size='sm'),
                            dbc.Button('Week', id='week-button', color='primary', size='sm'),
                            dbc.Button('Month', id='month-button', color='primary', size='sm'),
                            dbc.Button('Year', id='year-button', color='primary', size='sm'),
                        ]),
                    ]),
                ]),

            html.Div(id='power-profiles', className='row', children=[
                html.Div(className='col-lg-3', children=[
                    dbc.Card(id='power-profile-5', children=[
                        dbc.CardHeader(html.H4('5 Second Max Power', className='mb-0')),
                        dbc.Spinner(color='info', children=[
                            dbc.CardBody(
                                dcc.Graph(
                                    id='power-profile-5-chart',
                                    config={'displayModeBar': False},
                                    style={'height': '100%'},
                                )
                            )
                        ]),
                    ]
                             )
                ]),
                html.Div(className='col-lg-3', children=[
                    dbc.Card(id='power-profile-60', children=[
                        dbc.CardHeader(html.H4('1 Minute Max Power', className='mb-0')),
                        dbc.Spinner(color='info', children=[
                            dbc.CardBody(
                                dcc.Graph(
                                    id='power-profile-60-chart',
                                    config={'displayModeBar': False},
                                    style={'height': '100%'},
                                )
                            )
                        ]),
                    ]
                             )
                ]),
                html.Div(className='col-lg-3', children=[
                    dbc.Card(id='power-profile-300', children=[
                        dbc.CardHeader(html.H4('5 Minute Max Power', className='mb-0')),
                        dbc.Spinner(color='info', children=[
                            dbc.CardBody(
                                dcc.Graph(
                                    id='power-profile-300-chart',
                                    config={'displayModeBar': False},
                                    style={'height': '100%'},
                                )
                            )
                        ]),
                    ]
                             )
                ]),
                html.Div(className='col-lg-3', children=[
                    dbc.Card(id='power-profile-1200', children=[
                        dbc.CardHeader(html.H4('20 Minute Max Power', className='mb-0')),
                        dbc.Spinner(color='info', children=[
                            dbc.CardBody(
                                dcc.Graph(
                                    id='power-profile-1200-chart',
                                    config={'displayModeBar': False},
                                    style={'height': '100%'},
                                )
                            )
                        ]),
                    ]
                             )
                ]),
            ])
        ])


import scrapy

class Scrapy_hub_spider(scrapy.Spider):
    name = 'scrapy_hub'
    def start_requests(self):
        return [scrapy.Request(url = "https://blog.scrapinghub.com", callback = self.parse)]

    def parse(self, response):
        titles = response.css("div.post-listing div.post-item div.post-header h2 a::text").getall()
        dates = response.css("div.post-item div.post-header div.byline span.date a::text").getall()
        count = 0
        while (count < len(titles)):
            yield {"title": titles[count], "date":dates[count]}
            count += 1


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Nov  8 10:57:33 2019

@author: gary
    """
import argparse
import torch
from torch.utils.data import DataLoader
import os
import random
import datetime
import csv

from libs.config_defaults import _C as cfg
from libs.data.dataset_builder import dataset_factory
from libs.train import train, evaluate, test
from libs.logger import trainingLogger
from libs.model_utils import check_model,build_model, checkpoint

def get_unique_name(cfg):

    #Build save details from config
    separator = '-'
    model_name = separator.join(cfg.MODEL.components)

    if len(cfg.DATASET.train) == 1:
        unique_name = cfg.DATASET.train[0] + "-" + cfg.NAME
    else:
        seperator = '_'
        unique_name = seperator.join(cfg.DATASET.train) + "-" + cfg.NAME

    cfg.DIR.logs =  os.path.join(cfg.DIR.parent, cfg.DIR.logs, "-".join([model_name,unique_name]))

    cfg.DIR.output =  os.path.join(cfg.DIR.parent, cfg.DIR.output, model_name, unique_name)
    cfg.DIR.ckpts =  os.path.join(cfg.DIR.output,cfg.DIR.ckpts)
    cfg.DIR.val_metrics =  os.path.join(cfg.DIR.parent, cfg.DIR.output,cfg.DIR.val_metrics)
    cfg.DIR.vis =  os.path.join(cfg.DIR.output,cfg.DIR.vis)
    return cfg

####################################################################################################################
# Main Code
####################################################################################################################

parser = argparse.ArgumentParser(description="PyTorch Semantic Segmentation Training")
parser.add_argument(
    "--cfg",
    default="test_seg.yaml",
    metavar="FILE",
    help="path to config file",
    type=str,
)
parser.add_argument(

    "opts",
    help="Modify config options using the command-line",
    default=None,
    nargs=argparse.REMAINDER,
)

args = parser.parse_args()
args.cfg = os.path.join("data","config", args.cfg)

#Load Config from file and extra command line
cfg.merge_from_file(args.cfg) #From yacs
cfg.merge_from_list(args.opts)

#Set parameters dependant upon mode
if cfg.MODE.upper() == "TRAIN":
    cfg.PARAMS = cfg.TRAIN
elif cfg.MODE.upper() == "EVAL":
    cfg.PARAMS = cfg.VAL
elif cfg.MODE.upper() == "TEST":
    cfg.PARAMS = cfg.TEST

cfg = get_unique_name(cfg)

# Output directory for experimnet
if not os.path.isdir(cfg.DIR.output):
   os.makedirs(cfg.DIR.output)
with open(os.path.join(cfg.DIR.output, 'config.yaml'), 'w') as f:
    f.write("{}".format(cfg))
if not os.path.isdir(cfg.DIR.ckpts):
   os.makedirs(cfg.DIR.ckpts)

#In cases classes are ignored
if cfg.TRAIN.criterion_ignore_index != -100:
    cfg.DATASET.shift_class_labels = abs(cfg.TRAIN.criterion_ignore_index)
else:
    cfg.DATASET.shift_class_labels = 0

cfg = check_model(cfg)

if cfg.MODE.upper() == "TRAIN":

    dataset, collator = dataset_factory(cfg.DATASET.train,cfg.MODE.upper(),cfg.DIR.datasets,cfg.DATASET,trainvis=True)
    dataloader_train = DataLoader(dataset["TRAIN"], batch_size=cfg.TRAIN.batch_size_per_gpu, shuffle=True, num_workers=0, collate_fn=collator["TRAIN"])
    dataloader_vis = DataLoader(dataset["TRAINVIS"], batch_size=1, shuffle=False, num_workers=0, collate_fn=collator["TRAINVIS"])
    cfg.DATASET.num_class = dataset["TRAIN"].class_num

    random.seed(cfg.TRAIN.seed)
    torch.manual_seed(cfg.TRAIN.seed)
    #Setup logging
    history = {'train': {'epoch': [], 'loss': [], 'acc': []}}

    #time_stamp = datetime.datetime.now()
    #time_stamp = time_stamp.strftime("%m_%d_%y_%H_%M_%S")
    #log_folder = os.getcwd() +"/logs/"+ dataset_name + "_" + time_stamp
    logger = trainingLogger(dataset["TRAIN"].name,logs_dir=cfg.DIR.logs, img_dir=cfg.DIR.vis)
    model, optimizers, nets = build_model(cfg)

    cfg.TRAIN.max_iters = cfg.TRAIN.epoch_iters * cfg.TRAIN.num_epoch
    cfg.TRAIN.running_lr = [None] * len(optimizers)
    for i in range(len(optimizers)):
        cfg.TRAIN.running_lr[i] = cfg.TRAIN.lr[i]

    for epoch in range(cfg.TRAIN.checkpoint, cfg.TRAIN.num_epoch):
        train(model, dataloader_train, optimizers, logger, epoch+1, cfg, vis_data=dataloader_vis)
        # checkpointing
        checkpoint(nets, history, cfg, epoch+1)

if cfg.MODE.upper() == "EVAL":
    for ds in cfg.DATASET.val:
        dataset, collator = dataset_factory(ds,cfg.MODE.upper(),cfg.DIR.datasets,cfg.DATASET)
        dataloader = DataLoader(dataset["VAL"], batch_size=1, shuffle=False, num_workers=0, collate_fn=collator["VAL"])
        cfg.DATASET.num_class = dataset["VAL"].class_num
        logger = trainingLogger(dataset["VAL"].name,logs_dir=cfg.DIR.logs, img_dir=cfg.DIR.vis)
        model, optimizers, nets = build_model(cfg)
        results = evaluate(model, dataloader, logger, cfg)

        RESULTS2 = results["IOU"].tolist()

        with open('testres.csv','w', newline='') as result_file:
            wr = csv.writer(result_file, dialect='excel')
            wr.writerows(RESULTS2)

if cfg.MODE.upper() == "TEST":
    for ds in cfg.DATASET.test:
        dataset, collator = dataset_factory(ds,cfg.MODE.upper(),cfg.DIR.datasets,cfg.DATASET)
        dataloader = DataLoader(dataset["TEST"], batch_size=1, shuffle=False, num_workers=0, collate_fn=collator["TEST"])
        cfg.DATASET.num_class = dataset["TEST"].class_num
        logger = trainingLogger(dataset["TEST"].name,logs_dir=cfg.DIR.logs, img_dir=cfg.DIR.vis)
        model, optimizers, nets = build_model(cfg)
        test(model, dataloader, logger, cfg)


import pygame
pygame.init()
from random import randint

def move(snake, direction):
    if direction == 0:
        snake.append((snake[-1][0], snake[-1][1]+1))
    elif direction == 1:
        snake.append((snake[-1][0]+1, snake[-1][1]))
    elif direction == 2:
        snake.append((snake[-1][0], snake[-1][1]-1))
    elif direction == 3:
        snake.append((snake[-1][0]-1, snake[-1][1]))
    snake.pop(0)

def draw(surface, snake, tile_size, food):
    w,h = surface.get_size()
    for i in range(w//tile_size+1):
        pygame.draw.line(surface, (50,50,50), ((i+1)*tile_size,0), ((i+1)*tile_size, h))
    for i in range(h//tile_size):
        pygame.draw.line(surface, (50,50,50), (0,(i+1)*tile_size), (w,(i+1)*tile_size))

    prev_pos = snake[0]
    for s in snake:
        if s is prev_pos:
            pygame.draw.rect(surface, (0,150,0), ((s[0]*tile_size+1, s[1]*tile_size+1), (tile_size-1, tile_size-1)))
        else:
            if prev_pos[0] < s[0]:
                pygame.draw.rect(surface, (0,150,0), ((s[0]*tile_size, s[1]*tile_size+1), (tile_size, tile_size-1)))
            elif prev_pos[0] > s[0]:
                pygame.draw.rect(surface, (0,150,0), ((s[0]*tile_size+1, s[1]*tile_size+1), (tile_size, tile_size-1)))
            elif prev_pos[1] > s[1]:
                pygame.draw.rect(surface, (0,150,0), ((s[0]*tile_size+1, s[1]*tile_size+1), (tile_size-1, tile_size)))
            elif prev_pos[1] < s[1]:
                pygame.draw.rect(surface, (0,150,0), ((s[0]*tile_size+1, s[1]*tile_size), (tile_size-1, tile_size)))
        prev_pos = s
    pygame.draw.rect(surface, (150,0,0), ((food[0]*tile_size+1, food[1]*tile_size+1), (tile_size-1, tile_size-1)))

def update(snake, direction, sizex, sizey, food,m=True):
    if m:
        move(snake, direction)
    if snake[-1][0] == -1:
        return True
    if snake[-1][0] == sizex:
        return True
    if snake[-1][1] == -1:
        return True
    if snake[-1][1] == sizey:
        return True

    if snake.count(snake[-1]) != 1:
        return True
    if snake[-1] == food:
        return False
    return None

def new_food(snake, sizex, sizey):
    x, y = randint(0, sizex-1), randint(0, sizey-1)
    for i in range(20):
        if (x,y) not in snake:
            break
        x, y = randint(0, sizex-1), randint(0, sizey-1)
    return x,y

def game_loop():
    SCREEN = pygame.display.set_mode((800,600))

    direction = 0
    tile_size = 20
    sizex = SCREEN.get_width()//tile_size
    sizey = SCREEN.get_height()//tile_size
    snake = [(randint(0,sizex-5), randint(0,sizey-5))]

    food = new_food(snake, sizex, sizey)
    score = 0
    font = pygame.font.SysFont(None, 50)
    txt = font.render("0", True, (255,255,255))

    C = pygame.time.Clock()
    while True:
        SCREEN.fill((0,0,0))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return
                elif event.key == pygame.K_RIGHT:
                    direction = 1
                elif event.key == pygame.K_UP:
                    direction = 2
                elif event.key == pygame.K_DOWN:
                    direction = 0
                elif event.key == pygame.K_LEFT:
                    direction = 3

        a = update(snake, direction, sizex, sizey, food)
        if a:
            return
        elif a == False:
            snake.append(food)
            food = new_food(snake, sizex, sizey)
            score += 1
            txt = font.render(str(score), True, (255,255,255))
        draw(SCREEN, snake, tile_size, food)
        SCREEN.blit(txt, (sizex*tile_size-50,10))
        pygame.display.update()
        C.tick(10)

def generate_path(sizex, sizey):
    begin = (0,0)
    path = [begin]
    return path

def ia():
    SCREEN = pygame.display.set_mode((800,600))

    print(generate_path(20,20))

    direction = 0
    tile_size = 20
    sizex = SCREEN.get_width()//tile_size
    sizey = SCREEN.get_height()//tile_size
    snake = [(0,0) for i in range(1)]

    path = [(0,0)]

#     #sizex et sizey pairs
#     path.append((path[-1][0]+1, path[-1][1]))
#     path.append((path[-1][0], path[-1][1]+1))
#     for i in range((sizey//2)//2):
#         for x in range((sizex//2)-1):
#             path.append((path[-1][0]+1, path[-1][1]))
#             path.append((path[-1][0], path[-1][1]-1))
#             path.append((path[-1][0]+1, path[-1][1]))
#             path.append((path[-1][0], path[-1][1]+1))
#         for j in range(2):
#             path.append((path[-1][0], path[-1][1]+1))
#         for x in range((sizex//2)-1):
#             path.append((path[-1][0]-1, path[-1][1]))
#             path.append((path[-1][0], path[-1][1]-1))
#             path.append((path[-1][0]-1, path[-1][1]))
#             path.append((path[-1][0], path[-1][1]+1))
#         for j in range(2):
#             path.append((path[-1][0], path[-1][1]+1))
#     path.pop(len(path)-1)
#     path.pop(len(path)-1)
#     path.append((path[-1][0]-1, path[-1][1]))
#     for i in range(sizey-2):
#         path.append((0,sizey-2-i))
#         print(path[-1])

    for i in range(1,sizex):
        for j in range(0,sizey-1):
            if i % 2 == 0:
                path.append((i,sizey-j-2))
            else:
                path.append((i,j))
    for i in range(sizex):
        path.append((sizex-1-i,sizey-1))
    for i in range(sizey-2):
        path.append((0,sizey-2-i))

    step = 1

    food = new_food(snake, sizex, sizey)
    score = 0
    font = pygame.font.SysFont(None, 50)
    txt = font.render("0", True, (255,255,255))

    l = len(path)-1
    length = l+1
#     l = min(100, l)
    print(len(path),l)

    speed = 37464
    mode = True
    C = pygame.time.Clock()
    while True:
        SCREEN.fill((0,0,0))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return
                elif event.key == pygame.K_s:
                    mode = not mode
                    if speed == 37464:
                        speed = 20
                    else:
                        speed = 37464
#                 elif event.key == pygame.K_RIGHT:
#                     direction = 1
#                 elif event.key == pygame.K_UP:
#                     direction = 2
#                 elif event.key == pygame.K_DOWN:
#                     direction = 0
#                 elif event.key == pygame.K_LEFT:
#                     direction = 3

        snake.append(path[step])
        u = update(snake, direction, sizex, sizey, food,m=False)
        snake.pop(0)

        a,b = snake[-1]
        pos = None
        for i in range(l):
            x,y = path[(step+1+i)%length]
            if (x,y) in snake:
                break
            elif (x,y) == food:
                break
            if i > 0 and ((max(x,a)-min(x,a) == 1 and max(y,b)-min(y,b) == 0) or (max(x,a)-min(x,a) == 0 and max(y,b)-min(y,b) == 1)):
                for j in range(10):
                    if (path[(step+1+i+j)%length] in snake):
                        a = False
                if a:
                    pos = (step+i)%length#step = (step+i)%len(path)
                break
                #print((x,y),(a,b),food)
        if pos is not None:
            step = pos

        if u:
            return
        elif u == False:
            snake.insert(0, food)
            food = new_food(snake, sizex, sizey)
            score += 1
            txt = font.render(str(score), True, (255,255,255))
        if not mode or step%2==0:
            draw(SCREEN, snake, tile_size, food)
            SCREEN.blit(txt, (sizex*tile_size-50,10))
            pygame.display.update()
        C.tick(speed)
        step += 1
        step %= length

def main():
    SCREEN = pygame.display.set_mode((800,600))

    font = pygame.font.SysFont(None, 100)
    txt = font.render("Press a key ...", True, (255,255,255))

    x, y = SCREEN.get_rect().center
    w,h = txt.get_size()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    quit()
                elif event.key == pygame.K_i:
                    ia()
                else:
                    game_loop()

        SCREEN.fill((0,0,0))
        SCREEN.blit(txt, (x-w/2, y-h/2))
        pygame.display.update()

if __name__ == "__main__":
    main()


"""
web_server 程序搭建一个类
展示自己的网页
"""
from socket import *
from select import select
import re

# 搭建比并发服务,实现http功能
class WebServer:
    def __init__(self, host="0.0.0.0", port=80, html=None):
        self.host = host
        self.port = port
        self.html = html  # 网页的根目录
        self.rlist = []
        self.wlist = []
        self.xlist = []
        # 搭建服务的准备工作
        self.create_socket()
        self.bind()

    def create_socket(self):
        self.sock = socket()
        self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.sock.setblocking(False)

    def bind(self):
        self.address = (self.host, self.port)
        self.sock.bind(self.address)

    # 实现http功能
    def handle(self, connfd):
        # 接收浏览器请求
        request = connfd.recv(1024 * 10).decode()
        # 解析请求 --> 获取请求内容
        pattern = "[A-Z]+\s+(?P<info>/\S*)"
        result = re.match(pattern, request)
        if result:
            # 匹配到内容
            info = result.group("info")
            print("请求内容:", info)
            # 发送响应数据
            self.send_html(connfd, info)
        else:
            # 没有匹配到内容,客户端断开
            connfd.close()
            self.rlist.remove(connfd)
            return

    # 根据请求发送响应数据
    def send_html(self, connfd, info):
        if info == "/":
            filename = self.html + "/index.html"
        else:
            filename = self.html + info
        try:
            f = open(filename,"rb")
        except:
            # 文件不存在
            response = "HTTP/1.1 404 Not Found\r\n"
            response += "Content-Type:text/html\r\n"
            response += "\r\n"
            response += "
Sorry...
"
            response = response.encode()
        else:
            data = f.read()
            response = "HTTP/1.1 200 OK\r\n"
            response += "Content-Type:text/html\r\n"
            response += "Content-Length:%d\r\n"%len(data)
            response += "\r\n"
            response = response.encode()+ data
        finally:
            # 发送响应该客户端
            connfd.send(response)

    # 启动服务
    def start(self):
        self.sock.listen(5)
        print("Listen the port %d" % self.port)
        self.rlist = [self.sock]
        while True:
            # 循环监控
            rs, ws, xs = select(self.rlist, self.wlist, self.xlist)
            # 伴随监控的IO的增多,就绪的IO情况也会复杂化
            # 分类讨论 分成两类 sock   ---   connfd
            for r in rs:
                # 有客户端链接
                if r is self.sock:
                    connfd, addr = r.accept()
                    print("connect from", addr)
                    connfd.setblocking(False)
                    self.rlist.append(connfd)  # 客户端链接套接字放入监控列表中
                else:
                    # 某个客户端发消息了
                    try:
                        self.handle(r)
                    except:
                        r.close()
                        self.rlist.remove(r)

if __name__ == '__main__':
    # 实例化对象
    httpd = WebServer(host="0.0.0.0", port=8000, html="./static")
    # 启动服务
    httpd.start()


import asyncio
import datetime
import json
import logging
import operator
import time
import re
import socket

import websockets
from unidecode import unidecode

from herobrain import networking
from herobrain.analysis import QuestionAnalyser

class GameHandler:
    MORE_LOGS = False

    def __init__(self, socket_addr, headers, interface):
        self._log = logging.getLogger(GameHandler.__name__)
        self._log.info("Initialising on %s" % socket_addr)

        self._socket_addr = socket_addr
        self._socket_headers = headers

        self._interface = interface
        self._event_loop = asyncio.get_event_loop()

    async def _on_new_round(self, question, choices, number, num_questions):
        start_time = time.time()
        self._interface.report_question(question, choices, number, num_questions)

        analyser = QuestionAnalyser(question, choices)
        self._interface.report_analysis(analyser.get_analysis(), number)

        # Find the probability of answers
        answers, analysis = await analyser.find_answers()
        speed = round(time.time() - start_time, 2)

        self._interface.report_prediction(number, answers, speed, analysis)

    async def _on_round_complete(self, answer_counts, correct_answer, eliminated, advancing):
        self._interface.report_round_end(answer_counts, correct_answer, eliminated, advancing)

    async def _handle_event(self, message):
        # New question
        if message["type"] == "question":
            # decode the question
            question_str = unidecode(message["question"])
            choices = [unidecode(ans["text"]) for ans in message["answers"]]

            question_num = message['questionNumber']
            num_questions = message['questionCount']

            await self._on_new_round(question_str, choices, question_num, num_questions)

        # Round is over
        elif message["type"] == "questionSummary":
            answer_counts ={}
            correct = ""
            for answer in message["answerCounts"]:
                ans_str = unidecode(answer["answer"])
                answer_counts[ans_str] = answer["count"]
                if answer["correct"]:
                    correct = ans_str

            advancing = message['advancingPlayersCount']
            eliminated = message['eliminatedPlayersCount']

            await self._on_round_complete(answer_counts, correct, eliminated, advancing)

        elif message["type"] == "interaction":
            pass

    def _is_ending_message(self, message):
        return message["type"] == "broadcastEnded" and "reason" not in message

    async def _keep_open(self, socket):
        self._log.debug("Keeping socket open, pinging every 5 seconds")
        while True:
            try:
                await socket.ping()
            except (websockets.ConnectionClosed, KeyboardInterrupt):
                break
            await asyncio.sleep(5)
        self._log.debug("Ping loop ended")

    async def _game_connection(self):
        self._log.debug("Starting game connection")
        async with websockets.connect(self._socket_addr, extra_headers=self._socket_headers) as socket:
            asyncio.ensure_future(self._keep_open(socket))

            async for msg in socket:
                # We received a new message, remove any weird characters and
                message_data = json.loads(re.sub(r"[\x00-\x1f\x7f-\x9f]", "", msg))

                if GameHandler.MORE_LOGS:
                    self._log.debug(str(message_data))

                # Ah. Not good
                if "error" in message_data and message_data["error"] == "Auth not valid":
                    self._log.debug(message_data)
                    raise RuntimeError("Bad token")

                yield message_data

    async def play(self):
        try:
            while True:
                # Stay in this loop until
                # we get a connection closed error
                async for message in self._game_connection():
                    if self._is_ending_message(message):
                        self._log.info(f"Game ending: {message}")
                        return
                    # Don't stop receiving messages while we wait for the question to be answered
                    # perform the analysis in another coroutine
                    asyncio.ensure_future(self._handle_event(message))

        except (websockets.ConnectionClosed, ConnectionResetError):
            self._log.warning("%s closed unexpectedly" % self._socket_addr)
        except (ConnectionRefusedError, socket.gaierror) as e:
            self._log.error("Could not connect to %s: %s" % (self._socket_addr, e))


import os
import sqlite3
import urllib
from unittest import mock

import pytest
from yarl import URL

from neuro_cli import __version__
from neuro_cli.stats import (
    NEURO_EVENT_CATEGORY,
    SCHEMA,
    add_usage,
    delete_oldest,
    ensure_schema,
    make_record,
    select_oldest,
)

@pytest.fixture
def db() -> sqlite3.Connection:
    db = sqlite3.connect(":memory:")
    db.row_factory = sqlite3.Row
    return db

def check_tables(db: sqlite3.Connection) -> None:
    tables = {}

    for name, sql in db.execute(
        "SELECT name, sql from sqlite_master WHERE type='table'"
    ):
        tables[name] = sql

    assert tables == SCHEMA

def test_ensure_schema_empty(db: sqlite3.Connection) -> None:
    ensure_schema(db)
    check_tables(db)

def test_ensure_schema_invalid(db: sqlite3.Connection) -> None:
    db.execute("CREATE TABLE stats (invalid INTEGER)")
    ensure_schema(db)
    check_tables(db)

def test_add_usage(db: sqlite3.Connection) -> None:
    ensure_schema(db)
    add_usage(db, "neuro run", [{}, {"-s": "cpu-small", "image": None, "cmd": None}])
    add_usage(
        db,
        "neuro ps",
        [{}, {"-s": "('failed', 'running')", "image": None, "cmd": None}],
    )
    ret = list(db.execute("SELECT cmd, args, version FROM stats"))
    assert len(ret) == 2
    assert dict(ret[0]) == {
        "cmd": "neuro run",
        "args": '[{}, {"-s": "cpu-small", "image": null, "cmd": null}]',
        "version": __version__,
    }
    assert dict(ret[1]) == {
        "cmd": "neuro ps",
        "args": '[{}, {"-s": "(\'failed\', \'running\')", "image": null, "cmd": null}]',
        "version": __version__,
    }

def test_select_oldest(db: sqlite3.Connection) -> None:
    ensure_schema(db)
    add_usage(db, "neuro run", [{}, {"-s": "cpu-small", "image": None, "cmd": None}])
    add_usage(
        db,
        "neuro ps",
        [{}, {"-s": "('failed', 'running')", "image": None, "cmd": None}],
    )
    old = select_oldest(db, limit=1)
    assert len(old) == 1
    assert dict(old[0]) == {
        "rowid": mock.ANY,
        "cmd": "neuro run",
        "args": '[{}, {"-s": "cpu-small", "image": null, "cmd": null}]',
        "timestamp": mock.ANY,
        "version": __version__,
    }

def test_delete_oldest(db: sqlite3.Connection) -> None:
    ensure_schema(db)
    add_usage(db, "neuro run", [{}, {"-s": "cpu-small", "image": None, "cmd": None}])
    add_usage(
        db,
        "neuro ps",
        [{}, {"-s": "('failed', 'running')", "image": None, "cmd": None}],
    )
    old = select_oldest(db, limit=1)
    delete_oldest(db, old)
    ret = list(db.execute("SELECT cmd, args, version FROM stats"))
    assert len(ret) == 1
    assert dict(ret[0]) == {
        "cmd": "neuro ps",
        "args": '[{}, {"-s": "(\'failed\', \'running\')", "image": null, "cmd": null}]',
        "version": __version__,
    }

def test_make_record_cli() -> None:
    record = make_record(
        uid="uid",
        url=URL("https://dev.neu.ro/api/v1"),
        cmd="cmd",
        args="args",
        version="version",
    )
    parsed_record = urllib.parse.parse_qs(record)
    assert parsed_record["ec"] == ["CLI"]

def test_make_record_web_shell() -> None:
    os.environ[NEURO_EVENT_CATEGORY] = "WEB-CLI"
    record = make_record(
        uid="uid",
        url=URL("https://dev.neu.ro/api/v1"),
        cmd="cmd",
        args="args",
        version="version",
    )
    del os.environ[NEURO_EVENT_CATEGORY]
    parsed_record = urllib.parse.parse_qs(record)
    assert parsed_record["ec"] == ["WEB-CLI"]


import math
from sympy import *
x=symbols('x')

def factorial(n):
    M=1
    F=1
    while M<float(n):
        M=M+1
        F=F*M
    return (F)

n=input('da el grado de la derivada')
valordex=input('dame un numero x')

fun=(x**2-1)**float(n)
derivada=diff(fun,x,n)
libres={"x":valordex}
print(eval(derivada,{},libres))

#evaluar=diff(fun,x,n).subs(x,valordex)

#polinomio=(1/(2**float(n)*factorial(n)))*evaluar

print(factorial(n))
print(derivada)
#print(evaluar)

#print(polinomio.evalf(x))


"""Thanks PyTec team - FATEC - Jundiaí"""
ops = [1,2,3,4]
op = int(input("""Digite a operação:
                    1 - Soma
                    2 - Subtração
                    3 - Divisão
                    4 - Multiplicação
                    Operação : """))
if op not in ops:
    print('Operacao invalida')
else:
    n1 = float(input('Digite o primeiro valor: ')); n2 = float(input('Digite o segundo valor: '))

class Calc:
    def __init__(self):
        self.sum1 = n1 + n2
        self.subt1 = n1 - n2
        self.div1 = n1 %n2
        self.mult1 = n1 * n2
calcular = Calc()
if op == 1:
    print('o Resultado da soma é: ', calcular.sum1)
elif op == 2:
    print('o Resultado da Subtração é: ', calcular.subt1)
elif op == 3:
    print('o Resultado é Divisão: ', calcular.div1)
else:
    op == 4
    print("o Resultado é Multiplicação: ", calcular.mult1)