# Library and Modules used
import os
import cv2
import numpy as np
import customtkinter as ctk
from tkinter import *
from rembg import remove
from pathlib import Path
from threading import Thread
from datetime import datetime
from deepface import DeepFace
from PIL import Image ,ImageTk
from tkinter import ttk, filedialog
from pdf2image import convert_from_path
from tkinter.messagebox import showerror, showinfo

# Creating functions whihch used to provide fnctionality to pages
def Imgo( file, w, h) :

    # Image processing
    img = Image.open( file )
    pht = ImageTk.PhotoImage( img.resize( (w,h), Image.Resampling.LANCZOS))
    return pht

def change( can, page) :

    # Switching canvas
    can.destroy()
    page()

def mistake( message) :

    # Pop up window for errors
    showerror( title = "Error Occured", message = message )

def inform( message) :

    # Pop up window for info
    showinfo( title = "Done", message = message )

def analysis1( path1, path2, sample ) :

    # First method for image analysis
    result_1 = DeepFace.verify( img1_path = path1 , img2_path = path2,
                                    enforce_detection = False, model_name = "VGG-Face",
                                        distance_metric = "cosine", detector_backend = "opencv" )

    if result_1["verified"] :

        # Add image if match
        matches.add( sample )

def analysis2( path1, path2, sample ) :

    # Second method for image analysis
    result_2 = DeepFace.verify( img1_path = path1 , img2_path = path2,
                                    enforce_detection = False, model_name = "VGG-Face",
                                        distance_metric = "euclidean_l2", detector_backend = "ssd" )

    if result_2["verified"] :

        # Add image if match
        matches.add( sample )

def sortingImages() :

    # Sorting Images and Saving them in folders
    if ( values[2] != "" and values[4] != "" ) :

        main_img = {}
        check_img = {}

        # Processing First folder
        for img in os.listdir( values[2] ) :

            main_path = values[2] + '/' + str(img)
            main_img[img] = cv2.imread( main_path )

        # Processing Second folder
        for img in os.listdir( values[4] ) :

            check_path = values[4] + '/' + str(img)
            check_img[img] = cv2.imread( check_path )

        # Analysing both the folders together
        for x in main_img.keys() :

            for y in check_img.keys() :

                # Both at same time
                Thread(target = analysis1( main_img[x], check_img[y], y )).start()
                Thread(target = analysis2( main_img[x], check_img[y], y )).start()

            new_dir = os.path.join( values[2], str( x[ : len(x)-4] + "match") )
            os.mkdir( new_dir )

            for i in matches :

                # Saving Images to new directory
                cv2.imwrite( os.path.join( new_dir, i), check_img[i] )

            # Clearing the previous data from set
            matches.clear()

        # Message of completion
        inform( "Sorted And Saved" )
        values[2] = ""
        values[4] = ""

    else :

        # Showing error due to empty credientials
        mistake( "Empty Fields" )

def findingImages( label ) :

    # Finding similar images
    if ( values[0] != "" and values[2] != "" ) :

        # Processing image
        values[1] = cv2.imread( values[0] )

        # Getting into images of given folder
        for img in os.listdir( values[2] ) :

            img2_path = values[2] + '/' + str(img)
            values[3] = cv2.imread( img2_path )

            # Both at same time
            Thread(target = analysis1( values[1], values[3], img )).start()
            Thread(target = analysis2( values[1], values[3], img )).start()

        # For searching folder, method 2

        # df = DeepFace.find( img_path = values[0], db_path = values[2], enforce_detection = False )
        # res = pd.DataFrame( df )
        # for i in res["identity"] :
        #     print(i)

    # Show data
    if matches == set() :

        # Message in the frame
        label.configure( text = "No File Found")

    else :

        # Showing results in order
        x = 50
        output = ""

        for i in matches :

            if ( len(output + i) > x ) :

                x = x + 50
                output = output + os.linesep

            output = str(output + i + ", ")

        label.configure( text = output[:len(output)-2],
                            text_font = (ft[1], 20, "bold"))
        label.place_configure( x = 50, y = 50, anchor = "nw" )

    # Clearing previously processed data
    values[0] = ""
    values[1] = np.array([0,0,0])
    values[2] = ""
    values[3] = np.array([0,0,0])
    matches.clear()

def convertFile( can, formate ) :

    if ( values[0] == "" ) :
        mistake("Add or Enter File Path")
        return

    # Coverting files to another specified formate
    convert_to = formate.get()
    if (convert_to == "Select Type ") :
        mistake("Select File Format")
        return

    file_types = { "Select Type " : False,
                    "   PDF" : [ "PDF file", "*.pdf"],
                     "   PNG" : [ "PNG file", "*.png"],
                      "   JPG" : [ "JPG file", "*.jpg"],
                       "   JPEG" : [ "JPEG file", "*.jpeg"] }

    # Formate to which we have to convert
    convert_to = file_types[convert_to]

    try :
        # Checking Entrybox
        if( values[0][-3:] == convert_to[1][2:] ) :

            # Error due to converting to same formate
            mistake("SAME FORMATE")

        elif ( values[0] != "" ) and convert_to :

            # Converting pdf files
            if ( values[0][-3:] == 'pdf' ) :

                # Finding address to save file
                dirc = filedialog.askdirectory( initialdir = os.getcwd(), title = "Save file")

                # Poppler's Path
                poppler_path = os.path.join( os.getcwd(), r"poppler-23.01.0\Library\bin")

                # Processing page(s) of pdf
                pages = convert_from_path( pdf_path = values[0], poppler_path = poppler_path )

                # Getting into page(s) of pdf
                for page in pages :

                    file = datetime.now().strftime('%Y%m%d%H%M%S')
                    file = dirc + '/' + str(file) + convert_to[1][1:]
                    page.save(  Path(file), convert_to[1][2:])

            # Converting to pdf formate
            elif ( convert_to[1][2:] == "pdf" ) :

                # Finding address to save file
                file = filedialog.asksaveasfile( initialdir = os.getcwd(),
                                                    title = "Save file",
                                                        defaultextension = f"{convert_to[1]}",
                                                            filetypes =[( convert_to[0], f"{convert_to[1]}" )] )

                # Saving file
                let = Image.open( values[0] )
                to_pdf = let.convert('RGB')
                to_pdf.save( file.name )
                file.close()

            # For other types of formate conversion
            else :

                # Finding address to save file
                file = filedialog.asksaveasfile( initialdir = os.getcwd(), title = "Save file",
                                                    defaultextension = f"{convert_to[1]}",
                                                        filetypes =[( convert_to[0], f"{convert_to[1]}" )] )

                # Saving file
                values[1] = cv2.imread( values[0] )
                cv2.imwrite( file.name, values[1])
                file.close()

            # Clearing previously processed data
            values[0] = ""
            values[1] = np.array([0,0,0])

            # Message of complition and return back
            inform( "FILE SAVED" )
            change( can, menuPage)

        else :

            # Showing error due empty credientials
            mistake( "ENTER FILE NAME!" )

    except :
        mistake("Invalid Entry")

def removeBackground( click ) :

    # Removing the background of given Image

    # Checking entry
    if values[0] == "" :

        # Showing error due to empty credientials
        mistake( "FILE NOT FOUND!")

    else :

        # Removing the background of the Image
        original = cv2.imread( values[0] )
        values[1] = remove(original)
        values[0] = ""

        # Disabling the button after removing
        click.configure( state = DISABLED)

def savingFile( can ) :

    # Saving the file to a particular address
    if values[1].any() != 0 :

        # Finding address to save file
        file = filedialog.asksaveasfile( initialdir = os.getcwd(), title = "Save file",
                                            defaultextension = "*.png",
                                                filetypes = [( "PNG file", "*.png"), ( "JPG file", "*.jpg")] )

        # Saving file
        cv2.imwrite( file.name, values[1])
        values[1] = np.array([0,0,0])
        file.close()

        # Showing message of complition and returning back
        inform( "FILE SAVED" )
        change( can, menuPage)

    else :

        # Showing error due to empty credientials
        mistake( "ENTER FILE NAME!" )

def openingFolder( folder_path ) :

    # Opening Folder using filedialog
    if ( folder_path.get() != "" ) :

        # Getting path of folder from entry box
        open_folder = folder_path.get()

    else :

        # Getting path of folder from dialog
        open_folder = filedialog.askdirectory( initialdir = os.getcwd() ,title = "Browse Folder")

    # Checking for empty address
    if ( open_folder != "" ) :

        values[2] = open_folder

        if ( folder_path.get() != "" ) :
            folder_path.delete( 0, END)

        folder_path.insert( 0, open_folder )

    else :

        # Showing error due to empty credientials
        mistake( "FIELD EMPTY!" )

def openingFolder2( folder_path ) :

    # Opening Folder using filedialog
    if ( folder_path.get() != "" ) :

        # Getting path of folder from entry box
        open_folder = folder_path.get()

    else :

        # Getting path of folder from dialog
        open_folder = filedialog.askdirectory( initialdir = os.getcwd(), title = "Browse Folder")

    # Checking for empty address
    if ( open_folder != "" ) :

        values[4] = open_folder

        if ( folder_path.get() != "" ) :
            folder_path.delete( 0, END)

        folder_path.insert( 0, open_folder )

    else :

        # Showing error due to empty credientials
        mistake( "FIELD EMPTY!" )

def openingFile( file_path, file_formate ) :

    # Opening File
    if ( file_path.get() != "" ) :

        # Getting path of file from entry box
        open_file = file_path.get()

    else :

        # Getting path of file from filedialog
        open_file = filedialog.askopenfilename( initialdir = os.getcwd(), title = "Open file", filetypes = file_formate )

    # Checking for empty address
    if ( open_file != "" ) :

        values[0] = open_file

        if ( file_path.get() != "" ) :
            file_path.delete( 0, END)

        file_path.insert( 0, open_file )

    else :

        # Showing error due to empty credientials
        mistake( "FIELD EMPTY!" )

