"""
mapcache.py: a simple mapped object persistency model, with various backends.
"""
from __future__ import division, print_function

import pickle
from datetime import datetime
import os
import numpy as np

try:
    # Python 2
    from itertools import imap
except ImportError:
    imap = map

class MapCache(object):
    """Base Class for mapped object persistency"""
    def __init__(self, filename):
        raise NotImplementedError()

    def key_exists(self, key):
        raise NotImplementedError()

    def add_row(self, key, val, save=True):
        raise NotImplementedError()

    def get_row(self, key):
        raise NotImplementedError()

    def save(self):
        raise NotImplementedError()

    def load(self):
        raise NotImplementedError()

    def compute(self, func, key, *args, **kwargs):
        if self.key_exists(key):
            return self.get_row(key)
        else:
            return self.add_row(key, func(key, *args, **kwargs))

    def compute_iter(self, func, keys, *args, **kwargs):
        return [self.compute(func, key, *args, **kwargs)
                for key in keys]

class PickleCache(MapCache):
    """Pickle-backed Persistency Helper"""
    def __init__(self, filename):
        self.filename = filename
        self.dct = self.load() if os.path.exists(filename) else {}

    def __iter__(self):
        return iter(self.dct)

    def keys(self):
        return self.dct.keys()

    def values(self):
        return self.dct.values()

    def items(self):
        return self.dct.items()

    def key_exists(self, key):
        return key in self.dct

    def add_row(self, key, val, save=True):
        try:
            self.dct[key] = val
            return val
        finally:
            if save: self.save()

    def get_row(self, key):
        return self.dct[key]

    def save(self):
        with open(self.filename, 'wb') as f:
            pickle.dump(self.dct, f)

    def load(self):
        with open(self.filename, 'rb') as f:
            return pickle.load(f)

class NumpyCache(PickleCache):
    """Numpy storage-backed Persistency Helper"""
    def __init__(self, filename):
        if not filename.endswith('.npy'):
            filename += '.npy'
        PickleCache.__init__(self, filename)

    def save(self):
        np.save(self.filename, _dict_to_array(self.dct))

    def load(self):
        return _array_to_dict(np.load(self.filename))

#----------------------------------------------------------------------
# Utilities:

def _array_to_dict(arr):
    """Convert mapping array to a dictionary"""
    keys = arr['key']
    vals = arr['val']

    if keys.dtype.names is not None:
        names = keys.dtype.names
        keys = (tuple(key[name] for name in names) for key in keys)
    return dict(zip(keys, vals))

def _dtype_of_key(key):
    """Find dtype associated with the object or tuple"""
    dtypes = ['i8', 'f8', 'c16', '<U32']
    def get_type(val):
        for dtype in dtypes:
            if np.issubdtype(type(val), dtype):
                return dtype
        else:
            raise ValueError("Can't find type for {0}".format(val))

    if isinstance(key, tuple):
        return np.dtype(','.join(map(get_type, key)))
    else:
        return np.dtype(get_type(key))

def _dict_to_array(dct):
    """Convert dictionary to a mappint array"""
    keys, vals = map(list, zip(*dct.items()))

    keys_dtype = _dtype_of_key(keys[0])
    keys = np.array(keys, dtype=keys_dtype)

    vals = np.asarray(vals)
    vals_dtype = [('key', keys.dtype),
                  ('val', '{0}{1}'.format(vals.shape[1:],
                                          vals.dtype.name))]

    arr = np.zeros(len(keys), dtype=vals_dtype)
    arr['key'] = keys
    arr['val'] = vals
    return arr

#----------------------------------------------------------------------
# Tools for parallel computation
def compute_parallel(cache, func, keys, save_every=4,
                     func_args=None, func_kwargs=None,
                     parallel=True, client=None):
    """Do a parallel computation of a function"""
    keys = [key for key in keys]
    results = dict(cache.items())
    print(50 * '=')
    print("Starting parallel run of {0} results".format(len(keys)))
    print(" - parallel={0}".format(parallel))

    if results:
        print(" - found {0} previous results in {1}"
              "".format(len(results), cache.filename))
    keys_to_compute = [key for key in keys if key not in results]

    # default arguments
    def iter_function(key, func=func, func_args=func_args,
                      func_kwargs=func_kwargs):
        func_args = func_args or ()
        func_kwargs = func_kwargs or {}
        return func(key, *func_args, **func_kwargs)

    print(" - computing {0} results".format(len(keys_to_compute)))

    # Set up the iterator over results
    if parallel:
        # Use interactive to prevent namespace issues
        from IPython.parallel.util import interactive
        iter_function = interactive(iter_function)
        if client is None:
            from IPython.parallel import Client
            client = Client()
        lbv = client.load_balanced_view()
        results_iter = lbv.imap(iter_function, keys_to_compute,
                                ordered=False)
    else:
        results_iter = imap(iter_function, keys_to_compute)

    # Do the iteration, saving the results occasionally
    print(datetime.now())
    for i, (key, result) in enumerate(results_iter):
        print('{0}/{1}: {2}'.format(i + 1, len(keys_to_compute), result))
        print(' {0}'.format(datetime.now()))
        cache.add_row(key, result, save=((i + 1) % save_every == 0))
    cache.save()

    return np.array([cache.get_row(key) for key in keys])

#----------------------------------------------------------------------
# Unit tests:

def test_inputs_outputs():
    from numpy.testing import assert_equal, assert_allclose

    def check_class(Class, filename, compute, inputs):
        if os.path.exists(filename):
            os.remove(filename)

        # First time: make sure the function is called
        compute.count = 0
        db = Class(filename)
        res1 = db.compute_iter(compute, inputs)
        assert_equal(compute.count, 5)

        compute.count = 0
        res2 = db.compute_iter(compute, inputs)
        assert_equal(compute.count, 0)
        db.save()

        compute.count = 0
        db2 = Class(filename)
        res3 = db.compute_iter(compute, inputs)
        assert_equal(compute.count, 0)

        assert_allclose(res1, res2)
        assert_allclose(res2, res3)

        if os.path.exists(filename):
            os.remove(filename)
    def countevals(f):
        def g(*args, **kwargs):
            g.count += 1
            return f(*args, **kwargs)
        g.count = 0
        return g

    @countevals
    def compute_simple(i):
        return i ** 2

    @countevals
    def compute_simple_arr(i):
        return i ** np.arange(4)

    @countevals
    def compute_multikey(t):
        a, b, c = t
        return [a ** b, float(c)]
    inputs_multi = [(i, (i + 1) / 5, '{0}.{1}'.format(int(i), int(i + 2)))
                    for i in range(5)]

    for compute, inputs in [(compute_simple, range(5)),
                            (compute_simple_arr, range(5)),
                            (compute_multikey, inputs_multi)]:
        for c, f in [(PickleCache, 'tmp.pkl'), (NumpyCache, 'tmp.npy')]:
            yield check_class, c, f, compute, inputs

