import threading
import json
import os
from astar_model import AStarModel
from tkinter.ttk import Progressbar
from tkinter import (Tk, Frame, Button, Label, Entry, Checkbutton, Scale, Canvas, messagebox, filedialog,
                     StringVar, IntVar,
                     DISABLED, NORMAL,
                     W, S, NW, EW, NSEW,
                     BOTTOM, TOP, LEFT,
                     X, Y,
                     HORIZONTAL,
                     YES)

class AStarView(Tk):
    def __init__(self, width=15):
        Tk.__init__(self)
        self.title('A* Search Visualizer')

        # Application version
        self.__VERSION = '1.0.0'

        # Grid width slider range
        self.__MAX_GRID_WIDTH = 100
        self.__MIN_GRID_WIDTH = 2

        # Width of the control frame in pixels (you can modify this)
        self.__CONTROL_DIM_WIDTH = 250

        # Width of the grid in pixels (you can modify this)
        self.__GRID_DIM_WIDTH = 800

        # Calculate the correct dimensions for the root frame (8px offsets prevent grid clipping from grid border thickness)
        self.geometry('{}x{}'.format(
            self.__CONTROL_DIM_WIDTH + self.__GRID_DIM_WIDTH + 8, self.__GRID_DIM_WIDTH + 8))

        # Disable application resizing
        self.resizable(0, 0)

        # Event bindings
        self.bind('<ButtonPress-1>', self.__on_m1_down)
        self.bind('<B1-Motion>', self.__on_m1_down)
        self.bind('<ButtonPress-3>', self.__on_m3_down)
        self.bind('<B3-Motion>', self.__on_m3_down)
        self.bind('<KeyPress>', self.__on_key_press)
        self.bind('<KeyRelease>', self.__on_key_release)

        # Toggles when certain keys are held down
        self.__EDIT_MODE = {
            'setStart': False,
            'setEnd': False,
        }

        # Button colours (you can change these)
        self.__COLOUR_RECONFIGURE_BUTTON = 'coral'
        self.__COLOUR_HOW_TO_USE_BUTTON = 'SlateGray2'
        self.__COLOUR_ABOUT_BUTTON = 'SlateGray1'
        self.__COLOUR_IMPORT_MAZE_BUTTON = 'sandy brown'
        self.__COLOUR_EXPORT_MAZE_BUTTON = 'dark salmon'

        # Maze colours (you can change these)
        self.__COLOUR_EMPTY = 'white'
        self.__COLOUR_WALL = 'gray25'
        self.__COLOUR_START = 'pale green'
        self.__COLOUR_END = 'salmon'
        self.__COLOUR_UNSOLVED = 'thistle'
        self.__COLOUR_SOLVED = 'lemon chiffon'
        self.__COLOUR_PATH = 'light slate blue'

        # Dictionary for when update_gui() is called
        self.__SYMBOL_TO_COLOUR = {
            ' ': self.__COLOUR_EMPTY,
            'W': self.__COLOUR_WALL,
            'S': self.__COLOUR_START,
            'E': self.__COLOUR_END,
            '?': self.__COLOUR_UNSOLVED,
            'X': self.__COLOUR_SOLVED,
            'P': self.__COLOUR_PATH
        }

        # Dialog messages
        self.__DIALOG_MESSAGES = {
            'help': ('Holding left-click places walls\n'
                     'Holding right-click removes walls\n\n'
                     'Holding [S] while pressing left-click sets the start point\n'
                     'Holding [E] while pressing left-click sets the end point\n\n'
                     'Press [space] to start / stop the solver\n'
                     'Press [Esc] to close the application'),

            'about': ('This application visualizes the A* search algorithm.\n\n'
                      'Made by Jonathan Mack\n'
                      'v{}'.format(self.__VERSION)),
        }

        # Contains the GUI representaiton of model.settings
        self.cb_values = {}

        # Disables mouse and keyboard events while True
        self.__is_reconfiguring = False

        # Initialize the backing model
        self.__initialize_model(width, width)

        # Initialize the GUI
        self.__initialize_gui()

    def __initialize_model(self, nRow, nCol):
        ''' Initializes the backing model for the view.

        Args:
            nRow::[int]
                The number of rows in the model
            nCol::[int]
                The number of columns in the model

        Returns:
            None
        '''
        self.model = AStarModel(view=self, nRow=nRow, nCol=nCol)

        # Set the model settings to the GUI settings during reconfiguration
        self.__handle_cb()

        # Disable print to console
        self.model.set_setting('enablePrintToConsole', False)

    def __initialize_gui(self):
        ''' Initializes the GUI.

        Args:
            None

        Returns:
            None
        '''
        self.__initialize_control_frame()
        self.__initialize_grid_frame()

    def __initialize_control_frame(self):
        ''' Initializes the control frame which contains application options.

        Args:
            None

        Returns:
            None
        '''
        # The control frame itself
        control_frame = Frame(self, width=self.__CONTROL_DIM_WIDTH)
        control_frame.pack_propagate(0)
        control_frame.pack(side=LEFT, fill=Y)

        # Initialize children frames
        self.__initialize_configuration_frame(control_frame)
        self.__initialize_options_frame(control_frame)
        self.__initialize_import_export_frame(control_frame)
        self.__initialize_stats_frame(control_frame)
        self.__initialize_help_frame(control_frame)

        # Initialize the Start / Stop button
        self.start_stop_button = Button(control_frame,
                                        text='START',
                                        bg='pale green',
                                        relief='flat',
                                        pady=20,
                                        command=self.__toggle_solver)
        self.start_stop_button.pack(side=BOTTOM, fill=X)

        # Interactive GUI components are disabled during reconfiguration
        self.__interactive_gui_components = [self.grid_width_slider,
                                             self.__reconfigure_button,
                                             self.__cb_diagonal,
                                             self.__cb_grid_lines,
                                             self.__how_to_use_button,
                                             self.__about_button,
                                             self.__import_button,
                                             self.__export_button,
                                             self.start_stop_button]

    def __initialize_configuration_frame(self, master):
        ''' Initializes the configuration frame which is a child of the control frame.

        Args:
            master::[tk.Frame]
                The parent frame of this frame (the control frame)

        Returns:
            None
        '''
        # The configuration frame itself
        configuration_frame = Frame(master)
        configuration_frame.pack(anchor=W, padx=20, pady=20, fill=X)

        # Allow grid components to fill the width of the frame
        configuration_frame.grid_columnconfigure(0, weight=1)
        configuration_frame.grid_columnconfigure(1, weight=1)

        # Configuration label
        configuration_label = Label(
            configuration_frame, text='CONFIGURATION', font=('Helvetica', 16))
        configuration_label.grid(row=0, column=0, sticky=W, columnspan=2)

        # Grid width label
        self.grid_width_label = Label(
            configuration_frame, text='Grid width: {}'.format(self.model.get_nrow()))
        self.grid_width_label.grid(row=1, column=0, sticky=W, columnspan=2)

        # Grid width slider
        self.grid_width_slider = Scale(configuration_frame,
                                       width=20,
                                       from_=self.__MIN_GRID_WIDTH,
                                       to=self.__MAX_GRID_WIDTH,
                                       orient=HORIZONTAL,
                                       showvalue=False)

        # Set default slider value
        self.grid_width_slider.set(self.model.get_nrow())

        # Slider bindings
        self.grid_width_slider.bind(
            '<B1-Motion>', self.__handle_grid_width_slider_change)
        self.grid_width_slider.bind(
            '<ButtonRelease-1>', self.__handle_grid_width_slider_change)
        self.grid_width_slider.grid(row=2, column=0, sticky=EW, columnspan=2)

        # Reconfigure button
        self.__reconfigure_button = Button(
            configuration_frame,
            text='Reconfigure',
            bg=self.__COLOUR_RECONFIGURE_BUTTON,
            command=self.__handle_reconfigure)
        self.__reconfigure_button.grid(row=3,
                                       column=0,
                                       sticky=EW,
                                       columnspan=2)

        # Progress bar for reconfiguration
        self.__progress_bar = Progressbar(
            configuration_frame,
            orient=HORIZONTAL,
            mode='indeterminate')

    def __initialize_options_frame(self, master):
        ''' Initializes the options frame which is a child of the control frame.

        Args:
            master::[tk.Frame]
                The parent frame of this frame (the control frame)

        Returns:
            None
        '''
        # The options frame itself
        options_frame = Frame(master)
        options_frame.pack(anchor=W, padx=20, pady=20, fill=X)

        # Allow grid components to fill the width of the frame
        options_frame.grid_columnconfigure(0, weight=1)

        # Options label
        options_label = Label(
            options_frame,
            text='OPTIONS',
            font=('Helvetica', 16))
        options_label.grid(row=0, column=0, sticky=W, columnspan=2)

        # Allow diagonal movement Checkbutton
        self.allow_diagonals = IntVar(
            value=self.model.get_setting('allowDiagonals'))
        self.cb_values['allowDiagonals'] = self.allow_diagonals
        self.__cb_diagonal = Checkbutton(
            options_frame,
            text='Allow diagonal movement',
            variable=self.allow_diagonals,
            command=self.__handle_cb)
        self.__cb_diagonal.grid(row=1, column=0, sticky=W)

        # Show grid lines Checkbutton
        self.show_grid_lines = IntVar(value=True)
        self.__cb_grid_lines = Checkbutton(
            options_frame,
            text='Show grid lines',
            variable=self.show_grid_lines,
            command=self.__handle_show_grid_lines)
        self.__cb_grid_lines.grid(row=2, column=0, sticky=W)

    def __initialize_import_export_frame(self, master):
        ''' Initializes the Import / Export frame which is a child of the control frame.

        Args:
            master::[tk.Frame]
                The parent frame of this frame (the control frame)

        Returns:
            None
        '''
        # The Import / Export frame itself
        import_export_frame = Frame(master)
        import_export_frame.pack(anchor=W, padx=20, pady=20, fill=X)

        # Allow grid components to fill the width of the frame
        import_export_frame.grid_columnconfigure(0, weight=1)
        import_export_frame.grid_columnconfigure(1, weight=1)

        # Import / Export label
        import_export_label = Label(
            import_export_frame,
            text='IMPORT / EXPORT',
            font=('Helvetica', 16)
        )
        import_export_label.grid(row=0, column=0, sticky=W, columnspan=2)

        # Import button
        self.__import_button = Button(import_export_frame,
                                      text="Import Maze",
                                      bg=self.__COLOUR_IMPORT_MAZE_BUTTON,
                                      command=self.__handle_import)
        self.__import_button.grid(row=1, column=0, sticky=EW)

        # Export button
        self.__export_button = Button(import_export_frame,
                                      text="Export Maze",
                                      bg=self.__COLOUR_EXPORT_MAZE_BUTTON,
                                      command=self.__handle_export)
        self.__export_button.grid(row=1, column=1, sticky=EW)

    def __initialize_stats_frame(self, master):
        ''' Initializes the stats frame which is a child of the control frame.

        Args:
            master::[tk.Frame]
                The parent frame of this frame (the control frame)

        Returns:
            None
        '''
        # The stats frame itself
        stats_frame = Frame(master)
        stats_frame.pack(anchor=W, padx=20, pady=20, fill=X)

        # Allow grid components to fill the width of the frame
        stats_frame.grid_columnconfigure(0, weight=1)
        stats_frame.grid_columnconfigure(1, weight=5)

        # Stats label
        stats_label = Label(
            stats_frame,
            text='STATS',
            font=('Helvetica', 16)
        )
        stats_label.grid(row=0, column=0, sticky=W, columnspan=2)

        # Unsolved label
        self.unsolved_label_var = StringVar()
        unsolved_static_label = Label(
            stats_frame,
            text='# Unsolved Nodes',
            bg=self.__COLOUR_UNSOLVED,
            anchor=W
        )
        unsolved_static_label.grid(row=1, column=0, sticky=EW)

        unsolved_dynamic_label = Label(
            stats_frame,
            textvariable=self.unsolved_label_var
        )
        unsolved_dynamic_label.grid(row=1, column=1, sticky=W)

        # Solved label
        self.solved_label_var = StringVar()
        solved_static_label = Label(
            stats_frame,
            text='# Solved Nodes',
            bg=self.__COLOUR_SOLVED,
            anchor=W
        )
        solved_static_label.grid(row=2, column=0, sticky=EW)

        solved_dynamic_label = Label(
            stats_frame,
            textvariable=self.solved_label_var
        )
        solved_dynamic_label.grid(row=2, column=1, sticky=W)

        # Path label
        self.path_label_var = StringVar()
        path_static_label = Label(
            stats_frame,
            text='# Path Nodes',
            bg=self.__COLOUR_PATH,
            anchor=W
        )
        path_static_label.grid(row=3, column=0, sticky=EW)

        path_dynamic_label = Label(
            stats_frame,
            textvariable=self.path_label_var
        )
        path_dynamic_label.grid(row=3, column=1, sticky=W)

        # Elapsed time label
        self.elapsed_label_var = StringVar()
        elapsed_time_static_label = Label(
            stats_frame,
            text='Elapsed Time (s)'
        )
        elapsed_time_static_label.grid(row=4, column=0, sticky=W)

        elapsed_time_dynamic_label = Label(
            stats_frame,
            textvariable=self.elapsed_label_var
        )
        elapsed_time_dynamic_label.grid(row=4, column=1, sticky=W)

    def __initialize_help_frame(self, master):
        # The help frame itself
        help_frame = Frame(master)
        help_frame.pack(anchor=W, padx=20, pady=20, fill=X)

        # Allow grid components to fill the width of the frame
        help_frame.grid_columnconfigure(0, weight=1)
        help_frame.grid_columnconfigure(1, weight=1)

        # Help label
        help_label = Label(help_frame, text='HELP', font=('Helvetica', 16))
        help_label.grid(row=0, column=0, sticky=W, columnspan=2)

        # How to Use button
        self.__how_to_use_button = Button(help_frame,
                                          text='How to Use',
                                          bg=self.__COLOUR_HOW_TO_USE_BUTTON,
                                          command=lambda title='How to Use', message=self.__DIALOG_MESSAGES['help']: self.__show_info_dialog(title, message))
        self.__how_to_use_button.grid(row=1, column=0, sticky=EW)

        # About button
        self.__about_button = Button(help_frame,
                                     text='About',
                                     bg=self.__COLOUR_ABOUT_BUTTON,
                                     command=lambda title='About', message=self.__DIALOG_MESSAGES['about']: self.__show_info_dialog(title, message))
        self.__about_button.grid(row=1, column=1, sticky=EW)

    def __initialize_grid_frame(self):
        self.grid_frame = Frame(self,
                                height=self.__GRID_DIM_WIDTH,
                                width=self.__GRID_DIM_WIDTH,
                                highlightbackground='gray',
                                highlightthickness=3)

        # Create blank canvas
        self.canvas = Canvas(self.grid_frame,
                             height=self.__GRID_DIM_WIDTH,
                             width=self.__GRID_DIM_WIDTH,
                             bg='white')

        self.canvas.pack()
        self.grid_frame.pack(side=LEFT)

        # Colour the entire grid initially
        self.__POS_TO_SQUARE = {}

        all_indices = [(x, y) for x in range(self.model.get_nrow())
                       for y in range(self.model.get_ncol())]

        self.update_gui(maze=self.model.get_curr_maze(),
                        diff_positions=all_indices,
                        is_rapid_config=False)

    '''
    GUI HANDLERS.
    '''

    def __handle_reconfigure(self, is_importing=False, loaded_maze=None):

        def reconfiguration_thread():
            # Disable mouse and keyboard events
            self.__is_reconfiguring = True

            # Change the grid width slider before the GUI is disabled
            if is_importing:
                # Move the grid width slider to the new grid width
                self.grid_width_slider.set(loaded_maze['gridWidth'])

                # Update the grid width label
                self.grid_width_label.configure(
                    text='Grid width: {}'.format(self.grid_width_slider.get()))

            # Disable interactive GUI components
            self.__disable_gui()

            # Set the new grid width to the one in the imported file if importing, otherwise use the slider value
            print('{}...'.format(
                'Importing maze' if is_importing else 'Reconfiguring'))

            new_width = loaded_maze['gridWidth'] if is_importing else int(
                self.grid_width_slider.get())

            # Replace the Reconfigure button with the progress bar
            self.__reconfigure_button.grid_remove()
            self.__progress_bar.grid(row=3, column=0, sticky=EW, columnspan=2)
            self.__progress_bar.start()

            # The long reconfiguration task
            reconfigure(new_width=new_width,
                        is_importing=is_importing,
                        loaded_maze=loaded_maze)

            # Replace the progress bar with the Reconfigure button
            self.__progress_bar.stop()
            self.__progress_bar.grid_forget()
            self.__reconfigure_button.grid(
                row=3, column=0, sticky=EW, columnspan=2)

            # Re-enable mouse and keyboard events
            self.__is_reconfiguring = False

            # Re-enable interactive GUI components
            self.__enable_gui()

            print('{} complete!'.format(
                'Import' if is_importing
                else 'Reconfiguration'))

            # Show success dialog
            self.__show_info_dialog(
                title='{} Complete'.format(
                    'Import' if is_importing else 'Reconfiguration'),
                message='Successfully {} a {} x {} maze!'.format('imported' if is_importing else 'configured',
                                                                 self.model.get_nrow(),
                                                                 self.model.get_ncol()))

        def reconfigure(new_width, is_importing, loaded_maze):
            # Recreate the backing model and redraw the GUI
            self.__initialize_model(new_width, new_width)
            self.__POS_TO_SQUARE = {}
            self.canvas.delete('all')

            # Colour the entire grid initially
            all_indices = [(x, y) for x in range(self.model.get_nrow())
                           for y in range(self.model.get_ncol())]

            self.update_gui(maze=self.model.get_curr_maze(),
                            diff_positions=all_indices,
                            is_rapid_config=False)

            # Update model with the imported data if the reconfiguration was triggered by an import
            if is_importing and loaded_maze is not None:
                self.model.import_maze_data(loaded_maze)

        # Make sure that the solver is stopped
        if not self.model.is_solving():
            # Display confirmation dialog
            is_reconfiguring = messagebox.askyesno(title='Reconfigure',
                                                   message=('Are you sure you want to reconfigure?\n'
                                                            'All walls will be erased.'),
                                                   icon='warning')
            if is_reconfiguring == YES:
                threading.Thread(target=reconfiguration_thread).start()
        else:
            messagebox.showerror(title='Failed to Reconfigure',
                                 message='Cannot reconfigure while the solver is running.')

    def __handle_grid_width_slider_change(self, event):
        self.grid_width_label.configure(
            text='Grid width: {}'.format(self.grid_width_slider.get()))

    def __handle_cb(self):
        for k, v in self.cb_values.items():
            self.model.set_setting(k, bool(v.get()))

    def __handle_show_grid_lines(self):
        # Determine outline colour
        outline_colour = 'gray' if self.show_grid_lines.get() else ''

        # Colour all square outlines
        for pos in self.__POS_TO_SQUARE:
            square = self.__POS_TO_SQUARE[pos]
            self.canvas.itemconfig(square, outline=outline_colour)

    def __handle_import(self):
        if not self.model.is_solving():
            # Display load file dialog
            filename = filedialog.askopenfilename(parent=self,
                                                  title='Import Maze',
                                                  initialdir=os.getcwd() + '/sample_mazes')

            if filename != '':
                try:
                    # Read the contents of the file
                    with open(filename, 'r') as file:
                        content = file.readlines()

                    # Assign the file contents to a dictionary
                    loaded_maze = json.loads(content[0])

                    # Reconfigure the maze using the imported data
                    self.__handle_reconfigure(
                        is_importing=True, loaded_maze=loaded_maze)
                except:
                    print('Failed to import maze from {}: Incompatible or corrupted file'.format(
                        filename))

    def __handle_export(self):
        if not self.model.is_solving():
            # Display save file dialog
            filetypes = [('JSON', '*.json')]
            filename = filedialog.asksaveasfilename(parent=self,
                                                    title='Export Maze',
                                                    initialfile='my_astar_maze',
                                                    defaultextension='.json',
                                                    filetypes=filetypes)

            # Prepare the current maze configuration data
            curr_maze = {
                'gridWidth': self.model.get_nrow(),
                'start': self.model.get_start(),
                'end': self.model.get_end(),
                'walls': list(self.model.get_walls())
            }

            # Write to the file
            if filename != '':
                with open(filename, 'w') as file:
                    file.write(json.dumps(curr_maze))
                print('Successfully exported maze configuration to {}.'.format(filename))

    '''
    EVENT HANDLERS.
    '''

    def __on_m1_down(self, event):
        self.__handle_mouse_down(event, True)

    def __on_m3_down(self, event):
        self.__handle_mouse_down(event, False)

    def __handle_mouse_down(self, event, is_setting_wall):
        def is_event_pos_valid():
            return 0 <= event.x <= self.__GRID_DIM_WIDTH and 0 <= event.y <= self.__GRID_DIM_WIDTH

        def calculate_square_pos():
            square_pos_x = int(event.x // self.__SQUARE_WIDTH)
            square_pos_y = int(event.y // self.__SQUARE_WIDTH)
            return (square_pos_x, square_pos_y)

        # Validate that the solver is stopped, the GUI is not reconfiguring, and the position is good
        if (not self.model.is_solving()
            and not self.__is_reconfiguring
            and event.widget == self.canvas
                and is_event_pos_valid()):

            square_pos = calculate_square_pos()

            if self.__EDIT_MODE['setStart']:
                self.model.set_start(square_pos)
            elif self.__EDIT_MODE['setEnd']:
                self.model.set_end(square_pos)
            else:
                self.model.set_wall(square_pos, is_setting_wall)

    def __on_key_press(self, event):
        ''' Hanldes keyboard events.

        [space] - Start / stop the solver
        [Esc] - Quit the application
        [S] - Toggle set start node
        [E] - Toggle set end node

        Args:
            event::[tkinter.Event]
                The keyboard event.

        Returns:
            None
        '''
        key_code = event.keysym

        # Only handle keyboard events when the GUI is not reconfiguring
        if not self.__is_reconfiguring:

            # Press [space] to start / stop the solver
            if key_code == 'space':
                self.__toggle_solver()

            # Press [Esc] to quit the application
            elif key_code == 'Escape':
                if not self.model.is_solving():
                    # Display confirmation dialog
                    is_quitting = messagebox.askyesno(title='Exit Application',
                                                      message='Are you sure you want to exit the application?',
                                                      icon='warning')
                    if is_quitting == YES:
                        print('Closing application...')
                        self.destroy()
                else:
                    print(
                        'Cannot close the application while the solver is running.')
                    messagebox.showerror(title='Failed to Close Application',
                                         message='Cannot close the application while the solver is running.')

            # Edit mode toggles
            elif key_code == 's':
                self.__EDIT_MODE['setStart'] = True
            elif key_code == 'e':
                self.__EDIT_MODE['setEnd'] = True

    def __on_key_release(self, event):
        key_code = event.keysym

        if key_code == 's':
            self.__EDIT_MODE['setStart'] = False
        elif key_code == 'e':
            self.__EDIT_MODE['setEnd'] = False

    def __handle_wall_click(self, pos):
        self.model.set_wall(pos, True)

    '''
    UTILITY METHODS.
    '''

    def __toggle_solver(self):
        if not self.model.is_solving():
            # Disable everything in the GUI except the Start / Stop button and the Show grid lines Checkbutton
            self.__disable_gui()
            self.start_stop_button.configure(state=NORMAL)
            self.__cb_grid_lines.configure(state=NORMAL)
            self.model.solve()
        else:
            print('Solver stopped.')
            self.__enable_gui()
            self.model.stop_solving()

    def __disable_gui(self):
        for component in self.__interactive_gui_components:
            component.configure(state=DISABLED)

    def __enable_gui(self):
        for component in self.__interactive_gui_components:
            component.configure(state=NORMAL)

    def __show_info_dialog(self, title, message):
        messagebox.showinfo(title, message)

    def __show_error_dialog(self, title, message):
        messagebox.showerror(title, message)

    '''
    UPDATE METHOD.
    '''

    def __calculate_square_width(self):
        # Update the square width depending on the new model
        self.__SQUARE_WIDTH = self.__GRID_DIM_WIDTH / self.model.get_nrow()

    def update_gui(self, maze, diff_positions, is_rapid_config):
        ''' Updates the GUI by colouring the grid labels contained in diff_indices according to the maze symbols.
        Also updates the stats frame.

        Args:
            maze::[list[list]]
                A 2D array containing symbols that represent the maze
            diff_indices::[list]
                A list containing the positions of nodes that have changed since the previous update

        Returns:
            None
        '''
        # Configure the Start / Stop button to display the appropriate text and colour
        if self.model.is_solving():
            self.start_stop_button.configure(text='STOP', bg='salmon')
        elif not self.__is_reconfiguring:
            # Re-enable the GUI if the solver is done
            self.__enable_gui()
            self.start_stop_button.configure(text='START', bg='pale green')

        # Update the square width based on the current number of rows in the model
        self.__calculate_square_width()

        # Determine outline colour
        outline_colour = 'gray' if self.show_grid_lines.get() else ''

        # Update grid by colouring the appropriate square
        for (x, y) in diff_positions:

            # Create a square at (x, y) if it does not yet exist
            if (x, y) not in self.__POS_TO_SQUARE:
                square = self.canvas.create_rectangle(x * self.__SQUARE_WIDTH,
                                                      y * self.__SQUARE_WIDTH,
                                                      (x + 1) *
                                                      self.__SQUARE_WIDTH,
                                                      (y + 1) *
                                                      self.__SQUARE_WIDTH,
                                                      fill=self.__SYMBOL_TO_COLOUR[maze[x][y]],
                                                      outline=outline_colour,
                                                      tag='to-delete')
                self.__POS_TO_SQUARE[(x, y)] = square

            # Configure the square at (x, y) since it exists
            else:
                self.canvas.itemconfig(self.__POS_TO_SQUARE[(
                    x, y)], fill=self.__SYMBOL_TO_COLOUR[maze[x][y]])

        # Update stats
        self.unsolved_label_var.set(str(self.model.get_stat('numUnsolved')))
        self.solved_label_var.set(str(self.model.get_stat('numSolved')))
        self.path_label_var.set(str(self.model.get_stat('numPath')))
        self.elapsed_label_var.set(str(self.model.get_stat('elapsedTime')))

        # Handle GUI updates differently if the update is caused by a wall config or not
        if is_rapid_config:
            # update_idletasks() prevents fatal crashes when setting / removing nodes rapidly
            self.update_idletasks()
        else:
            # update() allows the user to stop the solver and prevents calls to model.solve() from queueing
            self.update()

def main():
    print('Starting application...')
    app = AStarView()
    app.mainloop()

if __name__ == '__main__':
    main()


from google.cloud import storage
from configparser import ConfigParser
from google.oauth2 import service_account
from googleapiclient.discovery import build
from utils.demo_io import (
    get_initial_slide_df_with_predictions_only,
    get_fovs_df,
    get_top_level_dirs,
    populate_slide_rows,
    get_histogram_df,
    list_blobs_with_prefix,
)
import polars as pl
from gcsfs import GCSFileSystem
from PIL import Image
import asyncio

from utils.zarr_utils import parse_slide, get_image_from_zarr

# Parse in key and bucket name from config file
cfp = ConfigParser()
cfp.read("config.ini")

service_account_key_json = cfp["GCS"]["gcs_storage_key"]
gs_url = cfp["GCS"]["bucket_url"]

bucket_name = gs_url.replace("gs://", "")

bucket2_name = "octopi-malaria-data-processing"

zipzarr_url = "octopi-malaria-data-processing/072622-D1-3_2022-07-26_17-50-42.852998/version1/spot_images.zip"

# Define GCS file system so files can be read
gcs = GCSFileSystem(token=service_account_key_json)

# Authenticate using the service account key file
credentials = service_account.Credentials.from_service_account_file(
    service_account_key_json, scopes=["https://www.googleapis.com/auth/cloud-platform"]
)

client = storage.Client.from_service_account_json(service_account_key_json)

# Create a storage client
storage_service = build("storage", "v1", credentials=credentials)

spot_img_zarr = parse_slide(gcs, zipzarr_url)

for i in range(25):
    spot_img = Image.fromarray(get_image_from_zarr(spot_img_zarr, 240 + i)["compose"])
    spot_img.show()


import psycopg2
import src.DBConfig as config

from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT

class Database:
    def __init__(self, debugLogFile):
        self.db_server_conn = None
        self.database_params = None
        self.db_cursor = None
        self.db_name = None
        self.bug_table_name = None
        self.debugLogFile = debugLogFile

    # Open a connection to the host server that the database is stored on.
    def connect(self, server_params):
        """
        Connects to the postgres server that has the bug tracker database on it.
        If an invalid key is provided then a connection will not be made.

        :param server_params: config file key
        :return:
        """
        try:
            # Get the server params from Database.ini
            params = config.config(server_params)
            # Create a connection to the posgresql database server
            self.db_server_conn = psycopg2.connect(**params)
            self.db_cursor = self.db_server_conn.cursor()
        except (Exception, psycopg2.Error) as e:
            self.debugLogFile.writeToFile("Error while connecting to PostgreSQL " + str(e))

    # Disconnect is its own function so that the connection to the
    # databases server can remain open while the database and table are being
    # queried.
    def disconnect(self):
        if self.db_server_conn:
            self.db_cursor.close()
            self.db_server_conn.close()
            print("PostgreSQL connection is closed")

    def create_database(self, database_params):
        if self.db_server_conn:
            try:
                self.database_params = config.config(database_params)
                self.db_server_conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
                database_name = self.database_params.get('database')
                sqlCreateDatabase = "create database " + database_name + ""
                self.db_cursor.execute(sqlCreateDatabase)

            except (Exception, psycopg2.Error) as e:
                self.debugLogFile.writeToFile("Could not create Database " + str(e))

    def create_table(self, table_name):
        # need to connect to the database that I am actually creating the table in.
        conn = None
        cursor = None
        self.bug_table_name = table_name
        try:
            # connect to the database on the postgres server.
            conn = psycopg2.connect(**self.database_params)
            # once we are connected to the database that we created on the postgreSQL server
            # we can create a table in that database.
            cursor = conn.cursor()
            # basic Table
            table = ("CREATE TABLE " + table_name + """ (
                        Title text NULL,
                        Traceback_info text NULL,
                        Resolved BOOL NULL,
                        Version text NULL,
                        PRIMARY KEY (Title)
                        ); """)
            # execute the sql query
            cursor.execute(table)
            # commit the changes so they are saved
            conn.commit()

        except (Exception, psycopg2.Error) as e:
            self.debugLogFile.writeToFile("Could not create table " + str(e))

        finally:
            # close the connection to the database to prevent memory leaks
            if conn:
                cursor.close()
                conn.close()

    def list_insert(self, bugRecordDTO):
        conn = None
        cursor = None
        try:
            conn = psycopg2.connect(**self.database_params)
            cursor = conn.cursor()
            insert_query = (" INSERT INTO " + self.bug_table_name + """(
                            Title,
                            Traceback_info,
                            Resolved,
                            Version)
                            VALUES (%s, %s, %s, %s); """)

            cursor.execute(insert_query,
                           (bugRecordDTO.title,
                            bugRecordDTO.tracebackInfo, bugRecordDTO.resolved, bugRecordDTO.version))
            conn.commit()
        except (Exception, psycopg2.Error) as e:
            self.debugLogFile.writeToFile("Could not insert record into table " + str(e))

        finally:
            if conn:
                cursor.close()
                conn.close()

    # update item in table
    # the title of the bug is the primary key. To update date a specific record enter the title of the bug.
    def update_record(self, title):
        conn = None
        cursor = None
        try:
            conn = psycopg2.connect(**self.database_params)
            cursor = conn.cursor()

            # When a bug is updated it means that it has been resolved so the bug_resolve flag is switched
            # from false to true.
            sql_update_query = (" UPDATE " + self.bug_table_name + """
                                SET Resolved = True
                                WHERE Resolved = %s
                                AND Title = %s """)

            update_values = (False, title)
            cursor.execute(sql_update_query, update_values)
            conn.commit()

            count = cursor.rowcount
            print(count, "Record updated successfully")

        except (Exception, psycopg2.Error) as e:
            self.debugLogFile.writeToFile("Could not update record in table " + str(e))
        finally:
            if conn:
                cursor.close()
                conn.close()

    # Retrieves all records with the same bug_title, bug_title is the primary key.
    def retrieve_record(self, title):
        conn = None
        cursor = None
        try:
            conn = psycopg2.connect(**self.database_params)
            cursor = conn.cursor()

            sql_retrieve_query = ("SELECT * from " + self.bug_table_name + """
                                WHERE Title = %s """)

            # record_name has to be a tuple or list to convert properly to execute the query.
            sql_query_values = (title,)
            cursor.execute(sql_retrieve_query, sql_query_values)
            # retrieves all the records that were queried.
            table_record = cursor.fetchall()

        except (Exception, psycopg2.Error) as e:
            self.debugLogFile.writeToFile("Could not retrieve record " + str(e))
        finally:
            if conn:
                cursor.close()
                conn.close()

            return table_record


import PyPDF2
import warnings

# Open the PDF file in read-binary mode
pdf_file = open('1.2.826.1.3680043.9.5282.150415.2352.16502352212057.pdf', 'rb')

pdf_reader = PyPDF2.PdfReader(pdf_file)

num_pages = len(pdf_reader.pages)
text = ""

for page_number in range(num_pages):
    page = pdf_reader.pages[page_number]
    page_text = page.extract_text()
    text += page_text
# print(text)
# print(type(text))

import re

# text = "Name MR.JITENDRA BHUPTANI 383669 Patient ID AS_BOR_CT_2746  Accession No 164_02746_212057 Age/Gender 80Y / Male  Referred By Dr.ANAND S. SHENAI MS Date 3-Oct-2021"

name_regex = r"Name\s+([A-Z\. ]+)"
patient_id_regex = r"Patient\s+ID\s+([\w-]+)"
accession_no_regex = r"Accession\s+No\s+([\d_]+)"
age_gender_regex = r"Age\/Gender\s+([\d]+[A-Za-z]*)\s+\/\s+([A-Za-z]+)"
date_regex = r"Date\s+(\d+-[A-Za-z]{3}-\d+)"

name = re.search(name_regex, text).group(1)
patient_id = re.search(patient_id_regex, text).group(1)
accession_no = re.search(accession_no_regex, text).group(1)
age, gender = re.search(age_gender_regex, text).groups()
date = re.search(date_regex, text).group(1)

print(f"Name: {name}")
print(f"Patient ID: {patient_id}")
print(f"Accession No: {accession_no}")
print(f"Age: {age}")
print(f"Gender: {gender}")
print(f"Date: {date}")


import math
from django.shortcuts import render, get_object_or_404, redirect, reverse
from django.utils import timezone
from authentication.models import UserProfile
from issue_tracker.models import Issue, Comment
from django.contrib.auth.models import User
from issue_tracker.forms import IssueForm, CommentForm
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

# Create your views here.
def get_all_issues(request):
    """
    Create a view that will return all the Issues from all Users
    that were published prior to now and render them all to issues.html
    template
    """
    all_issues = Issue.objects.filter(published_date__lte=timezone.now()).order_by("-total_votes")
    comments = Comment.objects.filter()
    paginator = Paginator(all_issues, 9)
    page = request.GET.get('page-issues')
    try:
        all_issues = paginator.page(page)
    except PageNotAnInteger:
        all_issues = paginator.page(1)
    except EmptyPage:
        all_issues = paginator.page(paginator.num_pages)
    paginator.page(paginator.num_pages)
    return render(request, "issues.html", {"all_issues": all_issues,
                                           "comments": comments,
                                        #   "comment_count": comment_count,
                                            })

def single_issue(request, pk):
    """
    Create a view that returns an issue based on the issue id (pk)
    and render it to viewissue.html or return 404 error if issue is not found
    """
    issue = get_object_or_404(Issue, pk=pk)
    comments = Comment.objects.filter(issue=issue).order_by("-id")
    if request.method == "POST":
        comment_form = CommentForm(request.POST)
        if comment_form.is_valid():
            content = request.POST.get('content')
            comment = Comment.objects.create(issue=issue,
                                             user_logged_in=request.user,
                                             content=content)
            comment.save()
            # Issue.objects.create(comment)
            issue.comment_number += 1
            issue.save()
            return redirect(single_issue, issue.pk)
    else:
        comment_form = CommentForm()

    author_image = []
    try:
        image = UserProfile.objects.get(user=issue.author).image
        author_image.append({"image": image})
    except:
        author_image.append({"image": None})

    comments_with_images = []
    for comment in comments:
        try:
            image = UserProfile.objects.get(user=comment.user_logged_in).image
            comments_with_images.append({"image": image, "comment": comment})
        except:
            comments_with_images.append({"image": None, "comment": comment})
    comment_count = comments.count()
    first_three_comments = comments_with_images[:3]
    return render(request, "viewissue.html", {"author_image": author_image,
                                              "issue": issue,
                                              "comments_with_images": comments_with_images,
                                              "comment_count": comment_count,
                                              "comment_form": comment_form,
                                              "first_three_comments": first_three_comments,
                                              })

def create_or_edit_issue(request, pk=None):
    """
    Create a view that allows the user to create or edit
    an issue depending if the issue ID is null or not
    """
    issue = get_object_or_404(Issue, pk=pk) if pk else None
    if request.method == "POST":
        form = IssueForm(request.POST, request.FILES, instance=issue)
        if form.is_valid():
            issue = form.save()
            issue.author = request.user
            issue.save()
            return redirect(single_issue, issue.pk)
    else:
        form = IssueForm(instance=issue)
    return render(request, "issueform.html", {"form": form})

def delete_issue(request, pk):
    """
    author of issue can delete their posted issue
    """
    issue = get_object_or_404(Issue, pk=pk)
    issue.delete()

    return redirect(get_all_issues)

@csrf_exempt
def vote(request, pk):
    """
    User in session can vote for an issue if it's helped them
    """
    user = request.user
    issue = get_object_or_404(Issue, pk=pk)
    vote_number = issue.total_votes
    if user.is_authenticated():
        if user in issue.vote.all():
            issue.vote.remove(user)
            up_vote = False
            vote_number -= 1
        else:
            issue.vote.add(user)
            up_vote = True
            vote_number += 1
    issue.total_votes = issue.vote.count()
    issue.save()
    data = {
        "up_vote": up_vote,
        "vote_number": vote_number
    }
    return JsonResponse(data)

@csrf_exempt
def done(request, pk):
    """
    Author of issue can click button if 'admin' has fixed the issue in
    their opinon
    """
    issue = get_object_or_404(Issue, pk=pk)
    data = {
        "is_done": issue.done
    }
    is_done = request.POST.get('is_done')
    if request.method == "POST":
        if str(is_done) == "true":
            issue.done = True
            issue.save()
        else:
            issue.done = False
            issue.save()
    return JsonResponse(data)


import pytest

from src.can_place_flowers import Solution

@pytest.mark.parametrize(
    "flowerbed,n,expected",
    (
        ([1, 0, 0, 0, 1], 1, True),
        ([1, 0, 0, 0, 1], 2, False),
        ([1, 0, 0, 0, 0, 1], 2, False),
        ([0], 1, True),
    ),
)
def test_solution(flowerbed, n, expected):
    assert Solution().canPlaceFlowers(flowerbed, n) is expected


#!/bin/env python

import streamlit as st
import pandas as pd
import os
import yaml
################################################################

def _main():
    _filenames = os.listdir()

    _filenames = [e for e in _filenames if os.path.splitext(e)[1] == '.json']
    _filenames = [os.path.splitext(e)[0] for e in _filenames]
    _filenames = [os.path.splitext(e)[0]
                  for e in _filenames if (not e.startswith('Master_')
                                          and not e.startswith('match_'))]

    _filenames.remove('EPFL')
    option = st.selectbox(
        "Select the university", _filenames, key="unisersity_selector_stat")

    try:
        with open(f'match_{option}.json', "r") as f:
            courses_matches = yaml.safe_load(f.read())
    except Exception:
        st.error("Matching tab should be executed once at least")
        return

    df = pd.read_json(option+'.json')
    df_epfl = pd.read_json('EPFL.json')

    sorted_match = {}

    for epfl_title, other_title in courses_matches.items():
        _sel = df_epfl[df_epfl["Course Title"] == epfl_title]
        if _sel.shape[0] == 0:
            st.write(f"cannot find class: {epfl_title}")
            continue
        epfl_class = _sel.iloc[0]
        epfl_year = epfl_class['Year']
        epfl_semester = epfl_class['Semester']
        other_class = df[df["Course Title"] == other_title].iloc[0]

        if epfl_year not in sorted_match:
            sorted_match[epfl_year] = {}
        if epfl_semester not in sorted_match[epfl_year]:
            sorted_match[epfl_year][epfl_semester] = []

        sorted_match[epfl_year][epfl_semester].append(
            (epfl_class, other_class))

    def plot_class(layout, title, year, semester, ects):
        layout.markdown(f"**{title}**, Year{year}-{semester} ({ects} ECTS)")

    hide = st.checkbox("Show only mismatches")

    def are_classes_different(classes):
        cols = st.columns(len(classes))
        ref_class = classes[0]
        ref_title = ref_class['Course Title']
        ref_year = ref_class['Year']
        ref_semester = ref_class['Semester']
        ref_ects = ref_class['ECTS']

        for c, col in zip(classes[1:], cols[1:]):
            title = c['Course Title']
            year = c['Year']
            semester = c['Semester']
            ects = c['ECTS']

            if year != ref_year:
                year = f":red[{year}]"
            if semester != ref_semester:
                semester = f":red[{semester}]"
            if ects != ref_ects:
                ects = f":red[{ects}]"

            if year == ref_year and semester == ref_semester and ects == ref_ects and hide:
                return

            plot_class(col, option + " " + title, year, semester, ects)
        plot_class(cols[0], 'EPFL-' + ref_title,
                   ref_year, ref_semester, ref_ects)

        with st.expander('description', expanded=False):
            cols = st.columns(len(classes))
            for c, col in zip(classes[:], cols[:]):
                col.markdown('- ' + '\n - '.join(c['Description']))

    for year, semesters in sorted_match.items():
        for semester, matches in semesters.items():
            st.markdown(f'### Year {year} - {semester} Semester')
            for classes in matches:
                are_classes_different(classes)
                st.markdown('---')

def main():
    with st.spinner('Loading'):
        _main()


import discord
from discord.ext import commands
from discord import Embed

from core.classes import *

class Global(CategoryExtension):

    @commands.command()
    @commands.has_permissions(manage_roles=True)
    async def ping(self, ctx):
        await ctx.send(f"Hey, it's me, HarTex! :eyes: Did you need something? - `{str(round(self.bot.latency * 1000))} ms`")

    @commands.command()
    async def about(self, ctx):
        about_me = discord.Embed(colour=0xa6f7ff)
        about_me.set_author(name="HarTex", icon_url="https://media.discordapp.net/attachments/582782144099778560/680318768907419668/your_profile_picture.jpg?width=450&height=450")
        about_me.description = "HarTex is a bot meant for servers with advanced moderation needs, run by Harry.#2450 and maintained by the HarTex Development Team."
        about_me.add_field(name="Whitelisted Servers", value=str(len(self.bot.guilds)), inline=False)

        await ctx.send(embed=about_me)

    @commands.command()
    async def help(self, ctx, command=None):
        """
        .help

        A custom help command.
        """
        if command is None:
            helpEmbed = Embed(title="HarTex Help", description="Common Moderation Commands",
                              footer="Execute `help <command>` to get more information about one command.",
                              colour=0xa6f7ff)
            helpEmbed.add_field(name="kick", value="Kicks a user.", inline=False)
            helpEmbed.add_field(name="ban", value="Bans a user.", inline=False)
            helpEmbed.add_field(name="unban", value="Unbans a user.", inline=False)
            helpEmbed.add_field(name="mute", value="Mutes a user.", inline=False)
            helpEmbed.add_field(name="unmute", value="Unmutes a user.", inline=False)
            helpEmbed.add_field(name="tempmute", value="Temporarily mutes a user.", inline=False)

            await ctx.send(embed=helpEmbed)
        elif command == "kick":
            helpKickEmbed = Embed(title="HarTex Help: Kick", description="Kick: Usage", colour=0xa6f7ff)
            helpKickEmbed.add_field(name="Usage", value="kick <member: **discord.Member**> <reason: **optional**>",
                                    inline=False)
            helpKickEmbed.add_field(name="Description", value="Kicks a member.", inline=False)

            await ctx.send(embed=helpKickEmbed)
        elif command == "ban":
            helpBanEmbed = Embed(title="HarTex Help: Ban", description="Ban: Usage", colour=0xa6f7ff)
            helpBanEmbed.add_field(name="Usage", value="ban <member: **discord.Member**> <reason: **optional**>",
                                   inline=False)
            helpBanEmbed.add_field(name="Description", value="Bans a member.", inline=False)

            await ctx.send(embed=helpBanEmbed)
        elif command == "unban":
            helpUnbanEmbed = Embed(title="HarTex Help: Unban", description="Unban: Usage", colour=0xa6f7ff)
            helpUnbanEmbed.add_field(name="Usage", value="unban <member: **discord.Member**> <reason: **optional**>",
                                     inline=False)
            helpUnbanEmbed.add_field(name="Description", value="Unbans a member.", inline=False)

            await ctx.send(embed=helpUnbanEmbed)
        elif command == "mute":
            helpMuteEmbed = Embed(title="HarTex Help: Mute", description="Mute: Usage", colour=0xa6f7ff)
            helpMuteEmbed.add_field(name="Usage", value="mute <member: **discord.Member**> <reason: **optional**>",
                                    inline=False)
            helpMuteEmbed.add_field(name="Description", value="Mutes a member.", inline=False)

            await ctx.send(embed=helpMuteEmbed)
        elif command == "unmute":
            helpUnmuteEmbed = Embed(title="HarTex Help: Unmute", description="Unmute: Usage", colour=0xa6f7ff)
            helpUnmuteEmbed.add_field(name="Usage", value="unmute <member: **discord.Member**> <reason: **optional**>",
                                      inline=False)
            helpUnmuteEmbed.add_field(name="Description", value="Unmutes a member.", inline=False)

            await ctx.send(embed=helpUnmuteEmbed)
        elif command == "tempmute":
            helpTempmuteEmbed = Embed(title="HarTex Help: Tempmute", description="Tempmute: Usage", colour=0xa6f7ff)
            helpTempmuteEmbed.add_field(name="Usage",
                                        value="tempmute <member: **discord.Member**> <time: **string**> <reason: **optional**>",
                                        inline=False)
            helpTempmuteEmbed.add_field(name="Description", value="Temporarily mutes a member.", inline=False)

            await ctx.send(embed=helpTempmuteEmbed)

    @commands.command()
    async def staff(self, ctx):
        staff_embed = discord.Embed(colour=0xa6f7ff)
        staff_embed.set_author(name="HarTex Staff",
                               icon_url="https://media.discordapp.net/attachments/582782144099778560/680318768907419668/your_profile_picture.jpg?width=450&height=450")
        staff_embed.description = "Without the people above, this bot cannot be this great!"
        staff_embed.add_field(name="Global Administrator & Developer", value="Harry.#2450", inline=False)
        staff_embed.add_field(name="Lead Developer", value="Harry.#2450", inline=False)
        staff_embed.add_field(name="Development Team", value="OfficialAz3#0762", inline=False)
        staff_embed.add_field(name="HarTex Support Team", value="Harry.#2450\nOfficialAz3#0762", inline=False)

        await ctx.send(embed=staff_embed)

def setup(hartex):
    hartex.add_cog(Global(hartex))


import os
import time
import requests
import telegram
import logging

from dotenv import load_dotenv, find_dotenv

logger = logging.getLogger('__name__')

class TelegramBotHandler(logging.Handler):

    def __init__(self, log_bot, chat_id):
        super().__init__()
        self.chat_id = chat_id
        self.log_bot = log_bot

    def emit(self, record):
        log_entry = self.format(record)
        self.log_bot.send_message(text=log_entry, chat_id=self.chat_id)

def main():
    load_dotenv(find_dotenv())
    token = os.environ['TOKEN']
    bot_token = os.environ['BOT_TOKEN']
    log_bot_token = os.environ['LOG_BOT_TOKEN']
    chat_id = os.environ['CHAT_ID']
    bot = telegram.Bot(bot_token)
    log_bot = telegram.Bot(log_bot_token)

    logger.setLevel(logging.INFO)
    logger.addHandler(TelegramBotHandler(log_bot, chat_id))
    try:
        url = 'https://dvmn.org/api/long_polling/'
        header = {
            'Authorization': f'Token {token}'
            }
        timestamp = ''

        logger.info('Bot started')
        while True:
            try:
                params = {'timestamp': timestamp}
                response = requests.get(url, headers=header, params=params, timeout=120)
                response.raise_for_status()
            except requests.exceptions.ReadTimeout:
                logger.info('Надо ж дать')
                continue
            except requests.ConnectionError as err:
                time.sleep(180)
                logger.error(err)
                continue
            response_attempts = response.json()
            if 'timestamp_to_request' in response_attempts:
                timestamp = response_attempts['timestamp_to_request']
            else:
                timestamp = response_attempts['last_attempt_timestamp']
                new_attempts = response_attempts['new_attempts']
                for attempt in new_attempts:
                    lesson_title = attempt['lesson_title']
                    if attempt['is_negative']:
                        add_text = 'К сожалению, в работе нашлись ошибки.'
                    else:
                        add_text = 'Преподавателю все понравилось, можно приступать к следующему уроку!'
                    lesson_url = attempt['lesson_url']
                    bot.send_message(text=f'У Вас проверили работу «{lesson_title}»\n\n{add_text}\n\n {lesson_url}', chat_id=chat_id)
    except Exception as err:
        logger.fatal(err)

if __name__ == '__main__':
    main()


import pytz
import json
import os
import mimetypes
import requests
import tempfile

from django import forms
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile, get_image_dimensions
from django.conf import settings

from allauth.socialaccount.models import SocialAccount

from .models import User, ScheduledPost

class SplitDateTimeWidget(forms.SplitDateTimeWidget):
    def __init__(self, *args, **kwargs):
        super(SplitDateTimeWidget, self).__init__(*args, **kwargs)
        self.widgets[0].input_type = "date"
        self.widgets[1].input_type = "time"

class ScheduledPostAddForm(forms.ModelForm):
    scheduled_datetime = forms.SplitDateTimeField(
        widget=SplitDateTimeWidget
    )
    scheduled_tz = forms.CharField(widget=forms.HiddenInput,
                                   initial="UTC", required=False)
    status = forms.CharField(
        widget=forms.Textarea(attrs={"rows": None, "cols": None})
    )
    attached_media = forms.ImageField(required=False)
    media_url = forms.URLField(required=False)

    def __init__(self, *args, **kwargs):
        user = kwargs.pop("user")
        super(ScheduledPostAddForm, self).__init__(*args, **kwargs)
        one_hour_hence = timezone.now() + timezone.timedelta(hours=1)
        self.fields["scheduled_datetime"].initial = one_hour_hence
        self.fields["service"].choices = []
        if SocialAccount.objects.filter(user=user, provider="facebook").count():
            self.fields["service"].choices.append(
                ("facebook", "Facebook"),
            )
        if SocialAccount.objects.filter(user=user, provider="twitter").count():
            self.fields["service"].choices.append(
                ("twitter", "Twitter"),
            )

    class Meta:
        model = ScheduledPost
        fields = ("scheduled_datetime", "service", "status", "attached_media")

    def clean_scheduled_datetime(self):
        scheduled_datetime = self.cleaned_data["scheduled_datetime"]
        if scheduled_datetime < timezone.now():
            raise forms.ValidationError(_("Time cannot be in the past"))
        return scheduled_datetime

    def clean(self):
        data = self.cleaned_data

        if data.get("scheduled_datetime"):
            sched_dt = data["scheduled_datetime"]
            sched_tz = timezone.pytz.timezone(data.get("scheduled_tz"))
            sched_dt = sched_tz.localize(sched_dt.replace(tzinfo=None))
            data["scheduled_datetime"] = timezone.localtime(sched_dt)

        if data.get("attached_media") and data.get("media_url"):
            raise forms.ValidationError(_("Only one of media URL or "
                                          "attached media may be provided"))

        if data.get("media_url"):
            response = requests.get(data["media_url"])
            if not response.ok:
                raise forms.ValidationError(_("An error occurred while "
                                              "downloading the media from the URL"))
            ext = mimetypes.guess_extension(response.headers['content-type'])
            ff = tempfile.NamedTemporaryFile(suffix=ext)
            ff.write(response.content)
            img_file = ImageFile(ff, name=ff.name)
            height, width = get_image_dimensions(img_file)
            if height is None or width is None:
                ff.close()
                raise forms.ValidationError(_("Invalid image"))
            data["attached_media"] = img_file
        return data


#!/usr/bin/env python3
import csv
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
import requests
import sys

HEADERS = {'Authorization' : 'Bearer key1agtUnabRLb2LS', 'accept' : 'text/plain'}
BASE_URL = 'https://api.airtable.com/v0/appj3UWymNh6FgtGR/'
VIEW = 'view=Grid%20view'

# Values related to the place table
PLACE_TABLE_NAME = 'Help%20Services'
PLACE_TABLE_VAR = 'placesTableCached'
PLACE_TABLE_MAP = {
    'name' : 'Name',
    'name_es' : 'Name-ES',
    'catSubcatId' : 'CatSubcat',
    'city' : 'City',
    'category' : 'Category',
    'subcategory' : 'Subcategory',
    'phone' : 'Phone Number',
    'address' : 'Physical Address',
    'latitude' : 'Latitude',
    'longitude' : 'Longitude',
    'url' : 'Web address',
    'email' : 'Email Address',
    'hours' : 'Hours of operation',
    'hours_es' : 'Hours of operation-ES',
    'description' : 'Description',
    'description_es' : 'Description-ES',
    'wheelchair' : 'Wheelchair access (y)',
    'languageHelp' : 'Language Help (y)',
    'schedule' : 'schedule',
}

# Values related to the Category table
CATEGORY_TABLE_NAME = 'Categories'
CATEGORY_TABLE_VAR = 'categoryTableCached'
CATEGORY_TABLE_MAP = {
      'name' : 'Name',
      'name_es' : 'Name-ES',
      'subcategories' : 'Subcategories',
}

# Values related to the Subcategory table
SUBCATEGORY_TABLE_NAME = 'Subcategories'
SUBCATEGORY_TABLE_VAR = 'subcategoryTableCached'
SUBCATEGORY_TABLE_MAP = {
      'categoryId' : 'Category',
      'name' : 'Name',
      'name_es' : 'Name-ES',
}

# Values related to the CatSubcat table
CATSUBCAT_TABLE_NAME = 'CatSubcats'
CATSUBCAT_TABLE_VAR = 'catSubcatTableCached'
# Cat Subcat handled specially, so doesn't use table map.
CATSUBCAT_TABLE_MAP = {}

# Values related to the Cities table
CITY_TABLE_NAME = 'Cities'
CITY_TABLE_VAR = 'cityTableCached'
CITY_TABLE_MAP = {
      'name' : 'Name',
}

ALERT_TABLE_NAME = 'Alerts'
ALERT_TABLE_VAR = 'alertTableCached'
ALERT_TABLE_MAP = {
    'title' : 'Title',
    'displayDate' : 'Display Date',
    'startDate' : 'StartDate',
    'endDate' : 'EndDate',
    'note' : 'Notes',
}

SCHEDULE_TABLE_NAME = 'schedule'
SCHEDULE_TABLE_VAR = 'scheduleTableCached'
SCHEDULE_TABLE_MAP = {
    'byDay' : 'byday',
    'opensAt' : 'opens_at',
    'closesAt' : 'closes_at',
    'byMonthDay' : 'bymonthday',
    'validFrom' : 'valid_from',
    'validTo' : 'valid_to',
}

# Make a record in our desired output format
def make_record(record_in, key_pairs):
    record_out = {}
    record_out["id"] = record_in["id"]
    for key in key_pairs:
        new_key = key
        old_key = key_pairs[new_key]
        record_out[new_key] = ''
        if old_key in record_in['fields']:
            record_out[new_key] = record_in['fields'][old_key]
    return record_out

# Make a record in our desired output format -- special handling for
# special CatSubcat features
def make_record_catsubcat(record_in):
    catSubcatId = record_in["id"]
    catSubcatName = record_in['fields']['Name']
    categoryId = record_in['fields']['Category'][0]

    subcategoryId = ''
    subcategoryName = ''
    subcategoryNameSpanish = ''
    if 'Subcategory' in record_in['fields']:
        subcategoryId = record_in['fields']['Subcategory'][0]
        subcategoryName = record_in['fields']['SubcategoryString'][0]
        subcategoryNameSpanish = record_in['fields']['Subcategory-ES'][0]

    record_out = {
      'catSubcatId': catSubcatId,
      'catSubcatName': catSubcatName,
      'categoryId' : categoryId,
      'subcategoryId' : subcategoryId,
      'name' : subcategoryName,
      'name_es' : subcategoryNameSpanish,
      'places' : []
    }
    return record_out

# Use map to convert a table to the form we want. Note the special code for the
# CatSubcat table because it is structured a little different from the others.
def table_map(table_name, table_raw, key_pairs):
    if table_name == CATSUBCAT_TABLE_NAME:
        table = map(lambda record: make_record_catsubcat(record), table_raw)
    else:
        table = map(lambda record: make_record(record, key_pairs), table_raw)
    table = list(table)
    return table

# Air table only allows pages of up to 100 records at a time. This gets a page
def get_page(url):
    r = requests.get(url, headers=HEADERS)
    data = r.json()
    records = data['records']
    offset = None
    if 'offset' in data:
        offset = data['offset']
    return records, offset

def get_table(table_name, mapping):
    # Get the table data from air table
    table_url = BASE_URL+table_name
    page, offset = get_page(table_url+'?'+VIEW)
    table_raw = page
    # Air table only allows 100 records at a time, so loop to get them all
    while offset:
        page, offset = get_page(table_url+'?offset='+offset+'&'+VIEW)
        table_raw.extend(page)
    # Map it into the form we need
    table = table_map(table_name, table_raw, mapping)
    return table

def do_table(table_name, mapping, var_name, f):
    table = get_table(table_name, mapping)
    # Write it into the javascript file
    print('const', var_name, '=', table, ';', file=f)

def do_mailmerge(dir, place_table, category_table, catsubcat_table, language_str):
    mailmerge_table = []
    for record in place_table:
        catsubcats = record['catSubcatId']
        for cat_subcat_id in catsubcats:
            # Wrangle the cats and subcats from the catSubcat table
            catsubcat_record = list(filter(lambda catsubcat_rec: catsubcat_rec['catSubcatId'] == cat_subcat_id, catsubcat_table))
            # Assuming the above returns exactly one record
            category_id = catsubcat_record[0]['categoryId']
            category_record = list(filter(lambda cat_rec: cat_rec['id'] == category_id, category_table))
            # Assuming the above returns exactly one record
            category_str = category_record[0]['name'+language_str]
            subcategory_str = catsubcat_record[0]['name'+language_str]

            mailmerge_table.append({
                'Category' : category_str,
                'Subcategory' : subcategory_str,
                'Service Name' : record['name'],
                'Phone Number' : record['phone'],
                'Physical Address' : record['address'],
                'Hours of operation' : record['hours'],
                'Description' : record['description'+language_str],
                'Wheelchair access (y)' : record['wheelchair'],
                'Language Help (y)' : record['languageHelp'],
                'Web address' : record['url'],
                'Email Address' : record['email'],
            })

    mailmerge_table = sorted(mailmerge_table, key = lambda i: (i['Category'], i['Subcategory'], i['Service Name']))

    # Save as csv file
    csv_columns = list(mailmerge_table[0].keys())
    filename = dir+'final_book'+language_str
    csv_file = filename+'.csv'
    xls_file = filename+'.xls'
    try:
        with open(csv_file, 'w') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
            writer.writeheader()
            for data in mailmerge_table:
                writer.writerow(data)
    except IOError:
        print("I/O error")
        exit

    # Convert the csv file to an Excel file
    f = open(csv_file, 'r')
    reader = csv.reader(f)
    wb = Workbook()
    ws = wb.worksheets[0]
    ws.title = "Sheet1"

    for row_index, row in enumerate(reader):
        for column_index, cell in enumerate(row):
            column_letter = get_column_letter((column_index + 1))
            ws[column_letter+str(row_index+1)] = cell

    wb.save(filename = xls_file)

dir = './'
if len(sys.argv) > 1:
    dir = sys.argv[1]
    if dir[-1] != '/':
        dir += '/'
# Process each table
f = open(dir+'cachedInlineTables.js', 'w')
do_table(PLACE_TABLE_NAME, PLACE_TABLE_MAP, PLACE_TABLE_VAR, f)
do_table(CATEGORY_TABLE_NAME, CATEGORY_TABLE_MAP, CATEGORY_TABLE_VAR, f)
do_table(SUBCATEGORY_TABLE_NAME, SUBCATEGORY_TABLE_MAP, SUBCATEGORY_TABLE_VAR, f)
do_table(CATSUBCAT_TABLE_NAME, CATSUBCAT_TABLE_MAP, CATSUBCAT_TABLE_VAR, f)
do_table(CITY_TABLE_NAME, CITY_TABLE_MAP, CITY_TABLE_VAR, f)
do_table(ALERT_TABLE_NAME, ALERT_TABLE_MAP, ALERT_TABLE_VAR, f)
do_table(SCHEDULE_TABLE_NAME, SCHEDULE_TABLE_MAP, SCHEDULE_TABLE_VAR, f)

place_table = get_table(PLACE_TABLE_NAME, PLACE_TABLE_MAP)
category_table = get_table(CATEGORY_TABLE_NAME, CATEGORY_TABLE_MAP)
catsubcat_table = get_table(CATSUBCAT_TABLE_NAME, CATSUBCAT_TABLE_MAP)
do_mailmerge(dir, place_table, category_table, catsubcat_table, '')
do_mailmerge(dir, place_table, category_table, catsubcat_table, '_es')


import math
import matplotlib.pyplot as plt

def integral(n):
	val = [1 - math.exp(-1)]
	if n < 1:
		return val
	for i in range(1,n+1):
		val.append(1 - i*val[i-1])
	return val

n = 20
x = range(0,n+1)
y = integral(n)
plt.plot(x, y, 'r')
plt.xlabel('k')
plt.ylabel('I(k)')
plt.show()


# import math

import requests
import re

def get_url():
  return ''.join([
    'http'+ ':' + '//',
    '.'.join(['www', 'cyber'+'syndrome', 'net']),
    '/'+'search.cgi'+'?' + '&'.join(['q=JP', 'a=A', 'f=d', 's=new', 'n=100'])
  ])

def read_page():
  url = get_url()
  ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
  headers = {
    'User-Agent': ua,
  }
  session = requests.Session()
  session.headers.update(headers)
  res = session.get(url)
  return res.text

def parse_as(html):
  re_str = r'as=\[([\d,]+)\];'
  r = re.search(re_str, html)
  res = ''
  if r:
    res = r.group(1).split(',')
  # print('as:', res)
  return res

def parse_ps(html):
  re_str = r'var ps=\[([\d,]+)\];'
  r = re.search(re_str, html)
  res = ''
  if r:
    res = r.group(1).split(',')
  # print('ps:', res)
  return res

def parse_n(html, ps):
  re_str = r'var n=(\(.+?\)\%\d+);'
  r = re.search(re_str, html)
  res = ''
  if r:
    _n = r.group(1)
    # print('_n:', _n)
    for r2 in re.findall(r'ps\[(\d+)\]', _n):
      # print('n:r2:', r2)
      _n = _n.replace('ps[%s]'%r2, ps[int(r2)])
    # print('_n:', _n)
    res = eval(_n)
  # print('n:', res)
  return res

def decode_ip_addrs(var_as, var_ps, var_n):
  as1 = var_as[0:var_n]
  as2 = var_as[var_n:]

  # tmp = {}
  # for i, a in enumerate(as2+as1):
  #   idx = math.floor(i/4)
  #   # print('idx:', idx)

  #   if not idx in tmp:
  #     # tmp[idx] = [None, None, None, None]
  #     tmp[idx] = []

  #   # if i%4 == 0:
  #   #   tmp[idx].append(a)
  #   # elif i%4 == 3:
  #   #   tmp[idx].append(a)
  #   # else:
  #   #   tmp[idx].append(a)
  #   tmp[idx].append(a)
  # for i in sorted(tmp.keys()):
  # for i in tmp.keys():
  #   # print('n%s:'%(i+1), '%s:%s'%(''.join(tmp[i]), ps[i]))
  #   addrs = '%s:%s'%('.'.join(tmp[i]), var_ps[i])
  #   res.append(addrs)
  #   # print('n%s:'%(i+1),  addrs)
  res = []
  tmp = []
  for i, a in enumerate(as2+as1):
    tmp.append(a)
    if len(tmp) == 4:
      ps_idx = len(res)
      adrs = '.'.join(tmp) + ':' + var_ps[ps_idx]
      res.append(adrs)
      tmp = []

  return res

def get_proxies():
  html = read_page()
  # print(html)

  var_as = parse_as(html)
  var_ps = parse_ps(html)
  var_n = parse_n(html, var_ps)
  ips = decode_ip_addrs(var_as, var_ps, var_n)
  return ips

if __name__ == '__main__':
  proxies = get_proxies()
  print(proxies)


# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function

import math

import numpy as np
import pytest

from .. import ChainedNeqSys, ConditionalNeqSys, NeqSys

try:
    import pynleq2  # noqa
except ImportError:
    HAVE_PYNLEQ2 = False
else:
    HAVE_PYNLEQ2 = True

def f(x, params):
    # docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root.html
    return [
        x[0] + (x[0] - x[1]) ** params[0] / 2 - 1,
        (x[1] - x[0]) ** params[0] / 2 + x[1],
    ]

def j(x, params):
    return [
        [
            1 + params[0] / 2 * (x[0] - x[1]) ** (params[0] - 1),
            -params[0] / 2 * (x[0] - x[1]) ** (params[0] - 1),
        ],
        [
            -params[0] / 2 * (x[1] - x[0]) ** (params[0] - 1),
            1 + params[0] / 2 * (x[1] - x[0]) ** (params[0] - 1),
        ],
    ]

def _test_fail(solver, **kwargs):
    def _f(x, p):
        return [p[0] + x[0] ** 2]

    def _j(x, p):
        return [[2 * x[0]]]

    ns = NeqSys(1, 1, _f, jac=_j)
    x, res = ns.solve([1], [1], solver=solver, **kwargs)
    assert len(x) == 1
    assert abs(x[0]) < 1e-8
    assert not res["success"]

def test_fail():
    _test_fail("scipy")

def test_neqsys_rms():
    ns = NeqSys(2, 2, f)
    x = [[1, 0], [2, 1], [3, 2], [7, 4], [5, 13]]
    p = [3]
    rms = ns.rms(x, p)
    ref = [np.sqrt(np.sum(np.square(f(x[i], p))) / 2) for i in range(5)]
    assert np.allclose(rms, ref)

def _test_neqsys_params(solver, **kwargs):
    ns = NeqSys(2, 2, f, jac=j)
    x, sol = ns.solve([0, 0], [3], solver=solver, **kwargs)
    assert abs(x[0] - 0.8411639) < 2e-7
    assert abs(x[1] - 0.1588361) < 2e-7

def _test_neqsys_solve_series(solver):
    ns = NeqSys(2, 2, f, jac=j)
    x, sol = ns.solve_series(solver, [0, 0], [0], var_data=[2, 3], var_idx=0)
    assert abs(x[0, 0] - 0.5) < 2e-7
    assert abs(x[0, 1] + 0.5) < 2e-7
    assert abs(x[1, 0] - 0.8411639) < 2e-7
    assert abs(x[1, 1] - 0.1588361) < 2e-7

def test_neqsys_params_scipy():
    _test_neqsys_params("scipy")

@pytest.mark.skipif(not HAVE_PYNLEQ2, reason="pynleq2 not installed on system.")
def test_neqsys_params_nleq2():
    _test_neqsys_params("nleq2")

def _test_neqsys_no_params(solver, **kwargs):
    ns = NeqSys(2, 2, lambda x: f(x, [3]), jac=lambda x: j(x, [3]))
    x, sol = ns.solve([0, 0], solver=solver, **kwargs)
    assert abs(x[0] - 0.8411639) < 2e-7
    assert abs(x[1] - 0.1588361) < 2e-7

def test_neqsys_no_params_scipy():
    _test_neqsys_no_params("scipy")

@pytest.mark.skipif(not HAVE_PYNLEQ2, reason="pynleq2 not installed on system.")
def test_neqsys_no_params_nleq2():
    _test_neqsys_no_params("nleq2")

def test_ConditionalNeqSys1():
    from math import pi, sin

    def f_a(x, p):
        return [sin(p[0] * x[0])]  # when x <= 0

    def f_b(x, p):
        return [x[0] * (p[1] - x[0])]  # when x >= 0

    def factory(conds):
        return NeqSys(1, 1, f_b) if conds[0] else NeqSys(1, 1, f_a)

    cneqsys = ConditionalNeqSys(
        [(lambda x, p: x[0] > 0, lambda x, p: x[0] >= 0)], factory
    )
    x, sol = cneqsys.solve([0], [pi, 3], solver="scipy")
    assert sol["success"]
    assert abs(x[0]) < 1e-13
    x, sol = cneqsys.solve([-1.4], [pi, 3], solver="scipy")
    assert sol["success"]
    assert abs(x[0] + 1) < 1e-13
    x, sol = cneqsys.solve([2], [pi, 3], solver="scipy")
    assert sol["success"]
    assert abs(x[0] - 3) < 1e-13
    x, sol = cneqsys.solve([7], [pi, 3], solver="scipy")
    assert sol["success"]
    assert abs(x[0] - 3) < 1e-13

def _check_NaCl(cneqsys, guesses, cases=-1, **kwargs):
    # Sodium chloride (NaCl) precipitation
    _init_final = [
        ([1, 1, 1], [2, 2, 0]),
        ([1, 1, 0], [1, 1, 0]),
        ([3, 3, 3], [2, 2, 4]),
        ([2, 2, 0], [2, 2, 0]),
        ([2 + 1e-8, 2 + 1e-8, 0], [2, 2, 1e-8]),
        ([3, 3, 0], [2, 2, 1]),
        ([0, 0, 3], [2, 2, 1]),
        ([0, 0, 2], [2, 2, 0]),
        ([2, 2, 2], [2, 2, 2]),
    ]
    info_dicts = []
    for init, final in _init_final[:cases]:
        print(init)
        for guess in guesses:
            print(guess)
            if guess is None:
                guess = init
            x, info_dict = cneqsys.solve(guess, init + [4], solver="scipy", **kwargs)
            assert info_dict["success"] and np.allclose(x, final)
            info_dicts.append(info_dict)
    return info_dicts

def _factory_lin(conds):
    # This is an example of NaCl precipitation
    # x = Na+, Cl-, NaCl(s)
    # p = [Na+]0, [Cl-]0, [NaCl(s)]0, Ksp
    # f[0] = x[0] + x[2] - p[0] - p[2]
    # f[1] = x[1] + x[2] - p[1] - p[2]
    # switch to precipitation: x[0]*x[1] > p[3]
    # keep precipitation if: x[2] > 0
    #
    # If we have a precipitate
    #    f[2] = x[0]*x[1] - p[3]
    # otherwise:
    #    f[2] = x[2]
    precip = conds[0]

    def cb(x, p):
        f = [None] * 3
        f[0] = x[0] + x[2] - p[0] - p[2]
        f[1] = x[1] + x[2] - p[1] - p[2]
        if precip:
            f[2] = x[0] * x[1] - p[3]
        else:
            f[2] = x[2]
        return f

    return NeqSys(3, 3, cb)

def _factory_log(small):
    # This is equivalent to _factory_lin
    # but this time variable transformations
    # are performed

    def _inner_factory(conds):
        precip = conds[0]

        def pre_processor(x, p):
            return np.log(np.asarray(x) + math.exp(small)), p

        def post_processor(x, p):
            return np.exp(x), p

        def fun(x, p):
            f = [None] * 3
            f[0] = math.exp(x[0]) + math.exp(x[2]) - p[0] - p[2]
            f[1] = math.exp(x[1]) + math.exp(x[2]) - p[1] - p[2]
            if precip:
                f[2] = x[0] + x[1] - math.log(p[3])
            else:
                f[2] = x[2] - small
            return f

        def jac(x, p):
            jout = np.empty((3, 3))

            jout[0, 0] = math.exp(x[0])
            jout[0, 1] = 0
            jout[0, 2] = math.exp(x[2])

            jout[1, 0] = 0
            jout[1, 1] = math.exp(x[1])
            jout[1, 2] = math.exp(x[2])

            if precip:
                jout[2, 0] = 1
                jout[2, 1] = 1
                jout[2, 2] = 0
            else:
                jout[2, 0] = 0
                jout[2, 1] = 0
                jout[2, 2] = 1

            return jout

        return NeqSys(
            3,
            3,
            fun,
            jac,
            pre_processors=[pre_processor],
            post_processors=[post_processor],
        )

    return _inner_factory

def _get_cneqsys2():
    return ConditionalNeqSys(
        [(lambda x, p: x[0] * x[1] > p[3], lambda x, p: x[2] > 0)], _factory_lin
    )

def test_ConditionalNeqSys2():
    _check_NaCl(_get_cneqsys2(), [(1, 1, 1), (1, 1, 0), (2, 2, 0), (1, 1, 3)])

def _get_cneqsys3(small):
    return ConditionalNeqSys(
        [(lambda x, p: x[0] * x[1] > p[3], lambda x, p: x[2] > math.exp(small))],
        _factory_log(small),
    )

def test_ConditionalNeqSys3():
    _check_NaCl(_get_cneqsys3(-60), [None], 4, method="lm")

def test_version():
    from pyneqsys import __version__

    assert int(__version__.split(".")[0]) >= 0

def test_solve_series():
    neqsys = NeqSys(1, 1, lambda x, p: [x[0] - p[0]])
    xout, sols = neqsys.solve_series([0], [0], [0, 1, 2, 3], 0, solver="scipy")
    assert np.allclose(xout[:, 0], [0, 1, 2, 3])

def test_ChainedNeqSys():
    neqsys_log = _get_cneqsys3(-60)
    neqsys_lin = _get_cneqsys2()
    chained = ChainedNeqSys([neqsys_log, neqsys_lin])
    info_dicts = _check_NaCl(chained, [None], 2, method="lm")
    for nfo in info_dicts:
        assert (
            nfo["intermediate_info"][0]["success"]
            and nfo["intermediate_info"][1]["success"]
        )

_powell_ref = (0.0001477105829534399, 6.769995622556115071)

def _powell(x, params, backend=math):
    A, exp = params[0], backend.exp
    return A * x[0] * x[1] - 1, exp(-x[0]) + exp(-x[1]) - (1 + A ** -1)

def _test_powell(sys_solver_pairs, x0=(1, 1), par=(1000.0,)):
    for sys, solver in sys_solver_pairs:
        x0, info = sys.solve(x0, par, solver=solver, tol=1e-12)
    assert info["success"]
    x = sorted(x0)
    assert abs(_powell_ref[0] - x[0]) < 2e-11
    assert abs(_powell_ref[1] - x[1]) < 6e-10

def test_chained_solvers():
    powell_numpy = NeqSys(2, 2, _powell)
    powell_mpmath = NeqSys(2, 2, _powell)
    _test_powell([(powell_numpy, None), (powell_mpmath, "mpmath")])

def test_x_by_name():
    powell_sys = NeqSys(2, f=_powell, names=["u", "v"], x_by_name=True)
    _test_powell(zip([powell_sys] * 2, [None, "mpmath"]), x0={"u": 1, "v": 1})


from __future__ import print_function
import logging
import re

from db_endpoint import DBEndpoint,TCPFrameClient
from utils import blockify
from thread_executor import ThreadExecutor, ThreadExecutorError
from ida_ts import get_func_length, get_func_data, get_func_comment,\
    set_func_comment, Functions, first_func_addr, GetFunctionName,\
    is_func_chunked, make_name

class FCatalogClientError(Exception): pass

logger = logging.getLogger(__name__)

# Minimum function size (in bytes) to be considered when trying to find
# similars.
MIN_FUNC_LENGTH = 0x60

FCATALOG_FUNC_NAME_PREFIX = 'FCATALOG__'
FCATALOG_COMMENT_PREFIX = '%%%'

# The grade of similarity for each function is a number between 0 and this
# constant (Inclusive):
MAX_SIM_GRADE = 16

# Amount of similar functions to return in every inquiry for similars function
# for a specific function:
NUM_SIMILARS = 1

# Amount of functions to be sent together to remote server when looking for
# similars:
GET_SIMILARS_BATCH_SIZE = 20

#########################################################################

def is_func_fcatalog(func_addr):
    """
    Have we obtained the name for this function from fcatalog server?
    We know this by the name of the function.
    """
    logger.debug('is_func_fcatalog {}'.format(func_addr))
    func_name = GetFunctionName(func_addr)
    return func_name.startswith(FCATALOG_FUNC_NAME_PREFIX)

def is_func_long_enough(func_addr):
    """
    Check if a given function is of suitable size to be commited.
    """
    logger.debug('is_func_long_enough {}'.format(func_addr))
    func_length = get_func_length(func_addr)
    if func_length < MIN_FUNC_LENGTH:
        return False

    return True

###########################################################################

def strip_comment_fcatalog(comment):
    """
    Remove all fcatalog comments from a given comment.
    """
    res_lines = []

    # Get only lines that don't start with FCATALOG_COMMENT_PREFIX:
    lines = comment.splitlines()
    for ln in lines:
        if ln.startswith(FCATALOG_COMMENT_PREFIX):
            continue
        res_lines.append(ln)

    return '\n'.join(res_lines)

def add_comment_fcatalog(comment,fcatalog_comment):
    """
    Add fcatalog comment to a function.
    """
    res_lines = []

    # Add the fcatalog_comment lines with a prefix:
    for ln in fcatalog_comment.splitlines():
        res_lines.append(FCATALOG_COMMENT_PREFIX + ' ' + ln)

    # Add the rest of the comment lines:
    for ln in comment.splitlines():
        res_lines.append(ln)

    return '\n'.join(res_lines)

def make_fcatalog_name(func_name,sim_grade,func_addr):
    """
    Make an fcatalog function name using function name and sim_grade.
    """
    lres = []
    lres.append(FCATALOG_FUNC_NAME_PREFIX)
    lres.append('{:0>2}__'.format(sim_grade))
    lres.append(func_name)
    lres.append('__{:0>8X}'.format(func_addr & 0xffffffff))
    return ''.join(lres)

###########################################################################

class FCatalogClient(object):
    def __init__(self,remote,db_name,exclude_pattern=None):
        # Keep remote address:
        self._remote = remote

        # Keep remote db name:
        self._db_name = db_name

        # A thread executor. Allows only one task to be run every time.
        self._te = ThreadExecutor()

        # A regexp pattern that identifies functions that are not named, and
        # should be ignored.
        self._exclude_pattern = exclude_pattern

        # A thread safe print function. I am not sure if this is rquired. It is
        # done to be one the safe side:
        self._print = print

    def _is_func_named(self,func_addr):
        """
        Check if a function was ever named by the user.
        """
        logger.debug('_is_func_named {}'.format(func_addr))
        func_name = GetFunctionName(func_addr)

        # Avoid functions like sub_409f498:
        if func_name.startswith('sub_'):
            return False

        # If exclude_pattern was provided, make sure that the function
        # name does not match it:
        if self._exclude_pattern is not None:
            mt = re.match(self._exclude_pattern,func_name)
            if mt is not None:
                return False

        # Avoid reindexing FCATALOG functions:
        if is_func_fcatalog(func_addr):
            return False

        return True

    def _is_func_commit_candidate(self,func_addr):
        """
        Is this function a candidate for committing?
        """
        # Don't commit if chunked:
        if is_func_chunked(func_addr):
            return False

        if not self._is_func_named(func_addr):
            return False

        if not is_func_long_enough(func_addr):
            return False

        return True

    def _is_func_find_candidate(self,func_addr):
        """
        Is this function a candidate for finding from database (Finding similars
        for this function?)
        """
        if is_func_chunked(func_addr):
            return False

        if self._is_func_named(func_addr):
            return False

        if not is_func_long_enough(func_addr):
            return False

        return True

    def _iter_func_find_candidates(self):
        """
        Iterate over all functions that are candidates for finding similars from
        the remote database.
        This function is IDA read thread safe.
        """
        for func_addr in Functions():
            if self._is_func_find_candidate(func_addr):
                yield func_addr

    def _commit_funcs_thread(self):
        """
        Commit all the named functions from this idb to the server.
        This is an IDA read thread safe function.
        """
        self._print('Commiting functions...')
        # Set up a connection to remote db:
        frame_endpoint = TCPFrameClient(self._remote)
        fdb = DBEndpoint(frame_endpoint,self._db_name)

        for func_addr in Functions():
            logger.debug('Iterating over func_addr: {}'.format(func_addr))
            if not self._is_func_commit_candidate(func_addr):
                continue

            func_name = GetFunctionName(func_addr)
            func_comment = strip_comment_fcatalog(get_func_comment(func_addr))
            func_data = get_func_data(func_addr)

            # If we had problems reading the function data, we skip it.
            if func_data is None:
                self._print('!> Skipping {}'.format(func_name))
                continue

            fdb.add_function(func_name,func_comment,func_data)
            self._print(func_name)

        # Close db:
        fdb.close()
        self._print('Done commiting functions.')

    def commit_funcs(self):
        """
        Commit all functions from this IDB to the server.
        """
        try:
            t = self._te.execute(self._commit_funcs_thread)
        except ThreadExecutorError:
            print('Another operation is currently running. Please wait.')

    def _batch_similars(self,fdb,l_func_addr):
        """
        Given a list of function addresses, request similars for each of those
        functions. Then wait for all the responses, and return a list of tuples
        of the form: (func_addr,similars)
        This function is IDA read thread safe.
        """
        # Send requests for similars for every function in l_func_addr list:
        for func_addr in l_func_addr:
            func_data = get_func_data(func_addr)
            fdb.request_similars(func_data,1)

        # Collect responses from remote server:
        lres = []
        for func_addr in l_func_addr:
            similars = fdb.response_similars()
            lres.append((func_addr,similars))

        return lres

    def _find_similars_thread(self,similarity_cut,batch_size):
        """
        For each unnamed function in this database find a similar functions
        from the fcatalog remote db, and rename appropriately.
        This thread is IDA write thread safe.
        """
        self._print('Finding similars...')

        # Set up a connection to remote db:
        frame_endpoint = TCPFrameClient(self._remote)
        fdb = DBEndpoint(frame_endpoint,self._db_name)

        # Iterate over blocks of candidate functions addresses:
        for l_func_addr in blockify(self._iter_func_find_candidates(),\
                batch_size):
            # Send block to remote server and get results:
            bsimilars = self._batch_similars(fdb,l_func_addr)
            # Iterate over functions and results:
            for func_addr,similars in bsimilars:

                if len(similars) == 0:
                    # No similars found.
                    continue

                # Get the first entry (Highest similarity):
                fsim = similars[0]

                # Discard if doesn't pass the similarity cut:
                if fsim.sim_grade < similarity_cut:
                    continue

                old_name = GetFunctionName(func_addr)

                # Generate new name:
                new_name = make_fcatalog_name(fsim.name,fsim.sim_grade,func_addr)

                # If name matches old name, skip:
                if new_name == old_name:
                    continue

                # Set function to have the new name:
                make_name(func_addr,new_name)

                # Add the comments from the fcatalog entry:
                func_comment = get_func_comment(func_addr)
                func_comment_new = \
                        add_comment_fcatalog(func_comment,fsim.comment)
                set_func_comment(func_addr,func_comment_new)

                self._print('{} --> {}'.format(old_name,new_name))

        # Close db:
        fdb.close()

        self._print('Done finding similars.')

    def find_similars(self,similarity_cut,batch_size=GET_SIMILARS_BATCH_SIZE):
        """
        For each unnamed function in this database find a similar functions
        from the fcatalog remote db, and rename appropriately.
        """
        try:
            t = self._te.execute(self._find_similars_thread,\
                    similarity_cut,batch_size)
        except ThreadExecutorError:
            print('Another operation is currently running. Please wait.')

def clean_idb():
    """
    Clean all fcatalog marks and names from this idb.
    """
    print('Cleaning idb...')
    for func_addr in Functions():
        # Skip functions that are not fcatalog named:
        if not is_func_fcatalog(func_addr):
            continue

        print('{}'.format(GetFunctionName(func_addr)))
        # Clear function's name:
        make_name(func_addr,'')

        # Clean fcatalog comments from the function:
        func_comment = get_func_comment(func_addr)
        set_func_comment(func_addr,strip_comment_fcatalog(func_comment))
    print('Done cleaning idb.')


# -*- coding: utf-8 -*-
"""
Created on Sat Jun  1 17:39:54 2019

@author: Akshay
"""
def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False
def alpha(line):
    temp=[]
    for word in line.split():
        if is_number(word):
            for num in list(word):
                temp.append(num)
        else:
            temp.append(word)
    return " ".join(temp)
def clean_text(text):
    '''not working'''

    #text=re.sub(r'(\d+)',r'',text)
    text=text.replace(u',','')
    text=text.replace(u'"','')
    text=text.replace(u'(','')
    text=text.replace(u')','')
    text=text.replace(u'"','')
    text=text.replace(u':','')
    text=text.replace(u"'",'')
    text=text.replace(u"‘‘",'')
    text=text.replace(u"’’",'')
    text=text.replace(u"''",'')
    text=text.replace(u".",'')
    text=text.replace(u"@",'')
    text=text.replace(u"%",'')
    text=text.replace(u"$",'')
    text=text.replace(u"*",'')
    text=text.replace(u"-",'')
    text=text.replace(u"&",'')
    text=text.replace(u"!",'')
    text=text.replace(u"^",'')
    text=text.replace(u"#",'')
    text=text.replace(u"*",'')
    text=text.replace(u"=",'')
    text=text.replace(u"<",'')
    text=text.replace(u">",'')
    text=text.replace(u"_",'')
    text=text.replace(u"*",'')

    #!"#$%&'()*+,-.:;<=>?@[]^_`{|}~

    return text

def clean_lines(line):
    # prepare a translation table to remove punctuation
    table = str.maketrans('', '', '!"#$%&\'()*+,-.:;<=>?@[\\]^_`{|}~')

    # strip source cnn office if it exists
    """ index = line.find('PUBLISHED:')
    if index > -1:
        line = line[index+len('(CNN)'):]"""
    # tokenize on white space
    line = line.split()
    # convert to lower case
    line = [word.lower() for word in line]
    # remove punctuation from each token
    line = [w.translate(table) for w in line]

    temp = []
    for word in line:
        if is_number(word):
            for num in list(word):
                temp.append(num)
        else:
            temp.append(word)
    line = temp
    # store as string

    # remove empty strings
    #cleaned = [c for c in cleaned if len(c) > 0]
    return ' '.join(line)
def hindi_clean(data):
    input_text=data
    remove_nuktas=False
    factory=IndicNormalizerFactory()
    normalizer=factory.get_normalizer("hi",remove_nuktas)
    output_text=normalizer.normalize(input_text)
    output_text=clean_lines(output_text)
    output_text=alpha(output_text)
    return output_text+'\n'

a = open("monolingual.hi","r",encoding="utf-8")
#data = a.readline()

count = 0
data = ''
while count != 500000:
    try:
        sentence = a.readline()
        sentence = hindi_clean(sentence)
        if 10 <len(sentence.strip().split()) <=50:
            data = data + sentence
            count+=1
            if count%1000==0:
                print(count)
    except UnicodeDecodeError:
        pass
a.close()
with open("hindi_mono_50000_iitB.txt","w",encoding="utf-8") as hindi:
    hindi.write(data)
with open("hindi_mono_50000_iitB.txt","r",encoding="utf-8") as hindi:
    data = hindi.readlines()
import re

INDIC_NLP_LIB_HOME=r"C:\Users\Akshay\Documents\GitHub\indic_nlp_library"

# The path to the local git repo for Indic NLP Resources
INDIC_NLP_RESOURCES=r"C:\Users\Akshay\Documents\GitHub\indic_nlp_resources"
import sys
sys.path.append('{}/src'.format(INDIC_NLP_LIB_HOME))
from indicnlp import common
common.set_resources_path(INDIC_NLP_RESOURCES)
from indicnlp import loader
loader.load()

# formating

from indicnlp.normalize.indic_normalize import IndicNormalizerFactory

input_text=data[2]
remove_nuktas=True
factory=IndicNormalizerFactory()
normalizer=factory.get_normalizer("hi",remove_nuktas)
output_text=normalizer.normalize(input_text)
print(input_text)
print(output_text)
print('Length before normalization: {}'.format(len(input_text)))
print('Length after normalization: {}'.format(len(output_text)))

line = data[1000]
import collections
temp=data.split()
vocab_eng = collections.Counter(temp)
vocab_eng = zip(vocab_eng.keys(),vocab_eng.values())
vocab_eng = list(vocab_eng)
vocab_eng = sorted(vocab_eng,key=lambda x : x[1],reverse=True)
vocab_eng = vocab_eng[:50000]
vocab_eng_text = ''
for word,count in vocab_eng:
    vocab_eng_text+=word+"\n"
with open("hindi_vocab.txt","w",encoding="utf-8") as a:
    a.write(vocab_eng_text)

#def clean_lines(line):
#    # prepare a translation table to remove punctuation
#    table = str.maketrans('', '', '!"#$%&\'()*+,-.:;<=>?@[\\]^_`{|}~')
#
#    # strip source cnn office if it exists
#    """ index = line.find('PUBLISHED:')
#    if index > -1:
#        line = line[index+len('(CNN)'):]"""
#    # tokenize on white space
#    line = line.split()
#    # convert to lower case
#    line = [word.lower() for word in line]
#    # remove punctuation from each token
#    line = [w.translate(table) for w in line]
#
#    temp = []
#    for word in line:
#        if is_number(word):
#            for num in list(word):
#                temp.append(num)
#        else:
#            temp.append(word)
#    line = temp
#    # store as string
#
#    # remove empty strings
#    #cleaned = [c for c in cleaned if len(c) > 0]
#    return ' '.join(line) + '\n'