"""Defines the Abaqus class"""
from typing import Union, Optional
from io import StringIO

import numpy as np
from cpylog import SimpleLogger, get_logger2
from pyNastran.converters.abaqus.abaqus_cards import (
    Assembly, Part, Elements, Step, cast_nodes)
import pyNastran.converters.abaqus.reader as reader
from pyNastran.converters.abaqus.reader_utils import print_data, clean_lines

def read_abaqus(abaqus_inp_filename, encoding=None,
                log=None, debug=False):
    """reads an abaqus model"""
    model = Abaqus(log=log, debug=debug)
    model.read_abaqus_inp(abaqus_inp_filename, encoding=encoding)
    return model

class Abaqus:
    """defines the abaqus reader"""
    def __init__(self, log: Optional[SimpleLogger]=None,
                 debug: Union[str, bool, None]=True):
        self.debug = debug
        self.parts = {}
        self.boundaries = {}
        self.materials = {}
        self.amplitudes = {}
        self.assembly = None
        self.initial_conditions = {}
        self.steps = []  #  type: list[Step]
        self.heading = None
        self.preprint = None

        self.shell_sections = []  # list[ShellSection]
        self.solid_sections = []  # list[SolidSection]
        self.log = get_logger2(log, debug)

    def read_abaqus_inp(self, abaqus_inp_filename: str, encoding: str=None):
        """reads an abaqus model"""
        if isinstance(abaqus_inp_filename, str):
            with open(abaqus_inp_filename, 'r', encoding=encoding) as abaqus_inp:
                lines = abaqus_inp.readlines()
        elif isinstance(abaqus_inp_filename, list):
            lines = abaqus_inp_filename
        elif isinstance(abaqus_inp_filename, StringIO):
            lines = abaqus_inp_filename.readlines()
        else:
            msg = 'abaqus_inp_filename=%s type=%r' % (
                abaqus_inp_filename, type(abaqus_inp_filename))
            raise NotImplementedError(msg)

        lines = clean_lines(lines)

        unused_ilines = []
        iline = 0
        nlines = len(lines)
        nassembly = 0
        istep = 1

        nids = []
        nodes = []
        node_sets = {}
        element_types = {}
        element_sets = {}
        solid_sections = []
        shell_sections = []
        boundaries = []
        steps = []

        log = self.log
        while iline < nlines:
            # not handling comments right now
            line0 = lines[iline].strip().lower()
            self.log.debug('%s: %s' % (iline, line0))
            #sline = line.split('**', 1)
            #if len(sline) == 1:
                #line0 = sline[0]
                #comment = ''
            #else:
                #line0, comment = sline
                #if not line0:
                    #iline += 1
                    #continue

            if '*' in line0[0]:
                word = line0.strip('*').lower()
                log.debug('main: word = %r' % word)
                if word == 'heading':
                    pass
                elif word.startswith('preprint'):
                    pass
                elif word == 'boundary':
                    iline += 1
                    boundary, iline, line0 = reader.read_boundary(lines, line0, iline)
                    boundaries.append(boundary)
                    iline -= 1
                    line0 = lines[iline].strip().lower()

                elif word.startswith('assembly'):
                    if nassembly != 0:
                        raise RuntimeError('only one assembly can be defined...')
                    iline, line0, assembly = self.read_assembly(lines, iline, line0, word)
                    self.assembly = assembly
                    nassembly += 1

                elif word.startswith('part'):
                    iline, line0, part_name, part = self.read_part(lines, iline, line0, word)
                    self.parts[part_name] = part
                    #print('part_name', part_name)
                    if self.debug:
                        self.log.debug('-------------------------------------')
                elif 'section controls' in word:
                    # TODO: skips header parsing
                    data_lines, iline, line0 = reader.read_star_block(lines, iline, line0, self.log)

                elif word.startswith('amplitude'):
                    param_map = reader.get_param_map(iline, word)
                    name = param_map['name']
                    if name in self.amplitudes:
                        raise RuntimeError(f'name={name!r} is already defined...')
                    # TODO: skips header parsing
                    iline += 1
                    line0 = lines[iline].strip().lower()
                    data_lines = []
                    while not line0.startswith('*'):
                        data_lines.append(line0.split(','))
                        iline += 1
                        line0 = lines[iline].strip().lower()
                    amplitude = []
                    for sline in data_lines[:-1]:
                        assert len(sline) == 8, sline
                        amplitude += sline
                    assert len(data_lines[-1]) <= 8, sline
                    amplitude += data_lines[-1]
                    self.amplitudes[name] = np.array(amplitude)
                    continue
                    #iline -= 1
                    #line0 = lines[iline].strip().lower()

                #elif 'include' in word:
                    #pass
                elif word.startswith('material'):
                    log.debug('start of material...')
                    iline, line0, material = reader.read_material(lines, iline, word, log)
                    if material.name in self.materials:
                        msg = 'material.name=%r is already defined...\n' % material.name
                        msg += 'old %s' % self.materials[material.name]
                        msg += 'new %s' % material
                        raise RuntimeError(msg)
                    self.materials[material.name] = material
                    log.debug('end of material')
                #elif word.startswith('spring'):
                    #log.debug('start of spring...')
                    #iline, line0, material = read_spring(lines, iline, word, log)
                    #asdf
                    #if material.name in self.materials:
                        #msg = 'material.name=%r is already defined...\n' % material.name
                        #msg += 'old %s' % self.materials[material.name]
                        #msg += 'new %s' % material
                        #raise RuntimeError(msg)
                    #self.materials[material.name] = material
                    #log.debug('end of spring')

                elif word.startswith('step'):
                    #print('step!!!!!!!')
                    iline, line0, step = self.read_step(lines, iline, line0, istep)
                    steps.append(step)
                    istep += 1
                elif word.startswith('initial conditions'):
                    data_lines, iline, line0 = reader.read_star_block(lines, iline, line0, self.log, )
                    for line in data_lines:
                        self.log.debug(line)
                    self.log.debug(f'line_end_of_IC = {line0!r}')
                elif word.startswith('surface interaction'):
                    unused_key = 'surface interaction'
                    unused_data = []
                    while '*' not in line0:
                        sline = line0.split(',')
                        iline += 1
                        line0 = lines[iline].strip().lower()
                    self.log.debug(line0)
                elif word.startswith('friction'):
                    unused_key = 'friction'
                    unused_data = []
                    while '*' not in line0:
                        sline = line0.split(',')
                        iline += 1
                        line0 = lines[iline].strip().lower()
                    self.log.debug(line0)
                elif word.startswith('surface behavior'):
                    unused_key = 'surface behavior'
                    unused_data = []
                    while '*' not in line0:
                        sline = line0.split(',')
                        iline += 1
                        line0 = lines[iline].strip().lower()
                    self.log.debug(line0)
                elif word.startswith('contact damping'):
                    unused_key = 'contact damping'
                    unused_data = []
                    while '*' not in line0:
                        sline = line0.split(',')
                        iline += 1
                        line0 = lines[iline].strip().lower()
                    self.log.debug(line0)
                elif word.startswith('contact pair'):
                    unused_key = 'contact pair'
                    unused_data = []
                    while '*' not in line0:
                        sline = line0.split(',')
                        iline += 1
                        line0 = lines[iline].strip().lower()
                    self.log.debug(line0)
                #elif word.startswith('contact output'):
                    #key = 'contact output'
                    #data = []
                    #while '*' not in line0:
                        #sline = line0.split(',')
                        #iline += 1
                        #line0 = lines[iline].strip().lower()
                    #self.log.debug(line0)

                #  part...
                elif word.startswith('node'):
                    iline, line0, nidsi, nodesi = reader.read_node(
                        lines, iline, self.log, skip_star=True)
                    nids.append(nidsi)
                    nodes.append(nodesi)
                    #print(f'end of node; iline={iline}')
                    iline -= 1
                    line0 = lines[iline].strip().lower()
                    #print(line0)
                elif '*element' in line0:
                    # line0: *ELEMENT,TYPE=C3D4
                    # iline: doesn't start on *element line
                    # 1,263,288,298,265
                    #print(f'start of element; iline={iline}')
                    iline0 = iline
                    line0 = lines[iline].strip().lower()
                    line0, iline, etype, elements = reader.read_element(
                        lines, line0, iline+1, self.log, self.debug)
                    element_types[etype] = elements
                    iline -= 1
                    line0 = lines[iline].strip().lower()
                    #print(f'end of element; iline={iline}')
                    #print(line0)
                    assert iline > iline0
                    #print(line0)

                elif word.startswith('nset'):
                    self.log.debug('reading nset')
                    iline0 = iline
                    self.log.debug(line0)
                    iline, line0, set_name, set_ids = reader.read_nset(
                        lines, iline, line0, self.log, is_instance=False)
                    node_sets[set_name] = set_ids
                    iline -= 1
                    line0 = lines[iline].strip().lower()
                    self.log.info(f'end of nset; line={line0} iline={iline}')
                    assert iline > iline0
                elif word.startswith('elset'):
                    self.log.debug('reading elset')
                    iline0 = iline
                    self.log.debug(line0)
                    iline, line0, set_name, set_ids = reader.read_elset(
                        lines, iline, line0, self.log, is_instance=False)
                    node_sets[set_name] = set_ids
                    iline -= 1
                    line0 = lines[iline].strip().lower()
                    self.log.info(f'end of elset; line={line0} iline={iline}')
                    assert iline > iline0
                elif '*solid section' in line0:
                    iline, solid_section = reader.read_solid_section(line0, lines, iline, self.log)
                    self.log.debug(f'solid_section = {solid_section}')
                    solid_sections.append(solid_section)
                    #iline -= 1
                    #line0 = lines[iline].strip().lower()
                elif '*shell section' in line0:
                    iline, shell_section = reader.read_shell_section(line0, lines, iline, self.log)
                    #print(shell_section)
                    shell_sections.append(shell_section)
                    iline -= 1
                    line0 = lines[iline].strip().lower()
                elif '*hourglass stiffness' in line0:
                    iline, hourglass_stiffness = reader.read_hourglass_stiffness(line0, lines, iline, self.log)
                elif '*orientation' in line0:
                    iline, line0, orientation = reader.read_orientation(line0, lines, iline, self.log)
                elif '*system' in line0:
                    iline, line0, system = reader.read_system(line0, lines, iline, self.log)
                elif '*transform' in line0:
                    iline, line0, transform = reader.read_transform(line0, lines, iline, self.log)
                else:
                    raise NotImplementedError(f'word={word!r} line0={line0!r}')
                wordi = word.split(',')[0]
                self.log.info(f'end of main {wordi!r}; line={line0!r} iline={iline}')
            else:
                # pass
                raise NotImplementedError(f'this should not happen; last_word={word!r} line={line0!r}')
            iline += 1

            #if self.debug:
                #self.log.debug('')

        self.nids = None
        self.nodes = None
        if nids or nodes:
            self.nids, self.nodes = cast_nodes(nids[0], nodes[0], self.log)
        self.elements = Elements(element_types, self.log)
        self.element_sets = element_sets
        self.shell_sections = shell_sections
        self.solid_sections = solid_sections
        self.boundaries = boundaries
        self.steps = steps
        self.log.debug('nassembly = %s' % nassembly)
        for part_name, part in sorted(self.parts.items()):
            self.log.info(str(part))
            part.check_materials(self.materials)
        for unused_mat_name, mat in sorted(self.materials.items()):
            self.log.debug(str(mat))

    def read_assembly(self, lines, iline, line0, word):
        """reads an Assembly object"""
        # TODO: skips header parsing

        iline += 1
        nlines = len(lines)
        line0 = lines[iline].strip().lower()
        element_types = {}
        node_sets = {}
        element_sets = {}

        while not line0.startswith('*end assembly') and iline < nlines:
            self.log.debug('line0 assembly = %s' % line0)

            word = line0.strip('*').lower()
            self.log.info('assembly: %s' % word)
            if '*instance' in line0:
                # TODO: skips header parsing
                iline += 1
                line0 = lines[iline].strip().lower()
                data_lines = []
                while not line0.startswith('*'):
                    data_lines.append(line0.split(','))
                    iline += 1
                    line0 = lines[iline].strip().lower()
                assert line0.startswith('*end instance'), line0
                iline += 1
                line0 = lines[iline].strip().lower()
            elif (word.startswith('surface') or word.startswith('rigid body') or
                  word.startswith('mpc') or word.startswith('tie')):
                # TODO: skips header parsing
                iline += 1
                line0 = lines[iline].strip().lower()
                data_lines = []
                while not line0.startswith('*'):
                    data_lines.append(line0.split(','))
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('nset'):
                iline, line0, set_name, set_ids = reader.read_nset(
                    lines, iline, word, self.log, is_instance=True)
                node_sets[set_name] = set_ids
            elif word.startswith('elset'):
                # TODO: skips header parsing
                params_map = reader.get_param_map(iline, word, required_keys=['instance'])
                set_name = params_map['elset']
                iline += 1
                line0 = lines[iline].strip().lower()
                set_ids, iline, line0 = reader.read_set(lines, iline, line0, params_map)
                element_sets[set_name] = set_ids
            elif word == 'node':
                iline, line0, nids, nodes = reader.read_node(
                    lines, iline, self.log, skip_star=True)
            elif '*element' in line0:
                # doesn't actually start on *element line
                # 1,263,288,298,265
                line0, iline, etype, elements = reader.read_element(lines, line0, iline)
                element_types[etype] = elements
                iline += 1
                line0 = lines[iline].strip().lower()
                #print('line_end =', line0)
            else:
                raise NotImplementedError('\nword=%r\nline=%r' % (word, line0))

        assembly = Assembly(element_types, node_sets, element_sets)
        return iline, line0, assembly

    def read_part(self, lines, iline, line0, word):
        """reads a Part object"""
        sline2 = word.split(',', 1)[1:]

        assert len(sline2) == 1, f'looking for part_name; word={word!r} sline2={sline2}'
        name_slot = sline2[0]
        assert 'name' in name_slot, name_slot
        part_name = name_slot.split('=', 1)[1]
        self.log.debug(f'part_name = {part_name!r}')
        #self.part_name = part_name

        iline += 1
        line0 = lines[iline].strip().lower()
        assert line0.startswith('*node'), line0

        #iline += 1
        #line0 = lines[iline].strip().lower()

        #iline += 1
        #line0 = lines[iline].strip().lower()
        #print('line0 * = ', line0)
        element_types = {}
        node_sets = {}
        element_sets = {}
        #print('resetting nids...')
        nids = []
        nodes = []
        unused_is_start = True
        solid_sections = []
        shell_sections = []
        log = self.log
        while not line0.startswith('*end part'):
            #if is_start:
            iline += 1 # skips over the header line
            log.debug('  ' + line0)
            iword = line0.strip('*').lower()
            log.info(f'part: {iword:s}')
            if '*node' in line0:
                assert len(nids) == 0, nids
                iline, line0, nids, nodes = reader.read_node(lines, iline, log)

            elif '*element' in line0:
                #print(line0)
                line0, iline, etype, elements = reader.read_element(
                    lines, line0, iline, self.log, self.debug)
                element_types[etype] = elements

            elif '*nset' in line0:
                #print(line0)
                iline, line0, set_name, set_ids = reader.read_nset(
                    lines, iline, line0, log, is_instance=False)
                node_sets[set_name] = set_ids

            elif '*elset' in line0:
                # TODO: skips header parsing
                #iline += 1
                #print('elset: ', line0)
                params_map = reader.get_param_map(iline, line0, required_keys=['elset'])
                set_name = params_map['elset']
                line0 = lines[iline].strip().lower()
                set_ids, iline, line0 = reader.read_set(lines, iline, line0, params_map)
                element_sets[set_name] = set_ids

            elif '*surface' in line0:
                # TODO: skips header parsing
                #iline += 1
                line0 = lines[iline].strip().lower()
                data_lines = []
                while not line0.startswith('*'):
                    data_lines.append(line0.split(','))
                    iline += 1
                    line0 = lines[iline].strip().lower()

            elif '*solid section' in line0:
                iline, solid_section = reader.read_solid_section(line0, lines, iline, log)
                solid_sections.append(solid_section)
            elif '*shell section' in line0:
                iline, shell_section = reader.read_shell_section(line0, lines, iline, log)
                shell_sections.append(shell_section)

            elif '*cohesive section' in line0:
                # TODO: skips header parsing
                #iline += 1
                line0 = lines[iline].strip().lower()
                data_lines = []
                while not line0.startswith('*'):
                    data_lines.append(line0.split(','))
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif '*mass' in line0:
                # TODO: skips header parsing
                #iline += 1
                line0 = lines[iline].strip().lower()
                data_lines = []
                while not line0.startswith('*'):
                    data_lines.append(line0.split(','))
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif '*rotary inertia' in line0:
                # TODO: skips header parsing
                #iline += 1
                line0 = lines[iline].strip().lower()
                data_lines = []
                while not line0.startswith('*'):
                    data_lines.append(line0.split(','))
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif '*orientation' in line0:
                iline, line0, orientation_fields = reader.read_orientation(line0, lines, iline, log)
            else:
                msg = f'line={line0!r}\n'
                allowed = ['*node', '*element', '*nset', '*elset', '*surface',
                           '*solid section', '*cohesive section']
                msg += 'expected=[%r]' % ', '.join(allowed)
                raise NotImplementedError(msg)

            line0 = lines[iline].strip().lower()
            unused_is_start = False

            #print(line0)
        #node_sets = []
        #element_sets = []

        if self.debug:
            self.log.debug('part_name = %r' % part_name)
        #print('part.shell_sections =', shell_sections)
        part = Part(part_name, nids, nodes, element_types, node_sets, element_sets,
                    solid_sections, shell_sections, self.log)
        return iline, line0, part_name, part

    def read_step(self, lines, iline, line0, istep):
        """reads a step object"""
        log = self.log
        log.debug(f'  start of step {istep:d}...')

        boundaries = []
        node_output = []
        element_output = []
        cloads = []
        # case 1
        # ------
        # *Step, name=Step-1, nlgeom=NO, inc=10000
        # *Static
        # 0.01, 1., 1e-05, 0.01
        #
        # case 2
        # ------
        #*STEP, NLGEOM=YES, AMPLITUDE=RAMP, INC=10000
        # Increase from T=117.0C to T=122.0C over 300.0 seconds  (1C/min)
        # *Static
        # 0.01, 1., 1e-05, 0.01
        #
        # case 3
        # ------
        # *STEP
        # *STATIC
        # *CLOAD
        # LOAD,2,-25

        iline += 1
        line0 = lines[iline].strip().lower()
        step_name = ''
        if not line0.startswith('*'):
            step_name = lines[iline].strip()
            iline += 1
            line0 = lines[iline].strip().lower()
        word = line0.strip('*').lower()

        #allowed_words = ['static', 'boundary', 'dsload', 'restart', 'output', 'node',
                         #'element output']
        #print('  word =', word)
        #print('  lineA =', line0)
        while word != 'end step':
            self.log.debug('    step_word = %r' % word)
            iline += 1
            line0 = lines[iline].strip().lower()
            #print('word =', word)
            #print('active_line =', line0)
            unused_data_lines = []
            if word == 'static':
                sline = line0.split(',')
                _line = lines[iline].strip().lower()
                if _line.startswith('*'):
                    pass
                else:
                    # 0.01, 1., 1e-05, 0.01
                    #print('sline', sline, line0)
                    assert len(sline) == 4, sline
                    iline += 1
            elif word.startswith('restart'):
                line0 = lines[iline].strip().lower()
                word = line0.strip('*').lower()
                continue
                #print('  line_sline =', line0)
                #iline -= 1
                #line0 = lines[iline].strip().lower()
                #sline = line0.split(',')
                #assert len(sline) == 3, sline
                #iline += 1

            elif word.startswith('dsload'):
                #iline += 1
                #line0 = lines[iline].strip().lower()
                #print('  line_sline =', line0)
                sline = line0.split(',')
                assert len(sline) == 3, sline
                iline += 1
            elif word.startswith('dynamic'):
                self.log.debug(f'    line_sline = {line0!r}')
                #iline += 1
                #line0 = lines[iline].strip().lower()
                sline = line0.split(',')
                assert len(sline) >= 2, sline
                iline += 1
            elif word.startswith('visco'):
                iline += 1
            elif word.startswith('temperature'):
                iline -= 1
                line0 = lines[iline].strip().lower()
                unused_data_lines, iline, line0 = reader.read_star_block(
                    lines, iline, line0, self.log, debug=True)
                iline += 1
            elif word.startswith('controls'):
                #self.log.debug('      controls')
                unused_data_lines, iline, line0 = reader.read_star_block(lines, iline, line0, self.log, )
                iline += 1
                line0 = lines[iline].strip().lower()
                #for line in data_lines:
                    #print(line)

            elif word.startswith('output'):
                line0 = lines[iline].strip().lower()
                word = line0.strip('*').lower()
                continue
            elif word == 'node output':
                node_output = []
                while '*' not in line0:
                    sline = line0.split(',')
                    node_output += [val.strip() for val in sline]
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('element output'):
                element_output = []
                while '*' not in line0:
                    sline = line0.split(',')
                    element_output += [val.strip() for val in sline]
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('contact output'):
                unused_contact_output = []
                while '*' not in line0:
                    sline = line0.split(',')
                    element_output += [val.strip() for val in sline]
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('boundary'):
                boundary, iline, line0 = reader.read_boundary(lines, line0, iline)
            elif word.startswith('buckle'):
                node_output = []
                while '*' not in line0:
                    sline = line0.split(',')
                    node_output += sline
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('cload'):
                iline, line0, cload = reader.read_cload(line0, lines, iline, log)
                cloads.append(cload)
            elif word.startswith('node print'):
                node_output = []
                while '*' not in line0:
                    sline = line0.split(',')
                    node_output += sline
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('node file'):
                node_output = []
                while '*' not in line0:
                    sline = line0.split(',')
                    node_output += [val.strip() for val in sline]
                    iline += 1
                    line0 = lines[iline].strip().lower()
            elif word.startswith('el file'):
                element_output = []
                while '*' not in line0:
                    sline = line0.strip().split(',')
                    element_output += [val.strip() for val in sline]
                    iline += 1
                    line0 = lines[iline].strip().lower()
            else:
                msg = print_data(lines, iline, word, 'is this an unallowed word for *Step?\n')
                raise NotImplementedError(msg)
            line0 = lines[iline].strip().lower()
            word = line0.strip('*').lower()
            #print('  lineB =', line0)
            #print('  word2 =', word)
        #iline += 1
        #iline -= 1
        step = Step(step_name, boundaries,
                    node_output, element_output,
                    cloads, is_nlgeom=False)
        self.log.debug('  end of step %i...' % istep)
        return iline, line0, step

    def write(self, abaqus_filename_out, is_2d=False):
        self.log.info('writing %r' % abaqus_filename_out)
        assert isinstance(self.steps, list), self.steps
        #self.parts = {}
        #self.boundaries = {}
        #self.materials = {}
        #self.amplitudes = {}
        #self.assembly = None
        #self.initial_conditions = {}
        #self.steps = {}
        #self.heading = None
        #self.preprint = None
        with open(abaqus_filename_out, 'w') as abq_file:
            self.log.debug(f'  nparts = {len(self.parts):d}')
            self.log.debug(f'  nmaterials = {len(self.materials):d}')
            if self.assembly is not None:
                self.assembly.write(abq_file)
            for unused_part_name, part in self.parts.items():
                part.write(abq_file, is_2d=is_2d)
            for unused_part_name, initial_conditions in self.initial_conditions.items():
                initial_conditions.write(abq_file)
            for unused_part_name, amplitude in self.amplitudes.items():
                amplitude.write(abq_file)
            for unused_mat_name, mat in self.materials.items():
                mat.write(abq_file)
            for step in self.steps:
                #print(step)
                #print(abq_file)
                step.write(abq_file)

    def __repr__(self) -> str:
        msg = (
            'Abaqus:\n'
            f'  parts={self.parts}\n'
            f'  boundaries={self.boundaries}\n'
            f'  materials={self.materials}\n'
            f'  amplitudes={self.amplitudes}\n'
            f'  assembly={self.assembly}\n'
            f'  initial_conditions={self.initial_conditions}\n'
            f'  steps={self.steps}\n'
            f'  shell_sections={self.shell_sections}\n'
            f'  solid_sections={self.solid_sections}\n'
        )
        return msg

