from __future__ import print_function
import numpy as np
import time, warnings
import itertools
import scipy
from scipy.stats import binned_statistic as binnedstat,chi2
from scipy.optimize import curve_fit
import itertools

try:
    from pandas import DataFrame
    import pandas as pd
except:
    import warnings
    warnings.warn("Could not find pandas. Install using pip; FisherMatrix will not work otherwise")
    class DataFrame:
        pass

def eig_analyze(cmb2d,start=0,eigfunc=np.linalg.eigh,plot_file=None):
    es = eigfunc(cmb2d[start:,start:,...].T)[0]
    print(start,es.min(),np.any(es<0.))
    numw = range(np.prod(es.shape[:-1]))
    pl = io.Plotter(xlabel='n',ylabel='e',yscale='log')
    for ind in range(es.shape[-1]):
        pl.add(numw,np.sort(np.real(es[...,ind].ravel())))
        pl.add(numw,np.sort(np.imag(es[...,ind].ravel())),ls="--")
    pl.done(plot_file)

def get_sigma2(ells,cls,w0,delta_ells,fsky,ell0=0,alpha=1,w0p=None,ell0p=0,alphap=1,clxx=None,clyy=None):
    afact = ((ell0/ells)**(-alpha)) if ell0>1.e-3 else 0.*ells
    nlxx = (w0*np.pi/180./60.)**2 * afact
    if clxx is not None:
        afact = ((ell0p/ells)**(-alphap)) if ell0>1.e-3 else 0.*ells
        nlyy = (w0p*np.pi/180./60.)**2 * afact
        tclxx = clxx + nlxx
        tclyy = clyy + nlyy
        tcl2 = cls**2 + tclxx*tclyy
    else:
        assert clyy is None
        assert w0p is None
        tcl2 = 2 * (cls+nlxx)**2
    return tcl2/(2*ells+1)/fsky/delta_ells

def fit_cltt_power(ells,cls,cltt_func,w0,sigma2,ell0=0,alpha=1,fix_knee=False):
    """
    Fit binned power spectra to a linear model of the form
    A * C_ell + B * w0^2 (ell/ell0)^alpha + C * w0^2
    """
    # Cinv = np.diag(1./sigma2)
    # Cov = np.diag(sigma2)
    sw0 = w0 * np.pi / 180./ 60.
    if fix_knee:
        funcs = [lambda x: sw0**2]
    else:
        funcs = [lambda x: sw0**2, lambda x: sw0**2 * (ell0/x)**(-alpha) if ell0>1e-3 else sw0**2 ]
    bds = (0,np.inf)
    X,_ = curve_fit(lambda x,*args: sum([arg*f(x) for f,arg in zip(funcs,args) ]) , ells, (cls-cltt_func(ells)),
                  p0=[1] if fix_knee else [1,ell0],sigma=np.sqrt(sigma2),absolute_sigma=True,
                  bounds=bds)
    #X,_,_,_ = fit_linear_model(ells,(cls-cltt_func(ells)),Cov,funcs,Cinv=Cinv) # Linear models dont allow bounds
    return lambda x : cltt_func(x) + sum([ coeff*f(x)  for coeff,f in zip(X,funcs)])

def fit_linear_model(x,y,ycov,funcs,dofs=None,deproject=True,Cinv=None,Cy=None):
    """
    Given measurements with known uncertainties, this function fits those to a linear model:
    y = a0*funcs[0](x) + a1*funcs[1](x) + ...
    and returns the best fit coefficients a0,a1,... and their uncertainties as a covariance matrix
    """
    s = solve if deproject else np.linalg.solve
    C = ycov
    y = y[:,None]
    A = np.zeros((y.size,len(funcs)))
    for i,func in enumerate(funcs):
        A[:,i] = func(x)
    CA = s(C,A) if Cinv is None else np.dot(Cinv,A)
    cov = np.linalg.inv(np.dot(A.T,CA))
    if Cy is None: Cy = s(C,y) if Cinv is None else np.dot(Cinv,y)
    b = np.dot(A.T,Cy)
    X = np.dot(cov,b)
    YAX = y - np.dot(A,X)
    CYAX = s(C,YAX) if Cinv is None else np.dot(Cinv,YAX)
    chisquare = np.dot(YAX.T,CYAX)
    dofs = len(x)-len(funcs)-1 if dofs is None else dofs
    pte = 1 - chi2.cdf(chisquare, dofs)
    return X,cov,chisquare/dofs,pte

def fit_gauss(x,y,mu_guess=None,sigma_guess=None):
    ynorm = np.trapz(y,x)
    ynormalized = y/ynorm
    gaussian = lambda t,mu,sigma: np.exp(-(t-mu)**2./2./sigma**2.)/np.sqrt(2.*np.pi*sigma**2.)
    popt,pcov = curve_fit(gaussian,x,ynormalized,p0=[mu_guess,sigma_guess])#,bounds=([-np.inf,0],[np.inf,np.inf]))
    fit_mean = popt[0]
    fit_sigma = popt[1]
    return fit_mean,np.abs(fit_sigma),ynorm,ynormalized

class Solver(object):
    """
    Calculate Cinv . x
    """
    def __init__(self,C,u=None):
        """
        C is an (NxN) covariance matrix
        u is an (Nxk) template matrix for rank-k deprojection
        """
        N = C.shape[0]
        if u is None: u = np.ones((N,1))
        Cinvu = np.linalg.solve(C,u)
        self.precalc = np.dot(Cinvu,np.linalg.solve(np.dot(u.T,Cinvu),u.T))
        self.C = C
    def solve(self,x):
        Cinvx = np.linalg.solve(self.C,x)
        correction = np.dot(self.precalc,Cinvx)
        return Cinvx - correction

def solve(C,x,u=None):
    """
    Typically, you do not want to invert your covariance matrix C, but really just want
    Cinv . x , for some vector x.
    You can get that with np.linalg.solve(C,x).
    This function goes one step further and deprojects a common mode for the entire
    covariance matrix, which is often what is needed.
    """
    N = C.shape[0]
    s = Solver(C,u=u)
    return s.solve(x)

def check_fisher_sanity(fmat,param_list):
    Ny,Nx = fmat.shape
    assert Ny==Nx
    assert Ny==len(param_list)
    assert len(param_list)==len(set(param_list))

def write_fisher(filename,fmat,delim=','):
    np.savetxt(filename,fmat,header=(delim).join(fmat.params),delimiter=delim)

def read_fisher_dataframe(csv_file):
    df = pd.read_csv(csv_file,index_col=0)
    params = list(df.columns)
    return FisherMatrix(fmat = df.values,param_list = params)

def read_fisher_pickle(pkl_file):
    import cPickle as pickle
    params,fmat = pickle.load(open(pkl_file,'rb'))
    return FisherMatrix(fmat = fmat,param_list = params)#,skip_inv=True)

def read_fisher(csv_file,delimiter=','):
    fmat = np.loadtxt(csv_file,delimiter=delimiter)
    with open(csv_file) as f:
        fline = f.readline()
    fline = fline.replace("#","")
    columns = fline.strip().split(delimiter)
    assert len(set(columns)) == len(columns)
    return FisherMatrix(fmat = fmat,param_list = columns)#,skip_inv=True)

def rename_fisher(fmat,pmapping):
    old_params = fmat.params
    new_params = list(old_params)
    for key in pmapping.keys():
        if key not in old_params: continue
        i = old_params.index(key)
        new_params[i] = pmapping[key]
    return FisherMatrix(fmat=fmat.values,param_list=new_params)#,skip_inv=True)

class FisherMatrix(DataFrame):
    """
    A Fisher Matrix object that subclasses pandas.DataFrame.
    This is essentially just a structured array that
    has identical column and row labels.

    You can initialize an empty one like:
    >> params = ['H0','om','sigma8']
    >> F = FisherMatrix(np.zeros((len(params),len(params))),params)

    where params is a list of parameter names. If you already have a
    Fisher matrix 'Fmatrix' whose diagonal parameter order is specified by
    the list 'params', then you can initialize this object as:

    >> F = FisherMatrix(Fmatrix,params)

    This makes the code 'aware' of the parameter order in a way that makes
    handling combinations of Fishers a lot easier.

    You can set individual elements like:

    >> F['s8']['H0'] = 1.

    Once you've populated the entries, you can do things like:
    >> Ftot = F1 + F2
    i.e. add Fisher matrices. The nice property here is that you needn't
    have initialized these objects with the same list of parameters!
    They can, for example, have mutually exclusive parameters, in which
    case you end up with some reordering of a block diagonal Fisher matrix.
    In the general case, of two overlapping parameter lists that don't
    have the same ordering, pandas will make sure the objects are added
    correctly.

    WARNING: No other operation other than addition is overloaded. Subtraction
    for instance will give unpredictable behaviour. (Will likely introduce
    NaNs) But you shouldn't be subtracting Fisher matrices anyway!

    You can add a gaussian prior to a parameter:
    >> F.add_prior('H0',2.0)

    You can drop an entire parameter (which removes that row and column):
    >> F.delete('s8')
    which does it in place.

    If you want to preserve the original before modifying, you can
    >> Forig = F.copy()

    You can get marginalized errors on each parameter as a dict:
    >> sigmas = F.sigmas()

    """

    def __init__(self,fmat,param_list,delete_params=None,prior_dict=None):#,skip_inv=False):
        """
        fmat            -- (n,n) shape numpy array containing initial Fisher matrix for n parameters
        param_list      -- n-element list specifying diagonal order of fmat
        delete_params   -- list of names of parameters you would like to delete from this
                        Fisher matrix when you initialize it. This is useful when skip_inv=False if some
                        of your parameters are not constrained. See skip_inv below.
        prior_dict      -- a dictionary that maps names of parameters to 1-sigma prior values
                        you would like to add on initialization. This can also be done later with the
                        add_prior function.
        skip_inv        -- If true, this skips calculation of the inverse of the Fisher matrix
                        when the object is initialized.
	"""

        check_fisher_sanity(fmat,param_list)
        pd.DataFrame.__init__(self,fmat.copy(),columns=param_list,index=param_list)
        try:
            a = self.params
            raise ValueError # self.params should not already exist
        except:
            pass
        self.params = param_list

        cols = self.columns.tolist()
        ind = self.index.tolist()
        assert set(self.params)==set(cols)
        assert set(self.params)==set(ind)

        if delete_params is not None:
            self.delete(delete_params)
        if prior_dict is not None:
            for prior in prior_dict.keys():
                self.add_prior(prior,prior_dict[prior])

    def copy(self, order='K'):
        """
        >> Fnew = F.copy()
        will create an independent Fnew that is not a view of the original.
        """
        f = FisherMatrix(pd.DataFrame.copy(self), list(self.params))
        return f

    def __radd__(self,other):
        return self._add(other,radd=True)

    def __add__(self,other):
        return self._add(other,radd=False)

    def _add(self,other,radd=False):
        if other is None: return self
        if radd:
            new_fpd = pd.DataFrame.radd(self,other.copy(),fill_value=0)
        else:
            new_fpd = pd.DataFrame.add(self,other.copy(),fill_value=0)
        return FisherMatrix(np.nan_to_num(new_fpd.values),new_fpd.columns.tolist())

    def add_prior(self,param,prior):
        """
        Adds 1-sigma value 'prior' to the parameter name specified by 'param'
        """
        self[param][param] += 1./prior**2.

    def sigmas(self):
        """
        Returns marginalized 1-sigma uncertainties on each parameter in the Fisher matrix.
        """
        # self._update()
        finv = np.linalg.inv(self.values)
        errs = np.diagonal(finv)**(0.5)
        return dict(zip(self.params,errs))

    def delete(self,params):
        """
        Given a list of parameter names 'params', deletes these from the Fisher matrix.
        """
        self.drop(labels=params,axis=0,inplace=True)
        self.drop(labels=params,axis=1,inplace=True)
        self.params = self.columns.tolist()
        assert set(self.index.tolist())==set(self.params)

    def reordered(self,params):
        # Return a reordered version of self
        return self[params].T[params]

    def marge_var_2param(self,param1,param2):
        """
        Returns the sub-matrix corresponding to two parameters param1 and param2.
        Useful for contour plots.
        """
        # self._update()
        finv = np.linalg.inv(self.values)
        i = self.params.index(param1)
        j = self.params.index(param2)
        chi211 = finv[i,i]
        chi222 = finv[j,j]
        chi212 = finv[i,j]

        return np.array([[chi211,chi212],[chi212,chi222]])

