import numpy as np
from scipy import stats, special
from scipy.fftpack import dct
import logging
from libNeuroDyn import lorentz, gauss
from sconf import create_dir, now

logging.getLogger('simu_lib').addHandler(logging.NullHandler())

__author__ = 'Jose M. Esnaola Acebes'

""" This file contains classes and functions to be used in the QIF network simulation.

    Data: (to store parameters, variables, and some functions)
    *****
"""

pi = np.pi

class Data:
    """ The data structure will have a general structure but must be shaped to match the simulation
        parameters and variables.
    """

    def __init__(self, opts, external=(None,)):
        self.logger = logging.getLogger('simu_lib.Data')
        self.logger.debug("Creating data structure.")

        # Mutable parameters will be stored in a dictionary called prmts
        self.opts = opts
        # Non-mutable parameters will be stored as separated variables
        self.t0 = opts['t0']  # Initial time
        self.tfinal = opts['tfinal']  # Final time
        self.total_time = opts['tfinal'] - opts['t0']  # Time of simulation
        self.dt = opts['dt']  # Time step
        self.taue = opts['taue']
        self.taui = opts['taui']
        self.faketau = float(opts['faketau'])
        self.n = opts['n']
        self.ne = opts['ne']
        self.ni = opts['ni']

        if self.ne + self.ni != 1:
            self.logger.warning("The proportion e:i of neurons %d:%d is odd..." % (self.ne * 100, self.ni * 100))

        # 0.2) Define the temporal resolution and other time-related variables
        self.tpoints = np.arange(self.t0, self.tfinal, self.dt)  # Points for the plots and others
        self.nsteps = len(self.tpoints)  # Total time steps

        self.systems = opts['systems']
        self.logger.debug("Systems to be simulated: %s" % self.systems)
        if set(self.systems).intersection({'nf', 'wc-nf', 'qif-nf', 'if-nf', 'eif-nf'}):
            self.nf = True
            self.logger.info("Type of system: Neural Network.")
        else:
            self.nf = False
            self.logger.info("Type of system: Neural Population(s).")
            if self.n > 2:
                self.n = 2
                self.opts['n'] = 2
                self.opts['network']['n'] = 2

        opts.update({'nf': self.nf})

        # Conductance-based neurons
        if opts.get('cond', False):
            self.cond = True
        else:
            self.cond = False

        # Other simulation options here:
        self.all = opts['ap']  # In the GUI it will expand all the mutable parameters entered in the conf file.

        # Simulation control variables
        self.opts['controls'] = {'exit': False, 'pause': True, 'stop': False, 'x': None, 'y': None}
        # Simulation raster control flags
        self.opts['raster'] = {'start': False, 'update': False, 'rate': 100, 'dynamic': False, 'pop': None}

        # Extra objects, such as perturbations, measures, etc.
        # TODO
        external = list(external)
        for obj in external:
            if obj:
                pass

        # ######## Edit below (and "population" function) to adapt for different simulations #####################
        self.vars = {'t': self.tpoints, 'tstep': 0, 'temps': 0.0, 'dummy': np.ones(self.nsteps) * 0.8, 'cycle': 0}
        self.lims = {'t': [0.0, self.tfinal * self.faketau], 're': [0, 100], 'ri': [0, 100], 'rwe': [0, 100], 'rwi': [0, 100],
                     'Pe': [-np.pi, np.pi],
                     've': [-2, 2], 'vi': [-2, 2], 'se': [0, 2], 'si': [0, 2], 'Pi': [-np.pi, np.pi]}
        # In case we have a neural field:
        if self.n > 2:
            self.vars.update({'phi': np.linspace(-np.pi, np.pi, self.n)})
            self.lims.update({'phi': [-np.pi, np.pi]})
            self.nf = True
            it = np.ones((self.nsteps, self.n)) * 0.0
        else:
            if not self.nf:
                self.n = 1
                opts['n'] = 1
                opts['network']['n'] = 1
                self.nf = False
            it = np.ones(self.nsteps) * 0.0

        # External time-dependent input current
        self.vars.update({'it': it})

        # Output variables will be stored in dictionaries to make the Queue handling easy
        exc, inh = (None, None)
        if set(self.systems).intersection({'fr', 'wc'}):
            exc = self.single_population(self.nsteps, 2.0, -1.0, 0.0, name="e")
            inh = self.single_population(self.nsteps, 1.0, -0.5, 0.0, name="i")
        elif set(self.systems).intersection({'nf', 'wc-nf'}):
            exc = self.network_population(self.nsteps, self.n, 2.0, -1.0, 0.0, name="e")
            inh = self.network_population(self.nsteps, self.n, 2.0, -1.0, 0.0, name="i")

        if set(self.systems).intersection({'fr', 'wc', 'nf', 'wc-nf'}):
            self.vars.update(exc)
            self.vars.update(inh)
            del exc, inh
        else:
            self.vars.update({'re': 0, 'ri': 0, 've': 0, 'vi': 0, 'rwe': 0, 'rwi': 0, 'ser': 0, 'sir': 0, 'swer': 0, 'swir': 0})

        # Spiking neurons
        # General configuration (number of neurons in each population, etc.)
        if set(self.systems).intersection({'qif-fr', 'if-fr', 'eif-fr', 'qif-nf', 'if-nf', 'eif-nf'}):
            # Configuration of the spiking neurons:
            self.vpeak = 100.0
            self.vreset = -100.0
            self.vth = 0.0  # Threshold voltage (not implemented)
            self.rte = self.taue * (1.0 / self.vpeak - 1.0 / self.vreset)
            self.rti = self.taui * (1.0 / self.vpeak - 1.0 / self.vreset)
            self.tpeak_e = self.taue / self.vpeak
            self.tpeak_i = self.taui / self.vpeak
            self.opts['sp'] = 'qif'
            if set(self.systems).intersection({'if-fr', 'eif-fr', 'if-nf', 'eif-nf'}):
                self.vpeak = opts.get('vpeak', 0)
                self.vreset = opts.get('vreset', -60.0)
                self.reversal = opts.get('revers', -50.0)
                self.sharp = opts.get('sharp', 3)
                self.rheo = opts.get('rheo', -53)
                if set(self.systems).intersection({'eif-fr', 'eif-nf'}):
                    self.rte = opts.get('rperiod', 0.25)
                    self.rti = self.rte*1.0
                    # self.rte = 0.1  # 5 ms assuming tau = 20 ms and dt = 2 * 10^-5 s
                    # self.rti = 0.1
                self.tpeak_e = self.dt
                self.tpeak_i = self.dt
                if set(self.systems).intersection({'if-fr', 'if-nf'}):
                    self.opts['sp'] = 'if'
                else:
                    self.opts['sp'] = 'eif'
            # Configuration of the network
            self.N = opts['N']
            self.Ne = int(self.N * self.ne)
            self.Ni = int(self.N * self.ni)
            self.logger.debug("Total number of neurons: %d."
                              "\n\t\t\t\t\t\tExcitatory: %d.\n\t\t\t\t\t\tInhibitory: %d." % (self.N, self.Ne, self.Ni))
            opts.update({'Ne': self.Ne, 'Ni': self.Ni})
            # It is necessary to load the FR class to be able to measure something
            self.fr = FiringRate(opts)
            self.sye = np.ones(self.nsteps) * 0.0
            self.syi = np.ones(self.nsteps) * 0.0
            self.vars.update({'sp_re': self.fr.re, 'sp_ri': self.fr.ri, 'sp_r': self.fr.r,
                              'tfr': self.fr.t, 'frtstep': self.fr.tstep, 'frtstep2': self.fr.tstep2, 'sye': self.sye,
                              'syi': self.syi})
            self.lims.update({'tfr': [0.0, self.tfinal * self.faketau], 'sp_re': [0.0, 100.0], 'sp_ri': [0.0, 100.0]})

            # Synaptic activation computation requires a convolution with a weighting function (Heaviside, expo, alpha)
            self.t_syn = opts.get('tsyn', 10)
            self.t_syn = 1
            self.a_tau = self.synaptic_activation(self.t_syn, self.dt)
            # Distribution of external currents
            self.eta_e = self.external_currents(opts['etae'], opts['delta'], self.Ne, n=self.n, distribution=opts['D'])
            self.eta_i = self.external_currents(opts['etai'], opts['delta'], self.Ni, n=self.n, distribution=opts['D'])
            # Distribution of reversal potentials
            if self.cond:
                self.rev_e = self.external_currents(opts['reverse'], opts['gamma'], self.Ne, n=self.n,
                                                    distribution=opts['D'])
                self.rev_i = self.external_currents(opts['reversi'], opts['gamma'], self.Ni, n=self.n,
                                                    distribution=opts['G'])

            # Matrices containing voltages, spikes, and times of the spikes
            m_type = np.dtype([('i', np.int32), ('v', np.float64), ('t', np.float32), ('s', np.int8)])
            self.m_e = np.ndarray([self.Ne], dtype=m_type)
            self.m_e['i'] = range(self.Ne)
            # self.m_e['v'] = np.random.randn(self.Ne)
            self.m_e['v'] = np.ones(self.Ne) * (-0.1)
            self.m_e['t'] = 0.0
            self.m_e['s'] = 0
            self.m_i = np.ndarray([self.Ni], dtype=m_type)
            self.m_i['i'] = range(self.Ni)
            # self.m_i['v'] = np.random.randn(self.Ni)
            self.m_i['v'] = np.ones(self.Ni) * (-0.1)
            self.m_i['t'] = 0.0
            self.m_i['s'] = 0

            # Matrices containing spikes, to be able to compute the synaptic activation
            self.spk_e = np.ones(shape=(self.Ne, self.t_syn), dtype=np.int8) * 0
            self.spk_i = np.ones(shape=(self.Ni, self.t_syn), dtype=np.int8) * 0
            # Matrices registering the spikes in the appropriate time step (takes into account the refractory period)
            self.spk_time_e = int(self.tpeak_e / self.dt)
            self.spk_time_i = int(self.tpeak_i / self.dt)
            if self.spk_time_e == 0 or self.spk_time_i == 0:
                self.spk_e_mod = None
                self.spk_i_mod = None
            else:
                self.spk_e_mod = np.ones(shape=(self.Ne, self.spk_time_e), dtype=np.int8) * 0
                self.spk_i_mod = np.ones(shape=(self.Ni, self.spk_time_i), dtype=np.int8) * 0
        else:
            self.fr = None

        if set(self.systems).intersection({'qif-fr', 'if-fr', 'eif-fr'}):
            # Anything special for the spk-n fr simulations goes here
            pass
        elif set(self.systems).intersection({'qif-nf', 'if-nf', 'eif-nf'}):
            self.dN = self.N / self.n
            self.dNe = self.Ne / self.n
            self.dNi = self.Ni / self.n
            # Auxiliary matrices for the dot product
            self.auxMatE = np.zeros((self.n, self.Ne))
            self.auxMatI = np.zeros((self.n, self.Ni))
            for i in xrange(self.n):
                self.auxMatE[i, i * self.dNe:(i + 1) * self.dNe] = 1.0
                self.auxMatI[i, i * self.dNi:(i + 1) * self.dNi] = 1.0

            self.auxMat = np.zeros((self.n, self.N))
            for i in xrange(self.n):
                self.auxMat[i, i * self.dN:(i + 1) * self.dN] = 1.0
            self.aux = {'e': self.auxMatE, 'i': self.auxMatI, 'r': self.auxMat}

            # Arrays to select different populations in the raster plot
            self.pope = {}
            self.popi = {}
            for n in xrange(self.n):
                self.pope[n] = np.arange(n * self.dNe, (n+1) * self.dNe)
                self.popi[n] = np.arange(n * self.dNi, (n + 1) * self.dNi)

        # Create a registry of the dimensions of the variables
        for var in self.vars:
            shape = np.shape(self.vars[var])
            # self.logger.debug("Dim of %s: %s." % (var, str(shape)))
            if len(list(shape)) > 1:
                cols = len(self.vars[var][0])
                # self.logger.debug("\tIs a Matrix with %d colums." % cols)
                try:
                    self.lims[var] = list(np.concatenate((np.array(self.lims[var]), np.array([cols]))))
                    self.logger.debug("\tLim array is now %s" % self.lims[var])
                except KeyError:
                    pass

        self.Save = SaveResults

    @staticmethod
    def single_population(nsteps, r0=1.0, v0=-1.0, s0=0.0, name=""):
        r = np.ones(nsteps) * 0.1
        rw = np.ones(nsteps) * 0.1
        v = np.ones(nsteps) * (-0.01)
        r[len(r) - 1] = r0
        v[len(v) - 1] = -v0
        s = np.ones(nsteps) * 0.1
        s[len(s) - 1] = s0
        sw = np.ones(nsteps) * 0.1
        sw[len(sw) - 1] = s0
        # Kuramoto order parameter and phase
        R = np.ones(nsteps) * 0.1
        P = np.ones(nsteps) * 0.1
        return {'rw' + name: rw, 'r' + name: r, 'v' + name: v, 's' + name + 'r': s, 'sw' + name + 'r': sw, 'R' + name: R, 'P' + name: P}

    @staticmethod
    def network_population(nsteps, n, r0=1.0, v0=-1.0, s0=0.0, name=""):
        r = np.ones((nsteps, n)) * 0.0
        v = np.ones((nsteps, n)) * 0.0
        r[len(r) - 1, :] = r0
        v[len(v) - 1, :] = v0
        s = np.ones((2, n)) * 1.0
        s[len(s) - 1, :] = s0
        sw = np.ones((2, n)) * 0.1
        sw[len(sw) - 1, :] = s0
        # Kuramoto order parameter and phase
        R = np.ones(nsteps) * 0.1
        P = np.ones(nsteps) * 0.1
        # WC neural field
        rw = np.ones((nsteps, n)) * 0.1
        return {'rw' + name: rw, 'r' + name: r, 'v' + name: v, 's' + name + 'r': s, 'sw' + name + 'r': sw, 'R' + name: R, 'P' + name: P}

    @staticmethod
    def synaptic_activation(tsyn_steps, dt, funct='heaviside'):
        tau_syn = tsyn_steps * dt
        h_tau = 1.0 / tau_syn
        if funct == 'heaviside':
            a_tau0 = np.transpose(h_tau * np.ones(tsyn_steps))
        elif funct == 'exp_decay':
            a_tau0 = np.transpose(h_tau * np.array(np.exp(-dt * h_tau * np.arange(tsyn_steps))))
        else:
            return None
        a_tau = np.zeros((tsyn_steps, tsyn_steps))
        for i in xrange(tsyn_steps):
            a_tau[i] = np.roll(a_tau0, i, 0)
        return a_tau

    @staticmethod
    def external_currents(center, width, pop, n=1, distribution='lorentz'):
        # Implemented distributions:
        dist = {'lorentz': lorentz, 'gauss': gauss}
        if distribution == 'noise':
            logging.info("Setting an homogeneous population (identical neurons).")
            eta_i = np.ones(pop / n) * center
        else:
            logging.info("Setting an heterogeneous population.")
            logging.debug("Distribution of external currents: %s" % distribution)
            try:
                eta_i = dist[distribution](pop / n, center, width)
            except KeyError:
                logging.error("Distribution %s not implemented." % distribution)
                return -1
        # Set distributions for each node in case is a NF
        if n > 2:
            eta = np.zeros(pop)
            for i in xrange(n):
                eta[i * (pop / n):(i + 1) * (pop / n)] = 1.0 * eta_i
            del eta_i
            eta_i = eta * 1.0
        return eta_i

