import numpy as np
import pandas as pd
import rnnInput as inp
import tensorflow as tf
import time as t
from tensorflow.contrib import learn
from rpy2.robjects.packages import importr
import rpy2.robjects as ro
import pandas.rpy.common as com

#length=2271
#maxSec=300
#TIMESTEPS=40

def poissonDiv(size=10):
    #gera uma distribuicao poison e devolve o vetor e o fator lambda que a gerou
    #faixa 60-120 para compatibilidade com DNAcopy
    val1=np.random.uniform(60,120)
    val2=np.random.uniform(60,120)
    a= np.random.poisson(lam=val1, size=size)
    b= np.random.poisson(lam=val2, size=size)
    lambRate=(val1/val2)
    c=np.log2(a/b)
    return (a/120,b/120,lambRate)

def makeCNV(tamVec,maxSector=80,minSector=30):
    #o processo de criacao do cnv ocorre da seguinte maneira
    #sorteio um tamanho para o setor, gero uma matriz com a resposta da funcao poisson
    #normalizo a matriz e preencho um vetor com o valor e a resposta (lambda/lambda)
    #repito o mesmo processo ate gerar um vetor de tamVec pra retornar

    #gera o vetor e cria o vetor resposta
    arr = np.ndarray(tamVec, np.float32)
    tY = np.ndarray(tamVec, np.float32)
    tY[:]=0
    #primeira iteracao
    tamSec=np.random.randint(minSector,high=maxSector)
    rawmatriz0,rawmatrizB,yy = poissonDiv(size=tamSec)
    matriz0=np.ma.compressed(np.ma.fix_invalid(rawmatriz0))
    matrizb=np.ma.compressed(np.ma.fix_invalid(rawmatrizB))
    #Nmatriz0=matriz0/matriz0.max()
    Nmatriz0=matriz0.compress(matriz0!=0)
    NmatrizB=matrizb.compress(matrizb!=0)
    #testeY=0

    #yy=testeY
    #preenche o vetor y
    for i in range (Nmatriz0.size): arr[i] = yy
    out = Nmatriz0
    outB = NmatrizB
    tempI=0

    #enquanto o vetor nao estiver todo preenchido, ele continua rodando
    if (Nmatriz0.size < tamVec):
        sizeZ=Nmatriz0.size
        while(sizeZ<tamVec):

            sizeZ+=tempI
            #testeY=sizeZ
            tamSec=np.random.randint(minSector,high=maxSector)
            rawMatrizT,rawMatrizB,yt = poissonDiv(size=tamSec)
            #yt = testeY
            if (sizeZ<tamVec):
                tY[sizeZ]=2**yt
            matrizT=np.ma.compressed(np.ma.fix_invalid(rawMatrizT))
            matrizB=np.ma.compressed(np.ma.fix_invalid(rawMatrizB))
            #NmatrizT=matrizT/matrizT.max()
            NmatrizT=matrizT.compress(matrizT!=0)
            NmatrizB=matrizB.compress(matrizB!=0)
            for i in range (sizeZ,NmatrizT.size+sizeZ):
                if (i<tamVec):
                    arr[i] = yt

            tempI=NmatrizT.size
            out = np.append(out,NmatrizT)
            outB = np.append(outB,NmatrizB)
        out = out[:tamVec]
        outB = outB[:tamVec]
        out=pd.DataFrame(out)
        outB=pd.DataFrame(outB)
        arr=pd.DataFrame(arr)
    #devolve o vetor escrito e o vetor gerado
    return (out,outB,arr,tY)

def getDataRND(maxLength,maxSector):
    #funcao que gera o cnv (rnd)
    #devolve o cnv e as "leituras"
    arrY=np.ndarray(shape=(maxLength))
    arrX=np.ndarray(shape=(maxLength,1))
    posAtual=0;
    setorM=np.random.randint(0,high=maxSector)
    valorSetor=np.random.uniform(0.1,0.9)
    randomFaixa = 0.18
    for i in range(maxLength):
        arrY[i] = valorSetor
        arrX[i] = np.random.uniform(valorSetor-randomFaixa,valorSetor+randomFaixa)
        if (i>setorM):
            setorM=np.random.randint(setorM,high=setorM+maxSector)
            valorSetor=np.random.uniform(0.1,0.9)
    arrY=pd.DataFrame(arrY)
    arrX=pd.DataFrame(arrX)
    return arrX,arrY

def getDataInverse(maxLength,timeSteps,maxSector):
    #devolve o inverso da funcao anterior
    #devolve leituras e na f(x) = cnv
    arrY=np.ndarray(shape=(maxLength))
    arrX=np.ndarray(shape=(maxLength,1))
    posAtual=0;
    randomFaixa = 0.08
    setorM=np.random.randint(0,high=maxSector)
    valorSetor=np.random.uniform(0.1,0.9)
    for i in range(maxLength):
        arrX[i] = valorSetor
        arrY[i] = np.random.uniform(np.absolute(valorSetor)-randomFaixa,np.absolute(valorSetor)+randomFaixa)
        if (i>setorM):
            setorM=np.random.randint(setorM,high=setorM+maxSector)
            valorSetor=np.random.uniform(0.1,0.9)
    arrY=pd.DataFrame(arrY)
    arrX=pd.DataFrame(arrX)
    return arrX,arrY

def getFullDataCNV(maxLength,maxSector):
    #apenas para simplificar
    x,y = makeCNV(maxLength,maxSector)
    return getPair(x,y)

def getFullDataOriginaCNV(maxLength,maxSector):
    #para retornar a gerador (multiplas previsoes)
    x,y = makeCNV(maxLength,maxSector=maxSector)
    gx,gy = getPair(x,y)
    return (x,y),(gx,gy)

def getFullDataRND(maxLength,maxSector):
    #apenas para simplificar
    x,y = getDataRND(maxLength,maxSector)
    return getPair(x,y)

def getFullDataOriginaRND(maxLength,maxSector):
    #para retornar a gerador (multiplas previsoes)
    x,y = getDataRND(maxLength,maxSector)
    gx,gy = getPair(x,y)
    return (x,y),(gx,gy)

def dataFitRND(length,maxSec):
    #apenas para simplificar o treinamento
    data_train2, y_train2 = getFullDataRND(length,maxSec)
    data_val2, y_val2 = getFullDataRND(int(length),maxSec)
    with tf.device('/gpu:0'):
        validation_monitor2 = learn.monitors.ValidationMonitor(data_val2, y_val2,
                                                     every_n_steps=100)
    with tf.device('/gpu:0'):
        regressor.fit(data_train2, y_train2,
              monitors=[validation_monitor2],
              batch_size=BATCH_SIZE,
              steps=TRAINING_STEPS)
    return (data_train2, y_train2)

def dataFitCNV(length,maxSec,TIMESTEPS,regressor,BATCH_SIZE,TRAINING_STEPS):
    #apenas para simplificar o treinamento
    (train_x,train_y),(orig_tes_x,origtest_y),(valid_x,valid_y) = retCNV2(length,maxSec,train=5)
    TX=inp.rnn_data(train_x,TIMESTEPS)
    TY=inp.rnn_data(train_y,TIMESTEPS,labels=True)
    VX=inp.rnn_data(valid_x,TIMESTEPS)
    VY=inp.rnn_data(valid_y,TIMESTEPS,labels=True)
    with tf.device('/gpu:0'):
    #o monitor de validacao que é responsavel por "reforcar" o aprendizado, ele usa o data_val para
        validation_monitor2 = learn.monitors.ValidationMonitor(VX, VY,
                                                     every_n_steps=1000,
                                                     early_stopping_rounds=1000)
    with tf.device('/gpu:0'):
        regressor.fit(TX, TY,
              monitors=[validation_monitor2],
              batch_size=BATCH_SIZE,
              steps=TRAINING_STEPS)
    return (TX, TY)

def retCNV2(length,maxSec,train=1):
    orig_trainxB,orig_trainxA,orig_trainy,orig_trainp = makeCNV(length*train,maxSec)
    orig_validxB,orig_validxA,orig_validy,orig_validp = makeCNV(length,maxSec)
    orig_testxB,orig_testxA,orig_testy,orig_testp = makeCNV(length,maxSec)

    orig_train_y=pd.DataFrame(orig_trainy,dtype=np.float32)
    t_p=pd.DataFrame(orig_trainy,dtype=np.float32)
    orig_train_y = orig_train_y.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_test_y=pd.DataFrame(orig_testy,dtype=np.float32)
    t_p=pd.DataFrame(orig_testy,dtype=np.float32)
    orig_test_y = orig_test_y.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_valid_y=pd.DataFrame(orig_validy,dtype=np.float32)
    t_p=pd.DataFrame(orig_validy,dtype=np.float32)
    orig_valid_y = orig_valid_y.join(t_p,lsuffix='L',rsuffix='R',sort=False)

    orig_train_x=pd.DataFrame(orig_trainxA,dtype=np.float32)
    t_p=pd.DataFrame(orig_trainxB,dtype=np.float32)
    orig_train_x = orig_train_x.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_test_x=pd.DataFrame(orig_testxA,dtype=np.float32)
    t_p=pd.DataFrame(orig_testxB,dtype=np.float32)
    orig_test_x = orig_test_x.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_valid_x=pd.DataFrame(orig_validxA,dtype=np.float32)
    t_p=pd.DataFrame(orig_validxB,dtype=np.float32)
    orig_valid_x = orig_valid_x.join(t_p,lsuffix='L',rsuffix='R',sort=False)

    return (orig_train_x,orig_train_y),(orig_test_x,orig_test_y),(orig_valid_x,orig_valid_y)

#funções para segmentação
def mediaS(vec,s,tam):
    somal=0
    for i in reversed(range(s,tam)):
        somal=somal+(vec[i])
    return(somal/(tam-s))