def alpha_from_confidence(c):
    """Returns the number of sigmas that corresponds to enclosure of c % of the
    probability density in a 2D gaussian.

    e.g. alpha_from_confidence(0.683) = 1.52
    """
    return np.sqrt(2.*np.log((1./(1.-c))))

def corner_plot(fishers,labels,fid_dict=None,params=None,confidence_level=0.683,show_1d=False,
                latex_dict=None,colors=itertools.repeat(None),lss=itertools.repeat(None),
                thk=2,center_marker=True,save_file=None,loc='upper right',labelsize=14,ticksize=2,lw=3,**kwargs):
    """Make a triangle/corner plot from Fisher matrices.
    Does not support multiple confidence levels. (Redundant under Gaussian approximation of Fisher)

    fishers -- list of stats.FisherMatrix objects
    labels -- labels corresponding to each Fisher matrix in fishers
    params -- By default (if None) uses all params in every Fisher matrix. If not None, uses only this list of params.
    fid_dict -- dictionary mapping parameter names to fiducial values to center ellipses on
    latex_dict -- dictionary mapping parameter names to LaTeX strings for axes labels. Defaults to parameter names.
    confidence_level -- fraction of probability density enclosed by ellipse
    colors -- list of colors corresponding to fishers
    lss -- list of line styles corresponding to fishers
    """

    from orphics import io
    import matplotlib
    import matplotlib.pyplot as plt
    if params is None:
        ps = [f.params for f in fishers]
        jparams = list(set([p for sub_ps in ps for p in sub_ps]))
    else:
        jparams = params
    numpars = len(jparams)
    alpha = alpha_from_confidence(confidence_level)
    xx = np.array(np.arange(360) / 180. * np.pi)
    circl = np.array([np.cos(xx),np.sin(xx)])

    fig=plt.figure(figsize=(2*(numpars+1),2*(numpars+1)),**kwargs) if show_1d else plt.figure(figsize=(2*numpars,2*numpars),**kwargs)
    startp = 0 if show_1d else 1

    if show_1d:
        sigmas = []
        for fish in fishers:
            sigmas.append(fish.sigmas())
    else:
        sigmas = itertools.repeat(None)

    for i in range(0,numpars):
        for j in range(i+startp,numpars):
            count = 1+(j)*(numpars) + (i+1) -1 if show_1d else 1+(j-1)*(numpars-1) + i
            paramX = jparams[i]
            paramY = jparams[j]
            ax = fig.add_subplot(numpars,numpars,count) if show_1d else fig.add_subplot(numpars-1,numpars-1,count)

            if i>0:
                ax.yaxis.set_visible(False)
            if j!=(numpars-1):
                ax.xaxis.set_visible(False)

            try:
                xval = fid_dict[paramX]
            except:
                xval = 0.
            try:
                yval = fid_dict[paramY]
            except:
                yval = 0.
            try:
                paramlabely = '$%s$' % latex_dict[paramY]
            except:
                paramlabely = '$%s$' % paramY
            try:
                paramlabelx = '$%s$' % latex_dict[paramX]
            except:
                paramlabelx = '$%s$' % paramX
            if center_marker:
                if i==j:
                    ax.axvline(x=xval,ls="--",color='k',alpha=0.5)
                else:
                    ax.plot(xval,yval,'xk',mew=thk)

            for fish,ls,col,lab,sig in zip(fishers,lss,colors,labels,sigmas):

                if i==j:
                    try:
                        s = sig[paramX]
                    except:
                        continue
                    nsigma = 5
                    xs = np.linspace(xval-nsigma*s,xval+nsigma*s,1000)
                    from scipy.stats import norm
                    g = norm.pdf(xs,loc=xval,scale=s)
                    ax.plot(xs,g/g.max())
                    continue

                try:
                    chisq = fish.marge_var_2param(paramX,paramY)
                except:
                    ax.plot([xval],[yval],linewidth=0,color=col,ls=ls,label=lab,alpha=0) # just to iterate the colors and ls
                    continue
                Lmat = np.linalg.cholesky(chisq)
                ansout = np.dot(alpha*Lmat,circl)
                ax.plot(ansout[0,:]+xval,ansout[1,:]+yval,linewidth=lw,color=col,ls=ls,label=lab)
            if (i==0):
                ax.set_ylabel(paramlabely, fontsize=labelsize,weight='bold')
            if (j == (numpars-1)):
                ax.set_xlabel(paramlabelx, fontsize=labelsize,weight='bold')

    handles, labels = ax.get_legend_handles_labels()
    legend = fig.legend(handles, labels,prop={'size':labelsize},numpoints=1,frameon = 0,loc=loc, bbox_to_anchor = (-0.1,-0.1,1,1),bbox_transform = plt.gcf().transFigure,**kwargs)

    if save_file is None:
        plt.show()
    else:
        plt.savefig(save_file, bbox_inches='tight')
        print(io.bcolors.OKGREEN+"Saved plot to", save_file+io.bcolors.ENDC)

class OQE(object):
    """Optimal Quadratic Estimator for likelihoods that
    are Gaussian in the model parameters.

    WARNING: This class has not been tested thoroughly.

    Given a fiducial covariance matrix for the data and derivatives
    of the covariance matrix w.r.t. each parameter of interest,
    this class precalculates things like the Fisher matrix when
    initialized.

    Subsequently, given a data vector, it returns the OQEstimate
    as a dictionary in the parameters.
    """
    def __init__(self,fid_cov,dcov_dict,fid_params_dict,invert=False,deproject=True,templates=None):

        self.params = dcov_dict.keys()
        self.fids = fid_params_dict
        Nparams = len(self.params)
        self.invert = invert
        if invert:
            self.Cinv = self._inv(fid_cov)

        if templates is not None: assert deproject
        self.biases = {}
        self.ps = {}
        for param in self.params:
            if not(invert):
                if deproject:
                    solution = solve(fid_cov,dcov_dict[param],u=templates)
                else:
                    solution = np.linalg.solve(fid_cov,dcov_dict[param])
            self.ps[param] = np.dot(self.Cinv,dcov_dict[param]) if invert else solution.copy()
            self.biases[param] = np.trace(self.ps[param])

        self.Fisher = np.zeros((Nparams,Nparams))
        param_combs = itertools.combinations_with_replacement(self.params,2)
        for param1,param2 in param_combs:
            i = self.params.index(param1)
            j = self.params.index(param2)
            self.Fisher[i,j] = 0.5 * np.trace(np.dot(self.ps[param1],self.ps[param2]))
            if j!=i: self.Fisher[j,i] = self.Fisher[i,j]

        self.Finv = self._inv(self.Fisher)

        self.marg_errors = np.diagonal(self.Finv)**(1./2.)

        if not(invert):
            if deproject:
                self.s = Solver(fid_cov,u=templates)
                self.solver = lambda x: self.s.solve(x)
            else:
                self.solver = lambda x: np.linalg.solve(fid_cov,x)

    def sigma(self):
        return dict(zip(self.params,self.marg_errors.tolist()))

    def estimate(self,data):
        vec = []
        for param in self.params:
            cinvdat = np.dot(self.Cinv,data) if self.invert else self.solver(data)
            fcore = np.dot(np.dot(data.T,self.ps[param]),cinvdat)
            bsubbed = fcore - self.biases[param]
            assert bsubbed.size == 1
            vec.append(bsubbed)
        vec = np.asarray(vec)
        ans = 0.5 * np.dot(self.Finv,vec)
        ans = dict(zip(self.params,ans.tolist()))
        res = {}
        for param in self.params:
            res[param] = self.fids[param] + ans[param]
        return res

    def _inv(self,cov):
        # return np.linalg.pinv(cov)
        return np.linalg.inv(cov)
        # return scipy.linalg.pinv2(cov)

class OQESlim(object):
    def __init__(self,fid_cov,dcov_dict,fid_params_dict,templates=None):

        self.params = dcov_dict.keys()
        self.fids = fid_params_dict
        Nparams = len(self.params)
        self.biases = {}
        self.ps = {}
        for param in self.params:
            solution = solve(fid_cov,dcov_dict[param],u=templates)
            self.ps[param] = solution.copy()
            self.biases[param] = np.trace(self.ps[param])

        self.Fisher = np.zeros((Nparams,Nparams))
        param_combs = itertools.combinations_with_replacement(self.params,2)
        for param1,param2 in param_combs:
            i = self.params.index(param1)
            j = self.params.index(param2)
            self.Fisher[i,j] = 0.5 * np.trace(np.dot(self.ps[param1],self.ps[param2]))
            if j!=i: self.Fisher[j,i] = self.Fisher[i,j]

        self.Finv = np.linalg.inv(self.Fisher)
        self.marg_errors = np.diagonal(self.Finv)**(1./2.)

        self.s = Solver(fid_cov,u=templates)
        self.solver = lambda x: self.s.solve(x)

    def sigma(self):
        return dict(zip(self.params,self.marg_errors.tolist()))

    def estimate(self,data):
        vec = []
        for param in self.params:
            cinvdat = self.solver(data)
            fcore = np.dot(np.dot(data.T,self.ps[param]),cinvdat)
            bsubbed = fcore - self.biases[param]
            assert bsubbed.size == 1
            vec.append(bsubbed)
        vec = np.asarray(vec)
        ans = 0.5 * np.dot(self.Finv,vec)
        ans = dict(zip(self.params,ans.tolist()))
        res = {}
        for param in self.params:
            res[param] = self.fids[param] + ans[param]
        return res

class CinvUpdater(object):

    def __init__(self,cinvs,logdets,profile):
        self.cinvs = cinvs
        self.logdets = logdets

        u = profile.reshape((len(profile),1))
        v = u.copy()
        vT = v.T
        self.update_unnormalized = []
        self.det_unnormalized = []
        for Ainv in cinvs:
            self.update_unnormalized.append( np.dot(Ainv, np.dot(np.dot(u,vT), Ainv)) )
            self.det_unnormalized.append( np.dot(vT, np.dot(Ainv, u)) )

    def get_cinv(self,index,amplitude):

        det_update = 1.+(amplitude**2.)*self.det_unnormalized[index]
        cinv_updated = self.cinvs[index] - (amplitude**2.)*( self.update_unnormalized[index]/ det_update)
        return  cinv_updated , np.log(det_update)+self.logdets[index]