def checkLogin( can, page, user, pwrd ) :

    # Checking the login credientials
    if ( user.get() != "" and pwrd.get() != "" ) :

        if ( user.get() == "JustFunfact" and pwrd.get() == "star is burning" ) :

            # Login, same entry found
            change( can, page )

        else :

            # Showing error due to no match
            mistake("ENTRY DOESN'T MATCH")

    else :

        # Showing error due to empty credientials
        mistake( "EMPTY FIELDS")

# Designing pages of respective functionality
def clearBack() :

    # Image Background removing

    # Defining Structure
    third_page = Canvas( root,
                          width = wid, height = hgt,
                           bg = "black", highlightcolor = "#3c5390",
                            borderwidth = 0 )
    third_page.pack( fill = "both", expand = True )

    # Background Image
    back_image = Imgo( os.path.join( os.getcwd(), "Background\Clear_Back_Page.jpg" ), 1498, 875)
    third_page.create_image( 0, 0, image = back_image , anchor = "nw")

    # Heading
    third_page.create_text( 500, 120, text = "Remove Background",
                             font = ( ft[0], 45, "bold"), fill = "#1c54df" )

    # Return Button
    ret = Imgo( os.path.join( os.getcwd(), r'Design\arrow.png' ), 45, 35)
    ret_bt = ctk.CTkButton( master = root, image = ret, text = None,
                             width = 60, height = 40, corner_radius = 23,
                              bg_color = "#d3eafc", fg_color = "red",
                               hover_color = "#ff5359", border_width = 0,
                                command = lambda : change( third_page, menuPage) )
    ret_bt_win = third_page.create_window( 30, 20, anchor = "nw", window = ret_bt )

    # Accessing the file
    file_path = ctk.CTkEntry( master = root,
                                placeholder_text = "Enter Path", text_font = ( ft[4], 20 ),
                                 width = 580, height = 30, corner_radius = 14,
                                  placeholder_text_color = "#494949", text_color = "#242424",
                                   fg_color = "#c3c3c3", bg_color = "#d3eafc",
                                    border_color = "white", border_width = 3)
    file_path_win = third_page.create_window( 125, 200, anchor = "nw", window = file_path )

    file_formate = [( "PNG file", "*.png"), ( "JPG file", "*.jpg")]

    # Adding file path
    add_bt = ctk.CTkButton( master = root,
                             text = "Add..", text_font = ( ft[1], 20 ),
                              width = 60, height = 40, corner_radius = 14,
                               bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : openingFile( file_path, file_formate) )
    add_bt_win = third_page.create_window( 860, 200-2, anchor = "nw", window = add_bt )

    #Design to display
    img_to_rem = Imgo( os.path.join( os.getcwd(), 'Design\Clear_back_design.png' ), 370, 370)
    third_page.create_image( 600-20, 350, image = img_to_rem , anchor = "nw")

    # Background removing button
    rem_bt = ctk.CTkButton( master = root,
                             text = "Remove", text_font = ( ft[4], 25 ),
                              width = 170, height = 50, corner_radius = 14,
                               bg_color = "#98e2fe", fg_color = "red", text_color = "white",
                                hover_color = "#ff5359", border_width = 0,
                                text_color_disabled = "#a4a4a4",
                                 command = lambda : removeBackground(rem_bt) )
    rem_bt_win = third_page.create_window( 300, 500, anchor = "nw", window = rem_bt )

    # Saving Image button
    save_bt = ctk.CTkButton( master = root,
                             text = "Save Image", text_font = ( ft[4], 25 ),
                              width = 220, height = 50, corner_radius = 14,
                               bg_color = "#98e2fe", fg_color = "red", text_color = "white",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : savingFile( third_page) )
    save_bt_win = third_page.create_window( 1000, 500, anchor = "nw", window = save_bt )

    root.mainloop()

def convertImage() :

     # Defining Structure
    fourth_page = Canvas( root,
                           width = wid, height = hgt,
                            bg = "black", highlightcolor = "#3c5390",
                             borderwidth = 0 )
    fourth_page.pack( fill = "both", expand = True )

    # Background Image
    back_image = Imgo( os.path.join( os.getcwd(), "Background\Convert_Img_Page.jpg" ), 1498, 875)
    fourth_page.create_image( 0, 0, image = back_image , anchor = "nw")

    # Heading
    fourth_page.create_text( 500, 120, text = "Convert Images",
                                font = ( ft[0], 45, "bold" ), fill = "#1c54df" )

    # Return Button
    ret = Imgo( os.path.join( os.getcwd(), r'Design\arrow.png' ), 45, 35)
    ret_bt = ctk.CTkButton( master = root,
                             image = ret, text = None,
                              width = 60, height = 40, corner_radius = 23,
                               bg_color = "#d3eafc", fg_color = "red",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : change( fourth_page, menuPage) )
    ret_bt_win = fourth_page.create_window( 30, 20, anchor = "nw", window = ret_bt )

    # Accessing the file
    file_path = ctk.CTkEntry( master = root,
                                placeholder_text = "Enter Path", text_font = ( ft[4], 20 ),
                                 width = 580, height = 30, corner_radius = 14,
                                  placeholder_text_color = "#494949", text_color = "#242424",
                                   fg_color = "#c3c3c3", bg_color = "#d3eafc",
                                    border_color = "white", border_width = 3)
    file_path_win = fourth_page.create_window( 300, 210, anchor = "nw", window = file_path )

    file_formate = [( "PNG file", "*.png"), ( "JPG file", "*.jpg"), ( "JPEG file", "*.jpeg"), ( "PDF file", "*.pdf") ]

    # Adding file path
    add_bt = ctk.CTkButton( master = root,
                             text = "Add..", text_font = ( ft[1], 20 ),
                              width = 60, height = 40, corner_radius = 14,
                               bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : openingFile( file_path, file_formate) )
    add_bt_win = fourth_page.create_window( 1035, 210-2, anchor = "nw", window = add_bt )

    #Design to display
    img_to_con = Imgo( os.path.join( os.getcwd(), "Design\Convert_img_design.png" ), 390, 390)
    fourth_page.create_image( 600-50, 350, image = img_to_con , anchor = "nw")

    # Option menu
    opt = ctk.StringVar(value = "Select Type " )
    com = ctk.CTkOptionMenu( master = root, variable = opt,
                              values = [ "   PDF", "   PNG", "   JPG", "   JPEG"],
                               text_font = ( ft[4], 20),
                                width = 170, height = 40, corner_radius = 15,
                                 bg_color = "#9ae2fe", fg_color = "red", text_color = "white",
                                  button_color = "#363fc8", button_hover_color = "#676fe8",
                                   dropdown_color = "#ff6c3d", dropdown_hover_color = "red",
                                    dropdown_text_color = "white", dropdown_text_font = ( ft[4], 16),
                                     dynamic_resizing = True )
    # com.set("Select File")
    com_win = fourth_page.create_window( 1000, 400, anchor = "nw", window = com )

    # Saving Image button
    save_bt = ctk.CTkButton( master = root,
                             text = "Save Image", text_font = ( ft[4], 22 ),
                              width = 200, height = 40, corner_radius = 14,
                               bg_color = "#98e2fe", fg_color = "red", text_color = "white",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : convertFile( fourth_page, com) )
    save_bt_win = fourth_page.create_window( 1005, 620, anchor = "nw", window = save_bt )

    root.mainloop()

def findImage() :

     # Defining Structure
    fifth_page = Canvas( root, width =
                          wid, height = hgt,
                           bg = "black", highlightcolor = "#3c5390",
                            borderwidth = 0 )
    fifth_page.pack( fill = "both", expand = True )

    # Background Image
    back_image = Imgo( os.path.join( os.getcwd(), "Background\Find_Img_Page.jpg" ), 1498, 875)
    fifth_page.create_image( 0, 0, image = back_image , anchor = "nw")

    # Heading
    fifth_page.create_text( 400, 120, text = "Find Image", font = ( ft[0], 45, "bold" ), fill = "#1c54df" )

    # Return Button
    ret = Imgo( os.path.join( os.getcwd(), r'Design\arrow.png' ), 45, 35 )
    ret_bt = ctk.CTkButton( master = root,
                             image = ret, text = None,
                              width = 60, height = 40, corner_radius = 23,
                               bg_color = "#d3eafc", fg_color = "red",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : change( fifth_page, menuPage) )
    ret_bt_win = fifth_page.create_window( 30, 20, anchor = "nw", window = ret_bt )

    # Accessing the image
    file_path = ctk.CTkEntry( master = root,
                                placeholder_text = "Enter Image Path", text_font = ( ft[4], 20 ),
                                 width = 603, height = 30, corner_radius = 14,
                                  placeholder_text_color = "#494949", text_color = "#242424",
                                   fg_color = "#c3c3c3", bg_color = "#d3eafc",
                                    border_color = "white", border_width = 3)
    file_path_win = fifth_page.create_window( 300, 210, anchor = "nw", window = file_path )

    file_formate = [( "PNG file", "*.png"), ( "JPG file", "*.jpg") ]

    # Adding image path
    add_bt = ctk.CTkButton( master = root,
                             text = "Add..", text_font = ( ft[1], 20 ),
                              width = 60, height = 40, corner_radius = 14,
                               bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : openingFile( file_path, file_formate) )
    add_bt_win = fifth_page.create_window( 1065, 210-2, anchor = "nw", window = add_bt )

    # Accessing the folder
    folder_path = ctk.CTkEntry( master = root,
                                 placeholder_text = "Enter Folder Path", text_font = ( ft[4], 20 ),
                                  width = 580, height = 30, corner_radius = 14,
                                   placeholder_text_color = "#494949", text_color = "#242424",
                                    fg_color = "#c3c3c3", bg_color = "#d3eafc",
                                     border_color = "white", border_width = 3)
    folder_path_win = fifth_page.create_window( 300, 295, anchor = "nw", window = folder_path )

    # Browse folder button
    browse_bt = ctk.CTkButton( master = root,
                                text = "Browse", text_font = ( ft[1], 20 ),
                                  width = 100, height = 40, corner_radius = 14,
                                   bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                    hover_color = "#ff5359", border_width = 0,
                                     command = lambda : openingFolder( folder_path ) )
    browse_bt_win = fifth_page.create_window( 1035, 295-2, anchor = "nw", window = browse_bt )

    # Frame
    mess = ctk.CTkFrame( master = fifth_page,
                          width = 780, height = 300, corner_radius = 30,
                           bg_color = "#d5eafd", fg_color = "#97e1fe",
                            border_color = "#4d89eb", border_width = 6)
    mess.place_configure( x = 280, y = 480, anchor = "nw")

    # Label in frame
    frm_label = ctk.CTkLabel( master = mess,
                                text = "Insert Values", text_font = (ft[0], 45, "bold"),
                                 width = 200, height = 50, corner_radius = 15,
                                  bg_color = "#97e1fe", fg_color = "#97e1fe", text_color = "#1c54df"  )
    frm_label.place_configure( x = 220, y = 120, anchor = "nw" )

    # Image Finding button
    find_bt = ctk.CTkButton( master = root,
                                text = "Find", text_font = ( ft[1], 24 ),
                                  width = 160, height = 45, corner_radius = 14,
                                   bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                    hover_color = "#ff5359", border_width = 0,
                                     command = lambda : findingImages( frm_label ) )
    find_bt_win = fifth_page.create_window( 650, 400, anchor = "nw", window = find_bt )

    root.mainloop()