def test_utils():
    from numpy.testing import assert_equal
    D = {(1, 2.5, 'abc'): np.arange(0, 3),
         (2, 3.5, 'def'): np.arange(4, 7)}
    arr = _dict_to_array(D)
    D2 = _array_to_dict(arr)
    arr2 = _dict_to_array(D2)
    assert_equal(D, D2)
    assert_equal(arr, arr2)

if __name__ == '__main__':
    import nose; nose.runmodule()


from PyQt5.QtGui import QPainter
from random import random
from modes import aimode
from constants import Directions as d, Types as t, StartingPositions as s, Colors as c, Options as o, TYPE_TIMER
import expections
import objects

class PowerUp(aimode.AIMode):

    def __init__(self, multiplayer):
        super().__init__(1)
        self.counter = 0
        self.multiplayer = multiplayer

    def start(self):
        self.car_list = []
        self.c1 = self.new_car(s.START_X_2P_1, s.START_Y_2P_1, d.NORTH, c.BLUE, o.PLAYER_NAME, 30)
        if self.multiplayer:
            self.c2 = self.new_car(s.START_X_2P_2, s.START_Y_2P_2, d.SOUTH, c.PINK, 'Player2', 30)
        else:
            self.c2 = self.new_ai(s.START_X_2P_2, s.START_Y_2P_2, d.SOUTH, c.RED, 'AI', 30)
        self.counter = 0
        self.powerup_1 = 0
        self.powerup_2 = 0
        self.obj_arr = [None, None]

    def move(self, car):
        """ Es wird ein Auto (car) bewegt, dabei wird geprüft ob es ein PowerUp einsammelt
        oder mit der Mauer, sich selbst oder einem anderen Auto kollidiert
        """
        try:
            car.move()
            for i in range(len(self.obj_arr)):
                if self.obj_arr[i] != None and self.obj_arr[i].collect(car):
                    self.obj_arr[i].action(car)
                    self.obj_arr[i] = None
            for i in self.car_list:
                if i != car:
                    self.test_collision(car, i)
        except(expections.OutOfMapError, expections.SelfDestruction, expections.Collision):
            car.lost = True

    def update_object(self):
        """ Es werden falls nicht vorhanden passende PowerUps auf dem Spielfeld plaziert
        """
        # Es ist immer ein PowerUp des Typen PLUS_LENGTH auf dem Spielfeld und befindet sich immer in obj_arr[0]
        if self.obj_arr[0] == None: # Falls kein solches PowerUp vorhanden, wird sofort eins erzeugt
            self.obj_arr[0] = objects.Object(self.car_list, self.obj_arr, t.PLUS_LENGTH)
        if self.counter == 0:       # Jedes PowerUp, das in obj_arr[1] liegt, hat einen Timer, wenn dieser
            self.obj_arr[1] = None  # abgelaufen ist wird das PowerUp entfernt
        else:
          self.counter = self.counter - 1
        # In obj_arr[1] liegen die anderen PowerUps, jedoch muss nicht immer eins vorhanden sein
        if self.obj_arr[1] == None:
            if random() < 0.05: # mit einer Wahrscheinlichkeit von 5% wird ein neues PowerUp erzeugt, falls keines vorhanden ist
                r = random()
                if r < 0.35:                                        # 35%
                    type = t.MINUS_LENGTH
                    self.counter = TYPE_TIMER[t.MINUS_LENGTH]
                elif r < 0.70:                                      # 35%
                    type = t.DESTROY_TAIL
                    self.counter = TYPE_TIMER[t.DESTROY_TAIL]
                elif r < 0.82 and self.powerup_1 < 2:               # 12%, wird maximal zwei mal im Spiel gespawnt
                    type = t.SELFDMG_OF
                    self.counter = TYPE_TIMER[t.SELFDMG_OF]
                    self.powerup_1 = self.powerup_1 + 1
                elif r < 0.94 and r > 0.82 and self.powerup_2 < 2:   # 12%, wird maximal zwei mal im Spiel gespawnt
                    type = t.BORDERDMG_OF
                    self.counter = TYPE_TIMER[t.BORDERDMG_OF]
                    self.powerup_2 = self.powerup_2 + 1
                else:                                                # 6% (- 30%), erhöhte Wahrscheinlichkeit, falls SELFDMG_OF und/oder                                                    # BORDER
                    type = t.LOSE                                    # BORDERDMG_OF schon zwei mal gespawnt sind
                    self.counter = TYPE_TIMER[t.LOSE]
                self.obj_arr[1] = objects.Object(self.car_list, self.obj_arr, type)

    def timerEvent(self, e):
        self.update_object()
        self.cars_move(self.car_list)
        self.update_cars(self.car_list)
        self.update()

    def paintEvent(self, e):
        painter = QPainter(self)
        for i in self.obj_arr:
            if i != None:
                self.draw_object(painter, i.x, i.y, i.color)
        self.draw_cars(painter, self.car_list)
        painter.end()


import json

from gootool.youtube import YoutubeMe

if __name__ == '__main__':
    youtube = YoutubeMe(
        credential='./.credentials/youtube-credential.json',
        client_secret='./client_secret.json',
        )
    results = youtube.get_playlists()
    with open('youtube-lists.json', 'w', encoding='utf-8') as f:
        json.dump(results, f, ensure_ascii=False, indent=2)


# ----------------------------------------------------------------------------------
# Copyright ENS, INRIA, CNRS
# Contributors: Romain Brette (brette@di.ens.fr) and Dan Goodman (goodman@di.ens.fr)
#
# Brian is a computer program whose purpose is to simulate models
# of biological neural networks.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software.  You can  use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
# ----------------------------------------------------------------------------------
#
'''
Threshold mechanisms
'''

import re
from random import sample # Python standard random module (sample is different)

from numpy import clip, Inf
from numpy.random import rand, randn
try:
    import weave
except ImportError:
    try:
        from scipy import weave
    except ImportError:
        weave = None
from scipy import random

from brian.clock import guess_clock
from brian.globalprefs import get_global_preference
from brian.inspection import namespace, get_identifiers
from brian.log import log_warn
from brian.units import check_units, second, msecond, mvolt
from brian.utils.approximatecomparisons import is_approx_equal

