#!/usr/bin/python
# -*- coding: utf-8 -*-
# Author: Andreas Buttenschoen
import numpy as np
import sympy as syp
import datetime

from sympy import symbols, simplify, cancel, Function, Integral, srepr
from sympy.matrices import zeros, ones
from sympy.solvers.deutils import ode_order
from sympy import sympify, preorder_traversal

from ...vectorspaces import Namespace

from .gen import CodeGeneratorBackend

from .support import execute_pycode, evaluate_expr_bc
from .support import sympy_base_program, convolve, term_simplify
from .OperatorSource import OperatorSource
from .NonlinearOperatorSource import NonlinearFunctionSource
from .NonlinearVectorFunctionSource import NonlinearVectorFunctionSource
from .DiffOperatorSource import DiffOperatorSource
from .IntegralOperatorSource import IntegralOperatorSource
from .function import DummyFunction
from .BoundaryConditionsSource import BoundaryConditionsSource
from .BoundaryConditionSource import BoundaryConditionSource

class Source:
    """ Source for the discretization of an operator on a Hilbert space.

        The required Frechet derivatives are computed symbolically using SymPy.

        TODO: Sometime in the future improve the quality and modularization of
        the code below. Right now this is completely horrific.
    """
    def __init__(self, eqn_src, cts_src, bcs_src, function_names,
                 constant_function_names, operator_names, parameter_names,
                 integration_kernels, *args, **kwargs):

        # The local namespace for python execution
        self.ns = Namespace()
        self.diffOrder = 0

        self.eqn_src = eqn_src
        self.cts_src = cts_src
        self.bcs_src = bcs_src
        self.ftype = kwargs.pop('ftype', 'cheb')

        self.function_names = function_names
        self.constant_function_names = constant_function_names
        self.operator_names = operator_names
        self.integration_kernels = integration_kernels
        self.parameter_names = parameter_names
        self.constant_functions = kwargs.pop('constant_functions', False)
        self.domain = kwargs.pop('domain', [-1, 1])
        self.proj_src = kwargs.pop('proj', [])

        # Keep track of the compiled parameters
        self.compiled_pars = []

        self.syp_src = {}
        self.symbol_names = {'eqn': 'f', 'cts': 'c', 'proj': 'proj', 'adh': 'g',
                             'bcs': 'bc',
                             'bif': 'moore', 'fold': 'fold', 'cts_fold': 'cts_fold',
                             'rhs_fold': 'rhs_fold', 'rhs_curv': 'rhs_curv'}

        # non-local operators have additional terms showing up in the linearization
        self.isNonLocal = kwargs.pop('nonLocal', False)
        if self.isNonLocal:
            assert len(self.operator_names) > 0, 'Non-local source requires non-local operator definition!'

        self.functions = None   # Sympy function objects
        self.operators = None   # Sympy operator objects
        self.kernels = None     # Sympy integration kernel objects
        self.common = None      # Contains common python code
        self.eqn  = None        # Compiled functions for the residual
        self.deqn  = None       # Compiled functions for the first derivative
        self.ddeqn  = None      # Compiled functions for the second derivative
        self.cts  = None        # Compiled functions for the constraints
        self.dp   = {}          # Compiled functions for the dp
        self.dxdp = ''          # Compiled functions for the dxdp
        self.dxdp_adj = ''      # Compiled functions for the adjoint of dxdp
        self.dpdp_adj = ''      # Compiled functions for the adjoint of dxdp
        self.bcs = []           # Compiled boundary condition operators

        # Compiled code
        self.d_spcode = {}
        self.dmdp = {}
        self.dmdxdp = {}
        self.dmdxdp_adj = {}
        self.dmdpdp_adj = {}
        self.par_pert = {}

        # Required temporary information
        self.pfuns = {}

    @property
    def lin(self):
        return self.syp_src['deqn']

    @property
    def bif(self):
        return self.syp_src['bif']

    @property
    def fold(self):
        return self.syp_src['fold']

    @property
    def lin_cts(self):
        if not self.cts_src: return None
        return self.syp_src['dcts']

    @property
    def rhs(self):
        return self.eqn

    @property
    def n_eqn(self):
        return len(self.eqn_src)

    @property
    def n_cts(self):
        return len(self.cts_src)

    def get_diff_order(self, srcs):
        return [0] + [ode_order(src, func) for src, func in zip(srcs, self.functions)]

    def get_function_names(self, type=None, constant=False):
        function_names = []
        function_names += self.function_names

        if constant:
            function_names += self.constant_function_names

        if type is None:
            return function_names

        if type == 'bilin' or type == 'trilin':
            function_names += [f.name for f in self.pfuns['p']]

        if type == 'trilin':
            function_names += [f.name for f in self.pfuns['pp']]

        return function_names

    def compile(self, cpars=[], debug=False, par=False, fold=False, bif=False):
        # Initialize the local namespace for the compile
        self.init(self.ns, debug=debug)
        self.functions = [self.ns[fname] for fname in self.function_names]
        self.operators = [self.ns[oname] for oname in self.operator_names]
        self.kernels   = [self.ns[kname] for kname in self.integration_kernels]

        # store the parameter names for which compiled stuff
        if not isinstance(cpars, list):
            cpars = [cpars]
        self.compiled_pars += cpars

        # Create common code
        pycode = self.__emit_common()
        self.common = pycode

        # compile right hand side
        for type in ['eqn', 'cts']:
            src = getattr(self, type + '_src')
            if src is None: continue
            name = self.symbol_names[type]
            spcode = self.sympify(src, self.ns, debug=debug)
            self.syp_src[type] = spcode

            # Compute the diff Order
            self.diffOrder = max(self.diffOrder, max(self.get_diff_order(spcode)))

            # Create a function source object representing the residual!
            func = NonlinearVectorFunctionSource(spcode, self.ftype,
                                                 self.domain, name=name,
                                                 functions=self.function_names)
            # FIXME
            func.ftype = self.ftype
            func.domain = self.domain
            setattr(self, type, func)

            # Generate linearization
            dname = 'd' + type
            d_spcode = self.linearize(spcode, self.diffOrder, name=name, debug=debug)
            d_spcode.name = type  # IMPROVE!
            # FIXME
            self.d_spcode[type] = d_spcode
            self.syp_src[dname] = d_spcode

        # compile the boundary conditions
        self.compile_bcs(self.function_names, debug=debug)

        if len(cpars) == 0 and (par or fold or bif):
            print('WARNING: no continuation parameters specified! Thus not generating any code to compute the required linearizations! Disable continuation.')

        # Compile the functions computing the first derivatives with respect
        # to the defined parameters!
        if (par or fold) and len(cpars) > 0:
            function_names = self.get_function_names()
            self.compile_dp(cpars, function_names, debug=debug)

        if fold and len(cpars) > 0:
            self.compile_fold(cpars, debug=debug)

        if bif and len(cpars) > 0:
            assert False, 'Fix me once again!'

    def compile_bcs(self, function_names, debug=False):
        """
            Function requires improving! FIXME!
        """
        if debug: print('Compiling boundary conditions!')
        self.bc_ns = Namespace()
        self.init(self.bc_ns, debug=debug, undefined_functions=True)
        #spcode = self.sympify(self.bcs_src, self.bc_ns, debug=debug)
        spcode = self.bcs_src # This is callable now
        op = self.d_spcode['eqn']
        self.bcs = BoundaryConditionsSource(op=op)

        # If the number of equation matches the BC -> duplicate the BC.
        # for i, j in np.ndindex(spcode.shape):
        for i in range(len(spcode)):
            #func_name, func_arg, func_res = evaluate_expr_bc(spcode[i])
            op = BoundaryConditionSource(coeffs=spcode[i])
            # func_name=func_name, residual=func_res, location=func_arg)
            self.bcs.append(op)

    def compile_fold(self, cpars, debug=False):
        """
            Compiles the required bilinear forms for fold computation and continuation.

            Currently we compute the bilinear forms required to use
            the minimal extended system. This means we must compute:

                The system is F(x, p, q) where p, q are parameters.
                We need the actions of the following two bilinear forms.

                1) G_xx[v, w]
                2) G_xx[v, w] adjoint
                3) G_xp[v, w] & G_xq[v, w]

        """
        for type in ['eqn', 'cts']:
            # Get the action of the linearization
            daction = self.syp_src['d' + type].action

            # Action of the linearization! TODO: No longer needed right now!
            # Create a function source object representing the residual!
            name = 'd' + self.symbol_names[type]
            function_names = self.get_function_names(type='bilin')
            func = NonlinearVectorFunctionSource(daction, self.ftype,
                                                 self.domain, name=name,
                                                 functions=function_names)
            if debug: print(func.emit())
            setattr(self, 'd' + type, func)

            # Compute the action of the second derivative!
            name = 'dd' + self.symbol_names[type]
            self.syp_src['dd' + type] = self.bilinear(self.syp_src['d'+type],
                                                      self.diffOrder,
                                                      name=name, debug=debug)

            # TODO improve this
            function_names = self.get_function_names()
            n = len(function_names)
            expr = zeros(n, 1)
            for i in range(n):
                expr[i, 0] = self.syp_src['dd'+type][i].biaction

            function_names = self.get_function_names(type='trilin')
            func = NonlinearVectorFunctionSource(expr, self.ftype,
                                                 self.domain, name=name,
                                                 functions=function_names)

            if debug: print(func.emit())
            setattr(self, 'dd' + type, func)

            # Compute adjoint of the second derivative!
            name = 'dd_adj' + self.symbol_names[type]
            self.syp_src['dd_adj' + type] = self.bilinear(self.syp_src['d'+type],
                                                          self.diffOrder, adjoint=True,
                                                          name=name, debug=debug)

            # TODO improve this
            function_names = self.get_function_names()
            n = len(function_names)
            expr = zeros(n, 1)
            for i in range(n):
                expr[i, 0] = self.syp_src['dd_adj'+type][i].biaction

            function_names = self.get_function_names(type='trilin')
            func = NonlinearVectorFunctionSource(expr, self.ftype,
                                                 self.domain, name=name,
                                                 functions=function_names)

            if debug: print(func.emit())
            setattr(self, 'dd_adj' + type, func)

        # Compute a bunch of coefficients
        self.bilinear_dp(self.diffOrder, cpars, name='dxdp', debug=debug)

    def bilinear_dp(self, diffOrder, cpars, name='ddp', debug=False, simplify=False, src_name='deqn'):
        """ Computes the linearization of the operator stored in matrix """
        # replace each of the functions with the perturbation
        src = self.syp_src[src_name]
        x = self.ns['x']
        setattr(self, name, {})
        dest = getattr(self, name)
        pfuns = self.get_pfuns()

        # Need to group values
        n = len(self.functions)
        m = src.shape[0]

        for cpar in cpars:
            assert cpar in self.parameter_names.keys(), '{} not a known parameter!'.format(cpar)

            # Look up parameter symbol and differentiation + simplify
            p = self.ns[cpar]

            # For each i we need to compute a new Hessian matrix
            DM = zeros(n, m)

            # Look over the two available first order derivatives
            for j, k in np.ndindex(DM.shape):
                DM[j, k] = syp.diff(src[j, k].expr, p)

            # Done symbolic differentiation for the block
            # Create the operator source representation
            osrc = OperatorSource(DM.shape, func_names=self.function_names,
                                  domain=self.domain, ftype=self.ftype,
                                  name=name + '_' + cpar,
                                  cfunc_names=list(self.constant_function_names.keys()),
                                  pars=self.parameter_names)

            for j, k in np.ndindex(DM.shape):
                pfun = pfuns[k].expr
                ex = term_simplify(syp.expand(DM[j, k]).coeff(pfun), ratio=1)
                op = DiffOperatorSource(fun=pfun, coeffs=ex, dummy=x, order=0)
                osrc[j, k].append(op)

                order = 1
                while order <= diffOrder:
                    dpfun = syp.Derivative(pfun, (x, order))
                    ex = term_simplify(syp.expand(DM[j, k]).coeff(dpfun), ratio=1)
                    op = DiffOperatorSource(fun=pfun, coeffs=ex, dummy=x, order=order)
                    osrc[j, k].append(op)
                    order += 1

                # Expand now -> otherwise trouble getting non-local coefficients
                DM[j, k] = syp.expand(DM[j, k])

            # Setup the source
            osrc.finish()

            # Create output function for derivatives!
            function_names = self.get_function_names(type='bilin')
            func = NonlinearVectorFunctionSource(osrc.action, self.ftype, self.domain,
                                                 name='{0:s}_{1:s}'.format(name, cpar),
                                                 functions=function_names)

            if debug: print(func.emit())
            dest[cpar] = func

        return None

    def bilinear(self, matrix, diffOrder, name='f', debug=False, simplify=False, adjoint=False):
        """ Computes the linearization of the operator stored in matrix """
        # replace each of the functions with the perturbation
        x = self.ns['x']

        # Look up the function definitions
        pfuns = self.get_pfuns(1)
        ppfuns = self.get_pfuns(2)

        # The increment used to compute the Frechet derivative
        e = syp.symbols('eps_pp', real=True)

        # Need to group values
        n = len(self.functions)
        m = matrix.shape[0]

        # Look up sympy objects
        funs = self.functions
        ops = self.operators
        assert len(ops) == 0, 'Bilinear support can not yet handle non-local operators!'

        # A dictionary to return a Hessian for each known function name
        osrcs = {}

        if adjoint:
            matrix = matrix.transpose

            for i in range(n):
                pfun, ppfun = pfuns[i].expr, ppfuns[i].expr
                for j in range(n):
                    matrix[j, i] = matrix[j, i].replace(pfun, ppfun)

        else:
            matrix = matrix.expr

        """
            Compute the ''Hessian'' of the vector valued function.
        """
        for i in range(n):
            # For each i we need to compute a new Hessian matrix
            DM = zeros(n, m)

            # Look over the two available first order derivatives
            for j in range(n):
                # Differentiate each with respect to all functions this one
                for k in range(n):
                    if adjoint:
                        fun, ppfun, pfun = funs[k], pfuns[k].expr, ppfuns[k].expr
                    else:
                        fun, pfun, ppfun = funs[k], pfuns[k].expr, ppfuns[k].expr

                    # Prepare for differentiation
                    exp = matrix[i, j].replace(fun, fun + e * ppfun)

                    # Compute the partial Frechet derivative!
                    # Make sure to call expand here -> otherwise subsequent coeff might fail!
                    DM[j, k] = syp.collect(syp.diff(exp, e).subs({e: 0}), ppfun)

            # Done symbolic differentiation for the block
            # Create the operator source representation
            osrc = OperatorSource(DM.shape, func_names=self.function_names,
                                  domain=self.domain, ftype=self.ftype, name=name + str(i),
                                  cfunc_names=list(self.constant_function_names.keys()),
                                  pars=self.parameter_names)

            for j, k in np.ndindex(DM.shape):
                pfun = ppfuns[j].expr if adjoint else ppfuns[k].expr
                ex = term_simplify(syp.expand(DM[j, k]).coeff(pfun), ratio=1)
                op = DiffOperatorSource(fun=pfun, coeffs=ex, dummy=x, order=0)
                osrc[j, k].append(op)

                order = 1
                while order <= diffOrder:
                    sign = (-1.0)**order if adjoint else 1.0
                    dpfun = syp.Derivative(pfun, (x, order))
                    ex = term_simplify(syp.expand(sign * DM[j, k]).coeff(dpfun), ratio=1)
                    op = DiffOperatorSource(fun=pfun, coeffs=ex, dummy=x, order=order)
                    osrc[j, k].append(op)
                    order += 1

            # Setup the source
            osrc.finish()
            osrcs[i] = osrc

        return osrcs

    def compile_dp(self, cpars, function_names, debug=False, name='dp', src_name='eqn'):
        src = self.syp_src[src_name]
        setattr(self, name, {})
        dest = getattr(self, name)
        for cpar in cpars:
            assert cpar in self.parameter_names.keys(), '{} not a known parameter!'.format(cpar)

            # Look up parameter symbol and differentiation + simplify
            p = self.ns[cpar]

            # TODO improve in the future!
            if p in src.free_symbols:
                p_src = simplify(syp.diff(src, p), ratio=1)
            else:
                ssrc = self.syp_src['cts']
                p_src = simplify(syp.diff(ssrc, p), ratio=1)

            # Create output function for derivatives!
            func = NonlinearVectorFunctionSource(p_src, self.ftype, self.domain,
                                                 name='{0:s}_{1:s}'.format(name, cpar),
                                                 functions=function_names)

            # Store this function
            if debug: print(func.emit())
            dest[cpar] = func

    def get_pfuns(self, order=1):
        key = order * 'p'
        if key not in self.pfuns.keys():
            x = self.ns['x']
            self.pfuns[key] = [DummyFunction(name + '_' + key, x) for name in self.function_names]

        return self.pfuns[key]

    def linearize(self, matrix, diffOrder, name='f', debug=False, simplify=False):
        """ Computes the linearization of the operator stored in matrix """
        # replace each of the functions with the perturbation
        x = self.ns['x']
        pfuns = self.get_pfuns()

        # The increment used to compute the Frechet derivative
        e = syp.symbols('eps_p', real=True)

        # Need to group values
        n = matrix.shape[0]
        m = len(self.functions)
        DM = zeros(n, m)

        # look up the sympy objects
        funs = self.functions
        ops  = self.operators
        assert len(ops) <= 1, 'Can only handle at most one operator!'

        for i, j in np.ndindex(DM.shape):
            fun, pfun = funs[j], pfuns[j].expr
            exp = matrix[i, 0].replace(fun, fun + e * pfun)

            # Compute the partial Frechet derivative!
            # Make sure to call expand here -> otherwise subsequent coeff might fail!
            DM[i, j] = syp.collect(syp.diff(exp, e).subs({e: 0}), pfun)

        # Create the operator source representation
        osrc = OperatorSource(DM.shape, func_names=self.function_names,
                              domain=self.domain, ftype=self.ftype, name=name,
                              cfunc_names=list(self.constant_function_names.keys()),
                              pfun=[pfun.expr for pfun in pfuns],
                              pars=self.parameter_names)

        for i, j in np.ndindex(DM.shape):
            pfun = pfuns[j].expr
            ex = term_simplify(syp.expand(DM[i, j]).coeff(pfun), ratio=1)
            op = DiffOperatorSource(fun=pfun, coeffs=ex, dummy=x, order=0)
            osrc[i, j].append(op)

            order = 1
            while order <= diffOrder:
                dpfun = syp.Derivative(pfun, (x, order))
                ex = term_simplify(syp.expand(DM[i, j]).coeff(dpfun), ratio=1)
                op = DiffOperatorSource(fun=pfun, coeffs=ex, dummy=x, order=order)
                osrc[i, j].append(op)
                order += 1

            # Expand now -> otherwise trouble getting non-local coefficients
            DM[i, j] = syp.expand(DM[i, j])

            # Collect integral terms so that we can extract them!
            integrals = [arg for arg in preorder_traversal(DM[i, j]) if isinstance(arg, Integral)]
            for dpfun in integrals:
                # Extract the coefficient
                ex = syp.simplify(DM[i, j].coeff(dpfun), ratio=1)

                # Extract the integrand
                integrand = syp.simplify(dpfun.function.coeff(pfun), ratio=1)

                op = IntegralOperatorSource(coeffs=ex, fun=pfun,
                                            integrand=integrand, dummy=x)
                osrc[i, j].append(op)

        # Setup the source
        osrc.finish()
        return osrc

    def init(self, namespace, debug=False, *args, **kwargs):
        # generate the Sympy base program
        pycode = sympy_base_program(self.function_names, self.constant_function_names,
                                    self.operator_names,
                                    self.integration_kernels, self.parameter_names,
                                    constant_functions=self.constant_functions,
                                    *args, **kwargs)
        execute_pycode(pycode, namespace, debug=debug)

    def sympify(self, src, namespace, *args, **kwargs):
        # create the Matrix for the nonlinear operator
        n_src = len(src)
        matrix = zeros(n_src, 1)
        for i, j in np.ndindex(matrix.shape):
            print('Trying to sympify something!')
            print(src[i])
            matrix[i, j] = sympify(src[i] if len(src[i]) > 0 else '0.0', locals=namespace)

        return matrix

    def __emit_common(self, var_names=['x']):
        """ Generates the code to have a spatial variables such as x available to the code """
        cg = CodeGeneratorBackend()
        cg.begin(tab=4*" ")
        cg.write('#!/usr/bin/python')
        cg.write('# -*- coding: utf-8 -*-')
        cg.write(35 * '#')
        cg.write('# This is the COLLOC BASE PROGRAM #')
        cg.write('#                                 #')
        cg.write('#     I am auto-generated!        #')
        cg.write('#     Do not mess with me!        #')
        cg.write('#                                 #')
        cg.write(35 * '#')
        cg.write('# author: Andreas Buttenschoen {0}'.format(datetime.datetime.now().year))
        cg.write('import numpy')
        cg.write('import scipy')
        cg.write('import numpy as np')
        cg.write('import funpy as fp')
        cg.write('from numpy import array, pi')
        cg.write('from funpy import *')
        cg.write('')
        cg.write(35 * '#')
        cg.write('# Spatial variables')
        cg.write(35 * '#')
        cg.write('domain=np.asarray([{0:.16f}, {1:.16f}])'.format(self.domain[0], self.domain[1]))

        for var in var_names:
            cg.write('{0} = Fun(op=lambda x: x, domain=[{1:.16f}, {2:.16f}])'.format(var, self.domain[0], self.domain[1]))

        if self.isNonLocal:
            cg.write('')
            cg.write(35 * '#')
            cg.write('# Non-local integration kernels')
            cg.write(35 * '#')
            cg.write('from colloc.kernels import uniform_kernel')
            cg.write('k = uniform_kernel(1024, domain)')

        #cg.write('_cos_ = Fun(op=lambda x: np.cos(x), type=\'{0:s}\', domain=[{1:.16f}, {2:.16f}])'.format(self.ftype, self.domain[0], self.domain[1]))
        #cg.write('_sin_ = Fun(op=lambda x: np.sin(x), type=\'{0:s}\', domain=[{1:.16f}, {2:.16f}])'.format(self.ftype, self.domain[0], self.domain[1]))
        cg.write('')

        cg.write('# Constant Function symbols')
        for function_name, function_code in self.constant_function_names.items():
            if isinstance(function_code, str):
                cg.write('{0} = Fun(op={1}, domain=[{2:.16f}, {3:.16f}])'.\
                         format(function_name, function_code,
                                 self.domain[0], self.domain[1]))

            elif isinstance(function_code, object):  # Python object -> call repr
                cg.write('{0} = {1}'.format(function_name, repr(function_code)))

            else:
                assert False, 'Don\'t know what to do with constant function {0:s}!'.format(function_name)

        cg.write('')
        # Create simple functions to debug parameter values etc.
        cg.write(35 * '#')
        cg.write('# Debug code')
        cg.write(35 * '#')
        cg.write('def print_pars():')
        cg.indent()

        cg.write("rstr=''")
        for p_name, p_value in self.parameter_names.items():
            cg.write('rstr += \"{0:s} = %.4g, \" % {0:s}'.format(p_name))
        cg.write('return rstr')
        cg.dedent()

        return cg.end()