def segmenta(vetor,menorSeg,treshold):
    pts= np.ndarray(vetor.size)
    pts2= np.ndarray(vetor.size)
    ptsf= np.ndarray(vetor.size)
    mean= np.ndarray(vetor.size)
    pts[:]=0
    pts2[:]=0
    ptsf[:]=0
    mean[:]=0
    dadoS=(vetor)
    dadosP=np.log2(dadoS)
    lastp=0
    m=menorSeg
    tresh=treshold
    for i in range (dadoS.size):
        if (i==m):
            pts[i]=mediaS(dadosP,i-(m-1),i)
        if (i>m):
            pts[i]=mediaS(dadosP,i-m,i)
            pts2[i]=(pts[i] - pts[i-1])
            if (np.absolute(pts2[i])>tresh):
                #nao gerar setores menores q o a media
                if (i>lastp+m):
                    ptsf[i]=1
                    lastp=i

    lastpoint=0
    for i in range(0,ptsf.size):
        if (ptsf[i]==1):
            mean[i] = mediaS(dadoS,lastpoint,i)
            lastpoint=i
    i=ptsf.size
    mean[i-1] = mediaS(dadoS,lastpoint,i)
    for i in reversed(range(0,ptsf.size)):
        if (mean[i]!=0):
            at=mean[i]
        else:
            mean[i]=at
    return mean

def trechos(entra,tresh=10):
    #entra=arrayDeslocaM
    vecB=np.ndarray(entra.size)
    vecB[:]=0
    val0=entra[0]
    #tresh = 10
    for i in range(vecB.size):
        if (entra[i]!=val0):
            for j in reversed(range(i-tresh,i+tresh)):
                if (j>0 and j<vecB.size):
                    vecB[j]=1
            vecB[i]=1
        val0=entra[i]
    return vecB

def DNAcopy(entraDNAcopy):
    ro.r('library(DNAcopy)')
    d_base = com.load_data('coriell')
    _=pd.DataFrame(d_base.index,dtype=np.float32)
    _=_-1
    _.loc[_.size] = _.size
    d_base['Position'] = _
    d_base['Chromosome'] = 1
    d_base=d_base.fillna(0)
    d_base['Coriell.05296'] = entraDNAcopy
    data_r= com.convert_to_r_dataframe(d_base)
    ro.globalenv['teste'] = data_r
    ro.r('CNA.object <- CNA(cbind(teste$Coriell.05296),  teste$Chromosome,teste$Position,  data.type="logratio",sampleid="c05296")')
    ro.r('smoothed.CNA.object <- smooth.CNA(CNA.object)')
    segmentadoR=ro.r('segment.smoothed.CNA.object <- segment(smoothed.CNA.object, verbose=1)')
    return segmentadoR

def segmentaDNAcopy(fimsetores,medias):
    valorMedia=medias
    mediaatual=0
    entrada=fimsetores
    saida=np.ndarray(entrada[entrada.size-1])
    saidaB=np.ndarray(entrada[entrada.size-1])
    saida[:]=0
    saidaB[:]=0
    valatual=entrada[mediaatual]
    for i in (range(0,saida.size)):
        valatual=valorMedia[mediaatual]
        if (i>entrada[mediaatual]):
            saidaB[i]=1
            mediaatual = mediaatual + 1
            valatual=valorMedia[mediaatual]
        saida[i]=valatual
    return (2**saida),saidaB

def qtdErros(vetEntrada,vetVerifica):
    entra=vetEntrada
    verifica=vetVerifica
    sobe=False
    ponto=False
    pontos=0
    acertos=0
    for i in range(entra.size):
        if (entra[i]==1):
            sobe=True
        if (sobe and verifica[i]):
            ponto=True
        if (entra[i]==0 and sobe):
            sobe=False
            if (ponto):
                acertos=acertos + 1
                ponto=False
            else: pontos = pontos + 1
    return acertos,pontos

def pontosDNAcopy(segmentadoR,arrayOrig):
    #entraDNAcopy=np.log2(orig_test_x['A1X']/orig_test_x['A0X'])
    #segmentadoR=bioinp.DNAcopy(entraDNAcopy)
    fimsetores = np.array(list(segmentadoR[2][1]))
    medias = np.array(list(segmentadoR[1][5]))

    med,loc=segmentaDNAcopy(fimsetores,medias)
    #med = med[loc.size-arrayOrig.size:]
    loc = loc[loc.size-arrayOrig.size:]
    orig=trechos(arrayOrig,tresh=5)
    c=trechos(loc,tresh=5)

    acertos,false_negative = qtdErros (orig,c)
    _,false_positive = qtdErros (c,orig)
    #print('acertos {} fn {} ,fp {}'.format(acertos,false_positive,false_negative))
    return (acertos,false_positive,false_negative)

def dataTestCNV(length,maxSec,TIMESTEPS,regressor,i=0):
    #batch de teste
    #retorna o erro e acerto de um teste utilizando o tcnv e dnacopy
    (train_x,train_y),(orig_test_x,orig_test_y),(valid_x,valid_y) = retCNV2(length,maxSec,train=1)

    #salvar dados
    inp.setPairCtoCSV(orig_test_x,orig_test_y,"test_valid"+str(i)+".csv")

    testX = inp.rnn_data(orig_test_x,TIMESTEPS)
    with tf.device('/gpu:0'):
        predicted = regressor.predict(testX)
    predictedArr = np.array(list(predicted))
    predictedDataFrame=pd.DataFrame(predictedArr)

    segmentado=segmenta(predictedDataFrame[0],10,0.025)
    origArr = np.array(list(orig_test_y['0L']))
    origArr.shape = -1,1
    origArr=origArr[TIMESTEPS:]
    arrayDeslocado=np.array(list(origArr))

    orig=trechos(arrayDeslocado,tresh=5)
    teste=trechos(segmentado,tresh=5)
    acertosT,false_negativeT = qtdErros (orig,teste)
    _,false_positiveT = qtdErros (teste,orig)

    entraDNAcopy=np.log2(orig_test_x['0L']/orig_test_x['0R'])
    segmentadoR=DNAcopy(entraDNAcopy)
    acertos,false_positive,false_negative = pontosDNAcopy(segmentadoR,arrayDeslocado)

    #erro divisao por 0
    if(acertosT==0): acertosT=1
    if(acertos==0): acertos=1
    return (acertosT,false_positiveT,false_negativeT),(acertos,false_positive,false_negative)

def comparaSeg(length,maxSec,TIMESTEPS,regressor,qtd=1):
    scorTCNV=pd.DataFrame(index=range(qtd), columns=['tp', 'fp', 'fn', 'ppv', 'sn','score'])
    scorDNAC=pd.DataFrame(index=range(qtd), columns=['tp', 'fp', 'fn', 'ppv', 'sn','score'])
    for i in range(qtd):
        (tpt,fpt,fnt),(tpd,fpd,fnd) = dataTestCNV(length,maxSec,TIMESTEPS,regressor,i=i)

        ppvt=tpt/(tpt+fpt)
        snt=tpt/(tpt+fnt)
        ppvd=tpd/(tpd+fpd)
        snd=tpd/(tpd+fnd)
        scorTCNV['tp'][i]=tpt
        scorTCNV['fp'][i]=fpt
        scorTCNV['fn'][i]=fnt
        scorTCNV['ppv'][i]=ppvt
        scorTCNV['sn'][i]=snt
        scorTCNV['score'][i]=2*((ppvt*snt)/(ppvt+snt))

        scorDNAC['tp'][i]=tpd
        scorDNAC['fp'][i]=fpd
        scorDNAC['fn'][i]=fnd
        scorDNAC['ppv'][i]=ppvd
        scorDNAC['sn'][i]=snd
        scorDNAC['score'][i]=2*((ppvd*snd)/(ppvd+snd))

    return (scorTCNV,scorDNAC)


from .opt import flag, param, arg, walk, sub
from .usage import Usage, Help

class Base(Help):
    "The based class fror your command line app"

    def ready(self, *args, **kwargs):
        "Called before walk options. Subclass should call super().ready(*args, **kwargs)"

    def start(self, *args, **kwargs):
        "Start point of app."
        " Called after walk options."
        " .main(...) --> ready(...) --> start(...)."
        return self

    def _o_walk_sub(self, which, **kwargs):
        klass = kwargs["cmd_map"][which]
        sub = klass()
        sub._o_parent = self
        # print(list(kwargs["opt"].argv))
        return sub.main(kwargs["opt"].argv, skip_first=False)

    def options(self, opt, *args, **kwargs):
        try:
            f = super().options
        except AttributeError:
            return opt
        else:
            return f(opt)

    def main(self, argv=None, skip_first=None, **kwargs):
        "Entry point of app"
        if argv is None:
            from sys import argv
        self.ready(**kwargs)
        opt = Opt(self)
        self.options(opt)
        opt.walk(argv, skip_first=skip_first)
        # NOTE: if .start did not do anything may be it has 'yield' statement
        return self.start(**kwargs)

class Main(Usage):
    "The based class fror your command line app"

    def ready(self, *args, **kwargs):
        "Called before walk options. Subclass should call super().ready(*args, **kwargs)"

    def main(self, argv=None, **kwargs):
        "Entry point of app"
        if argv is None:
            from sys import argv
        self.ready(**kwargs)
        walk(self, argv)
        # NOTE: if .start did not do anything may be it has 'yield' statement
        return self.start(**kwargs)

    def start(self, *args, **kwargs):
        "Start point of app."
        " Called after walk options."
        " .main(...) --> ready(...) --> start(...)."

    def _o_walk_sub(self, value, **kwargs):
        klass = self._o_sub[value]
        sub = klass()
        sub._o_parent = self
        sub.ready(**kwargs)
        walk(sub, self._o_argv, skip_first=False)
        return sub.start(**kwargs)

    # def __getattr__(self, name):
    #     # TODO: Document
    #     m = "__let_" not in name
    #     if m:
    #         m = getattr(self, "__let_" + name, None)
    #     if m:
    #         setattr(self, name, None)
    #         x = m()
    #         setattr(self, name, x)
    #         return x
    #     #
    #     try:
    #         m = super().__getattr__
    #     except AttributeError:
    #         raise AttributeError(name, self)
    #     else:
    #         return m(name)

