import tkinter
import tkinter.filedialog
import pygame
from pygame.constants import *
from game_objects import *
import random
import pygame_textinput
from pydub import AudioSegment
from utils import *
import sys
import pygame_textinput
import json

cursor_size = 64
cursor_surf = pygame.Surface((cursor_size, cursor_size))
cursor_surf.fill((1, 1, 1))
cursor_surf.set_colorkey((1, 1, 1))
pygame.draw.circle(cursor_surf, (255, 255, 255), (cursor_size/2, cursor_size/2), cursor_size/2, 0)
pygame.draw.circle(cursor_surf, (0, 0, 0), (cursor_size/2, cursor_size/2), cursor_size*7/16, cursor_size//8)

trail_time = 250
cursor_trail = pygame.Surface((8, 8))
cursor_trail.set_colorkey((0, 0, 0))
pygame.draw.circle(cursor_trail, pygame.Color('white'), (4, 4), 4, 0)

font_size = 32
font_file = 'fonts/Pixolletta8px.ttf'

beat_size = 128

def prompt_file():
    """Create a Tk file dialog and cleanup when finished"""
    pygame.display.set_mode((0, 0))
    top = tkinter.Tk()
    top.withdraw()  # hide window
    file_name = tkinter.filedialog.askopenfilename(parent=top)
    top.destroy()
    pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
    return file_name

pygame.mixer.init()
pygame.mixer.music.set_volume(0.25)

hit_sound = pygame.mixer.Sound(resource_path('sounds/hit.wav'))
hit_sound.set_volume(0.1)

map_hit = 20

arc_distance = 0.5

color_list = [(255, 64, 64), (255, 128, 64), (255, 255, 64), (128, 255, 64), (64, 255, 64), (64, 255, 128), (64, 255, 255), (64, 128, 255), (64, 64, 255), (128, 64, 255), (255, 64, 255), (255, 64, 128)]
colors = []
for i in range(0, len(color_list), 3):
    colors.append(color_list[i])
for i in range(1, len(color_list), 3):
    colors.append(color_list[i])
for i in range(2, len(color_list), 3):
    colors.append(color_list[i])

def get_color(num):
    return colors[num % len(colors)]

class BaseScene:
    def __init__(self):
        self.next = self
    def update(self, events):
        pass
    def render(self, screen):
        pass
    def switch(self, next_scene):
        self.next = next_scene

class TestScene(BaseScene):
    def __init__(self):
        BaseScene.__init__(self)
        self.last_pos = pygame.mouse.get_pos()
        self.objects = []
        self.objects.append(Circle((0, 0), 0.5, 500, 500, 100000000000000, get_color(random.randint(0, 6))))
        self.width, self.height = pygame.display.get_surface().get_size()

    def update(self, events):
        tick_time = pygame.time.get_ticks()

        if len(self.objects) == 1:
            self.objects.append(Arc(self.objects[0].pos, random.randint(0, 360), 60, self.objects[0].radius+arc_distance, self.objects[0].radius, pygame.time.get_ticks(), 500, {'good':50, 'ok':100}, self.objects[0].color))

        cur_pos = self.last_pos

        for event in events:
            if event.type == MOUSEMOTION:
                if event.pos != self.last_pos:
                    cur_pos = event.pos

        keep_objects = []
        first_arc = True
        for object in self.objects:
            object.update(tick_time)

            if isinstance(object, Arc) and first_arc:
                if self.last_pos != cur_pos:
                    result = object.check_hit([((self.last_pos[0]*2-self.width)/self.height, -(self.last_pos[1]*2-self.height)/self.height), ((cur_pos[0]*2-self.width)/self.height, -(cur_pos[1]*2-self.height)/self.height)])
                    if result > 0:
                        pygame.mixer.Sound.play(hit_sound)
                    self.last_pos = cur_pos
                first_arc = False

            if object.alive:
                keep_objects.append(object)

        self.objects = keep_objects

    def render(self, screen):
        playfield = pygame.Surface((internal_res, internal_res))

        for object in self.objects:
            object.render(playfield)

        screen.blit(pygame.transform.scale(playfield, (self.height, self.height)), ((self.width-self.height)/2, 0))

        pygame.draw.rect(screen, pygame.Color('white'), ((self.width-self.height)/2, 0, self.height, self.height), 4)

class InputField:
    def __init__(self, label, font, pos):
        self.font = font
        self.label = label
        self.labelsurf = self.font.render(label+' ', True, pygame.Color('white'))
        self.input_box = pygame_textinput.TextInputVisualizer(font_color=pygame.Color('white'), font_object=self.font, cursor_color=pygame.Color('white'))
        self.pos = pos
        self.active = False
    def update(self, events):
        for event in events:
            if event.type == MOUSEBUTTONDOWN:
                x, y = event.pos
                if x > self.pos[0] and x < self.pos[0] + self.labelsurf.get_width() + self.input_box.surface.get_width() and y > self.pos[1] and y < self.pos[1] + self.labelsurf.get_height():
                    self.active = True
                else:
                    self.active = False
                    try:
                        float(self.get_value())
                    except ValueError:
                        self.set_value('0')
        self.input_box.cursor_visible = self.active

        if self.active:
            self.input_box.update(events)

    def render(self, screen):
        rect = screen.blit(self.labelsurf, self.pos)
        screen.blit(self.input_box.surface, (self.pos[0]+rect[2], rect[1]))
    def set_value(self, val):
        self.input_box.value = val
    def get_value(self):
        return self.input_box.value

class MapScene(BaseScene):
    def __init__(self, file_path = None, start_pos = 0):
        BaseScene.__init__(self)
        self.last_pos = pygame.mouse.get_pos()
        self.objects = []
        self.width, self.height = pygame.display.get_surface().get_size()
        self.fields = []
        self.grid = 0.1
        self.divisor = 4
        self.anglesnap = 3
        self.play_pos = 0
        self.font = pygame.font.Font(resource_path(font_file), font_size)
        self.side_objects = []
        self.last_hit = 0

        if file_path == None:
            file_path = prompt_file()
            if len(file_path) == 0:
                pygame.quit()
                sys.exit()

        self.music_file_path = ''
        self.map_file_path = ''

        if file_path[-5:] == '.json':
            self.music_file_path = file_path[:-5]
            self.map_file_path = file_path
        else:
            self.music_file_path = file_path
            self.map_file_path = self.music_file_path+'.json'

        if self.music_file_path[-4:] != '.ogg':
            audio_file = AudioSegment.from_file(self.music_file_path)
            self.music_file_path = self.music_file_path[:-4]+'.ogg'
            self.map_file_path = self.music_file_path[:-4]+'.ogg.json'
            audio_file.export(self.music_file_path, format='ogg')

        self.duration = pygame.mixer.Sound(self.music_file_path).get_length()*1000

        if pygame.mixer.music.get_busy():
            pygame.mixer.music.stop()

        pygame.mixer.music.load(self.music_file_path)

        if start_pos:
            self.play_pos = start_pos*1000
            pygame.mixer.music.play(start=start_pos)
            pygame.mixer.music.pause()

        try:
            with open(self.map_file_path, 'r+') as map_file:
                self.map = json.load(map_file)
        except:
            self.map = {}
            open(self.map_file_path, 'x').close()
            self.save()

        if 'objects' not in self.map:
            self.map['objects'] = []
            self.save()

        input_fields = ['offset', 'bpm', 'approach', 'precision', 'width']

        x_pos = font_size
        y_pos = font_size

        for field in input_fields:
            f = InputField(field, self.font, (x_pos, y_pos))
            if field in self.map:
                f.set_value(self.map[field])
            else:
                f.set_value('0')
            self.fields.append(f)
            y_pos += font_size

    def snap(self, n):
        if self.grid == 0:
            return n
        grid = self.height*self.grid/2
        return grid*round(n/grid)

    def snap_pos(self, pos):
        if self.grid == 0:
            return pos
        grid = self.height*self.grid/2
        x_offset = (self.width-self.height)/2
        return (grid*round((pos[0]-x_offset)/grid)+x_offset, grid*round(pos[1]/grid))

    def get_object_time(self, object):
        if object['type'] == 'circle' or object['type'] == 'track':
            return object['start_time']
        elif object['type'] == 'arc':
            return object['start_time']+object['lifespan']
        return -1

    def save(self):
        for field in self.fields:
            self.map[field.label] = field.get_value()

        if 'objects' in self.map:
            self.map['objects'].sort(key=self.get_object_time)

        with open(self.map_file_path, 'r+') as map_file:
            json.dump(self.map, map_file)

    def add_object(self, obj, time):
        if len(self.map['objects']) > 0:
            for i in range(len(self.map['objects'])):
                object = self.map['objects'][i]
                if (object['start_time'] == time or object['start_time'] == obj['start_time']) and object['type'] == obj['type']:
                    self.map['objects'][i] == obj
                    return
                if object['type'] == 'circle' and object['start_time'] > time:
                    break
                if object['type'] == 'track' and object['start_time'] > time:
                    break
                if object['type'] == 'arc' and object['start_time']+object['lifespan'] > time:
                    break
            self.map['objects'].insert(i+1, obj)
        else:
            self.map['objects'].append(obj)

    def del_object(self, index):
        if len(self.map['objects']) > index:
            deleted = self.map['objects'].pop(index)
            if deleted['type'] == 'circle':
                keep_objects = []
                for i, object in enumerate(self.map['objects']):
                    if not(object['type'] == 'arc' and object['start_time']+object['lifespan'] >= deleted['start_time'] and object['start_time'] < deleted['end_time'] and object['radius'] == deleted['radius'] and object['pos'] == deleted['pos']):
                        keep_objects.append(object)
                self.map['objects'] = keep_objects

    def del_near_cursor_side(self):
        pos = pygame.mouse.get_pos()
        for object in self.side_objects:
            if object['type'] == 'circle' and Vector2(pos).distance_to(object['pos']) <= object['radius']:
                self.del_object(object['index'])
            elif object['type'] == 'arc' and abs(pos[1]-object['a'][1]) <= 2 and object['a'][0] < pos[0] < object['b'][0]:
                self.del_object(object['index'])
            elif object['type'] == 'track' and abs(pos[0]-object['a'][0]) <= 2 and object['a'][1] < pos[1] < object['b'][1]:
                self.del_object(object['index'])

    def get_circle_color(self, index):
        count = 0
        for i, object in enumerate(self.map['objects']):
            if i == index:
                break
            if object['type'] == 'circle':
                count += 1
        return get_color(count)

    def get_arc_color(self, index):
        object = self.map['objects'][index]
        for i, obj in enumerate(self.map['objects']):
            if obj['type'] == 'circle' and object['start_time']+object['lifespan'] >= obj['start_time'] and object['start_time'] < obj['end_time'] and object['radius'] == obj['radius'] and object['pos'] == obj['pos']:
                return self.get_circle_color(i)

        return pygame.Color('white')

    def get_pos(self):
        pos = round(self.play_pos)+pygame.mixer.music.get_pos()
        return max(0, pos)

    def bpm(self):
        if 'bpm' in self.map:
            return self.map['bpm']
        return 120

    def offset(self):
        if 'offset' in self.map:
            return self.map['offset']
        return 0

    def approach(self):
        return 200+float(self.map['approach'])*100

    def hit_window(self):
        return {'good': float(self.map['precision'])*5, 'ok': float(self.map['precision'])*10}

    def arc_width(self):
        return float(self.map['width'])/40

    def get_closest_arc_index(self):
        for i, object in reversed(list(enumerate(self.map['objects']))):
            if object['type'] == 'arc' and object['start_time']+object['lifespan'] < self.get_pos():
                return i
        return -1
    def get_closest_circle_index(self):
        possible_circles = []
        possible_indexes = []
        for i, object in enumerate(self.map['objects']):
            if object['type'] == 'circle' and object['start_time'] <= self.get_pos() and self.get_pos() <= object['end_time']:
                possible_circles.append(object)
                possible_indexes.append(i)
        if len(possible_circles) > 0:
            min_dist = 1000000
            min_index = 0
            for circle_index in range(len(possible_circles)):
                circle = possible_circles[circle_index]
                circle_pos = convert_pos(Vector2(circle['pos']), self.height)+Vector2((self.width-self.height)/2, 0)
                dist = abs(Vector2(pygame.mouse.get_pos()).distance_to(circle_pos)-convert_scalar(circle['radius'], self.height))
                if dist < min_dist:
                    min_dist = dist
                    min_index = circle_index
            return possible_indexes[min_index]
        return -1
    def update(self, events):
        for field in self.fields:
            field.update(events)
        pressed_keys = pygame.key.get_pressed()
        for event in events:
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                    x_end = (self.width+self.height)/2 + (self.width-self.height)/8
                    if x_end<event.pos[0]<x_end+16:
                        playing = pygame.mixer.music.get_busy()
                        self.play_pos = self.duration*pygame.mouse.get_pos()[1]/self.height
                        pygame.mixer.music.play(start=self.play_pos/1000)
                        if not playing:
                            pygame.mixer.music.stop()
                elif event.button == 3:
                    self.del_near_cursor_side()
                elif event.button == 4 or event.button == 5:
                    playing = pygame.mixer.music.get_busy()
                    mspb = 60000/float(self.bpm())
                    min_offset = float(self.offset())%mspb
                    mspb /= self.divisor
                    music_pos = self.get_pos()
                    if event.button == 4:
                        self.play_pos = max(0, min_offset+mspb*round((music_pos-min_offset)/mspb)-mspb)
                        pygame.mixer.music.play(start=self.play_pos/1000)
                        if not playing:
                            pygame.mixer.music.stop()
                    if event.button == 5:
                        self.play_pos = min(self.duration, min_offset+mspb*round((music_pos-min_offset)/mspb)+mspb)
                        pygame.mixer.music.play(start=self.play_pos/1000)
                        if not playing:
                            pygame.mixer.music.stop()

                    self.last_hit = 0

            if event.type == MOUSEBUTTONUP:
                pass
            if event.type == MOUSEMOTION:
                if pygame.mouse.get_pressed()[2]:
                    self.del_near_cursor_side()
            if event.type == KEYDOWN:
                ctrl_pressed = pressed_keys[pygame.K_LCTRL] or \
                              pressed_keys[pygame.K_RCTRL]
                if event.key == K_w:
                    self.object_preview = {'type':'circle', 'pos':self.snap_pos(pygame.mouse.get_pos()), 'radius':0}
                if event.key == K_e:
                    arc_index = self.get_closest_arc_index()
                    if arc_index > -1:
                        arc = self.map['objects'][arc_index]
                        initial_pos = convert_pos(Vector2(arc['pos']), self.height)+Vector2((self.width-self.height)/2, 0)
                        self.object_preview = {'type':'track', 'radius':arc['radius'], 'initial_pos': initial_pos, 'start_time': arc['start_time']+arc['lifespan'], 'start_pos':tuple(initial_pos+Vector2(convert_scalar(arc['radius'], self.height), 0).rotate(-arc['angle'])), 'start_angle':-arc['angle']}
                if event.key == K_r:
                    circle_index = self.get_closest_circle_index()
                    if circle_index > -1:
                        circle = self.map['objects'][circle_index]
                        self.object_preview = {'type':'arc', 'pos':tuple(convert_pos(Vector2(circle['pos']), self.height)+Vector2((self.width-self.height)/2, 0)), 'radius':convert_scalar(circle['radius'], self.height)}
                if event.key == K_g:
                    if self.grid == 0:
                        self.grid = 0.1
                    else:
                        self.grid = 0
                if event.key == K_f:
                    circle_index = self.get_closest_circle_index()
                    if circle_index >= 0:
                        self.map['objects'][circle_index]['end_time'] = self.get_pos()
                if event.key == K_d:
                    circle_index = self.get_closest_circle_index()
                    if circle_index >= 0:
                        self.map['objects'][circle_index]['end_time'] = self.duration
                if event.key == K_s and ctrl_pressed:
                    self.save()
                if event.key == K_SPACE:
                    if pygame.mixer.music.get_busy():

                        mspb = 60000/float(self.bpm())
                        min_offset = float(self.offset())%mspb
                        mspb /= self.divisor

                        self.play_pos = max(0, min_offset+mspb*round((self.get_pos()-min_offset)/mspb))
                        pygame.mixer.music.play(start=self.play_pos/1000)
                        pygame.mixer.music.stop()
                    else:
                        pygame.mixer.music.play(start=self.play_pos/1000)
                if event.key == K_t:
                    if self.divisor == 4:
                        self.divisor = 6
                    else:
                        self.divisor = 4
                if event.key == K_p:
                    self.save()
                    self.next = PlayScene(self.map_file_path, start_pos=self.play_pos/1000, auto=ctrl_pressed)
            if event.type == KEYUP:
                if event.key == K_w:
                    circle_obj = self.object_preview
                    if circle_obj['radius'] > 0:
                        x, y = circle_obj['pos']
                        circle = {}
                        circle['type'] = 'circle'
                        circle['pos'] = ((x*2 - self.width) / self.height, (-y*2 + self.height) / self.height)
                        circle['radius'] = circle_obj['radius']*2/self.height
                        circle['appear_time'] = self.approach()
                        circle['start_time'] = self.get_pos()
                        circle['end_time'] = int(self.duration*1000)
                        self.add_object(circle, self.get_pos())

                if event.key == K_r:
                    if hasattr(self, 'object_preview'):
                        arc_obj = self.object_preview
                        x, y = arc_obj['pos']
                        arc = {}
                        arc['type'] = 'arc'
                        arc['pos'] = ((x*2 - self.width) / self.height, (-y*2 + self.height) / self.height)
                        arc['radius'] = arc_obj['radius']*2/self.height
                        arc['angle'] = -(360/(4*self.anglesnap))*round(((Vector2(x, y)-Vector2(pygame.mouse.get_pos())).as_polar()[1])/(360/(4*self.anglesnap)))+180
                        arc['arc'] = math.degrees(2*self.arc_width()/arc['radius'])
                        arc['lifespan'] = self.approach()
                        arc['start_time'] = self.get_pos()-arc['lifespan']
                        self.add_object(arc, self.get_pos())
                if event.key == K_e:
                    if hasattr(self, 'object_preview'):
                        start_pos = Vector2(self.object_preview['start_pos'])
                        start_angle = (self.object_preview['start_angle'])%360
                        initial_pos = Vector2(self.object_preview['initial_pos'])
                        closest_circle = self.map['objects'][self.get_closest_circle_index()]
                        circle_pos = convert_pos(Vector2(closest_circle['pos']), self.height)+Vector2((self.width-self.height)/2, 0)
                        end_angle = (360/(4*self.anglesnap))*round(((Vector2(pygame.mouse.get_pos())-circle_pos).as_polar()[1])/(360/(4*self.anglesnap)))
                        # end_pos = circle_pos+Vector2(convert_scalar(closest_circle['radius'], self.height), 0).rotate(end_angle)
                        end_pos = circle_pos
                        interfere_arc = False
                        for object in self.map['objects']:
                            if object['type'] == 'arc' and object['start_time']+object['lifespan'] == self.get_pos():
                                interfere_arc = True
                                break
                            elif object['start_time'] > self.get_pos():
                                break
                        if not interfere_arc and start_angle%180 != end_angle%180:
                            track = {}
                            track['type'] = 'track'
                            # track['start_pos'] = ((start_pos.x*2 - self.width) / self.height, (-start_pos.y*2 + self.height) / self.height)
                            track['start_pos'] = ((initial_pos.x*2 - self.width) / self.height, (-initial_pos.y*2 + self.height) / self.height)
                            track['start_angle'] = start_angle
                            track['end_pos'] = ((end_pos.x*2 - self.width) / self.height, (-end_pos.y*2 + self.height) / self.height)
                            track['end_angle'] = end_angle
                            track['start_time'] = self.object_preview['start_time']
                            track['end_time'] = self.get_pos()
                            track['appear_time'] = self.approach()
                            track['width'] = self.object_preview['radius']
                            self.add_object(track, self.get_pos())

                if hasattr(self, 'object_preview'):
                    del self.object_preview

        self.side_objects = []

    def render(self, screen):
        pos = self.get_pos()

        playfield = pygame.Surface((internal_res, internal_res))

        for field in self.fields:
            field.render(screen)

        mspb = 60000/float(self.bpm())
        min_offset = float(self.offset())%mspb

        num_beats = self.height//beat_size

        x_start = (self.width+self.height)/2
        x_end = (self.width+self.height)/2 + (self.width-self.height)/8

        y_mid = self.height/2
        for i in range(-num_beats, num_beats):
            y_offset = y_mid + i * beat_size - beat_size*((pos-min_offset)%mspb)/mspb

            pygame.draw.line(screen, pygame.Color('grey30'), (x_start, y_offset), (x_end, y_offset), 2)
            for j in range(1, self.divisor):
                pygame.draw.line(screen, pygame.Color('grey30'), (x_start, beat_size*j/self.divisor+y_offset), (x_end-(x_end-x_start)/2, beat_size*j/self.divisor+y_offset), 1)

        for i in range(-num_beats, num_beats):
            y_offset = y_mid + i * beat_size - beat_size*((pos-min_offset)%mspb)/mspb
            cur_beat = min_offset+mspb*math.floor((pos-min_offset)/mspb)+i*mspb

            for k in range(len(self.map['objects'])):
                cur_object = self.map['objects'][k]
                if cur_object['type'] == 'circle':
                    if cur_object['start_time'] >= cur_beat and cur_object['start_time'] < cur_beat+mspb:
                        # y_offset_offset = beat_size*round(self.divisor*((cur_object['start_time']-min_offset)%mspb)/mspb)/self.divisor
                        y_offset_offset = beat_size*((cur_object['start_time']-min_offset)%mspb)/mspb
                        circle_pos = ((x_start+x_end)/2, y_offset + y_offset_offset)
                        circle_radius = beat_size/(self.divisor*2)
                        pygame.draw.circle(screen, self.get_circle_color(k), circle_pos, circle_radius, 2)

                        self.side_objects.append({'type':'circle', 'pos':circle_pos, 'radius': circle_radius, 'index': k})

                elif cur_object['type'] == 'arc':
                    if cur_object['start_time']+cur_object['lifespan'] >= cur_beat and cur_object['start_time']+cur_object['lifespan'] < cur_beat+mspb:
                        # y_offset_offset = beat_size*round(self.divisor*((cur_object['start_time']+cur_object['lifespan']-min_offset)%mspb)/mspb)/self.divisor
                        y_offset_offset = beat_size*((cur_object['start_time']+cur_object['lifespan']-min_offset)%mspb)/mspb
                        point_a = (x_start, y_offset + y_offset_offset)
                        point_b = (x_end, y_offset + y_offset_offset)
                        pygame.draw.line(screen, self.get_arc_color(k), point_a, point_b, 2)

                        self.side_objects.append({'type':'arc', 'a':point_a, 'b':point_b, 'index': k})

                elif cur_object['type'] == 'track':
                    if cur_object['start_time'] >= cur_beat and cur_object['start_time'] < cur_beat+mspb:
                        # y_offset_offset = beat_size*round(self.divisor*((cur_object['start_time']-min_offset)%mspb)/mspb)/self.divisor
                        y_offset_offset = beat_size*((cur_object['start_time']-min_offset)%mspb)/mspb
                        line_length = beat_size*(cur_object['end_time']-cur_object['start_time'])/mspb

                        point_a = (x_start+(x_end-x_start)/2, y_offset + y_offset_offset)
                        point_b = (x_start+(x_end-x_start)/2, y_offset + y_offset_offset + line_length)
                        pygame.draw.line(screen, pygame.Color('white'), point_a, point_b, 2)

                        self.side_objects.append({'type':'track', 'a':point_a, 'b':point_b, 'index': k })

        pygame.draw.line(screen, pygame.Color('white'), (x_start, self.height/2), ((x_start+x_end)/2, self.height/2), 2)

        line_length = self.get_pos()*self.height/self.duration
        pygame.draw.rect(screen, pygame.Color('white'), (x_end, 0, 16, line_length), 0)
        pygame.draw.rect(screen, pygame.Color('white'), (x_end, 0, 16, self.height), 1)

        play_time = str(self.get_pos())+'/'+str(int(self.duration))
        play_time_surf = self.font.render(play_time, True, pygame.Color('white'))
        screen.blit(play_time_surf, (self.width-play_time_surf.get_width()-font_size, font_size))

        if self.grid > 0:
            for i in range(1, int(2/self.grid)):
                pygame.draw.line(playfield, pygame.Color('grey30'), (0, internal_res*i/(2/self.grid)), (internal_res, internal_res*i/(2/self.grid)), 1)
                pygame.draw.line(playfield, pygame.Color('grey30'), (internal_res*i/(2/self.grid), 0), (internal_res*i/(2/self.grid), internal_res), 1)

        for i, object in enumerate(self.map['objects']):
            if object['type'] == 'circle' and object['start_time']-object['appear_time'] <= pos < object['end_time']+object['appear_time']:
                obj = Circle(object['pos'], object['radius'], object['appear_time'], object['start_time'], object['end_time'], self.get_circle_color(i))
                obj.update(pos, True)
                if obj.alive:
                    if self.anglesnap > 0:
                        circle_pos = convert_pos(Vector2(obj.pos), internal_res)
                        circle_rad = convert_scalar(obj.radius, internal_res)
                        for i in range(4*self.anglesnap):
                            pygame.draw.line(playfield, pygame.Color('grey30'), circle_pos, circle_pos+Vector2(circle_rad, 0).rotate(360*i/(self.anglesnap*4)), 1)
                    obj.render(playfield)
            elif object['type'] == 'arc' and object['start_time'] <= pos < object['start_time']+self.approach():
                obj = Arc(object['pos'], object['angle'], object['arc'], object['radius']+arc_distance, object['radius'], object['start_time'], self.approach(), self.hit_window(), self.get_arc_color(i))
                obj.update(pos, True)

                if pygame.mixer.music.get_busy() and abs(obj.start_time+obj.lifespan - self.get_pos()) < map_hit and abs(self.get_pos() - self.last_hit) > map_hit*2:
                    pygame.mixer.Sound.play(hit_sound)
                    self.last_hit = self.get_pos()

                if obj.alive:
                    obj.render(playfield)

            elif object['type'] == 'track' and object['start_time']-object['appear_time'] <= pos < object['end_time']:
                obj = Track(object['start_time'], object['appear_time'], object['end_time'], object['start_pos'], object['start_angle'], object['end_pos'], object['end_angle'], object['width'])
                obj.update(pos, True)

                if pygame.mixer.music.get_busy() and abs(obj.end_time - self.get_pos()) < map_hit and abs(self.get_pos() - self.last_hit) > map_hit*2:
                    pygame.mixer.Sound.play(hit_sound)
                    self.last_hit = self.get_pos()

                if obj.alive:
                    obj.render(playfield)

        screen.blit(pygame.transform.scale(playfield, (self.height, self.height)), ((self.width-self.height)/2, 0))

        if hasattr(self, 'object_preview'):
            if self.object_preview['type'] == 'circle':
                x, y = pygame.mouse.get_pos()
                if hasattr(self, 'object_preview'):
                    if self.object_preview['type'] == 'circle':
                        x2, y2 = self.object_preview['pos']
                        self.object_preview['radius'] = self.snap(math.sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2)))
                pygame.draw.circle(screen, pygame.Color('white'), self.object_preview['pos'], self.object_preview['radius'], 2)
            if self.object_preview['type'] == 'arc':
                pos = self.object_preview['pos']
                rad = self.object_preview['radius']
                angle = (360/(4*self.anglesnap))*round(((Vector2(pos)-Vector2(pygame.mouse.get_pos())).as_polar()[1])/(360/(4*self.anglesnap)))+180
                arc_size = math.degrees((self.height*self.arc_width())/rad)
                point_a = Vector2(pos)+Vector2(rad, 0).rotate(angle-arc_size/2)
                point_b = Vector2(pos)+Vector2(rad, 0).rotate(angle+arc_size/2)
                pygame.draw.line(screen, pygame.Color('white'), pos, point_a, 2)
                pygame.draw.line(screen, pygame.Color('white'), pos, point_b, 2)
                # pygame.draw.arc(screen, pygame.Color('white'), (pos[0]-rad, pos[1]-rad, rad*2, rad*2), angle-arc_size/2, angle+arc_size/2, 2)
            if self.object_preview['type'] == 'track':
                start_pos = Vector2(self.object_preview['start_pos'])
                start_angle = (self.object_preview['start_angle'])%360
                initial_pos = Vector2(self.object_preview['initial_pos'])
                closest_circle = self.map['objects'][self.get_closest_circle_index()]
                circle_pos = convert_pos(Vector2(closest_circle['pos']), self.height)+Vector2((self.width-self.height)/2, 0)
                end_angle = (360/(4*self.anglesnap))*round(((Vector2(pygame.mouse.get_pos())-circle_pos).as_polar()[1])/(360/(4*self.anglesnap)))
                # end_pos = circle_pos+Vector2(convert_scalar(closest_circle['radius'], self.height), 0).rotate(end_angle)
                end_pos = circle_pos

                if start_angle%180 != end_angle%180:
                    result = biarc_interpolator(initial_pos, start_angle, end_pos, end_angle)
                    if result:
                        if result['c1']:
                            a1a = round(180-(Vector2(result['c1'])-Vector2(result['pm'])).as_polar()[1])%360
                            a1b = round(180-(Vector2(result['c1'])-Vector2(initial_pos)).as_polar()[1])%360
                            # a1b = round(180-(Vector2(result['c1'])-Vector2(start_pos)).as_polar()[1])%360

                            anglediff = (a1a-a1b + 180) % 360 - 180
                            if anglediff > 0:
                                a1a, a1b = a1b, a1a

                            r1 = Vector2(result['pm']).distance_to(Vector2(result['c1']))

                        if result['c2']:
                            a2a = round(180-(Vector2(result['c2'])-Vector2(result['pm'])).as_polar()[1])%360
                            a2b = round(180-(Vector2(result['c2'])-Vector2(end_pos)).as_polar()[1])%360

                            anglediff = (a2a-a2b + 180) % 360 - 180
                            if anglediff > 0:
                                a2a, a2b = a2b, a2a

                            r2 = Vector2(result['pm']).distance_to(Vector2(result['c2']))

                        if result['c1']:
                            pygame.draw.arc(screen, pygame.Color('white'), (result['c1'][0]-r1, result['c1'][1]-r1, r1*2, r1*2), math.radians(a1a), math.radians(a1b), 2)
                        if result['c2']:
                            pygame.draw.arc(screen, pygame.Color('white'), (result['c2'][0]-r2, result['c2'][1]-r2, r2*2, r2*2), math.radians(a2a), math.radians(a2b), 2)

        pygame.draw.rect(screen, pygame.Color('white'), ((self.width-self.height)/2, 0, self.height, self.height), 4)

