from __future__ import print_function

import copy
import warnings

import graphviz
import matplotlib.pyplot as plt
import numpy as np
from graph import feed_forward_layers, DrawNN

def plot_time(begin, end, filename, generation_time):
    """ Plots the population's average and best fitness. """
    if plt is None:
        warnings.warn(
            "This display is not available due to a missing optional dependency (matplotlib)")
        return
    generation = range(begin, end)
    plt.plot(generation, generation_time, 'b-', label="seconds")
    plt.title("Population's average and best fitness")
    plt.xlabel("Generations")
    plt.ylabel("Generation time")
    plt.grid()
    plt.legend(loc="best")
    plt.savefig(filename)

    plt.close()
def plot_large_stats(statistics, ylog=False, view=False, filename='avg_fitness.svg', generations=1, begin=0, end=4):
    """ Plots the population's average and best fitness. """
    if plt is None:
        warnings.warn(
            "This display is not available due to a missing optional dependency (matplotlib)")
        return

    generation = range(begin, end)
    best_fitness = [c.fitness for c in statistics.most_fit_genomes][begin:end]
    avg_fitness = np.array(statistics.get_fitness_mean())[begin:end]
    stdev_fitness = np.array(statistics.get_fitness_stdev())[begin:end]

    plt.figure(figsize=(end-begin, 10))
    plt.plot(generation, avg_fitness, 'b-', label="average")
    plt.plot(generation, avg_fitness - stdev_fitness, 'g-.', label="-1 sd")
    plt.plot(generation, avg_fitness + stdev_fitness, 'g-.', label="+1 sd")
    plt.plot(generation, best_fitness, 'r-', label="best")

    plt.title("Population's average and best fitness")
    plt.xlabel("Generations")
    plt.ylabel("Fitness")
    plt.xticks(np.arange(begin, end))
    plt.grid()
    plt.legend(loc="best")
    if ylog:
        plt.gca().set_yscale('symlog')

    plt.savefig(filename)
    if view:
        plt.show()

    plt.close()

def plot_stats(statistics, ylog=False, view=False, filename='avg_fitness.svg', generations=1, begin=0, end=4):
    """ Plots the population's average and best fitness. """
    if plt is None:
        warnings.warn(
            "This display is not available due to a missing optional dependency (matplotlib)")
        return

    generation = range(begin, end)
    best_fitness = [c.fitness for c in statistics.most_fit_genomes][begin:end]
    avg_fitness = np.array(statistics.get_fitness_mean())[begin:end]
    stdev_fitness = np.array(statistics.get_fitness_stdev())[begin:end]

    plt.plot(generation, avg_fitness, 'b-', label="average")
    plt.plot(generation, avg_fitness - stdev_fitness, 'g-.', label="-1 sd")
    plt.plot(generation, avg_fitness + stdev_fitness, 'g-.', label="+1 sd")
    plt.plot(generation, best_fitness, 'r-', label="best")

    plt.title("Population's average and best fitness")
    plt.xlabel("Generations")
    plt.ylabel("Fitness")
    plt.grid()
    plt.legend(loc="best")
    if ylog:
        plt.gca().set_yscale('symlog')

    plt.savefig(filename)
    if view:
        plt.show()

    plt.close()

def plot_spikes(spikes, view=False, filename=None, title=None):
    """ Plots the trains for a single spiking neuron. """
    t_values = [t for t, I, v, u, f in spikes]
    v_values = [v for t, I, v, u, f in spikes]
    u_values = [u for t, I, v, u, f in spikes]
    I_values = [I for t, I, v, u, f in spikes]
    f_values = [f for t, I, v, u, f in spikes]

    fig = plt.figure()
    plt.subplot(4, 1, 1)
    plt.ylabel("Potential (mv)")
    plt.xlabel("Time (in ms)")
    plt.grid()
    plt.plot(t_values, v_values, "g-")

    if title is None:
        plt.title("Izhikevich's spiking neuron model")
    else:
        plt.title("Izhikevich's spiking neuron model ({0!s})".format(title))

    plt.subplot(4, 1, 2)
    plt.ylabel("Fired")
    plt.xlabel("Time (in ms)")
    plt.grid()
    plt.plot(t_values, f_values, "r-")

    plt.subplot(4, 1, 3)
    plt.ylabel("Recovery (u)")
    plt.xlabel("Time (in ms)")
    plt.grid()
    plt.plot(t_values, u_values, "r-")

    plt.subplot(4, 1, 4)
    plt.ylabel("Current (I)")
    plt.xlabel("Time (in ms)")
    plt.grid()
    plt.plot(t_values, I_values, "r-o")

    if filename is not None:
        plt.savefig(filename)

    if view:
        plt.show()
        plt.close()
        fig = None

    return fig

def plot_species(statistics, view=False, filename='speciation.svg'):
    """ Visualizes speciation throughout evolution. """
    if plt is None:
        warnings.warn(
            "This display is not available due to a missing optional dependency (matplotlib)")
        return

    species_sizes = statistics.get_species_sizes()
    num_generations = len(species_sizes)
    curves = np.array(species_sizes).T

    fig, ax = plt.subplots()
    ax.stackplot(range(num_generations), *curves)

    plt.title("Speciation")
    plt.ylabel("Size per Species")
    plt.xlabel("Generations")
    plt.savefig(filename)

    if view:
        plt.show()

    plt.close()

def draw_net2(config, genome, view=False, filename=None, node_names=None, show_disabled=True, prune_unused=False,
              node_colors=None, fmt='svg'):
    layers = feed_forward_layers(inputs=config.genome_config.input_keys, outputs=config.genome_config.output_keys, connections=[
                                 i.key for i in genome.connections.values()])
    neurons_per_layer = [len(layer) for layer in layers]
    neurons_per_layer.insert(0, len(config.genome_config.input_keys))
    neurons_per_layer.append(len(config.genome_config.output_keys))
    nn = DrawNN(neurons_per_layer, filename + "topology.png")
    layers = [list(l) for l in layers]
    layers.insert(0, config.genome_config.input_keys)
    layers.append(config.genome_config.output_keys)
    print(layers)
    nn.draw(layers, genome)

def draw_net(config, genome, view=False, filename=None, node_names=None, show_disabled=True, prune_unused=False,
             node_colors=None, fmt='svg'):
    """ Receives a genome and draws a neural network with arbitrary topology. """
    # Attributes for network nodes.
    if graphviz is None:
        warnings.warn(
            "This display is not available due to a missing optional dependency (graphviz)")
        return

    if node_names is None:
        node_names = {}

    assert type(node_names) is dict

    if node_colors is None:
        node_colors = {}

    assert type(node_colors) is dict

    node_attrs = {
        'shape': 'circle',
        'fontsize': '9',
        'height': '0.2',
        'width': '0.2'}

    dot = graphviz.Digraph(format=fmt, node_attr=node_attrs)

    inputs = set()
    for k in config.genome_config.input_keys:
        inputs.add(k)
        name = node_names.get(k, str(k))
        input_attrs = {'style': 'filled',
                       'shape': 'box'}
        input_attrs['fillcolor'] = node_colors.get(k, 'lightgray')
        dot.node(name, _attributes=input_attrs)

    outputs = set()
    for k in config.genome_config.output_keys:
        outputs.add(k)
        name = node_names.get(k, str(k))
        node_attrs = {'style': 'filled'}
        node_attrs['fillcolor'] = node_colors.get(k, 'lightblue')

        dot.node(name, _attributes=node_attrs)

    if prune_unused:
        connections = set()
        for cg in genome.connections.values():
            if cg.enabled or show_disabled:
                connections.add((cg.in_node_id, cg.out_node_id))

        used_nodes = copy.copy(outputs)
        pending = copy.copy(outputs)
        while pending:
            new_pending = set()
            for a, b in connections:
                if b in pending and a not in used_nodes:
                    new_pending.add(a)
                    used_nodes.add(a)
            pending = new_pending
    else:
        used_nodes = set(genome.nodes.keys())

    for n in used_nodes:
        if n in inputs or n in outputs:
            continue

        attrs = {'style': 'filled',
                 'fillcolor': node_colors.get(n, 'white')}
        dot.node(str(n), _attributes=attrs)

    for cg in genome.connections.values():
        if cg.enabled or show_disabled:
            # if cg.input not in used_nodes or cg.output not in used_nodes:
            #    continue
            input, output = cg.key
            a = node_names.get(input, str(input))
            b = node_names.get(output, str(output))
            style = 'solid' if cg.enabled else 'dotted'
            color = 'green' if cg.weight > 0 else 'red'
            width = str(0.1 + abs(cg.weight / 5.0))
            dot.edge(a, b, _attributes={
                     'style': style, 'color': color, 'penwidth': width})

    dot.render(filename + "digraph", view=view)
    draw_net2(config, genome, False, node_names=node_names, filename=filename)
    return dot

def graph_per_stage(config, map_number, genome, stats_reporter, generations, total_maps, generation_time):
    node_names = {
        0: 'direccion',
        1: 'velocidad',
        -1: 'Sensor Proximidad frontal',
        -2: 'Sensor Proximidad frontal derecho',
        -3: 'Sensor Proximidad frontal izquierdo',
        -4: 'Sensor Proximidad derecho',
        -5: 'Sensor Proximidad izquierdo',
        -6: 'Sensor Proximidad trasero',
        -7: 'Sensor Proximidad trasero derecho',
        -8: 'Sensor Proximidad trasero izquierdo',
    }
    draw_net(config, genome, False, node_names=node_names,
             filename=f'./graficos/{map_number}/')

    begin = int((map_number - 1) * (generations / total_maps))
    end = int((map_number) * (generations / total_maps))

    plot_stats(stats_reporter, ylog=False, view=True,
               filename=f'./graficos/{map_number}/avg_fitness.svg', begin=begin, end=end)
    plot_large_stats(stats_reporter, ylog=False, view=True,
                     filename=f'./graficos/{map_number}/large_avg_fitness.svg', begin=begin, end=end)
    plot_time(begin, end, filename=f'./graficos/{map_number}/time.svg', generation_time=generation_time)


import ply.yacc as yacc
from Parser.Lexer import Lexer