__all__ = ("flag", "param", "arg", "sub", "Main", "Base")

class Opt:
    __slots__ = ("cli", "o_params", "o_args", "inc", "argv")

    def __init__(self, cli):
        self.cli = cli
        self.inc = 0
        from collections import OrderedDict

        self.o_params = OrderedDict()
        self.o_args = OrderedDict()

    def flag(self, *args, **kwargs):
        o = self.o_params
        if not kwargs.get("dest"):
            for v in args:
                if v:
                    kwargs["dest"] = v
                    break
        for x in args:
            if x not in o:
                o[x] = kwargs
        return self

    def param(self, *args, **kwargs):
        if not kwargs.get("type"):
            kwargs["type"] = str
        return self.flag(*args, **kwargs)

    def arg(self, *args, **kwargs):
        o = self.o_args
        if True in o:
            return self
        for v in args:
            if v.isidentifier():
                # if v.isalnum():
                kwargs["dest"] = v
            else:  # v in ('+', '*', True)
                kwargs["required"] = v
        if "requires" in kwargs:
            kwargs["required"] = kwargs["requires"]

        dest = kwargs.get("dest")
        append = kwargs.get("append")
        call = kwargs.get("call")
        required = kwargs.get("required")
        if dest is None and append and append is not True:
            kwargs["dest"] = dest = append
        if dest is None and call:
            kwargs["dest"] = "arg:call:{!r}".format(call)
        k = True if (append or required in ("+", "*")) else (dest or id(kwargs))
        assert "append" not in kwargs or isinstance(
            kwargs["append"], (bool, str)
        ), ".arg append must string or True"
        # assert "dest" in kwargs, ".arg needs 'dest'"

        o[k] = kwargs
        return self

    def sub(self, cmd_map, *args, **kwargs):
        if "choices" not in kwargs:
            kwargs["choices"] = cmd_map.keys()
        if "call" not in kwargs:
            kwargs["call"] = "_o_walk_sub"
            kwargs["dest"] = "command"  # OTDO: use metavar like
        kwargs["kwargs"] = dict(cmd_map=cmd_map, opt=self)
        return self.arg(**kwargs)

    def walk(self, argv, skip_first=None):
        cli = self.cli
        _params = self.o_params
        _args = list(self.o_args.items())
        _ctx = {}
        # seen = set()
        # seen_a = {}
        # seen_p = {}
        # import pprint

        def next_arg(argv, before):
            try:
                return next(argv)
            except StopIteration:
                raise RuntimeError("Expected argument after {!r}".format(before))

        def find_param(name, arg, flag=None):
            for k, a in _params.items():
                if k == name:
                    if len(k) == 1 if flag else len(k) > 1:
                        if "seen" in a:
                            a["seen"] += 1
                        else:
                            a["seen"] = 1
                        return a
            if flag:
                raise RuntimeError("Unknown option {!r} in {!r}".format(name, arg))
            raise RuntimeError("Unknown option {!r}".format(arg))

        def find_arg(arg):
            if _args:
                k, v = _args.pop(0)
                # print("find_arg",  k, v, arg)
                if k is True:
                    _ctx["end"] = v
                return v
            elif "end" in _ctx:
                return _ctx["end"]
            raise RuntimeError("Unexpected argument {!r}".format(arg))

        # def try_call(call, v):
        #     (getattr(cli, call) if isinstance(call, str) else call)(v)

        def try_call(cur, v):
            call = cur.get("call")
            if call:
                try:
                    fn = getattr(cli, call) if isinstance(call, str) else call
                except Exception:
                    raise RuntimeError(
                        "from argument {!r} get {!r} failed".format(arg, call)
                    )
                try:
                    fn(v, **cur.get("kwargs", {}))
                except SystemExit:
                    raise
                except Exception:
                    raise RuntimeError(
                        "from argument {!r} call {!r} failed".format(arg, call)
                    )
                return True

        def push(cur, val):
            if "choices" in cur:
                val = cur.get("select", select)(val, cur["choices"])
            kind = cur.get("type")
            if kind:
                try:
                    val = kind(val)
                except Exception as exc:
                    raise RuntimeError(
                        "parse {!r} failed. from argument {!r}".format(arg, val)
                    ) from exc
            # - call
            if try_call(cur, val):
                return
            dest = cur.get("dest")
            # - append
            if "append" in cur:
                x = getattr(cli, dest, None)
                if x is None:
                    setattr(cli, dest, [val])
                else:
                    x.append(val)
                return
            setattr(cli, dest, val)

        def push_flag(cur, state):
            # - call
            if try_call(cur, state):
                return
            dest = cur["dest"]
            # - append
            append = cur.get("append")
            if append:
                if dest:
                    pass
                elif append and append is not True:
                    dest = append
                val = cur.get("const")
                x = getattr(cli, dest, None)
                if x is None:
                    if state is False:
                        pass
                    else:
                        setattr(cli, dest, [val])
                else:
                    if state is False:
                        try:
                            x.remove(val)
                        except ValueError:
                            pass
                    else:
                        x.append(val)
                return
            # - count
            count = cur.get("count")
            if count:
                if dest:
                    pass
                elif count and count is not True:
                    dest = count
                x = getattr(cli, dest, None)
                if x is None:
                    val = cur.get("const", 0)
                    if state is False:
                        setattr(cli, dest, val - 1)
                    else:
                        setattr(cli, dest, val + 1)
                else:
                    if state is False:
                        setattr(cli, dest, x - 1)
                    else:
                        setattr(cli, dest, x + 1)
                return
            # - set
            if "const" in cur:
                return setattr(cli, dest, cur["const"])
            else:
                # print("state", dest, state, state is not False)
                return setattr(cli, dest, state is not False)

        def plain(cur, arg):
            push(cur, arg)

        def short(cur, chrs, index, argv):
            index += 1
            if "type" in cur:  # params
                # print(cur['type'], chrs, index, index < len(chrs))
                push(cur, chrs[index:] if index < len(chrs) else next_arg(argv, chrs))
            else:  # flag
                # print(cur['type'], chrs, index, index < len(chrs))
                push_flag(cur, True)
                if index < len(chrs):
                    short(find_param(chrs[index], chrs, index), chrs, index, argv)

        def long(arg, cur, value=None, argv=None):
            if "type" in cur:  # params
                if value is False:
                    raise RuntimeError(
                        "{!r} takes an argument and not negatable".format(arg)
                    )
                elif value is not None:
                    push(cur, value)  # --name=VALUE
                elif argv is not None:
                    push(cur, next_arg(argv, arg))  # --name VALUE
                else:
                    raise RuntimeError("{!r} takes an argument".format(arg))
            elif value:
                raise RuntimeError("{!r} does not takes an argument".format(arg))
            else:  # flag
                assert value in (None, False)
                push_flag(cur, value)

        dd = None
        self.argv = argv = iter(argv)
        # skip prog name
        skip_first is False or next(argv, None)
        # get first argument
        arg = next(argv, None)
        while arg is not None:
            # print('ARG', arg, cli)
            if dd or ("-" == arg):
                plain(find_arg(arg), arg)
            elif "--" == arg:
                dd = True
            elif arg.startswith("--"):
                if "=" in arg:  # --name=value
                    t = arg.partition("=")
                    long(arg, find_param(t[0][2:].replace("-", "_"), arg), t[2], argv)
                elif arg.startswith("--no-"):  # --no-name
                    long(arg, find_param(arg[5:].replace("-", "_"), arg), False)
                elif arg.startswith("--no"):  # --noname
                    long(arg, find_param(arg[4:].replace("-", "_"), arg), False)
                else:  # --name
                    long(arg, find_param(arg[2:].replace("-", "_"), arg), None, argv)
            elif arg.startswith("-"):  # -qhoFILE == -q -h -o FILE
                short(find_param(arg[1], arg, 1), arg, 1, argv)
            else:
                plain(find_arg(arg), arg)
            # get next argument
            arg = next(argv, None)
        for v in _params.values():
            # print("enum_params", v)
            if "seen" in v:  # provided
                pass
            # elif hasattr(cli, v["dest"]):
            elif v["dest"] in cli.__dict__:
                pass
            elif "required" in v:
                raise RuntimeError("{} is required".format(v["dest"]))
            elif "default" in v:
                # print("default", n, v)
                setattr(cli, v["dest"], v["default"])
            elif "append" in v:
                setattr(cli, v["dest"], [])

        for k, a in _args:
            # print("enum_args", a)
            required = a.get("required")
            if required is None:
                # if hasattr(cli, a["dest"]):
                if a["dest"] in cli.__dict__:
                    pass
                elif "default" in a:
                    if try_call(a, a["default"]):
                        continue
                    setattr(cli, a["dest"], a["default"])
            elif required == "+":
                raise RuntimeError("{!r} needs atleast one argument".format(a["dest"]))
            elif required == "*":
                if "call" not in a:
                    setattr(cli, a["dest"], [])
            else:
                assert required is True, "Unexpected required value"
                raise RuntimeError("{!r} needed".format(a["dest"]))

def select(val, choices):
    chosen = None
    for n in choices:
        if not n or not n.startswith(val):
            pass
        elif chosen:
            raise RuntimeError("{!r} matches {!r} and {!r}".format(val, n, chosen))
        else:
            chosen = n
    # print(klass)
    if chosen is None:
        raise RuntimeError("Invalid choice {!r} choose from {!r} ".format(val, choices))
    return chosen