def eig_pow(C,exponent=-1,lim=1.e-8):
    e,v = np.linalg.eigh(C)
    emax = np.max(e)
    mask = e<emax*lim
    e[~mask] **= exponent
    e[mask]=0.
    return (v*e).dot(v.T)

def sm_update(Ainv, u, v=None):
    """Compute the value of (A + uv^T)^-1 given A^-1, u, and v.
    Uses the Sherman-Morrison formula."""

    v = u.copy() if v is None else v
    u = u.reshape((len(u),1))
    v = v.reshape((len(v),1))
    vT = v.T

    ldot = np.dot(vT, np.dot(Ainv, u))
    assert ldot.size==1
    det_update = 1.+ldot.ravel()[0]

    ans = Ainv - (np.dot(Ainv, np.dot(np.dot(u,vT), Ainv)) / det_update)
    return ans, det_update

def cov2corr_legacy(cov):
    # slow and stupid! see cov2corr
    d = np.diag(cov)
    stddev = np.sqrt(d)
    corr = cov.copy()*0.
    for i in range(cov.shape[0]):
        for j in range(cov.shape[0]):
            corr[i,j] = cov[i,j]/stddev[i]/stddev[j]
    return corr

def cov2corr(mat):
    diags = np.diagonal(mat).T
    xdiags = diags[:,None,...]
    ydiags = diags[None,:,...]
    corr = mat/np.sqrt(xdiags*ydiags)
    return corr

def correlated_hybrid_matrix(data_covmat,theory_covmat=None,theory_corr=None,cap=True,cap_off=0.99):
    """
    Given a diagonal matrix data_covmat,
    and a theory matrix theory_covmat or its correlation matrix theory_corr,
    produce a hybrid non-diagonal matrix that has the same diagonals as the data matrix
    but has correlation coefficient given by theory.
    """
    if theory_corr is None:
        assert theory_covmat is not None
        theory_corr = cov2corr(theory_covmat)
    r = theory_corr

    def _cap(imat,cval,csel):
        imat[imat>1] = 1
        imat[imat<-1] = -1
        imat[csel][imat[csel]>cval] = cval
        imat[csel][imat[csel]>-cval] = -cval

    d = data_covmat.copy()
    sel = np.where(~np.eye(d.shape[0],dtype=bool))
    d[sel] = 1
    dcorr = 1./cov2corr(d)
    if cap: _cap(r,cap_off,sel)
    fcorr = dcorr * r
    d[sel] = fcorr[sel]
    return d

class Stats(object):
    """
    An MPI enabled helper container for
    1) 1d measurements whose statistics need to be calculated
    2) 2d cumulative stacks

    where different MPI cores may be calculating different number
    of 1d measurements or 2d stacks.
    """

    def __init__(self,comm=None,root=0,loopover=None,tag_start=333):
        """
        comm - MPI.COMM_WORLD object
        tag_start - MPI comm tags start at this integer
        """

        if comm is not None:
            self.comm = comm
        else:
            from orphics.mpi import fakeMpiComm
            self.comm = fakeMpiComm()

        self.rank = self.comm.Get_rank()
        self.numcores = self.comm.Get_size()
        self.columns = {}

        self.vectors = {}
        self.little_stack = {}
        self.little_stack_count = {}
        self.tag_start = tag_start
        self.root = root
        if loopover is None:
            self.loopover = list(range(root+1,self.numcores))
        else:
            self.loopover = loopover

    def add_to_stats(self,label,vector,exclude=False):
        """
        Append the 1d vector to a statistic named "label".
        Create a new one if it doesn't already exist.
        """
        assert label!='stats', "Sorry, 'stats' is a forbidden label."

        vector = np.asarray(vector)

        if np.iscomplexobj(vector):
            print("ERROR: stats on complex arrays not supported. Do the real and imaginary parts separately.")
            raise TypeError

        if not(label in list(self.vectors.keys())):
            self.vectors[label] = []
            self.columns[label] = vector.shape
        if not(exclude):
            self.vectors[label].append(vector)

    def add_to_stack(self,label,arr,exclude=False):
        """
        This is just an accumulator, it can't track statisitics.
        Add arr to a cumulative stack named "label". Could be 2d arrays.
        Create a new one if it doesn't already exist.
        """
        assert label!='stats', "Sorry, 'stats' is a forbidden label."

        if np.iscomplexobj(arr):
            print("ERROR: stacking of complex arrays not supported. Stack the real and imaginary parts separately.")
            raise TypeError
        if not(label in list(self.little_stack.keys())):
            self.little_stack[label] = arr*0.
            self.little_stack_count[label] = 0
        if not(exclude):
            self.little_stack[label] += arr
            self.little_stack_count[label] += 1

    def get_stacks(self,verbose=True):
        """
        Collect from all MPI cores and calculate stacks.
        """

        if self.rank in self.loopover:

            for k,label in enumerate(self.little_stack.keys()):
                self.comm.send(self.little_stack_count[label], dest=self.root, tag=self.tag_start*3000+k)

            for k,label in enumerate(self.little_stack.keys()):
                send_dat = np.array(self.little_stack[label]).astype(np.float64)
                self.comm.Send(send_dat, dest=self.root, tag=self.tag_start*10+k)

        elif self.rank==self.root:
            self.stacks = {}
            self.stack_count = {}

            for k,label in enumerate(self.little_stack.keys()):
                self.stack_count[label] = self.little_stack_count[label]
                for core in self.loopover: #range(1,self.numcores):
                    if verbose: print("Waiting for core ", core , " / ", self.numcores)
                    data = self.comm.recv(source=core, tag=self.tag_start*3000+k)
                    self.stack_count[label] += data

            for k,label in enumerate(self.little_stack.keys()):
                self.stacks[label] = self.little_stack[label]
            for core in self.loopover: #range(1,self.numcores):
                if verbose: print("Waiting for core ", core , " / ", self.numcores)
                for k,label in enumerate(self.little_stack.keys()):
                    expected_shape = self.little_stack[label].shape
                    data_vessel = np.empty(expected_shape, dtype=np.float64)
                    self.comm.Recv(data_vessel, source=core, tag=self.tag_start*10+k)
                    self.stacks[label] += data_vessel

            for k,label in enumerate(self.little_stack.keys()):
                self.stacks[label] /= self.stack_count[label]

    def get_stats(self,verbose=True,skip_stats=False):
        """
        Collect from all MPI cores and calculate statistics for
        1d measurements.
        """

        if self.rank in self.loopover:
            for k,label in enumerate(self.vectors.keys()):
                self.comm.send(np.array(self.vectors[label]).shape[0], dest=self.root, tag=self.tag_start*2000+k)

            for k,label in enumerate(self.vectors.keys()):
                send_dat = np.array(self.vectors[label]).astype(np.float64)
                self.comm.Send(send_dat, dest=self.root, tag=self.tag_start+k)

        else:
            self.stats = {}
            self.numobj = {}
            for k,label in enumerate(self.vectors.keys()):
                self.numobj[label] = []
                self.numobj[label].append(np.array(self.vectors[label]).shape[0])
                for core in self.loopover: #range(1,self.numcores):
                    if verbose: print("Waiting for core ", core , " / ", self.numcores)
                    data = self.comm.recv(source=core, tag=self.tag_start*2000+k)
                    self.numobj[label].append(data)

            for k,label in enumerate(self.vectors.keys()):
                self.vectors[label] = np.array(self.vectors[label])
            for core in self.loopover: #range(1,self.numcores):
                if verbose: print("Waiting for core ", core , " / ", self.numcores)
                for k,label in enumerate(self.vectors.keys()):
                    expected_shape = (self.numobj[label][core],)+self.columns[label]
                    data_vessel = np.empty(expected_shape, dtype=np.float64)
                    self.comm.Recv(data_vessel, source=core, tag=self.tag_start+k)
                    try:
                        self.vectors[label] = np.append(self.vectors[label],data_vessel,axis=0)
                    except: # in case rank 0 has no data because it is not participating
                        self.vectors[label] = data_vessel

            if not(skip_stats):
                for k,label in enumerate(self.vectors.keys()):
                    self.stats[label] = get_stats(self.vectors[label])
            #self.vectors = {}

    def dump(self,path):
        for d,name in zip([self.vectors,self.stacks],['vectors','stack']):
            for key in d.keys():
                np.save(f"{path}/mstats_dump_{name}_{key}.npy",d[key])
        for key in self.stats.keys():
            for skey in self.stats[key].keys():
                np.savetxt(f"{path}/mstats_dump_stats_{key}_{skey}.txt",np.atleast_1d(self.stats[key][skey]))

def load_stats(path):
    import glob,re
    class S:
        pass
    s = S()
    s.vectors = {}
    s.stats = {}
    s.stacks = {}
    for sstr,sdict in zip(['vectors','stack'],[s.vectors,s.stacks]):
        vfiles = glob.glob(f"{path}/mstats_dump_{sstr}_*.npy")
        for vfile in vfiles:
            key = re.search(rf'mstats_dump_{sstr}_(.*?).npy', vfile).group(1)
            sdict[key] = np.load(f"{path}/mstats_dump_{sstr}_{key}.npy")

    vfiles = glob.glob(f"{path}/mstats_dump_stats_*_mean.txt")
    keys = []
    for vfile in vfiles:
        key = re.search(rf'mstats_dump_stats_(.*?)_mean.txt', vfile).group(1)
        keys.append(key)
    for key in keys:
        s.stats[key] = {}
        vfiles = glob.glob(f"{path}/mstats_dump_stats_{key}_*.txt")
        for vfile in vfiles:
            skey = re.search(rf'mstats_dump_stats_{key}_(.*?).txt', vfile).group(1)
            arr = np.loadtxt(f"{path}/mstats_dump_stats_{key}_{skey}.txt")
            if arr.size==1: arr = arr.ravel()[0]
            s.stats[key][skey] = arr
    return s

def npspace(minim,maxim,num,scale="lin"):
    if scale=="lin" or scale=="linear":
        return np.linspace(minim,maxim,num)
    elif scale=="log":
        return np.logspace(np.log10(minim),np.log10(maxim),num)

class bin2D(object):
    def __init__(self, modrmap, bin_edges):
        self.centers = (bin_edges[1:]+bin_edges[:-1])/2.
        self.digitized = np.digitize(modrmap.reshape(-1), bin_edges,right=True)
        self.bin_edges = bin_edges
        self.modrmap = modrmap

    def bin(self,data2d,weights=None,err=False,get_count=False,mask_nan=False):
        if weights is None:
            if mask_nan:
                keep = ~np.isnan(data2d.reshape(-1))
            else:
                keep = np.ones((data2d.size,),dtype=bool)
            count = np.bincount(self.digitized[keep])[1:-1]
            res = np.bincount(self.digitized[keep],(data2d).reshape(-1)[keep])[1:-1]/count
            if err:
                meanmap = self.modrmap.copy().reshape(-1) * 0
                for i in range(self.centers.size): meanmap[self.digitized==i] = res[i]
                std = np.sqrt(np.bincount(self.digitized[keep],((data2d-meanmap.reshape(self.modrmap.shape))**2.).reshape(-1)[keep])[1:-1]/(count-1)/count)
        else:
            count = np.bincount(self.digitized,weights.reshape(-1))[1:-1]
            res = np.bincount(self.digitized,(data2d*weights).reshape(-1))[1:-1]/count
        if get_count:
            assert not(err) # need to make more general
            return self.centers,res,count
        if err:
            assert not(get_count)
            return self.centers,res,std
        return self.centers,res