def sortImage() :

     # Defining Structure
    sixth_page = Canvas( root,
                          width = wid, height = hgt,
                           bg = "black", highlightcolor = "#3c5390",
                            borderwidth = 0 )
    sixth_page.pack( fill = "both", expand = True )

    # Background Image
    back_image = Imgo( os.path.join( os.getcwd(), "Background\Sort_Image_Page.jpg" ), 1498, 875)
    sixth_page.create_image( 0, 0, image = back_image , anchor = "nw")

    # Heading
    sixth_page.create_text( 400, 120, text = "Sort Images",
                                font = ( ft[0], 45, "bold" ), fill = "#1c54df" )

    # Return Button
    ret = Imgo( os.path.join( os.getcwd(), r'Design\arrow.png' ), 45, 35)
    ret_bt = ctk.CTkButton( master = root,
                             image = ret, text = None,
                              width = 60, height = 40, corner_radius = 23,
                               bg_color = "#d3eafc", fg_color = "red",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : change( sixth_page, menuPage) )
    ret_bt_win = sixth_page.create_window( 30, 20, anchor = "nw", window = ret_bt )

    # Accessing the folder1
    folder1_path = ctk.CTkEntry( master = root,
                                  placeholder_text = "Enter Folder 1 Path", text_font = ( ft[4], 20 ),
                                   width = 580, height = 30, corner_radius = 14,
                                    placeholder_text_color = "#494949", text_color = "#242424",
                                     fg_color = "#c3c3c3", bg_color = "#d3eafc",
                                      border_color = "white", border_width = 3)
    folder1_path_win = sixth_page.create_window( 300, 210, anchor = "nw", window = folder1_path )

    # Browse folde1 button
    browse_bt_1 = ctk.CTkButton( master = root,
                                  text = "Browse", text_font = ( ft[1], 20 ),
                                    width = 100, height = 40, corner_radius = 14,
                                     bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                      hover_color = "#ff5359", border_width = 0,
                                       command = lambda : openingFolder( folder1_path ) )
    browse_bt_1_win = sixth_page.create_window( 1035, 210-2, anchor = "nw", window = browse_bt_1 )

    # Accessing the folder2
    folder2_path = ctk.CTkEntry( master = root,
                                  placeholder_text = "Enter Folder 2 Path", text_font = ( ft[4], 20 ),
                                   width = 580, height = 30, corner_radius = 14,
                                    placeholder_text_color = "#494949", text_color = "#242424",
                                     fg_color = "#c3c3c3", bg_color = "#d3eafc",
                                      border_color = "white", border_width = 3)
    folder2_path_win = sixth_page.create_window( 300, 295, anchor = "nw", window = folder2_path )

    # Browse folder2 button
    browse_bt_2 = ctk.CTkButton( master = root,
                                  text = "Browse", text_font = ( ft[1], 20 ),
                                    width = 100, height = 40, corner_radius = 14,
                                     bg_color = "#d3eafc", fg_color = "red", text_color = "white",
                                      hover_color = "#ff5359", border_width = 0,
                                       command = lambda : openingFolder2( folder2_path ) )
    browse_bt_2_win = sixth_page.create_window( 1035, 295-2, anchor = "nw", window = browse_bt_2 )

    # Sorting button
    sort_bt = ctk.CTkButton( master = root,
                                text = "Start Sorting", text_font = ( ft[0], 24 ),
                                  width = 250, height = 45, corner_radius = 14,
                                   bg_color = "#99e2fe", fg_color = "red", text_color = "white",
                                    hover_color = "#ff5359", border_width = 0,
                                     command = lambda : sortingImages() )
    sort_bt_win = sixth_page.create_window( 610, 540, anchor = "nw", window = sort_bt )

    root.mainloop()

def menuPage() :

    # Defining Structure
    second_page = Canvas( root,
                           width = wid, height = hgt,
                            bg = "black", highlightcolor = "#3c5390",
                             borderwidth = 0 )
    second_page.pack( fill = "both", expand = True )

    # Background Image
    back_image = Imgo( os.path.join( os.getcwd(), "Background\Menu_Page.jpg" ), 1498, 875)
    second_page.create_image( 0, 0, image = back_image , anchor = "nw")

    # Heading
    second_page.create_text( 350, 120, text = "Content...",
                                font = ( ft[0], 45, "bold" ), fill = "#1c54df" )

    # Back Ground remover page window
    backRem = Imgo( os.path.join( os.getcwd(), "Design\Clear_Back_logo.png" ), 220, 200 )
    backRem_bt = ctk.CTkButton( master = root,
                                 image = backRem, compound = "top",
                                  text = "Clear Back", text_font = ( ft[0], 22, "bold" ),
                                   text_color = "white",
                                    width = 230, height = 240, corner_radius = 10,
                                     bg_color = "#d3eafc", fg_color = "#2d435b",
                                      hover_color = "#fdbf38", border_width = 0,
                                       command = lambda : change( second_page, clearBack ))
    backRem_bt_win = second_page.create_window( 190, 250, anchor = "nw", window = backRem_bt)

    # Image converter page window
    imgConvert = Imgo( os.path.join( os.getcwd(), "Design\Convert_Img_logo.png" ), 220, 200 )
    imgConvert_bt = ctk.CTkButton( master = root,
                                    image = imgConvert, compound = "top",
                                     text = "Converter", text_font = ( ft[0], 22, "bold" ),
                                      text_color = "white",
                                       width = 230, height = 240, corner_radius = 10,
                                        bg_color = "#d3eafc", fg_color = "#2d435b",
                                         hover_color = "#fdbf38", border_width = 0,
                                          command = lambda : change( second_page, convertImage ))
    imgConvert_bt_win = second_page.create_window( 500, 400, anchor = "nw", window = imgConvert_bt )

    # Search image page window
    findImg = Imgo( os.path.join( os.getcwd(), "Design\Find_Img_logo.png" ), 220, 200 )
    findImg_bt = ctk.CTkButton( master = root,
                                 image = findImg, compound = "top",
                                  text = "Find Image", text_font = ( ft[0], 22, "bold" ),
                                   text_color = "white",
                                    width = 230, height = 240, corner_radius = 10,
                                     bg_color = "#d3eafc", fg_color = "#2d435b",
                                      hover_color = "#fdbf38", border_width = 0,
                                       command = lambda : change( second_page, findImage ))
    findImg_bt_win = second_page.create_window( 810, 250, anchor = "nw", window = findImg_bt )

    # Sort image page window
    imgSort =Imgo( os.path.join( os.getcwd(), "Design\Sort_Img_logo.png" ), 220, 200 )
    imgSort_bt = ctk.CTkButton( master = root,
                                 image = imgSort, compound = "top",
                                  text = "Sort Images", text_font = ( ft[0], 22, "bold" ),
                                   text_color = "white",
                                    width = 230, height = 240, corner_radius = 10,
                                     bg_color = "#d3eafc", fg_color = "#2d435b",
                                      hover_color = "#fdbf38",  border_width = 0,
                                       command = lambda : change( second_page, sortImage ))
    imgSort_bt_win = second_page.create_window( 1120, 400, anchor = "nw", window = imgSort_bt )

    # Logout button
    log = Imgo( os.path.join( os.getcwd(), "Design\logout.png" ), 35, 35 )
    log_bt = ctk.CTkButton( master = root,
                             image = log, text = None,
                              width = 45, height = 45, corner_radius = 23,
                               bg_color = "#357adf", fg_color = "red",
                                hover_color = "#ff5359", border_width = 0,
                                 command = lambda : change( second_page, loginPage ))
    log_bt_win = second_page.create_window( 1420, 20, anchor = "nw", window = log_bt )

    root.mainloop()