def get_nodes_nnodes_nelements(model: Abaqus, stop_for_no_elements: bool=True):
    """helper method"""
    nnodes = 0
    nelements = 0
    nids = []
    all_nodes = []
    #if model.nodes and model.elements:
    if model.nids is not None and len(model.nids):
        nidsi = model.nids
        nodes = model.nodes
        elements = model.elements

        nnodes += nodes.shape[0]
        nelements += elements.nelements
        nids.append(nidsi)
        all_nodes.append(nodes)

    for unused_part_name, part in model.parts.items():
        nidsi = nnodes + part.nids
        nodes = part.nodes
        elements = part.elements

        nnodes += nodes.shape[0]
        nelements += elements.nelements
        nids.append(nidsi)
        all_nodes.append(nodes)

    if nelements == 0 and stop_for_no_elements:
        raise RuntimeError('nelements=0')

    if len(all_nodes) == 1:
        nids = nids[0]
        nodes = all_nodes[0]
    else:
        nids = np.hstack(nids)
        nodes = np.vstack(all_nodes)
        assert len(nodes) == len(nids)
    return nnodes, nids, nodes, nelements

def main(): # pragma: no cover
    """tests a simple abaqus model"""
    abaqus_inp_filename = 'mesh.inp'
    part_name = 'part-spec'
    eid = 3707

    model = read_abaqus(abaqus_inp_filename)
    part = model.parts[part_name]
    print(part)
    etype, ieid, elem = part.element(eid)
    print(f'etype={etype} ieid={ieid:d} elem={elem}')
    #return

    unused_nids = part.nids - 1
    nodes = part.nodes
    cohesive_elements = part.coh2d4
    assert cohesive_elements is not None, cohesive_elements
    n1 = cohesive_elements[:, 1] - 1
    n2 = cohesive_elements[:, 2] - 1
    #print('n1 =', n1)
    #print('n2 =', n2)
    #print('nodes =', nodes)

    #ix = np.unique(np.hstack([n2, n1]))
    ix = np.append(n2, n1[-1])
    eids = cohesive_elements[:, 0] #- cohesive_elements[0, 0]
    x = nodes[ix, 0]
    edge_length_21 = np.abs(nodes[n2, 0] - nodes[n1, 0])
    edge_length_max = edge_length_21.max()
    edge_length_min = edge_length_21.min()
    dedge = edge_length_max - edge_length_min
    #print('edge_length_21 =\n%s' % edge_length_21)

    import matplotlib.pyplot as plt
    plt.figure(1)
    plt.suptitle(abaqus_inp_filename)
    plt.plot(eids, edge_length_21 * 1000., 'b-o')
    if dedge < 1e-6:
        plt.ylim(0.98 * edge_length_min * 1000.,
                 1.02 * edge_length_min * 1000.)
    plt.ylabel('edge length (mm)')
    plt.xlabel('element id')
    plt.grid()

    plt.figure(2)
    plt.suptitle(abaqus_inp_filename)
    plt.plot(x[:-1] * 1000., edge_length_21 * 1000., 'b-o')
    if dedge < 1e-6:
        plt.ylim(0.98 * edge_length_min * 1000.,
                 1.02 * edge_length_min * 1000.)
    plt.grid()
    plt.ylabel('edge length (mm)')
    plt.xlabel('x location (mm)')
    plt.show()