"""Google Sign-In OAuth drop-in.

Google Sign-In API docs: https://developers.google.com/identity/protocols/OAuth2WebServer

Python API client docs: https://developers.google.com/api-client-library/python/

requests-oauthlib docs:

* https://requests-oauthlib.readthedocs.io/
* https://requests-oauthlib.readthedocs.io/en/latest/examples/google.html
"""
import logging

from flask import request
from google.cloud import ndb
from requests_oauthlib import OAuth2Session

from . import views, models
from .webutil import flask_util, util
from .webutil.util import json_dumps, json_loads

logger = logging.getLogger(__name__)

GOOGLE_CLIENT_ID = util.read('google_client_id')
GOOGLE_CLIENT_SECRET = util.read('google_client_secret')
AUTH_CODE_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
ACCESS_TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token'
# Discovered on 1/30/2019 from:
#   https://accounts.google.com/.well-known/openid-configuration
# Background: https://developers.google.com/identity/protocols/OpenIDConnect#discovery
OPENID_CONNECT_USERINFO = 'https://openidconnect.googleapis.com/v1/userinfo'

class GoogleUser(models.BaseAuth):
  """An authenticated Google user.

  Provides methods that return information about this user and make OAuth-signed
  requests to Google APIs. Stores OAuth credentials in the datastore. See
  :class:`models.BaseAuth` for usage details.

  To make Google API calls: https://google-auth.readthedocs.io/
  """
  user_json = ndb.TextProperty()
  token_json = ndb.TextProperty()

  def site_name(self):
    return 'Google'

  def user_display_name(self):
    """Returns the user's name."""
    return json_loads(self.user_json).get('name') or 'unknown'

  def access_token(self):
    """Returns the OAuth access token string."""
    return json_loads(self.token_json)['access_token']