class PlayScene(BaseScene):
    def __init__(self, file_path, start_pos=0, auto = False):
        BaseScene.__init__(self)

        self.font = pygame.font.Font(resource_path(font_file), font_size)

        self.cur_pos = pygame.mouse.get_pos()
        self.last_pos = self.cur_pos
        self.mouse_trail = []

        self.darken_surf = pygame.Surface(cursor_trail.get_size())

        self.score = 0
        self.unmultiplied_score = 0
        self.max_score = 0
        self.combo = 0
        self.accuracy = 100

        self.objects = []
        self.width, self.height = pygame.display.get_surface().get_size()

        self.circle_count = 0

        self.file_path = file_path

        self.music_file_path = ''
        self.map_file_path = ''

        if file_path[-5:] == '.json':
            self.music_file_path = file_path[:-5]
            self.map_file_path = file_path
        else:
            self.music_file_path = file_path
            self.map_file_path = self.music_file_path+'.json'

        self.duration = pygame.mixer.Sound(self.music_file_path).get_length()*1000
        pygame.mixer.music.load(self.music_file_path)

        with open(self.map_file_path, 'r') as map_file:
            self.map = json.load(map_file)

        self.object_queue = self.map['objects']

        self.hit_indicators = []

        self.auto = auto
        if not auto:
            pygame.mouse.set_visible(False)
        else:
            self.cur_pos = Vector2(self.width/2, self.height/2)
            self.cur_vel = Vector2(0, 0)
            self.last_tick = pygame.time.get_ticks()
        self.song_pos = start_pos*1000
        self.start_pos = start_pos

        pygame.mixer.music.play(start=start_pos)

    def auto_cursor(self, tick):
        if len(self.objects) > 0:
            first_hit_object = None
            last_tick_time = pygame.time.get_ticks()-self.last_tick
            for i, object in enumerate(self.objects):
                if isinstance(object, Arc) or isinstance(object, Track):
                    first_hit_object = object
                    break
            if first_hit_object:
                hit_pos = None
                hit_time = None
                # r = 0
                track = False
                if isinstance(first_hit_object, Arc):
                    center_pos = Vector2((self.width-self.height)/2, 0)+convert_pos(first_hit_object.pos, self.height)
                    r = convert_scalar(first_hit_object.end_radius, self.height)
                    hit_pos = center_pos+Vector2(r, 0).rotate(-first_hit_object.angle)
                    hit_time = first_hit_object.start_time+first_hit_object.lifespan
                else:
                    hit_pos = first_hit_object.circle_pos/internal_res
                    hit_pos = hit_pos*self.height+Vector2((self.width-self.height)/2, 0)
                    # r = first_hit_object.circle_radius*self.height/internal_res

                    track = True

                    hit_time = 1+tick

                hit_time = abs(hit_time-tick)
                t = max(hit_time, 1)

                s = hit_pos - self.cur_pos

                if s.magnitude() > 1:
                    if track:
                        self.cur_vel = s/2
                    else:
                        s.scale_to_length(s.magnitude()+2)
                        self.cur_vel = s*last_tick_time/t

                # if s.magnitude() > 1:
                #     v = 2*(s-0.5*self.cur_vel*t)/t

                    # if track:
                    #     lerp_val = 0.5
                    # else:
                    #     lerp_val = 1-min(1, t/self.approach())
                    #     lerp_val = lerp_val*lerp_val/1.5

                    # self.cur_vel = self.cur_vel.lerp(v, lerp_val)
                    # self.cur_vel = v

            self.last_tick = pygame.time.get_ticks()

        self.cur_pos = self.cur_pos + self.cur_vel

        if self.cur_pos.x < (self.width-self.height)/2:
            self.cur_pos.x = (self.width-self.height)/2
            self.cur_vel = self.cur_vel.reflect(Vector2(1, 0))
            if self.cur_vel.magnitude() > 0:
                self.cur_vel.scale_to_length(math.sqrt(self.cur_vel.magnitude()))
        if self.cur_pos.x > (self.width+self.height)/2:
            self.cur_pos.x = (self.width+self.height)/2
            self.cur_vel = self.cur_vel.reflect(Vector2(1, 0))
            if self.cur_vel.magnitude() > 0:
                self.cur_vel.scale_to_length(math.sqrt(self.cur_vel.magnitude()))
        if self.cur_pos.y < 0:
            self.cur_pos.y = 0
            self.cur_vel = self.cur_vel.reflect(Vector2(0, 1))
            if self.cur_vel.magnitude() > 0:
                self.cur_vel.scale_to_length(math.sqrt(self.cur_vel.magnitude()))
        if self.cur_pos.y > self.height-1:
            self.cur_pos.y = self.height-1
            self.cur_vel = self.cur_vel.reflect(Vector2(0, 1))
            if self.cur_vel.magnitude() > 0:
                self.cur_vel.scale_to_length(math.sqrt(self.cur_vel.magnitude()))

    def bpm(self):
        if 'bpm' in self.map:
            return float(self.map['bpm'])
        return 120

    def offset(self):
        if 'offset' in self.map:
            return float(self.map['offset'])
        return 0

    def get_circle_color(self):
        col = get_color(self.circle_count)
        self.circle_count += 1
        return col

    def get_arc_color(self, object):
        for obj in self.objects:
            if isinstance(obj, Circle) and object['start_time']+object['lifespan'] >= obj.start_time and object['start_time'] < obj.end_time and object['radius'] == obj.radius and object['pos'] == obj.pos:
                return obj.color
        return pygame.Color('white')

    def approach(self):
        return 200+float(self.map['approach'])*100

    def hit_window(self):
        return {'good': float(self.map['precision'])*5, 'ok': float(self.map['precision'])*10}

    def arc_width(self):
        return float(self.map['width'])/40

    def end_play(self):
        pygame.mouse.set_visible(True)

    def break_combo(self):
        self.combo = 0

    def skip_to(self, song_pos):
        self.song_pos = song_pos
        pygame.mixer.music.play(start=self.song_pos/1000)

    def update_stats(self, result):
        if result > 0:
            self.score += result*min(self.combo, 100)
            self.unmultiplied_score += result
            self.max_score += 100
            self.accuracy = round(10000*self.unmultiplied_score/self.max_score)/100
            self.combo += 1
        else:
            self.max_score += 100
            self.accuracy = round(10000*self.unmultiplied_score/self.max_score)/100
            self.combo = 0

    def update(self, events):
        tick_time = self.song_pos + pygame.mixer.music.get_pos()

        # if not self.auto:
        #     self.cur_pos = pygame.mouse.get_pos()
        # else:
        #     self.auto_cursor(tick_time)
        if self.auto:
            self.auto_cursor(tick_time)

        # self.mouse_trail.append((tick_time, self.cur_pos))
        while len(self.mouse_trail) and self.mouse_trail[0][0] < tick_time - trail_time:
            self.mouse_trail.pop(0)

        if len(self.objects) == 0 and len(self.object_queue) == 0:
            self.next = MapScene(self.file_path, start_pos=self.start_pos)
            self.end_play()
            return

        if len(self.object_queue):
            object = self.object_queue[0]
            if object['type'] == 'circle' and object['start_time']-object['appear_time'] < tick_time:
                self.objects.append(Circle(object['pos'], object['radius'], object['appear_time'], object['start_time'], object['end_time'], self.get_circle_color()))
                self.object_queue.pop(0)
            elif object['type'] == 'arc' and object['start_time'] < tick_time:
                self.objects.append(Arc(object['pos'], object['angle'], object['arc'], object['radius']+arc_distance, object['radius'], object['start_time'], self.approach(), self.hit_window(), self.get_arc_color(object)))
                self.object_queue.pop(0)
            elif object['type'] == 'track' and object['start_time']-object['appear_time'] < tick_time:
                self.objects.append(Track(object['start_time'], object['appear_time'], object['end_time'], object['start_pos'], object['start_angle'], object['end_pos'], object['end_angle'], object['width']))
                self.object_queue.pop(0)

        for event in events:
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    self.next = MapScene(self.file_path, start_pos=self.start_pos)
                    self.end_play()
                if event.key == K_SPACE:
                    if len(self.object_queue):
                        skip_time = self.object_queue[0]['start_time']-60000*4/self.bpm()
                        if tick_time < skip_time:
                            self.skip_to(skip_time)

            if event.type == MOUSEMOTION:
                self.mouse_trail.append((tick_time, event.pos))
                self.cur_pos = event.pos
        keep_objects = []
        first_obj = True
        cur_pos = ((self.cur_pos[0]*2-self.width)/self.height, -(self.cur_pos[1]*2-self.height)/self.height)

        for object in self.objects:
            object.update(tick_time)
            if isinstance(object, Arc) and first_obj:
                if self.last_pos != self.cur_pos:
                    last_pos = ((self.last_pos[0]*2-self.width)/self.height, -(self.last_pos[1]*2-self.height)/self.height)
                    self.last_pos = self.cur_pos
                    result = object.check_hit([last_pos, cur_pos])
                    if result > 0:
                        self.hit_indicators.append(HitIndicator(cur_pos, result, tick_time))
                        pygame.mixer.Sound.play(hit_sound)
                        self.update_stats(result)
                first_obj = False
            elif isinstance(object, Track) and first_obj:
                result = object.check_hit(cur_pos)
                if result < 0:
                    self.break_combo()
                elif object.hit_time == 0:
                    object.hit_time = tick_time
                elif not object.alive:
                    pygame.mixer.Sound.play(hit_sound)

                first_obj = False

            if object.alive:
                keep_objects.append(object)
            elif isinstance(object, Arc) and object.ttl <= -object.hit_window['ok']:
                self.update_stats(-1)
                self.hit_indicators.append(HitIndicator(object.pos+Vector2(object.end_radius, 0).rotate(object.angle), -1, tick_time))

        self.objects = keep_objects

        keep_indicators = []
        for object in self.hit_indicators:
            object.update(tick_time)
            if object.ttl > 0:
                keep_indicators.append(object)

        self.hit_indicators = keep_indicators

    def render(self, screen):
        playfield = pygame.Surface((internal_res, internal_res))

        for object in self.objects:
            object.render(playfield)

        for object in self.hit_indicators:
            object.render(playfield)

        screen.blit(pygame.transform.scale(playfield, (self.height, self.height)), ((self.width-self.height)/2, 0))

        pygame.draw.rect(screen, pygame.Color('white'), ((self.width-self.height)/2, 0, self.height, self.height), 4)

        score_surf = self.font.render(str(self.score), True, pygame.Color('white'))
        acc_surf = self.font.render(str(self.accuracy)+'%', True, pygame.Color('white'))
        combo_surf = self.font.render(str(self.combo)+'x', True, pygame.Color('white'))

        x_pos = (self.width+self.height)/2 - font_size/2
        screen.blit(score_surf, (x_pos-score_surf.get_width(), font_size/2))
        screen.blit(acc_surf, (x_pos-acc_surf.get_width(), font_size/2+font_size))
        screen.blit(combo_surf, (x_pos-combo_surf.get_width(), font_size/2+font_size*2))

        for i in range(1, len(self.mouse_trail)):
            pos_a = Vector2(self.mouse_trail[i-1][1])
            pos_b = Vector2(self.mouse_trail[i][1])
            cur_pos_time = self.mouse_trail[i][0]
            last_pos_time = self.mouse_trail[-1][0]

            copy_surf = cursor_trail.copy()
            self.darken_surf.set_alpha(255*(last_pos_time-cur_pos_time)/trail_time)
            copy_surf.blit(self.darken_surf, (0, 0))

            pos_dist = pos_a.distance_to(pos_b)
            if pos_dist > 0:
                for j in range(math.floor(pos_dist)):
                    lerp_pos = pos_a.lerp(pos_b, j/pos_dist)
                    screen.blit(copy_surf, lerp_pos-Vector2(copy_surf.get_size())/2, special_flags=BLEND_MAX)

        screen.blit(cursor_surf, Vector2(self.cur_pos)-Vector2(cursor_surf.get_size())/2)