if __name__ == '__main__': # pragma: no cover
    main()


"""
Custom TK dialogs
"""

from typing import List
import tkinter as tk

from flashcards.cards import Card, Deck

class DeckListDialog(tk.Toplevel):
    """Deck selection dialogue"""

    def __init__(self, master, decks, on_change):
        super().__init__(master)
        self.geometry("300x160")
        self.columnconfigure(0, weight=3)
        self.columnconfigure(1, weight=1)
        self._label = tk.Label(self, text="Select deck: ")
        self._label.grid(row=0, column=0, sticky="nsew")
        self._decks: List["Deck"] = decks
        self._deck_options = (d.deck_name for d in decks)
        self._option_var = tk.StringVar(self)
        self._options = tk.OptionMenu(
            self,
            variable=self._option_var,
            value=self._deck_options[0],
            *self._deck_options,
            command=self.on_selection_changed_cmd
        )
        self._options.grid(row=0, column=1, sticky="nsew")
        self._confirm_btn = tk.Button(
            self, text="Confirm", command=self.on_confirm_command
        )
        self._options.grid(row=1, column=1, sticky="e")
        self._selected_deck = None
        self._on_deck_changed_cb = on_change

    def on_selection_changed_cmd(self):
        """selection handler for option menu"""
        self._selected_deck = None
        for deck in self._decks:
            if deck.deck_name == self._option_var.get():
                self._selected_deck = deck
                break

    def on_confirm_command(self):
        "Confirmation button handler"
        if self._selected_deck is not None:
            self._on_deck_changed_cb(self._selected_deck)
            self.destroy()
            self.update()

def deck_list_dialog(decks: List["Deck"]) -> "Deck":
    """Dialogue returning one of the decks"""
    win = tk.Toplevel()
    win.title("Deck List")

    decks_names = [deck.deck_name for deck in decks]
    deck_list = tk.Variable(value=decks_names)
    deck_listbox = tk.Listbox(win, listvariable=deck_list, height=6)

    selected_element = None

    def store(_event):
        nonlocal selected_element
        selected_index = deck_listbox.curselection()
        deck_name = deck_listbox.get(selected_index)
        selected_element = decks[deck_name]

    deck_listbox.bind("<Double-1>", store)
    deck_listbox.pack(fill="both")
    win.wait_window()
    return selected_element

def card_edit_dialog(card: "Card" = None):
    """Dialogue for card editing"""
    win = tk.Toplevel()
    win.title("Card Edit")
    win.wait_window()
    return card


import numpy as np
import pandas as pd

import matplotlib.pyplot as plt

import torch
from torch import tensor as ts

import mdn_base
from factory_traffic_dataset import load_Map
from data_handler import Data_Handler as DH
from misc_mixture_density_network import Mixture_Density_Network as MDN

import timeit

print("Program: load\n")

model_path = './CASE/model/Model_CASE_p5m20_512_256_256_128_64'
data_path  = './CASE/data/210309_p5m20_2000s_h.2_700_314_249.csv'

df = pd.read_csv(data_path)
data = df.to_numpy()[:,:-2]
data[:,:-2] = data[:,:-2]
labels = df.to_numpy()[:,-2:]

maxT = 20
past = 5
num_gaus = 10
layer_param = [512, 256, 256, 128, 64]

data_shape = (1, past*2+4)
label_shape = (1, 2)

myMDN = MDN(data_shape, label_shape, num_gaus=num_gaus, layer_param=layer_param, verbose=False)
myMDN.build_Network()
model = myMDN.model
print(myMDN.layer_param)
model.load_state_dict(torch.load(model_path))
model.eval() # with BN layer, must run eval first