class Scopes(object):
  # OAuth/OpenID Connect scopes:
  #   https://developers.google.com/+/web/api/rest/oauth#authorization-scopes
  # Google scopes:
  #   https://developers.google.com/identity/protocols/googlescopes
  DEFAULT_SCOPE = 'openid https://www.googleapis.com/auth/userinfo.profile'
  SCOPE_SEPARATOR = ' '

class Start(Scopes, views.Start):
  """Starts the OAuth flow."""
  NAME = 'google_signin'
  LABEL = 'Google'
  """https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth"""
  INCLUDE_GRANTED_SCOPES = True

  def redirect_url(self, state=None):
    assert GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET, \
      "Please fill in the google_client_id and google_client_secret files in your app's root directory."

    session = OAuth2Session(GOOGLE_CLIENT_ID, scope=self.scope,
                            redirect_uri=self.to_url())
    auth_url, state = session.authorization_url(
      AUTH_CODE_URL, state=state,
      # ask for a refresh token so we can get an access token offline
      access_type='offline', prompt='consent',
      # https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth
      include_granted_scopes=self.INCLUDE_GRANTED_SCOPES)
    return auth_url

class Callback(Scopes, views.Callback):
  """Finishes the OAuth flow."""

  def dispatch_request(self):
    # handle errors
    state = request.values.get('state')
    error = request.values.get('error')
    desc = request.values.get('error_description')
    if error:
      msg = f'Error: {error}: {desc}'
      logger.info(msg)
      if error == 'access_denied':
        return self.finish(None, state=state)
      else:
        flask_util.error(msg)

    # extract auth code and request access token
    session = OAuth2Session(GOOGLE_CLIENT_ID, scope=self.scope,
                            redirect_uri=request.base_url)
    session.fetch_token(ACCESS_TOKEN_URL,
                        client_secret=GOOGLE_CLIENT_SECRET,
                        authorization_response=request.url)

    # get OpenID Connect user info
    # https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
    resp = session.get(OPENID_CONNECT_USERINFO)
    try:
      resp.raise_for_status()
    except BaseException as e:
      util.interpret_http_exception(e)
      raise

    user_json = json_loads(resp.text)
    logger.info('Got one person', user_json)

    user = GoogleUser(id=user_json['sub'], user_json=json_dumps(user_json),
                      token_json=json_dumps(session.token))
    user.put()
    return self.finish(user, state=state)


from django import template

register = template.Library()

@register.filter()
def smooth_timedelta(timedeltaobj):
    """Convert a datetime.timedelta object into Days, Hours, Minutes, Seconds."""
    secs = timedeltaobj.total_seconds()
    timetot = ""
    if secs > 86400: # 60sec * 60min * 24hrs
        days = secs // 86400
        timetot += "{} д.".format(int(days))
        secs = secs - days*86400

    if secs > 3600:
        hrs = secs // 3600
        timetot += " {} час.".format(int(hrs))
        secs = secs - hrs*3600

    if secs > 60:
        mins = secs // 60
        timetot += " {} мин.".format(int(mins))
        secs = secs - mins*60

    if secs > 0:
        timetot += " {} сек.".format(int(secs))
    return timetot


import pyttsx3
import PyPDF2

speaker = pyttsx3.init()
voices = speaker.getProperty('voices')
speaker.setProperty('voice', voices[1].id)
#rate = speaker.getProperty('rate')
speaker.setProperty("rate", 150)
speaker.say('i am alive I dont want it to go really slow just slow enough to be easy for a non native speaker to understand')

speaker.runAndWait()
#book  = open("","rb")


for i in range(int(input())):
    N = int(input())
    A= ""
    for j in range(N):
        if j%3==0:
            A+="a"
        if j % 3 == 1:
            A += "b"
        if j % 3 == 2:
            A += "c"
    print(A)
    continue


class Solution:
    # Runtime: 36 ms
    # Memory Usage: 16.3 MB
    def maxDepth(self, root: TreeNode) -> int:
        if root is None:
            return 0
        return self.search(root, 0)

    def search(self, node, depth):
        depth += 1
        depth_left = depth
        depth_right = depth

        if node.left is not None:
            depth_left = self.search(node.left, depth)
        if node.right is not None:
            depth_right = self.search(node.right, depth)

        if depth_right < depth_left:
            return depth_left
        else:
            return depth_right


import time

import numpy as np
import torch

from metrics import MetricMeter, Writer
from sklearn.metrics import average_precision_score, roc_auc_score
from utils import get_eta, move_to, save_checkpoint, to_numpy

def train_loops(
    args, train_epoch_fn, model, optimizer, scheduler, loader_test, ema_model=None, best_mAP=0.
):
    writer = Writer(args.output_dir)
    total_epoch_time = 0.
    for epoch in range(args.start_epoch, args.num_epochs):
        start_time = time.time()
        print(f"Starting train epochs: {epoch + 1}/{args.num_epochs}")
        train_epoch_fn(args, model, optimizer, scheduler, ema_model=ema_model)

        if ((epoch + 1) % args.eval_periods == 0) or ((epoch + 1) == args.num_epochs):
            results = validate(args, loader_test, model)
            results.update({"epoch": epoch + 1})
            mAP = results["macro_aurpc"]

            # remember the best mAP and save the checkpoint
            is_best = mAP > best_mAP
            best_mAP = max(mAP, best_mAP)
            save_checkpoint(
                args.output_dir, {
                    'epoch': epoch + 1,
                    'model': args.model,
                    'state_dict': model.state_dict(),
                    'ema_model': ema_model.state_dict() if ema_model is not None else None,
                    'mAP': mAP,
                    'best_mAP': best_mAP,
                    'optimizer': optimizer.state_dict(),
                    'scheduler': scheduler.state_dict()
                }, is_best
            )

            # write metrics
            writer.write(results)

        # output eta
        total_epoch_time += (time.time() - start_time)
        avg_epoch_time = total_epoch_time / (epoch + 1)
        eta = get_eta(avg_epoch_time, epoch + 1, args.num_epochs)
        print(
            "[Epoch {}/{}]  Total elapsed: {:.1f} seconds, ETA: {}".format(
                epoch + 1, args.num_epochs, total_epoch_time, eta
            )
        )
        print()

    # end of epoch
    print(f"Best mAP: {best_mAP:.4f}, Last mAP: {mAP:.4f}")

def do_evaluate(all_predictions, all_labels):
    # mesaure test performances
    macro_aurpc = average_precision_score(all_labels, all_predictions, average="macro")
    micro_aurpc = average_precision_score(all_labels, all_predictions, average="micro")

    macro_auroc = roc_auc_score(all_labels, all_predictions, average="macro")
    micro_auroc = roc_auc_score(all_labels, all_predictions, average="micro")

    # print results
    print(
        "[Evaluation] {aurpc} | {auroc}".format(
            aurpc=f"macro AURPC: {macro_aurpc:.4f}, micro AURPC: {micro_aurpc:.4f}",
            auroc=f"macro AUROC: {macro_auroc:.4f}, micro AUROC: {micro_auroc:.4f}"
        )
    )
    results = {}
    results.update({"macro_aurpc": macro_aurpc, "micro_aurpc": micro_aurpc})
    results.update({"macro_auroc": macro_auroc, "micro_auroc": micro_auroc})
    return results

def validate(args, data_loader, model):
    model.eval()
    criterion = torch.nn.BCEWithLogitsLoss().cuda()
    metric_meters = MetricMeter()

    pred_list = []
    label_list = []
    print(f"Starting evaluation for {len(data_loader)} batches...")
    for i, (images, labels) in enumerate(data_loader):
        start_time = time.time()
        with torch.no_grad():
            if torch.cuda.is_available():
                images, labels = move_to((images, labels), "cuda")
            preds = model(images)
            loss = criterion(preds, labels.float())

            pred_list.append(to_numpy(preds))
            label_list.append(to_numpy(labels))

            # log some metrics
            metric_dict = {"loss": loss, "batch_time": time.time() - start_time}
            metric_meters.update(metric_dict)

        if ((i + 1) % 20 == 0) or ((i + 1) == len(data_loader)):
            _eta = get_eta(metric_meters.meters["batch_time"].avg, i + 1, len(data_loader))
            print(
                "{steps},  {eta},  {metrics}".format(
                    steps=f"Steps: {i+1}/{len(data_loader)}", eta=_eta, metrics=metric_meters
                )
            )

    pred_list = np.concatenate(pred_list, axis=0)
    label_list = np.concatenate(label_list, axis=0)
    results = do_evaluate(pred_list, label_list)
    results.update({"eval_loss": metric_meters.meters["loss"].avg})

    return results