# import sys; input = sys.stdin.readline
N, M = map(int, input().split())
wok = list(map(int, input().split()))
wok_set = [False for _ in range(10001)]
for i in range(M):
    wok_set[wok[i]] = True
    for j in range(i + 1, M):
        if wok[i] + wok[j] > N: continue
        wok_set[wok[i] + wok[j]] = True
INF = 1234567890
memo = [INF for _ in range(10001)]
memo[0] = 0
for i in range(N + 1):
    if wok_set[i]:
        memo[i] = 1;
for i in range(N):
    for k in range(N):
        if not wok_set[k]: continue
        if i + k > N: continue
        memo[i + k] = min(memo[i] + 1, memo[i + k])

if INF == memo[N]:
    print(-1)
else:
    print(memo[N])


class bank:
    bank_name="sbk"

    def __init__ (self,accno,person_name,balance):
        self.accno=accno
        self.person_name=person_name
        self.balance=balance

    def deposit(self,amount):
        self.balance+=amount
        print(self.bank_name,"your account",self.accno,"has been credited with",amount,"you balance is",self.balance)

    def withdraw(self,amount):
        if amount>self.balance:
            print("insufficient balane")
        else:

            self.balance-=amount
            print("your account",self.accno,"has been withdrwaed with",amount,"you balance is",self.balance)

    def balnceenq(self):
        print("current balance",self.balance)