__all__ = ['Threshold', 'FunThreshold', 'VariableThreshold', 'NoThreshold',
          'EmpiricalThreshold', 'SimpleFunThreshold', 'PoissonThreshold',
          'HomogeneousPoissonThreshold', 'StringThreshold']

CThreshold = PythonThreshold = None

def select_threshold(expr, eqs, level=0):
    '''
    Automatically selects the appropriate Threshold object from a string.

    Matches the following patterns:

    var_name > or >= const : Threshold
    var_name > or >= var_name : VariableThreshold
    others : StringThreshold
    '''
    global CThreshold, PythonThreshold
    use_codegen = (get_global_preference('usecodegen') and
                   get_global_preference('usecodegenthreshold'))
    use_weave = (get_global_preference('useweave') and
                 get_global_preference('usecodegenweave'))
    if use_codegen:
        if CThreshold is None:
            from brian.experimental.codegen.threshold import (CThreshold,
                                                              PythonThreshold)
        if use_weave:
            log_warn('brian.threshold', 'Using codegen CThreshold')
            return CThreshold(expr, level=level + 1)
        else:
            log_warn('brian.threshold', 'Using codegen PythonThreshold')
            return PythonThreshold(expr, level=level + 1)
    # plan:
    # - see if it matches A > B or A >= B, if not select StringThreshold
    # - check if A, B both match diffeq variable names, and if so
    #   select VariableThreshold
    # - check that A is a variable name, if not select StringThreshold
    # - extract all the identifiers from B, and if none of them are
    #   callable, assume it is a constant, try to eval it and then use
    #   Threshold. If not, or if eval fails, use StringThreshold.
    # This misses the case of e.g. V>10*mV*exp(1) because exp will be
    # callable, but in general a callable means that it could be
    # non-constant.
    expr = expr.strip()
    eqs.prepare()
    ns = namespace(expr, level=level + 1)
    s = re.search(r'^\s*(\w+)\s*>=?(.+)', expr)
    if not s:
        return StringThreshold(expr, level=level + 1)
    A = s.group(1)
    B = s.group(2).strip()
    if A not in eqs._diffeq_names:
        return StringThreshold(expr, level=level + 1)
    if B in eqs._diffeq_names:
        return VariableThreshold(B, A)
    try:
        vars = get_identifiers(B)
    except SyntaxError:
        return StringThreshold(expr, level=level + 1)
    all_vars = eqs._eq_names + eqs._diffeq_names + eqs._alias.keys() + ['t']
    for v in vars:
        if v not in ns or v in all_vars or callable(ns[v]):
            return StringThreshold(expr, level=level + 1)
    try:
        val = eval(B, ns)
    except:
        return StringThreshold(expr, level=level + 1)
    return Threshold(val, A)

class Threshold(object):
    '''
    All neurons with a specified state variable above a fixed value fire a spike.

    **Initialised as:** ::

        Threshold([threshold=1*mV[,state=0])

    with arguments:

    ``threshold``
        The value above which a neuron will fire.
    ``state``
        The state variable which is checked.

    **Compilation**

    Note that if the global variable ``useweave`` is set to ``True``
    then this function will use a ``C++`` accelerated version which
    runs approximately 3x faster.
    '''

    def __init__(self, threshold=1 * mvolt, state=0):
        self.threshold = threshold
        self.state = state
        self._useaccel = get_global_preference('useweave')
        self._cpp_compiler = get_global_preference('weavecompiler')
        self._extra_compile_args = ['-O3']
        if self._cpp_compiler == 'gcc':
            self._extra_compile_args += get_global_preference('gcc_options') # ['-march=native', '-ffast-math']

    def __call__(self, P):
        '''
        Checks the threshold condition and returns spike times.
        P is the neuron group.

        Note the accelerated version runs 3x faster.
        '''
        if self._useaccel:
            spikes = P._spikesarray
            V = P.state_(self.state)
            Vt = float(self.threshold)
            N = int(len(P))
            code = """
                    int numspikes=0;
                    for(int i=0;i<N;i++)
                        if(V(i)>Vt)
                            spikes(numspikes++) = i;
                    return_val = numspikes;
                    """
            # WEAVE NOTE: set the environment variable USER if your username has a space
            # in it, say set USER=DanGoodman if your username is Dan Goodman, this is
            # because weave uses this to create file names, but doesn't correctly send these
            # values to the compiler, causing problems.
            numspikes = weave.inline(code, ['spikes', 'V', 'Vt', 'N'],
                                     compiler=self._cpp_compiler,
                                     type_converters=weave.converters.blitz,
                                     extra_compile_args=self._extra_compile_args)
            # WEAVE NOTE: setting verbose=True in the weave.inline function may help in
            # finding errors.
            return spikes[0:numspikes]
        else:
            return ((P.state_(self.state) > self.threshold).nonzero())[0]

    def __repr__(self):
        return '%s(threshold=%s, state=%s)' % (self.__class__.__name__,
                                               repr(self.threshold),
                                               repr(self.state))

    def __str__(self):
        return ('Threshold mechanism with value=' + str(self.threshold) +
                ' acting on state ' + str(self.state))

class StringThreshold(Threshold):
    '''
    A threshold specified by a string expression.

    Initialised with arguments:

    ``expr``
        The expression used to test whether a neuron has fired a spike.
        Should be a single statement that returns a value. For example,
        ``'V>50*mV'`` or ``'V>Vt'``.
    ``level``
        How many levels up in the calling sequence to look for
        names in the namespace. Usually 0 for user code.
    '''
    def __init__(self, expr, level=0):
        self._namespace, unknowns = namespace(expr, level=level + 1, return_unknowns=True)
        self._vars = unknowns
        self._expr = expr
        self._code = compile(expr, "StringThreshold", "eval")
        class Replacer(object):
            def __init__(self, func, n):
                self.n = n
                self.func = func
            def __call__(self):
                return self.func(self.n)
        self._Replacer = Replacer

    def __call__(self, P):
        for var in self._vars: # couldn't we do this just once?
            self._namespace[var] = P.state(var)
        self._namespace['rand'] = self._Replacer(rand, len(P))
        self._namespace['randn'] = self._Replacer(randn, len(P))
        return eval(self._code, self._namespace).nonzero()[0]

    def __repr__(self):
        return '%s(%s)' % (self.__class__.__name__, repr(self._expr))

    def __str__(self):
        return '%s using expression "%s"' % (self.__class__.__name__,
                                             str(self._expr))

class NoThreshold(Threshold):
    '''
    No thresholding mechanism.

    **Initialised as:** ::

        NoThreshold()
    '''
    def __init__(self):
        pass

    def __call__(self, P):
        return []

    def __repr__(self):
        return "NoThreshold()"

    def __str__(self):
        return "No threshold"