class Parser:
    tokens = Lexer.tokens

    def __init__(self):
        self.input = None
        self.symbolList = {'globalSymbol': {}, 'subFuncSymbol': {}, 'funcID': {}}
        self.isInSubFunc = False
        self.error = []
        self.warning = []
        self.symbolTable = {}
        self.parser = None

    def p_programstruct(self, p):
        '''programstruct : program_head ';' program_body '.' '''
        p[0] = {
            'p_length': len(p),
            'p_type': 'programstruct',
            'child_nodes': [p[1], p[3]],
            'info': {}
        }
        self.symbolTable = {
            'constTable': p[3]['st']['ct'],
            'varTable': p[3]['st']['vt'],
            'subFuncTable': p[3]['st']['sft']
        }

    def p_program_head(self, p):
        '''program_head : PROGRAM ID '(' idlist ')'
                        | PROGRAM ID'''
        if len(p) == 6:
            p[0] = {
                'p_length': len(p),
                'p_type': 'program_head',
                'child_nodes': [p[4], ],
                'info': {'ID': p[2]}
            }

        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'program_head',
                'child_nodes': [],
                'info': {
                    'ID': p[2],
                }
            }

    def p_program_body(self, p):
        '''program_body : const_declarations var_declarations subprogram_declarations compound_statement'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'program_body',
            'child_nodes': [p[1], p[2], p[3], p[4]],
            'info': {}
        }
        p[0]['st'] = {
            'ct': p[1]['st'],
            'vt': p[2]['st'],
            'sft': p[3]['st']
        }

    def p_idlist(self, p):
        '''idlist : ID
                  | idlist ',' ID'''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'idlist',
                'info': {
                    'id_l': p[1]['info']['id_l'] + [p[3]]
                }
            }
            self.isRepeatedDefine(p[3], p.slice[3].lineno, p.slice[3].lexpos)
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'idlist',
                'child_nodes': [],
                'info': {
                    'id_l': [p[1]]
                }
            }
            self.isRepeatedDefine(p[1], p.slice[1].lineno, p.slice[1].lexpos)

    def p_const_declarations(self, p):
        '''const_declarations : empty
                              | CONST const_declaration ';' '''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'const_declarations',
                'child_nodes': [p[2], ],
                'info': {}
            }
            p[0]['st'] = p[2]['st']
        else:
            p[0] = {'st': []}

    def p_const_declaration(self, p):
        '''const_declaration : ID RELOP const_value
                             | const_declaration ';' ID RELOP const_value'''  # RELOP只能为'='
        if len(p) == 6:
            p[0] = {
                'p_length': len(p),
                'p_type': 'const_declaration',
                'child_nodes': [p[1], p[5]],
                'info': {
                    'values': p[1]['info']['values'] + [{'ID': p[3], 'const_value': p[5]}]
                }
            }
            newST = {
                'id': p[3],
                'type': p[5]['info']['true_type'],
                'value': p[5]['info']['value']
            }
            p[0]['st'] = p[1]['st'] + [newST]
            self.isRepeatedDefine(p[3], p.slice[3].lineno, p.slice[3].lexpos)
            if self.isInSubFunc:
                self.symbolList['subFuncSymbol'][p[3]] = newST.copy()
                self.symbolList['subFuncSymbol'][p[3]]['isConst'] = True
            else:
                self.symbolList['globalSymbol'][p[3]] = newST.copy()
                self.symbolList['globalSymbol'][p[3]]['isConst'] = True
            if p[4] != '=':
                self.error.append({
                    'error': 'illegal syntax',
                    'value': p[4],
                    'line': p.slice[4].lineno,
                    'column': self.getColumn(self.input,  p.slice[4].lexpos)
                })
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'const_declaration',
                'child_nodes': [p[3], ],
                'info': {
                    'values': [{'ID': p[1], 'const_value': p[3]}]
                }
            }
            newST = {
                'id': p[1],
                'type': p[3]['info']['true_type'],
                'value': p[3]['info']['value']
            }
            p[0]['st'] = [newST]
            self.isRepeatedDefine(p[1], p.slice[1].lineno, p.slice[1].lexpos)
            if self.isInSubFunc:
                self.symbolList['subFuncSymbol'][p[1]] = newST.copy()
                self.symbolList['subFuncSymbol'][p[1]]['isConst'] = True
            else:
                self.symbolList['globalSymbol'][p[1]] = newST.copy()
                self.symbolList['globalSymbol'][p[1]]['isConst'] = True
            if p[2] != '=':
                self.error.append({
                    'error': 'illegal syntax',
                    'value': p[2],
                    'line': p.slice[2].lineno,
                    'column': self.getColumn(self.input,  p.slice[2].lexpos)
                })

    def p_const_value(self, p):
        '''const_value : ADDOP NUM
                       | NUM
                       | LETTER'''  # ADDOP只能为'+'、'-'
        if len(p) == 3:
            p[0] = {
                'p_length': len(p),
                'p_type': 'const_value',
                'child_nodes': [],
                'info': {
                    '_type': 'NUM',
                    'true_type': 'INTEGER' if type(p[2]) == int else 'REAL',
                    'value': -p[2] if p[1] == '-' else p[2]
                }
            }
            if p[1].upper() == 'OR':
                self.error.append({
                    'error': 'illegal syntax',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input,  p.slice[1].lexpos)
                })
        elif type(p[1]) != str:
            p[0] = {
                'p_length': len(p),
                'p_type': 'const_value',
                'child_nodes': [],
                'info': {
                    '_type': 'NUM',
                    'true_type': 'INTEGER' if type(p[1]) == int else 'REAL',
                    'value': p[1]
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'const_value',
                'child_nodes': [],
                'info': {
                    '_type': 'LETTER',
                    'true_type': 'CHAR',
                    'value': p[1]
                }
            }

    def p_var_declarations(self, p):
        '''var_declarations : empty
                            | VAR var_declaration ';' '''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'var_declarations',
                'child_nodes': [p[2], ],
                'info': {}
            }
            p[0]['st'] = p[2]['st']
        else:
            p[0] = {'st': []}

    def p_var_declaration(self, p):
        '''var_declaration : idlist ':' type
                           | var_declaration ';' idlist ':' type'''
        if len(p) == 6:
            p[0] = {
                'p_length': len(p),
                'p_type': 'var_declaration',
                'child_nodes': [p[1], p[3], p[5]],
                'info': {
                    'values': p[1]['info']['values'] + [{'id_l': p[3], 'type': p[5]}]
                }
            }
            p[0]['st'] = p[1]['st']
            for i in p[3]['info']['id_l']:
                newST = {
                    'id': i,
                    'type': p[5]['st']['type'],
                    'array': {
                        'isArray': p[5]['st']['array']['isArray'],
                        'dimension': p[5]['st']['array']['dimension'],
                        'size': p[5]['st']['array']['size'],
                        'start': p[5]['st']['array']['start'],
                    }
                }
                p[0]['st'] += [newST]
                if self.isInSubFunc:
                    self.symbolList['subFuncSymbol'][i] = newST.copy()
                    self.symbolList['subFuncSymbol'][i]['isConst'] = False
                else:
                    self.symbolList['globalSymbol'][i] = newST.copy()
                    self.symbolList['globalSymbol'][i]['isConst'] = False
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'var_declaration',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'values': [{'id_l': p[1], 'type': p[3]}]
                }
            }
            p[0]['st'] = []
            for i in p[1]['info']['id_l']:
                newST = {
                    'id': i,
                    'type': p[3]['st']['type'],
                    'array': {
                        'isArray': p[3]['st']['array']['isArray'],
                        'dimension': p[3]['st']['array']['dimension'],
                        'size': p[3]['st']['array']['size'],
                        'start': p[3]['st']['array']['start'],
                    }
                }
                p[0]['st'] += [newST]
                if self.isInSubFunc:
                    self.symbolList['subFuncSymbol'][i] = newST.copy()
                    self.symbolList['subFuncSymbol'][i]['isConst'] = False
                else:
                    self.symbolList['globalSymbol'][i] = newST.copy()
                    self.symbolList['globalSymbol'][i]['isConst'] = False

    def p_type(self, p):
        '''type : basic_type
                | ARRAY '[' period ']' OF basic_type'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'type',
                'child_nodes': [p[1], ],
                'info': {
                    '_type': p[1]
                }
            }
            p[0]['st'] = {
                'type': p[1]['st'],
                'array': {
                    'isArray': False,
                    'dimension': 0,
                    'size': [],
                    'start': [],
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'type',
                'child_nodes': [p[3], p[6]],
                'info': {
                    '_type': 'ARRAY',
                }
            }
            p[0]['st'] = {
                'type': p[6]['st'],
                'array': {
                    'isArray': True,
                    'dimension': p[3]['st']['dimension'],
                    'size': p[3]['st']['size'],
                    'start': p[3]['st']['start'],
                }
            }

    def p_basic_type(self, p):
        '''basic_type : INTEGER
                      | REAL
                      | BOOLEAN
                      | CHAR'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'basic_type',
            'child_nodes': [],
            'info': {
                '_type': p[1].upper()
            }
        }
        p[0]['st'] = p[1].upper()

    def p_period(self, p):
        '''period : NUM DOUBLEDOT NUM
                  | period ',' NUM DOUBLEDOT NUM'''
        if len(p) == 4:
            # NUM只能为整数
            if type(p[1]) == float:
                self.error.append({
                    'error': 'The number representing array bounds should be integer',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input,  p.slice[1].lexpos)
                })
            if type(p[3]) == float:
                self.error.append({
                    'error': 'The number representing array bounds should be integer',
                    'value': p[3],
                    'line': p.slice[3].lineno,
                    'column': self.getColumn(self.input,  p.slice[3].lexpos)
                })
            # 左边界不能大于右边界
            if p[1] > p[3]:
                self.error.append({
                    'error': 'The lower limit cannot be greater than the upper limit',
                    'value': [p[1], p[3]],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input,  p.slice[1].lexpos)
                })
            p[0] = {
                'p_length': len(p),
                'p_type': 'period',
                'child_nodes': [],
                'info': {
                    'values': [{'start': p[1], 'end': p[3]}]
                }
            }
            p[0]['st'] = {
                'dimension': 1,
                'start': [p[1]],
                'size': [p[3] - p[1] + 1]
            }
        else:
            # NUM只能为整数
            if type(p[3]) == float:
                self.error.append({
                    'error': 'The number representing array bounds should be integer',
                    'value': p[3],
                    'line': p.slice[3].lineno,
                    'column': self.getColumn(self.input,  p.slice[3].lexpos)
                })
            if type(p[5]) == float:
                self.error.append({
                    'error': 'The number representing array bounds should be integer',
                    'value': p[5],
                    'line': p.slice[5].lineno,
                    'column': self.getColumn(self.input,  p.slice[5].lexpos)
                })
            # 左边界不能大于右边界
            if p[3] > p[5]:
                self.error.append({
                    'error': 'The lower limit cannot be greater than the upper limit',
                    'value': [p[3], p[5]],
                    'line': p.slice[3].lineno,
                    'column': self.getColumn(self.input,  p.slice[3].lexpos)
                })
            p[0] = {
                'p_length': len(p),
                'p_type': 'period',
                'child_nodes': [p[1], ],
                'info': {
                    'values': p[1]['info']['values'] + [{'start': p[3], 'end': p[5]}]
                }
            }
            p[0]['st'] = {
                'dimension': p[1]['st']['dimension'] + 1,
                'start': p[1]['st']['start'] + [p[3]],
                'size': p[1]['st']['size'] + [p[5] - p[3] + 1]
            }

    def p_subprogram_declarations(self, p):
        '''subprogram_declarations : empty
                                   | subprogram_declarations subprogram ';' '''
        self.isInSubFunc = False
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'subprogram_declarations',
                'child_nodes': [p[1], p[2]],
                'info': {
                    'subprograms': p[1]['info']['subprograms'] + [p[2]] if 'info' in p[1].keys() else [p[2]]
                }
            }
            p[0]['st'] = p[1]['st'] + [p[2]['st']]
        else:
            p[0] = {'st': []}

    def p_subprogram(self, p):
        '''subprogram : subprogram_head ';' subprogram_body'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'subprogram',
            'child_nodes': [p[1], p[3]],
            'info': {
            }
        }
        p[0]['st'] = {
            'id': p[1]['st']['id'],
            'type': p[1]['st']['type'],
            'size': p[1]['st']['size'],
            'isReference': p[1]['st']['isReference'],
            'constTable': p[3]['st']['constTable'],
            'varTable': p[1]['st']['varTable'] + p[3]['st']['varTable']
        }

    def p_subprogram_head(self, p):
        '''subprogram_head : procedure ID formal_parameter
                           | function ID formal_parameter ':' basic_type'''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'subprogram_head',
                'child_nodes': [p[3], ],
                'info': {
                    'ID': p[2],
                    '_type': 'PROCEDURE'
                }
            }
            p[0]['st'] = {
                'id': p[2],
                'type': None,
                'size': p[3]['st']['size'],
                'isReference': p[3]['st']['isReference'],
                'varTable': p[3]['st']['varTable']
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'subprogram_head',
                'child_nodes': [p[3], p[5]],
                'info': {
                    'ID': p[2],
                    '_type': 'FUNCTION'
                }
            }
            p[0]['st'] = {
                'id': p[2],
                'type': p[5]['st'],
                'size': p[3]['st']['size'],
                'isReference': p[3]['st']['isReference'],
                'varTable': p[3]['st']['varTable']
            }
        if p[2] in self.symbolList['funcID'].keys():
            self.error.append({
                'error': 'Define function_ID repeatedly',
                'value': p[2],
                'line': p.slice[2].lineno,
                'column': self.getColumn(self.input,  p.slice[2].lexpos)
            })
        self.symbolList['funcID'][p[2]] = p[0]['st'].copy()
        self.symbolList['subFuncSymbol'][p[2]] = p[0]['st'].copy()
        self.symbolList['subFuncSymbol'][p[2]]['isConst'] = False

    def p_procedure(self, p):
        '''procedure : PROCEDURE'''
        self.isInSubFunc = True
        self.symbolList['subFuncSymbol'].clear()

    def p_function(self, p):
        '''function : FUNCTION'''
        self.isInSubFunc = True
        self.symbolList['subFuncSymbol'].clear()

    def p_formal_parameter(self, p):
        '''formal_parameter : empty
                            | '(' parameter_list ')' '''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'formal_parameter',
                'child_nodes': [p[2], ],
                'info': {}
            }
            p[0]['st'] = {
                'size': p[2]['st']['size'],
                'isReference': p[2]['st']['isReference'],
                'varTable': p[2]['st']['varTable']
            }
        else:
            p[0] = {'st': {
                'size': None,
                'isReference': None,
                'varTable': None
            }}

    def p_parameter_list(self, p):
        '''parameter_list : parameter
                          | parameter_list ';' parameter'''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'parameter_list',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'parameters': p[1]['info']['parameters'] + [p[3]]
                }
            }
            p[0]['st'] = {
                'size': p[1]['st']['size'] + p[3]['st']['size'],
                'isReference': p[1]['st']['isReference'] + p[3]['st']['isReference'],
                'varTable': p[1]['st']['varTable'] + p[3]['st']['varTable']
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'parameter_list',
                'child_nodes': [p[1], ],
                'info': {
                    'parameters': [p[1]]
                }
            }
            p[0]['st'] = {
                'size': p[1]['st']['size'],
                'isReference': p[1]['st']['isReference'],
                'varTable': p[1]['st']['varTable']
            }

    def p_parameter(self, p):
        '''parameter : var_parameter
                     | value_parameter'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'parameter',
            'child_nodes': [p[1], ],
            'info': {
                'value': p[1]
            }
        }
        p[0]['st'] = p[1]['st']

    def p_var_parameter(self, p):
        '''var_parameter : VAR value_parameter'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'var_parameter',
            'child_nodes': [p[2], ],
            'info': {}
        }
        p[0]['st'] = {
            'isReference': [True for i in range(len(p[2]['child_nodes'][0]['info']['id_l']))],
            'size': p[2]['st']['size'],
            'varTable': p[2]['st']['varTable']
        }

    def p_value_parameter(self, p):
        '''value_parameter : idlist ':' basic_type'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'value_parameter',
            'child_nodes': [p[1], p[3]],
            'info': {
                'basic_type': p[3]
            }
        }
        p[0]['st'] = {
            'isReference': [False for i in range(len(p[1]['info']['id_l']))],
            'size': len(p[1]['info']['id_l']),
            'varTable': []
        }
        for i in p[1]['info']['id_l']:
            newST = {
                'id': i,
                'type': p[3]['st'],
                'array': {
                    'isArray': False,
                    'dimension': 0,
                    'size': [],
                    'start': []
                }
            }
            p[0]['st']['varTable'] += [newST]
            self.symbolList['subFuncSymbol'][i] = newST.copy()
            self.symbolList['subFuncSymbol'][i]['isConst'] = False

    def p_subprogram_body(self, p):
        '''subprogram_body : const_declarations var_declarations compound_statement'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'subprogram_body',
            'child_nodes': [p[1], p[2], p[3]],
            'info': {}
        }
        p[0]['st'] = {
            'constTable': p[1]['st'],
            'varTable': p[2]['st']
        }

    def p_compound_statement(self, p):
        '''compound_statement : BEGIN statement_list END'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'compound_statement',
            'child_nodes': [p[2], ],
            'info': {}
        }

    def p_statement_list(self, p):
        '''statement_list : statement
                          | statement_list ';' statement'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'statement_list',
                'child_nodes': [p[1], ],
                'info': {
                    'statements': [p[1]]
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'statement_list',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'statements': p[1]['info']['statements'] + [p[3]]
                }
            }

    def p_statement(self, p):
        '''statement : empty
                     | variable ASSIGNOP expression
                     | procedure_call
                     | compound_statement
                     | IF expression THEN statement else_part
                     | FOR ID ASSIGNOP expression TO expression DO statement
                     | READ '(' variable_list ')'
                     | WRITE '(' expression_list ')' '''
        if type(p[1]) == str:
            if p[1].upper() == 'IF':
                p[0] = {
                    'p_length': len(p),
                    'p_type': "statement",
                    'child_nodes': [p[2], p[4], p[5]],
                    'info': {
                        '_type': 'IF'
                    }
                }
                if p[2]['info']['exp_type'] != 'BOOLEAN':
                    self.error.append({
                        'error': 'The type of the conditional judgment expression should be Boolean',
                        'value': 'The conditional judgment expression in IF statement',
                        'line': p.slice[1].lineno,
                        'column': self.getColumn(self.input, p.slice[1].lexpos + 3)
                    })
            elif p[1].upper() == 'FOR':
                p[0] = {
                    'p_length': len(p),
                    'p_type': "statement",
                    'child_nodes': [p[4], p[6], p[8]],
                    'info': {
                        '_type': 'FOR',
                        'ID': p[2],
                        'ASSIGNOP': p[3]
                    }
                }
                if not self.findSymbol(p[2]):
                    self.error.append({
                        'error': 'Undefined identifier',
                        'value': p[2],
                        'line': p.slice[2].lineno,
                        'column': self.getColumn(self.input,  p.slice[2].lexpos)
                    })
                elif self.findSymbol(p[2])['isConst']:
                    self.error.append({
                        'error': 'Constant cannot be assigned',
                        'value': p[2],
                        'line': p.slice[2].lineno,
                        'column': self.getColumn(self.input,  p.slice[2].lexpos)
                    })
                elif p[4]['info']['exp_type'] != 'Undefined' and not self.isSafeAssign(p[4]['info']['exp_type'], self.findSymbol(p[2])['type']):
                    self.warning.append({
                        'warning': 'Unsafe assignment',
                        'value': p[4]['info']['exp_type'] + ' assign to ' + self.findSymbol(p[2])['type'],
                        'line': p.slice[2].lineno,
                        'column': self.getColumn(self.input,  p.slice[2].lexpos)
                    })
            elif p[1].upper() == 'READ':
                p[0] = {
                    'p_length': len(p),
                    'p_type': "statement",
                    'child_nodes': [p[3], ],
                    'info': {
                        '_type': 'READ'
                    }
                }
            elif p[1].upper() == 'WRITE':
                p[0] = {
                    'p_length': len(p),
                    'p_type': "statement",
                    'child_nodes': [p[3], ],
                    'info': {
                        '_type': 'WRITE'
                    }
                }
        elif p[1]['p_type'] == 'empty':
            p[0] = []
        elif p[1]['p_type'] == 'variable':
            p[0] = {
                'p_length': len(p),
                'p_type': 'statement',
                'child_nodes': [p[1], p[3]],
                'info': {
                    '_type': 'variable',
                    'ASSIGNOP': p[2]
                }
            }
            if self.findSymbol(p[1]['info']['ID']):
                if self.findSymbol(p[1]['info']['ID'])['isConst']:
                    self.error.append({
                        'error': 'Constant cannot be assigned',
                        'value': p[1]['info']['ID'],
                        'line': p.slice[2].lineno,
                        'column': self.getColumn(self.input, p.slice[2].lexpos)
                    })
                else:
                    var_type = p[1]['info']['var_type']
                    exp_type = p[3]['info']['exp_type']
                    if var_type is None:
                        self.error.append({
                            'error': 'Procedure has no return value',
                            'value': p[1]['info']['ID'],
                            'line': p.slice[2].lineno,
                            'column': self.getColumn(self.input, p.slice[2].lexpos)
                        })
                    elif exp_type and exp_type != 'Undefined' and not self.isSafeAssign(exp_type, var_type):
                        self.warning.append({
                            'warning': 'Unsafe assignment',
                            'value': exp_type + ' assign to ' + var_type,
                            'line': p.slice[2].lineno,
                            'column': self.getColumn(self.input,  p.slice[2].lexpos)
                        })
        elif p[1]['p_type'] == 'procedure_call':
            p[0] = {
                'p_length': len(p),
                'p_type': "statement",
                'child_nodes': [p[1], ],
                'info': {
                    '_type': 'procedure_call'
                }
            }
        elif p[1]['p_type'] == 'compound_statement':
            p[0] = {
                'p_length': len(p),
                'p_type': "statement",
                'child_nodes': [p[1], ],
                'info': {
                    '_type': 'compound_statement'
                }
            }

    def p_variable_list(self, p):
        '''variable_list : variable
                         | variable_list ',' variable'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': "variable_list",
                'child_nodes': [p[1], ],
                'info': {
                    'variables': [p[1]]
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': "variable_list",
                'child_nodes': [p[1], p[3]],
                'info': {
                    'variables': p[1]['info']['variables'] + [p[3]]
                }
            }

    def p_variable(self, p):
        '''variable : ID id_varpart'''
        p[0] = {
            'p_length': len(p),
            'p_type': 'variable',
            'child_nodes': [p[2], ],
            'info': {
                'ID': p[1],
                'var_type': self.findSymbol(p[1])['type'] if self.findSymbol(p[1]) else 'UNDEFINED'
            }
        }
        if p[0]['info']['var_type'] == 'UNDEFINED':  # 判断id是否被定义
            self.error.append({
                'error': 'Undefined identifier',
                'value': p[1],
                'line': p.slice[1].lineno,
                'column': self.getColumn(self.input, p.slice[1].lexpos)
            })
        if p[2] is not None:
            if not self.findSymbol(p[1])['array']['isArray']:  # 判断若id有下标，id是否为array
                self.error.append({
                    'error': 'subscripted value is not an array',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input, p.slice[1].lexpos)
                })
            elif len(p[2]['child_nodes'][0]['info']['exp_type']) != self.findSymbol(p[1])['array']['dimension']:  # 维度是否一致
                self.error.append({
                    'error': 'Dimension different',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input, p.slice[1].lexpos)
                })
        elif 'array' in self.findSymbol(p[1]).keys() and self.findSymbol(p[1])['array']['isArray']:  # 维度是否一致
            self.error.append({
                'error': 'Dimension different',
                'value': p[1],
                'line': p.slice[1].lineno,
                'column': self.getColumn(self.input, p.slice[1].lexpos)
            })

    def p_id_varpart(self, p):
        '''id_varpart : empty
                      | '[' expression_list ']' '''
        if len(p) == 4:
            p[0] = {
                'p_length': len(p),
                'p_type': 'id_varpart',
                'child_nodes': [p[2], ],
                'info': {}
            }
        else:
            p[0] = None

    def p_procedure_call(self, p):
        '''procedure_call : ID
                          | ID '(' expression_list ')' '''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'procedure_call',
                'child_nodes': [],
                'info': {
                    'ID': p[1]
                }
            }
            if p[1] not in self.symbolList['funcID'].keys():
                self.error.append({
                    'error': 'Undefined identifier',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input,  p.slice[1].lexpos)
                })
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'procedure_call',
                'child_nodes': [p[3], ],
                'info': {
                    'ID': p[1]
                }
            }
            self.errorInProcedureCall(p)

    def p_else_part(self, p):
        '''else_part : empty
                     | ELSE statement'''
        if len(p) == 3:
            p[0] = {
                'p_length': len(p),
                'p_type': 'else_part',
                'child_nodes': [p[2], ],
                'info': {}
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'else_part',
                'child_nodes': [],
                'info': {}
            }

    def p_expression_list(self, p):
        '''expression_list : expression
                           | expression_list ',' expression'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'expression_list',
                'child_nodes': [p[1], ],
                'info': {
                    'exp_type': [p[1]['info']['exp_type']],
                    'expressions': [p[1]]
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'expression_list',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'exp_type': p[1]['info']['exp_type'] + [p[3]['info']['exp_type']],
                    'expressions': p[1]['info']['expressions'] + [p[3]]
                }
            }

    def p_expression(self, p):
        '''expression : simple_expression
                      | simple_expression RELOP simple_expression'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'expression',
                'child_nodes': [p[1], ],
                'info': {
                    'exp_type': p[1]['info']['exp_type']
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'expression',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'RELOP': p[2],
                    'exp_type': 'BOOLEAN'
                }
            }

    def p_simple_expression(self, p):
        '''simple_expression : term
                             | simple_expression ADDOP term'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'simple_expression',
                'child_nodes': [p[1], ],
                'info': {
                    'exp_type': p[1]['info']['exp_type']
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'simple_expression',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'ADDOP': p[2]
                }
            }
            self.getExpType(p)

    def p_term(self, p):
        '''term : factor
                | term MULOP factor'''
        if len(p) == 2:
            p[0] = {
                'p_length': len(p),
                'p_type': 'term',
                'child_nodes': [p[1], ],
                'info': {
                    'exp_type': p[1]['info']['exp_type']
                }
            }
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'term',
                'child_nodes': [p[1], p[3]],
                'info': {
                    'MULOP': p[2]
                }
            }
            self.getExpType(p)

    def p_factor(self, p):
        '''factor : NUM
                  | variable
                  | '(' expression ')'
                  | ID '(' expression_list ')'
                  | NOT factor
                  | ADDOP factor'''  # ADDOP只能为'-'
        if type(p[1]) == int or type(p[1]) == float:
            p[0] = {
                'p_length': len(p),
                'p_type': 'factor',
                'child_nodes': [],
                'info': {
                    '_type': 'NUM',
                    'exp_type': 'INTEGER' if type(p[1]) == int else 'REAL',
                    'NUM': p[1]
                }
            }
        elif type(p[1]) == dict:
            p[0] = {
                'p_length': len(p),
                'p_type': 'factor',
                'child_nodes': [p[1], ],
                'info': {
                    '_type': 'variable',
                    'exp_type': p[1]['info']['var_type']
                }
            }
        elif p[1] == '(':
            p[0] = {
                'p_length': len(p),
                'p_type': 'factor',
                'child_nodes': [p[2], ],
                'info': {
                    '_type': 'expression',
                    'exp_type': p[2]['info']['exp_type'],
                }
            }
        elif p[1].upper() == 'NOT':
            p[0] = {
                'p_length': len(p),
                'p_type': 'factor',
                'child_nodes': [p[2], ],
                'info': {
                    '_type': 'NOT',
                    'exp_type': p[2]['info']['exp_type'],
                }
            }
        elif p[1] == '+' or p[1] == '-' or p[1].upper == 'OR':
            p[0] = {
                'p_length': len(p),
                'p_type': 'factor',
                'child_nodes': [p[2], ],
                'info': {
                    '_type': 'UMINUS' if p[1] == '-' else 'NORMAL',
                    'exp_type': p[2]['info']['exp_type'],
                }
            }
            if p[1].upper == 'OR':
                self.error.append({
                    'error': 'illegal syntax',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input,  p.slice[1].lexpos)
                })
        else:
            p[0] = {
                'p_length': len(p),
                'p_type': 'factor',
                'child_nodes': [p[3], ],
                'info': {
                    '_type': 'procedure_id',
                    'exp_type': self.symbolList['funcID'][p[1]]['type'] if p[1] in self.symbolList['funcID'].keys() else 'Undefined',
                    'ID': p[1]
                }
            }
            if p[0]['info']['exp_type'] is None:
                self.error.append({
                    'error': 'Procedure has no return value',
                    'value': p[1],
                    'line': p.slice[1].lineno,
                    'column': self.getColumn(self.input, p.slice[1].lexpos)
                })
            self.errorInProcedureCall(p)

    def p_empty(self, p):
        'empty :'
        p[0] = {
            'p_length': len(p),
            'p_type': 'empty',
            'child_nodes': [],
            'info': {}
        }
        pass

    def p_error(self, p):
        if p:
            self.error.append({
                'error': 'illegal syntax',
                'value': p.value,
                'line': p.lineno,
                'column': self.getColumn(self.input,  p.lexpos)
            })

    def isRepeatedDefine(self, id, line, lexpos):
        if self.isInSubFunc:
            if id in self.symbolList['subFuncSymbol'].keys():
                self.error.append({
                    'error': 'Define ID repeatedly',
                    'value': id,
                    'line': line,
                    'column': self.getColumn(self.input,  lexpos)
                })
        else:
            if id in self.symbolList['globalSymbol'].keys():
                self.error.append({
                    'error': 'Define ID repeatedly',
                    'value': id,
                    'line': line,
                    'column': self.getColumn(self.input,  lexpos)
                })

    def getExpType(self, p):
        if p[1]['info']['exp_type'] == 'UNDEFINED' or p[3]['info']['exp_type'] == 'UNDEFINED':
            p[0]['info']['exp_type'] = 'UNDEFINED'
        elif p[1]['info']['exp_type'] == "REAL" or p[3]['info']['exp_type'] == "REAL":
            p[0]['info']['exp_type'] = "REAL"
        elif p[1]['info']['exp_type'] == "INTEGER" or p[3]['info']['exp_type'] == "INTEGER":
            p[0]['info']['exp_type'] = "INTEGER"
        elif p[1]['info']['exp_type'] == "CHAR" or p[3]['info']['exp_type'] == "CHAR":
            p[0]['info']['exp_type'] = "CHAR"
        elif p[1]['info']['exp_type'] == "BOOLEAN" or p[3]['info']['exp_type'] == "BOOLEAN":
            p[0]['info']['exp_type'] = "BOOLEAN"

    def findSymbol(self, id):
        if self.isInSubFunc and id in self.symbolList['subFuncSymbol'].keys():
            return self.symbolList['subFuncSymbol'][id]
        elif id in self.symbolList['globalSymbol'].keys():
            return self.symbolList['globalSymbol'][id]
        else:
            return False

    def isSafeAssign(self, sour, des):
        if (
                (sour == 'INTEGER' and (des == 'INTEGER' or des == 'REAL')) or
                (sour == 'REAL' and des == 'REAL') or
                (sour == 'CHAR' and (des == 'CHAR' or des == 'INTEGER' or des == 'REAL')) or
                (sour == 'BOOLEAN' and (des == 'BOOLEAN' or des == 'INTEGER' or des == 'REAL' or des == 'CHAR'))
        ):
            return True
        return False

    def errorInProcedureCall(self, p):
        if p[1] not in self.symbolList['funcID'].keys():
            self.error.append({
                'error': 'Undefined identifier',
                'value': p[1],
                'line': p.slice[1].lineno,
                'column': self.getColumn(self.input,  p.slice[1].lexpos)
            })
        elif len(p[3]['info']['expressions']) != self.symbolList['funcID'][p[1]]['size']:
            self.error.append({
                'error': 'The number of parameters does not match in function call',
                'value': p[1],
                'line': p.slice[1].lineno,
                'column': self.getColumn(self.input,  p.slice[1].lexpos)
            })
        else:
            for i in range(len(p[3]['info']['expressions'])):
                var_type = p[3]['info']['exp_type'][i]
                para_type = self.symbolList['funcID'][p[1]]['varTable'][i]['type']
                if var_type != 'Undefined' and para_type != 'Undefined' and not self.isSafeAssign(var_type, para_type):
                    self.warning.append({
                        'warning': 'Unsafe assignment',
                        'value': str(i + 1) + 'th parameter: ' + var_type + ' assign to ' + para_type,
                        'line': p.slice[1].lineno,
                        'column': self.getColumn(self.input,  p.slice[1].lexpos)
                    })
                elif self.symbolList['funcID'][p[1]]['isReference'][i] and not (
                        p[3]['info']['expressions'][i]['child_nodes'][0]['p_length'] == 2 and
                        p[3]['info']['expressions'][i]['child_nodes'][0]['child_nodes'][0]['p_length'] == 2 and
                        p[3]['info']['expressions'][i]['child_nodes'][0]['child_nodes'][0]['child_nodes'][0]['info']['_type'] == 'variable'
                ):
                    self.error.append({
                        'error': 'Unable to call by reference',
                        'value': str(i + 1) + 'th parameter',
                        'line': p.slice[1].lineno,
                        'column': self.getColumn(self.input,  p.slice[1].lexpos)
                    })

    def getColumn(self, input, lexpos):
        last_cr = input.rfind('\n', 0, lexpos)
        if last_cr < 0:
            last_cr = -1
        column = lexpos - last_cr
        return column

    def run(self, input, **kwargs):
        self.symbolTable.clear()
        self.warning.clear()
        self.error.clear()
        self.symbolList['globalSymbol'].clear()
        self.symbolList['subFuncSymbol'].clear()
        self.symbolList['funcID'].clear()
        self.input = input
        lexer = Lexer()
        lexer.build(input)
        self.parser = yacc.yacc(module=self, **kwargs)
        ast = self.parser.parse(input)
        return [ast, self.symbolTable, self.warning, lexer.error + self.error]