# fig, ax = plt.subplots()
# load_Map(ax)

print('There are {} samples.'.format(data.shape[0]))
idx = 12598 # change the index here

sample = data[idx,:] # x_h, y_h, x_t, y_t, T, type
label = labels[idx,:]

traj_input = sample[:-2]
T = sample[-2]
obj_type = sample[-1].astype(int)

past_x = traj_input[::2]
past_y = traj_input[1::2]

beta_test = ts(np.array([sample]).astype(np.float32))

start_time = timeit.default_timer()
alp, mu, sigma = model(beta_test)
inference_time = timeit.default_timer() - start_time
print(f'The inference time is ~{round(inference_time*1e3,2)} ms/sample.')

alp, mu, sigma = mdn_base.take_mainCompo(alp, mu, sigma, main=3)
# alp, mu, sigma = mdn_base.take_goodCompo(alp, mu, sigma, thre=0.1)
alp = alp/sum(alp[0])

# plt.plot(traj_input[-2], traj_input[-1], 'bx', label="current")
# plt.plot(label[0], label[1], 'rx', label="future truth")
# plt.plot(past_x[:-1], past_y[:-1], 'kx', label="past")

alp_det = alp.detach().numpy()[0]
alp_idx = np.argsort(alp_det)

pred_x = mu.detach().numpy()[0][:,0]
pred_y = mu.detach().numpy()[0][:,1]
style = ['gx','go','g.','g.','g.']
# for i in range(len(alp_det)):
#     if i==0:
#         plt.plot(pred_x[alp_idx[-1-i]], pred_y[alp_idx[-1-i]], style[i], label="First MDN Prediction")
#     elif i==1:
#         plt.plot(pred_x[alp_idx[-1-i]], pred_y[alp_idx[-1-i]], style[i], label="Second MDN Prediction")
#     else:
#         plt.plot(pred_x[alp_idx[-1-i]], pred_y[alp_idx[-1-i]], style[i], label="Other MDN Prediction")

# plt.xlabel("x"), plt.ylabel("y")
# plt.legend()
# plt.title("MDN CASE Demo")

# mdn_base.draw_probDistribution(ax, alp, mu, sigma, main=None, nsigma=2, step=0.01, colorbar=True) # step should be proper
# plt.show()


import copy
import cv2
from scipy import spatial
import numpy as np

sourceVideo = "This_Cool_Machine_Turns_Waste_Paper_Into_New_Paper_Within_Just_3_Minutes-LL9YgFEHW5U.mp4"
sourceVideo = "Sutherland-Hodgman_Polygon_Clipping_Algorithm-S091lKYWbSs.mp4"
cap = cv2.VideoCapture(sourceVideo)
cv2.waitKey(1)
counter = 0
while(cap.isOpened()):
    ret, frame = cap.read()
    if (type(frame) == type(None)):
        break
    if (cv2.waitKey(1) & 0xFF == ord('q')) or frame.size == 0:
        break

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    if counter % 30 == 0:
        print(counter)
    counter += 1

cap.release()


from app import app,api,ent_df
from app.get_tags import get_entity_tags,get_drugs_and_conditions,clean_text
from app.ner_tagger import medical_suite,load_crf

from flask import render_template,request,jsonify,url_for,redirect

crf_path = "crf_model"
crf = load_crf(crf_path)

text = ['''HISTORY OF PRESENT ILLNESS:  This is an 81-year-old female
with a history of emphysema (not on home O2), who presents
with three days of shortness of breath thought by her primary
care doctor to be a COPD flare.  Two days prior to admission,
she was started on a prednisone taper and one day prior to
admission she required oxygen at home in order to maintain
oxygen saturation greater than 90%.  She has also been on
levofloxacin and nebulizers, and was not getting better, and
presented to the [**Hospital1 18**] Emergency Room.''',
            '''HISTORY OF PRESENT ILLNESS:
44 yo female with a h/o left frontal AVM in the supplementary
motor area. The AVM was treated with stereotactic radiosurgery
(Gamma Knife)in [**2114**]. In [**2116**], the patient developed a seizure
disorder. [**2118-5-27**] she developed
headaches and after an MRI and a digital angiogram showed no
residual pathological vessels, a contrast enhancing lesion
with massive focal residual edema was diagnosed- very
likely represents radionecrosis. The patient had midline
shift and mass effect. On [**2118-8-10**] she had a left craniotomy for
resection of the radionecrosis. She then presented to the office
in [**2118-8-27**] with increased left facial swelling and incision
drainage, she was taken to the OR for a wound washout and
craniectomy. She now returns for a cranioplasty after a long
course of outpatient IV antibiotic therapy.''',
            '''HISTORY OF PRESENT ILLNESS:
This 81 year old woman has a history of COPD. Over the past five
years she has had progressive difficulties with her breathing.
In [**2118-6-4**] she was admitted to [**Hospital1 18**] for respiratory failure
due to a COPD exacerbation. Due to persistent hypoxemia, she
required intubation and a eventual bronchoscopy on [**2118-6-9**] revealed marked
narrowing of the airways on expiration consistent with
tracheomalacia. She subsequently underwent placement of two
silicone stents, one in the left main stem and one in the
trachea. During the admission the patient had complaints of
chest pain and ruled out for an MI. She was subsequently discharged to
[**Hospital1 **] for physical and pulmonary rehab. Repeat bronchoscopy
on [**2118-8-1**] revealed granulation tissue at the distal right lateral
wall of the tracheal stent. There was significant malacia of the
peripheral and central airways with complete collapse of the
airways on coughing and forced expiration. Small nodules were
also noted on the vocal cords. She has noticed improvement in
her respiratory status, but most recently has been in discussion
with Dr. [**First Name4 (NamePattern1) 951**] [**Last Name (NamePattern1) 952**]
regarding possible tracheobronchial plasty with mesh. Tracheal stents d/c [**2119-4-19**]
in anticipation of surgery. In terms of symptoms, she describes many years of intermittent
chest pain that she describes as left sided and occurring at any
time. Currently, she notices it about three times a week, and
states that it seems to resolve after three nitroglycerin.
She currently is dependent on oxygen and wears 1.5-2 liters
around the clock. She has frequent coughing and brings up "dark
sputum"''',
'''HISTORY OF PRESENT ILLNESS:
[**Known firstname 622**] [**Known lastname 1836**] is a 62-year-old woman, with longstanding
history of rheumatoid arthritis, probable Sweet's syndrome, and
multiple joint complications requiring orthopedic interventions.
She was found to hve a right cavernous sinus and nasopharyngeal
mass.  She underwent a biopsy of hte nasopharyngeal mass by Dr.
[**First Name4 (NamePattern1) **] [**Last Name (NamePattern1) 1837**] and the pathology, including flow
cytometry,
was reactive for T-cell lymphoid hyperplasia only.

She has a longstanding history of rheumatoid arthritis that
involved small and large joints in her body.  Her disease is
currently controlled by abatacept, hydroxychloroquine, and
methotrexate.  She also has a remote history of erythematous
nodules at her shins, dermatosis (probable Sweet's disease),
severe holocranial headache with an intensity of [**9-28**], and
dysphagia.  But her symptoms resolved with treatment for
autoimmune disease.  Please refer additional past medical
history, past surgical history, facial history, and social
history to the initial note on [**2171-11-4**].

She cam to the BTC for discussion about management of her right
cavernous sinus mass that extends into the middle cranial fossa.

She had a recent head CT at the [**Hospital1 756**] and Woman's Hospital on
[**2171-11-29**], when she went for a consultation there.
She is neurologically stable without headache, nausea, vomiting,
seizure, imbalance, or fall. She has no new systemic complaints.

Her neurological problem started [**9-/2171**] when she experienced
frontal pressure-like sensations.  There was no temporal
pattern;
but they may occur more often in the evening.
She had fullness in her ear and she also had a cold coinciding
to
the onset of her headache.  By late [**Month (only) 359**] and early [**2171-10-21**], she also developed a sharp pain intermittently in the
right
temple region.
She did not have nausea, vomiting, blurry vision, imbalance, or
fall.  A gadolinium-enhanced head MRI, performed at [**Hospital1 346**] on [**2171-10-30**], showed a bright mass
involving the cavernous sinus.''',
'''HISTORY OF PRESENT ILLNESS:
82 yo F with CAD, CHF, HTN, recent PE ([**10-17**]), who presents from
rehab with hypoxia and SOB despite Abx treatment for PNA x 3
days. The patient was in rehab after being discharged from here
for PE. She was scheduled to be discharged on [**12-6**]; on the day
prior to discharge she deveoped fever, hypoxia, and SOB. CXR
showed b/t lower lobe infiltrates. She was started on levoflox
and ceftriaxone on [**12-5**]. When she became hypoxic on NC they
brought her in to the ED.
.
In the [**Hospital1 18**] ED she was febrile to 102.7, P 109 BP 135/56 R 34
O2 90% on 3L. She was started on vanc and zosyn for broader
coverage, tylenol, and 2L NS.
.
The patient reports having sweats and cough before admission.
She complains of SOB and some upper back pain. She denies chest
pain, URI sx, nausea/vomiting, diarrhea, or dysuria. Of note she
had had a rash and was given prednisone for 7 days, ending
[**12-3**]. The rash was speculated to be due to coumadin, but she
was able to be continued on coumadin.''',
'''HISTORY OF PRESENT ILLNESS:
Mr. [**Known lastname 1858**] is an 84 yo man with moderate aortic stenosis (outside
hospital echo in [**2124**] with [**Location (un) 109**] 1 cm2, gradient 28 mmHg, moderate
mitral regurgitation, mild aortic insufficiency), chronic left
ventricular systolic heart failure with EF 25-30%, hypertension,
hyperlipidemia, diabetes mellitus, CAD s/p CABG in [**2099**] with
SVG-LAD-Diagonal, SVG-OM, and SVG-RPDA-RPL, with a re-do CABG in
[**9-/2117**] with LIMA-LAD, SVG-OM, SVG-diagonal, and SVG-RCA. He also
has severe peripheral arterial disease s/p peripheral bypass
surgery. He presented to [**Hospital 1474**] Hospital ER this morning with
shortness of breath and chest pain and was found to be in heart
failure.

He states he was in his usual state of health until 10:30 last
evening when he woke up feeling cold; 1 hour later he developed
moderate to severe sharp chest pain radiating across his chest
associated with nausea, diaphoresis, and dypsnea. The pain was
fairly constant and did not resolve until he was given sL NTG at
6 am by EMS. He has been pain free since. Presenting vitals BP
109/66, HR 71, O2 sat 88% on RA. CXR showed congestive heart
failure; initial troponin-I was mildly elevated at 0.4, CK 70.
He given aspirin and furosemide 80 mg IV (with ~600cc diuresis),
Nitropaste [**1-3**]", and Lovenox 80 mg SQ. During the ambulance
transfer to the [**Hospital1 18**], he also received ~500 cc IVF for ? low
BP).

On further questioning, Mr. [**Known lastname 1858**] has very poor exercise
tolerance due to knee pain that he attributes to osteoarthritis.
But he says that he gets chest pain (similar to pain he had last
night) with fairly minimal exertion (picking up his 11 lb cat,
carrying 1 gallon jug of water, first getting up from sitting to
walk outside or to walk to the bedroom). The pain is associated
with dyspnea and is relieved with few minutes rest. His symptoms
occur about every day to every other day and have been stable
over the past year. He denies orthopnea, paroxysmal nocturnal
dyspnea, but does endorse exertional dyspnea (he cannot identify
the amount of exertion required). Currently, he is dyspneic and
feels somewhat better sitting up; he reports no chest pain.

ROS is also positive for a nose bleed requiring ED visit several
months ago (and cessation of Plavix for a few days), and
currently gross hematuria after Foley placement and Lovenox.'''
    ]