class FunThreshold(Threshold):
    '''
    Threshold mechanism with a user-specified function.

    **Initialised as:** ::

        FunThreshold(thresholdfun)

    where ``thresholdfun`` is a function with one argument,
    the 2d state value array, where each row is an array of
    values for one state, of length N for N the number of
    neurons in the group. For efficiency, data are numpy
    arrays and there is no unit checking.

    Note: if you only need to consider one state variable,
    use the :class:`SimpleFunThreshold` object instead.
    '''
    def __init__(self, thresholdfun):
        self.thresholdfun = thresholdfun # Threshold function

    def __call__(self, P):
        '''
        Checks the threshold condition and returns spike times.
        P is the neuron group.
        '''
        spikes = (self.thresholdfun(*P._S).nonzero())[0]
        return spikes

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.thresholdfun)

    def __str__(self):
        return 'Functional threshold mechanism'

class SimpleFunThreshold(FunThreshold):
    '''
    Threshold mechanism with a user-specified function.

    **Initialised as:** ::

        FunThreshold(thresholdfun[,state=0])

    with arguments:

    ``thresholdfun``
        A function with one argument, the array of values for
        the specified state variable. For efficiency, this is
        a numpy array, and there is no unit checking.
    ``state``
        The name or number of the state variable to pass to
        the threshold function.

    **Sample usage:** ::

        FunThreshold(lambda V:V>=Vt,state='V')
    '''
    def __init__(self, thresholdfun, state=0):
        self.thresholdfun = thresholdfun # Threshold function
        self.state = state

    def __call__(self, P):
        '''
        Checks the threshold condition and returns spike times.
        P is the neuron group.
        '''
        spikes = (self.thresholdfun(P.state_(self.state)).nonzero())[0]
        #P.LS[spikes]=P.clock.t # Time of last spike (this line should be general)
        return spikes

class VariableThreshold(Threshold):
    '''
    Threshold mechanism where one state variable is compared to another.

    **Initialised as:** ::

        VariableThreshold([threshold_state=1[,state=0]])

    with arguments:

    ``threshold_state``
        The state holding the lower bound for spiking.
    ``state``
        The state that is checked.

    If ``x`` is the value of state variable ``threshold_state`` on neuron
    ``i`` and ``y`` is the value of state variable ``state`` on neuron
    ``i`` then neuron ``i`` will fire if ``y>x``.

    Typically, using this class is more time efficient than writing
    a custom thresholding operation.

    **Compilation**

    Note that if the global variable ``useweave`` is set to ``True``
    then this function will use a ``C++`` accelerated version.
    '''
    def __init__(self, threshold_state=1, state=0):
        self.threshold_state = threshold_state # State variable representing the threshold
        self.state = state
        self._useaccel = get_global_preference('useweave')
        self._cpp_compiler = get_global_preference('weavecompiler')
        self._extra_compile_args = ['-O3']
        if self._cpp_compiler == 'gcc':
            self._extra_compile_args += get_global_preference('gcc_options') # ['-march=native', '-ffast-math']

    def __call__(self, P):
        '''
        Checks the threshold condition, resets and returns spike times.
        P is the neuron group.
        '''
        if self._useaccel:
            spikes = P._spikesarray
            V = P.state_(self.state)
            Vt = P.state_(self.threshold_state)
            N = int(len(P))
            code = """
                    int numspikes=0;
                    for(int i=0;i<N;i++)
                        if(V(i)>Vt(i))
                            spikes(numspikes++) = i;
                    return_val = numspikes;
                    """
            numspikes = weave.inline(code, ['spikes', 'V', 'Vt', 'N'], \
                                     compiler=self._cpp_compiler,
                                     type_converters=weave.converters.blitz,
                                     extra_compile_args=self._extra_compile_args)
            return spikes[0:numspikes]
        else:
            return ((P.state_(self.state) > P.state_(self.threshold_state)).nonzero())[0]

    def __repr__(self):
        return '%s(threshold_state=%s, state=%s)' % (self.__class__.__name__,
                                                     repr(self.threshold_state),
                                                     repr(self.state))

    def __str__(self):
        return '%s comparing "%s" to the threshold "%s"' % (self.__class__.__name__,
                                                            self.state,
                                                            self.threshold_state)

class EmpiricalThreshold(Threshold):
    '''
    Empirical threshold, e.g. for Hodgkin-Huxley models.

    In empirical models such as the Hodgkin-Huxley method, after a spike
    neurons are not instantaneously reset, but reset themselves
    as part of the dynamical equations defining their behaviour. This class
    can be used to model that. It is a simple threshold mechanism that
    checks e.g. ``V>=Vt`` but it only does so for neurons that haven't
    recently fired (giving the dynamical equations time to reset
    the values naturally). It should be used in conjunction with the
    :class:`NoReset` object.

    **Initialised as:** ::

        EmpiricalThreshold([threshold=1*mV[,refractory=1*ms[,state=0[,clock]]]])

    with arguments:

    ``threshold``
        The lower bound for the state variable to induce a spike.
    ``refractory``
        The time to wait after a spike before checking for spikes again.
    ``state``
        The name or number of the state variable to check.
    ``clock``
        If this object is being used for a :class:`NeuronGroup` which doesn't
        use the default clock, you need to specify its clock here.
    '''
    @check_units(refractory=second)
    def __init__(self, threshold=1 * mvolt, refractory=1 * msecond, state=0, clock=None):
        self.threshold = threshold # Threshold value
        self.state = state
        clock = guess_clock(clock)
        self.refractory = int(refractory / clock.dt)
        # this assumes that if the state stays over the threshold, and say
        # refractory=5ms the user wants spiking at 0ms 5ms 10ms 15ms etc.
        if is_approx_equal(self.refractory * clock.dt, refractory) and self.refractory > 0:
            self.refractory -= 1

    def __call__(self, P):
        '''
        Checks the threshold condition, resets and returns spike times.
        P is the neuron group.
        '''
        #spikes=where((P._S[0,:]>self.Vt) & ((P.LS<P.clock.t-self.refractory) | (P.LS==P.clock.t)))[0]
        spikescond = P.state_(self.state) > self.threshold
        spikescond[P.LS[0:self.refractory]] = False
        return spikescond.nonzero()[0]
        #P.LS[spikes]=P.clock.t # Time of last spike (this line should be general)
        #return spikes