if __name__ == '__main__':
    data = open('../input.txt').read()
    m = Parser()
    import json as js
    fw=open("../input.out_const", "w+")
    ret=m.run(data)
    print(type(ret[1]))
    js.dump(ret,fw,indent=2)


from types import LambdaType
from typing import Any, Dict, Type, Union, Callable, List, overload, TypeVar

from kink.errors.service_error import ServiceError
from kink.typing_support import is_optional, unpack_optional

_MISSING_SERVICE = object()

T = TypeVar("T")

class Container:
    def __init__(self):
        self._memoized_services: Dict[Union[str, Type], Any] = {}
        self._services: Dict[Union[str, Type], Any] = {}
        self._factories: Dict[Union[str, Type], Callable[[Container], Any]] = {}
        self._aliases: Dict[Union[str, Type], List[Union[str, Type]]] = {}

    def __setitem__(self, key: Union[str, Type], value: Any) -> None:
        self._services[key] = value

        if key in self._memoized_services:
            del self._memoized_services[key]

    def add_alias(self, name: Union[str, Type], target: Union[str, Type]):
        if List[target] in self._memoized_services:  # type: ignore
            del self._memoized_services[List[target]]  # type: ignore

        if name not in self._aliases:
            self._aliases[name] = []
        self._aliases[name].append(target)

    @overload
    def __getitem__(self, key: str) -> Any:
        ...

    @overload
    def __getitem__(self, key: Type[T]) -> T:
        ...

    def __getitem__(self, key):
        if key in self._factories:
            return self._factories[key](self)

        if is_optional(key):
            return self[unpack_optional(key)]

        service = self._get(key)

        if service is not _MISSING_SERVICE:
            return service

        if key in self._aliases:
            unaliased_key = self._aliases[key][0]  # By default return first aliased service
            if unaliased_key in self._factories:
                return self._factories[unaliased_key](self)
            service = self._get(unaliased_key)

        if service is not _MISSING_SERVICE:
            return service

        # Support aliasing
        if self._has_alias_list_for(key):
            result = [self._get(alias) for alias in self._aliases[key.__args__[0]]]  # type: ignore
            self._memoized_services[key] = result
            return result

        raise ServiceError(f"Service {key} is not registered.")

    def _get(self, key: Union[str, Type]) -> Any:
        if key in self._memoized_services:
            return self._memoized_services[key]

        if key not in self._services:
            return _MISSING_SERVICE

        value = self._services[key]

        if isinstance(value, LambdaType) and value.__name__ == "<lambda>":
            self._memoized_services[key] = value(self)
            return self._memoized_services[key]

        return value

    def __contains__(self, key) -> bool:
        contains = key in self._services or key in self._factories or key in self._aliases

        if contains:
            return contains

        if self._has_alias_list_for(key):
            return True

        if is_optional(key):
            return unpack_optional(key) in self

        return False

    def _has_alias_list_for(self, key: Union[str, Type]) -> bool:
        return hasattr(key, "__origin__") and hasattr(key, "__args__") and key.__origin__ == list and key.__args__[0] in self._aliases  # type: ignore

    @property
    def factories(self) -> Dict[Union[str, Type], Callable[["Container"], Any]]:
        return self._factories

    def clear_cache(self) -> None:
        self._memoized_services = {}