class FiringRate:
    """ Firing rate measurement of populations of spiking neurons."""

    def __init__(self, opts):
        self.logger = logging.getLogger('simu_lib.FiringRate')
        self.name = 'firingrate'

        # Parameters of the simulation:
        self.o = opts
        self.dt = self.o['dt']
        self.N = self.o['N']
        self.nsteps = int((opts['tfinal'] - opts['t0']) / self.dt)
        self.n = self.o['n']
        if self.n > 2:
            self.nf = True
            self.logger.debug("Firing rate measure for a neural field.")
        else:
            self.nf = False
            self.logger.debug("Firing rate measure for a single population.")

        try:
            self.Ne = self.o['Ne']
            self.Ni = self.o['Ni']
            if self.nf:
                self.dN = self.N / self.n
                self.dNe = self.Ne / self.n
                self.dNi = self.Ni / self.n
        except KeyError:
            self.logger.warning("Trying to create Firing Rate object without having spiking neurons!")
            raise KeyError

        # Fundamental parameters of FR measurement: sliding window, sampling rate,
        #                                           convolution function (heaviside, gauss).
        self.fo = opts['firingrate']
        self.sld_windw = None  # Sliding window in simulation time units (dt units)
        self.sld_steps = None  # Sliding window in simulation time steps
        self.wones = None  # Auxiliary vector of ones (for dot product)

        # Rate of sampling
        self.spl_time = None
        self.spl_steps = None

        # Time vector and firig rate vectors for FR measure
        self.t = None
        self.length = None
        self.tstep = 0
        self.tstep2 = 0
        self.re = None
        self.ri = None
        self.r = None

        self.spikes_e = None
        self.spikes_i = None

        self.update(self.fo)

    def update(self, opts, **kwargs):
        """ Function to prepare the firing rate observables """
        self.o.update(opts)
        self._init_prmts()
        self.t = self._t_vector()
        self.length = len(self.t)
        self.re = self._r_vector((self.length, self.o['n']))
        self.ri = self._r_vector((self.length, self.o['n']))
        self.r = self._r_vector((self.length, self.o['n']))
        try:
            a = kwargs['tstep'] * 1
            del a
            # TODO: be able to change the FR vector size in the shared memory or use the output queue (easier)
            self.logger.warning("Firing Rate measurement parameters can't "
                                "be changed (yet) without restarting the program.")
        except KeyError:
            pass

    def _r_vector(self, size):
        """ Returns a FR vector with a size that depends on the sampling rate."""
        if type(size) is not tuple:
            self.logger.error("Size must be tuple T x n")
            return -1
        tlen = size[0]
        n = size[1]
        if self.nf:  # For NF computing (n represents number of nodes, spatial dimension)
            r = 0.0 * np.zeros(shape=(tlen, n))
        else:
            r = 0.0 * np.zeros(tlen)
        return r

    def _t_vector(self):
        """ Returns a Time vector with a size that depends on the sampling rate."""
        # Measures of the firing rate are done every self.spl_steps
        vt_steps = np.arange(int(self.o['t0'] / self.dt) + self.sld_steps, self.nsteps, self.spl_steps)
        return (vt_steps - self.sld_steps / 2) * self.dt

    def _init_prmts(self):
        """ Function to set up the basic parameters for measurement."""
        # Fundamental parameters of FR measurement: sliding window, sampling rate,
        #                                           convolution function (heaviside, gauss).
        self.sld_windw = self.fo['sw']  # Sliding window in simulation time units (dt units)
        self.sld_steps = int(self.sld_windw / self.dt)  # Sliding window in simulation time steps
        self.wones = np.ones(int(self.sld_steps))  # Auxiliary vector of ones (for dot product)
        self.tstep = 0
        # Rate of sampling
        self.spl_time = self.fo['spr']
        self.spl_steps = int(self.spl_time / self.dt)

        # TODO: Compute how much memory is going to be use, and check availability

        # Create Spikes matrices. DOES NOT depend on the number of populations (n).
        #                         IT DOES depend on the type of populations (exc, inh, ...)
        # During the simulation the spikes are transferred to the following matrices
        self.spikes_e = 0 * np.zeros(shape=(self.Ne, self.sld_steps))
        self.spikes_i = 0 * np.zeros(shape=(self.Ni, self.sld_steps))

    def firing_rate(self, tstep, temps, var, aux=None):
        """ Computes the firing rate for a given matrix of spikes. Firing rate is computed
            every certain time (sampling). Therefore at some time steps the firing rate is not computed,
        :param tstep: time step of the simulation
        :param temps: time of the simulation
        :param var: variable dictionary (where re and ri and r are)
        :param aux: auxiliary matrix for neural field computation (defined in data)
        :return: nothing. modifies self.t, self.re, self.ri. self.r)
        """
        if tstep % self.spl_steps == 0 and (temps >= self.sld_windw):
            # We divide by cases ('nf', 'fr', 'exc', 'inh', ...)
            if self.nf and aux:
                if not aux:
                    self.logger.error("Auxiliary matrix is needed.")
                    return -1
                var['sp_re'][self.tstep] = (1.0 / self.sld_windw / self.dNe) * np.dot(aux['e'],
                                                                                np.dot(self.spikes_e, self.wones))
                var['sp_ri'][self.tstep] = (1.0 / self.sld_windw / self.dNi) * np.dot(aux['i'],
                                                                                np.dot(self.spikes_i, self.wones))
            else:
                var['sp_re'][self.tstep] = (1.0 / self.dt) * self.spikes_e.mean()
                var['sp_ri'][self.tstep] = (1.0 / self.dt) * self.spikes_i.mean()

            var['sp_r'][self.tstep] = (var['sp_re'][self.tstep] + var['sp_ri'][self.tstep]) / 2.0
            self.tstep += 1
            self.tstep2 += 1
            self.tstep = self.tstep % self.length
            var['frtstep'].value = self.tstep
            var['frtstep2'].value = self.tstep2