f = open('day21.txt', 'r')
lines = f.readlines()

nodes=dict()
q=[]
for line in lines:
    parts = line.strip().split(' ')
    name=parts[0][:-1] # remove colon
    value=None
    left=None
    op=None
    right=None

    if parts[1].isnumeric():
        value=float(parts[1])
    else:
        left=parts[1]
        op=parts[2]
        right=parts[3]
    node = [name, value, left, op, right, [], None, None]   # last array is the node names that depend on this
    if value is not None:
        q.append(node)
    nodes[name] = node

for node in nodes.values():
    left=node[2]
    right=node[4]
    if left is not None:
        # we depend on left and right
        leftnode = nodes[left]
        rightnode = nodes[right]
        # Add our name to the left and right
        leftnode[5].append(node[0])
        rightnode[5].append(node[0])

#print("Values:")
#for node in nodes.values():
#    print(node)

def reset():
    global q
    q=[]
    for node in nodes.values():
        if node[3] is None:
            q.append(node)
        else:
            node[1] = None
        node[6] = None
        node[7] = None
    #print(q)

algo="""
L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edge

while S is not empty do
    remove a node n from S
    add n to L
    for each node m with an edge e from n to m do
        remove edge e from the graph
        if m has no other incoming edges then
            insert m into S

if graph has edges then
    return error   (graph has at least one cycle)
else
    return L   (a topologically sorted order)
"""
def onerun():
    while len(q):
        head=q.pop()
        #print("Head ", head)
        (name, value, left, op, right, deps, leftvalue, rightvalue) = head
        if name == 'root':
         #   print("Returning root", head)
            return head
        for depname in deps:
            # tell each dependency about our number
            dep = nodes[depname]
            if dep[1] is None:
                if dep[6] == None and dep[2] == name:
                    dep[6] = value
                if dep[7] == None and dep[4] == name:
                    dep[7] = value
                if dep[6] is not None and dep[7] is not None:
                    # both are numbers, calculate our new number and add us to the queue.
                    newvalue = 0
                    op=dep[3]
                    if op == '+':
                        newvalue = dep[6] + dep[7]
                    elif op == '-':
                        newvalue = dep[6] - dep[7]
                    elif op == '*':
                        newvalue = dep[6] * dep[7]
                    elif op == '/':
                        newvalue = dep[6] / dep[7]
                    else:
                        print("UNKNOWN OP ", op)
                    dep[1]= newvalue
                    #print("adding updated dep ", dep)
                    q.append(dep)
        #print("queue is now ", q)
    print("NO ROOT")

humn=nodes['humn']
low=0
high=100000000000000
iters=0
while True:
    mid = (high+low)/2
    humn[1]=mid
    reset()
    root=onerun()
    # print('Root ', root)
    # print('Humn ', humn)
    left=root[6]
    right=root[7]

    diff = left-right
    if abs(diff) < .001:
        print("FOUND IT!:", humn)
        print("Root:", root)
        break
    elif diff< 0:
        # Too high, make smaller
        high=mid-1
    else:
        # Too low, make bigger
        low = mid+1
    iters = iters + 1

print("Part 2:", int(humn[1]))
print(iters, "Iterations")


class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        def partition(nums, l, r, p):
            # pick pivot and move it to the right
            pivot = nums[p]
            nums[p], nums[r] = nums[r], nums[p]

            # move smaller item to the left
            i = l
            for j in range(l, r):
                if nums[j] < pivot:
                    nums[j], nums[i] = nums[i], nums[j]
                    i += 1 # now i is the pivot index

            # move pivot to the correct place
            nums[r], nums[i] = nums[i], nums[r]
            return i # return the pivot index

        def quick_select(nums, l, r, k_smallest):
            if l == r:
                return nums[l]
            p = random.randint(l, r)
            p_idx = partition(nums, l, r, p)
            if p_idx == k_smallest:
                return nums[p_idx]
            elif p_idx > k_smallest:
                # l, ---- k ----(p)---r
                return quick_select(nums, l, p_idx-1, k_smallest)
            else:
                return quick_select(nums, p_idx+1, r, k_smallest)

        return quick_select(nums, 0, len(nums)-1, len(nums)-k)