di: Container = Container()

__all__ = ["Container", "di"]


import re
from .string import String
from ..autodoc.schema import Array as AutoDocArray
from ..autodoc.schema import Object as AutoDocObject
from ..autodoc.schema import String as AutoDocString
from collections import OrderedDict

class BelongsTo(String):
    """
    Controls a belongs to relationship.

    This column should be named something like 'parent_id', e.g. user_id, column_id, etc...  It expects the actual
    database column to be an integer.  It also provides an additional property on the model which returns the
    related model, instead of the id, with a name given by dropping `_id` from the column name.  In other words,
    if you have a column called user_id and a particular model has a user_id of 5, then:

    ```
    print(model.user_id)
    # prints 5
    print(model.user.id)
    # prints 5
    print(model.user.name)
    # prints the name of the user with an id of 5.
    ```
    """

    wants_n_plus_one = True
    required_configs = [
        "parent_models_class",
    ]

    my_configs = [
        "model_column_name",
        "readable_parent_columns",
        "join_type",
    ]

    def __init__(self, di):
        super().__init__(di)

    def _check_configuration(self, configuration):
        super()._check_configuration(configuration)
        self.validate_models_class(configuration["parent_models_class"])

        if not configuration.get("model_column_name") and self.name[-3:] != "_id":
            raise ValueError(
                f"Invalid name for column '{self.name}' in '{self.model_class.__name__}' - "
                + "BelongsTo column names must end in '_id', or you must set 'model_column_name' to specify the name of the column "
                + "that the parent model can be fetched from."
            )
        if configuration.get("model_column_name") and type(configuration.get("model_column_name")) != str:
            raise ValueError(
                f"Configuration error for '{self.name}' in '{self.model_class.__name__}': 'model_column_name' must be a string."
            )

        join_type = configuration.get("join_type")
        if join_type and join_type.upper() not in ["LEFT", "INNER"]:
            raise ValueError(
                f"Configuration error for '{self.name}' in '{self.model_class.__name__}': join_type must be INNER or LEFT"
            )

        if configuration.get("readable_parent_columns"):
            parent_columns = self.di.build(configuration["parent_models_class"], cache=True).raw_columns_configuration()
            error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
            readable_parent_columns = configuration["readable_parent_columns"]
            if not hasattr(readable_parent_columns, "__iter__"):
                raise ValueError(
                    f"{error_prefix} 'readable_parent_columns' should be an iterable "
                    + "with the list of child columns to output."
                )
            if isinstance(readable_parent_columns, str):
                raise ValueError(
                    f"{error_prefix} 'readable_parent_columns' should be an iterable "
                    + "with the list of child columns to output."
                )
            for column_name in readable_parent_columns:
                if column_name not in parent_columns:
                    raise ValueError(
                        f"{error_prefix} 'readable_parent_columns' references column named '{column_name}' but this"
                        + "column does not exist in the model class."
                    )

    def _finalize_configuration(self, configuration):
        return {
            **super()._finalize_configuration(configuration),
            **{
                "model_column_name": configuration.get("model_column_name")
                if configuration.get("model_column_name")
                else self.name[:-3],
                "join_type": configuration.get("join_type", "INNER").upper(),
            },
        }

    def input_error_for_value(self, value, operator=None):
        integer_check = super().input_error_for_value(value)
        if integer_check:
            return integer_check
        parent_models = self.parent_models
        id_column_name = parent_models.get_id_column_name()
        if not len(parent_models.where(f"{id_column_name}={value}")):
            return f"Invalid selection for {self.name}: record does not exist"
        return ""

    def can_provide(self, column_name):
        return column_name == self.config("model_column_name")

    def provide(self, data, column_name):
        # did we have data parent data loaded up with a query?
        parent_table = self.parent_models.table_name()
        parent_id_column_name = self.parent_models.get_id_column_name()
        if f"{parent_table}_{parent_id_column_name}" in data:
            parent_data = {parent_id_column_name: data[f"{parent_table}_{parent_id_column_name}"]}
            for column_name in self.parent_columns.keys():
                select_alias = f"{parent_table}_{column_name}"
                parent_data[column_name] = data[select_alias] if select_alias in data else None
            return self.parent_models.model(parent_data)

        # if not, just look it up from the id
        parent_id = data.get(self.name)
        if parent_id:
            parent_id_column_name = self.parent_models.get_id_column_name()
            return self.parent_models.where(f"{parent_id_column_name}={parent_id}").first()
        return self.parent_models.empty_model()

    def configure_n_plus_one(self, models, columns=None):
        if columns is None:
            columns = self.config("readable_parent_columns", silent=True)
        if not columns:
            return models

        own_table_name = models.table_name()
        parent_table = self.parent_models.table_name()
        parent_id_column_name = self.parent_models.get_id_column_name()
        alias = models.is_joined(parent_table)
        if not alias:
            alias = parent_table
            join_type = "LEFT " if self.config("join_type") == "LEFT" else ""
            models = models.join(
                f"{join_type}JOIN {parent_table} on {parent_table}.{parent_id_column_name}={own_table_name}.{self.name}"
            )

        select_parts = [f"{alias}.{column_name} AS {parent_table}_{column_name}" for column_name in columns]
        select_parts.append(f"{alias}.{parent_id_column_name} AS {parent_table}_{parent_id_column_name}")
        return models.select(", ".join(select_parts))

    @property
    def parent_models(self):
        return self.di.build(self.config("parent_models_class"), cache=True)

    @property
    def parent_columns(self):
        return self.parent_models.model_columns

    def to_json(self, model):
        # if we don't have readable parent columns specified, then just return the id
        if not self.config("readable_parent_columns", silent=True):
            return super().to_json(model)

        # otherwise return an object with the readable parent columns
        columns = self.parent_columns
        parent = model.__getattr__(self.config("model_column_name"))
        json = OrderedDict()
        if parent.id_column_name not in self.config("readable_parent_columns"):
            json[parent.id_column_name] = list(columns[parent.id_column_name].to_json(parent).values())[0]
        for column_name in self.config("readable_parent_columns"):
            json = {**json, **columns[column_name].to_json(parent)}
        id_less_name = self.config("model_column_name")
        return {
            **super().to_json(model),
            id_less_name: json,
        }

    def documentation(self, name=None, example=None, value=None):
        columns = self.parent_columns
        parent_id_column_name = self.parent_models.get_id_column_name()
        parent_properties = [columns[parent_id_column_name].documentation()]

        parent_columns = self.config("readable_parent_columns", silent=True)
        parent_id_doc = AutoDocString(name if name is not None else self.name)
        if not parent_columns:
            return parent_id_doc

        for column_name in self.config("readable_parent_columns"):
            if column_name == parent_id_column_name:
                continue
            parent_properties.append(columns[column_name].documentation())

        return [
            parent_id_doc,
            AutoDocObject(
                self.config("model_column_name"),
                parent_properties,
            ),
        ]

    def is_allowed_operator(self, operator, relationship_reference=None):
        """
        This is called when processing user data to decide if the end-user is specifying an allowed operator
        """
        if not relationship_reference:
            return "="
        parent_columns = self.parent_columns
        if relationship_reference not in self.parent_columns:
            raise ValueError(
                "I was asked to search on a related column that doens't exist.  This shouldn't have happened :("
            )
        return self.parent_columns[relationship_reference].is_allowed_operator(operator)

    def check_search_value(self, value, operator=None, relationship_reference=None):
        if not relationship_reference:
            return self.input_error_for_value(value, operator=operator)
        parent_columns = self.parent_columns
        if relationship_reference not in self.parent_columns:
            raise ValueError(
                "I was asked to search on a related column that doens't exist.  This shouldn't have happened :("
            )
        return self.parent_columns[relationship_reference].check_search_value(value, operator=operator)

    def add_search(self, models, value, operator=None, relationship_reference=None):
        if not relationship_reference:
            return super().add_search(models, value, operator=operator)

        parent_columns = self.parent_columns
        if relationship_reference not in self.parent_columns:
            raise ValueError(
                "I was asked to search on a related column that doens't exist.  This shouldn't have happened :("
            )

        own_table_name = models.table_name()
        parent_table = self.parent_models.table_name()
        parent_id_column_name = self.parent_models.get_id_column_name()
        alias = models.is_joined(parent_table)
        if not alias:
            models = models.join(
                f"JOIN {parent_table} on {parent_table}.{parent_id_column_name}={own_table_name}.{self.name}"
            )
            alias = parent_table

        related_column = self.parent_columns[relationship_reference]
        return models.where(related_column.build_condition(value, operator=operator, column_prefix=f"{alias}."))