obj=bank(1000,"ajay",2000)

obj.balnceenq()
obj.deposit(2000)
obj.withdraw((1000))
obj.balnceenq()


import os, re

#2)Текстовый файл содержит записи о телефонах и их владельцах. Переписать в другой файл
#телефоны тех владельцев, фамилии которых начинаются с букв К и С.

def extract_k_c_names(input_file, output_file):
    '''Function reads all entries from the input file and writes to the output file phones of the people which surnames start from "K" or "C". '''

    phones = []

    template = re.compile(r'^(?P<phone>[0-9]+) (?P<surname>[cCkK][a-zA-Z]+)$')

    if not os.path.exists(input_file):
        raise FileNotFoundError('File does not exist.')

    with open(input_file, 'r+') as file:
        for line in file:
            entry = template.search(line)
            if entry:
                phones.append(entry.group('surname') + '\n')

    with open(output_file, 'w') as file:
        file.writelines(phones)

if __name__ == '__main__':
    extract_k_c_names('entries.txt', 'phones.txt')


# for spawning berries on tree
# can be clicked on to be collected -> inventory
# used to feed birds to have them fly off-screen

import pygame
import random

from interaction import *

class Berry(Interactable):
    def __init__(self, world, x, y):
        super().__init__()
        self.radius = 5
        self.world = world
        self.x, self.y = x, y
        self.rect = pygame.Rect(x - self.radius, y - self.radius,
                                2 * self.radius, 2 * self.radius)
        self.image = pygame.Surface((2 * self.radius, 2 * self.radius),
                                    pygame.SRCALPHA)
        self.image = self.image.convert_alpha()
        self.color = [(28, 59, 163, 190),
                      (230, 57, 57, 190),
                      (255, 156, 207, 190),
                      (255, 145, 0, 190),
                      (0, 138, 145, 190),
                      (0, 148, 222, 190),
                      (191, 34, 134, 190)]  # color options
        pygame.draw.circle(self.image, random.choice(self.color),
                           (self.radius, self.radius), self.radius)
        self.click = True
        self.priority = -1
        world.interactionHandler.register(self)

    def onClick(self, x, y):
        self.world.inventory.berryAmount += 1
        self.kill()
        self.world.ui.inventoryView.updateAmounts()

    def getRect(self):
        self.rect = pygame.Rect(self.x - self.radius, self.y - self.radius,
                                2 * self.radius, 2 * self.radius)

    def update(self, screenWidth, screenHeight):
        self.getRect()