class Solution1:
    def findKthLargest(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        def partition(left, right, pivot_index):
            pivot = nums[pivot_index]
            # 1. move pivot to end
            nums[pivot_index], nums[right] = nums[right], nums[pivot_index]

            # 2. move all smaller elements to the left
            store_index = left
            for i in range(left, right):
                if nums[i] < pivot:
                    nums[store_index], nums[i] = nums[i], nums[store_index]
                    store_index += 1

            # 3. move pivot to its final place
            nums[right], nums[store_index] = nums[store_index], nums[right]

            return store_index

        def select(left, right, k_smallest):
            """
            Returns the k-th smallest element of list within left..right
            """
            if left == right:       # If the list contains only one element,
                return nums[left]   # return that element

            # select a random pivot_index between
            pivot_index = random.randint(left, right)

            # find the pivot position in a sorted list
            pivot_index = partition(left, right, pivot_index)

            # the pivot is in its final sorted position
            if k_smallest == pivot_index:
                 return nums[k_smallest]
            # go left
            elif k_smallest < pivot_index:
                return select(left, pivot_index - 1, k_smallest)
            # go right
            else:
                return select(pivot_index + 1, right, k_smallest)

        # kth largest is (n - k)th smallest
        return select(0, len(nums) - 1, len(nums) - k)


import tkinter
import customtkinter

customtkinter.set_appearance_mode("Dark")
customtkinter.set_default_color_theme("dark-blue")

class App(customtkinter.CTk):
	def __init__(self):
		super ().__init__()

		# configue the window
		self.geometry(f"{1280}x{720}")
		self.title("Image Editing Screen")
		self.resizable(0, 0)

		# configure frame on right side for edit buttons
		self.edit_frame = customtkinter.CTkFrame(master=self, width=240, height=704)
		self.edit_frame.pack(padx=8, pady=8, side="right", fill="both", expand=True)

		# configure frame on left side for displaying/handling image
		self.image_frame = customtkinter.CTkFrame(master=self, width=960, height=704)
		self.image_frame.pack(padx=8, pady=8, side="left", fill ="both", expand=True)

	def create_buttons(self):
		# configure edit buttons that will be in the edit button frame
		self.colorselect_optionmenu = customtkinter.CTkOptionMenu(master=self.edit_frame, height=50, width=200, values=["Default", "other"], anchor=tkinter.CENTER, command = self.color_select)
		self.colorselect_optionmenu.place(relx=0.5, rely=0.05, anchor=tkinter.CENTER)

		self.design_button = customtkinter.CTkButton(master=self.edit_frame, height=50, width=200, text="Select Design", border_width=2, command=self.design_select)
		self.design_button.place(relx=0.5, rely=0.17, anchor=tkinter.CENTER)

		self.zoom_button = customtkinter.CTkButton(master=self.edit_frame, height=50, width=200, text="Zoom In/Zoom Out", border_width=2, command=self.zoom)
		self.zoom_button.place(relx=0.5, rely=0.29, anchor=tkinter.CENTER)
		self.zoom_slider = customtkinter.CTkSlider(master=self.edit_frame, from_=25, to=400, number_of_steps=75, command=self.zoom_slider)
		self.zoom_slider.place(relx=0.5, rely=0.34, anchor=tkinter.CENTER)

		self.undo_button = customtkinter.CTkButton(master=self.edit_frame, height=50, width=200, text="Undo Last Change", border_width=2, command=self.undo)
		self.undo_button.place(relx=0.5, rely=0.41, anchor=tkinter.CENTER)

		self.reset_button = customtkinter.CTkButton(master=self.edit_frame, height=50, width=200, text="Reset to Original", border_width=2, command=self.reset)
		self.reset_button.place(relx=0.5, rely=0.53, anchor=tkinter.CENTER)

		self.back_button = customtkinter.CTkButton(master=self.edit_frame, height=50, width=200, text="Back to Home", border_width=2, command=self.back)
		self.back_button.place(relx=0.5, rely=0.87, anchor=tkinter.CENTER)

		self.saveas_button = customtkinter.CTkButton(master=self.edit_frame, height=50, width=200, text="Save Image As", border_width=2, command=self.save_as)
		self.saveas_button.place(relx=0.5, rely=0.95, anchor=tkinter.CENTER)

	# method will handle the changing of the color after the user has selected and option from the dropdown
	def color_select(self, choice):
		print(f"Color select menu clicked, color selected: {choice}")

	# method will allow the user to choose an image file that is a design to add onto the clothing
	def design_select(self):
		print("Design selection button has been pressed")

	# method will handle the zooming in and out of the selected image to edit
	def zoom(self):
		print("Zoom button has been pressed")

	def zoom_slider(self, value):
		print(f"Zoom slider has been adjusted, new value: {value}")

	# un does the last change the user made to the image
	def undo(self):
		print("Undo button has been pressed")

	# resets the selected image to its default when it was first selected
	def reset(self):
		print("Reset button has been pressed")

	# takes the user back to the home screen
	def back(self):
		print("Back button has been pressed")

	# prompts the user to save the newly edited image to their computer
	def save_as(self):
		print("Save As button has been pressed")

if __name__ == "__main__":
	app = App()
	app.create_buttons()
	app.mainloop()


import pygame
import os
from handler import Handler
from keyHandler import Handler as KeyHandler
from webSupport import WebSupport
import sys
import json

class Asset:

    def __init__(this):
        this.assets = {}

    def new():
        return Asset()

    def loadAsset(this, path, name):
        print("loading: " + name)
        if path[-1] != os.sep:
            path += os.sep
        if len(name.split(".")) != 2:
            return False
        extension = name.split(".")[1]
        print(extension)
        if extension.lower() == "png":
            img = this.loadPNG(path, name)
            if img == False:
                return False
            this.assets[name] = img
            return True

    def loadPNG(this, path, name):
        if path[-1] != os.sep:
            path += os.sep
        try:
            print("loading image path: " + path + name)
            img = pygame.image.load(path + name)
        except Exception:
            return False
        return img

    def __getPNG__(this, path):
        return pygame.image.load(path)

    def loadBoxTexture(this, name):
        if name == "default":
            this.assets["BOX"+name] = this.__loadBoxTextures__(Handler.boxPath[:-1], "default")
        else:
            this.assets["BOX"+name] = this.__loadBoxTextures__(Handler.boxPath[:-1], name)
            #.assets["BOX"+name] = this.__loadBoxTextures__(os.sep.join([Handler.graphicsPath,"Boxes"], name))

    def __loadBoxTextures__(this, path, name):
        with open(os.sep.join([path,name + ".dat"]), "r", encoding="utf-16") as file:
            js = json.load(file, encoding="utf-16")

        print("js:",js)
        ret = {}
        ret["original"] = this.__getPNG__(os.sep.join([path,name]) + ".png")
        ret["whole"] = ret["original"].copy()

        ret.update(this.__getImagesFromJSON__(ret["whole"], js.get("cropping", {})))
        ret["options"] = js.get("contain", {}).get("options", {})

        scale = ret["options"].get("scale", 1)

        print(ret)

        if scale != 1:
            for key in ret:
                if key == "options" or key == "original":
                   continue
                img = ret[key]
                rect = img.get_rect()
                img = pygame.transform.scale(img, (int(rect.width * scale), int(rect.height * scale)))
                ret[key] = img
        print(ret)

        return ret
        '''
        return {"topLeft": this.loadPNG(path, "topLeft.png"), "top": this.loadPNG(path, "top.png"), "topRight":this.loadPNG(path, "topRight.png"),
                "left": this.loadPNG(path, "left.png"), "middle": this.loadPNG(path, "middle.png"), "right":this.loadPNG(path, "right.png"),
                "bottomLeft": this.loadPNG(path, "bottomLeft.png"), "bottom": this.loadPNG(path, "bottom.png"), "bottomRight":this.loadPNG(path, "bottomRight.png")}
        '''

    def __getImagesFromJSON__(this, wholeImage, jsonMap):
        ret = {}
        for key in jsonMap:
            ret[key] = wholeImage.subsurface(jsonMap[key])
        return ret

    def getBoxTexture(this, name, c=0):
        if not "BOX"+name in this.assets:
            try:
                this.loadBoxTexture(name)
            except IOError as e:
                if name == "default":
                    raise e
                if c == 0:
                    WebSupport.downloadFiles([name.join(("BOX",".png")), name.join(("BOX", ".dat"))],Handler.boxPath)
                    print("downloaded: " + name)
                    return this.getBoxTexture(name, c=1)
                print("using default, texture " + name + " not found")
                return this.getBoxTexture("default")
        return this.assets["BOX" + name]

    def __getitem__(this, key):
        if key.startswith("BOX"):
            return this.getBoxTexture(key.strip("BOX"))
        raise KeyError("Asset Manager can not find file: '" + str(key) + "' as no prefixes were detected")

class GameState:

    def __init__(this, gameState):
        Handler.currentGameState = gameState
        Handler.gameStateManager = this
        this.startGameLoop();
        this.running = None

    def new():
        return GameState()

    def startGameLoop(this):
        Handler.tickID = 0
        Handler.currentGameState.start()
        this.running = True
        while this.running:
            Handler.pygameEvents = pygame.event.get()
            KeyHandler.tick()
            print([str(x, encoding="utf-16").encode("utf-8") for x in KeyHandler.pressed])
            for event in Handler.pygameEvents:
                if event.type == pygame.QUIT:
                    this.stop()
            Handler.display.tick()
            Handler.tickID += 1
            Handler.currentGameState.tick()
            Handler.currentGameState.render()
            Handler.display.update()

    def stop(this):
        Handler.currentGameState.stop()
        pygame.quit()
        sys.exit()
        quit()

    def pause(this):
        this.running = False

    def resume(this):
        this.startGameLoop()

    def switchGameState(this, gameState):
        Handler.currentManagers = {"Asset":Asset(),"ScreenObject":ScreenObject()}
        Handler.currentGameState = gameState
        gameState.init()

class ScreenObject:

    def __init__(this):
        this.isFocused = True
        this.objects = []
        this.lastNext = -1

    def requestFocus(this):
        this.isFocused = True

    def removeFocus(this):
        this.isFocused = False

    def hasFocus(this):
        if this.isFocused:
            return True
        for objec in this.objects:
            if objec.hasFocus:
                return True
        return False

    def removeAllFocus(this):
        for objec in this.objects:
            if objec.isFocused:
                if not objec.removeFocus():
                    return False
                if "isGroup" in dir(objec) and objec.isGroup:
                    objec.objectManager.removeAllFocus()

    def putFocusOn(this, obj):
        if obj in this:
            for objec in this.objects:
                if objec == obj:
                    return obj.requestFocus()
                if "isGroup" in dir(objec) and objec.isGroup:
                    if obj in objec:
                        return objec.objectManager.putFocusOn(obj)
        return False

    def focus(this, obj):
        if obj in this.objects:
            for obj in this.objects:
                obj.removeFocus();
            return this.putFocusOn(obj)
        return False

    def getFocusedObject(this):
        for objec in this.objects:
            if objec.isFocused:
                if "isGroup" in dir(objec) and objec.isGroup:
                    return objec.objectManager.getFocusedObject()
                return objec

    def hasFocusedObject(this):
        for objec in this.objects:
            if objec.isFocused:
                if "isGroup" in dir(objec) and objec.isGroup:
                    return objec.objectManager.hasFocusedObject()
                return True

    def __contains__(this, obj):
        for objec in this.objects:
            if obj == objec:
                return True
            if "isGroup" in dir(objec) and objec.isGroup:
                return obj in objec

    def addObject(this, obj, focus=False):
        this.objects.append(obj)
        obj.belongsTo = this
        if focus:
            this.putFocusOn(obj)

    def removeObject(this, obj):
        for objec in this.objects:
            if objec == obj:
                this.remove(obj)
                return True
            elif "isGroup" in dir(objec) and objec.isGroup:
                if objec.remove(obj):
                    return True
        return False

    def removeDeadObjects(this):
        for obj in this.objects:
            if not obj.isAlive:
                this.objects.remove(obj)

    def tick(this):
        for obj in this.objects:
            obj.tick()
        this.removeDeadObjects()

    def render(this):
        for obj in sorted(this.objects, key=lambda obj: obj.zIndex):
            obj.render()

    def getMaxZIndex(this):
        if len(this.objects) == 0:
            return 0
        return max(this.objects, key=lambda obj: obj.zIndex).zIndex

    def new(this):
        return ScreenObject()

    def focusNext(this):
        print("Focusing Next")
        if this.lastNext == Handler.tickID:
            return False
        this.lastNext = Handler.tickID
        for i, objec in enumerate(this.objects):
            if "isGroup" in dir(objec) and objec.isGroup and objec.hasFocus():
                if objec.focusNext():
                    return True
                continue
            if objec.hasFocus():
                objec.removeFocus()
                d = i
                while True:
                    focusIndex = d + 1
                    if focusIndex >= len(this.objects):
                        focusIndex = 0
                    if this.objects[focusIndex].isFocusable:
                        break
                    d += 1
                Handler.currentManagers["ScreenObject"].putFocusOn(this.objects[focusIndex])
                print("Found: " + str(objec) + "@ index " + str(i) + " moving to " + str(this.objects[focusIndex]) + "@ index " + str(focusIndex))
                return True
        return False

def loadDefaultManagers():
    Handler.currentManagers["Asset"] = Asset()
    Handler.currentManagers["ScreenObject"] = ScreenObject()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Sat Jul  8 13:52:25 2017

@author: Gaspar
"""
import pygame
from pygame.locals import *
from sys import exit
import GameTools
from GameTools import *
import random
import math

#Iniciamos Pygame
pygame.init()
screen = pygame.display.set_mode(GameTools.SCREEN_SIZE)
pygame.display.set_caption("THE MONKEY GAME")

#Crea el monkey y las bananas
monkey = Player(screen, SCREEN_WIDTH/2, SCREEN_HEIGHT - 20) #Creamos el Monkey al centro de la pantalla
#num_bananas = 20
#lista_bananas = []
#lista_bananas = createListaBananas(screen, num_bananas)

lista_frutas = []

#Objeto para registro del tiempo
clock = pygame.time.Clock()

#Ciclo infinito para dibujar
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit() #necesario para cerrar la ventana al presionar X sino se usa se traba el juego
            running = False
            exit()

    #Revisa que tecla fue presionada
    keys = pygame.key.get_pressed()
    if keys[K_LEFT]:
        if checkLimitX(monkey, 'LEFT'):
            monkey.mover_left(PLAYER_SPEED)

    elif keys[K_RIGHT]:
        if checkLimitX(monkey, 'RIGHT'):
            monkey.mover_right(PLAYER_SPEED)

    screen.fill((0, 0, 0))

    monkey.dibuja() #Dibuja el monkey

    un_coco = crearFruta(screen, WHITE) #Crea un coco
    una_banana = crearFruta(screen, BLUE) #Crea una banana
    lista_frutas.append(un_coco)
    lista_frutas.append(una_banana)

    for f in lista_frutas:     #Dibuja y mueve las bananas
        if checkLimitY(f):     #Revisa si la fruta ya alcanzo el piso de la pantalla
            lista_frutas.remove(f)
        else:
            #revisa si hubo colision, actualiza el contador y la bandera de paro
            (col, record) = colision(f, monkey)
            if col:
                lista_frutas.remove(f)

        record += record
        f.dibuja()
        f.mover()

    #Refresca la pantalla
    pygame.display.update()


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import argparse
import json
import sys
import configparser
import os
from io import open
import requests
from inscrawler import InsCrawler
from inscrawler.settings import override_settings
from inscrawler.settings import prepare_override_settings

def usage():
    return """
        python crawler.py posts -u cal_foodie -n 100 -o ./output
        python crawler.py posts_full -u cal_foodie -n 100 -o ./output
        python crawler.py profile -u cal_foodie -o ./output
        python crawler.py profile_script -u cal_foodie -o ./output
        python crawler.py hashtag -t taiwan -o ./output

        The default number for fetching posts via hashtag is 100.
    """

def get_posts_by_user(username, number, detail, debug):
    ins_crawler = InsCrawler(has_screen=debug)
    return ins_crawler.get_user_posts(username, number, detail)

def get_profile(username):
    ins_crawler = InsCrawler()
    return ins_crawler.get_user_profile(username)

def get_profile_from_script(username):
    ins_cralwer = InsCrawler()
    return ins_cralwer.get_user_profile_from_script_shared_data(username)

def get_posts_by_hashtags(tag, number, debug):
    ins_crawler = InsCrawler(has_screen=debug)

    ret=ins_crawler.get_latest_posts_by_tag(tag, number)

    return ins_crawler.get_latest_posts_by_tag(tag, number)

def arg_required(args, fields=[]):
    for field in fields:
        if not getattr(args, field):
            parser.print_help()
            sys.exit()

def output(data, filepath):
    out = json.dumps(data, ensure_ascii=False)
    if filepath:
        with open(filepath, "w", encoding="utf8") as f:
            f.write(out)
    else:
        print(out)

if __name__ == "__main__":

    output_folder='./output_folder/'

    filenames=os.listdir(output_folder)

    if os.path.isdir('./img_folder/'):
        pass
    else:
        os.mkdir('./img_folder/')

    for filename in filenames:
        print(filename)
        count=0
        with open('{}/{}'.format(output_folder,filename), 'r', encoding="utf8") as read_file:
            dict_data = json.load(read_file)
            hashtag=filename.split('.')[0]

            if os.path.isdir('./img_folder/{}'.format(hashtag)):
                pass
            else:
                os.mkdir('./img_folder/{}'.format(hashtag))

            #print(dict_data)
            for k in dict_data:
                print(k['img_url'])

                filename=k['img_url'].split('/')[-1].split('.')[0]
                if os.path.isfile("./img_folder/{}/{}.png".format(hashtag,filename)):
                    print("./img_folder/{}/{}.png".format(hashtag,filename),'exist')
                    continue
                response = requests.get(k['img_url'])
                file = open("./img_folder/{}/{}.png".format(hashtag,filename), "wb")
                file.write(response.content)
                file.close()
                count+=1
                #break


import paddle
import paddle.fluid as fluid
import paddle.dataset.mnist as mnist

def network(reader):
    img, label = fluid.layers.read_file(reader)
    # 用户自定义网络，此处以softmax回归为例
    predict = fluid.layers.fc(input=img, size=10, act='softmax')
    loss = fluid.layers.cross_entropy(input=predict, label=label)
    return fluid.layers.mean(loss)

# 新建 train_main_prog 和 train_startup_prog
train_main_prog = fluid.Program()
train_startup_prog = fluid.Program()
with fluid.program_guard(train_main_prog, train_startup_prog):
    # 使用 fluid.unique_name.guard() 实现与test program的参数共享
    with fluid.unique_name.guard():
        train_reader = fluid.layers.py_reader(capacity=64,
                                              shapes=[(-1, 1, 28, 28), (-1, 1)],
                                              dtypes=['float32', 'int64'],
                                              name='train_reader')
        train_reader.decorate_paddle_reader(
        paddle.reader.shuffle(paddle.batch(mnist.train(),
                              batch_size=5),
                              buf_size=500))
        train_loss = network(train_reader) # 一些网络定义
        adam = fluid.optimizer.Adam(learning_rate=0.01)
        adam.minimize(train_loss)

# Create test_main_prog and test_startup_prog
test_main_prog = fluid.Program()
test_startup_prog = fluid.Program()
with fluid.program_guard(test_main_prog, test_startup_prog):
    # 使用 fluid.unique_name.guard() 实现与train program的参数共享
    with fluid.unique_name.guard():
        test_reader = fluid.layers.py_reader(capacity=32,
                                             shapes=[(-1, 1, 28, 28), (-1, 1)],
                                             dtypes=['float32', 'int64'],
                                             name='test_reader')
        test_reader.decorate_paddle_reader(paddle.batch(mnist.test(), 512))
        test_loss = network(test_reader)

fluid.Executor(fluid.CUDAPlace(0)).run(train_startup_prog)
fluid.Executor(fluid.CUDAPlace(0)).run(test_startup_prog)

train_exe = fluid.ParallelExecutor(use_cuda=True,
    loss_name=train_loss.name, main_program=train_main_prog)
test_exe = fluid.ParallelExecutor(use_cuda=True,
    loss_name=test_loss.name, main_program=test_main_prog)
for epoch_id in range(10):
    train_reader.start()
    try:
        while True:
            train_exe.run(fetch_list=[train_loss.name])
    except fluid.core.EOFException:
        train_reader.reset()

test_reader.start()
try:
    while True:
        test_exe.run(fetch_list=[test_loss.name])
except fluid.core.EOFException:
    test_reader.reset()


# 지역 변수 - 함수 내에서 사용된 변수, 함수 내에서만 사용 가능

# def kim():
#     temp = "김과장의 함수"
#     print(temp)
#
# def lee():
#     temp = 2**10
#     return temp
#
# def park(a):
#     temp = a*2
#     print(temp)
#
# kim()
# print(lee())
# park(6)

# 전역 변수 - 어디서든 접근 가능한 변수, 탑 레벨에서 사용된 변수

# salerate = 0.9
#
# def kim():
#     print("오늘의 할인율 : ", salerate)
#
# def lee():
#     price = 1000
#     print("가격 : ", price * salerate)
#
# kim()
# salerate = 1.1
# lee()

# price = 1000
#
# def sale():
#     price = 500
#     print("sale", id(price))
#
# sale()
# print("global", id(price))

# 가급적 지역변수만 사용하라, 전역변수는 프로그램을 오동작시키는 근원이 된다
# price = 1000
#
# def sale():
#     global price # 전역 변수 선언
#     price = 500
#
# sale()
# print(price)

# 변수의 범위 - docstring, 함수의 도움말, help(함수명) 호출 시 출력될 문자열

# def calcsum(n):
#     """1~n 까지의 합계를 구해 리턴한다"""
#     total = 0
#     for i in range(n+1):
#         total += i
#
#     return total
#
# help(calcsum)

# def findMax(*ints):
#     max = ints[0]
#     for num in ints:
#       if(max < num):
#         max = num
#
#     return max
#
# num = findMax(4,5,1,3,2,-1,-7)
# print("max = ", num)

# def swap(x, y):
#     temp = y
#     y = x
#     x = temp
#
#     print("x", x)
#     print('y', y)
#
# a = 10
# b = 20
# swap(a, b)
#
# print('a', a)
# print("b", b)

# 함수는 스택이다
# Call by value - 복사본을 호출 한다
# 어떤 함수를 호출하든 스택 프레임이 생긴다

import random

# def rand(start, end):
#     """랜덤값 반환해주는 함수"""
#     return int(random.random()*(end-start)) + start
#     # return number
#
#
# def main(): # entry point
#     start = 1
#     end = 6
#     for n in range(5):
#         number = rand(start, end+1)
#         print(number)
#
#     help(rand)
#
# main()

import random

def rand(start, end):

    """랜덤값을 반환해주는 함수(시작 값, 끝 값)"""

    return int(random.random()*(end)) + start

def main():

    num = rand(1,100)
    cnt = 0
    print("com = ",num)

    for n in range(5):
        me = int(input(str(n+1)+ "번째 추측값 : "))

        if num < me:
            print(me,"보다는 작습니다.\n")
        elif num > me:
            print(me,"보다는 큽니다.\n")
        elif num == me:
            print("정답입니다")
            break

        cnt += 1

        if cnt == 5:
            print("실패했습니다. 정답은 ", num)
            break
main()


from bottle import get, post, run, request, response
from json import dumps

customers = {}

@get('/customers')
def all_customers():
    response.content_type = 'application/json'
    return dumps(customers.values())

@get('/customers/<name>')
def get_customer(name=None):
    customer = customers.get(name, None)
    if customer:
        return customer
    else:
        response.status = 404
        return 'Customer {} not found.'.format(name)

@post('/customers')
def create_customer():
    customer = request.json
    name = customer['name']
    customers[name] = customer
    return {'status': 'ok'}

customers['test1'] = {'name': 'test1', 'born': 1815}

run(host='0.0.0.0', port=8888, debug=True)


import torch
import os
import json
import math
from tqdm import tqdm
from torch.utils.data import DataLoader
from dataloader.Distribution import MNIST, MnistGenerator
from torch.utils import tensorboard
from architectures.simple_fc import SimpleFC
from utils import metrics, utilities, spline_utils
import matplotlib.pyplot as plt

class TrainerWasserstein:
    """
    """
    def __init__(self, config, device):
        self.config = config
        self.device = device

        # Prepare dataset classes
        train_dataset = MNIST(config['training_options']['train_dataset_file'])
        self.mnist_generator = MnistGenerator(device=self.device)

        print('Preparing the dataloaders')
        # Prepare dataloaders
        self.train_dataloader = DataLoader(train_dataset, batch_size=config["training_options"]["batch_size"], shuffle=True, num_workers=config["training_options"]["num_workers"], drop_last=True)
        self.batch_size = config["training_options"]["batch_size"]
        self.val_dataset, _ = torch.load(config['training_options']['val_dataset_file'])
        self.val_dataset = self.val_dataset.unsqueeze(1)

        print('Building the model')
        # Build the model

        self.model = SimpleFC(config['net_params'], **config['activation_fn_params'])
        self.model = self.model.to(device)

        print("Number of parameters in the model: ", self.model.get_num_params())

        print(self.model)

        # Set up the optimizer
        self.set_optimization()
        self.batch_size = config['training_options']['batch_size']
        self.epochs = config["training_options"]['epochs']

        # CHECKPOINTS & TENSOBOARD
        run_name = config['exp_name']
        self.checkpoint_dir = os.path.join(config['log_dir'], config["exp_name"], 'checkpoints')
        if not os.path.exists(self.checkpoint_dir):
            os.makedirs(self.checkpoint_dir)
        config_save_path = os.path.join(config['log_dir'], config["exp_name"], 'config.json')
        with open(config_save_path, 'w') as handle:
            json.dump(self.config, handle, indent=4, sort_keys=True)

        writer_dir = os.path.join(config['log_dir'], config["exp_name"], 'tensorboard_logs')
        self.writer = tensorboard.SummaryWriter(writer_dir)

        self.total_training_step = 0

    def set_optimization(self):
        """Initialize the optmizer"""

        params_list = [{'params': spline_utils.get_no_spline_coefficients(self.model), 'lr': self.config["optimizer"]["lr_weights"]}]
        if self.model.using_splines:
            params_list.append({'params': spline_utils.get_spline_coefficients(self.model), \
                                'lr': self.config["optimizer"]["lr_spline_coeffs"]})

            params_list.append({'params': spline_utils.get_spline_scaling_coeffs(self.model), \
                                'lr': self.config["optimizer"]["lr_spline_scaling_coeffs"]})

        self.optimizer = torch.optim.Adam(params_list)

    def train(self):

        best_val = 0.0
        self.model.train()
        for epoch in range(self.epochs+1):
            self.train_epoch(epoch)
            if epoch % 5 == 0:
                val_result = self.valid_epoch(epoch)

            # SAVE CHECKPOINT
            if val_result > best_val and epoch % 5 == 0:
                best_val = val_result
                self.save_checkpoint(epoch)

        self.writer.flush()
        self.writer.close()

    def train_epoch(self, epoch):
        """
        """
        self.model.train()

        log = {}
        tbar = tqdm(self.train_dataloader, ncols=135, position=0, leave=True)
        for batch_idx, data in enumerate(tbar):
            data_p1 = data[0].to(self.device).view(-1, 784) / 255
            data_p2 = self.mnist_generator(self.batch_size).view(-1, 784)
            self.optimizer.zero_grad()

            wassserstein_loss = -1 * (torch.mean(self.model(data_p1)) - torch.mean(self.model(data_p2)))

            # regularization
            regularization = torch.zeros_like(wassserstein_loss)
            if self.model.using_splines and self.config['activation_fn_params']['lmbda'] > 0:
                regularization = self.config['activation_fn_params']['lmbda'] * self.model.TV2()

            total_loss = wassserstein_loss + regularization
            total_loss.backward()
            self.optimizer.step()

            log['train_loss'] = total_loss.detach().cpu().item()

            if self.model.using_splines:
                spline_scaling_coeffs = torch.nn.utils.parameters_to_vector(spline_utils.get_spline_scaling_coeffs(self.model))
                log['spline_scaling_coeff_mean'] = torch.mean(spline_scaling_coeffs).cpu().item()
                log['spline_scaling_coeff_std'] = torch.std(spline_scaling_coeffs).cpu().item()

                # the step actually represents the amount of data that has been in the model
            self.wrt_step = self.total_training_step * self.batch_size
            self.write_scalars_tb(log)

            tbar.set_description('T ({}) | TotalLoss {:.5f} |'.format(epoch, log['train_loss']))
            self.total_training_step += 1

        return log

    def valid_epoch(self, epoch):

        self.model.eval()

        with torch.no_grad():
            data_p1 = self.val_dataset.to(self.device).view(-1, 784) / 255
            data_p2 = self.mnist_generator(6000).view(-1, 784)

            mean_wasserstein_distance = (torch.mean(self.model(data_p1)) - torch.mean(self.model(data_p2)))

            # METRICS TO TENSORBOARD
            self.wrt_mode = 'val'
            self.writer.add_scalar(f'{self.wrt_mode}/Mean Wasserstein Distance', mean_wasserstein_distance, epoch)

            # Log parameters
            if epoch == self.epochs and self.model.using_splines:
                j = 1
                for module in self.model.modules_linearspline:
                    x = module.grid_tensor
                    y = module.lipschitz_coefficients
                    figures_list = []
                    for kk in range(x.shape[0]):
                        if kk % 16 == 0:
                            fig, ax = plt.subplots()
                            ax.grid()
                        ax.plot(x[kk,:].cpu().numpy(), y[kk,:].cpu().numpy())
                        if kk % 16 == 0:
                            figures_list.append(fig)
                            plt.close()
                    self.writer.add_figure(' Activation functions layer ' + str(j), figures_list, global_step=epoch)
                    j += 1

        return mean_wasserstein_distance

    def write_scalars_tb(self, logs):
        for k, v in logs.items():
            self.writer.add_scalar(f'train/{k}', v, self.wrt_step)

    def save_checkpoint(self, epoch):
        state = {
            'epoch': epoch,
            'state_dict': self.model.state_dict(),
            'optimizer': self.optimizer.state_dict(),
            'config': self.config
        }

        print('Saving a checkpoint:')
        filename = self.checkpoint_dir + '/checkpoint_best_epoch.pth'
        torch.save(state, filename)


import sys
import unittest
from sdl2 import SDL_Init, SDL_Quit, SDL_INIT_EVERYTHING
from sdl2 import clipboard
from sdl2.stdinc import SDL_TRUE

def is_win_or_mac():
    return sys.platform in ("win32", "cygwin", "darwin")

class SDLClipboardTest(unittest.TestCase):
    __tags__ = ["sdl"]

    @classmethod
    def setUpClass(cls):
        SDL_Init(SDL_INIT_EVERYTHING)

    @classmethod
    def tearDownClass(cls):
        SDL_Quit()

    # @unittest.skipIf(not is_win_or_mac(), "we would need a SDL window")
    # @interactive()
    # def test_SDL_HasClipboardText(self):
    #     doprint("Please put some text on the clipboard")
    #     self.assertEqual(clipboard.SDL_HasClipboardText(), SDL_TRUE)

    # @unittest.skipIf(not is_win_or_mac(), "we would need a SDL window")
    # @interactive("Does the shown value match the clipboard content?")
    # def test_SDL_GetClipboardText(self):
    #     doprint("Please put some text on the clipboard")
    #     retval = clipboard.SDL_GetClipboardText()
    #     doprint("Clipboard content: '%s'" % retval)

    # @unittest.skipIf(not is_win_or_mac(), "we would need a SDL window")
    # def test_SDL_SetClipboardText(self):
    #     self.assertEquals(clipboard.SDL_SetClipboardText(b"Test content"), 0)
    #     retval = clipboard.SDL_GetClipboardText()
    #     self.assertEqual(retval, b"Test content")

    #     self.assertEquals(clipboard.SDL_SetClipboardText(b""), 0)
    #     retval = clipboard.SDL_GetClipboardText()
    #     self.assertEqual(retval, b"")

    #     self.assertEquals(clipboard.SDL_SetClipboardText(b"Test content"), 0)
    #     retval = clipboard.SDL_GetClipboardText()
    #     self.assertEqual(retval, b"Test content")

    #     self.assertEquals(clipboard.SDL_SetClipboardText(None), 0)
    #     retval = clipboard.SDL_GetClipboardText()
    #     self.assertEqual(retval, b"")

if __name__ == '__main__':
    sys.exit(unittest.main())


import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision.models as models
from project.models import sew_resnet
from spikingjelly.clock_driven import neuron, functional, surrogate, layer
from einops import rearrange

def get_encoder(in_channels: int) -> nn.Module:
    resnet18 = models.resnet18(progress=True)

    resnet18.fc = nn.Identity()

    if in_channels != 3:
        resnet18.conv1 = nn.Conv2d(
            in_channels,
            64,
            kernel_size=(7, 7),
            stride=(2, 2),
            padding=(3, 3),
            bias=False,
        )

    return resnet18

def get_encoder_3d(in_channels: int) -> nn.Module:
    # resnet18 = models.video.r3d_18()
    resnet18 = models.video.mc3_18()
    resnet18.fc = nn.Identity()
    resnet18.stem[0] = nn.Conv3d(
        in_channels=in_channels,
        out_channels=64,
        kernel_size=(3, 7, 7),
        stride=(1, 2, 2),
        padding=(1, 3, 3),
        bias=False,
    )
    # resnet18 = models.video.r2plus1d_18()
    # resnet18.fc = nn.Identity()
    # resnet18.stem[0] = nn.Conv3d(in_channels=in_channels, out_channels=45, kernel_size=(1,7,7), stride=(1,2,2), padding=(0,3,3), bias=False)
    return resnet18

def get_projector(in_channels: int = 512):
    projector = nn.Sequential(
        nn.Linear(in_channels, 3 * in_channels),
        nn.BatchNorm1d(3 * in_channels),
        nn.ReLU(),
        nn.Linear(3 * in_channels, 3 * in_channels),
        nn.BatchNorm1d(3 * in_channels),
        nn.ReLU(),
        nn.Linear(3 * in_channels, 3 * in_channels),
    )

    return projector

def get_encoder_snn(in_channels: int, T: int):
    resnet18 = sew_resnet.MultiStepSEWResNet(
        block=sew_resnet.MultiStepBasicBlock,
        layers=[2, 2, 2, 2],
        zero_init_residual=True,
        T=T,
        cnf="ADD",
        multi_step_neuron=neuron.MultiStepIFNode,
        detach_reset=True,
        surrogate_function=surrogate.ATan(),
    )

    if in_channels != 3:
        resnet18.conv1 = nn.Conv2d(
            in_channels,
            64,
            kernel_size=(7, 7),
            stride=(2, 2),
            padding=(3, 3),
            bias=False,
        )

    return resnet18

class ConvEnc(nn.Module):
    def __init__(self, in_channels: int, timesteps: int, mode="snn"):
        super(ConvEnc, self).__init__()
        if mode not in ("3dcnn", "snn", "cnn"):
            raise ValueError(
                f"Mode must be one of these values: ('3dcnn', 'snn', 'cnn'). Got:{mode}"
            )

        if mode == "3dcnn":
            self.encoder = get_encoder_3d(in_channels=in_channels)
        elif mode == "snn":
            self.encoder = get_encoder_snn(in_channels=in_channels, T=timesteps)
        else:  # cnn
            self.encoder = get_encoder(in_channels=in_channels * timesteps)

        self.mode = mode
        self.timesteps = timesteps

    def forward(self, x):
        # x = B,T,C,H,W

        if self.mode == "snn":
            functional.reset_net(self.encoder)
            x = x.permute(1, 0, 2, 3, 4)  # from (B,T,C,H,W) to (T, B, C, H, W)
        elif self.mode == "3dcnn":
            x = x.permute(0, 2, 1, 3, 4)  # from (T,B,C,H,W) to (B,C,T,H,W)
        elif self.mode == "cnn" and len(x.shape) == 5:
            x = rearrange(
                x,
                "batch time channel height width -> batch (time channel) height width",
            )

        x = self.encoder(x)
        return x


import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import Tensor
import math
from typing import Optional, Tuple

def scaled_dot_product_attention(
    query: Tensor,
    key: Tensor,
    value: Tensor,
    mask: Optional[Tensor] = None,
    dropout: Optional[nn.Dropout] = None
) -> Tuple[Tensor, Tensor]:
    d_k: int = query.size(-1)
    scores: torch.Tensor = (query @ key.transpose(-2, -1)) / math.sqrt(d_k)

    if mask is not None:
        scores = scores.masked_fill(mask == 0, float('-inf'))

    weights: Tensor = F.softmax(scores, dim=-1)

    if dropout is not None:
        weights = dropout(weights)

    output: Tensor = weights @ value

    return output, weights

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model: int, num_heads: int, dropout: float = 0.1):
        super().__init__()
        assert d_model % num_heads == 0, "d_model must be divisible by num_heads"

        self.d_model = d_model
        self.d_k = d_model // num_heads
        self.num_heads = num_heads

        self.query_proj = nn.Linear(d_model, d_model)
        self.key_proj = nn.Linear(d_model, d_model)
        self.value_proj = nn.Linear(d_model, d_model)

        self.dropout = nn.Dropout(dropout)

        self.out_proj = nn.Linear(d_model, d_model)

    def forward(self, query: Tensor, key: Tensor, value: Tensor, mask: Optional[Tensor] = None) -> Tensor:
        batch_size = query.size(0)

        # Linear projection split into h heads
        query = self.query_proj(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        key = self.key_proj(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
        value = self.value_proj(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)

        if mask is not None:
            mask = mask.unsqueeze(1) # Same mask applied to all h heads.

        # Apply scaled dot product attention
        attention_output, _ = scaled_dot_product_attention(query, key, value, mask, self.dropout)

        attention_output = attention_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
        output = self.out_proj(attention_output)

        return output

class PositionalEncoding(nn.Module):
    def __init__(self, d_model: int, max_len: int = 5000, dropout: float = 0.1):
        super().__init__()
        self.dropout = nn.Dropout(dropout)
        self.encoding = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) # 10000.0 Hyperparameter (increase for larger sentences, decrease for smaller sentences)

        self.encoding[:, 0::2] = torch.sin(position * div_term)
        self.encoding[:, 1::2] = torch.cos(position * div_term)
        self.encoding = self.encoding.unsqueeze(0).transpose(0, 1)

    def forward(self, x: Tensor) -> Tensor:
        x = x + self.encoding[:x.size(0), :].detach()
        return self.dropout(x)

class TransformerEncoderLayer(nn.Module):
    def __init__(self, d_model: int, num_heads: int, dim_feedforward: int = 2048, dropout: float = 0.1):
        super().__init__()
        self.self_attn = MultiHeadAttention(d_model, num_heads, dropout)

        # Position-wise feedforward network
        self.feed_forward = nn.Sequential(
            nn.Linear(d_model, dim_feedforward),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(dim_feedforward, d_model),
        )

        # Layer normalization
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)

        # Dropout
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)

    def forward(self, src: Tensor, src_mask: Optional[Tensor] = None) -> Tensor:
        # Self-attention
        attn_output = self.self_attn(src, src, src, src_mask)[0]
        src = src + self.dropout1(attn_output)
        src = self.norm1(src)

        # Feed-forward network
        ff_output = self.feed_forward(src)
        src = src + self.dropout2(ff_output)
        src = self.norm2(src)

        return src

class TransformerEncoder(nn.Module):
    def __init__(self, num_layers: int, d_model: int, num_heads: int, dim_feedforward: int, dropout: float):
        super().__init__()
        self.layers = nn.ModuleList([
            TransformerEncoderLayer(d_model, num_heads, dim_feedforward, dropout) for _ in range(num_layers)
        ])
        self.norm = nn.LayerNorm(d_model)

    def forward(self, src: Tensor, mask: Optional[Tensor] = None) -> Tensor:
        for layer in self.layers:
            src = layer(src, src_mask=mask)

        return self.norm(src)

class TransformerDecoderLayer(nn.Module):
    def __init__(self, d_model: int, num_heads: int, dim_feedforward: int= 2048, dropout: float = 0.1):
        super().__init__()
        self.masked_attn_head = MultiHeadAttention(d_model, num_heads, dropout)
        self.attn_head_over_encoder = MultiHeadAttention(d_model, num_heads, dropout)

        # Position-wise feedforward network
        self.feed_forward = nn.Sequential(
            nn.Linear(d_model, dim_feedforward),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(dim_feedforward, d_model)
        )

        # Layer normalization
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
        self.norm3 = nn.LayerNorm(d_model)

        # Dropout
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.dropout3 = nn.Dropout(dropout)

    def forward(self, tgt: Tensor, memory: Tensor, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None) -> Tensor:
        # Masked multi-head attention
        tgt2 = self.masked_attn_head(tgt, tgt, tgt, tgt_mask)[0]
        tgt = tgt + self.dropout1(tgt2)
        tgt = self.norm1(tgt)

        # Multi-head attention over encoder output
        tgt2 = self.attn_head_over_encoder(tgt, memory, memory, memory_mask)[0]
        tgt = tgt + self.dropout2(tgt2)
        tgt = self.norm2(tgt)

        # Feed-forward network
        tgt2 = self.feed_forward(tgt)
        tgt = tgt + self.dropout3(tgt2)
        tgt = self.norm3(tgt)

        return tgt

class TransformerDecoder(nn.Module):
    def __init__(self, num_layers: int, d_model: int, num_heads: int, dim_feedforward: int, dropout: float):
        super().__init__()
        self.layers = nn.ModuleList([
            TransformerDecoderLayer(d_model, num_heads, dim_feedforward, dropout) for _ in range(num_layers)
        ])
        self.norm = nn.LayerNorm(d_model)

    def forward(self, tgt: Tensor, memory: Tensor, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None) -> Tensor:
        for layer in self.layers:
            tgt = layer(tgt, memory, tgt_mask, memory_mask)

        return self.norm(tgt)

class Transformer(nn.Module):
    def __init__(self, num_encoder_layers: int, num_decoder_layers: int, d_model: int, num_heads: int,
                 dim_feedforward: int, dropout: float, input_vocab_size: int, target_vocab_size: int):
        super().__init__()
        self.encoder = TransformerEncoder(num_encoder_layers, d_model, num_heads, dim_feedforward, dropout)
        self.decoder = TransformerDecoder(num_decoder_layers, d_model, num_heads, dim_feedforward, dropout)
        self.src_tok_emb = nn.Embedding(input_vocab_size, d_model)
        self.tgt_tok_emb = nn.Embedding(target_vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model)
        self.final_linear = nn.Linear(d_model, target_vocab_size)

    def forward(self, src: Tensor, tgt: Tensor, src_mask: Optional[Tensor], tgt_mask: Optional[Tensor],
                memory_mask: Optional[Tensor], src_key_padding_mask: Optional[Tensor],
                tgt_key_padding_mask: Optional[Tensor], memory_key_padding_mask: Optional[Tensor]) -> Tensor:
        src_emb = self.positional_encoding(self.src_tok_emb(src))
        tgt_emb = self.positional_encoding(self.tgt_tok_emb(tgt))
        memory = self.encoder(src_emb, mask=src_mask, src_key_padding_mask=src_key_padding_mask)
        outs = self.decoder(tgt_emb, memory, tgt_mask=tgt_mask, memory_mask=memory_mask,
                            tgt_key_padding_mask=tgt_key_padding_mask, memory_key_padding_mask=memory_key_padding_mask)
        return F.softmax(self.final_linear(outs), dim=-1)