from pathlib import Path
from multiprocessing import Pool
from itertools import product

import click
import numpy as np
import pandas as pd
import SimpleITK as sitk
from tqdm import tqdm
from radiomics.featureextractor import RadiomicsFeatureExtractor

center = "mda_test"
project_dir = Path(__file__).resolve().parents[2]
data_dir = project_dir / "data/hecktor2022/processed/"

default_input_folder = data_dir / f"{center}/"
default_output_path = project_dir / f"data/hecktor2022/qc_{center}.csv"

@click.command()
@click.argument('input_folder',
                type=click.Path(),
                default=default_input_folder)
@click.argument('output_path', type=click.Path(), default=default_output_path)
@click.option("--cores", type=click.INT, default=None)
def main(input_folder, output_path, cores):
    input_folder = Path(input_folder)
    vois = [
        f for f in input_folder.rglob("*labels_renamed/*_corrected.nii.gz")
    ]
    patient_ids = set([f.name.split("_")[0] for f in vois])
    if "mda_test" in center.lower():
        patient_ids = [p for p in patient_ids if int(p.split("-")[1]) > 201]
    processor = PatientProcessor(input_folder)
    if cores:
        with Pool(cores) as p:
            result = list(
                tqdm(p.imap(processor, patient_ids), total=len(patient_ids)))
    else:
        result = []
        for patient_id in tqdm(patient_ids):
            result.append(processor(patient_id))
    df = pd.concat(result)
    df.to_csv(output_path)