class bin1D:
    '''
    * Takes data defined on x0 and produces values binned on x.
    * Assumes x0 is linearly spaced and continuous in a domain?
    * Assumes x is continuous in a subdomain of x0.
    * Should handle NaNs correctly.
    '''

    def __init__(self, bin_edges):

        self.update_bin_edges(bin_edges)

    def update_bin_edges(self,bin_edges):

        self.bin_edges = bin_edges
        self.numbins = len(bin_edges)-1
        self.cents = (self.bin_edges[:-1]+self.bin_edges[1:])/2.

        self.bin_edges_min = self.bin_edges.min()
        self.bin_edges_max = self.bin_edges.max()

    def bin(self,ix,iy):
        x = ix.copy()
        y = iy.copy()
        # this just prevents an annoying warning (which is otherwise informative) everytime
        # all the values outside the bin_edges are nans
        y[x<self.bin_edges_min] = 0
        y[x>self.bin_edges_max] = 0

        # pretty sure this treats nans in y correctly, but should double-check!
        bin_means = binnedstat(x,y,bins=self.bin_edges,statistic=np.nanmean)[0]

        return self.cents,bin_means

def bin_in_annuli(data2d, modrmap, bin_edges):
    binner = bin2D(modrmap, bin_edges)
    return binner.bin(data2d)

def get_stats(binned_vectors):
    '''
    Returns statistics given an array-like object
    each row of which is an independent sample
    and each column holds possibly correlated
    binned values.

    The statistics are returned as a dictionary with
    keys:
    1. mean
    2. cov
    3. covmean
    4. err
    5. errmean
    6. corr
    '''
    # untested!

    arr = np.asarray(binned_vectors)

    N = arr.shape[0]
    ret = {}
    ret['mean'] = np.nanmean(arr,axis=0)
    ret['cov'] = np.cov(arr.transpose())
    ret['covmean'] = ret['cov'] / N
    if arr.shape[1]==1:
        ret['err'] = np.sqrt(ret['cov'])
    else:
        ret['err'] = np.sqrt(np.diagonal(ret['cov']))
    ret['errmean'] = ret['err'] / np.sqrt(N)

    # correlation matrix
    if arr.shape[1]==1:
        ret['corr'] = 1.
    else:

        # ???
        d = np.diag(ret['cov'])
        stddev = np.sqrt(d)
        ret['corr'] = ret['cov'] / stddev[:, None]
        ret['corr'] = ret['cov'] / stddev[None, :]
        np.clip(ret['corr'], -1, 1, out=ret['corr'])
        #ret['corr'] = cov2corr(ret['cov'])

    return ret

def timeit(method):

    def timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time()

        print('%r %2.2f sec' % \
              (method.__name__,te-ts))
        return result

    return timed


def pokerChips2(chips):
    moves1 = countPokerMoves(chips[:])
    moves2 = countPokerMoves(chips[::-1])
    if moves1 < moves2:
        return moves1
    return moves2

def countPokerMoves(chips):
    average = sum(chips) / len(chips)
    max_value = max(chips)
    moves = 0
    for i, chip in enumerate(chips):
        if chip == max_value:
            for j in range(len(chips)):
                if i+j == len(chips):
                    i = 0 - j
                temp = chips[i+j] - average
                if temp == 0:
                    continue
                else:
                    moves += 1
                chips[i + j] -= temp
                if i+j+1 == len(chips):
                    chips[0] += temp
                else:
                    chips[i+j+1] += temp
            break
    return moves

print(pokerChips2([13, 8, 28, 21, 30, 6, 13, 27, 23, 1]))


import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','project.settings')
import django
django.setup()
from datetime import datetime
from pytz import timezone
import json

from app1.models import pothole
from app1.serializers import PotholeSerializer

statistics_final = dict()
attributes = ['complaints_registered','complaints_completed']
final_list_registered = list()
final_list_completed = list()

def make_data():
    l = list()
    wardlist = list()

    for i in range(142):
        wardlist.append(i)

    pothole_overall_data = pothole.objects.filter(status='Recent')
    for i in pothole_overall_data:
        temp = str(i.uploaded_timestamp)
        key = temp[:10]
        if key not in statistics_final.keys():
            l.append(key)
            statistics_final[key] = dict.fromkeys(wardlist)
            l=[]

    for i,j in statistics_final.items():
        for k in j:
            j[k] = dict.fromkeys(attributes)

    for i,j in statistics_final.items():
        for k in j:
            j[k]['complaints_registered'] = 0
            j[k]['complaints_completed'] = 0

    for data in pothole_overall_data:
        for i,j in statistics_final.items():
            for k in j:
                if(k==data.ward_no):
                    temp = str(data.uploaded_timestamp)
                    key = temp[:10]
                    if(key == i):
                        j[k]['complaints_registered'] += 1

    pothole_completed_data = pothole.objects.filter(status='Completed')

    for data in pothole_completed_data:
        for i,j in statistics_final.items():
            for k in j:
                if(k==data.ward_no):
                    temp = str(data.uploaded_timestamp)
                    key = temp[:10]
                    if(key == i):
                        j[k]['complaints_completed'] += 1

def export_data_registered():

    global statistics_final
    global final_list_registered
    make_data()

    series=list()
    temp = dict()
    temp_2 = dict()
    for key,val in statistics_final.items():
        for i in val:
            temp['name'] = i
            temp_2['name'] = key
            temp_2['value'] = val[i]['complaints_registered']
            series.append(temp_2)
            temp_2 = dict()
            temp['series'] = series
            series = list()
            final_list_registered.append(temp)
            temp = dict()

    l = list()
    tempo = dict()
    for i in range(0,len(final_list_registered)-1):
        for j in range(i+1,len(final_list_registered)):
            if(final_list_registered[j]['name'] == final_list_registered[i]['name']):
                tempo['name'] = final_list_registered[j]['series'][0]['name']
                tempo['value'] = final_list_registered[j]['series'][0]['value']
                final_list_registered[i]['series'].append(tempo)
                tempo = dict()
                l.append(j)

    del final_list_registered[l[0]:l[len(l)-1]]

    del final_list_registered[(len(final_list_registered)-1)]
    l = list()

    res = []
    for i in range(len(final_list_registered)):
        for j in final_list_registered[i]['series']:
            if j not in res:
                res.append(j)
        final_list_registered[i]['series'] = res
        res = list()

    return final_list_registered

    statistics_final = dict()
    final_list_registered = list()

def export_registered_data_for_particular_ward(wd):

    #global final_list_registered
    global statistics_final
    f = export_data_registered()
    l = list()
    for i in range(len(f)):
        if(f[i]['name'] == wd):
            l.append(f[i])
            return l
            l=list()
            statistics_final = dict()

def export_data_completed():

    global statistics_final
    global final_list_completed
    make_data()

    series=list()
    temp = dict()
    temp_2 = dict()
    for key,val in statistics_final.items():
        for i in val:
            temp['name'] = i
            temp_2['name'] = key
            temp_2['value'] = val[i]['complaints_completed']
            series.append(temp_2)
            temp_2 = dict()
            temp['series'] = series
            series = list()
            final_list_completed.append(temp)
            temp = dict()

    l = list()
    tempo = dict()
    for i in range(0,len(final_list_completed)-1):
        for j in range(i+1,len(final_list_completed)):
            if(final_list_completed[j]['name'] == final_list_completed[i]['name']):
                tempo['name'] = final_list_completed[j]['series'][0]['name']
                tempo['value'] = final_list_completed[j]['series'][0]['value']
                final_list_completed[i]['series'].append(tempo)
                tempo = dict()
                l.append(j)

    del final_list_completed[l[0]:l[len(l)-1]]

    del final_list_completed[(len(final_list_completed)-1)]
    l = list()
    res = []
    for i in range(len(final_list_completed)):
        for j in final_list_completed[i]['series']:
            if j not in res:
                res.append(j)
        final_list_completed[i]['series'] = res
        res = list()

    return final_list_completed

    statistics_final = dict()
    final_list_completed = list()

def export_completed_data_for_particular_ward(wd):

    global statistics_final
    #global final_list_completed
    p = export_data_completed()
    l = list()
    for i in range(len(p)):
        if(p[i]['name'] == wd):
            l.append(p[i])
            return l
            l=list()
            statistics_final = dict()

def reg_vs_complete_particular(wd):

    global statistics_final
    #global final_list_registered
    #global final_list_completed
    f = export_data_registered()
    p = export_data_completed()

    merged_list = list()
    temp = dict()

    for i in range(len(f)):
        if(f[i]['name'] == wd):
            temp['name'] = 'Recent'
            temp['series'] = f[i]['series']
            merged_list.append(temp)
            temp = dict()

    for i in range(len(p)):
        if(p[i]['name'] == wd):
            temp['name'] = 'Completed'
            temp['series'] = p[i]['series']
            merged_list.append(temp)
            temp = dict()

    return merged_list

    merged_list = list()
    statistics_final = dict()

def piedata_for_particular(wd):

    pie_list = list()
    temp = dict()

    temp['name'] = 'Recent'
    temp['value'] = pothole.objects.filter(ward_no=wd,status='Recent').count()

    pie_list.append(temp)
    temp = dict()

    temp['name'] = 'Ongoing'
    temp['value'] = pothole.objects.filter(ward_no=wd, status='Ongoing').count()

    pie_list.append(temp)
    temp = dict()

    temp['name'] = 'Completed'
    temp['value'] = pothole.objects.filter(ward_no=wd, status='Completed').count()

    pie_list.append(temp)
    temp = dict()

    return pie_list

def piedata_for_all():

    pie_list = list()
    temp = dict()

    temp['name'] = 'Recent'
    temp['value'] = pothole.objects.filter(status='Recent').count()
    pie_list.append(temp)
    temp = dict()

    temp['name'] = 'Ongoing'
    temp['value'] = pothole.objects.filter(status='Ongoing').count()

    pie_list.append(temp)
    temp = dict()

    temp['name'] = 'Completed'
    temp['value'] = pothole.objects.filter(status='Completed').count()

    pie_list.append(temp)
    temp = dict()

    return pie_list


#!/usr/bin/env python3

import json
import urllib.request

class Command:
    arguments = []
    permissionLevel = 0
    permitExtraArgs = False
    manArgCheck = False
    defaultArgs = []
    callName = ""

    def __init__(self, bot):
        self.bot = bot
        self.bot.registerCommand(self)

    def on_call(self, event, *args):
        self.bot.do_command("help " + " ".join(args))

    def on_fail(self, event):
        self.bot.send_PrivMsg(event.user,
                              "The command must follow the syntax: /%s " % self.callName + " " + str(self.arguments))

    def checkPermissions(self, event, *args):
        pass

    def checkArgs(self, event, *args):
        pass

    def on_die(self, event):
        pass