class Connectivity:
    def __init__(self, opts, kind='effective'):
        self.log = logging.getLogger('simu_lib.Connectivity')
        self.name = 'network'
        # Connectivity set up
        self.log.debug("Setting up connectivity, type: %s" % kind)
        # Mutable parameters of the connectivity must be merged with parameters['parameters']
        o = opts['network']  # Network parameters
        self.n = opts['n']  # Number of nodes in the network
        # Basic connectivity elements
        [i, j] = np.meshgrid(xrange(self.n), xrange(self.n))
        self.ij = (i - j) * (2.0 * pi / self.n)  # Grid
        del i, j
        self.eigenmodes = None
        self.eigenvectors = None
        self.kind = kind

        profile = o['c']
        self.log.debug("Connectivity profile: %s" % profile)
        # Types of connectivity: FS (Fourier Series), Mex-Hat, Random Laplace
        self.cntswitch = {'fs': self.cosine_series, 'cs': self.cosine_series, 'mex-hat': None, 'twopops': self.twopop}
        self.c, prmts = self.cntswitch[profile](o, kind)
        opts['parameters'].update(prmts)

    def update(self, opts, **kwargs):
        try:
            a = kwargs['tstep'] * 1
            del a
        except KeyError:
            pass

        self.c, dummy = self.cntswitch[opts['c']](opts, self.kind)

    def cosine_series(self, opts, kind='effective'):
        sign = {'exc': 1.0, 'inh': -1.0}
        # Default modes of effective (jk) connectivity are 0, 10, 7.5, -2.5
        if 'jk' in opts:
            modes = np.array(opts['jk'])
            self.log.debug("Loading custom modes of effective connectivity: %s" % modes)
        else:
            modes = 10.0 * np.array([0, 1, 0.75, -0.25])
            self.log.debug("Loading default modes of effective connectivity: %s" % modes)

        eimodes = {}
        if kind in ('exc', 'inh'):
            key = 'jk' + kind[0]
            if key in opts:
                modes = sign[kind] * np.array(opts[key])
                self.log.debug("Using custom modes of connectivity: %s" % modes)
                cnt = self.jcntvty(modes, coords=self.ij)
            else:
                self.log.debug("Using the effective connectivity to compute the %s cntvty. profile." % kind)

                # Separating connectivity modes to build a excitatory modulated connectivity and a flat inhibitory
                # connectivity. Create a dummy connectivity to see the minimum value:
                minvalue = np.min(self.jcntvty(modes, coords=self.ij)[0])
                self.log.debug('Minumum value of the cntvty.: %f. Projected mode 0 value: %f' %
                               (minvalue, -1.0 * np.floor(minvalue)))
                minvalue = np.floor(minvalue)
                eimodes = {'exc': list(modes), 'inh': [0.0, 0.0]}
                if minvalue < 0:
                    newmode0 = -1.0 * minvalue
                else:
                    newmode0 = modes[0]
                mode0 = modes[0]
                if mode0 < newmode0:
                    eimodes['exc'][0] = newmode0
                    eimodes['inh'][0] = minvalue
                    self.log.debug('Mode 0 set to %f' % eimodes['exc'][0])
                self.log.debug("%s modes: %s" % (kind, str(eimodes[kind])))
                cnt = self.jcntvty(eimodes[kind], coords=self.ij)
                modes = eimodes[kind]
            eimodes[key] = modes
        else:
            cnt = self.jcntvty(modes, coords=self.ij)
        self.eigenmodes = modes
        return cnt, eimodes

    def twopop(self, opts, kind='sym'):
        # Four parameters jee, jei, jii, jie
        ds = {'jee', 'jii', 'js'}.intersection(opts)
        dc = {'jei', 'jie', 'jc'}.intersection(opts)
        if len(ds) > 2 or len(dc) > 2:
            self.log.warning("Redundant information about connectivity, use either jc/js, or j{e}{i}. Expect the "
                             "unexpected.")
            # In case is symmetrical, jee=jii=js, jei=jie=jc
        j = {}
        if kind == 'sym':
            if ds and dc:
                for key in ds:
                    j['js'] = opts[key]
                for key in dc:
                    j['jc'] = opts[key]
            else:
                j['js'] = opts['jke'][0]
                j['jc'] = opts['jke'][1]
        elif ds and dc:
            for key in ds.union(dc):
                j[key] = opts[key]
        else:
            j['jee'], j['jei'], j['jii'], j['jie'] = (opts['jke'][0], opts['jke'][1], opts['jki'][0], opts['jki'][1])
        return j, j

    @staticmethod
    def gauss0_pdf(x, std):
        return stats.norm.pdf(x, 0, std)

    @staticmethod
    def mexhat0(a1, std1, a2, std2, length=500):
        x = np.linspace(-np.pi, np.pi, length)
        return x, a1 * Connectivity.gauss0_pdf(x, std1) + a2 * Connectivity.gauss0_pdf(x, std2)

    @staticmethod
    def vonmises(je, me, ji, mi, length=None, coords=None):
        if coords is None:
            if length is None:
                length = 500
            theta = (2.0 * np.pi / length) * np.arange(length)
        else:
            theta = 1.0 * coords
        return je / special.i0(me) * np.exp(me * np.cos(theta)) - ji / special.i0(mi) * np.exp(mi * np.cos(theta))

    @staticmethod
    def jcntvty(jk, coords=None):
        """ Fourier series generator.
        :param jk: array of eigenvalues. Odd ordered modes of Fourier series (only cos part)
        :param coords: matrix of coordinates
        :return: connectivity matrix J(|phi_i - phi_j|)
        """
        jphi = 0
        for i in xrange(len(jk)):
            if i == 0:
                jphi = jk[0]
            else:
                # Factor 2.0 is to be coherent with the computation of the mean-field S, where
                # we devide all connectivity profile by (2\pi) (which is the spatial normalization factor)
                jphi += 2.0 * jk[i] * np.cos(i * coords)
        return jphi

    @staticmethod
    def jmodes0(a1, std1, a2, std2, n=20):
        return 1.0 / (2.0 * np.pi) * (
            a1 * np.exp(-0.5 * (np.arange(n)) ** 2 * std1 ** 2) + a2 * np.exp(-0.5 * (np.arange(n)) ** 2 * std2 ** 2))

    @staticmethod
    def jmodesdct(jcnt):
        """ Extract fourier first 20 odd modes from jcnt function.
        :param jcnt: periodic odd function.
        :return: array of nmodes amplitudes corresponding to the FOurie modes
        """
        l = np.size(jcnt)
        jk = dct(jcnt, type=2, norm='ortho')
        for i in xrange(len(jk)):
            if i == 0:
                jk[i] *= np.sqrt(1.0 / (4.0 * l))
            else:
                jk[i] *= np.sqrt(1.0 / (2.0 * l))
        return jk

    @staticmethod
    def vonmises_modes(je, me, ji, mi, n=20):
        """ Computes Fourier modes of a given connectivity profile, built using
            Von Mises circular gaussian functions (see Marti, Rinzel, 2013)
        """
        modes = np.arange(n)
        return je * special.iv(modes, me) / special.i0(me) - ji * special.iv(modes, mi) / special.i0(mi)

class Perturbation:
    """ Tool to handle perturbations: time, duration, shape (attack, decay, sustain, release (ADSR), etc. """

    def __init__(self, opts, **kwargs):
        self.logger = logging.getLogger('tools.Perturbation')
        self.name = 'perturbation'

        # The class will create a external current "it" vector containing any external perturbation
        # Depending on the selected system, mainly "fr" or "nf" the possible properties of the perturbation vary
        # but many properties are common: such as amplitude, duration of the pulse (if pulse), rising and decay, etc.

        # Common fixed parameters:
        self.n = opts['n']
        self.nf = opts['nf']
        self.dt = opts['dt']
        self.nsteps = int(opts['tfinal'] / self.dt)
        # Spatial modulation (wavelengths)
        self.random = False
        self.pstart = None
        self.pend = None
        self.phi = np.linspace(-pi, pi, self.n)
        self.it = None
        self.active = True
        self.systems = opts['systems']
        opts.update(kwargs)
        self.update(opts[self.name], 0)

    def check(self, tstep):
        # self.logger.debug("Perturbation end: %d, current tstep: %d" % (self.pend, tstep))
        if tstep >= self.pend:
            self.logger.debug("Deactivating perturbation.")
            self.it = self.it * 0.0
            self.active = False

    def update(self, opts, tstep, tag=None):
        self.pstart = tstep * 1
        it = None
        try:
            it = self.it * 1.0
        except TypeError:
            pass
        if tstep != 0:
            opts['p0'] = self.pstart * self.dt + self.dt
        else:
            self.pstart = opts['p0'] / self.dt
        self.it = self._time_modulation(**opts)
        self.active = True
        if self.nf:
            self.it = opts['amp'] * np.dot(np.array((self.it,)).T, (self._spatial_modulation(**opts),))
        else:
            self.it = opts['amp'] * self.it
        try:
            self.it += it
        except TypeError:
            pass

        # opts.update({'it': self.it})
        if tag:
            opts[tag].update({'it': opts['it'], 'sym': opts['sym'], 'update': 'idle'})
        return 0

    def _spatial_modulation(self, modes=(1,), sprofile='fourier', cntmodes=None, phi=False, **kwargs):
        """ Gives the spatial profile of the perturbation: different wavelength and combinations
            of them can be produced.
        """
        self.logger.debug("Spatial profile of the perturbation: '%s'" % sprofile)
        self.logger.debug("Additional arguments are %s" % kwargs)
        # Check format of modes
        try:
            modes = list(modes)
        except TypeError:
            modes = [modes]
        # Typical modulations are gaussian or cosine (fourier) series
        if sprofile == 'gauss':
            return Connectivity.vonmises(1.0, 8.0, 0.0, 1.0, self.n, np.linspace(-pi, pi, self.n))
        elif sprofile == 'fourier':
            sp = 0.0
            for m in modes:
                if cntmodes is None:  # Use the connectivity eigenmodes to create the perturbation modes
                    if phi:  # Set a phase different of zero: the perturbation is centered at phi
                        if phi == 0.0:
                            phi = np.random.randn(1) * np.pi
                    else:
                        phi = 0.0
                    self.logger.debug("Perturbation of mode %d with phase %f" % (m, phi))
                    sp += np.cos(m * self.phi + phi)
                else:
                    sp += cntmodes[m] * 1.0
            return sp
        else:
            self.logger.error("Spatial profile '%s' not implemented." % sprofile)
            return 0.0

    def _time_modulation(self, p0, pd=0.5, rise=0.2, decay=0.0, tprofile='pulse', **kwargs):

        """ Function that produces a specific time modulated function to be applied to the perturbation vector.

        :param p0: initial time of the perturbation, given in dt units (not in tsteps). Redundant times should be hand
                      led previously.
        :param pd: duration of the pulse in dt units (not time steps)
        :param rise: time constant of the rising function
        :param decay: time constant of the decay function
        :param tprofile: type of perturbation: "pulse", "oscil", "chirp"
        """
        self.logger.debug("Additional arguments are %s" % kwargs)
        # Set up relevant times in time-step units
        # We create the vector from 0 to dts and then we roll the vector to much t0s
        t0s = int(p0 / self.dt) % self.nsteps
        rt, dt, ft = (np.array([0.0]), np.array([0.0]), np.array([0.0]))
        it = np.zeros(self.nsteps)
        if pd > 0:  # Finite perturbation
            dts = int(pd / self.dt)

            if dts > self.nsteps:  # Various cycles of the simulation # TODO
                self.cycles = (t0s + dts) // self.nsteps
                pd = self.nsteps * self.dt * self.cycles
            t = np.arange(0.0, pd, self.dt)

            if tprofile == 'pulse':
                self.pend = self.pstart + dts - 1
                self.logger.debug("Perturbation starting at %d (%f) and finishing at %d (%f)" % (
                self.pstart, self.pstart * self.dt, self.pend, self.pend * self.dt))
                if rise > 0.01:
                    rt = np.exp(t / rise) - 1.0
                    mask = (rt >= 1.0)
                    rt[mask] = rt[mask] * 0.0 + 1.0
                else:
                    rt = np.ones(len(t)) * 1.0
                if decay > 0:
                    dt = np.exp(-t / decay) - 1.0
                ft = np.concatenate((rt, dt))
            elif tprofile == 'oscil':
                pass
            elif tprofile == 'chirp':
                pass
            else:
                self.logger.error("Temporal profile '%s' not implemented." % tprofile)
                return 0.0
            it[0:len(ft)] = ft * 1.0
        # Roll the vector to start on t0
        it = np.roll(it, t0s)
        return it

    def _destroy(self):
        # Delete the perturbation
        pass