from proteus import *
from ls_p import *

timeIntegration = BackwardEuler_cfl
stepController  = Min_dt_controller

femSpaces = {0:basis}

massLumping       = False
conservativeFlux  = None
numericalFluxType = NCLS.NumericalFlux
subgridError      = NCLS.SubgridError(coefficients,nd)
shockCapturing    = NCLS.ShockCapturing(coefficients,nd,shockCapturingFactor=ls_shockCapturingFactor,lag=ls_lag_shockCapturing)

fullNewtonFlag  = True
multilevelNonlinearSolver = Newton
levelNonlinearSolver      = Newton

nonlinearSmoother = None
linearSmoother    = None

matrix = SparseMatrix

if useOldPETSc:
    multilevelLinearSolver = PETSc
    levelLinearSolver      = PETSc
else:
    multilevelLinearSolver = KSP_petsc4py
    levelLinearSolver      = KSP_petsc4py

if useSuperlu:
    multilevelLinearSolver = LU
    levelLinearSolver      = LU

linear_solver_options_prefix = 'ncls_'
levelNonlinearSolverConvergenceTest = 'r'
linearSolverConvergenceTest         = 'r-true'

tolFac = 0.0
linTolFac = 0.001
l_atol_res = 0.001*ls_nl_atol_res
nl_atol_res = ls_nl_atol_res
useEisenstatWalker = False

maxNonlinearIts = 100 # 50 Farhad
maxLineSearches = 0


import matplotlib.pyplot as plt
import scipy.stats as ss
import seaborn as sns
import pandas as pd
import numpy as np

from sklearn.metrics import confusion_matrix, classification_report,ConfusionMatrixDisplay
from sklearn.experimental import enable_iterative_imputer
from sklearn.linear_model import LogisticRegression
from sklearn.impute import IterativeImputer
from sklearn.impute import KNNImputer

def ModelHist_plot(hist,plot1,plot2,leg1,leg2):
    plt.figure()
    plt.plot(hist.history[plot1])
    plt.plot(hist.history[plot2])
    plt.legend([leg1, leg2])
    plt.show()

def Time_eval(y_test,predicted,xlab='Time',ylab='Data',leg=['Actual', 'Predicted']):
    plt.figure()
    plt.plot(y_test,color='red')
    plt.plot(predicted,color='blue')
    plt.xlabel(xlab)
    plt.ylabel(ylab)
    plt.legend(leg)
    plt.show()

def Time_eval_inverse(mms,y_test,predicted,xlab='Time',ylab='Data',leg=['Actual', 'Predicted']):
    actual_price = mms.inverse_transform(y_test)
    predicted_price = mms.inverse_transform(predicted)
    plt.figure()
    plt.plot(y_test,color='red')
    plt.plot(predicted,color='blue')
    plt.xlabel(xlab)
    plt.ylabel(ylab)
    plt.legend(leg)
    plt.show()

def subplot(df1,df2,col,title1,title2,ylab,xlab1,xlab2):
    fig = plt.figure(figsize=[15,8])
    ax1=fig.add_subplot(2,1,1)
    ax2=fig.add_subplot(2,1,2)
    df1[col].plot.line(ax=ax1)
    df2[col].plot.line(ax=ax2)
    ax1.set_title(title1)
    ax1.set_ylabel(ylab)
    ax1.set_xlabel(xlab1)
    ax2.set_title(title2)
    ax2.set_ylabel(ylab)
    ax2.set_xlabel(xlab2)
    plt.tight_layout()
    plt.show()


from colorama import Style
from typing import Iterable, List, Tuple
import numpy as np
from sys import argv

class CaveFloor:
    def __init__(self, starting_energy: List[List[int]]):
        starting_energy = np.array(starting_energy)
        self.octopus_matrix = np.vectorize(Octopus)(starting_energy)

        h, w = self.octopus_matrix.shape

        def neighbours(p: Tuple[int, int]) -> Iterable[Tuple[int, int]]:
            y, x = p
            return (y-1, x-1), (y-1, x), (y-1, x+1), (y, x-1), (y, x+1), (y+1, x-1), (y+1, x), (y+1, x+1)

        def in_bounds(p):
            y, x = p
            return 0 <= y < h and 0 <= x < w

        for p in np.ndindex(self.octopus_matrix.shape):
            o = self.octopus_matrix[p]
            for n in filter(in_bounds, neighbours(p)):
                o.neighbours.append(self.octopus_matrix[n])

        self.octopuses: List[Octopus] = list(self.octopus_matrix.reshape(100))
        self.steps = 0

    def step(self, n: int = 1):
        for _ in range(n):
            for o in self.octopuses:
                o.increment()
            for o in self.octopuses:
                o.finalize()
            self.steps += 1

    def step_until_syncronized(self):
        while any(o.energy != 0 for o in self.octopuses):
            self.step()

    @property
    def flash_count(self) -> int:
        return sum(o.flash_count for o in self.octopuses)

class Octopus:
    def __init__(self, starting_energy: int):
        self.energy = starting_energy
        self.neighbours = []
        self.just_flashed = False
        self.flash_count = 0

    def __repr__(self) -> str:
        return f'Octopus({self.energy}, {len(self.neighbours)})'

    def finalize(self):
        self.just_flashed = False

    def increment(self):
        if not self.just_flashed:
            self.energy += 1
            if self.energy > 9:
                self.just_flashed = True
                self.flash_count += 1
                self.energy = 0
                for n in self.neighbours:
                    n.increment()

f = CaveFloor([[int(n) for n in l.strip()] for l in open(argv[1])])

f.step(100)
print(f'total flashes in 100 steps: {Style.BRIGHT}{f.flash_count}{Style.RESET_ALL}')

f.step_until_syncronized()
print(f'first syncronized step: {Style.BRIGHT}{f.steps}{Style.RESET_ALL}')


import time
import RPS_Template
import random

def get_perdiction():
    '''
    get_prediction() finds the predicted class and the confidence level of the prediction.
    It returns the predicted class and the confidence level.
    It calculates the time that has passed since the script started and prints the predicted class and the confidence level when the time has passed
    '''
    predictions = RPS_Template.prediction # predictions is a list of lists with the probabilities of each class
    predicted_class = RPS_Template.predicted_class # predicted_class is the class with the highest probability
    confidence_level = predictions[0][predicted_class] # confidence_level is the probability of the predicted_class
    start_time = time.time() # start_time is the time the script started
    while True:
        if time.time() - start_time >= 5:  # if 5 seconds have passed since the start time of the script
            print(
                f"you chose {predicted_class} with a confidence of {confidence_level}")
            break
    return predicted_class, confidence_level

computer_wins = 0
user_wins = 0
rounds = 0

while computer_wins < 3 or user_wins < 3:  # use while True: to keep playing
    if computer_wins == 3 or user_wins == 3:
        break
    else:
        if computer_wins < 3 or user_wins < 3:
            rounds += 1
            print(f"Round {rounds}")
            computer_choice = random.choice(['Rock', 'Paper', 'Scissors'])
            user_choice, confidence_level = get_perdiction()
            print(f"Computer chose {computer_choice}")
            print(
                f"User chose {user_choice} with a confidence of {confidence_level}")
            if computer_choice == user_choice:
                print("It's a tie!")
            elif computer_choice == "Rock":
                if user_choice == "Paper":
                    print("User wins!")
                    user_wins += 1
                else:
                    print("Computer wins!")
                    computer_wins = computer_wins + 1
            elif computer_choice == "Paper":
                if user_choice == "Scissors":
                    print("User wins!")
                    user_wins += 1
                else:
                    print("Computer wins!")
                    computer_wins += 1
            elif computer_choice == "Scissors":
                if user_choice == "Rock":
                    print("User wins!")
                    user_wins += 1
                else:
                    print("Computer wins!")
                    computer_wins += 1
            print(f"Computer wins: {computer_wins}")
            print(f"User wins: {user_wins}")
            print("")


from random import random, randrange, randint
import time
from colorama import init

init()
from colorama import Fore, Back, Style
"""
Description 3
"""

class Plant:
    def __init__(self, grow=0, life=100, size=0, name=None):
        self.life = life
        self.size = size
        self.name = name
        self.grow = grow

    def give_damage(self):
        if self.life > 50:
            return (randint(0, 40))
        elif self.life < 50:
            return (randint(0, 30))

    def get_damage(self, damage):
        self.life -= damage
        return self.life

class Insects:
    def __init__(self, life=10, name=None):
        self.life = life
        self.name = name

    def give_damage(self):
        if self.life > 50:
            return (randint(0, 40))
        elif self.life < 50:
            return (randint(0, 30))

    def get_damage(self, damage):
        self.life -= int(damage)

if __name__ == '__main__':
    chamomile = Plant(size=0, name='цветок', life=100)
    caterpillar = Insects(life=100, name='гусеница')

    while chamomile.size != 100:
        if chamomile.size < 100:
            chamomile.size = chamomile.size + 1
            time.sleep(0.005)
            print(f"растение выросло на {chamomile.size}%")
        else:
            print("2")

    while chamomile.life > 0 or caterpillar.life > 0:
        if caterpillar.life <= 0:
            print(Fore.LIGHTGREEN_EX + f"{chamomile.name} победил")
            time.sleep(2)
        else:
            time.sleep(2)
            caterpillar.get_damage(chamomile.give_damage())
            print(Fore.GREEN + f"жизни растения: {chamomile.life}")

        if chamomile.life <= 0:
            print(Fore.LIGHTRED_EX + f"{caterpillar.name} победила")
            time.sleep(2)
        else:
            time.sleep(2)
            chamomile.get_damage(caterpillar.give_damage())
            print(Fore.RED + f"жизни насекомого: {caterpillar.life}")