from setuptools import setup, find_packages

version = "0.1.7"

if __name__ == '__main__':
    setup(
        name="pygraph_utils",
        version=version,
        packages=find_packages(),
        python_requires='>=3.6',
        # include_package_data=True,
        # package_data={
        #     '': ['data/*.txt']
        # }
        url='https://github.com/microsoftgraph/dataconnect-solutions'
    )


"""
abc325 b
"""
N = int(input())
W, X = [], []
for _ in range(N):
    w, x = map(int, input().split())
    W.append(w)
    X.append(x)

ans: int = 0
for t_g in range(24):
    total: int = 0
    for w, x in zip(W, X):
        t_l = (t_g + x) % 24
        if 9 <= t_l < 18:
            total += w
    ans = max(ans, total)

print(ans)


import json
import socket
import traceback
from threading import Thread

from network.protocolHandler import ProtocolHandler
from tools import logger

class Client:

    def __init__(self, ip: str, port: int):
        self.socket = socket.socket()
        self.connectState = False
        self.protocolHandle = ProtocolHandler()
        self.role = None

        thread = Thread(target=self.connect, args=(ip, port))
        thread.setDaemon(True)
        thread.start()

    def connect(self, ip: str, port: int):
        try:
            self.socket.connect((ip, port))
            self.send({"protocol": "cli_connect"})
            self.receiveMessage()
        except ConnectionRefusedError:
            logger.log("无法连接服务器")

    def receiveMessage(self):
        while True:
            data = self.socket.recv(4096)
            strPackage = data.decode("utf-8")
            packages = strPackage.split("|#|")
            print(packages)
            for jsonPackage in packages[:-1]:
                package = json.loads(jsonPackage)
                self.protocolHandle(self, package)

    def send(self, data: dict):
        self.socket.sendall((json.dumps(data, ensure_ascii=False) + "|#|").encode())


__author__ = 'nncsang'
from magic import Magic
import magic
import sys
import os
import re
import mimetypes

def print_result(filename, real_ext, expected_ext):
    print("{:35s} \t {:10s} \t {:20s}".format(filename, real_ext, expected_ext))

# Default mime.types files
magic_file = "/etc/mime.types"
directory = None

# --------------------------------------------------------------------------------------------- #
# Check parameters
# --------------------------------------------------------------------------------------------- #
if (len(sys.argv) == 3):
    magic_file = sys.argv[2]
elif (len(sys.argv) == 2):
    pass
else:
    print("Usage: TypeDetector.py <directory> [magic_file]")
    exit(0)

directory = sys.argv[1]
type_maps = {}

# --------------------------------------------------------------------------------------------- #
# Parse mime.types
# --------------------------------------------------------------------------------------------- #
try:
    f = open(magic_file, "r")
    for line in f:
        if (len(line) == ""):
            continue

        if (line[0] == '#'):
            continue

        line = re.sub(r'\s+', '\t', line)

        parts = line.split("\t")

        type, ext = parts[0], parts[1:]
        type_maps[type] = ext
except:
    print("Cannot read " + magic_file)
    exit(0)

print("{:35s} \t {:10s} \t {:20s}".format('Camouflaged file', 'Claimed ext', 'Expected ext'))
# --------------------------------------------------------------------------------------------- #
# For each file:
#   + Get claimed ext
#   + Using magic and mime.types to find out expected exts
# --------------------------------------------------------------------------------------------- #

mg = Magic(mime=True, mime_encoding=False, keep_going=True, uncompress=False)
for folder, subs, files in os.walk(directory):
    for filename in files:
        try:
            full_path = folder + "//" + filename
            detected_type = str(mg.from_file(full_path).decode("utf-8"))
            ext = os.path.splitext(filename)[1][1:]
            expected_exts = type_maps[detected_type]
            if (ext != "" and ext not in expected_exts):
                # Print out highly likely camouflaged files
                print_result(full_path, ext, "".join([b + " " for b in expected_exts]))
        except:
            pass


# -*- coding: utf-8 -*-
"""
Created on Mon Jan 18 21:17:44 2016

@author: zhicheng.qi
gramophone官网的爬虫，暂时爬feature这个栏目，只爬第一页
第一版
"""

import requests
from bs4 import BeautifulSoup
list_page = 'http://www.gramophone.co.uk/features/all'
r = requests.get(list_page)

soup = BeautifulSoup(r.text)

news_list = soup.findAll("div", {"class": "node-article-layout"})
for item in news_list:
    title = item.find('h2').get_text()
    link = item.find('h2').find('a')['href']
    link = list_page + link
    introduction = item.findAll('p', {'class': 'tuck-down'})[0].get_text()
    print('标题:', title)
    print('介绍:', introduction)
    print('链接:', link)
    print('\n')

# detail_page = 'http://www.gramophone.co.uk/feature/my-music-john-bercow'

# r = requests.get(detail_page)
# soup = BeautifulSoup(r.text)

# sub_heading = soup.find('h2',{'class' : 'sub-heading'}).get_text()


import copy
import json
import random
import igraph as ig

from itertools import chain

from intervention import (
    NetworkIntervention,
    IndividualIntervention,
    MockInterventionA,
    MockInterventionB,
)
from agent import Agent

class Simulation:
    def __init__(self, ticks, **kwargs):

        self.__dict__.update(kwargs)
        self.total_ticks = ticks
        self.cur_tick = 0
        self.history = {
            "agents": [],
            "edges": [],
            "vertices": [],
            "networks": [],
            "interventions": [],
            "parameters": [],
        }

    def setup(self):

        # Network
        self.network = ig.Graph.Erdos_Renyi(
            n=self.n_agents, p=self.p_edge, directed=False, loops=False
        )

        # Agents
        self.agents = []
        for i, v in enumerate(self.network.vs):
            agent = Agent(id=i, n_beh=self.n_beh)

            # Vertex and agent must have same name
            v["name"] = agent.name

            self.agents.append(agent)

        # Interventions
        self.interventions = []
        for intv_params in self.intervention_params:
            intv_class = globals()[intv_params["intv_class_name"]]
            intv = intv_class(**intv_params)
            self.interventions.append(intv)

        self.validate()

        self.record_history()

    def tick(self):

        self.validate()

        random.shuffle(self.agents)

        # (A) Conduct any interventions
        for intv in self.interventions:
            if intv.is_setup_phase(self.cur_tick):
                intv.setup(self.agents, self.network, sui_ORs=self.sui_ORs)

            if intv.is_active_phase(self.cur_tick):
                intv.intervene(self.agents, self.network)

        # (B) Agents interact with each other and world
        for i, agent in enumerate(self.agents):

            # (1) Emulate alters
            self.agents[i].emulate_alters(
                agents=self.agents, network=self.network, p=self.p_emul
            )

            # (2) Prune old, dissimilar alters
            self.agents[i].prune_alters(
                agents=self.agents, network=self.network, sim_thresh=self.sim_thresh
            )

            # (3) Recruit new, more similar alters
            self.agents[i].recruit_alters(
                agents=self.agents, network=self.network, sim_thresh=self.sim_thresh
            )

            # (4) Spontaneously change in favor of baserates
            self.agents[i].spontaneously_change(
                baserates=self.baserates, susceptibility=self.p_spon_change
            )

            # (5) Consider whether to attempt suicide
            self.agents[i].consider_suicide(
                odds_ratios=self.sui_ORs,
                gen_sui_prev=self.gen_sui_prev,
                gen_ave_beh=self.gen_ave_beh,
            )

        self.validate()

        self.record_history()

        self.cur_tick = self.cur_tick + 1

    def go(self):
        self.validate()

        while self.cur_tick < self.total_ticks:
            self.tick()

        self.validate()

    def validate(self):

        # No extra / missing agents or vertices
        agent_names = [a.name for a in self.agents]
        vertex_names = [v["name"] for v in self.network.vs]
        assert set(agent_names) == set(vertex_names)

        assert self.n_beh == len(self.agents[0].beh)
        assert self.n_beh == len(self.sui_ORs)
        assert self.n_beh == len(self.baserates)

        for intv in self.interventions:
            assert intv.start_tick <= self.total_ticks
            assert (intv.start_tick + intv.duration - 1) <= self.total_ticks

        return True

    def agents_to_dict(self):
        agent_dicts = [a.as_dict(self.agents, self.network) for a in self.agents]

        return agent_dicts

    def edges_to_dict(self):
        edges = []
        for edge in self.network.es:
            edges.append(
                {
                    "src_index": edge.source,
                    "src_name": edge.source_vertex["name"],
                    "tar_index": edge.target,
                    "tar_name": edge.target_vertex["name"],
                }
            )

        return edges

    def verts_to_dict(self):
        verts = []
        for vert in self.network.vs:
            verts.append({"index": vert.index, "name": vert["name"]})

        return verts

    def interventions_to_dict(self):
        intv_dicts = []
        for intv in self.interventions:
            intv_dict = intv.as_dict()
            intv_dicts.append(intv_dict)

        return intv_dicts

    def params_to_dict(self, flat=True, in_list=True):
        params = copy.deepcopy(self.__dict__)

        non_params = ["network", "agents", "interventions", "history"]
        params = {key: val for key, val in params.items() if key not in non_params}

        intv_params = params.pop("intervention_params")[0]

        if flat:
            listlike_params = ["tar_severity"]
            for listlike in listlike_params:
                for i, item in enumerate(intv_params.pop(listlike)):
                    intv_params.update({f"{listlike}{i}": item})

            listlike_params = ["sui_ORs", "baserates"]
            for listlike in listlike_params:
                for i, item in enumerate(params.pop(listlike)):
                    params.update({f"{listlike}{i}": item})

        params.update(intv_params)

        if in_list:
            params = [params]

        return params

    def network_to_dict(self, in_list=True):
        d = {}

        d.update(
            {
                "density": self.network.density(),
                "n_agents": len(self.network.vs),
                "n_edges": len(self.network.es),
                "n_isolates": len([v for v in self.network.vs if v.degree() == 0]),
            }
        )

        # individual assortativities
        for i in range(self.n_beh):
            agent_behs = [a.beh[i] for a in self.agents]
            assort = self.network.assortativity(types1=agent_behs, directed=False)
            d.update({f"assort_beh{i}": assort})

        beh_sums = [sum(a.beh) for a in self.agents]
        d["assort_sum_beh"] = self.network.assortativity(
            types1=beh_sums, directed=False
        )

        cur_risks = [a.current_risk for a in self.agents]
        d["assort_cur_risk"] = self.network.assortativity(
            types1=cur_risks, directed=False
        )

        if in_list:
            d = [d]

        return d

    # def record_history(self):

    #     self.history["agents"].append(copy.deepcopy(self.agents_to_dict()))
    #     self.history["edges"].append(copy.deepcopy(self.edges_to_dict()))
    #     self.history["vertices"].append(copy.deepcopy(self.verts_to_dict()))
    #     self.history["networks"].append(copy.deepcopy(self.network_to_dict()))
    #     self.history["interventions"].append(
    #         copy.deepcopy(self.interventions_to_dict())
    #     )
    #     self.history["parameters"].append(copy.deepcopy(self.params_to_dict()))

    def record_history(self):

        self.history["agents"].append(self.agents_to_dict())
        self.history["edges"].append(self.edges_to_dict())
        # self.history["vertices"].append(self.verts_to_dict())
        self.history["networks"].append(self.network_to_dict())
        self.history["interventions"].append(self.interventions_to_dict())

        if self.cur_tick <= 1:
            self.history["parameters"].append(self.params_to_dict())

    def tag_history(self):

        # Go through each kind of history. Within each kind of history,
        # tag all of the individual items with the correct tick and with the
        # sim_id for this simulation, which can be used to uniquely identify it

        for aspect in self.history:
            for i, objs in enumerate(self.history[aspect]):
                for obj in objs:
                    obj.update({"tick": i, "sim_id": self.sim_id})

    def history_to_db(self, con, history_of=None, simplify=True):
        if history_of is None:
            history_aspects = ["agents", "edges", "parameters"]
        else:
            history_aspects = history_of

        for history_aspect in history_aspects:
            history = self.history_to_dataframe(history_aspect)
            # history["seed"] = self.seed
            history["sample_num"] = self.sample_num

            if simplify and history_aspect == "parameters":
                history = history.iloc[[0]]

            history.to_sql(history_aspect, con, if_exists="append", index=False)

    def history_for_db(self, simplify=True):
        self.tag_history()

        exportable_history = {}
        for history_of in [
            "agents",
            "edges",
            "parameters",
            "networks",
            "interventions",
        ]:
            flat_history = list(chain.from_iterable(self.history[history_of]))
            exportable_history[history_of] = flat_history

        if simplify:
            exportable_history["parameters"] = [exportable_history["parameters"][0]]

        return exportable_history

    @staticmethod
    def db_var_types(dict_list):
        db_mapping = {
            "<class 'int'>": "INTEGER",
            "<class 'numpy.int64'>": "INTEGER",
            "<class 'str'>": "TEXT",
            "<class 'bool'>": "INTEGER",
            "<class 'float'>": "REAL",
            "<class 'numpy.float64'>": "REAL",
        }

        var_types = {}
        for key, val in dict_list.items():
            var_type = db_mapping[str(type(val))]
            var_types.update({key: var_type})

        return var_types

    def create_history_tables(self, con):
        history = self.history_for_db()

        for aspect, data in history.items():
            var_types = ", ".join(Simulation.db_var_types(data[-1]))
            query = f"CREATE TABLE IF NOT EXISTS {aspect}({var_types})"

            with con:
                con.execute(query)

    def insert_history_to_db(self, con):
        history = self.history_for_db()

        for aspect, data in history.items():
            colnames = ", ".join([f":{key}" for key in data[-1]])
            query = f"INSERT INTO {aspect} VALUES({colnames})"

            with con:
                con.executemany(query, data)