class SaveResults:
    """ Class to save and load results. Save pickled data, save csv data, select saving file. Load initial
        conditions (for spiking neuron simulaitons). """

    def __init__(self, opts, variables):
        self.logger = logging.getLogger('tools.SaveResults')
        self.o = opts
        self.v = variables
        self.nsteps = len(np.arange(opts['t0'], opts['tfinal'], opts['dt']))
        self.choice = None
        self.xtvars = {'t': None, 'tfr': None, 'phi': None}
        self.ic = False
        for xtvar in self.xtvars.copy():
            var = variables.get(xtvar, None)
            if var is not None:
                l = len(var)
                self.xtvars[xtvar] = l
            else:
                self.xtvars.pop(xtvar)
        self.logger.debug("XTvars: %s" % self.xtvars)

    def __call__(self, *args, **kwargs):
        self.choice = kwargs.get('choice', 'all')
        self.path = kwargs.get('path', '%s/saved_result-%s' % (self.o['dir'], self.choice))
        if kwargs.get('save_ic', False):
            self.save_ic(kwargs['data'])
            return 0
        elif kwargs.get('load_ic', False):
            self.logger.info("Loading initial conditions.")
            return self.load_ic()

        elif self.choice:
            self.logger.info("Saving %s data to %s." % (self.choice, self.path))
            self.save_vars()
            return 0

        return -1

    def save_vars(self):
        # Two options: save all variables, or save selected variable
        dvars = []
        if self.choice == 'all':
            for var in self.v:
                if var not in self.xtvars:
                    dvars.append(self.identify_domain(var))
                    if dvars[-1]:
                        dvars[-1].append(var)
                    else:
                        dvars.pop(-1)
        # Save selected variable.
        else:
            dvars.append(self.identify_domain(self.choice))
            dvars[-1].append(self.choice)

        # Save
        self.save(dvars, self.path)

    def save_ic(self, data):
        # Identify system.
        if self.o['nf']:
            self.o['ic_dir'] = self.o['dir'] + '/ic/nf'
        else:
            self.o['ic_dir'] = self.o['dir'] + '/ic/fr'
        d = {'ic_file': True, 'var': self.choice}
        d.update(data)
        # Variable is a spiking neuron network variable
        if self.choice[0:2] == 'sp':
            system = 'sn' + '_' + self.o['sp']
            # "data" is a dictionary
        else:
            system = 'fr'
            vdata = self.v[self.choice][::]
            d.update({self.choice: vdata})
        # Create dir tree.
        self.o['ic_dir'] = self.o['ic_dir'] + '/' + system
        create_dir(self.o['ic_dir'])
        # Save data necessary to be able to load the same system.
        path = self.o['ic_dir'] + '/' + '-'.join(now('_', '.'))
        np.save(path, d)
        self.logger.info("Initial conditions saved to %s." % path)

    def load_ic(self):
        # Load file
        try:
            self.logger.info("Loading %s..." % self.path)
            data = dict(np.load(self.path).item())
            # Check format
            if not data.get('ic_file', False):
                self.logger.error("This is not an initial condition container.")
                return None
        except (KeyError, IOError):
            self.logger.error("This is not an initial condition container.")
            return None
        # Load options and create new data object
        self.o = data['opts']
        self.logger.debug("Creating new 'data' instance...")
        new_data = Data(self.o, external=(None,))
        # Identify the variable which has to be loaded
        var = data['var']
        self.logger.debug("Loading '%s'..." % var)
        # Variable is a Spiking neuron variable
        if var[0:2] == 'sp':
            # Load voltages and spikes matrices
            new_data.m_e = data['m_e']
            new_data.m_i = data['m_i']
            new_data.spk_e = data['spk_e']
            new_data.spk_i = data['spk_i']
            try:
                new_data.spk_e_mod = data['spk_e_mod']
                new_data.spk_i_mod = data['spk_i_mod']
            except:
                pass
        else:
            new_data.vars[var][::] = data[var][::]
        return new_data

    def save(self, data, path):
        """ Save the data. Different possibilities: pickled, plot, ... """
        # TODO: different saving formats
        # Dictionary: to save a pickled object
        dictio = self.create_dict(data)
        dictio['opts'] = self.o
        dictio['last_step'] = self.v['tstep'].value * 1.0
        np.save(path, dictio)

    def identify_domain(self, var):
        """ Function that identifies the domain of the variable named var"""
        dim = np.shape(self.v[var])
        dvars = []
        # Identify the variable(s)
        for d in dim:
            for xtvar in self.xtvars:
                if d == self.xtvars[xtvar]:
                    dvars.append(xtvar)
        return dvars

    def create_dict(self, variables):
        """ Creates a dictionary with the variables to be saved in a pickled object"""
        dictio = {}
        for var in variables:
            dictio[var[-1]] = {}
            for v in var:
                if v[0:2] == 'sp':
                    tstep = self.v['frtstep'].value
                else:
                    tstep = self.v['tstep'].value % self.nsteps
                if v not in ('t', 'tfr', 'phi'):
                    if var[0] in ('t', 'tfr') and tstep != 0:
                        ydata = np.concatenate((self.v[v][tstep:], self.v[v][:tstep]))
                    else:
                        ydata = self.v[v][::]
                else:
                    ydata = self.v[v][::]

                dictio[var[-1]][v] = ydata
        return dictio


import ConfigParser
import os
import random
import base64
try:
	from Crypto.Cipher import AES
	SECURED=True
except:
	SECURED=False

class Config:
	def __init__(self,key=None):
		""" Initializes the class. The optional key parameter
		is used to encrypt some important data """
		self.config=ConfigParser.SafeConfigParser()
		self.key=key
	def get(self,key,default=None):
		""" Gets a value from the configuration file.
		key Is the key to the property with the colon-separated section.
		default is the default value of the property if it is not in the configuration.

		>>> config=Config()
		>>> config.set('Main:Fruit','apple')
		>>> config.get('Main:Fruit')
		'apple'

		"""
		try:
			(section,property)=key.split(':')
		except:
			(section,property)=(ConfigParser.DEFAULTSECT,key)
		try:
			return self.config.get(section,property)
		except:
			return default
	def getbool(self,key,default=False):
		""" A convenience method to get a boolean

		>>> config=Config()
		>>> config.set('Main:Save',False)
		>>> config.getbool('Main:Save')
		False

		"""
		if not default:
			default = 'false'
		else:
			default = 'true'
		return not (self.get(key,default).lower()=='false')
	def getint(self,key,default=0):
		""" A convenience method to get an integer

		>>> config=Config()
		>>> config.set('Main:Apples',3)
		>>> config.getint('Main:Apples')
		3

		"""
		try:
			return int(self.get(key,str(default)))
		except:
			return default
	def set(self,key,value):
		""" Sets a property.
		key Is the key to the property with the colon-separated section. If there is no colon,
			the property will be saved in the default section
		value is the value of the property. Currently, it can be a string, an integer or a boolean.
		This method returns a reference to the Config object, in order to
		easily chain configurations.

		>>> config=Config()
		>>> config.set('Main:Name','Jesse James')
		>>> config.get('Main:Name')
		'Jesse James'

		"""

		# Get the section and property, or use the DEFAULT section
		try:
			(section,property)=key.split(':')
		except:
			(section,property)=(ConfigParser.DEFAULTSECT,key)
		# convert integers and booleans into strings
		if not value: value=''
		if type(value)==int: value='%d'%value
		if type(value)==bool:
			value='true'
			if not value: value='false'
		# create the section, if not pressent
		if not self.config.has_section(section):
			self.config.add_section(section)
		# save the value
		self.config.set(section,property,value)
		# return the same object (to chain .set() statements)
		return self
	def load(self,source):
		""" Loads the configuration from a source.
		If source is a file object, loads the configuration from the file.
		Else, it is managed as a string and reads configuration from string """
		if type(source)==str:
			import StringIO
			self.config.readfp(StringIO.StringIO(source))
		else:
			self.config.readfp(source)
	def save(self,fileobject=None):
		""" Saves the content in a destination.
		If a fileobject is provided, configuration is saved in the file and returns None.
		Otherwise, the configuration is returned as a string """
		if fileobject:
			self.config.write(fileobject)
			return None
		else:
			import StringIO
			s=StringIO.StringIO()
			self.config.write(s)
			return s.getvalue()
	def get_key(self,id,enc_key=None):
		""" A convenience method to get a key from the config file.
		If enc_key is provided, the key are decrypted with AES.
		If enc_key is None, use the key provided during the
		initialization of this class. """
		if not enc_key: enc_key=self.key
		kf = None
		if enc_key:
			kf=self.get('Keys:'+id,None)
			if kf: kf=AES.new(enc_key).decrypt(base64.b32decode(kf))
		else:
			kf=self.get('Keys:'+id,None)
			if kf: kf=base64.b32decode(kf)
		return kf
	def set_key(self,id,key,enc_key=None):
		""" A convenience method to set a key.
		If enc_key is provided, the key are crypted with AES. Else,
		the key are just Base32 encoded. If enc_key is None, use
		the key provided during the initialization of this class """
		if not key: return
		if not enc_key: enc_key=self.key
		if enc_key:
			self.set('Keys:'+id,base64.b32encode(AES.new(key).encrypt(key)))
		else:
			self.set('Keys:'+id,base64.b32encode(key))

def format_error():
	""" Returns a formated string with information about the last error """
	import traceback, sys
	ei=sys.exc_info()
	fn,ln,fun,t=traceback.extract_tb(ei[2],1)[0]
	traceback.print_exc()
	return '%s (file=%s line=%s text="%s")'%(ei[1],fn,ln,t)

random_string_seed='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'
def random_string(length,printable=True):
	"Returns a random string with a given length, optionally printable"
	s=[]
	for i in range(0,length):
		if printable:
			s.append(random.choice(random_string_seed))
		else:
			s.append(chr(random.randint(0,255)))
	return ''.join(s)

def password_to_key(pwd):
	""" Returns a 16B key (suitable for AES) based on a password """
	if SECURED:
		from Crypto.Hash import SHA
		return SHA.new(pwd).digest()[0:16]
	else:
		import sha
		return sha.sha(pwd).digest()[0:16]

def random_nick():
	" Returns a random nick "
	return random_string(6,printable=True)

def configure_logging(**kwargs):
	""" A convenience method to call to basicConfig a couple of times.
	This method has the same argument as logging.basicConfig plus:
		preserve If True, preserve previosly configured handlers (default False)
	"""
	import logging
	logging.raiseExceptions=0
	if not kwargs.get('preserve', False):
		for h in logging.root.handlers: logging.root.handlers.remove(h)
	logging.basicConfig(**kwargs)


from django import forms
from models import *

class Messageform(forms.ModelForm):
    class Meta:
        model=Messages_Orgs
        exclude = ('sender_org_id',)
class Notificationform(forms.ModelForm):
    class Meta:
        model=Notifications_Org
        fields=('target_org_id','is_message_from_org','message_from_org_id','is_message_from_admin','message_from_admin_id','is_request_from_admin','disaster_id')


import pytest
from collections import Counter

from src.encoding import Encryptor

@pytest.mark.parametrize("word,cant_be_shuffled", [
    ('b', True),
    ('big', True),
    ('biiig', True),
    ('home', False),
    ('aloha', False)
])
def test_shuffle_word(word, cant_be_shuffled):
    b = Encryptor('')
    if cant_be_shuffled:
        assert b._shuffle_word(word) == word
    else:
        assert b._shuffle_word(word) != word