class ping(Command):
    arguments = []
    permissionLevel = 2
    permitExtraArgs = False
    callName = "ping"
    defaultArgs = []

    def on_call(self, event, *args):
        self.bot.send_PrivMsg(event.user, "PONG")

class die(Command):
    arguments = []
    permissionLevel = 3
    permitExtraArgs = False
    callName = "die"
    defaultArgs = []

    def on_call(self, event, *args):
        self.bot.die()

class cnJoke(Command):
    arguments = []
    permissionLevel = 0
    permitExtraArgs = False
    callName = "cnjoke"
    defaultArgs = []

    def __init__(self, *args, **kwargs):
        Command.__init__(self, *args, **kwargs)

    def on_call(self, event, *args):
        x = urllib.request.urlopen("http://api.icndb.com/jokes/random")
        z = str(x.read(), "utf8")
        try:
            a = json.loads(z)
            self.bot.send_reply(event, a["value"]["joke"])
        except ValueError:
            print(z)

    def on_fail(self,event):
        self.bot.send_PrivMsg(event.user,
                              "You failed to type the command correctly puny human. \nChuck Norris will roundhouse kick you in the face shortly.")

class vote(Command):
    arguments = ["str", "str"]
    permissionLevel = -1
    permitExtraArgs = True
    manArgCheck = True
    defaultArgs = []
    callName = "vote"

    class poll():
        def __init__(self, *args):
            self.votes = {}
            self.voteids = {}
            self.voted = []
            for vote in args:
                self.votes[vote] = 0
                self.voteids[args.index(vote)] = vote

        def getVote(self, ID, data="name"):
            if data == "name":
                return self.voteids[ID]
            elif data == "score":
                return self.votes[self.voteids[ID]]

    def __init__(self, bot):
        Command.__init__(self, bot)
        self.polls = {}
        self.pollids = {}
        self.currentPoll = ""

    def createPoll(self, event, name, question, *options):
        self.currentPoll = name
        self.polls[name] = self.poll(*options)
        self.polls[len(self.polls.keys())] = name

        message = """%s has started a poll!""" % event.user+"\n"

        message += ("---%s---" % question)+"\n"

        for x in range(len(self.polls[name].voteids)):
            message += (str(x)+" :\t"+self.polls[name].getVote(x))+"\n"

        message += ("To vote, type in '%s:vote #', where '#' is your vote." % self.bot.callsign) + "\n"
        message += "---Note, you can't change your mind after you have voted, so think carefully."

        self.bot.send_PubMsg(message)

    def castVote(self, event, *args):
        if self.currentPoll == "":
            self.bot.send_PubMsg("Sorry %s, there is not vote running currently." % event.user)
            return

        curpoll = self.polls[self.currentPoll]
        if event.user in curpoll.voted:
            self.bot.send_PubMsg("Sorry %s, you can't vote again." % event.user)
            return

        curpoll = self.polls[self.currentPoll]
        curpoll.votes[curpoll.voteids[int(args[0])]] += 1
        alert = ("%s voted for '" + self.polls[self.currentPoll].voteids[int(args[0])] + "'!") % event.user

        curpoll.voted.append(event.user)
        self.bot.send_PubMsg(alert)

    def checkPermissions(self, event, *args):
        if len(args) == 0:
            return True
        base = args[0]
        if base in ("create", "results", "close"):
            if self.bot.getPermLevel(event) >= 1:
                return True
            else:
                return False
        else:
            if base.isdecimal() and (0 <= int(base) < len(self.polls[self.currentPoll].voteids)):
                return True
            else:
                return False

    def checkArgs(self, event, *args):
        if len(args) == 0:
            return 0
        return True

    def getResults(self, event, *args):
        if self.currentPoll == "":
            self.bot.sendPubMsg(event, "Sorry %s, there is not vote running currently." % event.user)
            return

        message = "---Current poll results---" + "\n"
        for ID in self.polls[self.currentPoll].voteids:
            x = self.polls[self.currentPoll].voteids[ID]
            message += ("    '%s': "+str(self.polls[self.currentPoll].votes[x])+" votes.") % x +"\n"
        message += "--------------------------"

    def closePoll(self, event, name):
        message = "The voting has now ended. The final results are:\n"
        for ID in self.polls[self.currentPoll].voteids:
            x = self.polls[self.currentPoll].voteids[ID]
            message += ("    '%s': "+str(self.polls[self.currentPoll].votes[x])+" votes.") % x

        message += "--------------------------"
        self.bot.send_PubMsg(message)

        self.currentPoll = ""

    def on_call(self, event, *args):
        print(args)
        if args[0] == "create":
            args = " ".join(args[1:]).split(", ")
            try:
                self.createPoll(event, args[0], args[1], *args[2:])
            except IndexError:
                pass

        elif args[0] == "results":
            self.getResults(event)
        elif args[0] == "close":
            try:
                self.closePoll(event, self.currentPoll)
            except NameError:
                self.bot.send_PubMsg("No poll to close.")
        else:
            if args[0].isdecimal():
                self.castVote(event, int(args[0]))

class help(Command):
    arguments = []
    permissionLevel = 0
    permitExtraArgs = False
    manArgCheck = False
    defaultArgs = []
    callName = "help"

    def on_call(self, event, *args):
        commands = []
        print(self.bot.commands)
        for x in list(self.bot.commands.items()):
            print(x)
            if x[1].permissionLevel <= self.bot.getPermLevel(event.user):
                commands.append(x[0])

        commands.sort()
        self.bot.send_PrivMsg(event.user, "---Commands avaliable to you---\n" +
                              "\n".join(self.bot.commandPrefix + cmd for cmd in commands) +
                              "\n-------------------------------")

class say(Command):
    arguments = []
    permissionLevel = 3
    permitExtraArgs = True
    manArgCheck = False
    defaultArgs = []
    callName = "say"

    def on_call(self, event, *args):
        self.bot.send_message(event.channel, " ".join(args))

class set(Command):
    arguments = ["str", "str"]
    permissionLevel = 3
    permitExtraArgs = False
    manArgCheck = False
    defaultArgs = []
    callName = "set"

    def on_call(self, event, *args):
        setattr(self.bot, *args)


from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from rest_framework import viewsets
from rest_framework.generics import UpdateAPIView, DestroyAPIView, CreateAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser
from nora.choices import nora
from nora.models import Menu, MenuUser, Ingredient
from nora.permissions import EmployeePermission, NoraPermission
from nora.serializers import MenuSerializer, SerializerSelectUserMenu, IngredientSerializer, CreateUserSerializer
import json

from nora.tasks import send_reminder_menu
from nora.utils.menu_manage import MenuAction
from nora.utils.utils import assign_menu

class MenuList(viewsets.ModelViewSet):
    """
    ViewSet for listing or retrieving all menu objects , list all menu objects
    with list function and detail specific menu with retrieve function, serializer
    class is instance
    """
    #permission_classes = (IsAuthenticated, EmployeePermission, NoraPermission)
    queryset = Menu.objects.all()
    serializer_class = MenuSerializer

    def list(self, request, *args, **kwargs):
        serializer = MenuSerializer(self.get_queryset(), many=True)
        return Response(serializer.data)

    def retrieve(self, request, *args, **kwargs):
        request_object = get_object_or_404(self.get_queryset(), pk=kwargs.get('pk'))
        serializer = MenuSerializer(request_object)
        return Response(serializer.data)

class MenuUserList(viewsets.ModelViewSet):
    """
    ViewSet for listing or retrieving all menu objects , list all menu objects
    with list function and detail specific menu with retrieve function, serializer
    class is instance
    """
    #permission_classes = (IsAuthenticated, EmployeePermission, NoraPermission)
    queryset = MenuUser.objects.all()
    serializer_class = SerializerSelectUserMenu

    def get_queryset(self):
        """
        function to obtain the list of menus selected by users, if the role of the user is nora
        all records are returned, if it is an employee only the menus of the employee
        in session are returned
        """
        data = MenuUser.objects.all() if self.request.user.profile is not None and self.request.user.profile.role == nora \
            else MenuUser.objects.filter(user=self.request.user)
        return data

    def list(self, request, *args, **kwargs):
        serializer = SerializerSelectUserMenu(self.get_queryset(), many=True)
        return Response(serializer.data)

    def retrieve(self, request, *args, **kwargs):
        request_object = get_object_or_404(self.get_queryset(), pk=kwargs.get('pk'))
        serializer = SerializerSelectUserMenu(request_object)
        return Response(serializer.data)

class CreateMenu(CreateAPIView):
    """
    class for the creation of menu, through the api,
    all the parameters such as name, ingredients and
    date are expected
    """
    #permission_classes = (IsAuthenticated, NoraPermission)
    serializer_class = MenuSerializer
    queryset = Menu.objects.all()

class UpdateMenu(UpdateAPIView):
    """
    class to update a specific menu, it is validated
    that on the indicated date there is no other menu with
    the same nam
    """
    #permission_classes = (IsAuthenticated, NoraPermission)
    serializer_class = MenuSerializer
    queryset = Menu.objects.all()

class DestroyMenu(DestroyAPIView):
    """
    Class for destroy or remove specific menu , receive one
    <pk:id> of menu selected for destroy
    """
    #permission_classes = (IsAuthenticated, NoraPermission)
    serializer_class = MenuSerializer
    queryset = Menu.objects.all()

class CreateMenuSelectedUser(CreateAPIView):
    """
    class to create a relationship between the user and a menu
    on a certain date for example the user mauricio chose the
    tray for July 27, 2021
    """
    #permission_classes = (IsAuthenticated, NoraPermission, EmployeePermission)
    serializer_class = SerializerSelectUserMenu
    queryset = MenuUser

class CreateIngredient(CreateAPIView):
    """
    Class for create instance of Ingredient Model
    """
    #permission_classes = (IsAuthenticated, NoraPermission)
    serializer_class = IngredientSerializer
    model = Ingredient

class CreateUserView(CreateAPIView):
    """
    class for create user in the system and create the profile of user, the profile user
    is create once the user is created, this is done by means of a signal,
    the relationship between user and profile is one to one
    """
    #permission_classes = (IsAuthenticated, NoraPermission)
    model = User
    serializer_class = CreateUserSerializer

class UploadMenuView(APIView):
    """
    Class for get json file with information about Menu
    """
    parser_classes = (MultiPartParser, )
    #permission_classes = (IsAuthenticated, NoraPermission)

    def post(self, request):
        """
        Method for get json file and process data , here create instance of Class
        Execute Workflow ,we need user_id and pin for create instance and execute all function
        """
        try:
            url = request.get_full_path()
            bytes_file = request.data.get('filename').read()
            data = json.loads(bytes_file.decode("UTF-8"))
            menu_information = list(data.values())[0]
            menu_actions = MenuAction(menu_information)
            menu_actions.save_menu()
            send_reminder_menu.delay(request.get_host())
        except UnicodeDecodeError:
            print("Could not open file:")
        return Response({'Processing data': []})

class ConfirmMenu(APIView):
    """
    Class for confirm menu via email , receive a token and filter in model
    UserToken if token in model , save menu user select information of instance
    model and destroy token
    """
    def get(self, request):
        token = request.GET['token']
        create = assign_menu(token)
        if create.get('response'):
            menu = create.get('menu')
            return Response({'Information': f'Menu {menu.menu.name} for {menu.date} was assigned'}, status=200)
        return Response({'error': 'some errors'}, status=400)