if __name__ == "__main__":
    from main import run_simulation

    x = run_simulation(1)


# You are given a scratchcard consists of a row of non-negative integers.
# To win the scratch game, you need to scratch at least 2 numbers of the same value.
# You can only scratch consecutive numbers from the card. Scratching each number costs 1 dollar.
# Return the minimum cost to win the game, or -1 if winning is not possible.

from typing import List

def min_cost_to_win(nums: List[int]) -> int:
    possibleScratches = []

    for i in range(0, len(nums)):
        for j in range(i+1, len(nums)):
            if nums[i] == nums[j]:
                possibleScratches.append(nums[i:j+1])
            elif j == len(nums)-1:
                break

    print("possibleScratches: ", possibleScratches)

    while len(possibleScratches) > 1:
        if len(possibleScratches[0]) >= len(possibleScratches[1]):
            possibleScratches.remove(possibleScratches[0])
        elif len(possibleScratches[0]) < len(possibleScratches[1]):
            possibleScratches.remove(possibleScratches[1])

    return (len(possibleScratches[0]) if len(possibleScratches) !=0 else -1)

if __name__ == '__main__':
    nums = [1, 3, 4, 2, 3, 4, 5]
    res = min_cost_to_win(nums)
    print(res)


from gridgen.grid2d import make_grid_with_labels, make_grid
from gridgen.grid3d import find_vertex, construct_grid, measure_distances, measure_angles, search_grid
from gridgen.grid3d.construct import index_order
from gridgen.io import read_surf, export_grid, read_surface_ras_shift

from numpy.testing import assert_array_almost_equal
from numpy import array

from .paths import GENERATED_PATH, SMOOTH_FILE, T1_FILE

GRID_FILE = GENERATED_PATH / 'grid_020.fcsv'

def test_geometry_construct():
    offset = read_surface_ras_shift(T1_FILE)
    smooth = read_surf(SMOOTH_FILE, ras_shift=offset, normals=True)
    out_vertex = find_vertex(smooth, [-54, 3, 5])
    assert out_vertex == 37613

    vertex = search_grid(smooth, out_vertex, 5, -5)
    assert vertex == 39945

    # test construct
    grid2d = make_grid_with_labels(4, 3, 'TBLR', chan_pattern='elec{}')
    grid3d = {
        'interelec_distance': 3,
        'maximum_angle': 5,
        'step_angle': 0.2,
        }

    grid = construct_grid(smooth, out_vertex, 'elec1', grid2d['label'], grid3d, rotation=20)
    assert_array_almost_equal(
        grid['pos'][4, 2],
        array([-57.797, 1.639, -9.119]),
        decimal=3)

    offset = read_surface_ras_shift(T1_FILE)
    export_grid(grid, offset, GRID_FILE, 'slicer')
    export_grid(grid, offset, GRID_FILE, 'freeview')

    m_col, m_row = measure_distances(grid)
    assert_array_almost_equal(m_col, 3.000, decimal=3)
    assert_array_almost_equal(m_row, 3.000, decimal=3)
    m_angle = measure_angles(grid)
    assert_array_almost_equal(m_angle, 89.999, decimal=3)

def test_make_grids_with_labels():
    n_rows = 4
    n_columns = 3
    chan_pattern = 'chan{:02d}'

    grid2d = make_grid_with_labels(n_rows, n_columns, 'TBLR', chan_pattern)
    assert grid2d['label'][0, 0] == chan_pattern.format(1)  # TL
    assert grid2d['label'][n_rows - 1, 0] == chan_pattern.format(n_rows)  # TR
    assert grid2d['label'][0, n_columns - 1] == chan_pattern.format((n_rows - 1) * n_columns)  # BL
    assert grid2d['label'][n_rows - 1, n_columns - 1] == chan_pattern.format(n_rows * n_columns)  # BR

    grid2d = make_grid_with_labels(n_rows, n_columns, 'RLBT', chan_pattern)
    assert grid2d['label'][0, 0] == chan_pattern.format(n_rows * n_columns)  # TL
    assert grid2d['label'][n_rows - 1, 0] == chan_pattern.format(n_columns)  # TR
    assert grid2d['label'][0, n_columns - 1] == chan_pattern.format((n_rows - 1) * n_columns + 1)  # BL
    assert grid2d['label'][n_rows - 1, n_columns - 1] == chan_pattern.format(1)  # BR

def test_index_order():
    grid2d = make_grid(2, 2)
    grid2d['label'] = [['1', '2'], ['3', '4']]
    assert list(index_order(grid2d, '1', 'minor')) == [(0, 0), (0, 1), (1, 0), (1, 1)]


import pygame
from scripts.button import Button

class Action_Menu():
    def __init__(self, hand, game, gameboard, x, y):
        self.hand = hand
        self.game = game
        self.gameboard = gameboard
        self.isInit = False
        self.x, self.y = x + 75, y - 100 #offset away from the hand center
        self.original_x, self.original_y = x+75, y-100
        SCALE = .07
        #intialize button objects
        self.hit_button_image = pygame.image.load("images/buttons/wood_button_hit.png").convert_alpha()
        self.double_button_image = pygame.image.load("images/buttons/wood_button_double.png").convert_alpha()
        self.split_button_image = pygame.image.load("images/buttons/wood_button_split.png").convert_alpha()
        self.stand_button_image = pygame.image.load("images/buttons/wood_button_stand.png").convert_alpha()
        self.hit_button = Button(self.x, self.y, self.hit_button_image, scale=SCALE)
        self.double_button = Button(self.x, self.y, self.double_button_image, scale=SCALE)
        self.split_button = Button(self.x, self.y, self.split_button_image, scale=SCALE)
        self.stand_button = Button(self.x, self.y, self.stand_button_image, scale=SCALE)

    def reset_placement(self):
        self.x = self.original_x
        self.y = self.original_y
        self.isInit = False

    #This section of code allows Dynamic relocation of the player's possible playing options
    def init_dynamic_buttons(self):
        if self.isInit == False:

            if self.hand.hand_sum == 21:
                self.y -=35 #move the original placement down
            self.hit_button.rect.center = (self.x, self.y)

            self.y += 35 #places the next y coordinate
            #if hand can double down, update the button's next y coordinate
            if len(self.hand.card_list) <= 2 and self.hand.hand_sum <=11 and self.gameboard.player.fund > self.hand.bet_amount:
                self.double_button.rect.center = (self.x, self.y) #the next coor
                self.y += 35 #updates next y coordinate
            #if hand can split, update the button's next y coordinate
            if self.hand.card_list[0].type == self.hand.card_list[1].type and self.hand.hand_size == 2:
                self.split_button.rect.center = (self.x,self.y)
                self.y += 35 #updates next y coordinate
            #Update Stand's coordinates
            self.stand_button.rect.center = (self.x, self.y)
            self.isInit = True #run once

#TODO make the menu dynamicly relocate base on avaiable player options
    def display(self):
        #Allows Dynamic buttons to appear
        self.init_dynamic_buttons()

        #HIT HAND BUTTON
        if self.hand.hand_sum != 21:
            if self.hit_button.draw(self.game.display):
                self.gameboard.hit(self.hand)
                self.reset_placement() #resets for next available options

        #DOUBLE DOWN BUTTON
        #check if hand is doubleable, if the first 2 cards are less than 11
        if len(self.hand.card_list) <= 2 and self.hand.hand_sum <=11 and self.gameboard.player.fund > self.hand.bet_amount:
            if self.double_button.draw(self.game.display):
                #player hits 1 cards side ways (pygame image rotation)
                self.gameboard.deck_pile.top().image_surface= pygame.transform.rotate(surface=self.gameboard.deck_pile.top().image_surface, angle = 90)
                self.gameboard.hit(self.hand)
                #add more funds equal to current bet from player funds and subtract from player's fund
                self.gameboard.player.fund -= self.hand.bet_amount #subtract funds
                self.gameboard.player.current_bet += self.hand.bet_amount #update current round bet
                self.hand.bet_amount = self.hand.bet_amount*2 #update hand bet amount
                self.hand.stand = True#ends the hand's turn
        #SPLIT BUTTON
        #Check if hands has matching pairs
        if self.hand.card_list[0].type == self.hand.card_list[1].type and self.hand.hand_size == 2:
            if self.split_button.draw(self.game.display):
                #subtract funds and update UI
                self.gameboard.player.fund -= self.hand.bet_amount
                self.gameboard.player.current_bet += self.hand.bet_amount
                #create a new test hand next
                self.gameboard.player.split_hand(hand=self.hand,
                                               isExtra=True,
                                                bet_amount=self.hand.bet_amount,
                                                x=self.hand.x,
                                                y=self.hand.y,
                                                isDealer=False)
                #Moves the hand to the right
                self.hand.x += 60
                #reverts to the last known placement
                self.hand.placement.x = self.hand.x-10
                self.hand.placement.y = self.hand.y-20
                #shifts the card to the right
                for i , card in enumerate(self.hand.card_list):
                    card.rect.x += 70
                #Pass out new cards to both hands and continue to play
                current_index = self.gameboard.turn_list.index(self.hand)
                self.gameboard.hit(self.hand)
                self.gameboard.hit(self.gameboard.turn_list[current_index+1])

                self.original_x += 70
                self.reset_placement() #resets for next available options

        #STAND BUTTON
        if self.stand_button.draw(self.game.display):
            self.hand.stand = True