@pytest.mark.parametrize("text,encrypted_words", [
    ('This is really big', ['really', 'This']),
    ('This, is !!! really ## big **,', ['really', 'This']),
    (
        'This is a long looong test sentence,\nwith some big (biiiiig) words!',
        ['long', 'looong', 'sentence', 'some', 'test', 'This', 'with', 'words']
    ),
    (
        'Lets say we have a sentence like that one',
        ['have', 'Lets', 'like', 'sentence', 'that']
    )
])
def test_encode(text, encrypted_words):
    b = Encryptor(text)
    b.encode()
    result = b.result_text.split('\n-weird-\n')[1]
    assert b.encrypted_words == encrypted_words
    for word in encrypted_words:
        assert word not in result


"""Update content metadata for websites based on a specific starter"""  # noqa: INP001
from django.db import transaction

from main.management.commands.filter import WebsiteFilterCommand
from websites.models import WebsiteContent, WebsiteStarter
from websites.site_config_api import SiteConfig
from websites.utils import get_dict_field, set_dict_field

class Command(WebsiteFilterCommand):
    """Update content metadata to the default value spcecified in starter. Only
    affects content whose value for that metadata entry are null or empty.
    """

    help = __doc__  # noqa: A003

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            "--starter", help="The WebsiteStarter slug to process", required=True
        )
        parser.add_argument(
            "--field",
            help="The metadata field's name path to update, in dot notation. Example: image_metadata.caption",  # noqa: E501
            required=True,
        )
        parser.add_argument(
            "-t",
            "--type",
            dest="type",
            help="Only update metadata for content with this type.",
            required=True,
        )

    def handle(self, *args, **options):
        super().handle(*args, **options)
        starter_str = options["starter"]
        field_path = options["field"]
        type_str = options["type"]

        content_qset = WebsiteContent.objects.filter(
            website__starter__slug=starter_str, type=type_str
        )
        content_qset = self.filter_website_contents(content_qset)

        base_metadata = SiteConfig(
            WebsiteStarter.objects.get(slug=starter_str).config
        ).generate_item_metadata(type_str, cls=WebsiteContent, use_defaults=True)
        default_value = get_dict_field(base_metadata, field_path)
        if default_value is None:
            msg = f"Metadata field {field_path} has no default"
            raise Exception(msg)  # noqa: TRY002

        def should_update(website_content):
            current_value = get_dict_field(website_content.metadata, field_path)
            return current_value is None or current_value == ""

        expected_updated = sum(1 for wc in content_qset.iterator() if should_update(wc))

        confirmation = input(
            f"""You are about to change {expected_updated} records metadata value:
    field:     {field_path}
    new value: {default_value}
    old value: '' or null

Would you like to proceed? (y/n):"""
        )
        if confirmation != "y":
            self.stdout.write("Aborting...")
            return

        updated = 0
        with transaction.atomic():
            for content in content_qset.iterator():
                if should_update(content):
                    set_dict_field(content.metadata, field_path, default_value)
                    content.save()
                    updated += 1

        self.stdout.write(f"Finished updating {updated} records.")


from selenium.webdriver.common.by import By
from scrapers.abstract import AbstractScraper
from scrapers.websites import BASEBALL_PRESS_URL
from models.mlb.lineup import MlbLineup

class MlbLineupScraper(AbstractScraper):
    def __init__(self):
        super().__init__(BASEBALL_PRESS_URL)

    def get_resource(self, args):
        date = args["date"]
        endpoint = f"lineups/{date}"
        self.get(endpoint)
        lineups = self.driver.find_elements(By.CLASS_NAME, "lineup-card")
        data = [MlbLineup(lineup).toJson() for lineup in lineups]
        return {"lineups": data}


import datetime
import dropbox
import logging

#---------------------------------------------------------------------------
# Modify
#---------------------------------------------------------------------------
MAX_CONTENT_AGE = 100
DISPLAY_ONLY_MODE = True
TOKEN = "YOUR-DROPBOX-BUSINESS-TEAM-MEMBER-FILE-ACCESS-TOKEN"
LOG_NAME = "dropboxpurge.log"
#---------------------------------------------------------------------------

dbx = dropbox.DropboxTeam(TOKEN)
MAX_AGE = datetime.timedelta(days=MAX_CONTENT_AGE)
FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(filename=LOG_NAME, format=FORMAT, level=logging.INFO)

class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

def log_init():
    logging.info("Begin pass...")
    logging.info("MAX_CONTENT_AGE: {}".format(MAX_CONTENT_AGE))
    logging.info("DISPLAY_ONLY_MODE: {}".format(str(DISPLAY_ONLY_MODE)))

def retrieve_member_list(recursive=True):
    logging.info("retrieve_member_list (recursive={}".format(str(recursive)))

    all_members = []
    members = dbx.team_members_list()
    all_members.extend(members.members)
    if recursive:
        while members.has_more:
            print("(retrieving another 1000 users)")
            logging.info("(retrieving another 1000 users)")
            members = dbx.team_members_list_continue(members.cursor)
            all_members.extend(members.members)

    logging.info("member list obtained - {} members".format(len(all_members)))
    return all_members

def dropbox_content(member_id):
    logging.info("retrieving content for {}".format(member_id))
    content_listing = []
    result = dbx.as_user(member_id).files_list_folder(path="",
                                                        recursive=True,
                                                        include_media_info=False,
                                                        include_deleted=False,
                                                        include_has_explicit_shared_members=False)
    content_listing.extend(result.entries)
    while result.has_more:
        result = dbx.as_user(member_id).files_list_folder_continue(result.cursor)
        content_listing.extend(result.entries)

    logging.info("retrieved content for {}".format(member_id))
    return content_listing

def display_content(content):
    for item in content:
        if type(item) == dropbox.files.FileMetadata:
            last_touch_date = item.client_modified if item.client_modified > item.server_modified else item.server_modified
            if last_touch_date.date() > (datetime.date.today() - MAX_AGE):
                print("\t{0} ({1})".format(item.path_lower, last_touch_date))
                logging.info("{} would not have been deleted".format(item.path_lower))
            else:
                print(bcolors.FAIL + "\t{0} ({1})".format(item.path_lower, last_touch_date) + bcolors.ENDC)
                logging.info("{} would have been deleted".format(item.path_lower))

def delete_content(content, member_id):
    for item in content:
        if type(item) == dropbox.files.FileMetadata:
            last_touch_date = item.client_modified if item.client_modified > item.server_modified else item.server_modified
            if last_touch_date.date() > (datetime.date.today() - MAX_AGE):
                print("Ignoring:\t{}".format(item.path_lower))
                logging.info("Ignored: {}".format(item.path_lower))
            else:
                print(bcolors.FAIL + "Deleting:\t{}".format(item.path_lower) + bcolors.ENDC)
                logging.info("Attempting to delete: {}".format(item.path_lower))
                delete(member_id, item.path_lower)

def delete(member_id, path):
    try:
        result = dbx.as_user(member_id).files_delete(path)
        if type(result) == dropbox.files.DeleteError:
            print("Problem deleting {}".format(path))
            logging.info("Tried to delete: {} but couldn't".format(path))
        else:
            print("deleted {}".format(path))
            logging.info("Deleted: {}".format(path))
    except dropbox.exceptions.ApiError:
        print("Problem deleting {}".format(path))
        logging.info("Tried to delete: {} but couldn't".format(path))

if __name__ == "__main__":
    log_init()
    for team_member in retrieve_member_list():
        if team_member.profile.status == dropbox.team.TeamMemberStatus.active:
            print(bcolors.BOLD + 'Team Member:\t{}'.format(team_member.profile.email) + bcolors.ENDC)
            content = dropbox_content(team_member.profile.team_member_id)
            if DISPLAY_ONLY_MODE:
                display_content(content)
            elif not DISPLAY_ONLY_MODE:
                delete_content(content, team_member.profile.team_member_id)


import os
import sys

from services.proto import delete_pb2 as dpb
from utils.articles import delete_article, get_article, get_sharers_of_article

HOSTNAME_ENV = 'HOST_NAME'

class ReceiveDeleteServicer:
    def __init__(self, logger, db, activ_util, users_util, hostname=None):
        self._logger = logger
        self._db = db
        self._activ_util = activ_util
        self._users_util = users_util
        self._hostname = hostname if hostname else os.environ.get(HOSTNAME_ENV)
        if not self._hostname:
            self._logger.error("Hostname for SendDeleteServicer not set")
            sys.exit(1)

    def ReceiveDeleteActivity(self, req, ctx):
        self._logger.info("Received delete for article '%s'", req.ap_id)
        article = get_article(self._logger, self._db, ap_id=req.ap_id)
        if article is None:
            # Don't have the article, our work here is done.
            # This can happen for natural reasons, dublicate deletes,
            # deletes of articles that were created before a follower
            # followed the creator, etc.
            self._logger.info("Don't have article %s, exiting", req.ap_id)
            return dpb.DeleteResponse(result_type=dpb.DeleteResponse.OK)
        author = self._users_util.get_user_from_db(global_id=article.author_id)
        if author is None:
            return dpb.DeleteResponse(
                result_type=dpb.DeleteResponse.ERROR,
                error="Could not retrieve author",
            )
        # Grab the people who shared the article before we delete everything.
        sharer_ids = get_sharers_of_article(
            self._logger, self._db, article.global_id)
        # Delete the local copy.
        if not delete_article(self._logger, self._db, ap_id=req.ap_id):
            return dpb.DeleteResponse(
                result_type=dpb.DeleteResponse.ERROR,
                error="Could not delete article",
            )
        # Forward the delete to the announcers.
        delete_obj = self._activ_util.build_delete(
            author, article, self._hostname)
        for user_id in sharer_ids:
            err = self._activ_util.forward_activity_to_followers(
                user_id, delete_obj)
            if err is not None:
                # Warn but do not quit on error sending to announcer followers.
                self._logger.warning(
                    "Sending activity to %d followers failed", user_id)
        return dpb.DeleteResponse(result_type=dpb.DeleteResponse.OK)


import enum
import typing

from dataclasses import dataclass, field

# Number of feet in a meter
M_2_FT = 3.28084

KM_2_MI = 0.621371

SPEED_OF_LIGHT = 299792458

EARTH_RADIUS_KM = 6371
EARTH_RADIUS_M = EARTH_RADIUS_KM * 1000

FREQUENCY_CHOICES = [
    (2.437, "2.4 GHz"),
    (3.6, "3.65 GHz"),
    (5.4925, "5 GHz"),
    (11.2, "11 GHz"),
    (18.7, "18 GHz"),
    (24.35, "24 GHz"),
    (64.79, "60 GHz"),
]

class FeatureType(enum.Enum):
    AP = "access_point"
    CPE = "cpe"
    PTP_LINK = "ptp_link"
    AP_CPE_LINK = "ap_cpe_link"
    COVERAGE_AREA = "coverage_area"
    AP_SECTOR = "sector"

@dataclass
class _Limits(object):
    min: float
    max: float
    default: float = 0

    # There might be other defaults defined. Use this to make it easier to look
    # them up
    other_defaults: typing.Dict[str, float] = field(default_factory=dict)

    # lookup <attr>_default
    def __getattr__(self, attr):
        if attr in self.other_defaults:
            return self.other_defaults[attr]
        else:
            raise AttributeError(
                f"type object {repr(self.__class__.__name__)} has no attribute {repr(attr)}"
            )

    # Return a version of this object with scaled factors
    def get_scaled_limits(self, scale_factor):
        return _Limits(
            scale_factor * self.min,
            scale_factor * self.max,
            scale_factor * self.default,
        )