def loginPage() :

    global user, pwrd, first_page

    # Defining Structure
    first_page = Canvas( root,
                          width = wid, height = hgt,
                           bg = "black", highlightcolor = "#3c5390",
                            borderwidth = 0 )
    first_page.pack( fill = "both", expand = True )

    # Background Image
    back_image = Imgo( os.path.join( os.getcwd(), "Background\Login_Page.jpg" ), 1498, 875)
    design_image = Imgo( os.path.join( os.getcwd(), "Design\Login_Design.png" ), 600, 400)
    first_page.create_image( 0, 0, image = back_image , anchor = "nw")
    first_page.create_image( 350, 325, image = design_image, anchor = "nw")

    # Heading
    first_page.create_text( 450, 150, text = "Image Sorter",
                                font = ( ft[0], 45, "bold" ), fill = "#1c54df" )
    first_page.create_text( 1150, 380, text = "Welcome\n    Back",
                                font = ( ft[0], 26, "bold" ), fill = "#0b4bf5" )

    # Entry of username and password
    user = ctk.CTkEntry( master = root,
                          placeholder_text = "Username", text_font = ( ft[1], 20 ),
                           width = 220, height = 30, corner_radius = 14,
                            placeholder_text_color = "#666666", text_color = "#191919",
                             fg_color = "#e1f5ff", bg_color = "#9ae2fe",
                              border_color = "white", border_width = 3)
    user_win = first_page.create_window( 1015, 470, anchor = "nw", window = user )

    pwrd = ctk.CTkEntry( master = root,
                          placeholder_text = "Password", text_font = ( ft[1], 20 ),
                           width = 220, height = 30, corner_radius = 14,
                            placeholder_text_color = "#666666", text_color = "#191919",
                             fg_color = "#e1f5ff", bg_color = "#9ae2fe",
                              border_color = "white", border_width = 3, show = "*" )
    pwrd_win = first_page.create_window( 1015, 550, anchor = "nw", window = pwrd )

    # Login button
    log_bt = ctk.CTkButton( master = root,
                             text = "Login", text_font = ( ft[0], 22 ),
                              text_color = "white",
                               width = 50, height = 25, corner_radius = 15,
                                bg_color = "#9ae2fe", fg_color = "red",
                                 hover_color = "#ff5359", border_width = 0,
                                  command = lambda : checkLogin( first_page, menuPage, user, pwrd ))
    log_bt_win = first_page.create_window( 1090, 650, anchor = "nw", window = log_bt )

    root.mainloop()

if __name__  == "__main__" :

    # Defining Main theme of all widgets
    ctk.set_appearance_mode( "dark" )
    ctk.set_default_color_theme( "dark-blue" )
    wid = 1200
    hgt = 700

    global root

    root = ctk.CTk()
    root.title( "Image Sorter" )
    root.iconbitmap( os.path.join( os.getcwd(), "Design\Project_Icon.ico" ))
    root.geometry( "1200x700+200+80" )
    root.resizable( False, False )
    ft = [ "Tahoma", "Seoge UI", "Heloia", "Book Antiqua", "Microsoft Sans Serif"]
    values = [ "", np.array([0,0,0]), "", np.array([0,0,0]), ""]
    matches = set()

    loginPage()


import copy
import threading
import socketserver
import json
from typing import List
from mcsu_data import *

_BUFFER_SIZE = 1024
_CLIENTS_MAX = 8

class State:
    def __init__(self):
        self.userdata = DataGame(0, [])
        self.uid_free = []
        self.uid = 0
        self.lock = threading.Semaphore()

    def get_userdata(self):
        self.lock.acquire()
        mycopy: DataGame = copy.deepcopy(self.userdata)
        self.lock.release()
        return mycopy

    def set_userdata(self, player: DataPlayer):
        self.lock.acquire()

        found = False
        for i, curplayer in enumerate(self.userdata.players):
            if curplayer.uid == player.uid:
                self.userdata.players[i] = player
                found = True
                break
        if not found:
            self.userdata.players.append(player)

        self.lock.release()

    def next_turn(self):
        self.lock.acquire()
        self.uid = (self.uid + 1) % len(self.userdata.players)
        self.lock.release()

    def del_uid(self, uid: int):
        self.lock.acquire()
        for player in self.userdata.players:
            if uid == player.uid:
                self.uid_free.append(uid)

                # we can't remain in this turn anymore
                if self.userdata.turn == player.uid:
                    self.next_turn()

                del self.userdata.players[uid]
                break
        self.lock.release()

    def new_uid(self) -> int:
        self.lock.acquire()
        if len(self.uid_free) > 0:
            uid = self.uid_free.pop(0)
            self.lock.release()
            return uid

        uid = self.uid
        self.uid += 1
        self.lock.release()
        return uid

class McsuServer:
    def __init__(self, host: str, port: int):
        self.host = host
        self.port = port
        self.state: State = State()
        self.turn: int = 0

    def run(self):
        class Handler(socketserver.ThreadingTCPServer):
            thisref = self
            def handle(self):
                this = Handler.thisref
                uid = -1

                # DC if too many clients
                if len(self.state) >= _CLIENTS_MAX:
                    print("Too many clients!")
                    return

                self.request.setblocking(True)
                while True:
                    data = self.receive()
                    if data is None:
                        if uid != -1: self.state.del_uid(uid)
                        print("No data")
                        break

                    command = data.get('_command')
                    if command is None:
                        if uid != -1: self.state.del_uid(uid)
                        print("No command")
                        break

                    elif command == 'register':
                        uid = self.state.new_uid()
                        units = data.get('units')
                        if not units is None:
                            data = DataPlayer(uid, units)
                            self.state.set_userdata(data)
                        data = DataRegister(uid)
                        self.send(data)

                    elif command == 'status':
                        if uid == -1:
                            print("No status uid")
                            break
                        data: DataGame = self.state.get_userdata()
                        self.send(data)

                    elif command == 'finish':
                        if uid == -1:
                            print("No finish uid")
                            break
                        self.state.next_turn()

                    elif command == 'quit':
                        if uid != -1:
                            print("No quit uid")
                            self.state.del_uid(uid)
                        self.state.del_uid(uid)
                        break

                    elif command == 'move':
                        if uid == -1:
                            print("No move uid")
                            break

                        # get commanded position
                        selected_unit = data.get('mover')
                        x = data.get('x')
                        y = data.get('y')
                        if selected_unit is None or x is None or y is None:
                            print("No selected unit/x/y")
                            break

                        state: DataGame = self.state.get_userdata()
                        need_to_break = False
                        for player in state.players:
                            if not uid == player.uid: continue

                            # find data unit from selected unit
                            for unit in player.units:
                                if unit.x == selected_unit.x and unit.y == selected_unit.y:
                                    # move it!
                                    unit.x = x
                                    unit.y = y
                            if need_to_break: break

                    elif command == 'attack':
                        pass

            def send(self, obj):
                myobj = obj if type(obj) is dict else obj.__dict__
                try:
                    message = json.dumps(myobj)
                except:
                    print('Failed to dumps')
                    return

                try:
                    self.request.sendall(message)
                except:
                    print('Failed to sendall')
                    return

            def receive(self):
                try:
                    self.message = self.request.recv(_BUFFER_SIZE)
                except:
                    print("Failed to receive")
                    return None

                # disconnected
                if len(self.message) == 0: return None

                try:
                    data: dict = json.loads(self.message)
                except:
                    print("Failed to loads")
                    return None
                return data

        with socketserver.ThreadingTCPServer((self.host, self.port), Handler) as server:
            server.serve_forever()

def main(args):
    server = McsuServer('0.0.0.0', 20000)
    server.run()
    return 0

if __name__ == '__main__':
    import sys
    exit(main(sys.argv))


"""
# Definition for a Node.
class Node(object):
    def __init__(self, val, children):
        self.val = val
        self.children = children
"""
class Solution(object):
    def levelOrder(self, root):
        """
        :type root: Node
        :rtype: List[List[int]]
        """
        result = []
        depth = 0
        return self.findLevels(root, result, depth)

    def findLevels(self, root, result, depth):
        if root is None:
            return result
        if len(result) <= depth:
            result.append([])
        result[depth].append(root.val)
        for child in root.children:
            result = self.findLevels(child, result, depth + 1)
        return result


"""
Entry point for the DB Load dispatcher based on scheduler events.
"""

import json
import queue
import uuid
from concurrent import futures
from typing import Any, Callable, Dict, List

from google.cloud import pubsub_v1

from common import settings as CFG
from common.data_representation.config import ConfigException
from common.data_representation.config.participant import ParticipantConfig
from common.dispatcher import is_cloud_function_exists
from common.elapsed_time import elapsed_timer
from common.logging import Logger
from common.thread_pool_executor import run_thread_pool_executor
from dispatcher.base_dispatcher import BaseDispatcher

DEFAULT_WORKER_REPLICA_AMOUNT = 20