@app.route("/contact")
def test():
    return render_template("/contact.html")

@app.route("/annotation")
def contribute():
    return render_template("/annotation.html")

@app.route("/services")
def services():
    return render_template("/services.html")

@app.route("/demo",methods=["GET","POST"])
def demo():
    idx = 0
    if request.method == "POST":
        idx = int(request.form["index"])
    return render_template("/demo.html",sample=text[idx],index=idx)

@app.route("/medical_suite",methods=["POST"])
def suite():
    if request.method == "POST":
        index = int(request.form["index"])
        sample = text[index]
        age,gender,symptoms,conditions,procedures,drugs = medical_suite(sample,crf)
        return render_template("/medical_suite.html",sample=sample,age=age,gender=gender,symptoms=symptoms,conditions=conditions,procedures=procedures,drugs=drugs)

@app.route("/",methods=["GET","POST"])
@app.route("/home",methods=["GET","POST"])
def home():
    d_and_c = {"DRUGS":['None'],"CONDITIONS":["None"]}
    if request.method == "POST":
        text = request.form["text"]
        text = clean_text(text)
        ent_locs = get_entity_tags(text,ent_df)
        d_and_c = get_drugs_and_conditions(ent_locs)
        return render_template("/home.html",ents = d_and_c)
    else:
        return render_template("/home.html",ents=d_and_c)


import pygame, sys, random, time
from pygame.locals import *

FPS=30
WINDOWWIDTH = 750
WINDOWHEIGHT = 800
BLOCK_SPACE = 1

BOARDWIDTH = 300 + 9 * BLOCK_SPACE
BOARDHEIGHT = 600 +19 * BLOCK_SPACE
BOARD_LEFT = (WINDOWWIDTH - BOARDWIDTH)/2
BOARD_UP = (WINDOWHEIGHT - BOARDHEIGHT)/2
RECT_BOARD = pygame.Rect(BOARD_LEFT, BOARD_UP, BOARDWIDTH, BOARDHEIGHT)

BLOCK_SIZE =(BOARDWIDTH - 9 * BLOCK_SPACE) / 10

#colors
BLACK   = (  0,  0,  0)
WHITE   = (255,255,255)
RED     = (255,  0,  0)
GREEN   = (  0,255,  0)
BLUE    = (  0,  0,255)
YELLOW  = (255,255,  0)
AQUA    = (  0,255,255)
MAGENTA = (255,  0,255)
ORANGE = (255,127,0)
PURPLE = (128,0,128)

BACK_GROUND_COLOR = BLACK
dropfrequency = 0.25
movesidewayfrequency = 0.15
movedownfrequency = 0.1
rotatefrequency = 0.15
class BLOCK:
    def __init__(self, x, y, color = BACK_GROUND_COLOR ):
        self.x = x
        self.y = y
        self.state = '0'
        self.color = BACK_GROUND_COLOR
        self.left = BOARD_LEFT + self.x * (BLOCK_SIZE + BLOCK_SPACE)
        self.up = BOARD_UP + self.y * (BLOCK_SIZE + BLOCK_SPACE)
        self.rect = (self.left, self.up, BLOCK_SIZE, BLOCK_SIZE)
    def clear(self):
        self.state = '0'
        self.color = BACK_GROUND_COLOR
    def __str__(self):
        return f'[{self.x}, {self.y}]'
AboveLine = 0

Allblocks=[[BLOCK(i,j) for i in range(10)] for j in range(-AboveLine,20)]
##Allblocksign = [[Allblocks[j][i].state for i in range(10)] for j in range(20 + AboveLine)]
#[...-3,...-2,...-1,[[0, 0], [1, 0],..., [9, 0]][[0, 1],...[9, 1]]...]
#O,I,J,L,S,T,Z
TetrisColorDict = {'I':AQUA, 'J':BLUE, 'L':ORANGE, 'O':YELLOW, 'S':GREEN, 'T':PURPLE, 'Z':RED}
TetrisRotationtypeDict = {'I':2, 'J':4, 'L':4, 'O':1, 'S':2, 'T':4, 'Z':2}
class Tetris:
    def __init__(self, shape):
        self.shape = shape
        self.center = [4,0]
        self.rotationstate = 0
        self.color = TetrisColorDict[self.shape]

    def centerx(self):
        return self.center[0]
    def centery(self):
        return self.center[1]
    def rotationtype(self):
        return TetrisRotationtypeDict[self.shape]
    def position(self):

        if self.shape == 'I':
            if self.rotationstate >= 2:
                self.rotationstate = self.rotationstate % 2
            search = {0:[[self.centerx(),self.centery()-2], [self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1]],
                      1:[[self.centerx()+2,self.centery()], [self.centerx()+1,self.centery()], [self.centerx(),self.centery()], [self.centerx()-1,self.centery()]]}

            return search[self.rotationstate]
        if self.shape == 'O':
            self.rotationtype = 1
            search = {0:
        [[self.centerx(),self.centery()],
        [self.centerx()+1,self.centery()],
        [self.centerx(),self.centery()+1],
        [self.centerx()+1,self.centery()+1]]
            }
            return search[self.rotationstate]
        if self.shape == 'J':
            if self.rotationstate >= 4:
                self.rotationstate = self.rotationstate % 4
            search = {0:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1], [self.centerx()-1,self.centery()+1]],
                      1:[[self.centerx()+1,self.centery()], [self.centerx(),self.centery()], [self.centerx()-1,self.centery()], [self.centerx()-1,self.centery()-1]],
                      2:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1], [self.centerx()+1,self.centery()-1]],
                      3:[[self.centerx()-1,self.centery()], [self.centerx(),self.centery()], [self.centerx()+1,self.centery()], [self.centerx()+1,self.centery()+1]],
                     }

            return search[self.rotationstate]

        if self.shape == 'L':
            if self.rotationstate >= 4:
                self.rotationstate = self.rotationstate % 4
            search = {0:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1], [self.centerx()+1,self.centery()+1]],
                      1:[[self.centerx()+1,self.centery()], [self.centerx(),self.centery()], [self.centerx()-1,self.centery()], [self.centerx()-1,self.centery()+1]],
                      2:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1], [self.centerx()-1,self.centery()-1]],
                      3:[[self.centerx()-1,self.centery()], [self.centerx(),self.centery()], [self.centerx()+1,self.centery()-1], [self.centerx()+1,self.centery()]],
                     }

            return search[self.rotationstate]
        if self.shape == 'S':
            if self.rotationstate >= 2:
                self.rotationstate = self.rotationstate % 2
            search = {0:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx()+1,self.centery()], [self.centerx()+1,self.centery()+1]],
                      1:[[self.centerx()-1,self.centery()], [self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx()+1,self.centery()-1]]}

            return search[self.rotationstate]

        if self.shape == 'T':
            if self.rotationstate >= 4:
                self.rotationstate = self.rotationstate % 4
            search = {0:[[self.centerx()-1,self.centery()], [self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx()+1,self.centery()]],
                      1:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx()+1,self.centery()], [self.centerx(),self.centery()+1]],
                      2:[[self.centerx()+1,self.centery()], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1], [self.centerx()-1,self.centery()]],
                      3:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx()-1,self.centery()], [self.centerx(),self.centery()+1]],
                     }

            return search[self.rotationstate]
        if self.shape == 'Z':
            if self.rotationstate >= 2:
                self.rotationstate = self.rotationstate % 2

            search = {0:[[self.centerx(),self.centery()-1], [self.centerx(),self.centery()], [self.centerx()-1,self.centery()], [self.centerx()-1,self.centery()+1]],
                      1:[[self.centerx()-1,self.centery()], [self.centerx(),self.centery()], [self.centerx(),self.centery()+1], [self.centerx()+1,self.centery()+1]]}

            return search[self.rotationstate]

def istop(InputTetris): # a subfunction of the 'topmost' function
    check = InputTetris.position()
    i = 0
    while i < len(check):
        if [check[i][0],check[i][1]-1] in check:
            check.remove([check[i][0],check[i][1]])
        else:
            i+=1

    return check
def topmost(InputTetris): # It is used to ensure that the dropping block would not appear on the screen until its y position > 0
    result = istop(InputTetris)
    empty = []
    for i in range(len(result)):
        empty.append(result[i][1])
    return min(empty)