class ModelLimits(object):
    # Height in meters
    HEIGHT = _Limits(0.1, 1000, 30, {"cpe_default": 1, "ptp_default": 18.29})

    # Height in ft
    HEIGHT_FT = HEIGHT.get_scaled_limits(M_2_FT)

    # Radius in km
    RADIUS = _Limits(0.1, 16, 2, {"no_check_radius_default": 0.01})

    # Radius in mi
    RADIUS_MILES = RADIUS.get_scaled_limits(KM_2_MI)

    # Frequency in GHz
    FREQUENCY = _Limits(0, 100, 2.437)

    # Heading in degrees
    HEADING = _Limits(0, 360, 0)

    # Azimuth in degrees
    AZIMUTH = _Limits(0.01, 360, 120)

    # Name length
    NAME = _Limits(1, 50)


import threading
import wx
from wx.lib.utils import AdjustRectToScreen
import plover.gui.util as util
from plover.dictionary_editor_store import DictionaryEditorStore
from plover.dictionary_editor_store import COLUMNS
from plover.dictionary_editor_store import COL_STROKE
from plover.dictionary_editor_store import COL_TRANSLATION
from plover.dictionary_editor_store import COL_DICTIONARY
from plover.dictionary_editor_store import COL_SPACER
from wx.grid import EVT_GRID_LABEL_LEFT_CLICK
from wx.grid import PyGridTableBase

TITLE = 'Plover: Dictionary Editor'

FILTER_BY_STROKE_TEXT = 'Filter by stroke:'
FILTER_BY_TRANSLATION_TEXT = 'Filter by translation:'
DO_FILTER_BUTTON_NAME = 'Filter'
INSERT_BUTTON_NAME = 'New Entry'
DELETE_BUTTON_NAME = 'Delete Selected'
SAVE_BUTTON_NAME = 'Save and Close'
CANCEL_BUTTON_NAME = 'Close'

NUM_COLS = len(COLUMNS)

class DictionaryEditor(wx.Dialog):

    BORDER = 3

    def __init__(self, parent, engine, config, on_exit):
        pos = (config.get_dictionary_editor_frame_x(),
               config.get_dictionary_editor_frame_y())
        wx.Dialog.__init__(self, parent, title=TITLE, pos=pos,
                           style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)

        self.config = config
        self.on_exit = on_exit

        # layout
        global_sizer = wx.BoxSizer(wx.VERTICAL)

        filter_sizer = wx.BoxSizer(wx.HORIZONTAL)

        filter_left_sizer = wx.FlexGridSizer(2, 2, 4, 10)

        label = wx.StaticText(self, label=FILTER_BY_STROKE_TEXT)
        filter_left_sizer.Add(label,
                              flag=wx.ALIGN_CENTER_VERTICAL,
                              border=self.BORDER)

        self.filter_by_stroke = wx.TextCtrl(self,
                                            style=wx.TE_PROCESS_ENTER,
                                            size=wx.Size(200, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self._do_filter, self.filter_by_stroke)
        filter_left_sizer.Add(self.filter_by_stroke)

        label = wx.StaticText(self, label=FILTER_BY_TRANSLATION_TEXT)
        filter_left_sizer.Add(label,
                              flag=wx.ALIGN_CENTER_VERTICAL,
                              border=self.BORDER)

        self.filter_by_translation = wx.TextCtrl(self,
                                                 style=wx.TE_PROCESS_ENTER,
                                                 size=wx.Size(200, 20))
        self.Bind(wx.EVT_TEXT_ENTER,
                  self._do_filter,
                  self.filter_by_translation)
        filter_left_sizer.Add(self.filter_by_translation)

        filter_sizer.Add(filter_left_sizer, flag=wx.ALL, border=self.BORDER)

        do_filter_button = wx.Button(self, label=DO_FILTER_BUTTON_NAME)
        self.Bind(wx.EVT_BUTTON, self._do_filter, do_filter_button)

        filter_sizer.Add(do_filter_button,
                         flag=wx.EXPAND | wx.ALL,
                         border=self.BORDER)

        global_sizer.Add(filter_sizer, flag=wx.ALL, border=self.BORDER)

        self.store = DictionaryEditorStore(engine, config)

        # Grid
        self.grid = DictionaryEditorGrid(self, size=wx.Size(800, 600))
        self.grid.CreateGrid(self.store, 0, NUM_COLS)

        self.grid.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)

        self.grid.SetColSize(COL_STROKE, 250)
        self.grid.SetColSize(COL_TRANSLATION, 250)
        self.grid.SetColSize(COL_DICTIONARY, 150)

        read_only_right_aligned = wx.grid.GridCellAttr()
        read_only_right_aligned.SetReadOnly(True)
        read_only_right_aligned.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
        self.grid.SetColAttr(COL_DICTIONARY, read_only_right_aligned)
        self.grid.SetColAttr(COL_SPACER, read_only_right_aligned)

        global_sizer.Add(self.grid, 1, wx.EXPAND)

        buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)

        insert_button = wx.Button(self, label=INSERT_BUTTON_NAME)
        self.Bind(wx.EVT_BUTTON, self._insert_new, insert_button)

        buttons_sizer.Add(insert_button, flag=wx.ALL, border=self.BORDER)

        delete_button = wx.Button(self, label=DELETE_BUTTON_NAME)
        self.Bind(wx.EVT_BUTTON, self._delete, delete_button)

        buttons_sizer.Add(delete_button, flag=wx.ALL, border=self.BORDER)

        buttons_sizer.Add((0, 0), 1, wx.EXPAND)

        save_button = wx.Button(self, label=SAVE_BUTTON_NAME)
        self.Bind(wx.EVT_BUTTON, self._save_close, save_button)

        buttons_sizer.Add(save_button, flag=wx.ALL, border=self.BORDER)

        cancel_button = wx.Button(self, label=CANCEL_BUTTON_NAME)
        self.Bind(wx.EVT_BUTTON, self._cancel_close, cancel_button)

        buttons_sizer.Add(cancel_button, flag=wx.ALL, border=self.BORDER)

        global_sizer.Add(buttons_sizer,
                         0,
                         flag=wx.EXPAND | wx.ALL,
                         border=self.BORDER)

        self.Bind(wx.EVT_MOVE, self._on_move)
        self.Bind(wx.EVT_CLOSE, self._on_close)

        self.SetAutoLayout(True)
        self.SetSizer(global_sizer)
        global_sizer.Fit(self)
        global_sizer.SetSizeHints(self)
        self.Layout()
        self.SetRect(AdjustRectToScreen(self.GetRect()))

        self.last_window = util.GetForegroundWindow()

    def _do_filter(self, event=None):
        threading.Thread(target=self._do_filter_thread).start()

    def _do_filter_thread(self):
        self.store.ApplyFilter(self.filter_by_stroke.GetValue(),
                               self.filter_by_translation.GetValue())
        self.grid.RefreshView()

    def _insert_new(self, event=None):
        self.grid.InsertNew()

    def _delete(self, event=None):
        self.grid.DeleteSelected()

    def _save_close(self, event=None):
        self.store.SaveChanges()
        self.Close()

    def _cancel_close(self, event=None):
        self.Close()

    def _on_move(self, event):
        pos = self.GetScreenPositionTuple()
        self.config.set_dictionary_editor_frame_x(pos[0])
        self.config.set_dictionary_editor_frame_y(pos[1])
        event.Skip()

    def _on_close(self, event=None):
        result = wx.ID_YES
        if self.store.pending_changes:
            dlg = wx.MessageDialog(self,
                                   "You will lose your changes. Are you sure?",
                                   "Cancel",
                                   wx.YES_NO | wx.ICON_QUESTION)
            result = dlg.ShowModal()
            dlg.Destroy()
        if result == wx.ID_YES:
            try:
                util.SetForegroundWindow(self.last_window)
            except:
                pass
            self.on_exit()
            self.Destroy()

class DictionaryEditorGrid(wx.grid.Grid):
    """ Dictionary Manager's grid """
    GRID_LABEL_STROKE = "Stroke"
    GRID_LABEL_TRANSLATION = "Translation"
    GRID_LABEL_DICTIONARY = "Dictionary"
    GRID_LABEL_SPACER = " "
    sorted_labels = sorted([[COL_STROKE, GRID_LABEL_STROKE],
                            [COL_TRANSLATION, GRID_LABEL_TRANSLATION],
                            [COL_SPACER, GRID_LABEL_SPACER],
                            [COL_DICTIONARY, GRID_LABEL_DICTIONARY]])
    grid_labels = [pair[1] for pair in sorted_labels]

    def __init__(self, *args, **kwargs):
        wx.grid.Grid.__init__(self, *args, **kwargs)

        self.parent = args[0]

        self._changedRow = None

    def CreateGrid(self, store, rows, cols):
        """ Create the grid """

        wx.grid.Grid.CreateGrid(self, rows, cols)
        wx.grid.Grid.DisableDragRowSize(self)

        self.store = store

        # Set GridTable
        self._table = DictionaryEditorGridTable(self.store)
        self.SetTable(self._table)

        self._sortingColumn = 0
        self._sortingAsc = None

        self.Bind(EVT_GRID_LABEL_LEFT_CLICK, self._onLabelClick)

    def RefreshView(self):
        self._table.ResetView(self)

    def InsertNew(self):
        selected_row = self.GetGridCursorRow()
        self.store.InsertNew(selected_row)
        self._table.ResetView(self)

    def DeleteSelected(self):
        selected_row = self.GetGridCursorRow()
        self.store.DeleteSelected(selected_row)
        self._table.ResetView(self)

    def _onLabelClick(self, evt):
        """ Handle Grid label click"""

        if evt.Row == -1:
            if evt.Col >= 0:
                self.store.Sort(evt.Col)
                sort_column = self.store.GetSortColumn()
                sort_mode = self.store.GetSortMode()
                self._updateGridLabel(sort_column, sort_mode)
                self._table.ResetView(self)

        if evt.Col == -1:
            if evt.Row >= 0:
                self.SelectRow(evt.Row)
                self.SetGridCursor(evt.Row, 0)

    def _updateGridLabel(self, column, mode):
        """ Change grid's column labels """

        directionLabel = ""
        if mode is not None:
            directionLabel = " (asc)" if mode else " (desc)"
        for i in range(len(self.grid_labels)):
            label = (self.grid_labels[i] +
                     (directionLabel if column == i else ""))
            self._table.SetColLabelValue(i, label)