class PatientProcessor():

    def __init__(self, input_folder):
        self.input_folder = input_folder
        self.resampler = sitk.ResampleImageFilter()
        self.resampler.SetInterpolator(sitk.sitkLinear)
        self.featureextractor = RadiomicsFeatureExtractor(
            str(project_dir / "src/data/qc_parameters.yaml"))

    def _clean_pyradiomics_outptut(self, output):
        return {k: v for k, v in output.items() if "iagnos" not in k}

    def _append_results(self, *, patient_id, output_pyradiomics, results,
                        modality, voi):
        output_pyradiomics = self._clean_pyradiomics_outptut(
            output_pyradiomics)
        results = pd.concat(
            [
                results,
                pd.DataFrame(
                    {
                        "patient_id": patient_id,
                        "VOI": voi,
                        "modality": modality,
                        **output_pyradiomics
                    },
                    index=[0],
                )
            ],
            ignore_index=True,
        )
        return results

    def _get_bb(self, mask):
        array = np.transpose(sitk.GetArrayFromImage(mask), (2, 1, 0))
        positions = np.where(array != 0)
        x_min = mask.TransformContinuousIndexToPhysicalPoint([
            np.min(positions[0]).astype(float),
            np.min(positions[1]).astype(float),
            np.min(positions[2]).astype(float),
        ])
        x_max = mask.TransformContinuousIndexToPhysicalPoint([
            np.max(positions[0]).astype(float),
            np.max(positions[1]).astype(float),
            np.max(positions[2]).astype(float),
        ])
        return np.array(
            [x_min[0], x_min[1], x_min[2], x_max[0], x_max[1], x_max[2]])

    def _configure_resampler(self, mask):
        resampling = np.array(mask.GetSpacing())
        bb = self._get_bb(mask)
        size = np.round((bb[3:] - bb[:3]) / resampling).astype(int)
        self.resampler.SetOutputOrigin(bb[:3])
        self.resampler.SetSize([int(k) for k in size])  # sitk is so stupid
        self.resampler.SetOutputSpacing(mask.GetSpacing())
        self.resampler.SetOutputDirection(mask.GetDirection())
        self.resampler.SetInterpolator(sitk.sitkNearestNeighbor)

    def __call__(self, patient_id):
        image_paths = [
            f
            for f in self.input_folder.rglob(f"*images_renamed/{patient_id}*")
        ]
        mask_paths = [
            f
            for f in self.input_folder.rglob(f"*labels_renamed/{patient_id}*")
        ]
        mask = sitk.ReadImage(str(mask_paths[0]))
        try:
            self._configure_resampler(mask)
            mask = self.resampler.Execute(mask)
        except Exception:
            print(
                f"{patient_id} failed to configure resampler, due to direction"
            )
            self.resampler.SetOutputOrigin(mask.GetOrigin())
            self.resampler.SetSize(mask.GetSize())  # sitk is so stupid
            self.resampler.SetOutputSpacing(mask.GetSpacing())
            self.resampler.SetOutputDirection(mask.GetDirection())

        self.resampler.SetInterpolator(sitk.sitkBSpline)
        images = [{
            "image": self.resampler.Execute(sitk.ReadImage(str(f))),
            "modality": f.name.split("__")[1].split(".")[0],
        } for f in image_paths]

        mask_array = sitk.GetArrayFromImage(mask)
        results = pd.DataFrame()
        vois_label = {"GTVp": 1, "GTVn": 2}
        for voi, image_ in product(vois_label.keys(), images):
            if np.sum(mask_array == vois_label[voi]) == 0:
                continue
            image = image_["image"]
            modality = image_["modality"]
            results = self._append_results(
                patient_id=patient_id,
                output_pyradiomics=self.featureextractor.execute(
                    image,
                    mask,
                    label=vois_label[voi],
                ),
                results=results,
                modality=modality,
                voi=voi,
            )
        return results

if __name__ == '__main__':
    main()


import os
import requests
import csv
import os
import json
import sys
import ast
import time

f = csv.writer(open('submissions.csv', 'a'))

uf= open('Tables/users.csv', 'r')

user_dict = set()

store_id = 0
authro = ''

worked = 0

zz = 0
for line in uf:
    if zz != 0:
        user_dict.add(line.split(',')[0])
    zz = 1

problems_dict = dict()
pf = open('Tables/problems.csv', 'r')
zz = 0
for line in pf:
    if zz != 0:
        xx = line.split(',')
        problems_dict[(xx[1],xx[2],xx[3])] = xx[0]
    zz = 1

def isInContest(submission):
    if 'contestId' not in submission:
        submission['contestId'] = -1
    return True

def checkUser(user):
    global authro, user_dict
    authro = ''
    if user['members'][0]['handle'] in user_dict:
        authro = user['members'][0]['handle']
        return True
    else:
        return False

def isNotinTeam(submission):
    arr = ['CONTESTANT','PRACTICE']
    if 'teamId' not in submission and ('ghost' in submission and submission['ghost'] == False ) and submission['participantType'] in arr:
        assert len(submission['members']) == 1
        return True
    return False

def removeQuote(s):
    s1 = s.replace('\"', '')
    s2 = s1.replace('\'', '')
    return s2

def checkProblem(problem):
    global store_id,problems_dict
    if 'rating' not in problem or 'contestId' not in problem or 'name' not in problem:
        return False
    store_id = -1
    ra = problem['name']
    rb = str(problem['contestId'])
    rc = str(problem['rating'])

    zz = (removeQuote(ra),rb,rc)
    if  zz in problems_dict:
        store_id = problems_dict[zz]
        return True
    else:
        return False

def hasVerdit(submission):
    return 'verdict' in submission

def isValid(submission):
    return isInContest(submission) and isNotinTeam(submission['author'])  and checkProblem(submission['problem']) and checkUser(submission['author']) and hasVerdit(submission)

def check_for_handle(__handle):
    global store_id,authro, worked
    worked = 0

    encoding = 'utf-8'

    api_url = 'https://codeforces.com/api/user.status?handle='+__handle+'&from=1&count=100000'
    response = requests.get(api_url)

    response = json.loads(response.text)

    if response['status'] != 'OK':
        print("Error")
        exit()
    else:
        worked = 1
        print("Got response")

    i = 0
    for lines in response['result']:
        if i != 0:
            x = lines
            if not isValid(x):
                continue
            f.writerow([x['id'], x['contestId'], x['author']['participantType'], store_id, authro, x['programmingLanguage'] , x['verdict'], str(x['timeConsumedMillis']), str(x['memoryConsumedBytes']) ])
        i = 1

f1 = open('users.csv', 'r')

id = 0
for line in f1:
    id += 1
    if id <= 10192:
        continue
    while worked == 0:
        check_for_handle(line.strip())
    worked = 0
    print("Processed: " , id)