def canRotate(InputTetris): # to simulate whether it is possible for the block to rotate
    if InputTetris.shape == 'O': # since the 'O' shape don't need to rotate
        return True
    else:
        InputTetris.rotationstate = (InputTetris.rotationstate + 1) % InputTetris.rotationtype() #let's change to the simulate state
        check = InputTetris.position()

        for i in range(len(check)):
            if check[i][1] >= 20: # touch the bottom boundary
                InputTetris.center[1] -= bottom(InputTetris) - 19 # to denote how many times the block should be lifted up
                check2 = InputTetris.position() # after lifting up the block, we need to detect whether the block position is suitable
                for i in range(len(check2)):
                    if Allblocks[check2[i][1]+AboveLine][check2[i][0]].state == '1' : # if the block collides with other blocks, which is forbidden
                        InputTetris.center[1] +=1 # back to the original state, and the rotation command is invalid
                        return False
            elif check[i][0] >= 10: # touch the right boundary
                InputTetris.center[0] -= rightmost(InputTetris) - 9 # to denote how many times the block should be moved left
                check2 = InputTetris.position()
                for i in range(len(check2)) :
                    if Allblocks[check2[i][1]+AboveLine][check2[i][0]].state == '1' : #touch other blocks
                        if InputTetris.shape == 'I': # the block 'I' case should be unique. Just consider this exclusive case for 'I' block:
                            InputTetris.center[0] -=1 # the rightmost block of the 'I' block overlaps with other blocks. As a result, we need to move left once again to fulfill this case.
                            check3 = InputTetris.position() # all the cases are considered. Let's test whether the block position is suitable:
                            for i in range(len(check3)) : # we don't need to consider the case that the leftmost part of the 'I' block cross the left boundary, which is impossible.
                                if Allblocks[check3[i][1]+AboveLine][check3[i][0]].state == '1' : # if the block still touches other blocks
                                    InputTetris.center[0] +=2 # back to the original state, and the rotation command is invalid
                                    return False
                            return True
                        else: # for other blocks, which are not as complicated as the 'I' block is,
                            InputTetris.center[0] +=1 #just back to the original state, invalidating the rotation command
                            return False
                return True
            elif check[i][0] < 0: # touch the left boundary. The rules are similar to the previous one.
                InputTetris.center[0] += -leftmost(InputTetris)
                check2 = InputTetris.position()
                for i in range(len(check2)):
                    if Allblocks[check2[i][1]+AboveLine][check2[i][0]].state == '1' :
                        if InputTetris.shape == 'I':
                            InputTetris.center[0] +=1
                            check3 = InputTetris.position()
                            for i in range(len(check3)) :
                                if Allblocks[check3[i][1]+AboveLine][check3[i][0]].state == '1' : #touch other blocks
                                    InputTetris.center[0] -=2
                                    return False
                            return True
                        else:
                            InputTetris.center[0] -=1
                            return False
                return True
            elif Allblocks[check[i][1]+AboveLine][check[i][0]].state == '1' : # overlap with other blocks
                if check[i] in isleft(InputTetris): # if other blocks collide with the 'left part' of the block:
                    InputTetris.center[0] +=1  # move right once
                    check2 = InputTetris.position() # check whether this modification is accepted:
                    for i in range(len(check2)) :
                        if check2[i][0] >= 10: #touch the right boundary
                            InputTetris.center[0] -=1
                            return False
                        elif Allblocks[check2[i][1]+AboveLine][check2[i][0]].state == '1' :
                            if InputTetris.shape == 'I': #consider the exclusive 'I' case: When the 'I' block is moved left once, it is still possible
                                InputTetris.center[0] +=1 # that the 'left part ' of the block still collides with other blocks. As a result, we need to
                                check3 = InputTetris.position() # move right again to include this case.
                                for i in range(len(check3)) :
                                    if check3[i][0] >= 10: # cross the right boundary, which is not allowed
                                        InputTetris.center[0] -=2
                                        return False
                                    elif Allblocks[check3[i][1]+AboveLine][check3[i][0]].state == '1' : # still collides with other blocks
                                        InputTetris.center[0] -=2
                                        return False
                            else: # for other blocks, just end the simulation and invalidate the rotation command
                                InputTetris.center[0] -=1
                                return False
                    return True
                elif check[i] in isright(InputTetris): # similar thoughts of the previous one
                    InputTetris.center[0] -=1
                    check2 = InputTetris.position()
                    for i in range(len(check2)) :
                        if check2[i][0] < 0:
                            InputTetris.center[0] +=1
                            return False
                        elif Allblocks[check2[i][1]+AboveLine][check2[i][0]].state == '1' :
                            if InputTetris.shape == 'I':
                                InputTetris.center[0] -=1
                                check3 = InputTetris.position()
                                for i in range(len(check3)) :
                                    if check3[i][0] < 0:
                                        InputTetris.center[0] +=2
                                        return False
                                    elif Allblocks[check3[i][1]+AboveLine][check3[i][0]].state == '1' :
                                        InputTetris.center[0] +=2
                                        return False
                            else:
                                InputTetris.center[0] +=1
                                return False
                    return True
                else: # only 'I' block could possibly reach here, for other blocks collide with the inner part of the 'I' block
                    if check[i][0] < InputTetris.center[0]: #The position of the collision is on the left side of the center part of the 'I' block.
                        InputTetris.center[0] +=2
                        check3 = InputTetris.position()
                        for i in range(len(check3)) :
                            if check3[i][0] >= 10:
                                InputTetris.center[0] -=2
                                return False
                            elif Allblocks[check3[i][1]+AboveLine][check3[i][0]].state == '1' :
                                InputTetris.center[0] -=2
                                return False
                        return True
                    else:
                        InputTetris.center[0] -=2
                        check3 = InputTetris.position()
                        for i in range(len(check3)) :
                            if check3[i][0] < 0:
                                InputTetris.center[0] +=2
                                return False
                            elif Allblocks[check3[i][1]+AboveLine][check3[i][0]].state == '1' :
                                InputTetris.center[0] +=2
                                return False
                        return True

        return True

def isbottom(InputTetris): # the 'bottom part' of the block: the possible part of the block that could touch other blocks below it.
    check = InputTetris.position()
    solvedict = {}
    empty = []
    for i in range(len(check)):
        if check[i][0] not in empty:
            empty.append(check[i][0])
            solvedict[check[i][0]] = [check[i][1]]
        else:
            solvedict[check[i][0]] += [check[i][1]]
    result = []
    for i in range(len(empty)):
        result.append([empty[i], max(solvedict[empty[i]])])
    return result
def bottom(InputTetris): # the bottom y location of the block
    result = isbottom(InputTetris)
    empty = []
    for i in range(len(result)):
        empty.append(result[i][1])
    return max(empty)
def canDrop(InputTetris):
        check = InputTetris.position()
        for i in range(len(check)):
            if check[i][1] >= 19: # if the block doesn't cross the bottom boundary
                return False
            elif check[i] in isbottom(InputTetris): # and doesn't touch any other block,
                if Allblocks[check[i][1]+AboveLine+1][check[i][0]].state == '1':
                    return False
        return True    #then the block can drop
def MoveDownSide(InputTetris):
    check = InputTetris.position()

    if canDrop(InputTetris):
        InputTetris.center[1] +=1
def Drop(InputTetris):

    if canDrop(InputTetris):
        InputTetris.center[1] +=1
# In fact, these two functions above are the same, just named differently for their intentions
def isleft(InputTetris):# the 'left part' of the block: the possible part of the block that could touch other blocks on the left side
    check = InputTetris.position()
    solvedict = {}
    empty = []
    for i in range(len(check)):
        if check[i][1] not in empty:
            empty.append(check[i][1])
            solvedict[check[i][1]] = [check[i][0]]
        else:
            solvedict[check[i][1]] += [check[i][0]]
    result = []
    for i in range(len(empty)):
        result.append([min(solvedict[empty[i]]),empty[i]])
    return result
def leftmost(InputTetris): # the leftmost x location of the block
    result = isleft(InputTetris)
    empty = []
    for i in range(len(result)):
        empty.append(result[i][0])
    return min(empty)
def canMoveLeftside(InputTetris):
    check = InputTetris.position()

    for i in range(len(check)):
        if check[i][0] <= 0:
            return False
        elif check[i] in isleft(InputTetris):
            if Allblocks[check[i][1]+AboveLine][check[i][0]-1].state == '1':
                return False
    return True
def MoveLeftSide(InputTetris):

    if canMoveLeftside(InputTetris):
        InputTetris.center[0] -= 1
def isright(InputTetris):
    check = InputTetris.position()
    solvedict = {}
    empty = []
    for i in range(len(check)):
        if check[i][1] not in empty:
            empty.append(check[i][1])
            solvedict[check[i][1]] = [check[i][0]]
        else:
            solvedict[check[i][1]] += [check[i][0]]
    result = []
    for i in range(len(empty)):
        result.append([max(solvedict[empty[i]]),empty[i]])
    return result
def rightmost(InputTetris):
    result = isright(InputTetris)
    empty = []
    for i in range(len(result)):
        empty.append(result[i][0])
    return max(empty)
def canMoveRightside(InputTetris):
    check = InputTetris.position()

    for i in range(len(check)):
        if check[i][0] >= 9:
            return False
        elif check[i] in isright(InputTetris):
            if Allblocks[check[i][1]+AboveLine][check[i][0]+1].state == '1':
                return False
    return True
def MoveRightSide(InputTetris):

    if canMoveRightside(InputTetris):
        InputTetris.center[0]+= 1
def MoveDownDirectly(InputTetris):

    while canDrop(InputTetris) == True:
        InputTetris.center[1] +=1