class PoissonThreshold(Threshold):
    '''
    Poisson threshold: a spike is produced with some probability S[0]*dt,
    or S[state]*dt.
    '''
    # TODO: check the state has units in Hz
    def __init__(self, state=0):
        self.state = state

    def __call__(self, P):
        return (random.rand(len(P)) < P.state_(self.state)[:] * P.clock.dt).nonzero()[0]

    def __repr__(self):
        return '%s(state=%s)' % (self.__class__.__name__,
                                 repr(self.state))

    def __str__(self):
        return '%s using state variable %s' % (self.__class__.__name__,
                                               str(self.state))

class HomogeneousPoissonThreshold(PoissonThreshold):
    '''
    Poisson threshold for spike trains with identical rates.
    The underlying NeuronGroup has only one state variable.
    N.B.: "homogeneous" is meant in the spatial (not temporal) sense,
    the rate may change in time.
    '''
    def __call__(self, P):
        # N.B.: is "float" necessary?
        # Other possibility to avoid sorting: use an exponential distribution
        n = random.poisson(float(len(P) * P.clock.dt * clip(P._S[self.state][0], 0, Inf))) # number of spikes
        if n > len(P):
            n = len(P)
            log_warn('brian.HomogeneousPoissonThreshold', 'HomogeneousPoissonThreshold cannot generate enough spikes.')
        spikes = sample(xrange(len(P)), n)
        spikes.sort() # necessary only for subgrouping
        return spikes


from django.urls import path, include
from django.conf import settings

urlpatterns = [
    path('', include('apps.core.urls')),
    path('budget/', include('apps.budget.urls')),
    path('mileage/', include('apps.mileage.urls')),
    path('episodetracker/', include('apps.episodetracker.urls')),
]

if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        path('__debug__/', include(debug_toolbar.urls)),
    ] + urlpatterns


from ast import Sub
import gzip
import hashlib
import os
from typing import Dict, List
from urllib.request import urlretrieve

import boto3
from botocore import UNSIGNED
from botocore.client import Config
from canvasapi import Canvas
import numpy as np
import numpy as np
import pandas as pd
from patsy import dmatrices
import plotly.graph_objects as go
import plotly.express as px
from plotly import colors
from plotly.subplots import make_subplots
import quandl
import requests
from scipy import stats
import statsmodels.api as sm
from tqdm.notebook import tqdm

# =============================================================================
# Credentials
# =============================================================================

quandl.ApiConfig.api_key = os.getenv("QUANDL_API_KEY")

# =============================================================================
# Canvas
# =============================================================================

def download_files(filename_frag: str):
    """Downloads files from Canvas with `filename_frag` in filename."""

    url = os.getenv("CANVAS_URL")
    token = os.getenv("CANVAS_TOKEN")

    course_id = 33395
    canvas = Canvas(url, token)
    course = canvas.get_course(course_id)

    for f in course.get_files():
        if filename_frag in f.filename:
            print(f.filename, f.id)
            file = course.get_file(f.id)
            file.download(file.filename)

# =============================================================================
# Reading Data
# =============================================================================

def get_trade_data(pair: str, year: str, path: str = "accumulation_opportunity/data"):
    """Reads local gzipped trade data file and return dataframe."""

    dtypes = {
        "PriceMillionths": int,
        "Side": int,
        "SizeBillionths": int,
        "timestamp_utc_nanoseconds": int,
    }

    filename = f"trades_narrow_{pair}_{year}.delim.gz"
    delimiter = {"2018": "|", "2021": "\t"}[year]

    with gzip.open(f"{path}/{filename}") as f:
        df = pd.read_csv(f, delimiter=delimiter, usecols=dtypes.keys(), dtype=dtypes)

    df.timestamp_utc_nanoseconds = pd.to_datetime(df.timestamp_utc_nanoseconds)

    return df.set_index("timestamp_utc_nanoseconds")

# =============================================================================
# Price Data
# =============================================================================

def get_table(dataset_code: str, database_code: str = "ZACKS"):
    """Downloads Zacks fundamental table from export api to local zip file."""

    url = (
        f"https://www.quandl.com/api/v3/datatables/{database_code}/{dataset_code}.json"
    )
    r = requests.get(
        url, params={"api_key": os.getenv("QUANDL_API_KEY"), "qopts.export": "true"}
    )
    data = r.json()
    urlretrieve(
        data["datatable_bulk_download"]["file"]["link"],
        f"zacks_{dataset_code.lower()}.zip",
    )

def load_table_files(table_filenames: Dict):
    """Loads Zacks fundamentals tables from csv files."""

    dfs = []
    for v in tqdm(table_filenames.values()):
        dfs.append(pd.read_csv(v, low_memory=False))

    return dfs

def get_hash(string: str) -> str:
    """Returns md5 hash of string."""

    return hashlib.md5(str(string).encode()).hexdigest()

def fetch_ticker(
    dataset_code: str, query_params: Dict = None, database_code: str = "EOD"
):
    """Fetches price data for a single ticker."""

    url = f"https://www.quandl.com/api/v3/datasets/{database_code}/{dataset_code}.json"

    params = dict(api_key=os.getenv("QUANDL_API_KEY"))
    if query_params is not None:
        params = dict(**params, **query_params)

    r = requests.get(url, params=params)

    dataset = r.json()["dataset"]
    df = pd.DataFrame(
        dataset["data"], columns=[c.lower() for c in dataset["column_names"]]
    )
    df["ticker"] = dataset["dataset_code"]

    return df.sort_values("date")

def fetch_all_tickers(tickers: List, query_params: Dict) -> pd.DataFrame:
    """Fetches price data from Quandl for each ticker in provide list and
    returns a dataframe of them concatenated together.
    """

    df_prices = pd.DataFrame()
    for t in tqdm(tickers):
        try:
            df = fetch_ticker(t, query_params)
            df_prices = pd.concat([df_prices, df])
        except:
            print(f"Couldn't get prices for {t}.")

    not_missing_data = (
        df_prices.set_index(["ticker", "date"])[["adj_close"]]
        .unstack("date")
        .isna()
        .sum(axis=1)
        == 0
    )

    df_prices = df_prices[
        df_prices.ticker.isin(not_missing_data[not_missing_data].index)
    ]

    return df_prices.set_index(["ticker", "date"])

# =============================================================================
# Download Preprocessed Files from S3
# =============================================================================

def upload_s3_file(filename: str):
    """Uploads file to S3. Requires credentials with write permissions to exist
    as environment variables.
    """

    client = boto3.client("s3")
    client.upload_file(filename, "finm33150", filename)

def download_s3_file(filename: str):
    """Downloads file from read only S3 bucket."""

    client = boto3.client("s3", config=Config(signature_version=UNSIGNED))
    client.download_file("finm33150", filename, filename)