def part1(steps):
    cur = 0
    buf = [0]
    for i in range(1,2018):
        forward = (steps + cur) % len(buf)
        buf.insert(forward+1,i)
        cur = forward+1
    for i,item in enumerate(buf):
        if item == 2017:
            print("part 1:",buf[i+1])

def part2(steps):
    cur = 0
    n = 0
    for i in range(1,50000001):
        forward = (steps + cur) % i
        if forward == 0:
            n = i
        cur = forward + 1
    print("part 2:",n)

def main():
    steps = 363
    part1(steps)
    part2(steps)

if __name__ =="__main__":
  main()


"""
Imports the Python libraries needed to the project.
"""
import sys
from enum import Enum
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QMainWindow, QGridLayout, QAction, QGroupBox, QRadioButton, QSlider, \
    QLabel, QPushButton, QApplication, QFileDialog, QColorDialog, QMessageBox
from PyQt5.QtGui import QIcon, QImage, QPen, QPainter, QColor
from PyQt5.QtCore import Qt, QPoint, QSize
from qtpy import QtCore, QtGui

"""
Defines an enum which represents the drawing modes.
"""
class DrawMode(Enum):
    Point = 1
    Line = 2

"""
Class inherited from a QWidget which initializes the toolbox on the left of the application.
"""
class ToolBox(QWidget):
    def __init__(self):
        super().__init__()

        """
        Sets a fix width so the toolbox doesn't get too thin or too large.
        """
        self.setMaximumWidth(150)
        self.setMinimumWidth(150)

        """
        Sets a vertical box layout as the default layout.
        """
        self.vbox = QVBoxLayout()
        self.setLayout(self.vbox)

"""
Class inherited from a QWidget which represents our drawing area.
"""
class DrawingArea(QWidget):
    def __init__(self):
        super().__init__()

        """
        Initializes two images which will be used later to resize or undo.
        """
        self.resizeSavedImage = QImage(0, 0, QImage.Format_RGB32)
        self.savedImage = QImage(0, 0, QImage.Format_RGB32)

        """
        Sets our default image with the right size filled in white.
        """
        self.image = QImage(self.width(), self.height(), QImage.Format_RGB32)
        self.image.fill(Qt.white)

        """
        Sets the draw default settings, such as the brush size, the color or the style.
        """
        self.drawing = False
        self.brushSize = 1
        self.brushColor = Qt.black
        self.brushStyle = Qt.SolidLine
        self.brushCap = Qt.RoundCap
        self.brushJoin = Qt.RoundJoin
        self.drawMode = DrawMode.Point

        """
        Initializes a point that we'll use later to draw lines.
        Sets a minimum width so the image width is never equal to 0.
        """
        self.lastPoint = QPoint()
        self.setMinimumWidth(150)

    """
    Method called when the widget is resized.
    The image needs to be scaled with the new size or problems will occur.
    """
    def resizeEvent(self, event):
        self.image = self.image.scaled(self.width(), self.height())

    """
    Method called when a button of the mouse is pressed.
    Only the left click is interesting here.
    """
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            """
            If the draw mode is set to Point we draw at the position of the mouse.
            """
            if self.drawMode == DrawMode.Point:
                painter = QPainter(self.image)  # object which allows drawing to take place on an image
                painter.setPen(QPen(self.brushColor, self.brushSize, self.brushStyle, self.brushCap, self.brushJoin))
                painter.drawPoint(event.pos())
                self.drawing = True  # we are now entering draw mode
                self.lastPoint = event.pos()  # new point is saved as last point
            elif self.drawMode == DrawMode.Line:
                """
                Else if the draw mode is set to Line we save a first point and
                draw a line when a second click is done.
                """
                if self.lastPoint == QPoint():
                    self.lastPoint = event.pos()
                else:
                    painter = QPainter(self.image)  # object which allows drawing to take place on an image
                    painter.setPen(QPen(self.brushColor, self.brushSize, self.brushStyle, self.brushCap, self.brushJoin))
                    painter.drawLine(self.lastPoint, event.pos())
                    self.lastPoint = QPoint()

            """
            Tells the library to update the widget, as something might have been drawn.
            """
            self.update()

    """
    Method called when the mouse is moved.
    Here it is only used when the draw mode is set to Point and if the user
    keeps drawing while moving the mouse.
    """
    def mouseMoveEvent(self, event):
        if (event.buttons() & Qt.LeftButton) & self.drawing & (self.drawMode == DrawMode.Point):
            painter = QPainter(self.image)  # object which allows drawing to take place on an image
            # allows the selection of brush colour, brush size, line type, cap type, join type
            painter.setPen(QPen(self.brushColor, self.brushSize, self.brushStyle, self.brushCap, self.brushJoin))
            painter.drawLine(self.lastPoint, event.pos())
            self.lastPoint = event.pos()
            self.update()

    """
    Method called when a button of the mouse is released.
    Here again we only are interested about the left click.
    """
    def mouseReleaseEvent(self, event):
        if event.button == Qt.LeftButton:
            """
            Saves the image before the modification.
            """
            self.savedImage = self.resizeSavedImage
            self.resizeSavedImage = self.image
            self.drawing = False

    """
    Method called when a painting event occurs.
    """
    def paintEvent(self, event):
        canvasPainter = QPainter(self)
        canvasPainter.drawImage(self.rect(), self.image, self.image.rect())

