import tkinter as tk
from random import randrange,choice
from initialisation import initialise
from gurobipy import *
import numpy as np
from contraintes import *
from objectif import *
from lirexcel import lirexcel, lirexcel2, reduction
from affichage import affiche_texte, affiche_avion
from tk_ffichage import new_aff
from postprocessing import post_traitement,dimension
from comparaison import verif_enfants,barycentre2

N = 29
P = 6

def dyna_ffichage(N, P,K,tab,vrai_ind,scenario):
    """
    Permet de s'occuper de la partie dynamique en générant l'interface graphique
     et en affectant les groupes aux places qu'ils ont choisit
    """
    choix_ind={}
    X_nouveau=[[[0 for k in range(K)]for j in range(P)]for i in range(N)]
    groupes={}
    ind=reduction(scenario, vrai_ind)
    groupe_compteur=1
    for indiv in ind:
        if indiv.idgroupe not in groupes:
            groupes[indiv.idgroupe]=[indiv]
        else :
            groupes[indiv.idgroupe].append(indiv)

    place_ind={}
    for i in range(N):
        for j in range(P):
            if sum([(k+1)*tab[i][j][k] for k in range(K)])>0:
                place_ind[sum([k*tab[i][j][k] for k in range(K)])]=(i,j)
    places_associes={}
    for k in range(K):
            places_associes[place_ind[k]]=list(set([place_ind[l.id] for l in ind[k].groupe]))

    issue_de_secours=list(set([sum([individu.idgroupe*tab[11,j,individu.id] for individu in ind]) for j in range(6) if sum([individu.idgroupe*tab[11,j,individu.id] for individu in ind])>0]))
    nombre_groupe_secours=[sum([1 for gr in issue_de_secours if len(groupes[gr])==card])for card in range(0,4)]

    def enfant(groupe):
        """
        Renvoie True si des enfants sont dans le groupe
        et False sinon
        """
        for ind in groupe:
            if ind.categorie=='E':
                return True
        return False

    def dernier_groupe_adulte(g,nb):
        """
        Renvoie True si il reste moins de nb groupe de même taille que g avec seulement des enfants
         et False sinon
        """
        if nb==0:
            return False
        for gr in groupes:
            if gr>g and len(groupes[g])==len(groupes[gr]) and not(enfant(groupes[gr])):
                if nb==1:
                    return False
                else:
                    return dernier_groupe_adulte(gr,nb-1)
        return True

    def calculer(g):
        """
        Détermine les places qui peuvent être choisi par le groupe g
        """
        places = []
        places_enfants = []
        if 'R' in [individu.categorie for individu in groupes[g]] or 'B' in [individu.categorie for individu in groupes[g]]:
            return [[place_ind[individu.id] for individu in groupes[g]]]

        for gr in groupes:
            if groupes[gr][0].classe==groupes[g][0].classe and len(groupes[gr])==len(groupes[g]) and groupes[g][0].classe==groupes[gr][0].classe and 'R' not in [individu.categorie for individu in groupes[gr]] and 'B' not in [individu.categorie for individu in groupes[gr]]:
                positions=[place_ind[individu.id] for individu in groupes[gr]]
                if sum([sum([k*X_nouveau[i][j][k] for i,j in positions])for k in range(K)])==0:
                    places.append(positions)
                    if sum([i==11 for (i,j) in positions])==0:
                        places_enfants.append(positions)
        if enfant(groupes[g]):
            return places_enfants
        elif not(dernier_groupe_adulte(g,nombre_groupe_secours[len(groupes[g])])):
            return places
        else:
            return [p for p in places if sum([i==11 for (i,j) in p])>0]
    root = tk.Tk()
    root.title('Navion')
    root.iconbitmap('data/navion.ico')

    canvas = tk.Canvas(root, width=30 * N + 10 * (N + 1), height=30 * (P + 1) + 10 * (P + 2))
    canvas.pack()

    canvas.create_rectangle(0, 0, 30 * N + 10 * (N + 1), 30 * (P + 1) + 10 * (P + 2), fill='#3C3C4B')

    places = [[canvas.create_rectangle(10 + i * 40, 10 + (j + j // 3) * 40, 10 + i * 40 + 30, 10 + (j + j // 3) * 40 + 30, width=0, fill='#465582') for j in range(P)] for i in range(N)]

    #tailles_groupes = [0] * ind[-1].idgroupe#non continuite groupe
    #ind_repr=[0]*ind[-1].idgroupe
    #for i in ind:
    #    tailles_groupes[i.idgroupe - 1] = len(i.groupe) + 1
    #    ind_repr[i.idgroupe-1]=i

    places_proposees = calculer(1)#changer liste de liste
    possibilite=[]
    for sous_groupe in places_proposees:
        for i,j in sous_groupe:
            canvas.itemconfig(places[i][j], fill='#FF0000')
            possibilite.append((i,j))

    groupe_compteur = 1

    def aleatoire_command():
        """
        Affecte aléatoirement un groupe à une place qu'il peut choisir
        """
        nonlocal possibilite
        nonlocal places_proposees
        nonlocal X_nouveau
        nonlocal groupe_compteur
        nonlocal places_rempli

        (i,j)=choice(possibilite)
        places_prises=[(i,j)]+places_associes[(i,j)]
        places_rempli=places_rempli+places_prises
        secours=0
        if 'R' in [individu.categorie for individu in groupes[groupe_compteur]] or 'B' in [individu.categorie for individu in groupes[groupe_compteur]]:
            for individu in groupes[groupe_compteur]:
                choix_ind[individu]=len(possibilite)
                for i in range(N):
                    for j in range(P):
                        X_nouveau[i][j][individu.id]=tab[i][j][individu.id]
        else:
            for id in range(len(places_prises)):
                choix_ind[groupes[groupe_compteur][id].id]=len(possibilite)
                i2,j2=places_prises[id]
                if i2==11 and secours==0:
                    secours+=1
                    nombre_groupe_secours[len(groupes[groupe_compteur])]=nombre_groupe_secours[len(groupes[groupe_compteur])]-1
                canvas.itemconfig(places[i2][j2], fill='#00FF00')
                X_nouveau[i2][j2][groupes[groupe_compteur][id].id]=1
        groupe_compteur+=1
        while groupe_compteur not in groupes and groupe_compteur<50000:
            groupe_compteur+=1

        if groupe_compteur>=40000:
            root.destroy()
            new_aff(N,P,np.array(X_nouveau),vrai_ind)
            print(barycentre2(np.array(X_nouveau),vrai_ind))
            verif_enfants(np.array(X_nouveau),vrai_ind)
            return choix_ind
        else:
            groupe_label['text'] ='Groupe ' +str(groupe_compteur)+' comprenant '+str(len(groupes[groupe_compteur]))+ ' personnes'

        places_proposees = calculer(groupe_compteur)
        possibilite=[]
        for sous_groupe in places_proposees:
            for i3,j3 in sous_groupe:
                if (i3,j3) not in places_rempli:
                    canvas.itemconfig(places[i3][j3], fill='#FF0000')
                    possibilite.append((i3,j3))
        for i4 in range(N):
            for j4 in range(P):
                if (i4, j4) in possibilite:
                    canvas.itemconfig(places[i4][j4], fill='#FF0000')
                elif (i4, j4) in places_rempli:
                    canvas.itemconfig(places[i4][j4], fill='#00FF00')
                else:
                    canvas.itemconfig(places[i4][j4], fill='#465582')

    aleatoire_button = tk.Button(root, text='Choix aléatoire', command=aleatoire_command)
    aleatoire_button.pack()

    def opti_command():
        """
        Trouve une solution optimale après que certains groupes aient choisi leurs places
        """
        root.destroy()
        nonlocal groupe_compteur
        nonlocal X_nouveau
        m2=Model()
        X_opti=initialise(m2,N,P,K)
        m2.update()
        barycentre(m2,X_opti,ind,N,P,K)
        unicite_personne(m2,X_opti,N,P,K)
        unicite_siege(m2,X_opti,N,P,K)
        chef_de_groupe(m2, X_opti, ind)
        #symetrie(m,X,ind,N,P,K)
        for i in range(N):
            for j in range(P):
                for k in range(K):
                    m2.addConstr(X_opti[i,j,k]>=X_nouveau[i][j][k])
        chaises_roulantes(m2, X_opti, ind)
        civieres(m2, X_opti, ind)
        nenfants(m2,X_opti,ind)
        taille=lutte_des_classes(m2,X_opti,ind)
        fct_objectif(m2, X_opti, ind, [0,2,2])
        m2.update()
        m2.optimize()

        post_traitement(m2, X_opti, ind, [False, False, False])
        m2.setObjective(bonus_groupe3(m2, X_opti, ind, [True, True]), GRB.MINIMIZE) # bonus_groupe3 quadratique
        m2.update()
        m2.optimize()
        new_aff(N,P,X_opti.x,vrai_ind)

    opti_button = tk.Button(root, text='Choix optimal', command=opti_command)
    opti_button.pack()

    places_rempli = []

    groupe_label = tk.Label(root, text='Groupe ' +str(groupe_compteur)+' comprenant '+str(len(groupes[groupe_compteur]))+ ' personnes')
    groupe_label.pack()

    def onclick(event):
            """
            Affecte à un groupe les places sur lesquelles ils ont cliqué
            si ces places sont bien proposé à ce groupe
            """
            nonlocal possibilite
            nonlocal places_proposees
            nonlocal X_nouveau
            nonlocal groupe_compteur
            nonlocal places_rempli

            i = -1
            j = -1

            if event.x % 40 <= 10:
                print('x en dehors d\'une place')
            else:
                i = event.x // 40
                print(f'i = {i}')

            if event.y % 40 <= 10 or 120 <= event.y <= 170:
                print('y en dehors d\'une place')
            else:
                j = event.y // 40
                if j >= 3:
                    j -= 1
                print(f'j = {j}')

            if i >= 0 and j >= 0:
                if (i, j) in possibilite:
                    places_prises=[(i,j)]+places_associes[(i,j)]
                    places_rempli=places_rempli+places_prises
                    secours=0
                    if 'R' in [individu.categorie for individu in groupes[groupe_compteur]] or 'B' in [individu.categorie for individu in groupes[groupe_compteur]]:
                        for individu in groupes[groupe_compteur]:
                            choix_ind[individu]=len(possibilite)
                            for i in range(N):
                                for j in range(P):
                                    X_nouveau[i][j][individu.id]=tab[i][j][individu.id]
                    else:

                        for id in range(len(places_prises)):
                            choix_ind[groupes[groupe_compteur][id].id]=len(possibilite)
                            i2,j2=places_prises[id]
                            if i2==11 and secours==0:
                                secours+=1
                                nombre_groupe_secours[len(groupes[groupe_compteur])]=nombre_groupe_secours[len(groupes[groupe_compteur])]-1
                            canvas.itemconfig(places[i2][j2], fill='#00FF00')
                            X_nouveau[i2][j2][groupes[groupe_compteur][id].id]=1
                    groupe_compteur+=1
                    while groupe_compteur not in groupes and groupe_compteur<50000:
                        groupe_compteur+=1

                    if groupe_compteur>=40000:
                        root.destroy()
                        new_aff(N,P,np.array(X_nouveau),vrai_ind)
                        verif_enfants(np.array(X_nouveau),vrai_ind)
                        print(barycentre2(np.array(X_nouveau),vrai_ind))
                        return choix_ind
                    else:
                        groupe_label['text'] ='Groupe ' +str(groupe_compteur)+' comprenant '+str(len(groupes[groupe_compteur]))+ ' personne' + ('s' if len(groupes[groupe_compteur]) > 1 else '')

                        for k in groupes[groupe_compteur]:
                            if k.categorie == 'B':
                                groupe_label['text'] += '(B)'
                                break

                        for k in groupes[groupe_compteur]:
                            if k.categorie == 'R':
                                groupe_label['text'] += '(R)'
                                break

                        for k in groupes[groupe_compteur]:
                            if 0 < k.transit <= 90:
                                groupe_label['text'] += '(t)'
                                break

                        for k in groupes[groupe_compteur]:
                            if k.classe == 1:
                                groupe_label['text'] += '(b)'
                                break

                    places_proposees = calculer(groupe_compteur)
                    possibilite=[]
                    for sous_groupe in places_proposees:
                        for i3,j3 in sous_groupe:
                            if (i3,j3) not in places_rempli:
                                canvas.itemconfig(places[i3][j3], fill='#FF0000')
                                possibilite.append((i3,j3))
                    for i4 in range(N):
                        for j4 in range(P):
                            if (i4, j4) in possibilite:
                                canvas.itemconfig(places[i4][j4], fill='#FF0000')
                            elif (i4, j4) in places_rempli:
                                canvas.itemconfig(places[i4][j4], fill='#00FF00')
                            else:
                                canvas.itemconfig(places[i4][j4], fill='#465582')

    def entree(event):
            """
            Colore en bleu les places qui seront sélectionnées
            si l'utilisateur clique sur cette place
            """
            i = -1
            j = -1

            if not(event.x % 40 <= 10):

                i = event.x // 40

            if not(event.y % 40 <= 10 or 120 <= event.y <= 170):

                j = event.y // 40
                if j >= 3:
                    j -= 1

            if i>=0 and j>=0 and (i,j) in places_associes.keys():
                places_prises=[(i,j)]+places_associes[(i,j)]
                for i2 in range(N):
                    for j2 in range(P):
                        if (i2,j2) in places_prises and (i2,j2) in possibilite:
                            for (i3,j3) in places_prises:
                                canvas.itemconfig(places[i3][j3], fill='#0000FF')

                        elif (i2, j2) in possibilite:
                            canvas.itemconfig(places[i2][j2], fill='#FF0000')
                        elif (i2, j2) in places_rempli:
                            canvas.itemconfig(places[i2][j2], fill='#00FF00')
                        else:
                            canvas.itemconfig(places[i2][j2], fill='#465582')

    canvas.bind('<Button-1>', onclick)
    canvas.bind('<Motion>',entree)

    root.mainloop()
    return(choix_ind)

if __name__ == '__main__':
    from lirexcel import lirexcel2

    m=Model()
    ind=lirexcel2(scenario)
    ind_reduit=reduction(scenario, ind)
    K=len(ind)
    X=initialise(m,N,P,K)
    m.update()
    barycentre_restreint(m,X,ind_reduit,N,P,K)
    unicite_personne(m,X,N,P,K)
    unicite_siege(m,X,N,P,K)
    chef_de_groupe(m, X, ind_reduit)
    #symetrie(m,X,ind,N,P,K)
    chaises_roulantes(m, X, ind_reduit)
    civieres(m, X, ind_reduit)
    nenfants(m,X,ind_reduit)
    taille=lutte_des_classes(m,X,ind_reduit)
    fct_objectif(m, X, ind_reduit, [0,2,2])
    m.update()
    m.optimize()
    tab=X.x

    post_traitement(m, X, ind_reduit, [False, False, False])
    m.setObjective(bonus_groupe3(m, X, ind_reduit, [True, True]), GRB.MINIMIZE) # bonus_groupe3 quadratique
    m.update()
    m.optimize()
    dyna_ffichage(30, 6, K, X.x, ind)


import time
from datetime import datetime
from eth_utils import to_checksum_address

def config_t(network):

  config = {}

  tokens_per_wei = [350, 300]

  tranches_quantity = len(tokens_per_wei)

  amounts = [3500000, 10500000]

  pre_ico_tranches_quantity = 1

  ico_tranches_quantity = tranches_quantity - pre_ico_tranches_quantity

  def toWei(x):
    return x*(10**18)

  amounts = list(map(toWei, amounts))

  config['tranches'] = []

  #Values for testing purposes only
  if (network != "liveNet"):

    config['multisig_owners'] = [to_checksum_address("0xf19258256b06324c7516b00bf5c76af001ee1e95")]

    config['startTime'] = int(round(time.time())) + 30

    pre_ico_tranches_start = config['startTime'] - 30

    pre_ico_tranches_end = config['startTime']

    ico_tranches_start = config['startTime']

    ico_tranches_end = ico_tranches_start + 60 * 60 * 24 * 2

    for x in range(pre_ico_tranches_quantity):
      config['tranches'].append(amounts[x])
      config['tranches'].append(pre_ico_tranches_start)
      config['tranches'].append(pre_ico_tranches_end)
      config['tranches'].append(tokens_per_wei[x])

    for x in range(pre_ico_tranches_quantity, tranches_quantity):
      config['tranches'].append(amounts[x])
      config['tranches'].append(ico_tranches_start)
      config['tranches'].append(ico_tranches_end)
      config['tranches'].append(tokens_per_wei[x])
      ico_tranches_end += 60*60*24

    config['endTime'] = config['tranches'][-2]
  else:
    #Main net configuration
    config['startTime'] = int(datetime(2017, 11, 23).timestamp())
    config['endTime'] = int(datetime(2018, 2, 20).timestamp())
    config['MW_address'] = "0xA8c39c22822a89bC8EAC413a1FFb93b73fb9c906"

    tranches_start = int(datetime(2017, 11, 19, 18).timestamp())

    tranches_end = [int(datetime(2017, 11, 30).timestamp()),
                    int(datetime(2017, 12, 7).timestamp()),
                    int(datetime(2017, 12, 18).timestamp()),
                    int(datetime(2017, 12, 25).timestamp()),
                    int(datetime(2018, 1, 1).timestamp()),
                    int(datetime(2018, 1, 8).timestamp()),
                    int(datetime(2018, 1, 15).timestamp()),
                    int(datetime(2018, 1, 22).timestamp()),
                    int(datetime(2018, 2, 2).timestamp()),
                    int(datetime(2018, 2, 14).timestamp()),
                    config['endTime']]

    for x in range(0, tranches_quantity):
      config['tranches'].append(amounts[x])
      config['tranches'].append(tranches_start)
      config['tranches'].append(tranches_end[x])
      config['tranches'].append(tokens_per_wei[x])

  return config


"""recreate tables maint_event and enum.state

Revision ID: f5a7cb7c1961
Revises: ce7304f169eb
Create Date: 2021-12-18 18:21:58.515453

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'f5a7cb7c1961'
down_revision = 'ce7304f169eb'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('maintenance_event',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('title', sa.String(length=100), nullable=False),
    sa.Column('content', sa.Text(), nullable=False),
    sa.Column('photo_url', sa.String(length=255), nullable=True),
    sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
    sa.Column('updated_on', sa.DateTime(), nullable=True),
    sa.Column('status', sa.Enum('pending', 'closed', 'raised', name='state'), nullable=False),
    sa.Column('home_owner_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['home_owner_id'], ['home_owner.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('maintenance_event')
    # ### end Alembic commands ###


import random

class Transcoder:
    """
        转码器类，因为密码机只运算数字，所以先把字符转换为数字，再输入密码机中运算
        之后把密码机的输出还原为字符。
        只支持 26 个大写字母
    """
    def __init__(self) -> None:
        self.num_range = 26

    def char_to_int(self, char):
        return ord(char) - ord('A')

    def int_to_char(self, num):
        return chr(ord('A') + num)

class AsciiTranscoder:
    """
    支持 256 个 ASCII 字符
    """
    def __init__(self) -> None:
        self.num_range = 256

    def char_to_int(self, char):
        return ord(char)

    def int_to_char(self, num):
        return chr(num)

class Enigma:
    """
        enigma 类 具体构造参考附录的原理图
    """
    def __init__(self, transcoder_class=Transcoder, rotor_num=3, rotor_cursors=[], plugboard_pairs={}) -> None:
        """
            args:
                transcoder: Transcoder 类实例，用于转换数字和字符
                roter_num: int 转子数量，默认为3，默认
                roter_cursors: 转子初始位置
                plugboard_pairs: plugboard 交换的字母对
        """
        self.transcoder = transcoder_class()
        num_range = self.transcoder.num_range

        if not rotor_cursors:
            rotor_cursors = [0 for i in range(rotor_num)]

        self.num_range = num_range
        self.rotor_num = rotor_num
        self.init_cursors = list(rotor_cursors)  # 复制一份
        self.pb = Plugboard(swap_pairs=plugboard_pairs, num_range=num_range)
        self.rotors = [Rotor(cursor=rotor_cursors[i], num_range=num_range) for i in range(rotor_num)]
        self.rf = Reflector(num_range=num_range)

    def reset_cursors(self):
        """
            重置所有转子
        """
        for i in range(self.rotor_num):
            self.rotors[i].set_cursor(self.init_cursors[i])

    def input(self, text):
        """
            加密主要流程
        """
        self.reset_cursors()
        rv = []
        for char in text:
            num = self.transcoder.char_to_int(char)
            num = self.pb.input_num(num)

            for i in range(self.rotor_num):
                num = self.rotors[i].input_num(num)

            num = self.rf.input_num(num)

            for i in range(self.rotor_num):
                num = self.rotors[self.rotor_num - 1 - i].input_num_reversed(num)

            num = self.pb.input_num(num)
            rv.append(num)

            # 转动转子
            is_rotated = False
            for i in range(self.rotor_num):
                if i == 0:
                    is_rotated = self.rotors[i].rotate()
                else:
                    if is_rotated:
                        is_rotated = self.rotors[i].rotate()

        return ''.join([self.transcoder.int_to_char(n) for n in rv])

class BetterEnigma(Enigma):
    def __init__(self, transcoder_class=Transcoder, rotor_num=3, rotor_cursors=[], plugboard_pairs={}) -> None:
        super().__init__(transcoder_class=transcoder_class, rotor_num=rotor_num, rotor_cursors=rotor_cursors, plugboard_pairs=plugboard_pairs)
        # 一个字母永远不会被加密成它自身，问题出在 reflector
        # 这里采用 BetterReflector 替换
        self.rf = BetterReflector(num_range=self.transcoder.num_range)

class Rotor:

    def __init__(self, scrambled_array=None, cursor=0, num_range=26) -> None:
        """
            Rotor （转子） 的初始化方法

            初始化过程中，根据数列生成一个乱序的 scrambled_array = [8, 2, 1, 3, ...]
            或者 scrambled_array 可以由用户提供

            Args:
                scrambled_array: 用户提供的乱序的数列，为 None 则自动随机生成。
                cursor: 为转子的初始位置，应该满足 0 <= cursor < num_range
                num_range: 数字范围
            Raises:
                InvalidScrambledCharsException：输入的 scrambled_array 不正确
                InavlidCursorException: 输入的 cursor 不正确
        """
        if not cursor in range(0, num_range):
            raise self.InvalidCursorException
        self.cursor = int(cursor)

        if scrambled_array:
            # 检查输入是否正确
            sorted_arrary =  sorted(scrambled_array, reverse=False)
            temp_list = list(range(0, num_range))

            if sorted_arrary != temp_list:
                raise self.InvalidScrambledCharsException
        else:
            # 随机生成
            scrambled_array = list(range(0, num_range))
            random.shuffle(scrambled_array)

        self.num_range = num_range
        self.array = scrambled_array
        self.array_reversed = [-1 for i in range(num_range)]
        for i in range(num_range):
            n = self.array[i]
            self.array_reversed[n] = i

    def input_num(self, num):
        """
            Rotor 输入方法

            Args:
                num: 输入的数字
            returns:
                加密后的数字
        """
        # 加上偏移量 cursor 之后找到下标 index
        index = (self.cursor + num) % self.num_range
        return self.array[index]

    def input_num_reversed(self, num):
        # 相当于 input_num 的逆运算，确保在同样的 cursor 条件下，输入等于输出
        rv = self.array_reversed[num] - self.cursor
        if rv < 0:
            rv += self.num_range
        return rv

    def rotate(self):
        """
            转动转子
            returns:
                tick: 默认为 False，如果转满一周 则为 True
        """
        self.cursor = (self.cursor + 1) % self.num_range
        return self.cursor == 0

    def set_cursor(self, pos):
        self.cursor = pos

    class InvalidScrambledCharsException(Exception):
        pass

    class InvalidCursorException(Exception):
        pass

class Plugboard:
    """
        PlugBoard 类
        交换一些输入的字母，比如交换 A 和 E 后，输入 E 会 输出 A，反之亦然
    """

    def __init__(self, swap_pairs={}, num_range=26):
        """
            Plugboard 初始方法
            Args:
                swap_pairs: 交换的数字对，格式为 {2: 3, 4: 7, 22: 13 ...}
                num_range: 数字范围
        """
        # 验证 swap_pairs
        pairs_reversed = {}
        _range = range(num_range)
        for k, v in swap_pairs.items():
            pairs_reversed[v] = k
            if k not in range(num_range):
                raise self.IvalidSwapPairsException('输入了奇怪的字符')
            if v not in range(num_range):
                raise self.IvalidSwapPairsException('输入了奇怪的字符')
            pairs_reversed[v] = k

        if len(swap_pairs) != len(pairs_reversed):
            raise self.IvalidSwapPairsException('可能存在重复的映射关系')

        self.array = list(range(num_range))

        for k, v in swap_pairs.items():
            self.array[k], self.array[v] = v, k

        self.num_range = num_range

    def input_num(self, num):
        """
            PlugBoard 输入 num 获得结果
            Args:
                num: int
            Returns:
                转换后的数字
        """
        return self.array[num]

    class IvalidSwapPairsException(Exception):
        pass

class Reflector(Plugboard):
    """
        Reflector 反射板，看作有13对交换数字的 Plugboard
    """
    def __init__(self, num_range=26) -> None:
        random_array = list(range(num_range))
        random.shuffle(random_array)
        swap_pairs = {}
        for i in range(int(num_range / 2)):
            n1 = random_array.pop()
            n2 = random_array.pop()
            swap_pairs[n1] = n2
        return super().__init__(swap_pairs, num_range=num_range)

class BetterReflector(Plugboard):
    """
        BetterReflector 不完全交换所有的字母，只交换一部分
    """
    def __init__(self, num_range=26) -> None:
        random_array = list(range(num_range))
        random.shuffle(random_array)
        swap_pairs = {}

        # 不去尽量交换所有的字母，而是只交换一部分
        # swap_pairs_num = random.randint(int(num_range / 6), int(num_range / 2))
        swap_pairs_num = int(num_range / 2 * 0.9)
        for i in range(swap_pairs_num):
            n1 = random_array.pop()
            n2 = random_array.pop()
            swap_pairs[n1] = n2
        return super().__init__(swap_pairs, num_range=num_range)

def test_rotor():
    r = Rotor()
    n0 = 12
    n1 = r.input_num(n0)
    n2 = r.input_num_reversed(n1)
    print(n0, n1, n2)
    assert(n0 == n2)

    r.rotate()
    n0 = 12
    n1 = r.input_num(n0)
    n2 = r.input_num_reversed(n1)
    assert(n0 == n2)
    print(n0, n1, n2)

def test_plugboard():
    pairs = {1: 3, 2: 4}
    pb = Plugboard(swap_pairs=pairs)
    assert(pb.input_num(0) == 0)
    assert(pb.input_num(4) == 2)
    assert(pb.input_num(1) == 3)
    assert(pb.input_num(3) == 1)

def test_reflector():
    rf = Reflector()
    for i in range(rf.num_range):
        _result = rf.input_num(i)
        _return = rf.input_num(_result)
        assert(i == _return)

def run_test():
    test_rotor()
    test_plugboard()
    test_reflector()

def main():
    # 可以添加任意个转子
    print('-- Enigma -------------')
    enigma = Enigma(rotor_num=5, rotor_cursors=[12, 8, 7, 3, 2], plugboard_pairs={1: 3, 2: 4, 10: 20})
    text = 'HELLOWORLD'
    cipher = enigma.input(text)
    print(cipher)
    plain = enigma.input(cipher)
    print(plain)

    print('---------------')

    text = 'A' * 30
    cipher = enigma.input(text)
    print(cipher)
    plain = enigma.input(cipher)
    print(plain)

    # 一个字母加密后永远不会是它自身
    assert(cipher.find('A') == -1)

    print('-- BetterEnigma -------------')

    be = BetterEnigma()

    text = 'A' * 30
    cipher = be.input(text)
    print(cipher)
    plain = be.input(cipher)
    print(plain)
    # 改进后有时可以是自身
    assert(cipher.find('A') != -1)

    print('-- BetterEnigma in ASCII -------------')

    be = BetterEnigma(transcoder_class=AsciiTranscoder)

    text = ''.join([chr(i) for i in range(ord('A'), ord('z') + 1)]) + '.,?!@#$%^&*()-='
    cipher = be.input(text)
    print(cipher)
    plain = be.input(cipher)
    print(plain)

if __name__ == '__main__':
    # run_test()
    main()


import argparse
import ast
import re
import sys

_BRACKETS_PATTERN = re.compile(r"\[[^\]]*\]")
_QUOTES_PATTERN = re.compile(r"(\"[^\"]*\"|\'[^\']*\')")
_WHITESPACE_PATTERN = re.compile(r"\s+")

_NUMBER_PATTERN = re.compile(r"[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?")

class Interval(object):
  """Represents an interval between a start and end value."""

  def __init__(self, start, start_included, end, end_included):
    self.start = start
    self.start_included = start_included
    self.end = end
    self.end_included = end_included

  def contains(self, value):
    if value < self.start or value == self.start and not self.start_included:
      return False
    if value > self.end or value == self.end and not self.end_included:
      return False
    return True

  def __eq__(self, other):
    return (self.start == other.start and
            self.start_included == other.start_included and
            self.end == other.end and
            self.end_included == other.end_included)

def parse_command(command):
  """Parse command string into a list of arguments.

  - Disregards whitespace inside double quotes and brackets.
  - Strips paired leading and trailing double quotes in arguments.
  - Splits the command at whitespace.

  Nested double quotes and brackets are not handled.

  Args:
    command: (str) Input command.

  Returns:
    (list of str) List of arguments.
  """

  command = command.strip()
  if not command:
    return []

  brackets_intervals = [f.span() for f in _BRACKETS_PATTERN.finditer(command)]
  quotes_intervals = [f.span() for f in _QUOTES_PATTERN.finditer(command)]
  whitespaces_intervals = [
      f.span() for f in _WHITESPACE_PATTERN.finditer(command)
  ]

  if not whitespaces_intervals:
    return [command]

  arguments = []
  idx0 = 0
  for start, end in whitespaces_intervals + [(len(command), None)]:
    # Skip whitespace stretches enclosed in brackets or double quotes.

    if not any(interval[0] < start < interval[1]
               for interval in brackets_intervals + quotes_intervals):
      argument = command[idx0:start]

      # Strip leading and trailing double quote if they are paired.
      if (argument.startswith("\"") and argument.endswith("\"") or
          argument.startswith("'") and argument.endswith("'")):
        argument = argument[1:-1]
      arguments.append(argument)
      idx0 = end

  return arguments

def extract_output_file_path(args):
  """Extract output file path from command arguments.

  Args:
    args: (list of str) command arguments.

  Returns:
    (list of str) Command arguments with the output file path part stripped.
    (str or None) Output file path (if any).

  Raises:
    SyntaxError: If there is no file path after the last ">" character.
  """

  if args and args[-1].endswith(">"):
    raise SyntaxError("Redirect file path is empty")
  elif args and args[-1].startswith(">"):
    try:
      _parse_interval(args[-1])
      if len(args) > 1 and args[-2].startswith("-"):
        output_file_path = None
      else:
        output_file_path = args[-1][1:]
        args = args[:-1]
    except ValueError:
      output_file_path = args[-1][1:]
      args = args[:-1]
  elif len(args) > 1 and args[-2] == ">":
    output_file_path = args[-1]
    args = args[:-2]
  elif args and args[-1].count(">") == 1:
    gt_index = args[-1].index(">")
    if gt_index > 0 and args[-1][gt_index - 1] == "=":
      output_file_path = None
    else:
      output_file_path = args[-1][gt_index + 1:]
      args[-1] = args[-1][:gt_index]
  elif len(args) > 1 and args[-2].endswith(">"):
    output_file_path = args[-1]
    args = args[:-1]
    args[-1] = args[-1][:-1]
  else:
    output_file_path = None

  return args, output_file_path

def parse_tensor_name_with_slicing(in_str):
  """Parse tensor name, potentially suffixed by slicing string.

  Args:
    in_str: (str) Input name of the tensor, potentially followed by a slicing
      string. E.g.: Without slicing string: "hidden/weights/Variable:0", with
      slicing string: "hidden/weights/Variable:0[1, :]"

  Returns:
    (str) name of the tensor
    (str) slicing string, if any. If no slicing string is present, return "".
  """

  if in_str.count("[") == 1 and in_str.endswith("]"):
    tensor_name = in_str[:in_str.index("[")]
    tensor_slicing = in_str[in_str.index("["):]
  else:
    tensor_name = in_str
    tensor_slicing = ""

  return tensor_name, tensor_slicing

def validate_slicing_string(slicing_string):
  """Validate a slicing string.

  Check if the input string contains only brackets, digits, commas and
  colons that are valid characters in numpy-style array slicing.

  Args:
    slicing_string: (str) Input slicing string to be validated.

  Returns:
    (bool) True if and only if the slicing string is valid.
  """

  return bool(re.search(r"^\[(\d|,|\s|:)+\]$", slicing_string))

def _parse_slices(slicing_string):
  """Construct a tuple of slices from the slicing string.

  The string must be a valid slicing string.

  Args:
    slicing_string: (str) Input slicing string to be parsed.

  Returns:
    tuple(slice1, slice2, ...)

  Raises:
    ValueError: If tensor_slicing is not a valid numpy ndarray slicing str.
  """
  parsed = []
  for slice_string in slicing_string[1:-1].split(","):
    indices = slice_string.split(":")
    if len(indices) == 1:
      parsed.append(int(indices[0].strip()))
    elif 2 <= len(indices) <= 3:
      parsed.append(
          slice(*[
              int(index.strip()) if index.strip() else None for index in indices
          ]))
    else:
      raise ValueError("Invalid tensor-slicing string.")
  return tuple(parsed)

def parse_indices(indices_string):
  """Parse a string representing indices.

  For example, if the input is "[1, 2, 3]", the return value will be a list of
  indices: [1, 2, 3]

  Args:
    indices_string: (str) a string representing indices. Can optionally be
      surrounded by a pair of brackets.

  Returns:
    (list of int): Parsed indices.
  """

  # Strip whitespace.
  indices_string = re.sub(r"\s+", "", indices_string)

  # Strip any brackets at the two ends.
  if indices_string.startswith("[") and indices_string.endswith("]"):
    indices_string = indices_string[1:-1]

  return [int(element) for element in indices_string.split(",")]

def parse_ranges(range_string):
  """Parse a string representing numerical range(s).

  Args:
    range_string: (str) A string representing a numerical range or a list of
      them. For example:
        "[-1.0,1.0]", "[-inf, 0]", "[[-inf, -1.0], [1.0, inf]]"

  Returns:
    (list of list of float) A list of numerical ranges parsed from the input
      string.

  Raises:
    ValueError: If the input doesn't represent a range or a list of ranges.
  """

  range_string = range_string.strip()
  if not range_string:
    return []

  if "inf" in range_string:
    range_string = re.sub(r"inf", repr(sys.float_info.max), range_string)

  ranges = ast.literal_eval(range_string)
  if isinstance(ranges, list) and not isinstance(ranges[0], list):
    ranges = [ranges]

  # Verify that ranges is a list of list of numbers.
  for item in ranges:
    if len(item) != 2:
      raise ValueError("Incorrect number of elements in range")
    elif not isinstance(item[0], (int, float)):
      raise ValueError("Incorrect type in the 1st element of range: %s" %
                       type(item[0]))
    elif not isinstance(item[1], (int, float)):
      raise ValueError("Incorrect type in the 2nd element of range: %s" %
                       type(item[0]))

  return ranges

def parse_memory_interval(interval_str):
  """Convert a human-readable memory interval to a tuple of start and end value.

  Args:
    interval_str: (`str`) A human-readable str representing an interval
      (e.g., "[10kB, 20kB]", "<100M", ">100G"). Only the units "kB", "MB", "GB"
      are supported. The "B character at the end of the input `str` may be
      omitted.

  Returns:
    `Interval` object where start and end are in bytes.

  Raises:
    ValueError: if the input is not valid.
  """
  str_interval = _parse_interval(interval_str)
  interval_start = 0
  interval_end = float("inf")
  if str_interval.start:
    interval_start = parse_readable_size_str(str_interval.start)
  if str_interval.end:
    interval_end = parse_readable_size_str(str_interval.end)
  if interval_start > interval_end:
    raise ValueError(
        "Invalid interval %s. Start of interval must be less than or equal "
        "to end of interval." % interval_str)
  return Interval(interval_start, str_interval.start_included,
                  interval_end, str_interval.end_included)

def parse_time_interval(interval_str):
  """Convert a human-readable time interval to a tuple of start and end value.

  Args:
    interval_str: (`str`) A human-readable str representing an interval
      (e.g., "[10us, 20us]", "<100s", ">100ms"). Supported time suffixes are
      us, ms, s.

  Returns:
    `Interval` object where start and end are in microseconds.

  Raises:
    ValueError: if the input is not valid.
  """
  str_interval = _parse_interval(interval_str)
  interval_start = 0
  interval_end = float("inf")
  if str_interval.start:
    interval_start = parse_readable_time_str(str_interval.start)
  if str_interval.end:
    interval_end = parse_readable_time_str(str_interval.end)
  if interval_start > interval_end:
    raise ValueError(
        "Invalid interval %s. Start must be before end of interval." %
        interval_str)
  return Interval(interval_start, str_interval.start_included,
                  interval_end, str_interval.end_included)

def _parse_interval(interval_str):
  """Convert a human-readable interval to a tuple of start and end value.

  Args:
    interval_str: (`str`) A human-readable str representing an interval
      (e.g., "[1M, 2M]", "<100k", ">100ms"). The items following the ">", "<",
      ">=" and "<=" signs have to start with a number (e.g., 3.0, -2, .98).
      The same requirement applies to the items in the parentheses or brackets.

  Returns:
    Interval object where start or end can be None
    if the range is specified as "<N" or ">N" respectively.

  Raises:
    ValueError: if the input is not valid.
  """
  interval_str = interval_str.strip()
  if interval_str.startswith("<="):
    if _NUMBER_PATTERN.match(interval_str[2:].strip()):
      return Interval(start=None, start_included=False,
                      end=interval_str[2:].strip(), end_included=True)
    else:
      raise ValueError("Invalid value string after <= in '%s'" % interval_str)
  if interval_str.startswith("<"):
    if _NUMBER_PATTERN.match(interval_str[1:].strip()):
      return Interval(start=None, start_included=False,
                      end=interval_str[1:].strip(), end_included=False)
    else:
      raise ValueError("Invalid value string after < in '%s'" % interval_str)
  if interval_str.startswith(">="):
    if _NUMBER_PATTERN.match(interval_str[2:].strip()):
      return Interval(start=interval_str[2:].strip(), start_included=True,
                      end=None, end_included=False)
    else:
      raise ValueError("Invalid value string after >= in '%s'" % interval_str)
  if interval_str.startswith(">"):
    if _NUMBER_PATTERN.match(interval_str[1:].strip()):
      return Interval(start=interval_str[1:].strip(), start_included=False,
                      end=None, end_included=False)
    else:
      raise ValueError("Invalid value string after > in '%s'" % interval_str)

  if (not interval_str.startswith(("[", "("))
      or not interval_str.endswith(("]", ")"))):
    raise ValueError(
        "Invalid interval format: %s. Valid formats are: [min, max], "
        "(min, max), <max, >min" % interval_str)
  interval = interval_str[1:-1].split(",")
  if len(interval) != 2:
    raise ValueError(
        "Incorrect interval format: %s. Interval should specify two values: "
        "[min, max] or (min, max)." % interval_str)

  start_item = interval[0].strip()
  if not _NUMBER_PATTERN.match(start_item):
    raise ValueError("Invalid first item in interval: '%s'" % start_item)
  end_item = interval[1].strip()
  if not _NUMBER_PATTERN.match(end_item):
    raise ValueError("Invalid second item in interval: '%s'" % end_item)

  return Interval(start=start_item,
                  start_included=(interval_str[0] == "["),
                  end=end_item,
                  end_included=(interval_str[-1] == "]"))

def parse_readable_size_str(size_str):
  """Convert a human-readable str representation to number of bytes.

  Only the units "kB", "MB", "GB" are supported. The "B character at the end
  of the input `str` may be omitted.

  Args:
    size_str: (`str`) A human-readable str representing a number of bytes
      (e.g., "0", "1023", "1.1kB", "24 MB", "23GB", "100 G".

  Returns:
    (`int`) The parsed number of bytes.

  Raises:
    ValueError: on failure to parse the input `size_str`.
  """

  size_str = size_str.strip()
  if size_str.endswith("B"):
    size_str = size_str[:-1]

  if size_str.isdigit():
    return int(size_str)
  elif size_str.endswith("k"):
    return int(float(size_str[:-1]) * 1024)
  elif size_str.endswith("M"):
    return int(float(size_str[:-1]) * 1048576)
  elif size_str.endswith("G"):
    return int(float(size_str[:-1]) * 1073741824)
  else:
    raise ValueError("Failed to parsed human-readable byte size str: \"%s\"" %
                     size_str)

def parse_readable_time_str(time_str):
  """Parses a time string in the format N, Nus, Nms, Ns.

  Args:
    time_str: (`str`) string consisting of an integer time value optionally
      followed by 'us', 'ms', or 's' suffix. If suffix is not specified,
      value is assumed to be in microseconds. (e.g. 100us, 8ms, 5s, 100).

  Returns:
    Microseconds value.
  """
  def parse_positive_float(value_str):
    value = float(value_str)
    if value < 0:
      raise ValueError(
          "Invalid time %s. Time value must be positive." % value_str)
    return value

  time_str = time_str.strip()
  if time_str.endswith("us"):
    return int(parse_positive_float(time_str[:-2]))
  elif time_str.endswith("ms"):
    return int(parse_positive_float(time_str[:-2]) * 1e3)
  elif time_str.endswith("s"):
    return int(parse_positive_float(time_str[:-1]) * 1e6)
  return int(parse_positive_float(time_str))

def evaluate_tensor_slice(tensor, tensor_slicing):
  """Call eval on the slicing of a tensor, with validation.

  Args:
    tensor: (numpy ndarray) The tensor value.
    tensor_slicing: (str or None) Slicing of the tensor, e.g., "[:, 1]". If
      None, no slicing will be performed on the tensor.

  Returns:
    (numpy ndarray) The sliced tensor.

  Raises:
    ValueError: If tensor_slicing is not a valid numpy ndarray slicing str.
  """

  _ = tensor

  if not validate_slicing_string(tensor_slicing):
    raise ValueError("Invalid tensor-slicing string.")

  return tensor[_parse_slices(tensor_slicing)]

def get_print_tensor_argparser(description):
  """Get an ArgumentParser for a command that prints tensor values.

  Examples of such commands include print_tensor and print_feed.

  Args:
    description: Description of the ArgumentParser.

  Returns:
    An instance of argparse.ArgumentParser.
  """

  ap = argparse.ArgumentParser(
      description=description, usage=argparse.SUPPRESS)
  ap.add_argument(
      "tensor_name",
      type=str,
      help="Name of the tensor, followed by any slicing indices, "
      "e.g., hidden1/Wx_plus_b/MatMul:0, "
      "hidden1/Wx_plus_b/MatMul:0[1, :]")
  ap.add_argument(
      "-n",
      "--number",
      dest="number",
      type=int,
      default=-1,
      help="0-based dump number for the specified tensor. "
      "Required for tensor with multiple dumps.")
  ap.add_argument(
      "-r",
      "--ranges",
      dest="ranges",
      type=str,
      default="",
      help="Numerical ranges to highlight tensor elements in. "
      "Examples: -r 0,1e-8, -r [-0.1,0.1], "
      "-r \"[[-inf, -0.1], [0.1, inf]]\"")
  ap.add_argument(
      "-a",
      "--all",
      dest="print_all",
      action="store_true",
      help="Print the tensor in its entirety, i.e., do not use ellipses.")
  ap.add_argument(
      "-s",
      "--numeric_summary",
      action="store_true",
      help="Include summary for non-empty tensors of numeric (int*, float*, "
      "complex*) and Boolean types.")
  ap.add_argument(
      "-w",
      "--write_path",
      type=str,
      default="",
      help="Path of the numpy file to write the tensor data to, using "
      "numpy.save().")
  return ap


from sys import stdin

stdin = open("03.txt")

input = stdin.readline

# int(stdin.readline())

n, m = map(int, input().split())

result = 0

for x in range(n):
    data = list(map(int, input().split()))
    min_value = min(data)

    # if min_value > result:
    #     result = min_value

    result = max(result, min_value)

print(result)


from typing import Set

import rich
import rich.progress
import rich.table

from bygg.action import Action
from bygg.common_types import CommandStatus
from bygg.runner import JobStatus

console = rich.console.Console()

progress: rich.progress.Progress = rich.progress.Progress(
    rich.progress.SpinnerColumn(
        table_column=rich.table.Column(ratio=None),
        spinner_name="bouncingBar",
        style="blue",
    ),
    rich.progress.TimeElapsedColumn(table_column=rich.table.Column(ratio=None)),
    rich.progress.TextColumn(
        "[cyan]{task.description}",
        table_column=rich.table.Column(ratio=1),
    ),
    transient=True,
    console=console,
)

task_id = None

def on_runner_status(message: str):
    console.print(f"[blue]{message}")

running_jobs: Set[str] = set()
processed_jobs = 0

def print_job_ended(
    name: str, job_status: JobStatus, action: Action, status: CommandStatus | None
):
    job_prefix = "[red]✗[/red]" if job_status == "failed" else "[green]✓[/green]"
    status_color = "red" if job_status == "failed" else "green"

    status_message = status.message if status and status.message else ""

    table = rich.table.Table(show_header=False, pad_edge=False, box=None)
    table.add_column()
    table.add_row(
        f"{job_prefix} [{status_color}]{name}[/{status_color}]"
        f"{': [cyan]' + status_message + '[/cyan]' }"
        f"{' (start) ---' if status and status.output else ''}"
    )
    if status and status.output:
        table.add_row(status.output)
        table.add_row(f"--- [{status_color}]{name}[/{status_color}] (end) ---")
        table.add_row()

    console.print(table)

def on_job_status(
    name: str, job_status: JobStatus, action: Action, status: CommandStatus | None
):
    global processed_jobs
    global task_id
    if task_id is None:
        task_id = progress.add_task("[blue]Building[/blue]")

    if job_status == "failed":
        processed_jobs += 1
        if name in running_jobs:
            running_jobs.remove(name)
        print_job_ended(name, job_status, action, status)
    elif job_status == "running":
        running_jobs.add(name)
        progress.update(
            task_id,
            description=f"[blue]Finished [yellow]{processed_jobs}[/yellow] jobs.[/blue]",
        )
    else:
        processed_jobs += 1
        print_job_ended(name, job_status, action, status)
        if name in running_jobs:
            running_jobs.remove(name)


N = int ( input() )

#Counting sort
arr = [0] * ( 2000001 )
for i in range( N ) :
    temp = int ( input() )
    temp += 1000000
    arr[temp] += 1

#print ( '-' * 10)
for i in range( 2000001 ) :
    while arr[i] :
        print ( i - 1000000 )
        arr[i] -= 1
    #if arr[i] != 0 :
        #for j in range( arr[i] ) :
            #print ( i - 1000000 )


import pygame
import random

BOMB=1
BARRIER=2
BOX=3
FIRE=4
TOOL_SPEED_UP=5
TOOL_SCOPE_UP=6
TOOL_BOMBS_UP=7
TOOL_HP_UP=8
TOOL=[TOOL_SPEED_UP,TOOL_SCOPE_UP,TOOL_BOMBS_UP,TOOL_HP_UP]

NUM_OF_BOXS=100
NUM_OF_BARRIER=100

matrix=[[0 for i in range(22)] for i in range(22)]
did=[[0 for i in range(22)] for i in range(22)]

def dfs(x,y):
	did[x][y]=True;
	if matrix[x-1][y]==0 and not did[x-1][y]:dfs(x-1,y)
	if matrix[x+1][y]==0 and not did[x+1][y]:dfs(x+1,y)
	if matrix[x][y-1]==0 and not did[x][y-1]:dfs(x,y-1)
	if matrix[x][y+1]==0 and not did[x][y+1]:dfs(x,y+1)

def check(map):
	global matrix
	global did
	matrix=[[0 for i in range(22)] for i in range(22)]
	did=[[0 for i in range(22)] for i in range(22)]
	for i in range(0,21):matrix[i][0]=1;matrix[i][20]=1;
	for i in range(0,21):matrix[0][i]=1;matrix[20][i]=1;
	for i in map:matrix[i[0]][i[1]]=1
	flag=False
	for i in range(1,20):
		for j in range(1,20):
			if matrix[i][j]!=1 and not did[i][j]:
				if flag: return False
				flag=True
				dfs(i,j)
	return True

def genbar(blocked):
	res=[];a=False;num=0;
	while not a or not check(res):
		res=[];a=True;num=0
		while num<NUM_OF_BARRIER:
			col=random.randrange(1,20);
			row=random.randrange(1,20);
			if (not (col,row) in res) and (not (col,row) in blocked):
				res.append((col,row))
				num+=1
	return res;

class Block(pygame.sprite.Sprite):

	def __init__(self, block_img, init_pos, category):
		pygame.sprite.Sprite.__init__(self)
		self.image = block_img
		self.rect = self.image.get_rect()
		self.rect.topleft = init_pos
		self.category = category

class Box(Block):

	def __init__(self, box_img, init_pos, has_tool):
		super(Box, self).__init__(box_img, init_pos, BOX)
		self.pos = init_pos
		self._has_tool = has_tool

class MapEditer(object):
	"""docstring for MapEditer"""
	_instance = None

	def __init__(self):
		self.blocks = pygame.sprite.Group()
		self.blocks.empty()

	@staticmethod
	def instance():
		if MapEditer._instance is None:
			MapEditer._instance = MapEditer()
		return MapEditer._instance

	@staticmethod
	def to_map_coordinate(xy):
		return (15 + 30 * (xy[0] - 1), 15 + 30 * (xy[1] - 1))

	@staticmethod
	def to_real_coordinate(cor):
		return ((cor[0] - 15) // 30 + 1, (cor[1] - 15) // 30 + 1)

	@staticmethod
	def Paste(pair, img, category):
		col, row = MapEditer.to_map_coordinate(pair)
		block_img = pygame.image.load(img)
		if category == BOX:
			block = Box(block_img, (col, row), img.endswith("box0.png") or img.endswith("box3.png") and random.random() > 0.65)
		else:
			block = Block(block_img, (col, row), category)
		return block

	def refresh(self):
		self.blocks.draw(self.screen)

	def empty(self):
		self.blocks.empty()

	def BuildMap(self, screen):
		self.screen=screen

		# 不能生成箱子的地方
		corner=[(1,1),(1,2),(2,1),(1,19),(1,18),(2,19),(18,1),(19,1),(19,2),(19,19),(18,19),(19,18)]
		# 四个区域的边界
		border=[(1,10),(1,11),(10,20),(1,11),(1,10),(11,20),(10,20),(11,20)]
		# 箱子的坐标列表
		boxs=[]
		barriers=[]
		random.seed()

		# 随机产生宝箱位置
		total=0
		index=0

		barriers=genbar(corner)
		total=0
		while total<NUM_OF_BOXS:
			x=random.randrange(1,20);y=random.randrange(1,20)
			if (x,y) not in boxs and (x,y) not in corner and ((x,y) not in barriers):
				boxs.append((x,y))
				total+=1
		# while index<7:
			# for col in range(border[index][0],border[index][1]):
				# num=3 if col%2!=0 else 2
				# increament=1 if col%2!=0 else 2
				# total=0
				# while total<num:
					# print(total,num)
					# row=random.randrange(border[index+1][0],border[index+1][1],increament)
					# if (col,row) not in boxs and (col,row) not in corner and ((col,row) not in barriers):
						# boxs.append((col,row))
						# total+=1
			# index+=2
		# block = self.Paste((3, 1), "resources/image/box" +
		#                    str(random.randint(0, 3)) + ".png", BOX)
		# print('box ', ((block.rect.top - 15) / 30 +
		#                1, (block.rect.left - 15) / 30 + 1))
		# self.blocks.add(block)
		# 放置宝箱
		for pair in boxs:
			block=self.Paste(pair,"resources/image/box"+str(random.randint(0,3))+".png",BOX)
			block.top=30
			block.left=30
			self.blocks.add(block)

		# for col in range(2,19,4):
		# 	for row in range(2,19,4):
		# 		barriers.append((col,row))

		for pair in barriers:
			block=self.Paste(pair,"resources/image/barrier.png",BARRIER)
			block.top=30
			block.left=30
			self.blocks.add(block)
		self.refresh()

	def GetBlocks(self):
		return self.blocks


# indice de acidentes = numero de acidentes / numero de veiculos

maiorIndice = 0
menorIndice = 0
codMaiorIndice = 0
codMenorIndice = 0
totalVeiculos = 0
totalAcidentes = 0
contador = 0
acidenteAte2000 = 0
for i in range(1, 6):
    while True:
        try:
            codigoCidade = int(input('Código da cidade: '))
            nVeiculos = int(input('Qtd. veículos de passeio: '))
            nAcidentes = int(input('Qtd. acidentes de trânsito: '))
        except:
            print('Valor inválido! Tente novamente.')
            continue
        indice = round(nAcidentes/nVeiculos)
        break
    totalAcidentes += nAcidentes
    totalVeiculos += nVeiculos
    if (maiorIndice == 0):
        maiorIndice = indice
        codMaiorIndice = codigoCidade
    elif (maiorIndice < indice):
        maiorIndice = indice
        codMaiorIndice = codigoCidade

    if (menorIndice == 0):
        menorIndice = indice
        codMenorIndice = codigoCidade
    elif (menorIndice > indice):
        menorIndice = indice
        codMenorIndice = codigoCidade

    if (nVeiculos < 2000):
        contador += 1
        acidenteAte2000 += nAcidentes

mediaVeiculos = round(totalVeiculos/5)
mediaAcidentes = round(totalAcidentes/5)
mediaAcidentesAte2000 = round(acidenteAte2000/contador)

print(f'A cidade com o código {codMenorIndice} teve o menor indice, sendo {menorIndice} acidentes por veículo.')
print(f'A cidade com o código {codMaiorIndice} teve o maior indice, sendo {maiorIndice} acidentes por veículo.')
print(f'A média de veículos das 5 cidades somadas foi {mediaVeiculos} veículos por cidade.')
print(f'A média de acidentes de trânsito nas cidades com menos de 2000 veículos foi de {mediaAcidentes} acidentes por veículo.')


class Solution:
    def exist(self, board, word):
        m = len(board)
        n = len(board[0])

        def dfs(x, y, k):
            if not 0 <= x < m or not 0 <= y < n or board[x][y] != word[k]:
                return False
            elif k == len(word) - 1:
                return True
            else:
                board[x][y] = ''
                res = dfs(x + 1, y, k + 1) or dfs(x - 1, y, k + 1) or dfs(x, y + 1, k + 1) or dfs(x, y - 1, k + 1)
                board[x][y] = word[k]
            return res

        for i in range(m):
            for j in range(n):
                if dfs(i, j, 0):
                    return True
        return False


## Разбивание видео на отдельные изображения

import numpy as np
import cv2

cap_1 = cv2.VideoCapture('N120131007093530930.avi')
cap_2 = cv2.VideoCapture('N120131007162257079.avi')

while(cap_1.isOpened()):
    ret, frame_1 = cap_1.read() # читаем кадр из видео
    ret, frame_2 = cap_2.read()
    #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # можем преобразовать изображение в серое

    cv2.imshow('frame_1', frame_1) # показываем текущий кадр
    cv2.imshow('frame_2', frame_2)
    #time.sleep(0.03) # здесь можем управлять частотой кадров
    if cv2.waitKey(0) & 0xFF == ord('q'): # если нужно, то завершаем программу
        break

cap_1.release()
cap_2.release()
cv2.destroyAllWindows()


from lzma import LZMADecompressor, LZMAError, FORMAT_AUTO
import optparse
import os, copy
import struct
from datetime import datetime, timedelta
from urllib import request
from urllib.error import HTTPError
from core.mongodb import Mongodb
import calendar
import pandas as pd

pymongo = Mongodb(host='127.0.0.1', port=27017)

def get_weekday(datetime_obj, week_day="monday"):
    """
    获取指定时间的当周的星期x
    :param datetime_obj: 时间
    :param week_day: 指定的星期x
    :return:
    """
    d = dict(zip(("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"),
                 range(7)))  # datetime 模块中，星期一到星期天对应数字 0 到 6
    delta_hour = timedelta(days=1)  # 改变幅度为 1 天
    while datetime_obj.weekday() != d.get(week_day):
        if datetime_obj.weekday() > d.get(week_day):
            datetime_obj -= delta_hour
        elif datetime_obj.weekday() < d.get(week_day):
            datetime_obj += delta_hour
        else:
            pass
    return datetime_obj

def get_first_weekday(year, month, n=1, w="monday"):
    """
    获取 year 年，month 月 的第n个星期w和倒数第n个星期w的日期
    :param year: 指定年份，如 2019
    :param month: 指定月份，如 6
    :param n: 第n个
    :param w: 指定的星期w
    :return:
    """
    # 获取第一和最后一天
    d = dict(zip(("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"),
                 range(7)))  # datetime 模块中，星期一到星期天对应数字 0 到 6
    weekday, count_day = calendar.monthrange(year=year,
                                             month=month)  # 返回指定月份第一天（即1号）的星期日期，和本月的总天数 https://blog.csdn.net/tz_zs/article/details/86629959
    first_day = datetime(year=year, month=month, day=1)  # <type 'datetime.datetime'>
    last_day = datetime(year=year, month=month, day=count_day)
    # first_day, last_day = get_month_firstday_and_lastday(year=year, month=month, n=1)

    # 第1个星期w
    if first_day.weekday() > d.get(w):  # 说明本周的星期w在上个月
        datetime_obj = first_day + timedelta(weeks=1)
    else:
        datetime_obj = first_day
    datetime_obj += timedelta(weeks=n - 1)
    first_weekday = get_weekday(datetime_obj=datetime_obj, week_day=w)

    return first_weekday

def decompress_lzma(data):
    results = []
    len(data)
    while True:
        decomp = LZMADecompressor(FORMAT_AUTO, None, None)
        try:
            res = decomp.decompress(data)
        except LZMAError:
            if results:
                break
            else:
                raise
        results.append(res)
        data = decomp.unused_data
        if not data:
            break
        if not decomp.eof:
            raise LZMAError("Compressed data ended before the end-of-stream marker was reached")
    return b"".join(results)

def tokenize(buffer):
    token_size = 20
    token_count = int(len(buffer) / token_size)
    tokens = list(map(lambda x: struct.unpack_from('>3I2f', buffer, token_size * x), range(0, token_count)))
    return tokens

def normalize_tick(symbol, day, time, ask, bid, ask_vol, bid_vol):
    date = day + timedelta(milliseconds=time)

    # TODO 網羅する。この通過ペア以外も有るかも
    if any(map(lambda x: x in symbol.lower(), ['usdrub', 'xagusd', 'xauusd', 'jpy'])):
        point = 1000
    else:
        point = 100000

    return [date, ask / point, bid / point, round(ask_vol * 1000000), round(bid_vol * 1000000)]

def format_to_csv_for_ticks(ticks):
    df = pd.DataFrame(ticks, columns=['date', 'Ask', 'Bid', 'AskVolume', 'BidVolume'])
    df = df.drop(['Ask', 'AskVolume', 'BidVolume'], axis=1)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date', inplace=True)
    return df

def format_to_csv_for_candle(ticks, scale, price_type):
    df = pd.DataFrame(ticks, columns=['date', 'Ask', 'Bid', 'AskVolume', 'BidVolume'])
    if price_type == "bid":
        df = df.drop(['Ask', 'AskVolume', 'BidVolume'], axis=1)
    elif price_type == "ask":

        df = df.drop(['Bid', 'AskVolume', 'BidVolume'], axis=1)
    else:
        df = df.drop(['AskVolume', 'BidVolume'], axis=1)
    df['date'] = pd.to_datetime(df['date'])
    df.set_index('date', inplace=True)
    if scale.lower() in ["4h", "1d"]:
        # is_dst_result = is_dst(df.index[0])
        is_dst_result, _, _ = is_dst(datetime.strptime(df.index[0].strftime("%Y-%m-%d"), "%Y-%m-%d"))
        if is_dst_result:
            # 夏令时，4h的开始时间是凌晨1点
            h4_start_hour = "21h"
        else:
            h4_start_hour = "22h"
        df_ohlc = df.resample(scale, offset=h4_start_hour).ohlc()
        df_ohlcv = df_ohlc.assign(Volume=df.iloc[:, 0].resample(scale, offset=h4_start_hour).count())
    else:
        df_ohlc = df.resample(scale).ohlc()
        df_ohlcv = df_ohlc.assign(Volume=df.iloc[:, 0].resample(scale).count())

    return df_ohlcv

def is_dst(day):
    dst_start = get_first_weekday(year=day.year, month=3, n=2, w="sunday")
    dst_end = get_first_weekday(year=day.year, month=11, n=1, w="sunday")
    if dst_start <= day < dst_end:
        return True, dst_start, dst_end
    return False, dst_start, dst_end

def load_bi5(path, symbol, day: datetime):
    file_name = f'{day.hour:02d}h_ticks.bi5'
    file_path = f'{path}\{symbol}\{day.year:04d}\{day.month - 1:02d}\{day.day:02d}\{file_name}'
    print(f'loading: {file_path}')

    # req = request.Request(url)
    try:
        with open(file_path, "rb") as res:
            res_body = res.read()
    except Exception as e:
        print('load failed. continuing...save_path=%s' % file_path)

        url_prefix = 'https://datafeed.dukascopy.com/datafeed'
        url = f'{url_prefix}/{symbol}/{day.year:04d}/{day.month - 1:02d}/{day.day:02d}/{file_name}'
        print("download url:%s" % url)
        if not os.path.exists(file_path[:file_path.rfind("\\")]):
            os.makedirs(file_path[:file_path.rfind("\\")])
        req = request.Request(url)
        try:
            with request.urlopen(req) as res:
                if res.code == 200:
                    res_body = res.read()

                    with open(file_path, "wb") as f:
                        f.write(res_body)
                else:
                    print("download fail:%s" % url)
        except HTTPError:
            print('download failed. continuing..')

    data = []
    if len(res_body):
        try:
            data = decompress_lzma(res_body)
        except LZMAError:
            print('decompress failed. continuing..')

    tokenized_data = tokenize(data)

    ticks_hour = list(map(lambda x: normalize_tick(symbol, day, *x), tokenized_data))
    return ticks_hour

def from_local_path(path, symbol, day):
    ticks_day = []

    result, dst_start, dst_end = is_dst(day)
    if result:
        # end_time = day + timedelta(hours=0) + timedelta(days=1)
        # if dst_start == day:
        #     day += timedelta(hours=20)
        #
        # while True:
        #     if day == end_time:
        #         break
        #     day = day + timedelta(hours=1)
        #     ticks_hour = load_bi5(path, symbol, day)
        #     ticks_day.extend(ticks_hour)

        if dst_start == day + timedelta(days=1):
            return []
        end_time = day + timedelta(hours=21) + timedelta(days=1)
        if dst_end == day:
            day = day + timedelta(hours=-3)
        else:
            day = day + timedelta(hours=-3) + timedelta(days=1)
        while True:
            if day == end_time:
                break

            ticks_hour = load_bi5(path, symbol, day)
            day = day + timedelta(hours=1)
            ticks_day.extend(ticks_hour)

    else:
        if dst_start == day + timedelta(days=1):
            return []
        end_time = day + timedelta(hours=22) + timedelta(days=1)
        if dst_end == day:
            day = day + timedelta(hours=-2)
        else:
            day = day + timedelta(hours=-2) + timedelta(days=1)
        while True:
            if day == end_time:
                break

            ticks_hour = load_bi5(path, symbol, day)
            day = day + timedelta(hours=1)
            ticks_day.extend(ticks_hour)
    print("end...........")
    return ticks_day

def main():
    parser = optparse.OptionParser(
        usage='Usage: python {} [options] <symbol> <start: yyyy-mm-dd> <end: yyyy-mm-dd>'.format(
            os.path.basename(__file__)))
    parser.add_option('-c', action="store", metavar='time_scale', dest="c", help="candlestick. ex: 1min, 1H, 1D")
    parser.add_option('-d', action="store", metavar='output_dir', dest="d", default='./', help="output directory.")
    parser.add_option('-t', action="store", metavar='price_type', dest="t", default='bid',
                      help="select price type. ex: bid, ask, all")
    parser.add_option('--mongo', action="store_true", help="save data to mongodb")
    parser.add_option('-s', action="store", metavar='source_dir', dest="s", default='./', help="source data directory.")

    (options, args) = parser.parse_args()

    if len(args) < 3:
        parser.print_help()
        exit(1)

    symbol = args[0].upper()
    start_date = datetime.strptime(args[1], '%Y-%m-%d')
    end_date = datetime.strptime(args[2], '%Y-%m-%d')

    save_mongo = True if options.mongo else False
    output_dir = options.d
    output_suffix = f'_{options.c}' if options.c is not None else ''
    output_csv = f'{output_dir}/{symbol}_{start_date.strftime("%Y-%m-%d")}_{end_date.strftime("%Y-%m-%d")}{output_suffix}_{options.t}.csv'

    d = start_date

    result_df = pd.DataFrame()
    while d <= end_date:
        df = pd.DataFrame()
        ticks_day = from_local_path(options.s, symbol, d)

        if options.c is None:

            df = format_to_csv_for_ticks(ticks_day)
            result_df = pd.concat([result_df, df])
            # result_df.columns = ["bid"]

        else:
            if ticks_day:
                df = format_to_csv_for_candle(ticks_day, options.c, options.t)

                df.columns = ["open", "high", "low", "close", "volume"] if options.t in ["bid", "ask"] else ["ask_open",
                                                                                "ask_high",
                                                                                "ask_low",
                                                                                "ask_close",
                                                                                "bid_open",
                                                                                "bid_high",
                                                                                "bid_low",
                                                                                "bid_close",
                                                                                "volume"]

                result_df = pd.concat([result_df, df])

            # f.write(format_to_csv_for_candle(ticks_day, options.c))

        d += timedelta(days=1)

        if save_mongo:
            try:
                df = df.reset_index()
                df["complete"] = 1.0
                df["date"] = df["date"].astype(str)
                df["volume"] = df['volume'].astype(float)
                df['timestamp'] = df["date"].apply(lambda x: x.split(" ")[1])
                data = df.to_dict(orient='records')
                pymongo.conn["%s_duka" %symbol][options.c].insert_many(data)
            except Exception as e:
                print(e)

    if not save_mongo:
        result_df.to_csv(output_csv)

if __name__ == '__main__':
    main()