def maxIncreaseKeepingSkyline(grid):
    m, n = len(grid), len(grid[0])
    row_h , col_h = [], []
    for i in grid:
        row_h.append(max(i))
    for i in zip(*grid):
        col_h.append(max(i))
    res = 0
    for i in range(m):
        for j in range(n):
            cur = grid[i][j]
            target = min(row_h[i], col_h[j])
            res += target-cur
            # grid[i][j] = target
    return res

print(maxIncreaseKeepingSkyline(grid = [[3,0,8,4],[2,4,5,7],[9,2,6,3],[0,3,1,0]]))

807. 保持城市天际线.py

807. 保持城市天际线.py


import numpy

def createMedianMask(disparityMap, validDepthMask, rect = None):
    """Return a mask selecting the median layer, plus shadows."""
    if rect is not None:
        x, y, w, h = rect
        disparityMap = disparityMap[y:y+h, x:x+w]
        validDepthMask = validDepthMask[y:y+h, x:x+w]
    median = numpy.median(disparityMap)
    return numpy.where((validDepthMask == 0) | \
                       (abs(disparityMap - median) < 12),
                       255, 0).astype(numpy.uint8)


#!/usr/bin/python3
katc_letters = "aefhilmnorsxAEFHILMNORSX"
word = input("I will cheer for you! Enter a word: ")
times = int(input("How enthusiatic(1-10): "))
a = 0
while a < len(word):
    char = word[a]
    if char in katc_letters:
        print("Give me an " + char + "! " + char)
    else:
        print("Give me a " + char + "! " + char)
    a += 1
print("What does that spell?")
for a in range(times):
    print(word, "!!!")


import cv2
import numpy as np

def improve_image_with_median_filter(image_for_median):
    processed_image = cv2.medianBlur(image_for_median, 7)
    cv2.imshow('Median Filter', processed_image)
    cv2.imwrite('median_processed_image.png', processed_image)

def improve_image_with_mean_filter(image_for_mean):
    kernel = np.ones((5, 5), np.float32) / 25
    processed_image = cv2.filter2D(image_for_mean, -1, kernel)
    cv2.imshow('Mean Filter', processed_image)
    cv2.imwrite('mean_processed_image.png', processed_image)

def improve_image_with_erode(image_for_erode):
    kernel = np.ones((5, 5), np.uint8)
    processed_image = cv2.erode(image_for_erode, kernel, iterations=1)
    cv2.imshow('Erode Filter', processed_image)
    cv2.imwrite('erode_processed_image.png', processed_image)

image = cv2.imread('02.jpg')
dsize = (int(image.shape[1] * .5), int(image.shape[0] * .5))
shrunk_image = cv2.resize(image, dsize, interpolation=cv2.INTER_AREA)
cv2.namedWindow('Initial image', cv2.WINDOW_AUTOSIZE)
cv2.imshow('Initial image', shrunk_image)

improve_image_with_median_filter(shrunk_image)
improve_image_with_mean_filter(shrunk_image)
improve_image_with_erode(shrunk_image)

cv2.waitKey(0)
cv2.destroyAllWindows()


"""
The prime factors of 13195 are 5, 7, 13 and 29.

What is the largest prime factor of the number 600851475143 ?
"""
toFactorise = 600851475143
for a in range(2, toFactorise):
    while toFactorise % a == 0:
        toFactorise //= a
    if toFactorise == 1:
        print(a)
        break


import sys
import re
import logging
import tatsu
import ChallengerGrammar

EMPTYLINE = ""
NODELIM = None
SPACE = ' '
NEWLINE = '\n'

logger = logging.getLogger('root')
FORMAT = "%(filename)s:%(lineno)d:%(funcName)20s() : %(message)s"
logging.basicConfig(stream=sys.stderr, format=FORMAT, level=logging.INFO)

def tr(inS, i, s):
    return inS.translate(str.maketrans(i,s))

def CharIgnore(inp):
    return ""

class SingleBlock:
    def __init__(self):
        return

    def parse(self, inp):
        return inp

class OrBlock(SingleBlock):
    def __init__(self, parsers):
        self.parsers = parsers

        for p in self.parsers:
            if not issubclass(type(p), SingleBlock):
                raise TypeError("OrBlock all parsers must be SingleBlock")

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)

        self.value = None
        for p in self.parsers:
            try:
                self.value = p.parse(inp)
                break
            except:
                continue

        if self.value is None:
            raise Exception("No parsers for \"%s\"" % inp)

        return self.value

class LiteralBlock(SingleBlock):
    def __init__(self, parser, callback=None):
        self.parser = parser
        self.callback = callback

        if not callable(parser):
            raise TypeError("Literal parser must be callable")

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)

        if self.callback is not None:
            return self.callback(self.parser(inp))

        return self.parser(inp)

class LiteralNoParse(SingleBlock):
    def __init__(self, absolute=None):
        self.absolute = absolute
        return

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)
        if self.absolute is not None:
            if inp != self.absolute:
                raise ValueError("LiteralNoParse exact value expected not received")
        return None

class EncapsulatedLine(SingleBlock):
    def __init__(self, trimmer, block):
        self.trimmer = trimmer
        self.block = block

        if not callable(self.trimmer):
            raise TypeError("EncapsulatedLine requires callable trimmer")

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)
        inp = self.trimmer(inp)
        return self.block.parse(inp)

class MultiBlockLine(SingleBlock):
    def __init__(self, blocks, delimiter, callback=None):
        self.blocks = blocks
        self.delimiter = delimiter
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        for b in self.blocks:
            if not issubclass(type(b), SingleBlock):
                raise TypeError("MultiBlockLine must parse blocks")

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)
        self.items = []
        for (line, b) in zip(inp.split(self.delimiter), self.blocks):
            logging.debug("inp: \"%s\"" % inp)
            bout = b.parse(line)
            if bout is not None:
                self.items.append(bout)

        if len(self.items) == 1:
            self.items = self.items[0]

        if self.callback is not None:
                return self.callback(self.items)
        return self.items

class ListBlock(SingleBlock):
    def __init__(self, elementParser, delimiter, callback=None):
        self.elementParser = elementParser
        self.delimiter = delimiter
        self.callback = callback

        if not callable(elementParser):
            raise TypeError("List elementParser must be callable", elementParser)

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

    def parse(self, inp):
        self.list = []
        logging.debug("inp: \"%s\"" % inp)
        if self.delimiter is None:
            for i in inp:
                eparse = self.elementParser(i)
                if eparse is not None:
                    self.list.append(eparse)
        else:
            for i in inp.split(self.delimiter):
                eparse = self.elementParser(i)
                if eparse is not None:
                    self.list.append(eparse)

        if self.callback is not None:
            return self.callback(self.list)

        return self.list

GACCEPT = 1
GREJECT = 2
GCONTINUE = 3

class ListElementMunch(SingleBlock):
    def __init__(self, elementEvaluator, elementParser, delimiter, callback=None):
        self.elementEvaluator = elementEvaluator
        self.elementParser = elementParser
        self.delimiter = delimiter
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not callable(elementEvaluator):
            raise TypeError("List elementEvaluator must be callable")

        if not callable(elementParser):
            raise TypeError("List elementParser must be callable")

    def elementTester_helper(self, cand, remaining):
        if self.delimiter == None:
            delim = ''
        else:
            delim = self.delimiter

        cand.append(remaining.pop(0))
        eparse = self.elementParser(delim.join(cand))
        accept = self.elementEvaluator(eparse)
        if accept == GACCEPT:
            self.list.append(eparse)
            cand = []
        elif accept == GREJECT:
            # When rejecting we throw away the first character and continue
            # searching
            cand.pop(0)
            remaining[0:0] = cand

        return cand, remaining

    def parse(self, inp):
        self.list = []
        logging.debug("inp: \"%s\"" % inp)
        if self.delimiter is None:
            remaining = [c for c in inp]
        else:
            remaining = inp.split(self.delimiter)

        cand = []
        while len(remaining) > 0:
            cand, remaining = self.elementTester_helper(cand, remaining)

        if self.callback is not None:
            return self.callback(self.list)

        return self.list

class SetBlock(ListBlock):
    def __init__(self, elementParser, delimiter, callback=None):
        super().__init__(elementParser, delimiter, callback)

    def parse(self, inp):
        tlist = super().parse(inp)
        return set(tlist)

class HashPairBlock(SingleBlock):
    def __init__(self, keyblock, valueblock, seperator, distribute=False, reverse=False, callback=None):
        self.keyblock = keyblock
        self.valueblock = valueblock
        self.seperator = seperator
        self.distribute = distribute
        self.reverse = reverse
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not callable(self.keyblock) and \
            not isinstance(self.valueblock, SingleBlock):
            raise TypeError("Keyblock must be callable or SinglBlock")

        if not callable(self.valueblock) and \
            not isinstance(self.valueblock, SingleBlock):
            raise TypeError("Valueblock must be callable or SinglBlock")

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)
        if not self.reverse:
            key, value = inp.split(self.seperator)
        else:
            value, key = inp.split(self.seperator)

        if isinstance(self.keyblock, SingleBlock):
            self.key = self.keyblock.parse(key)
        else:
            self.key = self.keyblock(key)

        if isinstance(self.valueblock, SingleBlock):
            self.value = self.valueblock.parse(value)
        else:
            self.value = self.valueblock(value)

        self.hash = {}
        if self.distribute:
            for k in self.key:
                self.hash[k] = self.value
        else:
            self.hash[key] = self.value

        if self.callback is not None:
            return self.callback(self.hash)

        return self.hash

class HashLineBlock(SingleBlock):
    def __init__(self, hashparser, delimiter, callback=None):
        self.hashparser = hashparser
        self.delimiter = delimiter
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not isinstance(self.hashparser, HashPairBlock):
            raise TypeError("HashLineBuilder needs HashPairBlock")

    def parse(self, inp):
        logging.debug("inp: \"%s\"" % inp)
        self.hash = {}
        if self.delimiter is not None:
            for l in inp.split(self.delimiter):
                lh = self.hashparser.parse(l)
                if lh is not None:
                    self.hash.update(lh)
        else:
            lh = self.hashparser.parse(inp)
            if lh is not None:
                self.hash.update(lh)

        if self.callback is not None:
            return self.callback(self.hash)

        return self.hash

class MuiltiLineBlock:
    def __init__(self):
        return

    def parse(self, inp):
        return inp

class MultiLineSpanBuilder(MuiltiLineBlock):
    def __init__(self, lineblock, seperator, endvalue, callback=None):
        self.lineblock = lineblock
        self.seperator = seperator
        self.endvalue = endvalue
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not issubclass(type(self.lineblock), SingleBlock):
            raise TypeError("SingleLineBuilder needs SingleBlock to build")

    def parse(self, infile, incLine=""):
        logging.debug("inp: \"%s\"" % incLine)
        compositeline = incLine

        line = infile.readline().rstrip()
        while line != self.endvalue:
            compositeline += self.seperator + line
            logging.debug("inp: \"%s\"" % line)
            line = infile.readline().rstrip()

        logging.debug("inp: \"%s\"" % line)

        if self.callback is not None:
            return self.callback(self.lineblock.parse(compositeline))
        return self.lineblock.parse(compositeline)