"""

Revision ID: 0185
Revises: 0184_alter_primary_key_1
Create Date: 2018-05-14 12:27:41.127488

"""
from alembic import op
import sqlalchemy as sa
import datetime
import uuid

revision = '0185'
down_revision = '0184_alter_primary_key_1'

user_id= '6af522d0-2915-4e52-83a3-3690455a5fe6'
service_id = 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553'

permissions = [
    'manage_users',
    'manage_templates',
    'manage_settings',
    'send_texts',
    'send_emails',
    'send_letters',
    'manage_api_keys',
    'view_activity'
]

def upgrade():
    op.execute("""
    UPDATE users SET
          email_address = 'notify-service-user@digital.gov.au'
    WHERE email_address = 'notify-service-user@digital.cabinet-office.gov.uk'
    """)

    for permission in permissions:
        op.execute("""
        INSERT INTO permissions
               (id, service_id, user_id, permission, created_at)
        VALUES ('{id}', '{service_id}', '{user_id}', '{permission}', '{created_at}')
        """.format(
            id=str(uuid.uuid4()),
            service_id=service_id,
            user_id=user_id,
            permission=permission,
            created_at=datetime.datetime.utcnow(),
        ))

def downgrade():
    op.execute("""
    UPDATE users SET
          email_address = 'notify-service-user@digital.cabinet-office.gov.uk',
          mobile_number = '+441234123412'
    WHERE email_address = 'notify-service-user@digital.gov.au'
    """)

    op.execute("""
    DELETE FROM permissions WHERE user_id = '{user_id}'
    """.format(user_id=user_id)
    )


from typing import Any, List
import pickle

import numpy as np
import networkx as nx

TC_PAUSE = 3
FORWARD_DELAY = 2

class frameBPDUMST():
    def __init__(self,
        root_bid: str,
        external_rpc: int,
        region_root_bid: str,
        internal_rpc: int,
        bid: str,
        send_port_id: int,
        recv_port_id: int
        ):

        self.root_bid = root_bid
        self.external_rpc = external_rpc
        self.region_root_bid = region_root_bid
        self.internal_rpc = internal_rpc
        self.bid = bid
        self.send_port_id = send_port_id
        self.recv_port_id = recv_port_id

    def getBest(self, other):

        """
        Из двух BPDU кадров выбирается наименьший. Сраниваются поля в
        следующеи порядке:
        1. Root bridge id
        2. Root path cost
        3. Sender bridge id
        4. Sender port id
        """
        if not other:
            return self

        _bpdu = (self.root_bid, self.external_rpc, self.region_root_bid, \
                self.internal_rpc, self.bid, self.send_port_id, self.recv_port_id)
        _bpdu_other = (other.root_bid, other.external_rpc, other.region_root_bid, \
                other.internal_rpc, other.bid, other.send_port_id, other.recv_port_id)

        return self if _bpdu <= _bpdu_other else other

    def __repr__(self) -> str:
        return f"{self.root_bid}, {self.external_rpc}, {self.region_root_bid}, {self.internal_rpc}, {self.bid}, {self.send_port_id}"

    def __str__(self) -> str:
        return f"{self.root_bid}, {self.external_rpc}, {self.region_root_bid}, {self.internal_rpc}, {self.bid}, {self.send_port_id}"

    def tostp(self):
        b = frameBPDUSTP(self.root_bid, self.external_rpc, self.bid, self.send_port_id)
        return b

    def tomst(self, *args):
        return self

class frameBPDUSTP():
    def __init__(self,
        root_bid: str,
        rpc: int,
        sender_bid: int,
        send_port_id: str):

        self.root_bid = root_bid
        self.rpc = rpc
        self.sender_bid = sender_bid
        self.send_port_id = send_port_id

    def getBest(self, other):

        """
        Из двух BPDU кадров выбирается наименьший. Сраниваются поля в
        следующеи порядке:
        1. Root bridge id
        2. Root path cost
        3. Sender bridge id
        4. Sender port id
        """

        if not other:
            return self

        if type(other) != frameBPDUSTP:
            other = other.tostp()
        _bpdu = (self.root_bid, self.rpc, self.sender_bid, self.send_port_id)
        _bpdu_other = (other.root_bid, other.rpc, other.sender_bid, other.send_port_id)
        return self if _bpdu <= _bpdu_other else other

    def __repr__(self) -> str:
        return f"[{self.root_bid}, {self.rpc}, {self.sender_bid}, {self.send_port_id}]"

    def __str__(self) -> str:
        return f"[{self.root_bid}, {self.rpc}, {self.sender_bid}, {self.send_port_id}]"

    def tomst(self, recv_port_id):
        b = frameBPDUMST(self.root_bid, self.rpc, self.sender_bid, 0, self.sender_bid, self.send_port_id, recv_port_id)
        return b
    def tostp(self):
        return self

class Port():
    """
    Порт коммутатора
    """

    ROLE_STATUS_MAP_STP = {
        "Root Port": {
            "Blocked": "Listening",
            "Listening": "Learning",
            "Learning": "Forwarding",
            "Forwarding": "Forwarding"
        },
        "Designated": {
            "Blocked": "Listening",
            "Listening": "Learning",
            "Learning": "Forwarding",
            "Forwarding": "Forwarding"
        },
        "Undesignated": "Blocked"
    }

    ROLE_STATUS_MAP_RSTP = {
        "Root Port": {
            "Blocked": "Learning",
            "Learning": "Forwarding",
            "Forwarding": "Forwarding"
        },
        "Designated": {
            "Blocked": "Learning",
            "Learning": "Forwarding",
            "Forwarding": "Forwarding"
        },
        "Undesignated": "Blocked"
    }

    def __init__(self, id, cost, rstp, mst, FORWARD_DELAY):
        self.id = id
        self.cost = cost
        self.remote_port = None
        self.region_root = True
        self.root = True
        self.broken = False
        self.rstp = rstp
        self.mst = mst
        self.FORWARD_DELAY = FORWARD_DELAY
        self.resetSTP()

    def resetSTP(self) -> None:
        self.best_bpdu = None
        self.role = "Undesignated"
        self.status = "Blocked"
        self.rpc = None
        self.timer = self.FORWARD_DELAY

    def setRemote(self, remote) -> None:
        self.remote_port = remote

    def sendBPDU(self, b)-> None:
        # сохраняем superior BPDU
        if self.remote_port.mst:
            self.best_bpdu = self.best_bpdu.getBest(b) if self.best_bpdu else b
            self.remote_port.receiveBPDU(b)
        else:
            if b:
                b = b.tostp()
            if self.best_bpdu:
                self.best_bpdu = self.best_bpdu.tostp()

            self.best_bpdu = self.best_bpdu.getBest(b) if self.best_bpdu else b
            self.remote_port.receiveBPDU(b)

    def receiveBPDU(self, b) -> None:
        # сохраняем superior BPDU
        if self.remote_port.mst:
            self.best_bpdu = self.best_bpdu.getBest(b) if self.best_bpdu else b
        else:
            if self.best_bpdu:
                self.best_bpdu = self.best_bpdu.tostp()
            self.best_bpdu = self.best_bpdu.getBest(b) if self.best_bpdu else b

    def setRole(self, role: str)-> None:
        # обновление роли и статуса
        if self.delay():
            self.role = role
            status = self.ROLE_STATUS_MAP_RSTP[role] if self.rstp else self.ROLE_STATUS_MAP_STP[role]
            self.status = status[self.status] if type(status) == dict else status
            self.timer = self.FORWARD_DELAY
            if self.status in ['Blocked', 'Forwarding']:
                changed = False
            else:
                changed = True

        else:
            changed = True
        return (self.status, changed)

    def broke(self):
        self.broken = True

    def delay(self):
        if self.timer > 0:
            self.timer -= 1
        return self.timer == 0

    def __repr__(self):
        return f"{self.id} {self.role} {self.status} {self.mst}"