# WordNetのlemma.count()を用いて各word,synset,lemmaの頻度を算出してtextファイル化
import os
import sys
#WordNet
import nltk
from nltk.corpus import WordNetCorpusReader
from nltk.corpus import wordnet as wn

import codecs

import util

class ForwardWNGeneralityExtractor:
    def __init__(self, folder, lang):
        self.folder = folder
        if lang in wn.langs():
            self.lang = lang
        else:
            print("language: '%s' is not supported, try another language" % lang)
        #initialize
        self.WordCounter = {}
        self.SynsetCounter = {}
        self.pos_list = ['a', 's', 'r', 'n', 'v']

    def main(self):

        ver = wn.get_version()
        print("RESOURCE: WN " + str(ver) + "\n")
        print("LANGUAGE: "+self.lang+"\n")
        print("TARGET: " + self.folder + "\n")

        self.extractWordsAndSynsetsGenerality(self.folder + "words.txt",self.folder + "synsets.txt",self.folder + "lemmas.txt")
        print("DONE")

    def extractWordsAndSynsetsGenerality(self, filenameWords, filenameSynsets,  filenameLemmas):
        #file
        fWords = codecs.open(filenameWords, 'w', 'utf-8')
        fSynsets = codecs.open(filenameSynsets, 'w', 'utf-8')
        fLemmas = codecs.open(filenameLemmas, 'w', 'utf-8')

        frequencyAllTold = 0

        for pos in self.pos_list:
            for synset in wn.all_synsets(pos=pos):
                synsetId = synset.name()
                self.SynsetCounter[synsetId] = 0

                fSynsets.write(synsetId+" ")

                for lemma in synset.lemmas():
                    frequencyAllTold += lemma.count()

                    wordId = lemma.name().lower()
                    fLemmas.write(synsetId+":"+wordId+" "+str(lemma.count())+"\n")

                    if wordId not in self.WordCounter:
                        self.WordCounter[wordId] = 0

                    self.SynsetCounter[synsetId] += lemma.count()
                    self.WordCounter[wordId] += lemma.count()

                fSynsets.write(str(self.SynsetCounter[synsetId])+"\n")

        for word, count in self.WordCounter.items():
            fWords.write(word+" "+str(count)+"\n")

        fWords.close()
        fSynsets.close()
        fLemmas.close()

        print("  Frequency all told: %d\n" % frequencyAllTold)
        print("  Words: %d\n" % len(self.wordCounter))
        print("  Synsets: %d\n" % len(self.synsetCounter))

if __name__ == '__main__':
    #path to output folder
    folder = '/Users/arai9814/Desktop/generality/'
    #language
    lang = 'eng'
    fwnge = ForwardWNGeneralityExtractor(folder, lang)
    fwnge.main()


"""

dictionary_hogwartshouses.py

create dictionary from JSON and prints information for any given Hogwarts house
"""

import sys
import json
import urllib.request

url = "https://raw.githubusercontent.com/kathyvsinternet/Python-INFO1-CE9990/master/hogwarts_houses.json"

try:
    data = urllib.request.urlopen(url)
except urllib.error.URLError as urlError:
    print("urllib.error.URLError", urlError)
    sys.exit(1)

file = data.read()

data.close()

try:
    f = file.decode("utf-8")
except UnicodeError as unicodeError:
    print(unicodeError)
    sys.exit(1)

try:
    info = json.loads(f)
except json.JSONDecodeError as jsonDecodeError:
    print(jsonDecodeError)
    sys.exit(1)

h = input("Which Hogwarts House were you sorted in? ")

print()

house = h.lower()

def details(house):
    print(dictionary["Founder"], "is the founder of the", h, "house.")
    print("Its house animal is the ", dictionary["Animal"], ".", sep = "")
    print(dictionary["Head"], "is head of house.")
    print(dictionary["Ghost"], "is the", h, "ghost.")
    print("The ", h, " common room is ", dictionary["Common Room"], ".", sep = "")

for dictionary in info:
    if dictionary["House"].lower() == house:
        details(dictionary)
        break
else:
    print("Are you sure", h, "is a Hogwarts house?")
    sys.exit(1)

sys.exit(0)


import numpy as np
import pandas as pd
import pytest
from datetime import datetime, timedelta
from metloom.variables import MesowestVariables, CdecStationVariables

from metloom.dataframe_utils import (
    join_df, append_df, merge_df, resample_df, resample_whole_df
)

df1 = pd.DataFrame.from_records([{"foo": 12.0}, {"foo": 11.0}])
df2 = pd.DataFrame.from_records([{"bar": 1.0}, {"bar": 1.0}])
df3 = pd.DataFrame.from_records([
    {"foo": 12.0, "bar": 1.0}, {"foo": 11.0, "bar": 1.0}
])
df4 = pd.DataFrame.from_records(
    [
        {"foo": 12.0},
        {"foo": 11.0},
        {"foo": 12.0},
        {"foo": 11.0},
    ],
    index=[0, 1, 0, 1],
)

@pytest.mark.parametrize(
    "set1, set2, expected", [(df1, df2, df3), (df1, None, df1), (None, df2, df2)]
)
def test_join_df(set1, set2, expected):
    pd.testing.assert_frame_equal(join_df(set1, set2), expected)

def test_join_df_failure():
    with pytest.raises((ValueError, AttributeError)):
        join_df(df1, "bad value")

@pytest.mark.parametrize(
    "set1, set2, expected", [(df1, None, df1), (None, df2, df2), (df1, df1, df4)]
)
def test_append_df(set1, set2, expected):
    pd.testing.assert_frame_equal(append_df(set1, set2), expected)

def test_merge_df():
    first = pd.DataFrame(
        {
            "datetime": [
                datetime(2020, 1, 3), datetime(2020, 1, 6),
                datetime(2020, 1, 7)
            ],
            "a": [3.0, 6.0, 7.0],
            "c": [3.0, 6.0, 7.0]
        }
    ).set_index("datetime")
    second = pd.DataFrame(
        {
            "datetime": [
                datetime(2020, 1, 2), datetime(2020, 1, 5),
                datetime(2020, 1, 6), datetime(2020, 1, 8)],
            "b": [2.0, 5.0, 6.0, 8.0],
            "c": [2.0, 5.0, 6.0, 8.0]
        },
    ).set_index("datetime")

    expected = pd.DataFrame(
        {
            "datetime": [
                datetime(2020, 1, 2), datetime(2020, 1, 3), datetime(2020, 1, 5),
                datetime(2020, 1, 6), datetime(2020, 1, 7), datetime(2020, 1, 8)],
            "a": [np.nan, 3.0, np.nan, 6.0, 7.0, np.nan],
            "c": [2.0, 3.0, 5.0, 6.0, 7.0, 8.0],
            "b": [2.0, np.nan, 5.0, 6.0, np.nan, 8.0],
        }
    ).set_index("datetime")
    result = merge_df(first, second)
    pd.testing.assert_frame_equal(expected, result)

def test_merge_df_duplicates():
    first = pd.DataFrame(
        {
            "datetime": [
                datetime(2020, 1, 2), datetime(2020, 1, 5)
            ],
            "b": [6.0, 8.0],
            "c": [6.0, 8.0],
        },
    ).set_index("datetime")
    second = pd.DataFrame(
        {
            "datetime": [
                datetime(2020, 1, 3), datetime(2020, 1, 3),
                datetime(2020, 1, 7)
            ],
            "a": [3.0, 3.0, 3.0],
            "c": [4.0, 4.0, 4.0]
        }
    ).set_index("datetime")
    result = merge_df(first, second)
    expected = pd.DataFrame(
        {
            "datetime": [
                datetime(2020, 1, 2),
                datetime(2020, 1, 3),
                datetime(2020, 1, 5), datetime(2020, 1, 7)
            ],
            "b": [6.0, np.nan, 8.0, np.nan],
            "c": [6.0, 4.0, 8.0, 4.0],
            "a": [np.nan, 3.0, np.nan, 3.0],
        }
    ).set_index("datetime")
    pd.testing.assert_frame_equal(result, expected)

@pytest.fixture()
def sample_df(in_data, variable, delta_t, interval):
    """
    Build a dataframe given a incoming data set and interval to
    look at and a delta time to create the datetime index.
    """
    if interval == 'H':
        dt = timedelta(minutes=delta_t)
    elif interval == 'D':
        dt = timedelta(hours=delta_t)

    d_time = [datetime(2021, 12, 1) + i * dt for i in range(len(in_data))]
    df = pd.DataFrame.from_dict({'datetime': d_time, variable.name: in_data})
    df = df.set_index('datetime')
    yield df

@pytest.mark.parametrize("in_data, variable, delta_t, interval, expected_data", [
    ([1, 3, 2, 4], MesowestVariables.TEMP, 30, 'H', [2, 3]),
    ([3, 3, 2, 2], CdecStationVariables.SWE, 12, 'D', [3, 2]),
])
def test_resample_df(sample_df, in_data, variable, delta_t, interval, expected_data):
    """
    Test the resample function can resample values according to
    hourly and daily time intervals
    """
    out_df = resample_df(sample_df, variable, interval=interval)
    assert out_df[variable.name].values == pytest.approx(expected_data)

def test_resample_df_odd_increment():
    df = pd.DataFrame.from_records([
        {"datetime": datetime(2020, 1, 2, 11, 3, 20), "AIR TEMP": 1.0},
        {"datetime": datetime(2020, 1, 2, 11, 13, 21), "AIR TEMP": 2.0},
        {"datetime": datetime(2020, 1, 2, 11, 42, 1), "AIR TEMP": 3.0},
        {"datetime": datetime(2020, 1, 2, 12, 5, 28), "AIR TEMP": 2.0},
        {"datetime": datetime(2020, 1, 2, 12, 59, 3), "AIR TEMP": 1.5},
    ])
    df.set_index("datetime", inplace=True)
    out_df = resample_df(df, MesowestVariables.TEMP, interval="H")
    expected = pd.DataFrame.from_records([
        {"datetime": datetime(2020, 1, 2, 11), "AIR TEMP": 2.0},
        {"datetime": datetime(2020, 1, 2, 12,), "AIR TEMP": 1.75},
    ])
    expected.set_index("datetime", inplace=True)
    expected.index.freq = "H"
    pd.testing.assert_frame_equal(out_df, expected)