# =============================================================================
# Prepare Data
# =============================================================================

def get_data(date_range: pd.date_range) -> pd.DataFrame:
    """
    Loads spreads and prices, calculates spreads and returns dataframe.
    """

    # load spreads data
    df_spreads = (
        pd.read_csv("Liq5YCDS.delim", sep="\t", index_col=0, parse_dates=["date"])
        .set_index(["ticker", "date"])
        .unstack("ticker")
        .loc[date_range, ["spread5y"]]
    )
    df_returns = np.log(df_spreads / df_spreads.shift())
    df_returns.columns = df_returns.columns.set_levels(["r_spread"], level=0)
    df_spreads = pd.concat([df_spreads, df_returns], axis=1)

    # load prices
    df_prices = (
        pd.read_csv("df_prices_new.csv", parse_dates=["date"])
        .set_index(["ticker", "date"])[["adj_close"]]
        .unstack("ticker")
        .reindex(date_range)
        .ffill()
    )
    assert len(df_prices) == len(df_spreads)

    df_prices.columns = df_prices.columns.set_names(["series", "ticker"])
    df_returns = np.log(df_prices / df_prices.shift())
    df_returns.columns = df_returns.columns.set_levels(["r_equity"], level=0)
    df_prices = pd.concat([df_prices, df_returns], axis=1)
    df_data = pd.concat([df_prices, df_spreads], axis=1).iloc[1:]
    df_data = df_data.stack("ticker")
    df_data.index = df_data.index.set_names(["date", "ticker"])

    # Excludes return of subject security
    df_data["r_index"] = (
        df_data.groupby("date")["r_spread"].transform("sum") - df_data["r_spread"]
    ) / (df_data.groupby("date")["r_spread"].transform("count") - 1)

    df_data["r_spy"] = df_data.index.get_level_values("date").map(
        df_data[df_data.index.get_level_values("ticker") == "SPY"]
        .reset_index()
        .set_index("date")
        .r_equity
    )

    df_data = df_data[df_data.index.get_level_values("ticker") != "SPY"]
    df_data.index = df_data.index.remove_unused_levels()

    return df_data

def get_daily_data():
    """
    Loads spreads and prices, calculates spreads and returns dataframe on daily
    basis with no re-indexing.
    """

    df_spreads = (
        pd.read_csv("Liq5YCDS.delim", sep="\t", index_col=0, parse_dates=["date"])
        .set_index(["ticker", "date"])
        .unstack("ticker")[["spread5y"]]
    )
    df_returns = np.log(df_spreads / df_spreads.shift())
    df_returns.columns = df_returns.columns.set_levels(["r_spread"], level=0)
    df_spreads = pd.concat([df_spreads, df_returns], axis=1)

    df_prices = (
        pd.read_csv("df_prices_new.csv", parse_dates=["date"])
        .set_index(["ticker", "date"])[["adj_close"]]
        .unstack("ticker")
        .ffill()
    )

    df_prices.columns = df_prices.columns.set_names(["series", "ticker"])
    df_returns = np.log(df_prices / df_prices.shift())
    df_returns.columns = df_returns.columns.set_levels(["r_equity"], level=0)
    df_prices = pd.concat([df_prices, df_returns], axis=1)
    df_data = pd.concat([df_prices, df_spreads], axis=1).iloc[1:]
    df_data = df_data.stack("ticker")
    df_data.index = df_data.index.set_names(["date", "ticker"])

    # Excludes return of subject security
    df_data["r_index"] = (
        df_data.groupby("date")["r_spread"].transform("sum") - df_data["r_spread"]
    ) / (df_data.groupby("date")["r_spread"].transform("count") - 1)

    df_data["r_spy"] = df_data.index.get_level_values("date").map(
        df_data[df_data.index.get_level_values("ticker") == "SPY"]
        .reset_index()
        .set_index("date")
        .r_equity
    )

    df_data = df_data[df_data.index.get_level_values("ticker") != "SPY"]
    df_data.index = df_data.index.remove_unused_levels()
    df_data = df_data.loc["2018-01-03":"2020-04-20"]

    return df_data

# =============================================================================
# Regression
# =============================================================================

def get_errors(
    df_data: pd.DataFrame,
    model: str = "OLS",
    penalty: str = "Huber",
    weeks_in: int = 16,
    weeks_out: int = 1,
    formula: str = "r_equity ~ r_spy + 1",
):

    errors_list = []
    for day in df_data.index.levels[0][: -(weeks_in + weeks_out - 1)]:
        df_in = df_data.loc[day : day + pd.DateOffset(weeks=weeks_in - 1)]
        df_out = df_data.loc[
            day
            + pd.DateOffset(weeks=weeks_in) : day
            + pd.DateOffset(weeks=weeks_in + weeks_out - 1)
        ]

        for ticker in df_in.index.levels[1]:
            y_in, X_in = dmatrices(
                formula,
                data=df_in.loc[df_in.index.get_level_values("ticker") == ticker],
                return_type="dataframe",
            )

            if model == "OLS":
                res = sm.OLS(y_in, X_in).fit()
            else:
                if penalty == "Tukey":
                    res = sm.RLM(y_in, X_in, M=sm.robust.norms.TukeyBiweight()).fit()
                else:
                    res = sm.RLM(y_in, X_in).fit()

            y_out, X_out = dmatrices(
                formula,
                data=df_out.loc[df_out.index.get_level_values("ticker") == ticker],
                return_type="dataframe",
            )
            df_e = (res.predict(X_out) - y_out[formula.split(" ~ ")[0]]).to_frame(
                name="resid"
            )
            df_e["scale"] = res.scale
            df_e["sresid"] = df_e.resid / df_e.scale
            df_e["model_date"] = df_in.reset_index().date.min()

            df_e["distance"] = df_e.groupby(["date"]).ngroup() + 1
            errors_list.append(df_e)

    return pd.concat(errors_list), res.summary()

def get_contemp_resids(df_data: pd.DataFrame) -> pd.DataFrame:
    """
    Wrapper function to return contemporaneous equity and cds
    residuals.
    """

    df_eq_errors, _ = get_errors(df_data, model="OLS")
    df_cds_errors, _ = get_errors(
        df_data, model="RLM", formula="r_spread ~ r_equity + r_index + 1"
    )

    df_resid = pd.concat(
        [df_cds_errors.resid, df_eq_errors.resid.groupby("ticker").shift()], axis=1
    )

    df_resid.columns = ["cds_resid", "eq_resid"]

    return df_resid.dropna()