class DbLoadDispatcher(BaseDispatcher):
    """Read prticipant config and dispatch dbload tasks"""

    __description__ = "DB LOAD DISPATCHER"
    __local_cfg_name_tmpl__ = "dbload_{participant_id}_payload.json"

    def __init__(self, env_tz_info=CFG.ENVIRONMENT_TIME_ZONE) -> None:
        super().__init__(env_tz_info=env_tz_info)

        self._topic_path = self._publisher.topic_path(  # pylint: disable=no-member
            CFG.PROJECT, CFG.DB_LOAD_FUNCTION_NAME
        )
        self._payloads = queue.Queue()

    def _get_payload(self, raw_cfg: dict, participant_id: int) -> dict:
        """Generate connector payload

        Args:
            raw_mtr_cfg (dict): Meter full configuration

        Returns:
            dict: Connector payload
        """
        with elapsed_timer() as elapsed:
            payload = {
                "meters": [],
                "extra": {"participant_id": participant_id},
            }

            for cnctr_cfg in raw_cfg.get("connectors", []):
                for meter_name, meter_info in cnctr_cfg.get("meters", {}).items():
                    payload["meters"].append(
                        {
                            "meter_name": meter_name,
                            "meter_id": meter_info["meter_id"],
                            "meter_uri": meter_info["meter_uri"],
                            "type": meter_info["type"],
                            "extra": {
                                "timezone": meter_info.get("timezone", ""),
                                "standardized": {
                                    "bucket": meter_info.get(
                                        "meteredDataLocation", {}
                                    ).get("bucket", ""),
                                    "path": meter_info.get(
                                        "meteredDataLocation", {}
                                    ).get("path", ""),
                                },
                            },
                        }
                    )
            self._logger.debug(
                "The payload has been created.",
                extra={"labels": {"elapsed_time": elapsed()}},
            )
        return payload

    def _parse_participant_config(self, bucket) -> Dict:
        with elapsed_timer() as elapsed:
            cfg = ParticipantConfig()
            cfg.read_from_bucket(
                bucket=bucket.name,
                subdirectory=CFG.CONFIG_BASE_PATH,
                filename=CFG.PARTICIPANT_CONFIG_NAME,
                binary_mode=False,
            )
            json_cfg = cfg.as_json()
            self._logger.debug(
                f"Completed `{bucket.name}` config parsing.",
                extra={"labels": {"elapsed_time": elapsed()}},
            )
            return json_cfg

    def _parse_participant_configs(self, buckets: List[Any]) -> None:
        self._logger.info("Processing participants configuration.")
        with elapsed_timer() as elapsed:
            for bucket in buckets:
                participant_id = int(bucket.name.lstrip(CFG.BUCKET_PREFFIX))
                self._logger.debug(
                    f"Processing participant {participant_id} configuration"
                )

                try:
                    json_cfg = self._parse_participant_config(bucket)
                except ConfigException as err:
                    self._logger.error(
                        f"Cannot parse {bucket.name}/{CFG.CONFIG_BASE_PATH}/"
                        f"{CFG.PARTICIPANT_CONFIG_NAME} due to the error "
                        f"'{err}'. Skipping."
                    )
                    continue

                if not json_cfg.get("connectors", []):
                    self._logger.warning(
                        f"The participant {participant_id} configuration does "
                        "not contain connectors. Skipping"
                    )
                    continue
                # TODO: OPENWEATHER ISSUE. DEBUG CODE. REMOVE AFTER COMPLETION
                payload = self._get_payload(json_cfg, participant_id)
                message_json = json.dumps(
                    {
                        "data": payload,
                    }
                )

                payload_data = message_json.encode("utf-8")
                self._logger.debug("=" * 40)
                self._logger.debug(f"payload_data - '{payload_data}'")
                self._logger.debug("=" * 40)
                # ============================================================
                self._payloads.put(payload)
            self._logger.debug(
                "Processed participants configuration.",
                extra={"labels": {"elapsed_time": elapsed()}},
            )

    @staticmethod
    def _pub_sub_done_callback(
        publish_future: pubsub_v1.publisher.futures.Future,  # pylint:disable=unused-argument
        payload: str,
        description: str,
        timeout: int = 120,
    ) -> Callable[[pubsub_v1.publisher.futures.Future], None]:
        def callback(publish_future: pubsub_v1.publisher.futures.Future) -> None:
            call_logger = Logger(
                name=description,
                level="DEBUG",
                description=description,
                trace_id=uuid.uuid4(),
            )
            try:
                # Wait timeout seconds for the publish call to succeed.
                call_logger.info(
                    f"Result of call dbload for participant "
                    f"{payload['extra']['participant_id']} is"
                    f"{publish_future.result(timeout=timeout)}"
                )
            except futures.TimeoutError:
                call_logger.error(
                    f"Publishing {payload['extra']['participant_id']} timed out."
                )

        return callback

    @staticmethod
    def _call_dbload_worker(  # pylint:disable=too-many-arguments
        publisher: pubsub_v1.PublisherClient,
        topic_path: str,
        payload_queue: queue.Queue,
        futures_queue: queue.Queue,
        done_callback: Callable,
        description: str,
        worker_idx: int,  # pylint:disable=unused-argument
    ) -> None:
        empty_run_count = 0
        while True:
            if payload_queue.empty():
                if empty_run_count == 3:
                    break
                empty_run_count += 1
            else:
                payload = payload_queue.get()
                message_json = json.dumps(
                    {
                        "data": payload,
                    }
                )

                payload_data = message_json.encode("utf-8")

                publish_future = publisher.publish(topic_path, data=payload_data)

                publish_future.add_done_callback(
                    done_callback(publish_future, payload, description=description)
                )
                futures_queue.put(publish_future)
                payload_queue.task_done()

    def _save_payload_locally(self):
        with elapsed_timer() as elapsed:
            self._logger.debug("Saving payload locally.")

            if self._payloads.empty():
                self._logger.warning(
                    "There is no payload required for the call "
                    "`{CFG.DB_LOAD_FUNCTION_NAME}` function. Exit."
                )
                return None

            while not self._payloads.empty():
                payload = self._payloads.get()
                json_cfg = {
                    "data": payload,
                }

                filename = self.__local_cfg_name_tmpl__.format(
                    participant_id=json_cfg["data"]["extra"]["participant_id"]
                )

                file_path = self.__local_path__.joinpath(filename)
                with open(file_path, "w", encoding="UTF-8") as cfg_fl:
                    json.dump(json_cfg, cfg_fl, indent=4)
            self._logger.debug(
                "Saved payload locally.", extra={"labels": {"elapsed_time": elapsed()}}
            )

    def _run_dbload(self, workers_replica: int = DEFAULT_WORKER_REPLICA_AMOUNT) -> None:

        with elapsed_timer() as elapsed:
            self._logger.debug("Running db load.")

            if self._payloads.empty():
                self._logger.warning(
                    f"The data for call `{CFG.DB_LOAD_FUNCTION_NAME}` function "
                    "absent. Exit."
                )
                return None

            is_connector_exists = is_cloud_function_exists(CFG.DB_LOAD_FUNCTION_NAME)

            if not is_connector_exists:
                self._logger.error(
                    f"The `{CFG.DB_LOAD_FUNCTION_NAME}` connector is not "
                    f"deployed in the '{CFG.PROJECT}' environment. Exit"
                )
                return None
            result_queue = queue.Queue()
            pub_futures = run_thread_pool_executor(
                workers=[
                    (
                        self._call_dbload_worker,
                        [
                            self._publisher,
                            self._topic_path,
                            self._payloads,
                            result_queue,
                            self._pub_sub_done_callback,
                            self.__description__,
                        ],
                    )
                ],
                worker_replica=workers_replica,
            )

            for res in pub_futures:
                try:
                    res.result()
                except Exception as err:  # pylint: disable=broad-except
                    self._logger.error(
                        f"Received error '{err}' during call "
                        f"{CFG.DB_LOAD_FUNCTION_NAME}"
                    )

            pub_sub_futures = []
            while not result_queue.empty():
                pub_sub_futures.append(result_queue.get())
                result_queue.task_done()

            # Wait for all the publish futures to resolve before exiting.
            futures.wait(pub_sub_futures, return_when=futures.ALL_COMPLETED)
            self._logger.debug(
                "Completed db load run.", extra={"labels": {"elapsed_time": elapsed()}}
            )

    def run(self) -> None:
        """Run dispatcher loop."""
        with elapsed_timer() as elapsed:
            self._logger.debug("Run db load from scratch")

            buckets = self.get_buckets()
            self._parse_participant_configs(buckets)
            if not CFG.DEBUG:
                self._run_dbload()
            else:
                self._save_payload_locally()
            self._logger.debug(
                "Completed load run.", extra={"labels": {"elapsed_time": elapsed()}}
            )

def main():
    """Entry point"""
    dblod_dispatcher = DbLoadDispatcher()
    dblod_dispatcher.run()

# TODO: Commented code below should be removed after restore logic completion
# def main1():
#     """Entry point logic."""

#     publisher = pubsub_v1.PublisherClient()

#     restore_function_names_to_call = [
#         func_name
#         for func_name in list_cloud_functions()
#         if func_name.startswith(CFG.DB_LOAD_RESTORE_FUNCTION_NAME_TEMPLATE)
#     ]
#     print(
#         f"DEBUG: DB LOAD DISPATCHER: RESTORE FUNCTIONS TO CALL {restore_function_names_to_call}."
#     )

#     with elapsed_timer() as global_ellapsed:
#         with elapsed_timer() as bckts_elpsd:
#             buckets = get_buckets_list(
#                 project=CFG.PROJECT, preffix=CFG.BUCKET_PREFFIX
#             )
#             print(
#                 f"DEBUG: DB LOAD DISPATCHER: Loaded buckets list. Ellapsed time {bckts_elpsd()}."
#             )

#         for bucket in buckets:
#             print(f"DEBUG: DB LOAD DISPATCHER: Processing {bucket.name} configuration")

#             if DEBUG and bucket.name not in ALLOWED_BUCKETS:
#                 print(
#                     f"WARNING: DB LOAD DISPATCHER: Enabled  DEBUG mode. "
#                     f"Disabled {bucket.name} processing. Skipping"
#                 )
#                 continue