class SingleLineBuilder(MuiltiLineBlock):
    def __init__(self, lineblock, callback=None):
        self.lineblock = lineblock
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not issubclass(type(self.lineblock), SingleBlock):
            raise TypeError("SingleLineBuilder needs SingleBlock to build")

    def parse(self, infile, intLine=None):
        if intLine == None:
            line = infile.readline().rstrip()
        else:
            line = intLine
        logging.debug("inp: \"%s\"" % line)

        if self.callback is not None:
            return self.callback(self.lineblock.parse(compositeline))
        return self.lineblock.parse(line)

class SingleLineBuilderThrowToEnd(SingleLineBuilder):
    def __init__(self, lineblock, endvalue, callback=None):
        super().__init__(lineblock, callback=callback)
        self.endvalue = endvalue
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

    def parse(self, infile, intLine=None):
        if intLine == None:
            line = infile.readline().rstrip()
        else:
            line = intLine
        logging.debug("inp: \"%s\"" % line)

        l = super().parse(infile)
        while infile.readline().rstrip() != self.endvalue:
            continue
        return l

class MultiBuilderBuilder(MuiltiLineBlock):
    def __init__(self, blocks, endvalue, callback=None):
        self.blocks = blocks
        self.endvalue = endvalue
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        for b in self.blocks:
            if not isinstance(b, MuiltiLineBlock) and \
                not isinstance(b, MultiLineSpanBuilder):
                raise TypeError("MultiBuilderBuilder combines several multiline blocks")

    def parse(self, infile, intLine=None):
        if intLine == None:
            line = infile.readline().rstrip()
        else:
            line = intLine
        logging.debug("inp: \"%s\"" % line)
        self.list = []
        while line != self.endvalue:
            blockOut = []
            for b in self.blocks:
                if isinstance(b, MultiLineSpanBuilder):
                    l = b.parse(infile, line)
                elif isinstance(b, MuiltiLineBlock):
                    l = b.parse(infile, line)
                else:
                    raise Exception("oops")

                if l is not None:
                    blockOut.append(l)

                line = infile.readline().rstrip()
                logging.debug("inp: \"%s\"" % line)
            self.list += blockOut

        if len(self.list) == 1:
            self.list = self.list[0]

        if self.callback is not None:
            return self.callback(self.list)
        return self.list

class ListBuilder(MuiltiLineBlock):
    def __init__(self, lineblock, endvalue, callback=None):
        self.lineblock = lineblock
        self.endvalue = endvalue
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not isinstance(self.lineblock, SingleBlock) and \
            not isinstance(self.lineblock, MuiltiLineBlock) and \
            not isinstance(self.lineblock, MultiLineSpanBuilder):
            raise TypeError("Listbuilder needs SingleBlock or MultiLineSpanBuilder got \"%s\"" % type(self.lineblock))

    def parse(self, infile, intLine=None):
        if intLine == None:
            line = infile.readline().rstrip()
        else:
            line = intLine
        logging.debug("inp: \"%s\"" % line)
        self.list = []
        while line != self.endvalue:
            if isinstance(self.lineblock, SingleBlock):
                l = self.lineblock.parse(line)
            elif isinstance(self.lineblock, MultiLineSpanBuilder):
                l = self.lineblock.parse(infile, line)
            elif isinstance(self.lineblock, MuiltiLineBlock):
                l = self.lineblock.parse(infile, line)
            else:
                raise Exception("oops")

            if l is not None:
                self.list.append(l)

            line = infile.readline().rstrip()
            logging.debug("inp: \"%s\"" % line)

        if len(self.list) == 1:
            self.list = self.list[0]

        if self.callback is not None:
            return self.callback(self.list)
        return self.list

class HashBuilder(MuiltiLineBlock):
    def __init__(self, hashblock, endvalue, callback=None):
        self.hashblock = hashblock
        self.endvalue = endvalue
        self.callback = callback

        if callback is not None and not callable(callback):
            raise TypeError("Callback must be callable")

        if not isinstance(self.hashblock, HashPairBlock) and \
            not isinstance(self.hashblock, HashLineBlock):
            raise TypeError("Hashbuilder needs HashPairBlock to build")

    def parse(self, infile, intLine=None):
        if intLine == None:
            line = infile.readline().rstrip()
        else:
            line = intLine

        logging.debug("inp: \"%s\"" % line)

        self.hash = {}
        while line != self.endvalue:
            lineH = self.hashblock.parse(line)

            self.hash.update(lineH)

            line = infile.readline().rstrip()

        if self.callback is not None:
            return self.callback(self.hash)
        return self.hash

class InputDefinition:
    def __init__(self):
        self.builders = []
        self.functions = {
            'int' : int,
            'str' : str }

    def buildersFromStr(self, stringDef):
        if stringDef is not None:
            self.stringDef = stringDef.split('\n')
            self.stridx = 0
            self.strParseRootBuilders()

    def strParseUnQuote(self, str):
        return str[1:-1]

    def strParseRootBuilders(self):
        while self.stridx < len(self.stringDef):
            self.addBuilder(self.strParseBuilder())

    def strParseBuilder_helper(self, ast):
        # At this point the AST is either a builder symbol, which can only be one item
        # For reasons I don't understand tatsu will return this as a none tuple
        # Any tuple is by definition not the start of a builder block, and those must be
        # of a length greater than 1
        if isinstance(ast, tuple) and len(ast) > 1:
            return SingleLineBuilder(self.strParseBlock(ast))
        elif ast == '((':
            return self.strParseMultiBuilderBuilder()
        elif ast == '[[':
            return self.strParseListBuilder()
        elif ast == '{{':
            return self.strParseHashBuilder()
        else:
            raise ValueError("Not a valid builder")

    def strParseBuilder(self):
        while self.stridx < len(self.stringDef):
            ast = tatsu.parse(ChallengerGrammar.GRAMMAR, self.stringDef[self.stridx])
            self.stridx += 1
            logging.debug("ast: \"%s\"" % str(ast))
            return self.strParseBuilder_helper(ast)

    def strParseBuilder_closehelper(self, ast):
        # There are 4 valid close forms for any builder:
        #   ')',
        #   (')', "delimiter")
        #   (')', '/',          callback)
        #   (')', "delimiter",  '/',        callback)
        # The first is handled directly, the ast passed here is only after the initial
        # close symbol, and assumes that is stripped

        if len(ast) == 1:
            # Can only have a delimiter:
            return self.strParseUnQuote(ast[0]), None
        elif len(ast) == 2:
            # Can only be a callback
            return None, self.functions[ast[1]]
        elif len(ast) == 3:
            # Both!
            return self.strParseUnQuote(ast[0]), self.functions[ast[2]]
        else:
            raise ValueError("Malformed builder close: \"%s\"" % (ast))

    def strParseMultiBuilderBuilder(self):
        builders = []
        while self.stridx < len(self.stringDef):
            ast = tatsu.parse(ChallengerGrammar.GRAMMAR, self.stringDef[self.stridx])
            self.stridx += 1
            logging.debug("ast: \"%s\"" % str(ast))
            # As above, the close is a str if it's a single close
            if isinstance(ast, str) and ast == '))':
                #Close this Multibuilder
                return MultiBuilderBuilder(builders, EMPTYLINE)
            elif isinstance(ast, tuple) and ast[0] == '))':
                delimiter, callback = self.strParseBuilder_closehelper(ast[1:])
                return MultiBuilderBuilder(builders, delimiter, callback)
            else:
                builders.append(self.strParseBuilder_helper(ast))

    def strParseListBuilder(self):
        builder = None
        while self.stridx < len(self.stringDef):
            ast = tatsu.parse(ChallengerGrammar.GRAMMAR, self.stringDef[self.stridx])
            self.stridx += 1
            logging.debug("ast: \"%s\"" % str(ast))
            # Same close forms as above, but with ']'
            if isinstance(ast, str) and ast == ']]':
                return ListBuilder(builder, EMPTYLINE)
            elif isinstance(ast, tuple) and ast[0] == ']]':
                delimiter, callback = self.strParseBuilder_closehelper(ast[1:])
                return ListBuilder(builder, delimiter, callback)
            else:
                if builder is not None:
                    raise ValueError("List Builder can only contain one element")
                builder = self.strParseBuilder_helper(ast)

    def strParseHashBuilder(self):
        builder = None
        while self.stridx < len(self.stringDef):
            ast = tatsu.parse(ChallengerGrammar.GRAMMAR, self.stringDef[self.stridx])
            self.stridx += 1
            logging.debug("ast: \"%s\"" % str(ast))
            # Same close forms as above
            if isinstance(ast, str) and ast == '}}':
                return HashBuilder(builder, EMPTYLINE)
            elif isinstance(ast, tuple) and ast[0] == '}}':
                delimiter, callback = self.strParseBuilder_closehelper(ast[1:])
                return HashBuilder(builder, delimiter, callback)
            else:
                # Hash builders can only contain hash builder type elements
                # This means that the blocks encountered must be either '{', '{*', or '{d'
                # All of these are multipls so will be a tuple
                if isinstance(ast, tuple) and len(ast) > 1 and \
                    (ast[0] == '{' or ast[0] == '{*' or ast[0] == '{d'):
                    if builder is not None:
                        raise ValueError("Hash Builder can only contain one element")
                    builder = self.strParseBlock(ast)
                else:
                    raise ValueError("Not a valid builder")

    def strParseBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))
        if ast[0] == '#':
            return self.strParseLiteralBlock(ast)
        elif ast[0] == '(':
            return self.strParseMultiBlockLine(ast)
        elif ast[0] == '[':
            return self.strParseListBlock(ast)
        elif ast[0] == '[*':
            return self.strParseListMunchBlock(ast)
        elif ast[0] == '[<':
            return self.strParseSetBlock(ast)
        elif ast[0] == '{':
            return self.strParseHashPairBlock(ast)
        elif ast[0] == '{*':
            return self.strParseHashLineBlock(ast)
        elif ast[0] == '{<':
            return self.strParseHashPairBlock(ast, True)
        elif ast[0] == 'or':
            return self.strParseOrBlock(ast)
        elif ast[0] == '>':
            return self.strParseEncapBlock(ast)
        else:
            raise ValueError("Not a valid block")

    def strParseLiteralBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))
        # Literals have 3 forms:
        # ('#', parserFunction, #')
        # ('#', "ExactMatch", '#')
        # ('#', '#')
        if len(ast) == 2:
            return LiteralNoParse()
        m = re.match(r"\"(.+)\"", ast[1])
        if m is not None:
            return LiteralNoParse(self.strParseUnQuote(ast[1]))
        else:
            return LiteralBlock(self.functions[ast[1]])

    def strParseTrailingArgs_helper(self, ast):
        # All List/Multi blocks varients have the form:
        #  ... "delimiter", ']')
        #  ... "delimiter", '/', callback ']')
        # To make this reusable, only the deliminator onwards is passed
        logging.debug("ast: \"%s\"" % str(ast))

        if len(ast) == 2:
            delimiter = ast[0]
            callback = None
        elif len(ast) == 4:
            delimiter = ast[0]
            callback = self.functions[ast[2]]

        if delimiter == "None":
            delimiter = None
        else:
            delimiter = self.strParseUnQuote(delimiter)

        return delimiter, callback

    def strParseMultiBlockLine(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))
        blocks = []
        # Multi blocks have the forms:
        #  ('(', [ (any block) ...], "delimiter", ')')
        #  ('(', [ (any block) ...], "delimiter", '/', callback ')')

        for b in ast[1]:
            blocks.append(self.strParseBlock(b))

        delimiter, callback = self.strParseTrailingArgs_helper(ast[2:])

        return MultiBlockLine(blocks, delimiter, callback)

    def strParseListBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))
        # List blocks have the forms:
        #  ('[', elementParser, "delimiter", ']')
        #  ('[', elementParser, "delimiter", '/', callback ']')

        elP = self.functions[ast[1]]

        delimiter, callback = self.strParseTrailingArgs_helper(ast[2:])

        return ListBlock(elP, delimiter, callback)

    def strParseSetBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))

        elP = self.functions[ast[1]]

        delimiter, callback = self.strParseTrailingArgs_helper(ast[2:])

        return SetBlock(elP, delimiter, callback)

    def strParseListMunchBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))

        # This block requires two functions
        # [* elementParser elementEvaluator "delimiter"...

        elP = self.functions[ast[1]]
        elEval = self.functions[ast[2]]

        delimiter, callback = self.strParseTrailingArgs_helper(ast[3:])

        return ListElementMunch(elEval, elP, delimiter, callback)

    def strParseHashTypeKV_helper(self, ast):
        # Hash types allow the key and value to also be blocks
        # So we pull those out of the ast and recursively parse them
        logging.debug("ast: \"%s\"" % str(ast))

        # Hash pair blocks take the form:
        #  ('{', [rev], keyparser|block, valueparser|block, "seperator", '}')
        #  ('{', [rev], keyparser|block, valueparser|block, "seperator", '\', callback, '}')
        # If key or value are blocks, then they will be nested tuples

        # Support the reversed type by detecting revesed, caller will pass enough
        # AST to not have to worry, this function slices the AST to after the KV objects

        if type(ast[0]) == str and ast[0] == "rev":
            astKey = ast[2]
            astValue = ast[1]
            astRemaining = ast[3:]
            reverse = True
        else:
            astKey = ast[0]
            astValue = ast[1]
            astRemaining = ast[2:]
            reverse = False

        if type(astKey) == tuple:
            key = self.strParseBlock(astKey)
        else:
            key = self.functions[astKey]

        if type(astValue) == tuple:
            value = self.strParseBlock(astValue)
        else:
            value = self.functions[astValue]

        return key, value, reverse, astRemaining

    def strParseHashPairBlock(self, ast, distribute=False):
        logging.debug("ast: \"%s\"" % str(ast))

        key, value, reverse, ast = self.strParseHashTypeKV_helper(ast[1:])
        # ast is replaces and is now align to after the kv pair

        # Incidentally the seperator and optional callback take the same form here
        # As in listblocks, so reuse to make life easier
        seperator, callback = self.strParseTrailingArgs_helper(ast)

        return HashPairBlock(key, value, seperator, distribute, reverse, callback)

    def strParseHashLineBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))

        # A HashLineBlock is almost identical to the HashPair, except that an additional
        # Seperator is added. The first is always the key/value seperator and the second
        # is always the item seperator. We reuse the trailing arg function as it will work

        key, value, reverse, ast = self.strParseHashTypeKV_helper(ast[1:])
        # ast is replaces and is now align to after the kv pair

        seperator = self.strParseUnQuote(ast[0])

        itemSeperator, callback = self.strParseTrailingArgs_helper(ast[1:])

        return HashLineBlock(HashPairBlock(key, value, seperator, reverse=reverse), itemSeperator, callback)

    def strParseOrBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))

        # OrBlocks are 'special', they are parsed to be left noted
        # i.e "block1 or block2" becomes ('or', block1, block2)
        # When ors nest, this pattern repeats, so "bl1 or bl2 or bl3"
        # becomes ('or', bl1, ('or', bl2, 'bl3'))
        # Though untested, this should automatically build recursive or's
        # Which is sub-optimal as it increases recursion depth (where the orblock
        # can hypothetically work on n blocks)

        blocks = []
        for b in ast[1:]:
            blocks.append(self.strParseBlock(b))

        return OrBlock(blocks)

    def strParseEncapBlock(self, ast):
        logging.debug("ast: \"%s\"" % str(ast))

        return EncapsulatedLine(self.functions[ast[2]], self.strParseBlock(ast[1]))

    def addBuilder(self, builder):
        if not issubclass(type(builder), MuiltiLineBlock):
            raise TypeError("Builders must be MuiltiLineBlocks, use SingleLineBuilder for 1 line")
        self.builders.append(builder)

    def addFunction(self, name, func):
        if not callable(func):
            raise TypeError("Parser functions must be callable")

        self.functions[name] = func