def test_resample_full_df_odd_increment():
    df = pd.DataFrame.from_records([
        {"datetime": datetime(2020, 1, 2, 11, 3, 20), "other": "value",
         "AIR TEMP": 1.0},
        {"datetime": datetime(2020, 1, 2, 11, 13, 21), "other": "value",
         "AIR TEMP": 2.0},
        {"datetime": datetime(2020, 1, 2, 11, 42, 1), "other": "value",
         "AIR TEMP": 3.0},
        {"datetime": datetime(2020, 1, 2, 12, 5, 28), "other": "value",
         "AIR TEMP": 2.0},
        {"datetime": datetime(2020, 1, 2, 12, 59, 3), "other": "value",
         "AIR TEMP": 1.5},
    ])
    df.set_index("datetime", inplace=True)
    out_df = resample_whole_df(df, MesowestVariables.TEMP, interval="H")
    expected = pd.DataFrame.from_records([
        {"datetime": datetime(2020, 1, 2, 11), "other": "value", "AIR TEMP": 2.0},
        {"datetime": datetime(2020, 1, 2, 12,), "other": "value", "AIR TEMP": 1.75},
    ])
    expected.set_index("datetime", inplace=True)
    expected.index.freq = "H"
    pd.testing.assert_frame_equal(out_df, expected, check_like=True)


from __future__ import annotations

import pathlib
from collections import deque

class Monka:
    def __init__(
        self,
        items: list,
        operator_value: str,
        operator_type: str,
        divider: int,
        target_index_true: int,
        target_index_false: int,
        high_worry: bool
    ) -> None:
        self.items = deque(items)
        self.operator_type = operator_type
        self.divider = divider
        self.target_index_true = target_index_true
        self.target_index_false = target_index_false
        self.high_worry = high_worry

        self.worry_operator = 1
        self.inspections = 0

        if operator_value == "old":
            self.operator_value = None
        else:
            self.operator_value = int(operator_value)

    def print_items(self) -> None:
        print(self.items)

    def has_items(self) -> bool:
        return len(self.items) != 0

    def __rule(self, old_value: int) -> int:
        opv = old_value if self.operator_value is None else self.operator_value

        if self.operator_type == "+":
            return old_value + opv
        if self.operator_type == "*":
            return old_value * opv

        raise NotImplementedError()

    def catch_item(self, new_item:int):
        self.items.append(new_item)

    def next_monka(self) -> tuple[int, int]:
        self.inspections += 1

        current_item = self.items.popleft()
        n_value = self.__rule(current_item)

        if not self.high_worry:
            n_value //= 3
        else:
            n_value %= self.worry_operator

        if n_value % self.divider == 0:
            return self.target_index_true, n_value

        return self.target_index_false, n_value

    @staticmethod
    def new(text_block: str, high_worry: bool) -> Monka:
        text_block = text_block.strip().split("\n")
        items = list(map(int, text_block[1].split(":")[-1].split(",")))

        o_type, o_value = text_block[2].split(" ")[-2:]

        divider = int(text_block[3].split(" ")[-1])
        target_index_true  = int(text_block[4].split(" ")[-1])
        target_index_false = int(text_block[5].split(" ")[-1])
        return Monka(
            items=items,
            operator_type=o_type,
            operator_value=o_value,
            divider=divider,
            target_index_true=target_index_true,
            target_index_false=target_index_false,
            high_worry=high_worry
        )

def worry_game(lines, rounds, high_worry):
    monkas: list[Monka] = []
    for block in lines:
        monkas.append(Monka.new(text_block=block, high_worry=high_worry))

    # 猴子 :^)
    worry_operator = 1
    for monka in monkas:
        worry_operator *= monka.divider
    for monka in monkas:
        monka.worry_operator = worry_operator

    for _ in range(1, rounds+1):
        for monka in monkas:
            while monka.has_items():
                target_idx, n_item = monka.next_monka()
                monkas[target_idx].catch_item(new_item=n_item)

    inspections = [monka.inspections for monka in monkas]
    t1, t2 = sorted(inspections)[-2:]
    return t1, t2, t1*t2

lines = pathlib.Path("input.txt").read_text().split("\n\n")
print("Task 1", worry_game(lines,     20, high_worry=False))
print("Task 2", worry_game(lines, 10_000, high_worry=True))


datbase_url = "https://handsin-bot-database.herokuapp.com/"
from pprint import pprint
import requests
import json
from pyinflect import getAllInflections, getInflection
import re
import random
import copy
from nltk.stem import PorterStemmer
from collections import OrderedDict

porter = PorterStemmer()

def isEntityUnknown(value):
	products = make_query("products?product_value_name="+value)
	services = make_query("services?service_value_name="+value)
	return not(products != [] or services != [])

def get_price(service):
	price = make_query("services?service_value_name="+service)
	return price[0].get("price")

def make_query(query):
	return(requests.get(datbase_url+query).json())
def GeneralRetrievalQuery(resp,client_id,recipient,intent,answer=None):
	if answer == None:
		answer_id  = get_answer_id(resp,intent)
	else:
		answer_id = answer

	print(answer_id)
	query = datbase_url + "responses?answer_id=" + answer_id + "&_where[client.sendr_id]="+str(client_id)
	answer =	build_answer(query,resp)
	answer = substituter(answer,resp,recipient)
	return answer

def build_answer(query,resp):
	print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
	print(query)
	r = requests.get(query)
	if r != []:
				answer = [data.get("text") for data in r.json()]
				answer = random.choice(answer)
	else:
			answer = "What do you mean by " + resp.get("text") + "?"
	return answer

def variate_answer_with_user_input(resp,entity_key,modify_word=None):
	word = "placeholder"
	# Only process entities if they exist in the message
	if entity_key in resp.get("entities").keys():
		entity = resp.get("entities").get(entity_key)
		#check if the word used by the user is an english word if not use the default value retrieved by the wit.ai bot
		body  = entity[0].get("body")
		if body.isalpha():
			#if it is a word pick at random either the user value or the default value to substitute in the answer
			word = random.choice([entity[0].get("body"), entity[0].get("value")])
		else:
				word = entity[0].get("value")

		#format word so it is in it's basic form exampel "repaired" becomes "repair"
		new_word = porter.stem(word)
		#apply one of the formatations bellow to the word
		#TODO get rid of all these if's
		if modify_word == "past":
			new_word = getInflection(new_word,"VBN")
			if new_word != None:
				word = new_word[0]
		elif(modify_word == "plural"):
			new_word =  getInflection(word, "NNS")
			if new_word != None:
				word = new_word[0]
		else:
			word = new_word
	return word

def listServices(product):
	string = "\n"
	if product == "all":
		products = make_query("products")
		products = [product.get("product_value_name") for product in products]
	else:
		products = make_query("products?product_value_name="+product)
		products = [products[0].get("product_value_name")]
	for product in products:
		string += product.upper() +"\n"
		services = make_query("services?_where[products.product_value_name]="+product)
		services = [service.get("service_value_name")+"\n" for service in services]
		string += "".join(services)
	return string

def substituter(answer,resp,recipient,ent=None):
	entities = resp.get("entities")
	price = "placeholder"
	value = "placeholder"
	if "productAction:productAction" in entities.keys():
		price = get_price(entities.get("productAction:productAction")[0].get("value"))
		print("*"*10)
		print("price",price)
		print("*"*10)
	if "product:product" in entities.keys():
			value = entities.get("product:product")[0].get("value")
	#list of key value pairs the keys are to be substituted in the template answers by the values
	values = [
            [r"\bPRODUCT_VALUE\b",variate_answer_with_user_input(resp,"product:product")],
            [r"\bPRODUCT_VALUE_PLURAL\b",variate_answer_with_user_input(resp,"product:product","plural")],
            [r"\bPRODUCT_SERVICES\b",listServices(value)],
            [r"\bALL_PRODUCT_VALUES_SERVICES\b",listServices("all")],
						[r"\bPRODUCT_ACTION_VALUE_PAST_TENSED\b",variate_answer_with_user_input(resp,"ProductAction:ProductAction","past")],
						[r"\bPRODUCT_ACTION_VALUE\b",variate_answer_with_user_input(resp,"productAction:productAction")],
						[r"\bNAME\b", recipient.get("first_name") ],
						[r"\bPRODUCT_SERVICE_RATE\b",price],
						[r"\bPRICE\b",price],
					]
	new_answer = copy.deepcopy(answer)
	if "ALL_PRODUCT_VALUES_SERVICES" in answer:
		new_answer = "Idon't know what do you mean by " + resp.get("text") + " but...\n" + new_answer
	for value in values:
			new_answer = re.sub(value[0], value[1], new_answer)
	return new_answer

def sanitizeKey(entities_list,entities,key):
	new_key = copy.deepcopy(key)
	for entity in entities_list:
		value = entities.get((entity + ":"+ entity).replace("|",""))[0].get("value")
		check = isEntityUnknown(value)
		print(check)
		if check:
			new_key = new_key.replace("|"+ entity,"")
	print(new_key)
	return new_key

def get_answer_id(resp,intent):
	entities = resp.get("entities")
	entities_list = sorted(list(OrderedDict.fromkeys([i.split(":")[0]+"|" for i in entities])), key=len)
	entities_list[-1] = entities_list[-1].replace("|","")
	entities_key = "".join(entities_list)
	key = sanitizeKey(entities_list,entities,intent + "|" + entities_key)
	return key

def requestServices(resp,client_id,recipient,intent):
	template_answer = GeneralRetrievalQuery(resp,client_id,recipient,intent)
	return template_answer

def ourServices(resp,client_id,recipient,intent):
	if intent == "NONE":
		template_answer =	GeneralRetrievalQuery(resp,client_id,recipient,intent,"ourServices|NONE")
	else:
		template_answer = GeneralRetrievalQuery(resp,client_id,recipient,intent)
	return template_answer

queries = {
	"ourServices":ourServices,
	"NONE":ourServices,
	"requestServices":requestServices
}

def call_query(intent,resp,client_id,recipient):
	query = queries.get(intent)
	return query(resp,client_id,recipient,intent)


import sqlite3
from datetime import *

class DB:
    def __init__(self) -> None:
        self.con = sqlite3.connect('clock.db')
        self.cur = self.con.cursor()

    def create_table(self):

        """
        CREATE TIME TABLE TO SAVE USER'S TIMES

        """
        self.cur.execute("CREATE TABLE IF NOT EXISTS time(id INTEGER PRIMARY KEY, user_time TEXT)")
        self.con.commit()

    def add_time_to_db(self, *time:str):

        for i in time:

            try:
                self.cur.execute("""INSERT INTO time(user_time) VALUES (?) """, (i, ))
                self.con.commit()

            except ValueError:

                ValueError('Type Eror: input %s must be whole numeric string' % i )

    def read_last_time(self):
        try:
            time = self.cur.execute('SELECT * FROM time WHERE ID = (SELECT MAX(ID) FROM time)')
            return time.fetchall()
        except RuntimeError:
            RuntimeError("error during gettig last records\n perhaps the time table is not yet created\n or there's no record")

    def get_all_records(self):
        try:
            times = self.cur.execute('SELECT * FROM time')
            return [i for i in times.fetchall()]

        except RuntimeError:
            RuntimeError("error during gettig all records\n perhaps the time table is not yet created\n or there's no record")

    def delete_all_records(self):

            self.cur.execute('DELETE FROM time')
            self.con.commit()