#             full_config_name = (
#                 f"{bucket.name}/{CFG.CONFIG_BASE_PATH}/{CFG.PARTICIPANT_CONFIG_NAME}"
#             )
#             # Entry Point for bucket operation multiprocessing
#             participant_id = int(bucket.name.lstrip(CFG.BUCKET_PREFFIX))
#             with elapsed_timer() as cnfg_prs_elpsd:
#                 try:
#                     cfg = ParticipantConfig()
#                     cfg.read_from_bucket(
#                         bucket=bucket.name,
#                         subdirectory=CFG.CONFIG_BASE_PATH,
#                         filename=CFG.PARTICIPANT_CONFIG_NAME,
#                         binary_mode=False,
#                     )
#                     json_cfg = cfg.as_json()
#                     print(
#                         f"DEBUG: DB LOAD DISPATCHER: Completed "
#                         f"{full_config_name} config parsing. "
#                         f"Ellapsed time {cnfg_prs_elpsd()}."
#                     )
#                 except ConfigException as err:
#                     print(
#                         f"ERROR: DB LOAD DISPATCHER: Cannot parse "
#                         f"{full_config_name} due "
#                         f"to the error {err}. Skipping"
#                     )
#                     continue

#             if not json_cfg.get("connectors", []):
#                 print(
#                     f"WARNING: DB LOAD DISPATCHER: The participant "
#                     f'configuration "{full_config_name}" does not contain '
#                     "defined connectors"
#                 )

#             with elapsed_timer() as payload_ellapsed:
#                 payload = _get_payload(json_cfg, participant_id)
#                 print(
#                     "DEBUG: DB LOAD DISPATCHER: The function "
#                     f"{CFG.DB_LOAD_FUNCTION_NAME} payload formed. "
#                     f"Ellapsed time {payload_ellapsed()}."
#                 )

#             print(
#                 f"INFO: DB LOAD DISPATCHER: Calling {CFG.DB_LOAD_FUNCTION_NAME} to load data."
#             )

#             with elapsed_timer() as gcp_func_ex_elpsd:
#                 is_connector_exists = is_cloud_function_exists(
#                     CFG.DB_LOAD_FUNCTION_NAME
#                 )
#                 print(
#                     f"DEBUG: DB LOAD DISPATCHER: Completed check of cloud function "
#                     f"{CFG.DB_LOAD_FUNCTION_NAME} deployment. "
#                     f"Ellapsed time {gcp_func_ex_elpsd()}."
#                 )
#             if not is_connector_exists:
#                 print(
#                     "ERROR: DB LOAD DISPATCHER: The given db load "
#                     f'connector "{CFG.DB_LOAD_FUNCTION_NAME}" '
#                     f'is not deployed to the "{CFG.PROJECT}" environment. '
#                     f"Skipping"
#                 )
#                 continue

#             with elapsed_timer() as gcp_func_call_ellapsed:
#                 topic_path = publisher.topic_path(  # pylint: disable=no-member
#                     CFG.PROJECT, CFG.DB_LOAD_FUNCTION_NAME
#                 )
#                 message_json = json.dumps(
#                     {
#                         "data": payload,
#                     }
#                 )
#                 data = message_json.encode("utf-8")
#                 print(f"DEBUG: TOPIC PATH {topic_path}")
#                 future = publisher.publish(topic_path, data=data)
#                 result = future.result()
#                 print(
#                     f"DEBUG: DISPATCHER: Data pushed to gcp topic "
#                     f"{CFG.DB_LOAD_FUNCTION_NAME}. "
#                     f"{gcp_func_call_ellapsed()}, with result {result}"
#                 )

#             if restore_function_names_to_call:
#                 print(
#                     "DEBUG: DISPATCHER: Start calling DW restore cloud "
#                     f"functions {restore_function_names_to_call}. "
#                 )
#                 for func in restore_function_names_to_call:

#                     topic_path = publisher.topic_path(  # pylint: disable=no-member
#                         CFG.PROJECT, func
#                     )
#                     function_id = func.split(
#                         CFG.DB_LOAD_RESTORE_FUNCTION_NAME_TEMPLATE
#                     )[-1]
#                     payload["function_id"] = function_id

#                     if not DEBUG:
#                         message_json = json.dumps(
#                             {
#                                 "data": payload,
#                             }
#                         )
#                         data = message_json.encode("utf-8")
#                         print(f"DEBUG: TOPIC PATH {topic_path}")
#                         future = publisher.publish(topic_path, data=data)
#                         result = future.result()
#                         print(
#                             f"DEBUG: DISPATCHER: Data pushed to gcp topic {func}. "
#                             f"with result {result}"
#                         )
#                     else:
#                         json_cfg = {
#                             "data": payload,
#                         }
#                         with open(
#                             f"/tmp/participant_db_load_payload_{bucket[-1]}.json",
#                             "w",
#                             encoding="UTF-8",
#                         ) as prtcpnt_fl:
#                             json.dump(json_cfg, prtcpnt_fl, indent=4)

#                 print(
#                     "DEBUG: DISPATCHER: Finish calling DW restore cloud "
#                     f"functions {restore_function_names_to_call}. "
#                 )

#     print(
#         "DEBUG: DB LOAD DISPATCHER: Completed loop. "
#         f"Execution time {global_ellapsed()}."
#     )
#     return "Prototype scheduler executed successfully"

# def _get_payload(raw_cfg: dict, participant_id: int) -> dict:
#     """Generate connector payload

#     Args:
#         raw_mtr_cfg (dict): Meter full configuration

#     Returns:
#         dict: Connector payload
#     """
#     payload = {
#         "meters": [],
#         "extra": {"participant_id": participant_id},
#     }

#     for cnctr_cfg in raw_cfg.get("connectors", []):
#         for meter_name, meter_info in cnctr_cfg.get("meters", {}).items():

#             payload["meters"].append(
#                 {
#                     "meter_name": meter_name,
#                     "meter_id": meter_info["meter_id"],
#                     "meter_uri": meter_info["meter_uri"],
#                     "type": meter_info["type"],
#                     "extra": {
#                         "timezone": meter_info.get("timezone", ""),
#                         "standardized": {
#                             "bucket": meter_info.get("meteredDataLocation", {}).get(
#                                 "bucket", ""
#                             ),
#                             "path": meter_info.get("meteredDataLocation", {}).get(
#                                 "path", ""
#                             ),
#                         },
#                     },
#                 }
#             )
#     return payload

if __name__ == "__main__":
    # In general to be able run this code locally a few steps must be comleted before
    # 1. Setup GCP authentication on local environment
    #   1.1 MAke shure that shell command 'gcloud auth application-default login' completed before
    # 2. Setup Project Environment Variables - PROJECT, BUCKET_PREFFIX
    #    NOTE: If variables aren't defined in your ~/.bashrc file or directly by export command
    #          the following values wil be use
    #               PROJECT = 'develop-epbp'
    #               BUCKET_PREFFIX = 'prototype_develop-epbp_participant_'

    # import debugpy
    # debugpy.listen(CFG.DEBUG_PORT)
    # debugpy.wait_for_client()  # blocks execution until client is attached
    # debugpy.breakpoint()
    main()


def union(x,y):
    px = find(x)
    py = find(y)
    if px != py :
        mn = min(cost[px], cost[py])
        parent[py] = px
        cost[px] = mn
        cost[py] = mn

def find(x):
    if parent[x] == x:
        return x
    parent[x] = find(parent[x])
    return parent[x]

n,m,k = map(int,input().split())
arr = list(map(int,input().split()))
parent = [i for i in range(n+1)]
cost = [1e9]*(n+1)

for i in range(n):
    cost[i+1] = arr[i]

for i in range(m):
    v,w = map(int,input().split())
    union(v,w)

ans = 0
for i in range(1,n+1):
    px = find(i)
    if px != 0:
        ans += cost[px]
        union(0,px)

if ans <= k:
    print(ans)
else:
    print('Oh no')

4월 4주차/친구비.py


import pandas as pd
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder
from st_aggrid.shared import GridUpdateMode

STREAMLIT_AGGRID_URL = "https://github.com/PablocFonseca/streamlit-aggrid"
st.set_page_config(
    layout="centered", page_icon="🖱" , page_title="Interactive table app"
)
st.title("🖱 Interactive table app" )
st.write(
    """Indiana Births and Infant Mortality."""
)

st.write("Go ahead, click on a row in the table below!")

def aggrid_interactive_table(df: pd.DataFrame):
    """Creates an st-aggrid interactive table based on a dataframe.

    Args:
        df (pd.DataFrame]): Source dataframe

    Returns:
        dict: The selected row
    """
    options = GridOptionsBuilder.from_dataframe(
        df, enableRowGroup=True, enableValue=True, enablePivot=True
    )

    options.configure_side_bar()

    options.configure_selection("single")
    selection = AgGrid(
        df,
        enable_enterprise_modules=True,
        gridOptions=options.build(),
        theme="light",
        update_mode=GridUpdateMode.MODEL_CHANGED,
        allow_unsafe_jscode=True,
    )

    return selection

##"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv"

iris = pd.read_csv(

    "http://www.indybiosystems.com/datasets/ibid_5000.txt"
)

selection = aggrid_interactive_table(df=iris)

if selection:
    st.write("You selected:")
    st.json(selection["selected_rows"])

st.write("## Code")


import os
import sys
import transaction

import json

from pyramid.paster import (
    get_appsettings,
    setup_logging,
    )

from pyramid.scripts.common import parse_vars

from ..models.meta import Base
from ..models import (
    get_engine,
    get_session_factory,
    get_tm_session,
    )
from ..models import Song, Lyric, Tag, Page, User, Funds

def usage(argv):
    cmd = os.path.basename(argv[0])
    print('usage: %s <config_uri> [var=value]\n'
          '(example: "%s development.ini")' % (cmd, cmd))
    sys.exit(1)