class DictionaryEditorGridTable(PyGridTableBase):
    """
    A custom wx.Grid Table using user supplied data
    """
    def __init__(self, store):
        """ Init GridTableBase with a Store. """

        # The base class must be initialized *first*
        PyGridTableBase.__init__(self)
        self.store = store
        cols = sorted([[COL_STROKE, "Stroke"],
                       [COL_SPACER, ""],
                       [COL_TRANSLATION, "Translation"],
                       [COL_DICTIONARY, "Dictionary"]])
        self.col_names = [pair[1] for pair in cols]

        self._rows = self.GetNumberRows()
        self._cols = self.GetNumberCols()

    def GetNumberCols(self):
        return len(self.col_names)

    def GetNumberRows(self):
        return self.store.GetNumberOfRows()

    def GetColLabelValue(self, col):
        return self.col_names[col]

    def SetColLabelValue(self, col, name):
        self.col_names[col] = name

    def GetRowLabelValue(self, row):
        return str(row + 1)

    def GetValue(self, row, col):
        return self.store.GetValue(row, col)

    def SetValue(self, row, col, value):
        self.store.SetValue(row, col, value)

    def ResetView(self, grid):

        grid.BeginBatch()

        for current, new, delmsg, addmsg in [
            (self._rows, self.GetNumberRows(),
                wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED,
                wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED),
            (self._cols, self.GetNumberCols(),
                wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED,
                wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED)
        ]:
            if new < current:
                msg = wx.grid.GridTableMessage(self,
                                               delmsg,
                                               new,
                                               current-new)
                grid.ProcessTableMessage(msg)
            elif new > current:
                msg = wx.grid.GridTableMessage(self,
                                               addmsg,
                                               new-current)
                grid.ProcessTableMessage(msg)
                self.UpdateValues(grid)

        grid.EndBatch()

        self._rows = self.GetNumberRows()
        self._cols = self.GetNumberCols()

        grid.AdjustScrollbars()
        grid.ForceRefresh()

    def UpdateValues(self, grid):
        """Update all displayed values"""
        # This sends an event to the grid table to update all of the values
        msg = (wx.grid
               .GridTableMessage(self,
                                 wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES))
        grid.ProcessTableMessage(msg)

def Show(parent, engine, config):
    if 'dialog_instance' not in Show.__dict__:
        Show.dialog_instance = None

    def clear_instance():
        Show.dialog_instance = None

    if Show.dialog_instance is None:
        Show.dialog_instance = DictionaryEditor(parent,
                                                engine,
                                                config,
                                                clear_instance)
    Show.dialog_instance.Show()
    Show.dialog_instance.Raise()
    util.SetTopApp()


from art import logo
from menu import Menu, MenuItem
from coffee_maker import CoffeeMaker
from money_machine import MoneyMachine

coffe_maker = CoffeeMaker()
menu = Menu()
money_machine = MoneyMachine()

print(logo)

is_on = True
while is_on:

    choice = input(f'What would you like? ({menu.get_items()}): ')
    if choice == 'off':
        is_on = False
    elif choice == 'report':
        coffe_maker.report()
        money_machine.report()
    else:
        # retrive drink from menu module
        drink = menu.find_drink(choice)
        # check if we have sufficient ingredients for the drink
        if coffe_maker.resource_sufficient(drink):
            print(f"Please insert ${drink.cost}.")
            # check if we have enough money for the drink then make the drink
            if money_machine.make_payment(drink.cost):
                coffe_maker.make_drink(drink)


#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# ================================================================================================ #
# Project    : Atelier AI: Studio for AI Designers                                                 #
# Version    : 0.1.4                                                                               #
# Python     : 3.10.4                                                                              #
# Filename   : /atelier/persistence/odb.py                                                         #
# ------------------------------------------------------------------------------------------------ #
# Author     : John James                                                                          #
# Email      : john.james.ai.studio@gmail.com                                                      #
# URL        : https://github.com/john-james-ai/atelier-ai                                         #
# ------------------------------------------------------------------------------------------------ #
# Created    : Thursday March 2nd 2023 05:49:40 am                                                 #
# Modified   : Thursday March 2nd 2023 03:55:14 pm                                                 #
# ------------------------------------------------------------------------------------------------ #
# License    : MIT License                                                                         #
# Copyright  : (c) 2023 John James                                                                 #
# ================================================================================================ #
import os
import shelve
from typing import Any

from atelier.persistence.database import Database
from atelier.persistence.exceptions import (
    ObjectExistsError,
    ObjectNotFoundError,
    ObjectDatabaseConnectionError,
)

# ------------------------------------------------------------------------------------------------ #
#                                       OBJECT DB                                                  #
# ------------------------------------------------------------------------------------------------ #
class ObjectDB(Database):
    """Object Database"""

    def __init__(self, name: str, filepath: str) -> None:
        super().__init__()
        self._name = name
        self._filepath = filepath
        self._is_connected = False
        self._connection = None
        os.makedirs(os.path.dirname(self._filepath), exist_ok=True)

    @property
    def name(self) -> str:
        return self._name

    @property
    def filepath(self) -> str:
        return self._filepath

    @property
    def is_connected(self) -> bool:
        return self._is_connected

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if self._is_connected:
            self.close()
        if exc_type is not None:
            self._logger.error(f"\nExecution Type: {exc_type}")
            self._logger.error(f"\nExecution Value: {exc_value}")
            self._logger.error(f"\nTraceback: {traceback}")

    def connect(self) -> None:
        """Connects to the database."""
        self._connection = shelve.open(self._filepath)
        self._is_connected = True

    def close(self) -> None:
        """Closes the underlying database connection."""
        self._connection.close()
        self._is_connected = False

    def insert(self, key: str, value: Any) -> None:
        """Inserts a key/value pair into the database."""
        if self.exists(key):
            msg = f"Object with key {key} already exists in the database {self._name}."
            self._logger.error(msg)
            raise ObjectExistsError(msg)

        self._connection[key] = value

    def select(self, key: str) -> Any:
        """Retrieves data from the database"""
        try:
            return self._connection[key]
        except KeyError:
            msg = f"Object with key {key} not found in database {self._name}."
            self._logger.error(msg)
            raise ObjectNotFoundError(msg)
        except ValueError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)
        except TypeError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)

    def selectall(self) -> Any:
        """Retrieves all data from the database"""
        objects = {}
        try:
            keys = self._connection.keys()
            for key in keys:
                objects[key] = self._connection[key]
            return objects
        except ValueError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)
        except AttributeError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)

    def update(self, key: str, value: Any) -> None:
        """Updates an existing object in the database."""
        if self.exists(key):
            self._connection[key] = value

        else:
            msg = f"Object with key {key} not found in database {self._name}."
            self._logger.error(msg)
            raise ObjectNotFoundError(msg)

    def delete(self, key: str) -> None:
        """Deletes existing data."""
        try:
            del self._connection[key]
        except KeyError:
            msg = f"Object with key {key} doesn't exist in the database {self._name}."
            self._logger.error(msg)
            raise ObjectNotFoundError(msg)
        except ValueError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)
        except TypeError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)

    def exists(self, key: str) -> bool:
        """Checks existence of an item in the database."""
        try:
            return key in self._connection.keys()
        except ValueError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)
        except AttributeError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)

    def clear(self) -> None:
        """Clears cache of all objects."""
        try:
            self._connection.clear()
        except ValueError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)
        except AttributeError:
            msg = f"Database connection {self._name} is closed."
            self._logger.error(msg)
            raise ObjectDatabaseConnectionError(msg)


import os
import cv2
import numpy as np
from keras.preprocessing.image import img_to_array, load_img
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
import keras
import tensorflow as tf
import PIL
from keras.applications.resnet50 import ResNet50
from keras.applications.xception import Xception
from keras.applications.inception_v3 import InceptionV3
from keras.applications.inception_resnet_v2 import InceptionResNetV2
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, InputLayer
from keras.models import Sequential
from keras import optimizers
from keras import backend as K
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from keras.models import Model
import wandb
from wandb.keras import WandbCallback
from keras.callbacks import EarlyStopping
from keras.regularizers import l2
from keras.models import model_from_json

train_folder ='gdrive/MyDrive/inaturalist_12K/train'
test_folder ='gdrive/MyDrive/inaturalist_12K/val'
model_folder ='./model'
IMG_DIM = (256,256,3)
batch_size =32
steps_per_epoch =250
validation_steps =30

configuration = {
    "model_name" : 'inceptionresnetv2', #  replace value of model with either of the 4 - xception,inceptionv3, inceptionresnetv2, resnet
    "num_classes" : 10,
    "no_layers_to_freeze" :0,
    "epochs": 15,
    "learning_rate": 0.001,
    "optimizer": 'momentum',#sgd,nesterov,adam,nadam,rmsprop
    "number_dense_layers": 5,
    "activation" : 'relu',#sigmoid,tanh
    "dropout":0.1,
    "l2": 0

}

def define_classes():
    classes =dict()
    class_=[f for f in os.listdir(train_folder) if f.startswith('.')!=1]
    for index,i in enumerate(class_):
        if (i.startswith('.')!=1):
            classes[index+1]= i
    return classes