"""
Main class inherited from a QMainWindow which is the main window of the program.
"""
class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        """
        Sets some default settings of the window such as the name, the size and the icon.
        """
        self.setWindowTitle("QPaint")
        self.setGeometry(100, 100, 800, 600)  # top, left, width, height
        self.setWindowIcon(QIcon("./icons/paint-brush.png"))

        """
        Initializes layouts and call the methods that will initialize
        specific parts of the window.
        """
        self.grid = QGridLayout()
        self.box = ToolBox()
        self.imageArea = DrawingArea()
        self.setBrushSlider()
        self.setBrushStyle()
        self.setBrushCap()
        self.setBrushJoin()
        self.setColorChanger()

        """
        Creates a grid with the toolbox and the drawing area,
        which we both set in a widget which is the central widget of our window.
        """
        self.grid.addWidget(self.box, 0, 0, 1, 1)
        self.grid.addWidget(self.imageArea, 0, 1, 1, 6)
        win = QWidget()
        win.setLayout(self.grid)
        self.setCentralWidget(win)

        """
        Creates the menu bar of our window with 3 menus.
        """
        # menus
        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu(" File")  # the space is required as "File" is reserved in Mac
        drawMenu = mainMenu.addMenu("Draw")
        helpMenu = mainMenu.addMenu("Help")

        """
        Creates the Save action and adds it to the "File" menu.
        """
        saveAction = QAction(QIcon("./icons/save.png"), "Save", self)
        saveAction.setShortcut("Ctrl+S")
        fileMenu.addAction(saveAction)
        """
        When the menu option is selected or the shortcut is used the save action is triggered.
        """
        saveAction.triggered.connect(self.save)

        """
        Creates the Open action and adds it to the "File" menu.
        """
        openAction = QAction(QIcon("./icons/open.png"), "Open", self)
        openAction.setShortcut("Ctrl+O")
        fileMenu.addAction(openAction)
        """
        When the menu option is selected or the shortcut is used the open action is triggered.
        """
        openAction.triggered.connect(self.open)

        """
        Creates the Undo action and adds it to the "File" menu.
        """
        undoAction = QAction(QIcon("./icons/undo.png"), "Undo", self)
        undoAction.setShortcut("Ctrl+Z")
        fileMenu.addAction(undoAction)
        """
        When the menu option is selected or the shortcut is used the undo action is triggered.
        """
        undoAction.triggered.connect(self.undo)

        """
        Creates the Clear action and adds it to the "File" menu.
        """
        clearAction = QAction(QIcon("./icons/clear.png"), "Clear", self)
        clearAction.setShortcut("Ctrl+C")
        fileMenu.addAction(clearAction)
        """
        When the menu option is selected or the shortcut is used the clear action is triggered.
        """
        clearAction.triggered.connect(self.clear)

        """
        Creates the Exit action and adds it to the "File" menu.
        """
        exitAction = QAction(QIcon("./icons/exit.png"), "Exit", self)
        exitAction.setShortcut("Ctrl+Q")
        fileMenu.addAction(exitAction)
        """
        When the menu option is selected or the shortcut is used the exit action is triggered.
        """
        exitAction.triggered.connect(self.exitProgram)

        """
        Creates the Point action and adds it to the "Draw" menu.
        """
        self.pointAction = QAction("Point", self, checkable=True)
        self.pointAction.setShortcut("Ctrl+P")
        self.pointAction.setChecked(True)
        drawMenu.addAction(self.pointAction)
        """
        When the menu option is selected or the shortcut is used the change draw mode action is triggered.
        """
        self.pointAction.triggered.connect(lambda: self.changeDrawMode(self.pointAction))

        """
        Creates the Line action and adds it to the "Draw" menu.
        """
        self.lineAction = QAction("Line", self, checkable=True)
        self.lineAction.setShortcut("Ctrl+L")
        drawMenu.addAction(self.lineAction)
        """
        When the menu option is selected or the shortcut is used the change draw mode action is triggered.
        """
        self.lineAction.triggered.connect(lambda: self.changeDrawMode(self.lineAction))

        """
        Creates the About action and adds it to the "Help" menu.
        """
        aboutAction = QAction(QIcon("./icons/about.png"), "About", self)
        aboutAction.setShortcut("Ctrl+I")
        helpMenu.addAction(aboutAction)
        """
        When the menu option is selected or the shortcut is used the about action is triggered.
        """
        aboutAction.triggered.connect(self.about)

        """
        Creates the Help action and adds it to the "Help" menu.
        """
        helpAction = QAction(QIcon("./icons/help.png"), "Help", self)
        helpAction.setShortcut("Ctrl+H")
        helpMenu.addAction(helpAction)
        """
        When the menu option is selected or the shortcut is used the help action is triggered.
        """
        helpAction.triggered.connect(self.help)

        """
        Updates the widget with the default settings.
        """
        self.imageArea.update()

    """
    Method which changes the draw mode depending on which action has been called.
    """
    def changeDrawMode(self, check):
        if check.text() == "Point":
            self.pointAction.setChecked(True)
            self.lineAction.setChecked(False)
            self.imageArea.drawMode = DrawMode.Point
        elif check.text() == "Line":
            self.pointAction.setChecked(False)
            self.lineAction.setChecked(True)
            self.imageArea.drawMode = DrawMode.Line
        """
        Resets the saved point.
        """
        self.imageArea.lastPoint = QPoint()

    """
    Initializes the layout on which we can change the Join setting of the brush.
    """
    def setBrushJoin(self):
        self.brush_join_type = QGroupBox("Brush join")
        self.brush_join_type.setMaximumHeight(100)

        """
        Creates the radio buttons to let us make a choice between these 3 options.
        Each one is connected to a method which will change the setting depending on which
        button is clicked.
        """
        self.joinBtn1 = QRadioButton("Round")
        self.joinBtn1.clicked.connect(lambda: self.changeBrushJoin(self.joinBtn1))
        self.joinBtn2 = QRadioButton("Miter")
        self.joinBtn2.clicked.connect(lambda: self.changeBrushJoin(self.joinBtn2))
        self.joinBtn3 = QRadioButton("Bevel")
        self.joinBtn3.clicked.connect(lambda: self.changeBrushJoin(self.joinBtn3))

        """
        Sets a default value.
        Adds the buttons to the layout which is added to the parent box.
        """
        self.joinBtn1.setChecked(True)
        qv = QVBoxLayout()
        qv.addWidget(self.joinBtn1)
        qv.addWidget(self.joinBtn2)
        qv.addWidget(self.joinBtn3)
        self.brush_join_type.setLayout(qv)
        self.box.vbox.addWidget(self.brush_join_type)

    """
    Initializes the layout on which we can change the Type setting of the brush.
    """
    def setBrushStyle(self):
        self.brush_line_type = QGroupBox("Brush style")
        self.brush_line_type.setMaximumHeight(100)

        """
        Creates the radio buttons to let us make a choice between these 3 options.
        Each one is connected to a method which will change the setting depending on which
        button is clicked.
        """
        self.styleBtn1 = QRadioButton(" Solid")
        self.styleBtn1.setIcon(QIcon("./icons/solid.png"))
        self.styleBtn1.setIconSize(QSize(32, 64))
        self.styleBtn1.clicked.connect(lambda: self.changeBrushStyle(self.styleBtn1))

        self.styleBtn2 = QRadioButton(" Dash")
        self.styleBtn2.setIcon(QIcon("./icons/dash.png"))
        self.styleBtn2.setIconSize(QSize(32, 64))
        self.styleBtn2.clicked.connect(lambda: self.changeBrushStyle(self.styleBtn2))

        self.styleBtn3 = QRadioButton(" Dot")
        self.styleBtn3.setIcon(QIcon("./icons/dot.png"))
        self.styleBtn3.setIconSize(QSize(32, 64))
        self.styleBtn3.clicked.connect(lambda: self.changeBrushStyle(self.styleBtn3))

        """
        Sets a default value.
        Adds the buttons to the layout which is added to the parent box.
        """
        self.styleBtn1.setChecked(True)
        qv = QVBoxLayout()
        qv.addWidget(self.styleBtn1)
        qv.addWidget(self.styleBtn2)
        qv.addWidget(self.styleBtn3)
        self.brush_line_type.setLayout(qv)
        self.box.vbox.addWidget(self.brush_line_type)

    """
    Initializes the layout on which we can change the Cap setting of the brush.
    """
    def setBrushCap(self):
        self.brush_cap_type = QGroupBox("Brush cap")
        self.brush_cap_type.setMaximumHeight(100)

        """
        Creates the radio buttons to let us make a choice between these 3 options.
        Each one is connected to a method which will change the setting depending on which
        button is clicked.
        """
        self.capBtn1 = QRadioButton("Square")
        self.capBtn1.clicked.connect(lambda: self.changeBrushCap(self.capBtn1))
        self.capBtn2 = QRadioButton("Flat")
        self.capBtn2.clicked.connect(lambda: self.changeBrushCap(self.capBtn2))
        self.capBtn3 = QRadioButton("Round")
        self.capBtn3.clicked.connect(lambda: self.changeBrushCap(self.capBtn3))

        """
        Sets a default value.
        Adds the buttons to the layout which is added to the parent box.
        """
        self.capBtn3.setChecked(True)
        qv = QVBoxLayout()
        qv.addWidget(self.capBtn1)
        qv.addWidget(self.capBtn2)
        qv.addWidget(self.capBtn3)
        self.brush_cap_type.setLayout(qv)
        self.box.vbox.addWidget(self.brush_cap_type)

    """
    Method which changes the Join setting of the brush depending
    on which button has been previously clicked.
    """
    def changeBrushJoin(self, btn):
        if btn.text() == "Round":
            if btn.isChecked():
                self.imageArea.brushJoin = Qt.RoundJoin
        if btn.text() == "Miter":
            if btn.isChecked():
                self.imageArea.brushJoin = Qt.MiterJoin
        if btn.text() == "Bevel":
            if btn.isChecked():
                self.imageArea.brushJoin = Qt.BevelJoin

    """
    Method which changes the Cap setting of the brush depending
    on which button has been previously clicked.
    """
    def changeBrushCap(self, btn):
        if btn.text() == "Square":
            if btn.isChecked():
                self.imageArea.brushCap = Qt.SquareCap
        if btn.text() == "Flat":
            if btn.isChecked():
                self.imageArea.brushCap = Qt.FlatCap
        if btn.text() == "Round":
            if btn.isChecked():
                self.imageArea.brushCap = Qt.RoundCap

    """
    Method which changes the Type setting of the brush depending
    on which button has been previously clicked.
    """
    def changeBrushStyle(self, btn):
        if btn.text() == " Solid":
            if btn.isChecked():
                self.imageArea.brushStyle = Qt.SolidLine
        if btn.text() == " Dash":
            if btn.isChecked():
                self.imageArea.brushStyle = Qt.DashLine
        if btn.text() == " Dot":
            if btn.isChecked():
                self.imageArea.brushStyle = Qt.DotLine

    """
    Initializes the layout on which we can change the brush size.
    """
    def setBrushSlider(self):
        self.groupBoxSlider = QGroupBox("Brush size")
        self.groupBoxSlider.setMaximumHeight(100)

        """
        Sets a vertical slider with a min and a max value.
        """
        self.brush_thickness = QSlider(Qt.Horizontal)
        self.brush_thickness.setMinimum(1)
        self.brush_thickness.setMaximum(40)
        self.brush_thickness.valueChanged.connect(self.sizeSliderChange)

        """
        Sets a label to display the size of the brush.
        """
        self.brushSizeLabel = QLabel()
        self.brushSizeLabel.setText("%s px" % self.imageArea.brushSize)

        """
        Adds the buttons to the layout which is added to the parent box.
        """
        qv = QVBoxLayout()
        qv.addWidget(self.brush_thickness)
        qv.addWidget(self.brushSizeLabel)
        self.groupBoxSlider.setLayout(qv)

        self.box.vbox.addWidget(self.groupBoxSlider)

    """
    Method which changes the brush size depending on the value
    sent from the slider.
    """
    def sizeSliderChange(self, value):
        self.imageArea.brushSize = value
        self.brushSizeLabel.setText("%s px" % value)

    """
    Initializes the layout on which we can change the color of the brush.
    """
    def setColorChanger(self):
        self.groupBoxColor = QGroupBox("Color")
        self.groupBoxColor.setMaximumHeight(100)

        """
        Initializes a color and sets a button with this color as background.
        """
        self.col = QColor(0, 0, 0)
        self.brush_colour = QPushButton()
        self.brush_colour.setFixedSize(60, 60)
        self.brush_colour.clicked.connect(self.showColorDialog)
        self.brush_colour.setStyleSheet("background-color: %s" % self.col.name())
        self.box.vbox.addWidget(self.brush_colour)

        """
        Adds the buttons to the layout which is added to the parent box.
        """
        qv = QVBoxLayout()
        qv.addWidget(self.brush_colour)
        self.groupBoxColor.setLayout(qv)

        self.box.vbox.addWidget(self.groupBoxColor)

    """
    Method which displays a color picker and sets the brush color.
    """
    def showColorDialog(self):
        self.col = QColorDialog.getColor()
        if self.col.isValid():
            self.brush_colour.setStyleSheet("background-color: %s" % self.col.name())
            self.imageArea.brushColor = self.col

    """
    Method called when the main window is resized.
    Scales the image area with the new size.
    """
    def resizeEvent(self, a0: QtGui.QResizeEvent):
        if self.imageArea.resizeSavedImage.width() != 0:
            self.imageArea.image = self.imageArea.resizeSavedImage.scaled(self.imageArea.width(), self.imageArea.height(), QtCore.Qt.IgnoreAspectRatio)
        self.imageArea.update()

    """
    Method called when we execute the save action.
    It opens a file dialog in which the user can choose the path of where
    he would like to save the current image.
    """
    def save(self):
        filePath, _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG(*.png);;JPG(*.jpg *.jpeg);;All Files (*.*)")
        if filePath == "":
            return
        self.imageArea.image.save(filePath)

    """
    Method called when we execute the open action.
    It opens a file dialog in which the user can choose the path of the image
    he wants to open in the program.
    """
    def open(self):
        filePath, _ = QFileDialog.getOpenFileName(self, "Open Image", "", "PNG(*.png);;JPG(*.jpg *.jpeg);;All Files (*.*)")
        if filePath == "":
            return
        with open(filePath, 'rb') as f:
            content = f.read()

        """
        Loads the data from the file to the image.
        Scales it and updates the drawing area.
        """
        self.imageArea.image.loadFromData(content)
        self.imageArea.image = self.imageArea.image.scaled(self.imageArea.width(), self.imageArea.height(), QtCore.Qt.IgnoreAspectRatio)
        self.imageArea.resizeSavedImage = self.imageArea.image  # saves the image for later resizing
        self.imageArea.update()

    """
    Method called when we execute the undo action.
    The user can go back to the previous state of the image
    before the last modification he made.
    """
    def undo(self):
        copyImage = self.imageArea.image
        if self.imageArea.savedImage.width() != 0:
            """
            If the saved image exists, we set the actual image to the saved one scaled to the right size.
            """
            self.imageArea.image = self.imageArea.savedImage.scaled(self.imageArea.width(), self.imageArea.height(), QtCore.Qt.IgnoreAspectRatio)
        else:
            """
            If no saved image exist we just clean the current one.
            """
            self.imageArea.image = QImage(self.imageArea.width(), self.imageArea.height(), QImage.Format_RGB32)
            self.imageArea.image.fill(Qt.white)
        """
        Sets the saved image as the copy from the screen.
        """
        self.imageArea.savedImage = copyImage
        self.imageArea.update()

    """
    Method called when we execute the clear action.
    It fills the image in white and updates it.
    """
    def clear(self):
        self.imageArea.image.fill(Qt.white)
        self.imageArea.update()

    """
    Method called when we execute the exit action.
    Exits the program.
    """
    def exitProgram(self):
        QtCore.QCoreApplication.quit()

    """
    Method called when we execute the about action.
    Displays a message about the program.
    """
    def about(self):
        QMessageBox.about(self, "About QPaint",
                          "<p>This Qt Application is a basic paint program made with PyQt. "
                          "You can draw something by yourself and then save it as a file. "
                          "PNG and JPG files can also be opened and edited.</p>")

    """
    Method called when we execute the help action.
    Displays a help message about the program.
    """
    def help(self):
        msg = QMessageBox()
        msg.setText("Help"
                    "<p>Welcome on QPaint.</p> "
                    "<p>On the left side of the screen you can see a toolbox on which you have different boxes. "
                    "Each of these boxes contains buttons or sliders which allow you to customize the brush you want to"
                    "draw with.</p>"
                    "<p>The right size of the screen is the drawing area, where you can draw.</p> "
                    "<p>The program also has different menus you can see at the top of the window. "
                    "<p>These menus allow you to open a file, save your image, clean it, or even exit the program.</p>"
                    "We hope you will enjoy your experience."
                    "If you encounter any difficulty or need any information "
                    "you can send an email to nicolas.guillon@epitech.eu.</p>")
        msg.setWindowTitle("Help")
        msg.move(self.width() / 2, self.height() / 2)
        msg.exec_()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    """
    Starts a new instance of the main window.
    """
    window = Window()
    window.show()
    app.exec()