def main(argv=sys.argv):
    if len(argv) < 2:
        usage(argv)
    config_uri = argv[1]
    options = parse_vars(argv[2:])
    setup_logging(config_uri)
    settings = get_appsettings(config_uri, options=options)

    engine = get_engine(settings)
    Base.metadata.create_all(engine)

    session_factory = get_session_factory(engine)

    with transaction.manager:
        dbsession = get_tm_session(session_factory, transaction.manager)

        editor = User(name='editor', role='editor')
        editor.set_password('editor')
        dbsession.add(editor)

        basic = User(name='basic', role='basic')
        basic.set_password('basic')
        dbsession.add(basic)

        page = Page(
            name='FrontPage',
            title='Prva Stran',
            subtitle='Dobrodosli',
            creator=editor,
            data='Novice o Kripto Kojnih.',
        )
        dbsession.add(page)

        about = Page(
            name='About',
            title='O Nas',
            subtitle='O Nas',
            creator=basic,
            data='GoOpen',
        )
        dbsession.add(about)

        with open('./cryptodokladi/scripts/capoeiralyrics_2017-12-18.json') as data_file:
            data = json.load(data_file)

        for song in data:
            s = Song(title = song['title'],
                    subtitle = song['subtitle'])

            if song['ytplayer'] is not None:
                s.ytplayer = song['ytplayer']

            dbsession.add(s)

            for key in song['lyrics'].keys():

                l = Lyric(language=key, text=song['lyrics'][key].replace('\n', '<br />'), song=s)

                dbsession.add(l)

        with open('./cryptodokladi/scripts/tags_2017-12-18.json') as data_file:
            data = json.load(data_file)

        for s in data:
            song = dbsession.query(Song).filter_by(title=s['title']).first()
            if song is not None:

                for t in s['tags']:
                    tag = dbsession.query(Tag).filter_by(text=t).first()
                    if tag is None:
                        tag = Tag(text = t)
                        dbsession.add(tag)

                    song.tags.append(tag)


from tkinter import ttk, constants, Menu
from logic.chain_analytics_service import chain_analytics_service

class NewEventsView:
    def __init__(self, root, transactions_handler, filter_handler):
        self._root = root
        self._transactions_handler = transactions_handler
        self._filter_handler = filter_handler
        self._frame = None
        self._contract_list = None
        self._intitialize()

    def pack(self):
        self._frame.pack(fill=constants.X)

    def destroy(self):
        self._frame.destroy()

    def _intitialize(self):
        self._frame = ttk.Frame(master=self._root)

        label = ttk.Label(master=self._frame, text="Input/Filter field:")

        self._wallet_id = ttk.Entry(master=self._frame)

        button_start = ttk.Button(
            master=self._frame,
            text="Find all transactions by address",
            command=lambda: self._transactions_handler(
                str(self._wallet_id.get()))
        )

        button_add_by_hash = ttk.Button(
            master=self._frame,
            text="Add transaction by transaction hash",
            command=lambda: self._add_transaction_by_hash(
                str(self._wallet_id.get()))
        )

        button_database = ttk.Button(
            master=self._frame,
            text="Get everything in database",
            command=lambda: self._get_all_transactions_db()
        )

        button_filter_by_to = ttk.Button(
            master=self._frame,
            text="Filter by from address",
            command=lambda: self._get_all_transactions_db("to")
        )

        button_filter_by_from = ttk.Button(
            master=self._frame,
            text="Filter by to address",
            command=lambda: self._get_all_transactions_db("from")
        )

        label.grid(row=1, column=0)
        button_start.grid(row=0, column=0)
        button_add_by_hash.grid(row=0,column=1)
        button_database.grid(row=1, column=1)
        button_filter_by_to.grid(row=1, column=2)
        button_filter_by_from.grid(row=1, column=3)

        self._wallet_id.grid(row=2)

        frame_contracts = ttk.Frame(master=self._frame)
        frame_contracts.grid(row=3)
        columns = ["Timestamp", "Transaction hash",
                   "To address", "From address", "Amount", "Gas fee"]
        self._contract_list = ttk.Treeview(
            master=frame_contracts, columns=columns, show="headings")
        self._contract_list.grid(row=0, column=0)
        self._contract_list.bind('<Button-3>', self.clipboard)
        for col in columns[0:]:
            self._contract_list.column(col, width=120)
            self._contract_list.heading(col, text=col)

    def _get_all_transactions_db(self, filter=None):
        transactions = chain_analytics_service.get_transactions_in_db()
        transactions_filtered = []
        if filter:
            for transaction in transactions:
                if filter == "to" and transaction.to_address == self._wallet_id.get():
                    transactions_filtered.append(transaction)
                elif filter == "from" and transaction.from_address == self._wallet_id.get():
                    transactions_filtered.append(transaction)
        else:
            transactions_filtered = transactions

        for i in self._contract_list.get_children():
            self._contract_list.delete(i)
        for transaction in transactions_filtered:
            self._contract_list.insert('', 'end', values=(transaction.timestamp, transaction.transaction_hash,
                                       transaction.to_address, transaction.from_address, transaction.amount, transaction.gas_fee))

    def _add_transaction_by_hash(self,transaction_hash):
        chain_analytics_service.add_transaction_to_db(transaction_hash)
        self._get_all_transactions_db()

    def clipboard(self, event):
        tree = self._contract_list
        cur_item = tree.item(tree.focus())
        col = tree.identify_column(event.x)
        if col == '#2':
            self._frame.clipboard_clear()
            print(cur_item)
            self._frame.clipboard_append(cur_item["values"][1])
        if col == '#3':
            self._frame.clipboard_clear()
            print(cur_item)
            self._frame.clipboard_append(cur_item["values"][2])
        if col == '#4':
            self._frame.clipboard_clear()
            print(cur_item)
            self._frame.clipboard_append(cur_item["values"][3])


from functools import partial, wraps

from usage_model import Redis

def init_redis(func=None, *, redis: Redis = None):
    if func is None:
        return partial(init_redis, redis=redis)

    @wraps(func)
    async def wrapper(*args, **kwargs):
        if not redis.is_connected:
            await redis.connect()
        try:
            result = func(*args, **kwargs)
        finally:
            if redis.is_connected:
                await redis.dis_connected()
        return result

    return wrapper


#!/usr/bin/env python
#
# License: BSD
#   https://raw.github.com/robotics-in-concert/concert_services/license/LICENSE
#
##############################################################################
# About
##############################################################################

# Simple script to pimp out make a map operations for rocon interactions.
#
# - watch the app manager status and when it has a remote controller,
# - flip a spawn/kill pair across
# - call the spawn api
#  - the turtle herder will flip back some handles then.

##############################################################################
# Imports
##############################################################################

import rospy
import rocon_uri
import concert_service_utilities
import unique_id
import rocon_std_msgs.msg as rocon_std_msgs
import scheduler_msgs.msg as scheduler_msgs
import concert_service_msgs.msg as concert_service_msgs

from concert_software_farmer import SoftwareFarmClient, FailedToStartSoftwareException

class WaypointNavPimp(concert_service_utilities.ResourcePimp):

    def setup_variables(self):
        '''
            Need to setup the following variables
            service_priority, service_id, resource_type, available_resource_publisher_name, capture_topic_name
        '''
        (service_name, service_description, service_priority, service_id) = concert_service_utilities.get_service_info()
        self.service_priority = service_priority
        self.service_id = service_id
        self.resource_type = 'rocon_apps/waypoint_nav'
        self.available_resource_publisher_name = 'available_waypoint_nav'
        self.capture_topic_name = 'capture_waypoint_nav'

        # it should be rapp's public interface
        self._default_map_topic = 'map'
        self._default_waypoints_topic = 'waypoints'

    def ros_capture_callback(self, request_id, msg):
        '''
         Processes the service pair server 'capture_waypiont_nav'. This will run
         in a thread of its own for each request. It has a significantly long lock
         though - this needs to get fixed.
        '''
        # Todo : request the scheduler for this resource,
        # use self.allocation_timeout to fail gracefully
        response = concert_service_msgs.CaptureResourceResponse()
        response.result = False
        if not msg.release:  # i.e. we're capturing:
            if msg.rocon_uri not in [r.uri for r in self.available_resources]:
                self.logwarn("couldn't capture resource [not available][%s]" % msg.rocon_uri)
                response.result = False
            else:
                resource = self._create_resource(msg.rocon_uri)
                request_result, resource_request_id = self.send_allocation_request(resource)
                response.result = request_result
                if request_result == False:
                    self.logwarn("couldn't capture resource [timed out][%s]" % msg.rocon_uri)
                else:
                    self.loginfo("captured resource [%s][%s]" % (msg.rocon_uri, resource_request_id))
                    response.remappings = resource.remappings
        else:  # we're releasing
            self.send_releasing_request(msg.rocon_uri)
            response.result = True
        return response

    def _create_resource(self, uri):
        # Create a resource to request
        resource = scheduler_msgs.Resource()
        resource.id = unique_id.toMsg(unique_id.fromRandom())
        resource.rapp = self.resource_type
        resource.uri = uri
        resource.remappings = []
        map_remapped, waypoints_remapped  = self._get_remapped_topic(rocon_uri.parse(resource.uri).name.string)
        resource.remappings = [rocon_std_msgs.Remapping(self._default_map_topic, map_remapped), rocon_std_msgs.Remapping(self._default_waypoints_topic, waypoints_remapped)]
        return resource

    def _get_remapped_topic(self, name):
        '''
          Sets up remapping rules for Rapp configuration
        '''
        map_remapped = rospy.resolve_name(rospy.get_param('map_topic'))
        waypoints_remapped = rospy.resolve_name(rospy.get_param('waypoints_topic'))

        return map_remapped, waypoints_remapped

    def loginfo(self, msg):
        rospy.loginfo("WaypointNavPimp : %s"%str(msg))

    def logwarn(self, msg):
        rospy.logwarn("WaypointNavPimp : %s"%str(msg))

    def logerr(self, msg):
        rospy.logerr("WaypointNavPimp : %s"%str(msg))