class Bridge():
    """
    Коммутатор
    """

    def __init__(
            self,
            name: str,
            bid: str,
            region_name: str,
            revision_level: int,
            msti: int):

        self.name = name
        self.bid = bid
        self.root_bid = self.bid
        self.boundary = None
        self.ports = []
        self.timer = 0
        self.region_name = region_name
        self.revision_level = revision_level
        self.msti = msti

    def launch(self) -> None:
        """
        Когда коммутатор подключается к сети, он думает - что он
        корневой, и посылает стратовый BPDU на каждый порт
        """

        self.best_bpdu = frameBPDUMST(self.bid, 0, self.bid, 0, self.bid, 0, 0)
        self.root_bid = self.bid
        self.root = True
        self.region_root = True
        self.region_root_bid = self.bid
        self.root_port = None

        for port in self.ports:
            port.resetSTP()

    def processBPDUs(self, net) -> None:
        """
        Коммутатор генерирует новый пакет BPDU, основываясь на данных
        из предыдущего BPDU. Если новый BPDU является superior по
        отношению BPDU, который коммутатор получил на порту, он
        передаст новый BPDU, иначе он больше не будет передавать
        BPDU на этот порт. Этот порт будет либо root, либо
        blocked. Другие порты являются designated. Коммутатор
        продолжит посылать BPDU на эти порты и передавать пакеты
        данных.
        """
        if self.delay():
            # Создаем на каждый порт по BPDU, каждое BPDU рассчитывается
            # на основе наименьшего BPDU и цены интерфейса, и id порта.
            best = []

            for port in self.ports:
                if port.best_bpdu:
                    if type(port.best_bpdu) == frameBPDUMST:
                        b = frameBPDUMST(
                                port.best_bpdu.root_bid,
                                port.best_bpdu.external_rpc + port.cost * int(not port.mst),
                                port.best_bpdu.region_root_bid,
                                port.best_bpdu.internal_rpc + port.cost,
                                self.bid,
                                port.id,
                                port.remote_port.id)
                    else:
                        b = frameBPDUMST(
                                port.best_bpdu.root_bid,
                                port.best_bpdu.rpc + port.cost,
                                self.bid,
                                0,
                                self.bid,
                                port.id,
                                port.remote_port.id)
                    best += [(b, port)]

            # Выбор наименьшего BPDU между всеми портами
            best_bpdu = frameBPDUMST(self.bid, 0, self.bid, 0, self.bid, 0, 0)
            root_port = None
            region_root_port = None
            for b, port in best:
                if b.getBest(best_bpdu) == b:
                    best_bpdu = b
                    root_port = b.send_port_id
                    if not port.mst:
                        region_root_port = port

            if (best_bpdu.root_bid, best_bpdu.external_rpc, best_bpdu.region_root_bid, best_bpdu.internal_rpc, best_bpdu.bid) != \
                (self.best_bpdu.root_bid, self.best_bpdu.external_rpc, self.best_bpdu.region_root_bid, self.best_bpdu.internal_rpc, self.best_bpdu.bid):
                net.evolvs(True)
                # print(best_bpdu)
                # print(self.best_bpdu)
                # print('BDPU changed')

            self.best_bpdu = best_bpdu

            if self.root_port != root_port:
                net.evolvs(True)
                # print('root_port')

            self.root_port = root_port

            new_root = self.best_bpdu.root_bid == self.bid
            if new_root != self.root:
                net.evolvs(True)
                # print('root')

            self.root = new_root
            # self.region_root_bid = self.best_bpdu.region_root_bid if mst_seen else self.region_root_bid
            self.region_root_bid = self.best_bpdu.region_root_bid
            if region_root_port:
                region_root_port.root = True
                self.region_root_port = region_root_port
                for port in self.ports:
                    if port != region_root_port:
                        port.root = False
            self.region_root = self.region_root_bid == self.bid
            new_root_bid = self.best_bpdu.root_bid

            if new_root_bid != self.root_bid:
                net.evolvs(True)
                # print('root_bid')

            self.root_bid = new_root_bid
            # print(self.root_port)
            # print(self.best_bpdu)
            # print(self.ports)

    def sendBPDUs(self, net) -> None:
        delay =  self.delay()
        port_status_changed = []
        brige_best_bpdu = self.best_bpdu.tostp()
        if self.root:
            for port in self.ports:
                if not port.broken:
                    if delay:
                        self.best_bpdu.send_port_id = port.id
                        port.sendBPDU(self.best_bpdu)
                    if port.mst or not port.mst and port.remote_port.region_root:

                        port_status_changed += [port.setRole("Designated")]
                    else:
                        port.rpc = None
                        port_status_changed += [port.setRole("Undesignated")]
        elif self.region_root:
            for port in self.ports:
                if not port.broken:
                    if port.mst:
                        if delay:
                            self.best_bpdu.send_port_id = port.id
                            port.sendBPDU(self.best_bpdu)
                        port_status_changed += [port.setRole("Designated")]
                    else:
                        if brige_best_bpdu.getBest(port.best_bpdu) == brige_best_bpdu: # port.remote_port.region_root:
                            if delay:
                                port.best_bpdu = port.best_bpdu.tostp() if port.best_bpdu else None
                                port.sendBPDU(self.best_bpdu)
                            port_status_changed += [port.setRole("Designated")]
                        elif port.region_root and port.id == self.root_port:
                            if delay:
                                port.rpc = brige_best_bpdu.rpc
                            port_status_changed += [port.setRole("Root Port")]
                        else:
                            if delay:
                                port.rpc = None
                            port_status_changed += [port.setRole("Undesignated")]
        else:
            for port in self.ports:
                if not port.broken:
                    if port.mst:
                        port_best_bpdu = port.best_bpdu.tomst(port.remote_port.id)
                        # self.best_bpdu = self.best_bpdu.tomst(port.remote_port.id)
                        if self.best_bpdu.getBest(port_best_bpdu) == self.best_bpdu:
                            if delay:
                                port.sendBPDU(self.best_bpdu)
                                port.rpc = None
                            port_status_changed += [port.setRole("Designated")]
                        elif port.id == self.root_port:
                            if delay:
                                port.rpc = self.best_bpdu.external_rpc
                            port_status_changed += [port.setRole("Root Port")]
                        else:
                            if delay:
                                port.rpc = None
                            port_status_changed += [port.setRole("Undesignated")]
                    else:
                        port_best_bpdu = port.best_bpdu.tostp() if port.best_bpdu else None
                        if brige_best_bpdu.getBest(port_best_bpdu) == brige_best_bpdu: # port.remote_port.region_root:
                            if delay:
                                port.sendBPDU(brige_best_bpdu)
                                port.rpc = None
                            port_status_changed += [port.setRole("Designated")]
                        elif port.id == self.root_port:
                            if delay:
                                port.rpc = brige_best_bpdu.rpc
                            port_status_changed += [port.setRole("Root Port")]
                        else:
                            if delay:
                                port.rpc = None
                            port_status_changed += [port.setRole("Undesignated")]

        port_changed = [s[1] for s in port_status_changed]
        port_blocked = [s[0] == 'Blocked' for s in port_status_changed]
        if any(port_changed) or all(port_blocked):
            net.evolvs(True)

    def setDelay(self):
        self.timer = TC_PAUSE

    def delay(self):
        if self.timer > 0:
            self.timer -= 1
        return self.timer == 0

    def reportSTP(self):

        root_id = "Этот мост - root" if self.root else ""
        print(f"BID: {self.bid}. {root_id}")

        row_format = "{:<8} {:<15} {:<15} {:<8}"
        print("-" * 65)
        print(row_format.format('Port', 'Role', 'Status', 'Cost', 'Cost-to-Root'))
        print("-" * 65)
        for p in sorted(self.ports, key=lambda x: x.id):
            print(row_format.format(p.id, p.role, p.status, p.cost))
        print()

class Network():

    COST_MAP_STP = {
        10: 100,
        100: 19,
        1000: 4,
        10000: 2
    }

    COST_MAP_RSTP = {
        10: 2000000,
        100: 200000,
        1000: 20000,
        10000: 2000
    }

    def __init__(self, rstp=True, bridge2vlan=None, vlans=None, FORWARD_DELAY=FORWARD_DELAY):
        self.bridges = {}
        self.edges = []
        self.evolving = None
        self.rstp = rstp
        self.bridge2vlan = bridge2vlan
        self.vlans = vlans
        self.FORWARD_DELAY = FORWARD_DELAY

    def evolvs(self, evolvs):
        self.evolving = evolvs

    # def cut_edge(self, onbids=False, bid1=None, bid2=None, edgeid=None):

    #     if onbids:
    #         for edge in self.edges:
    #             br1, br2 = edge[0], edge[1]
    #             if br1.bid in (bid1, bid2) and br2.bid in (bid1, bid2):
    #                 p1, p2 = edge[2], edge[3]
    #     else:
    #         edge = self.edges[edgeid]
    #         br1, br2 = edge[0], edge[1]
    #         p1, p2 = edge[2], edge[3]

    #     p1.sendBPDU(frameBPDUMST(False, self.bid, 0, self.bid, 0, self.bid, 0))
    #     p2.sendBPDU(frameBPDUMST(False, self.bid, 0, self.bid, 0, self.bid, 0))
    #     p1.broke()
    #     p2.broke()

        # print('CUTTED!')

    def getBridge(self, name, bid, region_name, revision_level, msti):
        if bid in self.bridges:
            return self.bridges[bid]
        # имея несколько vlan в одном дереве, получаем
        # что вероятность участия моста в соединении с хостами
        # конкретного vlan-а есть вероятность того, что среди
        # len(vlans) испытания есть хотя бы одно удачное
        # mst = np.random.binomial(1, 0.7, 1)[0] >= 1
        br = Bridge(name, bid, region_name, revision_level, msti)

        self.bridges[bid] = br
        return br

    def getAllBridges(self):
        return self.bridges.values()

    def connect(self, br1, port1, br2, port2, speed):
        cost = self.COST_MAP_RSTP[speed] if self.rstp else self.COST_MAP_STP[speed]
        mst = br1.region_name == br2.region_name and \
            br1.revision_level == br2.revision_level and \
            br1.msti == br2.msti

        local = Port(port1, cost, self.rstp, mst, self.FORWARD_DELAY)
        remote = Port(port2, cost, self.rstp, mst, self.FORWARD_DELAY)
        local.setRemote(remote)
        remote.setRemote(local)
        br1.ports.append(local)
        br2.ports.append(remote)
        self.edges += [(br1, br2, local, remote, cost)]

    def drawG(self):

        status2c = {
            "Blocked": 'b',
            "Listening": 'li',
            "Learning": 'le',
            "Forwarding": 'f'
        }

        G = nx.Graph()
        G.add_edges_from([(br1.bid, br2.bid) for br1, br2, _, _, _ in self.edges])

        positions = nx.kamada_kawai_layout(G, scale=3)
        edge_labels = {}
        for edge in self.edges:
            br1, br2, local, remote, cost = edge
            gap = " "*1
            if local.broken and remote.broken:
                l = 'X'
            else:
                l = f"{status2c[local.status]}" + gap + f"{cost}" + gap + f"{status2c[remote.status]}"
            edge_labels[(br1.bid, br2.bid)] = l

            if local.broken and remote.broken:
                G.edges[br1.bid, br2.bid]['color'] = 'black'
            elif local.status == 'Forwarding' and remote.status == 'Forwarding' and br1.bid != br2.bid:
                G.edges[br1.bid, br2.bid]['color'] = 'blue'
            else:
                G.edges[br1.bid, br2.bid]['color'] = 'red'

        labels = {bid : f"bid:{bid}\n" + \
                  f"R: {self.bridges[bid].root_bid}\n" + \
                  f"vlan:{self.bridge2vlan[bid] if bid in self.bridge2vlan else 'X'}\n" + \
                  f"RR: {self.bridges[bid].region_root_bid}\n" + \
                  f"E: {self.bridges[bid].best_bpdu.external_rpc}\n" + \
                  f"I: {self.bridges[bid].best_bpdu.internal_rpc}"
                  for bid in positions.keys()}
        nx.draw_networkx_labels(G, positions, labels=labels)
        _, colors_edges = zip(*nx.get_edge_attributes(G,'color').items())

        color_set = ['orange', 'yellow', 'green', 'red', 'pink']
        for bid, bridge in self.bridges.items():
            G.nodes[bid]['color'] =  color_set[bridge.region_name]

        _, colors_nodes = zip(*nx.get_node_attributes(G,'color').items())

        return G, positions, edge_labels, colors_edges, colors_nodes

    def save(self, name):
        with open(name, 'wb') as handle:
            pickle.dump((self.bridges, self.edges), handle, protocol=pickle.HIGHEST_PROTOCOL)

    def load(self, name):
        with open(name, 'rb') as handle:
            self.bridges, self.edges = pickle.load(handle)

    def __repr__(self):
        return ",".join(map(str, self.bridges.keys()))


"""
DiCarlo_setup: functions to return the parameters for noise and experimental
design of DiCarlo qubits, in a format compatable with a circuit builder.
"""
import warnings

import numpy as np
from numpy import pi

from quantumsim.circuit import (uniform_noisy_sampler, uniform_sampler,
                                _ensure_rng)
from .experiment_setup import Setup
from . import gate_templates as gt
from .setup_functions import make_1q2q_gateset

# Computations are not reproducible, won't fix (here, Quantumsim will get this
# functionality with reproducible computations)
warnings.simplefilter('ignore', UserWarning)