def get_predicted_resids(df_resid: pd.DataFrame) -> pd.DataFrame:
    """
    Calculates regression coefficients and predicted residuals using
    discounted least squares for exponentially weighted and boxcar
    windows of 4 to 60 weeks.
    """

    df_resid = df_resid.unstack("ticker")
    win_lengths = np.arange(6, 47, 2)

    col_list = []
    for w in win_lengths:
        for s in df_resid.columns.levels[1]:
            df_pair = df_resid.loc[:, [("cds_resid", s), ("eq_resid", s)]]
            df_pair.columns = ["cds_resid", "eq_resid"]
            for win_type, df_win in {
                "exp_wm": df_pair.ewm(alpha=1 / w),
                "boxcar": df_pair.rolling(window=2 * w, min_periods=4),
            }.items():
                df_cov = df_win.cov()
                df_mean = df_win.mean()

                s_var = df_cov["eq_resid"].xs("eq_resid", level=1)
                s_var.name = ("var_x", win_type, f"t_{w:02d}", s)

                s_cov = df_cov["eq_resid"].xs("cds_resid", level=1)
                s_cov.name = ("cov_xy", win_type, f"t_{w:02d}", s)

                s_beta_1 = s_cov / s_var
                s_beta_1.name = ("beta_1", win_type, f"t_{w:02d}", s)

                s_beta_0 = s_beta_1 * df_mean.eq_resid - df_mean.cds_resid
                s_beta_0.name = ("beta_0", win_type, f"t_{w:02d}", s)

                s_resid_sq = (
                    s_beta_0 + df_pair.eq_resid * s_beta_1 - df_pair.cds_resid
                ).pow(2)
                s_resid_sq.name = ("resid_sq", win_type, f"t_{w:02d}", s)

                s_error_sq = (
                    df_pair.cds_resid.loc[~s_resid_sq.isna()]
                    - df_pair.cds_resid.loc[~s_resid_sq.isna()].mean()
                ).pow(2)
                s_error_sq.name = ("error_sq", win_type, f"t_{w:02d}", s)

                col_list.extend(
                    [s_resid_sq, s_error_sq, s_beta_0, s_beta_1, s_var, s_cov]
                )

    df_betas = pd.concat(col_list, axis=1)
    df_betas.columns.names = ["stat", "win_type", "win_length", "ticker"]
    df_betas = df_betas.stack("ticker")
    df_betas = df_betas.swaplevel("stat", "win_type", axis=1)

    return df_betas.sort_index(axis=1)

def get_ticker_rsq(df_resid_pred: pd.DataFrame) -> pd.DataFrame:
    """
    Calculates R-squared for each ticker for each combination of window
    methodology and length.
    """

    ticker_rsq = []
    for t in df_resid_pred.index.levels[1]:
        df = df_resid_pred.loc[df_resid_pred.index.get_level_values("ticker") == t]
        for w in df_resid_pred.columns.levels[0]:
            rsq = 1 - df[w].resid_sq.sum() / df[w].error_sq.sum()
            rsq.name = (t, w)
            ticker_rsq.append(rsq)

    df_ticker_rsq = pd.concat(ticker_rsq, axis=1).sort_index(axis=1)
    df_ticker_rsq.columns.names = ["ticker", "win_type"]

    return df_ticker_rsq

# =============================================================================
# Charts
# =============================================================================

COLORS = colors.qualitative.T10

def make_lines_chart(
    series: pd.Series,
    title: str = "5-Year CDS Spread",
) -> go.Figure:
    fig = go.Figure()

    for t in series.index.levels[1]:
        fig.add_trace(
            go.Scatter(
                x=series.index.levels[0],
                y=series.loc[series.index.get_level_values("ticker") == t],
                name=t,
                line=dict(width=1),
            )
        )

    fig.update(layout_title=title)

    return fig

IS_labels = [
    ("obs", lambda x: f"{x:>7d}"),
    ("min:max", lambda x: f"{x[0]:>0.4f}:{x[1]:>0.3f}"),
    ("mean", lambda x: f"{x:>7.4f}"),
    ("std", lambda x: f"{x:>7.4f}"),
    ("skewness", lambda x: f"{x:>7.4f}"),
    ("kurtosis", lambda x: f"{x:>7.4f}"),
]

def get_moments_annotation(
    s: pd.Series,
    xref: str,
    yref: str,
    x: float,
    y: float,
    xanchor: str,
    title: str,
    labels: List,
) -> go.layout.Annotation:
    """Calculates summary statistics for a series and returns and
    Annotation object.
    """
    moments = list(stats.describe(s.to_numpy()))
    moments[3] = np.sqrt(moments[3])

    sharpe = s.mean() / s.std()

    return go.layout.Annotation(
        text=(
            ("
").join(
                [f"{k[0]:<9}{k[1](moments[i])}" for i, k in enumerate(labels)]
            )
        ),
        align="left",
        showarrow=False,
        xref=xref,
        yref=yref,
        x=x,
        y=y,
        bordercolor="black",
        borderwidth=0.5,
        borderpad=2,
        bgcolor="white",
        xanchor=xanchor,
        yanchor="top",
    )

def make_overview_chart(
    series: pd.DataFrame, title: str, subtitle_base: str = "Residuals"
) -> go.Figure:

    fig = make_subplots(
        rows=1,
        cols=2,
        subplot_titles=[
            f"{subtitle_base} Distribution",
            f"Q/Q Plot",
        ],
        vertical_spacing=0.09,
        horizontal_spacing=0.08,
    )

    # Returns Distribution
    series_cuts = pd.cut(series, 100).value_counts().sort_index()
    midpoints = series_cuts.index.map(lambda interval: interval.right).to_numpy()
    norm_dist = stats.norm.pdf(midpoints, loc=series.mean(), scale=series.std())

    fig.add_trace(
        go.Bar(
            x=[interval.mid for interval in series_cuts.index],
            y=series_cuts / series_cuts.sum(),
            name="pct. of returns",
            marker=dict(color=COLORS[0]),
        ),
        row=1,
        col=1,
    )

    fig.add_trace(
        go.Scatter(
            x=[interval.mid for interval in series_cuts.index],
            y=norm_dist / norm_dist.sum(),
            name="normal",
            line=dict(width=1, color=COLORS[1]),
        ),
        row=1,
        col=1,
    )

    # Q/Q Data
    returns_norm = ((series - series.mean()) / series.std()).sort_values()
    norm_dist = pd.Series(
        list(map(stats.norm.ppf, np.linspace(0.001, 0.999, len(series)))),
        name="normal",
    )

    fig.append_trace(
        go.Scatter(
            x=norm_dist,
            y=returns_norm,
            name="resid. norm.",
            mode="markers",
            marker=dict(color=COLORS[0], size=3),
        ),
        row=1,
        col=2,
    )

    fig.add_trace(
        go.Scatter(
            x=norm_dist,
            y=norm_dist,
            name="norm.",
            line=dict(width=1, color=COLORS[1]),
        ),
        row=1,
        col=2,
    )

    fig.add_annotation(
        get_moments_annotation(
            series.dropna(),
            xref="paper",
            yref="paper",
            x=0.95,
            y=0.45,
            xanchor="right",
            title="Returns",
            labels=IS_labels,
        ),
        font=dict(size=6, family="Courier New, monospace"),
    )

    fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True)
    fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True)

    fig.update_layout(
        title_text=(
            f"{title}
"
            f"{series.index.levels[0].min().strftime('%Y-%m-%d')}"
            f" - {series.index.levels[0].max().strftime('%Y-%m-%d')}"
        ),
        showlegend=False,
        font=dict(size=10),
        margin=dict(l=50, r=50, b=50, t=100),
        yaxis=dict(tickformat="0.3f"),
        yaxis2=dict(tickformat="0.1f"),
        xaxis2=dict(tickformat="0.1f"),
    )

    for i in fig["layout"]["annotations"]:
        i["font"]["size"] = 12

    fig.update_annotations(font=dict(size=10))

    return fig