##############################################################################
# Launch point
##############################################################################

if __name__ == '__main__':
    rospy.init_node('waypoint_nav_pimp')

    pimp = WaypointNavPimp()
    rospy.on_shutdown(pimp.cancel_all_requests)
    pimp.loginfo("Initialised")
    rospy.spin()
    pimp.loginfo("Bye Bye")


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from environments.mc_model.mc_environment import MonteCarloEnv
from collections import defaultdict
import pickle

def heatmap_Q(Q_tab, file_path=None, skip_T=False):
    """
    generates a heatmap based on Q_tab

    Parameters
    ----------
    Q_tab : dictionary
        a dictionary with values for all state-action pairs
    file_path : str
        where the files should be saved
    skip_T : bool
        whether or not the final time step should be included

    Returns
    -------
    None
    """

    optimal_bid = dict()
    optimal_ask = dict()
    optimal_MO = dict()

    plt.figure()
    for state in list(Q_tab.keys()):
        optimal_action = np.unravel_index(Q_tab[state].argmax(), Q_tab[state].shape)
        optimal_bid[state] = optimal_action[0] + 1
        optimal_ask[state] = optimal_action[1] + 1
        optimal_MO[state] = optimal_action[2]

    ser = pd.Series(
        list(optimal_bid.values()), index=pd.MultiIndex.from_tuples(optimal_bid.keys())
    )
    df = ser.unstack().fillna(0)
    if skip_T:
        df = df.iloc[:, :-1]
    fig = sns.heatmap(df)
    fig.set_title("Optimal bid depth")
    fig.set_xlabel("t (grouped)")
    fig.set_ylabel("inventory (grouped)")

    if file_path == None:
        plt.show()
    else:
        plt.savefig(file_path + "opt_bid_heat")
        plt.close()

    plt.figure()
    ser = pd.Series(
        list(optimal_ask.values()), index=pd.MultiIndex.from_tuples(optimal_ask.keys())
    )
    df = ser.unstack().fillna(0)
    if skip_T:
        df = df.iloc[:, :-1]
    fig = sns.heatmap(df)
    fig.set_title("Optimal ask depth")
    fig.set_xlabel("t (grouped)")
    fig.set_ylabel("inventory (grouped)")

    if file_path == None:
        plt.show()
    else:
        plt.savefig(file_path + "opt_ask_heat")
        plt.close()

    plt.figure()
    ser = pd.Series(
        list(optimal_MO.values()), index=pd.MultiIndex.from_tuples(optimal_MO.keys())
    )
    df = ser.unstack().fillna(0)
    if skip_T:
        df = df.iloc[:, :-1]
    fig = sns.heatmap(df)
    fig.set_title("Market order")
    fig.set_xlabel("t (grouped)")
    fig.set_ylabel("inventory (grouped)")

    if file_path == None:
        plt.show()
    else:
        plt.savefig(file_path + "opt_mo_heat")
        plt.close()

def heatmap_Q_std(Q_std, file_path=None):
    """
    Plots a heatmap of the standard deviation of the q-value of the optimal actions

    Parameters
    ----------
    Q_std : defaultdict
        a defaultdict with states as keys and standard deviations as values
    file_path : str
        where the files should be saved

    Returns
    -------
    None
    """

    plt.figure()

    ser = pd.Series(list(Q_std.values()), index=pd.MultiIndex.from_tuples(Q_std.keys()))
    df = ser.unstack().fillna(0)
    fig = sns.heatmap(df)
    fig.set_title("Standard deviation of optimal actions")
    fig.set_xlabel("t (grouped)")
    fig.set_ylabel("inventory (grouped)")

    if file_path == None:
        plt.show()
    else:
        plt.savefig(file_path + "heatmap_of_std")
        plt.close()

def heatmap_Q_n_errors(Q_mean, Q_tables, n_unique=True, file_path=None):
    """
    Plots a heatmap of the difference in optimal actions between runs. Can show number of
    unique actions or number of actions not agreeing with mean optimal.

    Parameters
    ----------
    Q_mean : defaultdict
        a defaultdict with states as keys and mean q-values as values
    Q_tables : list
        a list with defaultdicts with states as keys and q-values as values
    n_unique : bool
        whether or not number of unique actions should be used or not. If False,
        errors compared to mean optimal will be used
    file_path : str
        where the files should be saved

    Returns
    -------
    None
    """

    Q_n_errors = Q_mean

    if n_unique:
        # ----- CALCULATE THE NUMBER OF UNIQUE ACTIONS -----
        title = "Number of unique of optimal actions"
        vmin = 1
        for state in list(Q_mean.keys()):
            opt_action_array = []

            for Q_tab in Q_tables:
                opt_action = np.unravel_index(Q_tab[state].argmax(), Q_tab[state].shape)
                opt_action_array.append(opt_action)

            n_unique_opt_actions = len(set(opt_action_array))

            Q_n_errors[state] = n_unique_opt_actions

    else:
        # ----- CALCULATE THE NUMBER ERRORS COMPARED TO MEAN OPTIMAL -----
        title = "Number of actions not agreeing with mean optimal action"
        vmin = 0
        for state in list(Q_mean.keys()):
            num_errors = 0

            for Q_tab in Q_tables:
                error = np.unravel_index(
                    Q_tab[state].argmax(), Q_tab[state].shape
                ) != np.unravel_index(Q_mean[state].argmax(), Q_mean[state].shape)
                num_errors += error

            Q_n_errors[state] = num_errors

    plt.figure()
    ser = pd.Series(
        list(Q_n_errors.values()), index=pd.MultiIndex.from_tuples(Q_n_errors.keys())
    )
    df = ser.unstack().fillna(0)
    fig = sns.heatmap(df, vmin=vmin, vmax=len(Q_tables))
    fig.set_title(title)
    fig.set_xlabel("t (grouped)")
    fig.set_ylabel("inventory (grouped)")

    if file_path == None:
        plt.show()

    else:
        if n_unique:
            plt.savefig(file_path + "n_unique_opt_actions")
            plt.close()
        else:
            plt.savefig(file_path + "n_errors_compared_to_mean")
            plt.close()

def show_Q(Q_tab, file_path=None):
    """
    plotting the optimal depths from Q_tab

    Parameters
    ----------
    Q_tab : dictionary
        a dictionary with values for all state-action pairs
    file_path : str
        where the files should be saved

    Returns
    -------
    None
    """

    optimal_bid = dict()
    optimal_ask = dict()

    for state in list(Q_tab.keys()):
        optimal_action = np.array(
            np.unravel_index(Q_tab[state].argmax(), Q_tab[state].shape)
        )
        [optimal_bid[state], optimal_ask[state]] = optimal_action[0:2] + 1

    ser = pd.Series(
        list(optimal_bid.values()), index=pd.MultiIndex.from_tuples(optimal_bid.keys())
    )
    df = ser.unstack()
    df = df.T
    df.columns = "q=" + df.columns.map(str)
    df.plot.line(title="Optimal bid depth", style=".-")
    plt.legend(loc="upper right")
    plt.xlabel("t (grouped)")
    plt.ylabel("depth")
    plt.xticks(np.arange(1, df.shape[0] + 1))

    if file_path == None:
        plt.show()
    else:
        plt.savefig(file_path + "opt_bid_strategy")
        plt.close()

    ser = pd.Series(
        list(optimal_ask.values()), index=pd.MultiIndex.from_tuples(optimal_ask.keys())
    )
    df = ser.unstack()
    df = df.T
    df.columns = "q=" + df.columns.map(str)
    df.plot.line(title="Optimal ask depth", style=".-")
    plt.legend(loc="upper right")
    plt.xlabel("t (grouped)")
    plt.ylabel("depth")
    plt.xticks(np.arange(1, df.shape[0] + 1))

    if file_path == None:
        plt.show()
    else:
        plt.savefig(file_path + "opt_ask_strategy")
        plt.close()

def load_Q(filename, default=True):
    """
    loads a Q table from a pkl file

    Parameters
    ----------
    filename : str
        a string for the filename
    default : bool
        if a defaultdictionary or a dictionary should be returned

    Returns
    -------
    Q : dict
        a defaultdictionary/dictionary will all Q tables. the keys are actions and the values are the actual Q tables
    args : dict
        the model parameters
    n : int
        the number of episodes the Q-learning was run for
    rewards : list
        the saved rewards during training
    """

    # Load the file
    file = open("Q_tables/" + filename + ".pkl", "rb")
    Q_raw, args, n, rewards = pickle.load(file)

    # If we don't want a defaultdict, just return a dict
    if not default:
        return Q_raw

    # Find d
    dim = Q_raw[(0, 0)].shape[0]

    # Transform to a default_dict
    Q_loaded = defaultdict(lambda: np.zeros((dim, dim)))
    Q_loaded.update(Q_raw)

    return Q_loaded, args, n, rewards


# Reference: https://leetcode.com/problems/palindrome-pairs/discuss/535904/Python-3-Clean-Solutions
class Solution:
    def palindromePairs(self, words: list) -> list:
        def palindrome(word:str) -> bool:
            return word == word[::-1]

        ans = []

        table = {}
        for i, word in enumerate(words):
            table[word] = i

        for index, word in enumerate(words):
            for j in range(len(word)+1):
                w1, w2 = word[:j], word[j:]
                if palindrome(w1):
                    rev = w2[::-1]
                    if rev in table and table[rev] != index:
                        ans.append([table[rev], index])

                if w2 and palindrome(w2):
                    rev = w1[::-1]
                    if rev in table and table[rev] != index:
                        ans.append([index, table[rev]])

        return ans