class Input:
    def __init__(self, infile, definition):
        self.infile = infile
        self.definition = definition

        if not isinstance(self.definition, InputDefinition):
            raise TypeError("InputDefinition required")

    def parse(self):
        if len(self.definition.builders) == 1:
            self.blockOut = self.definition.builders[0].parse(self.infile)
        else:
            self.blockOut = []
            for b in self.definition.builders:
                bout = b.parse(self.infile)
                if bout is not None:
                    self.blockOut.append(bout)

        return self.blockOut

    def retrieve(self):
        return self.blockOut


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This script calculates a climatology from a decade of interest from the output
of calculate_aragonite.py
@author: Noam Vogt-Vincent
"""

import numpy as np
import os
from netCDF4 import Dataset
from datetime import datetime

##############################################################################
# Parameters #################################################################
##############################################################################

# File name (without extension)
fh = 'TA-pCO2_HISTORICAL'

# Decade of interest
dec = 1850       # First year of the decade (i.e. 1850 = 1850-1859)
clim_length = 50 # Length of climatology (years, default 10)

##############################################################################
# Parameter processing #######################################################
##############################################################################

root_dir = os.path.dirname(os.path.realpath(__file__)) + '/../'
out_fh = (root_dir + 'processed_data/' + fh +'_' +
          str(dec) + '-' + str(dec+clim_length-1) + '_MonClim.nc')
fh = root_dir + 'processed_data/' + fh + '.nc'

with Dataset(fh, mode='r') as nc:
    # Load variables
    lon = np.array(nc.variables['lon'][:])
    lat = np.array(nc.variables['lat'][:])
    time = np.array(nc.variables['time'][:]) # Days from Jan-1 1850

    # Extract time indices
    days_since_1850 = (dec - 1850)*360 + 15
    t0 = np.searchsorted(time, days_since_1850)
    t1 = t0 + 12*clim_length

    omega = nc.variables['omega'][:, t0:t1, :, :]
    rate = nc.variables['rate'][:, t0:t1, :, :]
    sst = nc.variables['sst'][:, t0:t1, :, :]

    fill = nc.variables['omega']._FillValue
    model_names = nc.model_names
    scenario    = nc.scenario
    par1        = nc.par1
    par2        = nc.par2

nmodel = np.shape(omega)[0]

# Calculate climatologies
omega_clim = np.zeros((nmodel, 12, len(lat), len(lon)))
rate_clim  = np.zeros_like(omega_clim)
sst_clim   = np.zeros_like(omega_clim)

coef = 1/(clim_length)

for t in range(12*clim_length):
    month = t%12
    omega_clim[:, month, :, :] += coef*omega[:, t, :, :]
    rate_clim[:, month, :, :] += coef*rate[:, t, :, :]
    sst_clim[:, month, :, :] += coef*sst[:, t, :, :]

# Mask variables
mask = np.ma.getmask(omega)[0, 0, :, :]
mask = np.tile(mask, (nmodel, 12, 1, 1))

omega_clim = np.ma.masked_array(omega_clim, mask=mask).filled(fill)
rate_clim = np.ma.masked_array(rate_clim, mask=mask).filled(fill)
sst_clim = np.ma.masked_array(sst_clim, mask=mask).filled(fill)

##############################################################################
# Write file #################################################################
##############################################################################

with Dataset(out_fh, mode='w') as nc:
    # Create dimensions
    nc.createDimension('lon', len(lon))
    nc.createDimension('lat', len(lat))
    nc.createDimension('month', 12)
    nc.createDimension('model', nmodel)

    # Create variables
    nc.createVariable('lon', 'f4', ('lon'), zlib=True)
    nc.createVariable('lat', 'f4', ('lat'), zlib=True)
    nc.createVariable('month', 'f4', ('month'), zlib=True)
    nc.createVariable('sst_clim', 'f4',
                      ('model', 'month', 'lat', 'lon'),
                      zlib=True, fill_value=fill)
    nc.createVariable('omega_clim', 'f4',
                      ('model', 'month', 'lat', 'lon'),
                      zlib=True, fill_value=fill)
    nc.createVariable('rate_clim', 'f4',
                      ('model', 'month', 'lat', 'lon'),
                      zlib=True, fill_value=fill)

    # Write non-model-dependent variables
    nc.variables['lon'].long_name = 'longitude'
    nc.variables['lon'].units = 'degrees_east'
    nc.variables['lon'].standard_name = 'longitude'
    nc.variables['lon'][:] = lon

    nc.variables['lat'].long_name = 'latitude'
    nc.variables['lat'].units = 'degrees_north'
    nc.variables['lat'].standard_name = 'latitude'
    nc.variables['lat'][:] = lat

    nc.variables['month'].long_name = 'month'
    nc.variables['month'].units = 'months'
    nc.variables['month'].standard_name = 'month'
    nc.variables['month'][:] = np.arange(1,13)

    nc.variables['sst_clim'].long_name = 'sea_surface_temperature_monthly_climatology'
    nc.variables['sst_clim'].units = 'degrees Celsius'
    nc.variables['sst_clim'].standard_name = 'sea_surface_temperature_climatology'
    nc.variables['sst_clim'][:] = sst_clim

    nc.variables['omega_clim'].long_name = 'aragonite_saturation_state_monthly_climatology'
    nc.variables['omega_clim'].units = 'no_units'
    nc.variables['omega_clim'].standard_name = 'omega_aragonite_climatology'
    nc.variables['omega_clim'][:] = omega_clim

    nc.variables['rate_clim'].long_name = 'aragonite_precipitation_rate_monthly_climatology'
    nc.variables['rate_clim'].units = 'mmol m-2 day-1'
    nc.variables['rate_clim'].standard_name = 'aragonite_precipitation_rate_climatology'
    nc.variables['rate_clim'][:] = rate_clim

    # Create global attributes
    date = datetime.now()
    date = date.strftime('%d/%m/%Y, %H:%M:%S')

    nc.model_names = model_names
    nc.scenario = scenario
    nc.date_created = date
    nc.par1 = par1
    nc.par2 = par2
    nc.decade = str(dec)


# 查看字典方法 setdefault 的文档，并使用该方法写一个更简洁的 invert_dict
from __future__ import print_function, division

def invert_dict(d):
    """
    反转字典，key变成value，
    :param d:
    :return:
    """
    inverse = {}
    for key in d:
        val = d[key]
        inverse.setdefault(val, []).append(key)
    return inverse

if __name__ == '__main__':
    d = dict(a=1, b=2, c=3, z=1)
    inverse = invert_dict(d)
    print(inverse)
    for val in inverse:
        keys = inverse[val]
        print(val, keys)