from collections import deque

def solution(rectangle, characterX, characterY, itemX, itemY):
    board = [[-1] * 102 for _ in range(102)]
    visited = [[False] * 102 for _ in range(102)]
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]
    q = deque([(characterX, characterY)])
    visited[characterX][characterY] = True
    ans = 0

    for rect in rectangle:
        x1, y1, x2, y2 = map(lambda x: x - 1, rect)
        for x in range(x1, x2 * 2 + 1):
            for y in range(y1, y2 * 2 + 1):
                if board[x][y] == -1:
                    board[x][y] = 1
                elif board[x][y] == 0:
                    board[x][y] = 0
                if x1 < x < x2 * 2 and y1 < y < y2 * 2:
                    board[x][y] = 0

    for i in range(102):
        for j in range(102):
            print(board[i][j], end=' ')
        print()

    for x in range(102):
        for y in range(102):
            if board[x][y] == 1:
                q.append((x, y))

    while q:
        x, y = q.popleft()
        if x == itemX and y == itemY:
            break

        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]

            if 0 <= nx <= 101 and 0 <= ny <= 101 and not visited[nx][ny] and board[x][y] == 1:
                q.append((nx, ny))
                visited[nx][ny] = True
                ans += 1

    return ans // 2

rect = [[1,1,7,4],[3,2,5,5],[4,3,6,9],[2,6,8,8]]
cx = 1
cy = 3
ix = 7
iy = 8
print(solution(rect, cx, cy, ix, iy))


import logging
from ncservice.ncDeviceOps.nc_device_ops import NcDeviceOps
from ncservice.ncDeviceOps.task_report import TaskReport
from ncservice.ncDeviceOps.threaded.base_thread_class import BaseThreadClass

logger = logging.getLogger('main.{}'.format(__name__))
extra = {'signature': '---SIGNATURE-NOT-SET---'}

class GetConfigs(BaseThreadClass):
    def __init__(self, service):
        super().__init__()
        self.service = service
        self.results = TaskReport(service)

    def get_configs(self):
        logger.debug('Requesting thread queue for _th_read_configs', extra=extra)
        enclosure_queue = self.create_thread_queue(
            self._th_read_configs
        )

        for device in self.service:
            enclosure_queue.put(device)

        enclosure_queue.join()
        return self.results

    def _th_read_configs(self, tid, queue):
        while True:
            target_device = queue.get()
            device_name = target_device['device']
            host = target_device['host']
            port = target_device.get('ncport', 830)

            session = NcDeviceOps(host, port=port, tid=tid)
            current_config = session.nc_get_configs()
            if current_config is not None:
                self.results.set_device_config_data('original_running_configs', device_name, current_config)
                self.results.set_device_config_data('current_running_configs', device_name, current_config)
                self.results.set_service_result(device_name, 'SUCCESS')
            else:
                logger.error('TID-{}: Unable to retrieve config for device: {}'
                             .format(tid, device_name), extra=extra)
                queue.task_done()
                continue

            session.close_session()
            queue.task_done()


import numpy as np
from typing import Dict, Optional, Tuple

from autoconf import cached_property

from autoarray.inversion.mappers.abstract import AbstractMapper
from autoarray.inversion.mappers.abstract import PixSubWeights
from autoarray.structures.arrays.uniform_2d import Array2D
from autoarray.structures.grids.uniform_2d import Grid2D

from autoarray.numba_util import profile_func
from autoarray.structures.grids import grid_2d_util

class MapperRectangularNoInterp(AbstractMapper):
    def __init__(
        self,
        source_grid_slim: Grid2D,
        source_pixelization_grid,
        data_pixelization_grid: Grid2D = None,
        hyper_image: Array2D = None,
        profiling_dict: Optional[Dict] = None,
    ):
        """
        To understand a `Mapper` one must be familiar `Pixelization` objects and the `pixelization` package, where
        the following four grids are explained: `data_grid_slim`, `source_grid_slim`, `data_pixelization_grid` and
        `source_pixelization_grid`. If you are not familiar with these grids, read the docstrings of the
        `pixelization` package first.

        A `Mapper` determines the mappings between the masked data grid's pixels (`data_grid_slim` and
        `source_grid_slim`) and the pixelization's pixels (`data_pixelization_grid` and `source_pixelization_grid`).

        The 1D Indexing of each grid is identical in the `data` and `source` frames (e.g. the transformation does not
        change the indexing, such that `source_grid_slim[0]` corresponds to the transformed value
        of `data_grid_slim[0]` and so on).

        A mapper therefore only needs to determine the index mappings between the `grid_slim` and `pixelization_grid`,
        noting that associations are made by pairing `source_pixelization_grid` with `source_grid_slim`.

        Mappings are represented in the 2D ndarray `pix_indexes_for_sub_slim_index`, whereby the index of
        a pixel on the `pixelization_grid` maps to the index of a pixel on the `grid_slim` as follows:

        - pix_indexes_for_sub_slim_index[0, 0] = 0: the data's 1st sub-pixel maps to the pixelization's 1st pixel.
        - pix_indexes_for_sub_slim_index[1, 0] = 3: the data's 2nd sub-pixel maps to the pixelization's 4th pixel.
        - pix_indexes_for_sub_slim_index[2, 0] = 1: the data's 3rd sub-pixel maps to the pixelization's 2nd pixel.

        The second dimension of this array (where all three examples above are 0) is used for cases where a
        single pixel on the `grid_slim` maps to multiple pixels on the `pixelization_grid`. For example, a
        `Delaunay` triangulation, where every `grid_slim` pixel maps to three Delaunay pixels (the corners of the
        triangles) with varying interpolation weights .

        For a `Rectangular` pixelization every pixel in the masked data maps to only one pixel, thus the second
        dimension of `pix_indexes_for_sub_slim_index` is always of size 1.

        The mapper allows us to create a mapping matrix, which is a matrix representing the mapping between every
        unmasked data pixel annd the pixels of a pixelization. This matrix is the basis of performing an `Inversion`,
        which reconstructs the data using the `source_pixelization_grid`.

        Parameters
        ----------
        source_grid_slim
            A 2D grid of (y,x) coordinates associated with the unmasked 2D data after it has been transformed to the
            `source` reference frame.
        source_pixelization_grid
            The 2D grid of (y,x) centres of every pixelization pixel in the `source` frame.
        data_pixelization_grid
            The sparse set of (y,x) coordinates computed from the unmasked data in the `data` frame. This has a
            transformation applied to it to create the `source_pixelization_grid`.
        hyper_image
            An image which is used to determine the `data_pixelization_grid` and therefore adapt the distribution of
            pixels of the Delaunay grid to the data it discretizes.
        profiling_dict
            A dictionary which contains timing of certain functions calls which is used for profiling.
        """
        super().__init__(
            source_grid_slim=source_grid_slim,
            source_pixelization_grid=source_pixelization_grid,
            data_pixelization_grid=data_pixelization_grid,
            hyper_image=hyper_image,
            profiling_dict=profiling_dict,
        )

    @property
    def shape_native(self) -> Tuple[int, int]:
        return self.source_pixelization_grid.shape_native

    @cached_property
    @profile_func
    def pix_sub_weights(self) -> "PixSubWeights":
        """
        Computes the following three quantities describing the mappings between of every sub-pixel in the masked data
        and pixel in the `Rectangular` pixelization.

        - `pix_indexes_for_sub_slim_index`: the mapping of every data pixel (given its `sub_slim_index`)
        to pixelization pixels (given their `pix_indexes`).

        - `pix_sizes_for_sub_slim_index`: the number of mappings of every data pixel to pixelization pixels.

        - `pix_weights_for_sub_slim_index`: the interpolation weights of every data pixel's pixelization
        pixel mapping

        These are packaged into the class `PixSubWeights` with attributes `mappings`, `sizes` and `weights`.

        The `sub_slim_index` refers to the masked data sub-pixels and `pix_indexes` the pixelization pixel indexes,
        for example:

        - `pix_indexes_for_sub_slim_index[0, 0] = 2`: The data's first (index 0) sub-pixel maps to the Rectangular
        pixelization's third (index 2) pixel.

        - `pix_indexes_for_sub_slim_index[2, 0] = 4`: The data's third (index 2) sub-pixel maps to the Rectangular
        pixelization's fifth (index 4) pixel.

        The second dimension of the array `pix_indexes_for_sub_slim_index`, which is 0 in both examples above, is used
        for cases where a data pixel maps to more than one pixelization pixel (for example a `Delaunay` triangulation
        where each data pixel maps to 3 Delaunay triangles with interpolation weights). The weights of multiple mappings
        are stored in the array `pix_weights_for_sub_slim_index`.

        For a Rectangular pixelizaiton each data sub-pixel maps to a single pixelization pixel, thus the second
        dimension of the array `pix_indexes_for_sub_slim_index` 1 and all entries in `pix_weights_for_sub_slim_index`
        are equal to 1.0.
        """
        mappings = grid_2d_util.grid_pixel_indexes_2d_slim_from(
            grid_scaled_2d_slim=self.source_grid_slim,
            shape_native=self.source_pixelization_grid.shape_native,
            pixel_scales=self.source_pixelization_grid.pixel_scales,
            origin=self.source_pixelization_grid.origin,
        ).astype("int")

        mappings = mappings.reshape((len(mappings), 1))

        return PixSubWeights(
            mappings=mappings.reshape((len(mappings), 1)),
            sizes=np.ones(len(mappings), dtype="int"),
            weights=np.ones((len(self.source_grid_slim), 1), dtype="int"),
        )


#블로그 참고
#메모리 :33324 시간 :644
import sys
input=sys.stdin.readline
import heapq
n=int(input())
m=int(input())

graph=[[1e8]*(n+1) for _ in range(n+1)]

for _ in range(m):
    a,b,c=map(int,input().split())
    graph[a][b]=min(graph[a][b],c)

for k in range(1,n+1):
    for i in range(1,n+1):
        for j in range(1,n+1):
            if i==j:
                graph[i][j]=0
            graph[i][j]=min(graph[i][j],graph[i][k]+graph[k][j])

for i in range(1,n+1):
    for j in range(1,n+1):
        if graph[i][j]==1e8:
            print("0", end=" ")
        else:
            print(graph[i][j], end=" ")
    print()

'''
def dijkstra(a,b):
    visited=[1e8]*(n+1)
    heap=[]
    heapq.heappush(heap,(0,a))
    visited[a]=0

    while heap:
        cost,node=heapq.heappop(heap)
        if visited[node]<cost:
            continue

        for i in graph[node]:
            nextnode,nextcost=i
            if visited[nextnode]>nextcost+cost:
                visited[nextnode]=nextcost+cost
                heapq.heappush(heap,(nextcost+cost,nextnode))
    return visited[b]

for _ in range(m):
    a,b,c=map(int,input().split())
    graph[a].append((b,c))

print(graph)
result=[[1e8]*(n) for _ in range(n)]
for i in range(1,n+1):
    for j in range(1,n+1):
        if i==j:
            result[i-1][j-1]=0
        else:
            result[i-1][j-1]=dijkstra(i,j)

for i in range(n):
    print(*result[i])'''