class pretrained_model():

  def __init__(self, model_name= configuration.get("model_name"),num_classes= configuration.get("num_classes"),epochs= configuration.get("epochs"), num_frozen_layer = configuration.get("no_layers_to_freeze"), learning_rate =configuration.get("learning_rate"), optimizer = configuration.get("optimizer"),activation = configuration.get("activation"), no_dense_layers = configuration.get("number_dense_layers"),dropoutp = configuration.get("dropout"),l2 =configuration.get("l2")):
    self.model_name = model_name
    self.num_classes = num_classes
    self.classes = define_classes()
    self.model = []
    self.epochs = epochs
    self.num_frozen_layer = num_frozen_layer
    self.optimizer = optimizer
    self.lr = learning_rate
    self.activation = activation
    self.no_dense_layers = no_dense_layers
    self.dropout = dropoutp
    self.l2 = l2

    datagen = ImageDataGenerator(validation_split=0.1,rescale = 1./255.)
    self.train = datagen.flow_from_directory(
        train_folder,
        subset='training',
        batch_size=batch_size,
        target_size=(256,256)
    )

    self.val = datagen.flow_from_directory(
        train_folder,
        subset='validation',
        batch_size=batch_size,
        target_size=(256,256)
    )
    datagen = ImageDataGenerator(rescale = 1./255.)
    self.test_it = datagen.flow_from_directory(test_folder, batch_size=batch_size,target_size=(256,256))

  def model_(self):

    if(self.model_name =='resnet'):
        model = ResNet50(weights='imagenet',include_top=False,input_shape=IMG_DIM)
    elif (self.model_name =='xception'):
        model = Xception(weights='imagenet',include_top=False,input_shape=IMG_DIM)
    elif (self.model_name =='inceptionv3'):
        model = InceptionV3(weights='imagenet',include_top=False,input_shape=IMG_DIM)
    elif (self.model_name =='inceptionresnetv2'):
        model = InceptionResNetV2(weights='imagenet',include_top=False,input_shape=IMG_DIM)

    if self.num_frozen_layer == -1:
      model.trainable = True
    else:
      for index,layer in enumerate(reversed(model.layers)):
          if index <self.num_frozen_layer:
            layer.trainable = True
          else:
            layer.trainable = False

    self.model = Sequential()
    self.model.add(model)
    self.model.add(Flatten())
    for i in range(0,self.no_dense_layers):
      self.model.add(Dense(pow(2,(10-i)), activation=self.activation,input_dim= self.model.output_shape,bias_regularizer=l2(self.l2)))
      self.model.add(Dropout(self.dropout))
    self.model.add(Dense(self.num_classes, activation='softmax',bias_regularizer=l2(self.l2)))

    if(self.optimizer == 'rmsprop'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.RMSprop(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'adam'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.Adam(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'nadam'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.Nadam(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'sgd'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'momentum'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(self.lr,momentum = 0.9),
              metrics=['accuracy'])
    elif(self.optimizer == 'nesterov'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(self.lr,momentum = 0.9, nesterov=True),
              metrics=['accuracy'])

    self.model.summary()

  def finetune(self):
    es = EarlyStopping(monitor='val_loss', mode='min', verbose=1)
    with tf.device('/device:GPU:0'):
      self.model.fit_generator(self.train,steps_per_epoch =steps_per_epoch,epochs=self.epochs,verbose=1,validation_data=self.val,validation_steps=validation_steps,callbacks=[es])#,callbacks=[WandbCallback()])

  def save_model(self):
    try:
      os.mkdir(model_folder)
    except:
      pass
    model_json = self.model.to_json()
    with open(model_folder+"/model.json", "w") as json_file:
      json_file.write(model_json)
    self.model.save(model_folder+'/cnn.h5')

  def load_model(self):
    json_file = open(model_folder+'/model.json', 'r')
    loaded_model_json = json_file.read()
    json_file.close()
    self.model = model_from_json(loaded_model_json)
    self.model.load_weights(model_folder+"/cnn.h5")
    if(self.optimizer == 'rmsprop'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.RMSprop(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'adam'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.Adam(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'nadam'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.Nadam(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'sgd'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(self.lr),
              metrics=['accuracy'])
    elif(self.optimizer == 'momentum'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(self.lr,momentum = 0.9),
              metrics=['accuracy'])
    elif(self.optimizer == 'nesterov'):
      self.model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(self.lr,momentum = 0.9, nesterov=True),
              metrics=['accuracy'])

  def predict(self):
    score=self.model.evaluate(self.test_it,verbose=0)
    print("Test Accuracy:  ",score[1])

def train():
  wandb.init(config=configuration, magic=True,reinit = True)
  wandb.run.name = 'mn-'+wandb.config.model_name+'-no_layers_to_freeze-'+str(wandb.config.no_layers_to_freeze)+'-epochs-'+str(wandb.config.epochs)+'-dense-layers-'+str(wandb.config.number_dense_layers)+'-op-'+str(wandb.config.optimizer)
  print(wandb.run.name)

  model_name = wandb.config.model_name #  replace value of model with either of the 4 - xception,inceptionv3, inceptionresnetv2, resnet
  num_classes = wandb.config.num_classes
  no_layers_to_freeze = wandb.config.no_layers_to_freeze
  epochs= wandb.config.epochs
  learning_rate = wandb.config.learning_rate
  optimizer = wandb.config.optimizer
  activation = wandb.config.activation
  no_dense_layers = wandb.config.number_dense_layers
  dropoutp=wandb.config.dropout
  l2 = wandb.config.l2

  cnn = pretrained_model(model_name, num_classes,epochs, no_layers_to_freeze,learning_rate, optimizer,activation, no_dense_layers,dropoutp,l2)
  cnn.model_()
  cnn.finetune()
  cnn.save_model()
  cnn.predict()

if __name__ == "__main__":
  train()
  wandb.finish()


from numpy import zeros, ones, sin, cos, uint8, pi
from sys import argv
from tqdm import tqdm
from png import Writer
try: from PIL.Image import open as iopen
except ImportError: from Image import open as iopen
class DoublePendulum():
    def __init__(self, thetas): self.t, self.td, self.l, self.m = thetas, zeros(thetas.shape), ones(thetas.shape), ones(thetas.shape)
    def step(self):
        self.td[:, 0] += (-10 * (2 * self.m[:, 0] + self.m[:, 1]) * sin(self.t[:, 0]) - self.m[:, 1] * 10 * sin(self.t[:, 0] - 2 * self.t[:, 1]) - 2 * sin(self.t[:, 0] - self.t[:, 1]) * self.m[:, 1] * (self.td[:, 1] ** 2 * self.l[:, 1] + self.td[:, 0] ** 2 * self.l[:, 0] * cos(self.t[:, 0] - self.t[:, 1]))) / (2000 * self.l[:, 0] * (2 * self.m[:, 0] + self.m[:, 1] * (1 - cos(2 * (self.t[:, 0] - self.t[:, 1])))))
        self.td[:, 1] += (2 * sin(self.t[:, 0] - self.t[:, 1]) * (self.td[:, 0] ** 2 * self.l[:, 0] * (self.m[:, 0] + self.m[:, 1]) + 10 * (self.m[:, 0] + self.m[:, 1]) * cos(self.t[:, 0]) + self.td[:, 1] ** 2 * self.l[:, 1] * self.m[:, 1] * cos(self.t[:, 0] - self.t[:, 1]))) / (2000 * self.l[:, 1] * (2 * self.m[:, 0] + self.m[:, 1] * (1 - cos(2 * (self.t[:, 0] - self.t[:, 1])))))
        self.t += self.td / 2000
color = lambda x, y: (127 * cos(x / 4 - y / 4), 127 * (cos(x / 4 - y / 4) - sin(x / 4 + y / 4)), 127 * (sin(x / 4 - y / 4) + cos(x / 4 + y / 4)))
thetas = ones((893025, 2))
for i in range(893025): thetas[i, 0], thetas[i, 1] = pi * (2 * (i % 945) / 945 - 1), pi * (2 * (i // 945) / 945 - 1)
p, frames, FRAMES = DoublePendulum(thetas), [], int(argv[1])
for frame in tqdm(range(FRAMES)):
    fractal, file, writer = uint8([[a for i in range(945) for a in color(p.t[j * 945 + i, 0], p.t[j * 945 + i, 1])] for j in range(945)]), open(f'frames/frame{frame}.png', 'wb+'), Writer(945, 945, greyscale = False)
    writer.write(file, fractal)
    frames += [iopen(file)]
    for _ in range(33): p.step()
frames[0].save('output.gif', format = 'GIF', append_images = frames[1:], save_all = True, duration = FRAMES / 60)


####################################################
# # mission_to_mars
# ----
#
# Written in the Python 3.7.9 Environment
#
# By Nicole Lund
#
# This Python script scrapes Mars space data from various
# locations for storage in a Pymongo DB and display on a webpage.
####################################################

# Import Dependencies
import pandas as pd
import time
from splinter import Browser
from bs4 import BeautifulSoup
from webdriver_manager.chrome import ChromeDriverManager

def nasa_news(browser):
    ####################################################
    # NASA Mars News
    ####################################################

    # Access NASA news site
    nasa_url = 'https://mars.nasa.gov/news/'
    browser.visit(nasa_url)
    nasa_html = browser.html
    nasa_soup = BeautifulSoup(nasa_html, 'html.parser')

    # Allow webpage to load fully
    time.sleep(1.5)

    # Collect the latest news headline and paragraph
    latest_container = nasa_soup.find('div', class_='image_and_description_container')

    # Final Nasa Result
    news_headline = latest_container.find('div', class_='content_title').find('a').text
    news_teaser = latest_container.find('div', class_='article_teaser_body').text
    nasa_news_headline = {'headline':news_headline,'teaser':news_teaser}

    # print('')
    # print('-------- NASA News Top Headline --------')
    # print(nasa_news_headline)

    return nasa_news_headline

def jpl_feature(browser):
    ####################################################
    # JPL Mars Space Images - Featured Image
    ####################################################

    # Access JPL image site
    jpl_base_url = 'https://data-class-jpl-space.s3.amazonaws.com/JPL_Space/'
    jpl_url = jpl_base_url + 'index.html'
    browser.visit(jpl_url)
    jpl_html = browser.html
    jpl_soup = BeautifulSoup(jpl_html, 'html.parser')
    # Allow webpage to load fully - Has not been necessary
    # time.sleep(1)

    # Collect the full url path for the full size featured image
    featured = jpl_soup.find('div', class_='floating_text_area')
    jpl_relative_url = featured.find('a')['href']

    # Final JPL Result
    featured_image_url = jpl_base_url + jpl_relative_url

    # print('')
    # print('-------- JPL Featured Image --------')
    # print(featured_image_url)

    return featured_image_url

def mars_facts(browser):
    ####################################################
    # Mars Facts
    ####################################################

    # Collect Mars Facts Table
    facts_url = 'https://space-facts.com/mars/'
    facts_df = pd.read_html(facts_url)[0]

    # print('')
    # print('-------- Mars Facts Table --------')
    # print(facts_df)
    # print('')

    # Final Mars Facts Result - Convert facts table to html string
    # Note from Nicole Lund - I chose to remove all formatting from
    # the table as an esthetic choice because the Description column
    # fields include a : at the end.
    facts_html = facts_df.to_html(justify='left',border=0,header=False,index=False)

    return facts_html

def mars_hemispheres(browser):
    ####################################################
    # Mars Hemispheres
    ####################################################

    # Access Astrogeology site
    astropedia_base_url = 'https://astrogeology.usgs.gov'
    astropedia_relative_url = '/search/results?q=hemisphere+enhanced&k1=target&v1=Mars'
    astropedia_url = astropedia_base_url + astropedia_relative_url
    browser.visit(astropedia_url)
    astropedia_html = browser.html
    astropedia_soup = BeautifulSoup(astropedia_html, 'html.parser')

    # Allow webpage to load fully - Has not been necessary
    # time.sleep(1)

    # Collect hemisphere titles
    hemisphere_titles = []
    hemisphere_containers = astropedia_soup.find_all('div', class_='description')

    for image_num in range(0,5):
        try:
            hemisphere_found = hemisphere_containers[image_num].h3.text
            hemisphere_titles.append(hemisphere_found)
            print(f'Found Hemisphere: {hemisphere_found}')
        except IndexError:
            print('All Hemispheres Found')

    # Navigate to each hemisphere link and collect image link and title in a dictionary
    hemisphere_image_urls = []

    for hemisphere in hemisphere_titles:
        # Navigate to each hemisphere link
        browser.links.find_by_partial_text(hemisphere).click()

        # Collect URL for full size image
        hemisphere_html = browser.html
        hemisphere_soup = BeautifulSoup(hemisphere_html, 'html.parser')
        # Allow webpage to load fully - Has not been necessary
        # time.sleep(1)

        hemisphere_image = hemisphere_soup.find('img', class_='wide-image')['src']
        image_link = astropedia_base_url + hemisphere_image
        hemisphere_image_urls.append(\
            {"title":hemisphere,"img_url":image_link})

        # Return to main page
        browser.visit(astropedia_url)

    # print('')
    # print('-------- Mars Hemisphere Images --------')
    # print(hemisphere_image_urls)
    # print('')

    return hemisphere_image_urls

def scrape():
    ####################################################
    # Scrape Mars Related Data
    ####################################################

    # Initialize browser
    executable_path = {'executable_path': ChromeDriverManager().install()}
    browser = Browser('chrome', **executable_path, headless=False)

    # Retrieve NASA news
    nasa_headline_teaser = nasa_news(browser)

    # Retrieve JPL Featured Image
    jpl_image = jpl_feature(browser)

    # Retrieve Mars Facts Table
    facts_table = mars_facts(browser)

    # Retrieve Mars Hemisphere Images
    hemisphere_image_links = mars_hemispheres(browser)

    # Store all retrieved data within a single dictionary
    mars_data = {\
        'nasa_top_story':nasa_headline_teaser,\
        'jpl_featured_img':jpl_image,\
        'facts_table_html':facts_table,\
        'hemisphere_images':hemisphere_image_links\
        }

    # print('')
    # print('-------- Combined Mars Data --------')
    # print(mars_data)

    # Close splinter browser
    browser.quit()

    return(mars_data)

if __name__ == "__main__":
   scrape()