def quick_setup(qubit_list, connectivity_dic=None, rng=None, *, seed=None,
                **kwargs):
    """
    Quick setup: a function to return a setup that may be immediately
    used to make a qsoverlay builder.

    The setup file accepts the following parameters, defined in
    arXiv:1703:04136 (or elsewhere):

    Parameters
    ----------
    noise_flag(=True): turn on or off noise (for debugging)
    scale(=1): multiplier for t1,t2 (and divides other error rates
            by same amount)
    t1(=30000ns): standard T1 time
                (arXiv:1703.04136, App.B.1, eq. B2)
    t2(=30000ns): standard T2 time
                (arXiv:1703.04136, App.B.1, eq. B3
                                   and Sec.IV.B.1 eq. 6)
    dephasing_axis(=1e-4): dephasing of x/y rotations along the
            axis (arXiv:1703.04136 App.B.3 eq below B5
                  defined as p_axis)
    dephasing_angle(=5e-4): dephasing of x/y rotations in the plane
                (arXiv:1703.04136 App.B.3 eq below B5
                 defined as p_plane)
    dephasing(=5e-4): dephasing of z rotations in the plane
                (arXiv:1703.04136 App.B.3 eq below B5
                 defined as p_plane)
    p_exc_init(=0.0): additional excitations between initial
                dephasing and readout in measurement
                (separate from T1 effects!)
                (arXiv:1703.04136 App.B.6 Fig.9
                 defined as $p_1^uparrow$)
    p_dec_init(=0.005): additional relaxation between initial
                dephasing and readout in measurement
                (separate from T1 effects!)
                (arXiv:1703.04136 App.B.6 Fig.9
                 defined as $p_1^downarrow$)
    p_exc_fin(=0.0): additional excitations between readout
                and end of the measurement period
                (separate from T1 effects!)
                (arXiv:1703.04136 App.B.6 Fig.9
                 defined as $p_2^uparrow$)
    p_dec_fin(=0.015): additional relaxation between readout
                and end of the measurement period
                (separate from T1 effects!)
                (arXiv:1703.04136 App.B.6 Fig.9
                 defined as $p_2^downarrow$)
    residual_excitations(=0.01): residual excitation population
                in transmon qubits after readout.
    photons(=False): whether to include dephasing due to
                resonator photons after measurements.
                (arXiv:1703.04136 App.B.2)
    alpha0(=4 photons): photon population at the end of the
                measurement window
                (arXiv:1703.04136 App.B.2 equation
                 bottom-left of page 12.)
    kappa(=1 / 250 ns^-1): Resonator kappa
                (arXiv:1703.04136 App.B.2 equation
                 bottom-left of page 12.)
    chi(=1.3 * 1e-3 ns^-1): Resonator chi
                (arXiv:1703.04136 App.B.2 equation
                 bottom-left of page 12.)
    static_flux_std(=None): Variance of static flux
                in the resonator. None=off.
                Note - turning this on
                will require a circuit be repeatedly simulated
                to acquire good statistics of this noise.
                (arXiv:1703.04136 App.B.5 final
                 equation.)
    high_frequency(=False): Whether this qubit
                is affected by two-qubit flux noise.
    dephase_var(=1e-2/(2*pi)): Variance of incoherent version
                of static flux noise (integrated over
                all realisations).
    msmt_time(=600ns): Total time for measurement (including
                time for resonator depletion/relaxation).
    interval_time(=150ns): Point in the measurement time that
                the readout occurs at.
    oneq_gate_time(=20ns): Time that every single qubit gate
                takes to perform.
    CZ_gate_time(=40ns): time required to perform CZ gate.
                ISwap gate times are fixed to sqrt(2) times this.
    reset_time(=500ns): time required to reset qubits (following
                arXiv:1801.07689, but currently without any error
                in the reset itself).
    sampler(=None): sampler to generate measurement results.
    rng(=None): seed to generate new sampler if the above is None.
    readout_error(=0.005): readout error in a sampler if the
                above is None
                (epsilon_{RO}^0=epsilon_{RO}^1 in
                 arXiv:1703.04136, App.B.6 Fig.9)
    """
    if seed:
        warnings.warn('`seed` keyword argument is deprecated,'
                      ' please use `rng`', DeprecationWarning)
        rng = seed
    if 'sampler' not in kwargs.keys():
        kwargs['state'] = _ensure_rng(rng)

    setup = {
        'gate_dic': get_gate_dic(),
        'update_rules': get_update_rules(**kwargs),
        'qubit_dic': {
            q: get_qubit(**kwargs) for q in qubit_list
        }
    }

    if connectivity_dic:
        for qubit in qubit_list:
            if qubit not in connectivity_dic:
                connectivity_dic[qubit] = []

    setup['gate_set'] = make_1q2q_gateset(qubit_dic=setup['qubit_dic'],
                                          gate_dic=setup['gate_dic'],
                                          connectivity_dic=connectivity_dic)
    return Setup(**setup)

def asymmetric_setup(qubit_parameters=None,
                     connectivity_dic=None,
                     rng=None,
                     *, seed=None, **kwargs):
    """
    Prepares a setup for asymmetric qubits that may be immediately used to make
    a qsoverlay builder

    qubit_parameters is a dictionary that contains the paramters for each qubit
    in the following form:
    {'q1': {'t1': val, 't2': val, ...},
     'q2': {'t1': val, 't2': val, ...}}
    or:
    [['q1', 'q2', .., 'qn'],[{'t1': value, 't2': value, ...},
    {'t1': value, 't2: value, ...}, ..., {'t1': value, 't2': value, ...}]]
    Unspecified parameters will take the default values from get_qubit function
    """
    if qubit_parameters is None:
        qubit_parameters = {}
    if type(qubit_parameters) == list:
        qubit_parameters = {
            key: val
            for key, val in zip(qubit_parameters[0], qubit_parameters[1])}

    if seed:
        warnings.warn('`seed` keyword argument is deprecated,'
                      ' please use `rng`', DeprecationWarning)
        rng = seed
    if 'sampler' not in kwargs.keys():
        kwargs['state'] = _ensure_rng(rng)

    qubit_list = qubit_parameters.keys()
    asym_setup = {
        'gate_dic': get_gate_dic(),
        'update_rules': get_update_rules(**kwargs),
        'qubit_dic': {q: get_qubit(**params)
                      for q, params in qubit_parameters.items()}
    }
    if connectivity_dic:
        for qubit in qubit_list:
            if qubit not in connectivity_dic:
                connectivity_dic[qubit] = []

    asym_setup['gate_set'] = make_1q2q_gateset(
        qubit_dic=asym_setup['qubit_dic'],
        gate_dic=asym_setup['gate_dic'],
        connectivity_dic=connectivity_dic)
    return Setup(**asym_setup)

def get_gate_dic():
    """
    Returns the set of gates allowed on DiCarlo qubits.
    Measurement time is something that's still being optimized,
    so this might change.
    (msmt_time = the total time taken for measurement + depletion)
    """

    # Initialise gate set with all allowed gates
    gate_dic = {
        'CZ': gt.CZ,
        'C-Phase': gt.CPhase,
        'CPhase': gt.CPhase,
        'RotateX': gt.RotateX,
        'RX': gt.RotateX,
        'Rx': gt.RotateX,
        'RotateY': gt.RotateY,
        'RY': gt.RotateY,
        'Ry': gt.RotateY,
        'RotateZ': gt.RotateZ,
        'RZ': gt.RotateZ,
        'Rz': gt.RotateZ,
        'RotateXY': gt.RotateXY,
        'RXY': gt.RotateXY,
        'Rxy': gt.RotateXY,
        'RotateEuler': gt.RotateEuler,
        'Measure': gt.Measure,
        'ISwap': gt.ISwap,
        'ISwapRotation': gt.ISwapRotation,
        'prepz': gt.PrepGate,
        'PrepGate': gt.PrepGate,
        'ResetGate': gt.ResetGate,
        'Reset': gt.ResetGate,
        'Had': gt.Had,
        'H': gt.Had,
        'CNOT': gt.CNOT,
        'CRX': gt.CRX,
        'X': gt.XGate,
        'Y': gt.YGate,
        'Z': gt.ZGate
    }

    return gate_dic

def get_qubit(noise_flag=True,
              scale=1,
              t1=30000,
              t2=30000,
              dephasing_axis=1e-4,
              dephasing_angle=5e-4,
              dephasing=5e-4,
              p_exc_init=0.0,
              p_dec_init=0.005,
              p_exc_fin=0.0,
              p_dec_fin=0.015,
              residual_excitations=0.01,
              photons=False,
              alpha0=4,
              kappa=1 / 250,
              chi=1.3 * 1e-3,
              static_flux_std=None,
              high_frequency=False,
              dephase_var=1e-2/(2*pi),
              msmt_time=600,
              interval_time=150,
              oneq_gate_time=20,
              CZ_gate_time=40,
              reset_time=500,
              state=None,
              sampler=None,
              readout_error=0.005,
              **kwargs):
    """
    The dictionary for parameters of the DiCarlo qubits, with standard
    parameters pre-set.

    This is a bit messy right now, but has the advantage of telling
    the user which parameters they can set. Not sure how to improve
    over this.
    """
    if sampler is None:
        if noise_flag is True:
            sampler = uniform_noisy_sampler(state=state,
                                            readout_error=readout_error)
        else:
            readout_error = 0
            sampler = uniform_sampler(state=state)

    if static_flux_std is not None:
        quasistatic_flux = static_flux_std * np.random.randn()
    else:
        quasistatic_flux = None

    if noise_flag is True:

        param_dic = {
            't1': t1/scale,
            't2': t2/scale,
            'dephasing_axis': dephasing_axis*scale,
            'dephasing': dephasing*scale,
            'dephasing_angle': dephasing_angle*scale,
            'dephase_var': dephase_var*scale,
            'p_exc_init': p_exc_init*scale,
            'p_dec_init': p_dec_init*scale,
            'p_exc_fin': p_exc_fin*scale,
            'p_dec_fin': p_dec_fin*scale,
            'residual_excitations': residual_excitations*scale,
            'msmt_time': msmt_time,
            'interval_time': interval_time,
            'oneq_gate_time': oneq_gate_time,
            'CZ_gate_time': CZ_gate_time,
            'ISwap_gate_time': CZ_gate_time*np.sqrt(2),
            'reset_time': reset_time,
            'photons': photons,
            'alpha0': alpha0,
            'kappa': kappa,
            'chi': chi,
            'quasistatic_flux': quasistatic_flux,
            'high_frequency': high_frequency,
            'sampler': sampler,
            'readout_error': readout_error,
        }
    else:

        param_dic = {
            't1': np.inf,
            't2': np.inf,
            'dephasing_axis': 0,
            'dephasing': 0,
            'dephasing_angle': 0,
            'dephase_var': 0,
            'p_exc_init': 0,
            'p_dec_init': 0,
            'p_exc_fin': 0,
            'p_dec_fin': 0,
            'residual_excitations': 0,
            'msmt_time': msmt_time,
            'interval_time': interval_time,
            'oneq_gate_time': oneq_gate_time,
            'CZ_gate_time': CZ_gate_time,
            'ISwap_gate_time': CZ_gate_time*np.sqrt(2),
            'reset_time': reset_time,
            'photons': False,
            'quasistatic_flux': None,
            'high_frequency': False,
            'sampler': sampler,
            'readout_error': 0,
        }

    for key, val in kwargs.items():
        param_dic[key] = val

    return param_dic

# noinspection PyUnusedLocal
def get_update_rules(**kwargs):
    update_rules = ['update_quasistatic_flux']
    return update_rules