def make_residual_scatter(df_resid: pd.DataFrame, n_trend_obs: int = 200) -> go.Figure:
    df_resid.eq_resid = df_resid.eq_resid.shift()
    df_resid = df_resid.dropna()

    ols_result = stats.linregress(df_resid.eq_resid, df_resid.cds_resid)
    # print(ols_result.slope ** 2)

    fig = px.scatter(
        y=df_resid.cds_resid,
        x=df_resid.eq_resid,
        title="Equity Return Residual vs. CDS Return Residual",
        labels=dict(y="cds residual (n)", x="equity residual (n-1)"),
    )

    fig.add_scatter(
        x=[-0.3, 0.3],
        y=[ols_result.intercept + ols_result.slope * x for x in [-0.3, 0.3]],
        mode="lines",
    )

    fig.update_layout(showlegend=False)

    return fig

def make_rsq_comparison(df_resid_pred: pd.DataFrame) -> go.Figure:
    fig = go.Figure()

    for w in df_resid_pred.columns.levels[0]:
        rsq = 1 - df_resid_pred[w].resid_sq.sum() / df_resid_pred[w].error_sq.sum()
        fig.add_trace(go.Bar(x=rsq.index, y=rsq.values, name=w))

    fig.update_layout(title="Comparision of R-squared: Window Type by Length")

    return fig

def make_rsq_ticker_comparison(df_resid_pred: pd.DataFrame) -> go.Figure:
    tickers = {
        "BA": (1, 1),
        "C": (1, 2),
        "DD": (2, 1),
        "F": (2, 2),
        "GE": (3, 1),
        "JPM": (3, 2),
        "LOW": (4, 1),
        "LUV": (4, 2),
        "MAR": (5, 1),
        "T": (5, 2),
        "WFC": (6, 1),
        "XOM": (6, 2),
    }

    fig = make_subplots(
        rows=6,
        cols=2,
        subplot_titles=list(tickers.keys()),
        vertical_spacing=0.09,
        horizontal_spacing=0.08,
    )

    for t, (r, c) in tickers.items():
        for j, w in enumerate(df_resid_pred.columns.levels[1]):
            fig.add_trace(
                go.Bar(
                    x=df_resid_pred.index,
                    y=df_resid_pred[t][w],
                    name=w,
                    marker=dict(color=COLORS[j]),
                    showlegend=True if t == "BA" else False,
                    legendgroup=w,
                ),
                row=r,
                col=c,
            )

    fig.update_layout(title="Comparision of R-squared: Window Type by Length")

    fig.update_layout(height=1600)
    return fig


'''
db 연결 설정 코드입니다
'''
from flask_restful import reqparse, Resource, Api
from flask import request, Flask
from flaskext.mysql import MySQL
import config

app = Flask(__name__)
api = Api(app)

# MySQL 연결 세팅
app.config['MYSQL_DATABASE_USER'] = config._DB_CONF['user']
app.config['MYSQL_DATABASE_PASSWORD'] = config._DB_CONF['passwd']
app.config['MYSQL_DATABASE_DB'] = config._DB_CONF['db']
app.config['MYSQL_DATABASE_HOST'] = config._DB_CONF['host']
app.config['MYSQL_DATABASE_PORT'] = config._DB_CONF['port']

# MySQL 연결
mysql = MySQL()
mysql.init_app(app)
conn = mysql.connect()
cursor = conn.cursor()
_query = "select * from prescription"
cursor.execute(_query)
_data = cursor.fetchall()
result= []
for data in _data:
    _insurance = data[1]
    _nursesign = data[2]
    _grantnum = data[3]
    _patientname = data[4]
    _patientreginum = data[5]
    _insname = data[6]
    _insphonenum = data[7]
    _insfaxnum = data[8]
    _insemail = data[9]
    _diseasecode1 = data[10]
    _diseasecode2 = data[11]
    _doctorname = data[12]
    _doctortype = data[13]
    _doctornum = data[14]
    _mediname = data[15]
    _medidose = data[16]
    _medidailydose = data[17]
    _meditotalday = data[18]
    _mediusage = data[19]
    _mediinside = data[20]
    _usepreiod = data[21]
    _dispensename = data[22]
    _pharmacistname = data[23]
    _pharmacistseal = data[24]
    _preparationamount = data[25]
    _preparationyear = data[26]
    _changeprescription = data[27]
    tempjson= { "insurance":_insurance ,"nursesign": _nursesign,"grantnum": _grantnum , "patientname":_patientname ,"patientreginum" :_patientreginum ,"insname" : _insname ,"insphonenum": _insphonenum ,"insfaxnum": _insfaxnum , "insemail": _insemail ,"diseasecode1": _diseasecode1 ,"diseasecode2": _diseasecode2,"doctorname": _doctorname ,"doctortype": _doctortype ,"doctornum": _doctornum ,"mediname": _mediname ,"medidose": _medidose ,"medidailydose":_medidailydose ,"meditotalday" :_meditotalday , "mediusage":_mediusage ,"mediinside": _mediinside ,"usepreiod": _usepreiod ,"dispensename": _dispensename ,"pharmacistname": _pharmacistname , "pharmacistseal": _pharmacistseal ,"preparationamount": _preparationamount ,"preparationyear": _preparationyear , "changeprescription": _changeprescription }
    result.append(tempjson)
# resultjson = json.dumps(result)