#'I','J','L','O', 'S', 'T', 'Z'
def main():
    global DISPLAYSURF, Allblocks, score, image
    pygame.init()
    score = 0
    FPSCLOCK = pygame.time.Clock()
    pygame.display.set_caption('Tetris')

    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    DISPLAYSURF.fill(BACK_GROUND_COLOR)
    image = {}
    for shape in ['I','J','L','O', 'S', 'T', 'Z']:
        image[shape] = pygame.image.load(f'image\Tetris{shape}.png')
        image[shape].convert()
    #Openingscreen()
    #DrawBoard()
    # if 'single' is pressed:
    StartGameAnimation()

    if random.randint(0, 1) == 0:
        pygame.mixer.music.load('tetrisb.mid')
    else:
        pygame.mixer.music.load('tetrisc.mid')
    pygame.mixer.music.play(-1, 0.0)

    thefirsttwochoice = [random.choice(['I','J','L','O', 'S', 'T', 'Z']), random.choice(['I','J','L','O', 'S', 'T', 'Z'])]
    thefirstone = thefirsttwochoice[0]
    thesecondone = thefirsttwochoice[1]
    droppingblock = Tetris(thefirstone)
    print('next =', thesecondone)
    hold = None
    print('hold =', hold)
    print('score =', score)
    record = False
    lastdrop = None
    pause = False
    gameover = False
    moveleft = False
    moveright = False
    movedown = False
    space = False
    rightshift = False
    sound = True
    rotate = False
    level = 1
    lastMoveSidewaysTime = time.time()
    lastRotateTime = time.time()
    lastMoveDownTime = time.time()

    fallingspeed = 0.5
    if pause == False:
        DrawBoard(thesecondone, hold, score, level)
    return_to_page = False
    perform = False
    while topmost(droppingblock) < 0:
        droppingblock.center[1] +=1
    while True:

        for event in pygame.event.get():
            # When QUIT or press the esc key, quit the game
            if event.type == KEYUP:
                if event.key == K_m:
                    if sound == True:
                        pygame.mixer.music.stop()
                        sound = False
                    else:
                        pygame.mixer.music.play(-1, 0.0)
                        sound = True
                elif event.key ==K_p:
                    if pause == False:

                        pause = True

                        Allblockscolor =[]
                        for row in Allblocks:

                            rowcolor=[]
                            for block in row:
                                rowcolor.append(block.color)
                            Allblockscolor.append(rowcolor)

                        for row in Allblocks:
                            for block in row:
                                block.color = BACK_GROUND_COLOR

                        DrawBoard(thesecondone, hold, score, level)
                        TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, 'PAUSE', DISPLAY_TIME=0)

                        '''
                        pygame.mixer.music.stop()
                         '''
                        #pause
                    else:
                        pause = False
                        TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, 'PAUSE',FRONTCOLOR = BACK_GROUND_COLOR, DISPLAY_TIME=0)
                        for row in Allblocks:
                            for block in row:
                                block.color = Allblockscolor[block.y][block.x]

                        DrawBoard(thesecondone, hold, score, level)

                        '''
                        pygame.mixer.music.play(-1, 0.0)
                        '''
                elif event.key == K_SPACE:
                    space = False
                elif event.key == K_LEFT:
                    moveleft = False
                elif event.key == K_RIGHT:
                    moveright = False
                elif event.key == K_UP:
                    rotate = False
                elif event.key == K_DOWN:
                    movedown = False
                elif event.key == K_ESCAPE:
                   # pygame.mixer.music.stop()
                    pygame.quit()
                    #sys.exit()
                    return_to_page = True
                    break
                elif event.key == K_RSHIFT or event.key == K_LSHIFT :
                    rightshift = False

            elif pause == True:

                break

            elif event.type == QUIT :
                #pygame.mixer.music.stop()
                pygame.quit()
                #sys.exit()
                return_to_page = True
                break
            elif gameover:
                break
            elif event.type == KEYDOWN:
                if event.key == K_LEFT :
                    MoveLeftSide(droppingblock)
                    moveleft = True
                    moveright = False
                    lastMoveSidewaysTime = time.time()
                elif event.key == K_RIGHT:
                    MoveRightSide(droppingblock)
                    lastMoveSidewaysTime = time.time()
                    moveright = True
                    moveleft = False
                elif event.key == K_UP:
                    #rotation
                    rotate = True
                    if not canRotate(droppingblock):
                        if droppingblock.rotationstate == 0:
                            droppingblock.rotationstate = droppingblock.rotationtype() - 1
                        else:
                            droppingblock.rotationstate -= 1
                    lastRotateTime = time.time()
                elif event.key == K_DOWN:
                    MoveDownSide(droppingblock)
                    movedown = True
                    lastMoveDownTime = time.time()
                    #accelerate the speed
                elif event.key == K_SPACE:
                    space = True
                    MoveDownDirectly(droppingblock)

                    for location in range(len(droppingblock.position())):
                        rowlocation = droppingblock.position()[location][1]
                        columnlocation = droppingblock.position()[location][0]
                        Allblocks[rowlocation+AboveLine][columnlocation].state = '1'
                        Allblocks[rowlocation+AboveLine][columnlocation].color = droppingblock.color

                    DrawBoard(thesecondone, hold, score, level)

                    CompleteRow = FindCompleteRow()
                    completerow = CompleteRow[:]
                    if len(completerow) > 0:
                        last_score = score

                    score += len(completerow)

                    EliminateRowBlocks(CompleteRow)
                    thechoice = random.choice(['I','J','L','O', 'S', 'T', 'Z'])
                    thefirsttwochoice = thefirsttwochoice[1:]+[thechoice]
                    thefirstone = thefirsttwochoice[0]
                    thesecondone = thefirsttwochoice[1]
                    droppingblock = Tetris(thefirstone)
                    while topmost(droppingblock) < 0:
                        droppingblock.center[1] +=1
                    if len(completerow) > 0:
                        DrawBoard(thesecondone, hold, last_score, level, SCORECOLOR = BACK_GROUND_COLOR)

                    DrawBoard(thesecondone, hold, score, level)
                    if option_check(droppingblock, Allblocks) == False:
                        gameover = True
                        break
                    print('next =', thesecondone)
                    if hold == None:
                        print('hold =', hold)
                    else:
                        print('hold =', hold.shape)
                    record = False
                    print('score =', score)
                    '''
                    check = droppingblock.position()
                    for i in range(len(check)):
                        if Allblocks[check[i][1]+AboveLine][check[i][0]].state == '1' :
                            gameoveranimation()
                            pygame.mixer.music.stop()
                            pygame.quit()
                            sys.exit()
                            '''
                    #just move to the bottom without animation
                elif event.key == K_RSHIFT or event.key == K_LSHIFT:
                    if record == False:
                        rightshift = True
        fallingspeedlist = [0.5 ,0.5, 0.44, 0.38, 0.32, 0.26, 0.21, 0.17, 0.13, 0.10, 0.08, 0.06, 0.04, 0.03, 0.02, 0.02]

        if score > level * 10:
            level += 1
            if level <= 15:
                fallingspeed =  fallingspeedlist[level]
            else:fallingspeed =  fallingspeedlist[15]
            DrawBoard(thesecondone, hold, score, level - 1, LEVELCOLOR = BACK_GROUND_COLOR)
            DrawBoard(thesecondone, hold, score, level)
        if return_to_page :
            break
        if pause:

            continue
        if gameover:
            if not perform :
                for i in range(10):
                    Game_Over_Animation(Allblocks)
                    DrawBoard(thesecondone, hold, score, level)
                    pygame.time.delay(100)
                perform = True
            else:
                TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, 'GAME OVER', DISPLAY_TIME=2000)

            continue
        #draw on the AllBlock
        if rightshift == True:
            if hold == None:
                test = Tetris(thefirsttwochoice[1])
                test.center = droppingblock.center
                if holdtest(test, droppingblock) == True:
                    print('hold')
                    hold = droppingblock
                    fixedpoint = hold.center
                    thechoice = random.choice(['I','J','L','O', 'S', 'T', 'Z'])
                    thefirsttwochoice = thefirsttwochoice[1:]+[thechoice]
                    thefirstone = thefirsttwochoice[0]
                    thesecondone = thefirsttwochoice[1]
                    droppingblock = Tetris(thefirstone)
                    droppingblock.center = fixedpoint
                    print('next =', thesecondone)
                    print('hold =', hold.shape)
                    print('score =', score)
                    rightshift = False
                    record = True
            else:
                print('hold')
                if holdtest(hold, droppingblock) == True:
                    fixedpoint = droppingblock.center
                    hold, droppingblock = droppingblock, hold
                    droppingblock.center = fixedpoint
                    rightshift = False
                    record = True
                else:
                    print('hold error')
                print('next =', thesecondone)
                print('hold =', hold.shape)
                print('score =', score)

        if (moveleft or moveright) and time.time() - lastMoveSidewaysTime > movesidewayfrequency:
            if moveleft:
                MoveLeftSide(droppingblock)
            elif moveright:
                MoveRightSide(droppingblock)
            lastMoveSidewaysTime = time.time()

        if movedown and time.time() - lastMoveDownTime > movedownfrequency :
            MoveDownSide(droppingblock)
            lastMoveDownTime = time.time()
            '''
        if rotate and time.time() - lastRotateTime > rotatefrequency :
            if not canRotate(droppingblock):
                if droppingblock.rotationstate == 0:
                    droppingblock.rotationstate = droppingblock.rotationtype() - 1
                else:
                    droppingblock.rotationstate -= 1
            lastRotateTime = time.time()
            '''
        for location in range(len(droppingblock.position())):
            rowlocation = droppingblock.position()[location][1]
            columnlocation = droppingblock.position()[location][0]
            Allblocks[rowlocation+AboveLine][columnlocation].state = '1'
            Allblocks[rowlocation+AboveLine][columnlocation].color = droppingblock.color

        DrawBoard(thesecondone, hold, score, level)
        #check if the dropping block is descended to the bottom
        if canDrop(droppingblock) == False:

            if time.time() - lastdrop > fallingspeed :
                CompleteRow = FindCompleteRow()
                completerow = CompleteRow[:]
                if len(completerow) > 0:
                    last_score = score

                score += len(completerow)

                EliminateRowBlocks(CompleteRow)
                thechoice = random.choice(['I','J','L','O', 'S', 'T', 'Z'])
                thefirsttwochoice = thefirsttwochoice[1:]+[thechoice]
                thefirstone = thefirsttwochoice[0]
                thesecondone = thefirsttwochoice[1]
                droppingblock = Tetris(thefirstone)
                while topmost(droppingblock) < 0:
                    droppingblock.center[1] +=1

                if len(completerow) > 0:
                    DrawBoard(thesecondone, hold, last_score, level, SCORECOLOR = BACK_GROUND_COLOR)

                DrawBoard(thesecondone, hold, score, level)

                if option_check(droppingblock, Allblocks) == False:
                    gameover = True
                print('next =', thesecondone)
                if hold == None:
                    print('hold =', hold)
                else:
                    print('hold =', hold.shape)
                record = False
                print('score =', score)
            # change to the new one
            else:
                for location in range(len(droppingblock.position())):
                    rowlocation = droppingblock.position()[location][1]
                    columnlocation = droppingblock.position()[location][0]
                    Allblocks[rowlocation+AboveLine][columnlocation].state = '0'
                    Allblocks[rowlocation+AboveLine][columnlocation].color = BACK_GROUND_COLOR
        else:

            for location in range(len(droppingblock.position())):
                rowlocation = droppingblock.position()[location][1]
                columnlocation = droppingblock.position()[location][0]
                Allblocks[rowlocation+AboveLine][columnlocation].state = '0'
                Allblocks[rowlocation+AboveLine][columnlocation].color = BACK_GROUND_COLOR
            if lastdrop == None:
                Drop(droppingblock)
                lastdrop = time.time()
            else:
                if time.time() - lastdrop > fallingspeed:
                    Drop(droppingblock)
                    lastdrop = time.time()
        pygame.display.update()

        FPSCLOCK.tick(FPS)
    #elif 'double' is pressed:
def holdtest(hold, droppingblock):
    check = hold
    check.center = droppingblock.center
    checkposition = check.position()
    for i in range(len(checkposition)):
        if checkposition[i][0] > 9 or checkposition[i][0] < 0:
            return False
        elif checkposition[i][1] > 19:
            return False
        elif Allblocks[checkposition[i][1]+AboveLine][checkposition[i][0]].state == '1':
            return False
    return True
#Redraw the game board
'''DIDN'T DRAW y<0'''
def DrawBoard(thesecondone, hold, score, level, SCORECOLOR = WHITE, LEVELCOLOR = WHITE, BACKCOLOR = BACK_GROUND_COLOR, TEXT_FONT = 35):
    global BOARDWIDTH, BOARD_LEFT, BOARD_UP, RECT_BOARD, BLOCK_SIZE
    Coord_centerx = BOARD_LEFT + BOARDWIDTH + 30
    Coord_centery = BOARD_UP + 40
    for raw_index in range(AboveLine,len(Allblocks)):
        for block in Allblocks[raw_index]:
            pygame.draw.rect(DISPLAYSURF, block.color, block.rect)
    if hold:
        pygame.draw.rect(DISPLAYSURF, BACK_GROUND_COLOR, (BOARD_LEFT-4*BLOCK_SIZE-6, BOARD_UP, 4*BLOCK_SIZE, 4*BLOCK_SIZE))
        DISPLAYSURF.blit(image[hold.shape], (BOARD_LEFT-4*BLOCK_SIZE-6, BOARD_UP))

    #next block
    pygame.draw.rect(DISPLAYSURF, BACK_GROUND_COLOR, (BOARD_LEFT+BOARDWIDTH+6, BOARD_UP, 4*BLOCK_SIZE, 4*BLOCK_SIZE))
    DISPLAYSURF.blit(image[thesecondone], (BOARD_LEFT+BOARDWIDTH+6, BOARD_UP))

    fontObj = pygame.font.Font('freesansbold.ttf', TEXT_FONT)

    textSurfaceObj3 = fontObj.render(f'score:', True, WHITE,BACKCOLOR)
    textRectObj3 = textSurfaceObj3.get_rect()
    textRectObj3.center = (Coord_centerx + 30,Coord_centery + 160)
    DISPLAYSURF.blit(textSurfaceObj3, textRectObj3)

    textSurfaceObj3_1 = fontObj.render(f'{score}', True, SCORECOLOR,BACKCOLOR)
    textRectObj3_1 = textSurfaceObj3_1.get_rect()
    textRectObj3_1.topleft = textRectObj3.topright
    DISPLAYSURF.blit(textSurfaceObj3_1, textRectObj3_1)

    textSurfaceObj4 = fontObj.render(f'level:', True, WHITE,BACKCOLOR)
    textRectObj4 = textSurfaceObj4.get_rect()
    textRectObj4.center = (Coord_centerx + 30,Coord_centery + 200)
    DISPLAYSURF.blit(textSurfaceObj4, textRectObj4)

    textSurfaceObj4_1 = fontObj.render(f'{level}', True, LEVELCOLOR,BACKCOLOR)
    textRectObj4_1 = textSurfaceObj4_1.get_rect()
    textRectObj4_1.topleft = textRectObj4.topright
    DISPLAYSURF.blit(textSurfaceObj4_1, textRectObj4_1)

    textSurfaceObj5 = fontObj.render(f'HOLD', True, WHITE,BACKCOLOR)
    textRectObj5 = textSurfaceObj5.get_rect()
    textRectObj5.center = (BOARD_LEFT - 2*BLOCK_SIZE - 3, Coord_centery + 120)
    DISPLAYSURF.blit(textSurfaceObj5, textRectObj5)

    textSurfaceObj6 = fontObj.render(f'NEXT', True, WHITE,BACKCOLOR)
    textRectObj6 = textSurfaceObj6.get_rect()
    textRectObj6.center = (BOARD_LEFT +  BOARDWIDTH + 2*BLOCK_SIZE + 6, Coord_centery + 120)
    DISPLAYSURF.blit(textSurfaceObj6, textRectObj6)
    pygame.display.update()
#DISPLAY_TIME == 0 means it won't be removed
def StartGameAnimation():
    pygame.draw.rect(DISPLAYSURF, RED, (BOARD_LEFT-3, BOARD_UP-3, BOARDWIDTH+6, BOARDHEIGHT+6),3)

    #next
    pygame.draw.rect(DISPLAYSURF, RED, (BOARD_LEFT+BOARDWIDTH+3, BOARD_UP-3, 4*BLOCK_SIZE+6, 4*BLOCK_SIZE+6),3)

    #hold
    pygame.draw.rect(DISPLAYSURF, RED, (BOARD_LEFT-4*BLOCK_SIZE-9, BOARD_UP-3, 4*BLOCK_SIZE+6, 4*BLOCK_SIZE+6),3)

    TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, 'READY', DISPLAY_TIME=2000)
    TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, '3', DISPLAY_TIME=1000)
    TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, '2', DISPLAY_TIME=1000)
    TEXT_Animation(RECT_BOARD.centerx, RECT_BOARD.centery, '1', DISPLAY_TIME=1000)

def TEXT_Animation(Coord_centerx, Coord_centery, TEXTmsg, TEXT_FONT = 32, FRONTCOLOR = RED, BACKCOLOR = BACK_GROUND_COLOR, DISPLAY_TIME = 0):

    fontObj = pygame.font.Font('freesansbold.ttf', TEXT_FONT)
    textSurfaceObj = fontObj.render(TEXTmsg, True, FRONTCOLOR,BACKCOLOR)
    textRectObj = textSurfaceObj.get_rect()
    textRectObj.center = (Coord_centerx,Coord_centery)

    DISPLAYSURF.blit(textSurfaceObj, textRectObj)
    pygame.display.update()

    if DISPLAY_TIME != 0:
        pygame.time.delay(DISPLAY_TIME)
        pygame.draw.rect(DISPLAYSURF, BACK_GROUND_COLOR, textRectObj)
        pygame.display.update()

#return a list with complete row(index by its coordinate.y)
#empty list means there isn't any complete row
def FindCompleteRow():

    CompleteRow = []
    for row_index in range(len(Allblocks)):
        state_one_count = 0
        for block in Allblocks[row_index]:
            if block.state == '1':
                state_one_count += 1
        if state_one_count == len(Allblocks[row_index]):
            CompleteRow.append(row_index)
    return CompleteRow
#eliminate complete row blocks and drop the upper blocks

def EliminateRowBlocks(CompleteRow):
    count = 0
    def BlocksDropInRow(finish_row):
        #let every block in row 's color and state copy the upper one
        for row_index in range(finish_row,0,-1):
            for block_index in range(len(Allblocks[row_index])):
                Allblocks[row_index][block_index].color = Allblocks[row_index-1][block_index].color
                Allblocks[row_index][block_index].state = Allblocks[row_index-1][block_index].state
        #let raw0 be state0 and backgroundcolor
        for block in Allblocks[0]:
            block.clear()

    while len(CompleteRow) != 0 :
        #eliminate the most bottom row
        for block in Allblocks[max(CompleteRow) + count]:
            block.clear()
        #let upper blocks drop
        BlocksDropInRow(max(CompleteRow)+count)
        CompleteRow.remove(max(CompleteRow))
        count += 1

    pygame.display.update()
def option_check(InputTetris, Allblocks):
    check = InputTetris.position()
    for i in range(len(check)):
        if Allblocks[check[i][1]][check[i][0]].state == '1':
            return False
    return True
def Game_Over_Animation(Allblocks):
    for row in Allblocks:
        for block in row:
            t = list(block.color)
            for i in range(len(t)):
                if t[i] > 10:
                    t[i] -= 10
            block.color = tuple(t)

if __name__=='__main__':
    main()


from typing import List

# complexity: time - O(log n) | space - O(1), where n - length of letters
class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        left = -1
        right = len(letters)

        while left + 1 < right:
            mid = (left + right) // 2
            if is_greater(letters[mid], target):
                right = mid
            else:
                left = mid
        return letters[right] if right < len(letters) else letters[0]

def is_greater(a: int, b: int) -> bool:
    return a > b

if __name__ == "__main__":
    instance = Solution()
    print(instance.nextGreatestLetter(["c", "f", "j"], "c"))  # f
    print(instance.nextGreatestLetter(["x", "x", "y", "y"], "z"))  # x


import asyncio
import json
import time

import httpx
from celery import Celery
from celery.signals import celeryd_init

from app import app

from .helpers import process_request, process_request_async
from .logger import get_logger
from .models import Task, TaskStatus

celery = Celery(__name__)
celery.conf.broker_url = app.config.broker_uri
celery.conf.result_backend = app.config.result_backend

def celery_setup_context():
    """Celery app does not launch fastapi server itself."""
    if not hasattr(app, "logger"):
        app.logger = get_logger()
    if not hasattr(app, "client"):
        app.client = httpx.Client()

@celeryd_init.connect
def configure_workers(sender=None, conf=None, **kwargs):
    celery_setup_context()

@celery.task(name="process_task")
def process_task(message: dict):
    """Process default cpu-bound task."""
    update_status_url = f"{app.config.tasks_api_uri}/{message['id']}"
    try:
        task = Task(**message)
        process_request(
            method="patch",
            url=update_status_url,
            json={"status": TaskStatus.processing},
        )
        time.sleep(task.processing_time)
        process_request(
            method="patch",
            url=update_status_url,
            json={"status": TaskStatus.completed},
        )
    except Exception as e:
        app.logger.exception(e)
        process_request(
            method="patch",
            url=update_status_url,
            json={"status": TaskStatus.error},
        )

async def process_io_task(message: dict):
    """Process task asynchronously."""
    update_status_url = f"{app.config.tasks_api_uri}/{message['id']}"
    try:
        task = Task(**message)
        await process_request_async(
            method="patch",
            url=update_status_url,
            json={"status": TaskStatus.processing},
        )
        await asyncio.sleep(task.processing_time)
        await process_request_async(
            method="patch",
            url=update_status_url,
            json={"status": TaskStatus.completed},
        )
    except Exception as e:
        app.logger.exception(e)
        await process_request_async(
            method="patch",
            url=update_status_url,
            json={"status": TaskStatus.error},
        )

async def process_incoming_message(message):
    await message.ack()
    body = json.loads(message.body)
    app.logger.info(f"received message {body}")
    if body.get("cpu_bound"):
        process_task.delay(body)
    else:
        asyncio.create_task(process_io_task(body))