import tkinter
from tkinter import *
from tkinter import ttk
import tkinter.messagebox as tkMessageBox
from tkinter.filedialog import askopenfilename
from tkinter import filedialog
from PIL import Image, ImageTk
import mysql.connector
import datetime
from tkcalendar import Calendar, DateEntry
import re
import io
from io import BytesIO
import tkinter.messagebox as mbox
from tkinter.font import Font
import pyttsx3

engine = pyttsx3.init()

def database():
    try:
        global con, cur
        con = mysql.connector.connect(
        host="localhost",
        user="root",
        password="",
        database="python_mini_project"
        )
        print("Connection Established Successfully")

        cur = con.cursor()

    except:
        print("Database Status: There was an error")

def detailDisplay(event):
    database()
    detail_display = Tk()
    window_width = 700
    window_height = 700
    screen_width = detail_display.winfo_screenwidth()
    screen_height = detail_display.winfo_screenheight()
    x = (screen_width/2) - (window_width/2)
    y = (screen_height/2) - (window_height/2)
    detail_display.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y-50))
    detail_display.resizable(width=False, height=False)
    detail_display.configure(bg='white')
    item = tree.selection()[0]
    sap_no = tree.item(item, "values")[3]
    cur.execute("SELECT * FROM student_data WHERE sap_no=%s", (sap_no,))
    record = cur.fetchone()

    if record:
        detail_display.title(f"{record[2]} Record Details")

        # Add labels to display the record details

        roll_label = Label(detail_display, text="Roll No", font=("Helvatica", 10, "bold"), bg="white")
        roll_label.place(x=370, y=50)
        Label(detail_display, text=record[2],  bg="white", font=("Helvatica", 10)).place(x=430,y=50)

        sap_label = Label(detail_display, text="Sap No", font=("Helvatica", 10, "bold"), bg="white")
        sap_label.place(x=150, y=50)
        Label(detail_display, text=record[1],font=("Helvatica", 10),  bg="white").place(x=210,y=50)

        year_of_admission_label = Label(detail_display, text="Year of Admission: ", bg="white", font=("Helvatica", 10, "bold"))
        year_of_admission_label.place(x=20,y=120)
        Label(detail_display, text=record[3],font=("Helvatica", 10),  bg="white").place(x=150,y=120)

        type_of_admission_label = Label(detail_display, text="Type of Admission: ", bg="white", font=("Helvatica", 10, "bold"))
        type_of_admission_label.place(x=20,y=155)
        Label(detail_display, text=record[4], font=("Helvatica", 10),  bg="white").place(x=150,y=155)

        branch_label = Label(detail_display, text="Branch: ", bg="white", font=("Helvatica", 10, "bold"))
        branch_label.place(x=20,y=210)
        Label(detail_display, text=record[5], font=("Helvatica", 10),  bg="white").place(x=150,y=210)

        name_label = Label(detail_display, text="Student Name: ", bg="white", font=("Helvatica", 10, "bold"))
        name_label.place(x=20, y=245)
        Label(detail_display, text=record[6], font=("Helvatica", 10),  bg="white").place(x=150, y=245)

        contact_label = Label(detail_display, text="Contact No.: ", bg="white", font=("Helvatica", 10, "bold"))
        contact_label.place(x=20,y=290)
        Label(detail_display, text=record[8], font=("Helvatica", 10),  bg="white").place(x=150,y=315)
        Label(detail_display, text=record[7], font=("Helvatica", 10),  bg="white").place(x=150,y=290)

        email_label = Label(detail_display, text="Email: ", bg="white", font=("Helvatica", 10, "bold"))
        email_label.place(x=20,y=360)
        Label(detail_display, text=record[10], font=("Helvatica", 10),  bg="white").place(x=150,y=390)
        Label(detail_display, text=record[9], font=("Helvatica", 10),  bg="white").place(x=150,y=360)

        date_of_birth_label = Label(detail_display, text="Date of Birth:", bg="white", font=("Helvatica", 10, "bold"))
        date_of_birth_label.place(x=20,y=445)
        Label(detail_display, text=record[11], font=("Helvatica", 10),  bg="white").place(x=150, y=445)

        address_label = Label(detail_display, text="Current Address: ", bg="white", font=("Helvatica", 10, "bold"))
        address_label.place(x=20, y=505)
        addr = Label(detail_display, text=record[12], font=("Helvatica", 10),  bg="white", width=20, height=2, wraplength=160, anchor="w", justify="left")
        addr.place(x=150, y=505)

        achedemic_label = Label(detail_display, text="Achedemic Record: ", bg="white", font=("Helvatica", 10, "bold"))
        achedemic_label.place(x=20, y=560)

        l1 = Label(detail_display, text="Exam ", bg="white", font=("Helvatica", 10)).place(x=20, y=580)
        l2 = Label(detail_display, text="Year of Passing", bg="white", font=("Helvatica", 10)).place(x=80, y=580)
        l3 = Label(detail_display, text="Percentage(%)", bg="white", font=("Helvatica", 10)).place(x=200,y=580)
        l4 = Label(detail_display, text="SSC", bg="white", font=("Helvatica", 10)).place(x=20, y=620)
        l5 = Label(detail_display, text="HSC", bg="white", font=("Helvatica", 10)).place(x=20, y=660)

        Label(detail_display, text=record[13], font=("Helvatica", 10),  bg="white").place(x=80,y=620)
        Label(detail_display, text=record[14], font=("Helvatica", 10),  bg="white").place(x=200,y=620)
        Label(detail_display, text=record[15], font=("Helvatica", 10),  bg="white").place(x=80,y=660)
        Label(detail_display, text=record[16], font=("Helvatica", 10),  bg="white").place(x=200,y=660)

        sem_label = Label(detail_display, text="Semesters", bg="white", font=("Helvatica", 10, "bold")).place(x=400, y=400)
        year_label = Label(detail_display, text="Year", bg="white", font=("Helvatica", 10, "bold")).place(x=500, y=400)
        percent_label = Label(detail_display, text="Percentages(%)", bg="white", font=("Helvatica", 10, "bold")).place(x=580, y=400)

        sem1_label = Label(detail_display, text="Semester 1", bg="white", font=("Helvatica", 10)).place(x=400, y=440)
        sem2_label = Label(detail_display, text="Semester 2", bg="white", font=("Helvatica", 10)).place(x=400, y=460)
        sem3_label = Label(detail_display, text="Semester 3", bg="white", font=("Helvatica", 10)).place(x=400, y=480)
        sem4_label = Label(detail_display, text="Semester 4", bg="white", font=("Helvatica", 10)).place(x=400, y=500)
        sem5_label = Label(detail_display, text="Semester 5", bg="white", font=("Helvatica", 10)).place(x=400, y=520)
        sem6_label = Label(detail_display, text="Semester 6", bg="white", font=("Helvatica", 10)).place(x=400, y=540)

        Label(detail_display, text=record[18], font=("Helvatica", 10),  bg="white").place(x=500, y=440)
        Label(detail_display, text=record[19], font=("Helvatica", 10),  bg="white").place(x=500, y=460)
        Label(detail_display, text=record[20], font=("Helvatica", 10),  bg="white").place(x=500, y=480)
        Label(detail_display, text=record[21], font=("Helvatica", 10),  bg="white").place(x=500, y=500)
        Label(detail_display, text=record[22], font=("Helvatica", 10),  bg="white").place(x=500, y=520)
        Label(detail_display, text=record[23], font=("Helvatica", 10),  bg="white").place(x=500, y=540)
        Label(detail_display, text=record[24], font=("Helvatica", 10),  bg="white").place(x=580, y=440)
        Label(detail_display, text=record[25], font=("Helvatica", 10),  bg="white").place(x=580, y=460)
        Label(detail_display, text=record[26], font=("Helvatica", 10),  bg="white").place(x=580, y=480)
        Label(detail_display, text=record[27], font=("Helvatica", 10),  bg="white").place(x=580, y=500)
        Label(detail_display, text=record[28], font=("Helvatica", 10),  bg="white").place(x=580, y=520)
        Label(detail_display, text=record[29], font=("Helvatica", 10),  bg="white").place(x=580, y=540)

        image_data = record[30]

        # Create an image from the retrieved data
        image = Image.open(io.BytesIO(image_data))

        # Calculate the aspect ratio of the image
        image_width, image_height = image.size
        aspect_ratio = image_width / image_height

        # Determine the dimensions for cropping
        canvas_width = 150
        canvas_height = 150
        if aspect_ratio > 1:
            # Landscape image, crop the width
            crop_width = image_height * aspect_ratio
            crop_height = image_height
        else:
            # Portrait image or square image, crop the height
            crop_width = image_width
            crop_height = image_width / aspect_ratio
        crop_x = (image_width - crop_width) / 2
        crop_y = (image_height - crop_height) / 2

        # Crop the image to desired dimensions
        cropped_image = image.crop((crop_x, crop_y, crop_x + crop_width, crop_y + crop_height))

        # Resize the cropped image to fit the canvas
        resized_image = cropped_image.resize((canvas_width, canvas_height), Image.ANTIALIAS)

        # Display the resized image in a canvas
        image_upload = Canvas(detail_display, width=canvas_width, height=canvas_height)
        image_upload.place(x=500, y=120)
        image_object = ImageTk.PhotoImage(resized_image)
        image_upload.image = image_object  # Keep a reference to the image object
        image_upload.create_image(int(canvas_width / 2), int(canvas_height / 2), image=image_object)
        image_upload.update()

def display():
    database()
    count = 0
    tree.delete(*tree.get_children())
    cur.execute("ALTER TABLE student_data AUTO_INCREMENT = 1")
    cur.execute("select sr_no, stu_name, roll_no, sap_no, branch, present_year from student_data")
    fetch = cur.fetchall()
    for row in fetch:
        tree.insert("", "end", values=row)
        tree.bind("<Double-Button-1>", detailDisplay)
        count += 1

    tkMessageBox.showinfo("Total Records",f"Total Records Found: {count}")
    cur.close()
    con.close()

def insert():
    """
    This function creates a GUI window for inserting student biodata records with various input fields
    and options.
    """
    def clearData():
        image = canvas_image.delete(0, ALL)
        sap_no = sap_text.delete(0,END)
        roll_no = roll_text.delete(0,END)
        yoa = year_of_admission_option.delete(0,END)
        branch = branch_text.delete(0,END)
        stu_name = name_text.delete(0,END)
        contact1 = contact_text1.delete(0,END)
        contact2 = contact_text2.delete(0,END)
        email1 = email_text1.delete(0,END)
        email2 = email_text2.delete(0,END)
        address = address_text.delete("1.0",END)
        ssc_pass_year = tf1.delete(0,END)
        ssc_marks = tf2.delete(0,END)
        hsc_pass_year = tf3.delete(0,END)
        hsc_marks = tf4.delete(0,END)
        sem1 = year1_entry.delete(0,END)
        sem2 = year2_entry.delete(0,END)
        sem3 = year3_entry.delete(0,END)
        sem4 = year4_entry.delete(0,END)
        sem5 = year5_entry.delete(0,END)
        sem6 = year6_entry.delete(0,END)
        sem1_percent = percentage1_entry.delete(0,END)
        sem2_percent = percentage2_entry.delete(0,END)
        sem3_percent = percentage3_entry.delete(0,END)
        sem4_percent = percentage4_entry.delete(0,END)
        sem5_percent = percentage5_entry.delete(0,END)
        sem6_percent = percentage6_entry.delete(0,END)

    def storeData():
        """
        The function connects to a database, inserts a record, commits the changes, and closes the
        database.
        """
        try:
            database()

            sap_no = sap_text.get()
            if not sap_no:
                tkMessageBox.showinfo("Alert", "Please Enter SAP No")
                return False

            cur.execute("SELECT COUNT(*) FROM student_data WHERE sap_no = %s", (sap_no,))
            count = cur.fetchone()[0]
            if count > 0:
                tkMessageBox.showinfo("Alert", "SAP No already exists")
                return False

            image_binary = open(filename, 'rb').read()
            if not image_binary:
                tkMessageBox.showinfo("Alert","Please Select a Image")
                return False

            sap_no = sap_text.get()
            if not sap_no:
                tkMessageBox.showinfo("Alert", "Please Enter SAP No")
                return False

            roll_no = roll_text.get()
            if not roll_no:
                tkMessageBox.showinfo("Alert", "Please Enter Roll No")
                return False

            yoa = year_of_admission_option.get()
            if not yoa:
                tkMessageBox.showinfo("Alert", "Please Select Year of Admission")
                return False

            toa = selected_value.get()

            branch = branch_text.get()
            if not branch:
                tkMessageBox.showinfo("Alert", "Please Select A Branch")
                return False

            stu_name = name_text.get()
            if not stu_name:
                tkMessageBox.showinfo("Alert", "Please Enter Stuent Name")
                return False

            contact1 = contact_text1.get()
            contact2 = contact_text2.get()
            if not contact1 and not contact2:
                tkMessageBox.showinfo("Alert", "Please Enter Atleast 1 Contact Info.")
                return False

            email1 = email_text1.get()
            email2 = email_text2.get()
            if not email1 and not email2:
                tkMessageBox.showinfo("Alert", "Please Enter Atleast 1 Email ID")

                if validate_email_input:
                    tkMessageBox.showinfo("Alert", "Please Enter Correct Email")
                    return False

            date = date_of_birth.get()
            dob = str(date)

            address = address_text.get("1.0", "end-1c")
            if not address:
                tkMessageBox.showinfo("Alert", "Please Enter Your Address")
                return False

            ssc_pass_year = tf1.get()
            ssc_marks = tf2.get()
            hsc_pass_year = tf3.get()
            hsc_marks = tf4.get()
            sem1 = year1_entry.get()
            sem2 = year2_entry.get()
            sem3 = year3_entry.get()
            sem4 = year4_entry.get()
            sem5 = year5_entry.get()
            sem6 = year6_entry.get()
            sem1_percent = percentage1_entry.get()
            sem2_percent = percentage2_entry.get()
            sem3_percent = percentage3_entry.get()
            sem4_percent = percentage4_entry.get()
            sem5_percent = percentage5_entry.get()
            sem6_percent = percentage6_entry.get()
            calc = int(yoa)
            year = datetime.datetime.now().year
            year = year - calc
            present_year = str(year)

            cur.execute("insert into student_data (sap_no, roll_no, yoa, toa, branch, stu_name, contact1, contact2, email1, email2, dob, address, ssc_pass_year, ssc_marks, hsc_pass_year, hsc_marks, present_year, sem1, sem2, sem3, sem4, sem5, sem6, sem1_percent, sem2_percent, sem3_percent, sem4_percent, sem5_percent, sem6_percent, image_binary) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
                        (sap_no, roll_no, yoa, toa, branch,stu_name,contact1, contact2, email1, email2, dob, address, ssc_pass_year, ssc_marks, hsc_pass_year, hsc_marks, present_year, sem1, sem2, sem3, sem4, sem5, sem6, sem1_percent, sem2_percent, sem3_percent, sem4_percent, sem5_percent, sem6_percent,image_binary))
            con.commit()
            tkMessageBox.showinfo("Success", "Record Inserted")
            display()

        except Exception as e:
            print("Database Error => ", e)
        finally:
            if con:
                con.close()
                print("Database Closed Successfully")
                print()

    insert = Tk()
    insert.title("Add Student")

    # setting window on the center of the screen
    window_width = 700
    window_height = 700
    screen_width = insert.winfo_screenwidth()
    screen_height = insert.winfo_screenheight()
    x = (screen_width/2) - (window_width/2)
    y = (screen_height/2) - (window_height/2)
    insert.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y-50))
    insert.resizable(width=False, height=False)
    insert.configure(bg='white')

    def choose_image():
        global canvas_image, image, filename
        canvas_image = Canvas(width=150, height=150)
        canvas_image.place(x=500,y=120)
        filename = filedialog.askopenfilename(title="Select file", filetypes=(("PNG files", "*.png"), ("JPEG files", "*.jpg"), ("JPG files", "*.jpg")))
        print("Selected file:", filename)  # Add this line to print the filename
        # Load the image
        if filename:
            try:
                image = Image.open(filename)
            except Exception as e:
                print("Error loading image:", e)  # Add this line to print any errors
                return
            image_width, image_height = image.size
            canvas_width, canvas_height = canvas_image.winfo_width(), canvas_image.winfo_height()
            # Calculate the scaling factor
            scale = min(canvas_width/image_width, canvas_height/image_height)
            new_width = int(image_width*scale)
            new_height = int(image_height*scale)
            # Resize the image
            resized_image = image.resize((new_width, new_height), Image.ANTIALIAS)
            image_object = ImageTk.PhotoImage(resized_image)
            # Add the image to the canvas
            canvas_image.delete("all")  # Remove any previously added image
            canvas_image.create_image(canvas_width/2, canvas_height/2, image=image_object)
            canvas_image.image = image_object

    # Creating a form
    canvas_image_upload = Canvas(insert, width=150, height=150)
    canvas_image_upload.place(x=500,y=120)

    img_label = Label(insert, text="Upload Photo: ", bg="white", font=("Helvatica", 10, "bold"))
    img_label.place(x=400, y=120)

    # Create a button for selecting the image
    upload_button = Button(insert, text="Choose Photo", command=choose_image)
    upload_button.place(x=530, y=290)

    sap_label = Label(insert, text="Sap No", font=("Helvatica", 10, "bold"), bg="white")
    sap_label.place(x=50, y=50)
    sap_text = Entry(insert, width=20, font=("Helvatica", 10), relief="groove", bd=2)
    sap_text.place(x=110,y=50)

    roll_label = Label(insert, text="Roll No", font=("Helvatica", 10, "bold"), bg="white")
    roll_label.place(x=330, y=50)
    roll_text = Entry(insert, width=20, font=("Helvatica", 10), relief="groove", bd=2)
    roll_text.place(x=400,y=50)

    current_year = datetime.datetime.now().year
    year = [i for i in range(2005,current_year)]
    year_of_admission_label = Label(insert, text="Year of Admission: ", bg="white", font=("Helvatica", 10, "bold"))
    year_of_admission_option = ttk.Combobox(insert, width=20, font=("Helvatica", 10), values=year, state="readonly")
    year_of_admission_option.place(x=150,y=120)
    year_of_admission_label.place(x=20,y=120)

    selected_value = StringVar(value="First Year")

    def update_selection(selection):
        selected_value.set(selection)

    type_of_admission_label = Label(insert, text="Type of Admission: ", bg="white", font=("Helvatica", 10, "bold"))
    type_of_admission_option1 = Radiobutton(insert, text="First Year", variable=selected_value, value="First Year", bg="white", command=lambda: update_selection("First Year"))
    type_of_admission_option2 = Radiobutton(insert, text="Direct Second Year",  variable=selected_value, value="Direct Second Year", bg="white", command=lambda: update_selection("Direct Second Year"))
    type_of_admission_option1.place(x=150,y=155)
    type_of_admission_option2.place(x=150,y=176)
    type_of_admission_label.place(x=20,y=155)

    branch_options = ["Information Technology", "Computer Engineering", "Mechanical Engineering", "Civil Engineering", "Electrical Engineering", "Plastic Engineering", "Chemical Engineering", "Electronics & Telecommunicaltion Engineering"]
    branch_label = Label(insert, text="Branch: ", bg="white", font=("Helvatica", 10, "bold"))
    branch_text = ttk.Combobox(insert, width=20, font=("Helvatica", 10), values=branch_options, state="readonly")
    branch_text.place(x=150,y=210)
    branch_label.place(x=20,y=210)

    def validate_name_input(name):
        if name == "":
            return True
        name_regex = "^[A-Za-z\s]{1,}[\.]{0,1}[A-Za-z\s]{0,}$"
        if re.match(name_regex, name):
            return True
        else:
            return False
    validate_name = (insert.register(validate_name_input), '%P')
    name_label = Label(insert, text="Student Name: ", bg="white", font=("Helvatica", 10, "bold"))
    name_text = Entry(insert, width=23, font=("Helvatica", 10), relief="groove", bd=2, validate='all', validatecommand=validate_name)
    name_text.place(x=150, y=245)
    name_label.place(x=20, y=245)

    def validate_contact_input(contact):
        contact_regex = r"\d{10}"
        if re.match(contact_regex, contact):
            return True
        else:
            return False
    validate_contact = (insert.register(validate_contact_input), '%P')
    contact_label = Label(insert, text="Contact No.: ", bg="white", font=("Helvatica", 10, "bold"))
    contact_text1 = Entry(insert, width=23, font=("Helvatica", 10), relief="groove", bd=2, validate='focusout', validatecommand=validate_contact)
    contact_text1.place(x=150,y=290)
    contact_text2 = Entry(insert, width=23, font=("Helvatica", 10), relief="groove", bd=2, validate='focusout', validatecommand=validate_contact)
    contact_text2.place(x=150,y=315)
    contact_label.place(x=20,y=290)

    def validate_email_input(email):
        email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if re.match(email_regex, email):
            return True
        else:
            return False
    validate_email = (insert.register(validate_email_input), '%P')
    email_label = Label(insert, text="Email: ", bg="white", font=("Helvatica", 10, "bold"))
    email_text1 = Entry(insert, width=23, font=("Helvatica", 10), relief="groove", bd=2, validate='focusout', validatecommand=validate_email)
    email_text1.place(x=150,y=360)
    email_text2 = Entry(insert, width=23, font=("Helvatica", 10), relief="groove", bd=2, validate='focusout', validatecommand=validate_email)
    email_text2.place(x=150,y=390)
    email_label.place(x=20,y=360)

    date_of_birth_label = Label(insert, text="Date of Birth:", bg="white", font=("Helvatica", 10, "bold"))
    date_of_birth_label.place(x=20,y=445)
    date_of_birth = DateEntry(insert, width=12, background='darkblue', foreground='white', borderwidth=2, state="readonly", date_pattern="dd-mm-yyyy")
    date_of_birth.place(x=150, y=445)

    address_label = Label(insert, text="Current Address: ", bg="white", font=("Helvatica", 10, "bold"))
    address_label.place(x=20, y=505)
    address_text = Text(insert, width=23, font=("Helvatica", 10), relief="groove", bd=2, height=2, wrap="word")
    address_text.place(x=150, y=505)

    achedemic_label = Label(insert, text="Achedemic Record: ", bg="white", font=("Helvatica", 10, "bold"))
    achedemic_label.place(x=20, y=560)

    l1 = Label(insert, text="Exam ", bg="white", font=("Helvatica", 10)).place(x=20, y=580)
    l2 = Label(insert, text="Year of Passing", bg="white", font=("Helvatica", 10)).place(x=80, y=580)
    l3 = Label(insert, text="Percentage(%)", bg="white", font=("Helvatica", 10)).place(x=200,y=580)
    l4 = Label(insert, text="SSC", bg="white", font=("Helvatica", 10)).place(x=20, y=620)
    l5 = Label(insert, text="HSC", bg="white", font=("Helvatica", 10)).place(x=20, y=660)

    tf1 = Entry(insert, width=10, font=("Helvatica", 10), relief="groove", bd=2)
    tf1.place(x=80,y=620)
    tf2 = Entry(insert, width=10, font=("Helvatica", 10), relief="groove", bd=2)
    tf2.place(x=200,y=620)
    tf3 = Entry(insert, width=10, font=("Helvatica", 10), relief="groove", bd=2)
    tf3.place(x=80,y=660)
    tf4 = Entry(insert, width=10, font=("Helvatica", 10), relief="groove", bd=2)
    tf4.place(x=200,y=660)

    sem_label = Label(insert, text="Semesters", bg="white", font=("Helvatica", 10, "bold")).place(x=350, y=400)

    sem1_label = Label(insert, text="Semester 1", bg="white", font=("Helvatica", 10)).place(x=350, y=440)
    sem2_label = Label(insert, text="Semester 2", bg="white", font=("Helvatica", 10)).place(x=350, y=460)
    sem3_label = Label(insert, text="Semester 3", bg="white", font=("Helvatica", 10)).place(x=350, y=480)
    sem4_label = Label(insert, text="Semester 4", bg="white", font=("Helvatica", 10)).place(x=350, y=500)
    sem5_label = Label(insert, text="Semester 5", bg="white", font=("Helvatica", 10)).place(x=350, y=520)
    sem6_label = Label(insert, text="Semester 6", bg="white", font=("Helvatica", 10)).place(x=350, y=540)

    year_label = Label(insert, text="Year, Month", bg="white", font=("Helvatica", 10, "bold")).place(x=460, y=400)
    year1_entry = Entry(insert, text="M1", bg="white", font=("Helvatica", 10), width=9, relief="groove", bd=2)
    year1_entry.place(x=470, y=440)
    year2_entry = Entry(insert, text="M2", bg="white", font=("Helvatica", 10), width=9, relief="groove", bd=2)
    year2_entry.place(x=470, y=460)
    year3_entry = Entry(insert, text="M3", bg="white", font=("Helvatica", 10), width=9, relief="groove", bd=2)
    year3_entry.place(x=470, y=480)
    year4_entry = Entry(insert, text="M4", bg="white", font=("Helvatica", 10), width=9, relief="groove", bd=2)
    year4_entry.place(x=470, y=500)
    year5_entry = Entry(insert, text="M5", bg="white", font=("Helvatica", 10), width=9, relief="groove", bd=2)
    year5_entry.place(x=470, y=520)
    year6_entry = Entry(insert, text="M6", bg="white", font=("Helvatica", 10), width=9, relief="groove", bd=2)
    year6_entry.place(x=470, y=540)

    percent_label = Label(insert, text="%", bg="white", font=("Helvatica", 10, "bold")).place(x=570, y=400)
    percentage1_entry = Entry(insert, text="Percentage 1", bg="white", font=("Helvatica", 10), width=7, relief="groove", bd=2)
    percentage1_entry.place(x=560, y=440)
    percentage2_entry = Entry(insert, text="Percentage 2", bg="white", font=("Helvatica", 10), width=7, relief="groove", bd=2)
    percentage2_entry.place(x=560, y=460)
    percentage3_entry = Entry(insert, text="Percentage 3", bg="white", font=("Helvatica", 10), width=7, relief="groove", bd=2)
    percentage3_entry.place(x=560, y=480)
    percentage4_entry = Entry(insert, text="Percentage 4", bg="white", font=("Helvatica", 10), width=7, relief="groove", bd=2)
    percentage4_entry.place(x=560, y=500)
    percentage5_entry = Entry(insert, text="Percentage 5", bg="white", font=("Helvatica", 10), width=7, relief="groove", bd=2)
    percentage5_entry.place(x=560, y=520)
    percentage6_entry = Entry(insert, text="Percentage 6", bg="white", font=("Helvatica", 10), width=7, relief="groove", bd=2)
    percentage6_entry.place(x=560, y=540)

    submit_record = Button(insert, text="Submit", command=storeData, width=10, bg="blue", fg="white", font=("Helvatica", 10, "bold"))
    submit_record.place(x=400,y=600)

    clear_record = Button(insert, text="Clear", command=clearData, width=10, bg="blue", fg="white", font=("Helvatica", 10, "bold"))
    clear_record.place(x=500,y=600)

    insert.mainloop()

def delete():
    """
    This function creates a frame to delete a record from the database and asks for a reason.
    """
    def delete_student():
        database()
        reason = delete_text.get('1.0', 'end-1c')
        if not reason:
            tkMessageBox.showinfo("Alert", "Please fill the mandatory field")
            return False

        selected_items = tree.selection()  # Get the selected items (rows)
        for item in selected_items:
            sap_id = tree.item(item)['values'][3]  # Retrieve the Sap Id (index 3) from the item
            cur.execute("DELETE FROM student_data WHERE sap_no=%s", (sap_id,))

        con.commit()
        display()
        tkMessageBox.showinfo("", f"{sap_id} Deleted")

    def delete_reason():
        global delete_text
        delete_reason = Tk()
        delete_reason.geometry("300x200")
        delete_reason.resizable(False, False)
        delete_reason.configure(background="#1338BE")
        delete_label = Label(delete_reason, text="Please Enter A Reason", font=("Helvatica", 11, "bold"), bg="#1338BE", fg="white").place(x=70, y=30)
        delete_text = Text(delete_reason, width=30, height=3, font=("Helvatica", 10), wrap="word",  relief="solid")
        delete_text.place(x=50,y=70)
        delete_msg = delete_text.get("1.0", "end-1c")
        confirm_button = Button(delete_reason, text="Confirm", width=10, height=1, font=("Helvatica", 10), bg="white", fg="green", command=delete_student).place(x=50, y=150)
        cancel_button = Button(delete_reason, text="Cancel", width=10, height=1, font=("Helvatica", 10), bg="white", fg="red", command=delete_reason.destroy).place(x=160, y=150)
        delete_reason.mainloop()

    if not tree.selection():
        tkMessageBox.showinfo("","Please Select a Student Record to Delete")
    else:
        delete_reason()

def search():
    """
    This function creates a search window with a label, text box, search button, and close button, and
    performs a search query on a database based on the keywords entered by the user.
    """
    search_window = Tk()
    global SEARCH
    SEARCH = StringVar()
    # create a new window
    search_window.title("Search Data")
    search_window.resizable(width=False, height=False)
    search_window.configure(background="#1338BE")
    # create a label and text box for the user to enter search keywords
    search_label = Label(search_window, text="Enter keywords to search:", bg="#1338BE", fg="white")
    search_label.pack(side=LEFT, padx=5, pady=5)
    search_entry = Entry(search_window, textvariable=SEARCH)
    search_entry.pack(side=LEFT, padx=5, pady=5)

    def advanced_search():
        """
        The function "advanced_search" is used to retrive data such as
        Marks Range or semester or HSC or SSC results also used to retrive results of semesters
        eg:
        [] > [] or [] < []
        [] >= [] or [] <= []
        """
        tkMessageBox.showinfo("Alert", "This feature is under progress!")

    # create a function to search for the keywords entered by the user
    def search_keyword():
        try:
            database()
            #checking search text is empty or not
            if SEARCH.get() != "":
                #clearing current display data
                tree.delete(*tree.get_children())

                #select query with where clause
                cur.execute("""SELECT sr_no, stu_name, roll_no, sap_no, branch, present_year FROM student_data WHERE
                                sr_no = %s OR
                                stu_name LIKE %s OR
                                roll_no LIKE %s OR
                                sap_no LIKE %s OR
                                branch LIKE %s OR
                                present_year LIKE %s""",
                                (SEARCH.get(), '%' + SEARCH.get() + '%', '%' + SEARCH.get() + '%', '%' + SEARCH.get() + '%', '%' + SEARCH.get() + '%', '%' + SEARCH.get() + '%'))

                #fetch all matching records
                fetch = cur.fetchall()

                #loop for displaying all records into GUI
                if len(fetch) == 0 or SEARCH.get() == "" or SEARCH.get() is None:
                    tkMessageBox.showinfo("Alert","Record Not Found")
                else:
                    for data in fetch:
                        tree.insert('', 'end', values=data)
                        # set foreground color for inserted items
                        if SEARCH.get().casefold() in data[1].casefold() or data[2].casefold() or data[3].casefold() or data[4].casefold() or data[5].casefold():
                            tree.tag_configure('found', foreground='red')
                            tree.item(tree.get_children()[-1], tags=('found',))
        except Exception as e:
            print("Database error => ", e)

        finally:
            if con:
                con.close()
                print("Database closed successfully")
                print()

    # create a button to perform the search
    search_button = Button(search_window, text="Search", command=search_keyword, bg="white")
    search_button.pack(side=LEFT, padx=5, pady=5)

    # create a button to close the search window
    close_button = Button(search_window, text="Close", command=search_window.destroy, bg="white")
    close_button.pack(side=LEFT, padx=5, pady=5)

    adv_button = Button(search_window, text="Advanced Search", command=advanced_search, bg="white")
    adv_button.pack(side=LEFT, padx=5, pady=5)

    search_window.mainloop()

def sort():
    '''
    In this function i have created a login window for admin
    which have input fields such as username and password as well as submit button to submit the form
    '''
    tkMessageBox.showinfo("Alert","This module is not been completed yet")

def updateDisplay(event):
    database()
    update = Tk()
    window_width = 700
    window_height = 700
    screen_width = update.winfo_screenwidth()
    screen_height = update.winfo_screenheight()
    x = (screen_width/2) - (window_width/2)
    y = (screen_height/2) - (window_height/2)
    update.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y-50))
    update.resizable(width=False, height=False)
    update.configure(bg='white')
    item = tree.selection()[0]
    sap_no = tree.item(item, "values")[3]
    cur.execute("SELECT * FROM student_data WHERE sap_no=%s", (sap_no,))
    record = cur.fetchone()

    def revert_info():
        database()
        cur.execute("SELECT * FROM student_data WHERE sap_no=%s", (sap_no,))
        record = cur.fetchone()

        if record:
            # Clear the entry fields
            contact1_entry.delete(0, END)
            contact2_entry.delete(0, END)
            email1_entry.delete(0, END)
            email2_entry.delete(0, END)
            address_entry.delete("1.0", END)
            y1.delete(0,END)
            y2.delete(0,END)
            y3.delete(0,END)
            y4.delete(0,END)
            y5.delete(0,END)
            y6.delete(0,END)
            p1.delete(0,END)
            p2.delete(0,END)
            p3.delete(0,END)
            p4.delete(0,END)
            p5.delete(0,END)
            p6.delete(0,END)

            # Restore the original values
            contact1_entry.insert(END, record[7])
            contact2_entry.insert(END, record[8])
            email1_entry.insert(END, record[9])
            email2_entry.insert(END, record[10])
            address_entry.insert(END, record[12])
            y1.insert(END, record[18])
            y2.insert(END, record[19])
            y3.insert(END, record[20])
            y4.insert(END, record[21])
            y5.insert(END, record[22])
            y6.insert(END, record[23])
            p1.insert(END, record[24])
            p2.insert(END, record[25])
            p3.insert(END, record[26])
            p4.insert(END, record[27])
            p5.insert(END, record[28])
            p6.insert(END, record[29])
            tkMessageBox.showinfo("Success", "Reverted to original values")

        con.close()

    def update_info():
        database()
        cur.execute("SELECT * FROM student_data WHERE sap_no=%s", (sap_no,))
        record = cur.fetchone()
        if record:
            new_address = address_entry.get("1.0", "end-1c")
            new_contact1 = contact1_entry.get()
            new_contact2 = contact2_entry.get()
            new_email1 = email1_entry.get()
            new_email2 = email2_entry.get()
            new_y1 = y1.get()
            new_y2 = y2.get()
            new_y3 = y3.get()
            new_y4 = y4.get()
            new_y5 = y5.get()
            new_y6 = y6.get()
            new_p1 = p1.get()
            new_p2 = p2.get()
            new_p3 = p3.get()
            new_p4 = p4.get()
            new_p5 = p5.get()
            new_p6 = p6.get()
            cur.execute("UPDATE student_data SET address=%s, contact1=%s, contact2=%s, email1=%s, email2=%s, sem1=%s, sem2=%s, sem3=%s, sem4=%s, sem5=%s, sem6=%s, sem1_percent=%s, sem2_percent=%s, sem3_percent=%s, sem4_percent=%s, sem5_percent=%s, sem6_percent=%s WHERE sap_no=%s",
                        (new_address, new_contact1, new_contact2, new_email1, new_email2, new_y1, new_y2, new_y3, new_y4, new_y5, new_y6, new_p1, new_p2, new_p3, new_p4, new_p5, new_p6, sap_no))
            con.commit()
            tkMessageBox.showinfo("Success", "Record Updated Successfully")

    if record:
        update.title(f"{record[2]} Record Details")

        # Add labels to display the record details

        roll_label = Label(update, text="Roll No", font=("Helvatica", 10, "bold"), bg="white")
        roll_label.place(x=370, y=50)
        Label(update, text=record[2],  bg="white", font=("Helvatica", 10)).place(x=430,y=50)

        sap_label = Label(update, text="Sap No", font=("Helvatica", 10, "bold"), bg="white")
        sap_label.place(x=150, y=50)
        Label(update, text=record[1],font=("Helvatica", 10),  bg="white").place(x=210,y=50)

        year_of_admission_label = Label(update, text="Year of Admission: ", bg="white", font=("Helvatica", 10, "bold"))
        year_of_admission_label.place(x=20,y=120)
        Label(update, text=record[3],font=("Helvatica", 10),  bg="white").place(x=150,y=120)

        type_of_admission_label = Label(update, text="Type of Admission: ", bg="white", font=("Helvatica", 10, "bold"))
        type_of_admission_label.place(x=20,y=155)
        Label(update, text=record[4], font=("Helvatica", 10),  bg="white").place(x=150,y=155)

        branch_label = Label(update, text="Branch: ", bg="white", font=("Helvatica", 10, "bold"))
        branch_label.place(x=20,y=210)
        Label(update, text=record[5], font=("Helvatica", 10),  bg="white").place(x=150,y=210)

        name_label = Label(update, text="Student Name: ", bg="white", font=("Helvatica", 10, "bold"))
        name_label.place(x=20, y=245)
        Label(update, text=record[6], font=("Helvatica", 10),  bg="white").place(x=150, y=245)

        contact1_entry = Entry(update, width=20, font=("Helvetica", 10),relief="groove", bd=2)
        contact1_entry.place(x=150, y=290)
        contact1_entry.insert(END, record[7])

        contact2_entry = Entry(update, width=20, font=("Helvetica", 10), relief="groove", bd=2)
        contact2_entry.place(x=150, y=315)
        contact2_entry.insert(END, record[8])

        email1_entry = Entry(update, width=20, font=("Helvetica", 10), relief="groove", bd=2)
        email1_entry.place(x=150, y=360)
        email1_entry.insert(END, record[9])

        email2_entry = Entry(update, width=20, font=("Helvetica", 10), relief="groove", bd=2)
        email2_entry.place(x=150, y=390)
        email2_entry.insert(END, record[10])

        date_of_birth_label = Label(update, text="Date of Birth:", bg="white", font=("Helvatica", 10, "bold"))
        date_of_birth_label.place(x=20,y=445)
        Label(update, text=record[11], font=("Helvatica", 10),  bg="white").place(x=150, y=445)

        address_label = Label(update, text="Address:", bg="white", font=("Helvatica", 10, "bold"))
        address_label.place(x=20,y=505)
        address_entry = Text(update, width=20, font=("Helvetica", 10), height=2, wrap="word", relief="groove", bd=2)
        address_entry.place(x=150, y=505)
        address_entry.insert(END, record[12])

        achedemic_label = Label(update, text="Achedemic Record: ", bg="white", font=("Helvatica", 10, "bold"))
        achedemic_label.place(x=20, y=560)

        l1 = Label(update, text="Exam ", bg="white", font=("Helvatica", 10)).place(x=20, y=580)
        l2 = Label(update, text="Year of Passing", bg="white", font=("Helvatica", 10)).place(x=80, y=580)
        l3 = Label(update, text="Percentage(%)", bg="white", font=("Helvatica", 10)).place(x=200,y=580)
        l4 = Label(update, text="SSC", bg="white", font=("Helvatica", 10)).place(x=20, y=620)
        l5 = Label(update, text="HSC", bg="white", font=("Helvatica", 10)).place(x=20, y=660)

        Label(update, text=record[13], font=("Helvatica", 10),  bg="white").place(x=80,y=620)
        Label(update, text=record[14], font=("Helvatica", 10),  bg="white").place(x=200,y=620)
        Label(update, text=record[15], font=("Helvatica", 10),  bg="white").place(x=80,y=660)
        Label(update, text=record[16], font=("Helvatica", 10),  bg="white").place(x=200,y=660)

        sem_label = Label(update, text="Semesters", bg="white", font=("Helvatica", 10, "bold")).place(x=400, y=400)
        year_label = Label(update, text="Year, Month", bg="white", font=("Helvatica", 10, "bold")).place(x=500, y=400)
        percent_label = Label(update, text="%", bg="white", font=("Helvatica", 10, "bold")).place(x=580, y=400)

        sem1_label = Label(update, text="Semester 1", bg="white", font=("Helvatica", 10)).place(x=400, y=440)
        sem2_label = Label(update, text="Semester 2", bg="white", font=("Helvatica", 10)).place(x=400, y=460)
        sem3_label = Label(update, text="Semester 3", bg="white", font=("Helvatica", 10)).place(x=400, y=480)
        sem4_label = Label(update, text="Semester 4", bg="white", font=("Helvatica", 10)).place(x=400, y=500)
        sem5_label = Label(update, text="Semester 5", bg="white", font=("Helvatica", 10)).place(x=400, y=520)
        sem6_label = Label(update, text="Semester 6", bg="white", font=("Helvatica", 10)).place(x=400, y=540)

        y1 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        y1.place(x=500, y=442)
        y1.insert(END, record[18])
        y2 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        y2.place(x=500, y=462)
        y2.insert(END, record[19])
        y3 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        y3.place(x=500, y=482)
        y3.insert(END, record[20])
        y4 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        y4.place(x=500, y=502)
        y4.insert(END, record[21])
        y5 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        y5.place(x=500, y=522)
        y5.insert(END, record[22])
        y6 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        y6.place(x=500, y=542)
        y6.insert(END, record[23])
        p1 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        p1.place(x=585, y=442)
        p1.insert(END, record[24])
        p2 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        p2.place(x=585, y=462)
        p2.insert(END, record[25])
        p3 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        p3.place(x=585, y=482)
        p3.insert(END, record[26])
        p4 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        p4.place(x=585, y=502)
        p4.insert(END, record[27])
        p5 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        p5.place(x=585, y=522)
        p5.insert(END, record[28])
        p6 = Entry(update, font=("Helvatica", 10),  bg="white", width=8, relief="groove", bd=2)
        p6.place(x=585, y=542)
        p6.insert(END, record[29])

        update_record = Button(update, text="Update", width=10, bg="blue", fg="white", font=("Helvatica", 10, "bold"), command=update_info)
        update_record.place(x=400,y=600)

        revert_record = Button(update, text="Revert", width=10, bg="blue", fg="white", font=("Helvatica", 10, "bold"), command=revert_info)
        revert_record.place(x=500,y=600)

        image_data = record[30]

        # Create an image from the retrieved data
        image = Image.open(io.BytesIO(image_data))

        # Calculate the aspect ratio of the image
        image_width, image_height = image.size
        aspect_ratio = image_width / image_height

        # Determine the dimensions for cropping
        canvas_width = 150
        canvas_height = 150
        if aspect_ratio > 1:
            # Landscape image, crop the width
            crop_width = image_height * aspect_ratio
            crop_height = image_height
        else:
            # Portrait image or square image, crop the height
            crop_width = image_width
            crop_height = image_width / aspect_ratio
        crop_x = (image_width - crop_width) / 2
        crop_y = (image_height - crop_height) / 2

        # Crop the image to desired dimensions
        cropped_image = image.crop((crop_x, crop_y, crop_x + crop_width, crop_y + crop_height))

        # Resize the cropped image to fit the canvas
        resized_image = cropped_image.resize((canvas_width, canvas_height), Image.ANTIALIAS)

        # Display the resized image in a canvas
        image_upload = Canvas(update, width=canvas_width, height=canvas_height)
        image_upload.place(x=500, y=120)
        image_object = ImageTk.PhotoImage(resized_image)
        image_upload.image = image_object  # Keep a reference to the image object
        image_upload.create_image(int(canvas_width / 2), int(canvas_height / 2), image=image_object)
        image_upload.update()

    update.mainloop()

def update():
    if not tree.selection():
        tkMessageBox.showinfo("","Please Select a Student Record to Update")
    else:
        selected_item = tree.selection()
        sap_no = tree.item(selected_item)['values'][3]

        # Perform the detail display of the particular SAP number
        updateDisplay(sap_no)

def help_option():
    help_window = Tk()
    help_window.title('Help')
    help_window.geometry("500x500")

    # Define the font style
    title_font = Font(family="Helvetica", size=11, weight="bold")
    text_font = Font(family="Helvetica", size=9)

    # Insert label and description
    insert_label = Label(help_window, text="Insert", font=title_font, anchor="w", justify=LEFT)
    insert_label.pack()
    insert_description = Label(help_window, text="The insert feature allows users to add new student data into the record management system. It prompts the user to input information such as the student's name, age, grade, and other relevant details. The system then stores this data, enabling easy retrieval and management of student records", font=text_font, anchor="w", justify=LEFT, wraplength=480)
    insert_description.pack()

    # Delete label and description
    delete_label = Label(help_window, text="Delete", font=title_font, anchor="w", justify=LEFT)
    delete_label.pack()
    delete_description = Label(help_window, text=" The delete feature enables users to remove specific student data from the record management system. Users can select a student's record to delete based on unique identifiers such as student ID or name. Deleting a record ensures that outdated or erroneous information is removed from the system, maintaining data accuracy.", font=text_font, anchor="w", justify=LEFT, wraplength=480)
    delete_description.pack()

    # Search label and description
    search_label = Label(help_window, text="Search", font=title_font, anchor="w", justify=LEFT)
    search_label.pack()
    search_description = Label(help_window, text="The search feature allows users to find specific student data within the record management system. Users can enter search criteria such as student name, ID, or any other relevant information. The system then retrieves and displays the corresponding student records, making it efficient to locate specific information quickly.", font=text_font, anchor="w", justify=LEFT, wraplength=480)
    search_description.pack()

    # Sort label and description
    sort_label = Label(help_window, text="Sort", font=title_font, anchor="w", justify=LEFT)
    sort_label.pack()
    sort_description = Label(help_window, text="The sort feature organizes the student records within the system based on a specific criterion. Users can choose to sort the records alphabetically, numerically, or based on other relevant attributes such as grades or enrollment dates. Sorting helps in presenting the student records in a structured and easily navigable manner.", font=text_font, anchor="w", justify=LEFT, wraplength=480)
    sort_description.pack()

    # Display label and description
    display_label = Label(help_window, text="Display", font=title_font, anchor="w", justify=LEFT)
    display_label.pack()
    display_description = Label(help_window, text="The display feature presents all the student records stored in the system. It provides an overview of all the information available, such as student names, ages, grades, and any other relevant data. Displaying the records allows users to review the complete dataset, facilitating analysis, decision-making, or generating reports as needed.", font=text_font, anchor="w", justify=LEFT, wraplength=480)
    display_description.pack()

    help_window.mainloop()

def aboutUs():
    about_window = Tk()
    about_window.title('About Us')
    about_window.geometry("500x300")

    name_label = Label(about_window, text="Jainam Barbhaya", font=("Varenda", 15, "bold"))
    name_label.place(x=20, y=20)

    message = """
    Email       : jainambarbhaya1509@gmail.com \n
    Contact     : +91 9702288992\n
    Department  : Information Technology\n
    SAP No      : 57498210010\n
    Roll No     : T010\n
    Semester    : 4\n
    """
    info = Message(about_window, text=message, width=400, font=("Varenda", 8, "bold"))
    info.place(x=0, y=50)

    # Canvas for image
    img_canvas = Canvas(about_window, height=150, width=150)
    img_canvas.place(x=300, y=20)

    # Load and crop the image
    image = Image.open("C:\\Users\\Jainam Barbhaya\\Desktop\\Python-GUI\\Python Mini Project\\Images\\image.jpeg")
    image = image.resize((150, 150), Image.ANTIALIAS)  # Resize the image to fit the canvas
    cropped_image = ImageTk.PhotoImage(image)

    # Display the cropped image on the canvas
    img_canvas.create_image(0, 0, anchor="nw", image=cropped_image)

    about_window.mainloop()

def window():
    '''
    In this function i have created main window after admin logins
    this module contains clickable labels such as insert, update, delete, sort, search & display
    on clicking the label, it will be redirected to a new screen where the admin can perform the function

    This function uses TreeView to display the data in tabular format
    '''

    # creating a window
    global window

    window = Tk()
    window.title("Student Bio-Data RMS")

    # setting window on the center of the screen
    window_width = 1000
    window_height = 500
    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    x = (screen_width/2) - (window_width/2)
    y = (screen_height/2) - (window_height/2)
    window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
    window.resizable(width=False, height=False)
    window.configure(bg='white')

    # setting canvas taskbar
    canvas = Canvas(window, width=1000, height=30, bg="blue")
    canvas.pack()

    #setting scrollbar
    global tree
    scrollbary = Scrollbar(window, orient=VERTICAL)
    tree = ttk.Treeview(window, columns=("Sr No.", "Full Name", "Roll No.", "Sap Id", "Department","Year"),selectmode="extended", height=21, yscrollcommand=scrollbary.set)
    scrollbary.config(command=tree.yview)
    scrollbary.pack(side=RIGHT, fill=Y)

    tree.heading('Sr No.', text="Sr No.", anchor=W)
    tree.heading('Full Name', text="Full Name", anchor=W)
    tree.heading('Roll No.', text="Roll No.", anchor=W)
    tree.heading('Sap Id', text="Sap Id", anchor=W)
    tree.heading('Department', text="Department", anchor=W)
    tree.heading('Year', text="Year", anchor=W)

    #setting width of the columns
    tree.column('#0', stretch=NO, minwidth=0, width=0)
    tree.column('#1', stretch=NO, minwidth=0, width=140)
    tree.column('#2', stretch=NO, minwidth=0, width=140)
    tree.column('#3', stretch=NO, minwidth=0, width=140)
    tree.column('#4', stretch=NO, minwidth=0, width=140)
    tree.place(x=10,y=40)

    # adding CRUD lables and their function in list and iterating them

    # display()

    add_label = Label(window,text="Add", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    add_label.place(x=20, y=7)
    add_label.bind("<Button-1>", lambda event: insert())

    delete_label = Label(window,text="Delete", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    delete_label.place(x=70, y=7)
    delete_label.bind("<Button-1>", lambda event: delete())

    search_label = Label(window,text="Search", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    search_label.place(x=130, y=7)
    search_label.bind("<Button-1>", lambda event: search())

    display_label = Label(window,text="Display", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    display_label.place(x=190, y=7)
    display_label.bind("<Button-1>", lambda event: display())

    update_label = Label(window,text="Update", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    update_label.place(x=250, y=7)
    update_label.bind("<Button-1>", lambda event: update())

    sort_label = Label(window,text="Sort", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    sort_label.place(x=310, y=7)
    sort_label.bind("<Button-1>", lambda event: sort())

    help_label = Label(window,text="Help", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    help_label.place(x=850, y=7)
    help_label.bind("<Button-1>", lambda event: help_option())

    about_label = Label(window,text="About Us", fg="white", bg="blue", cursor="hand2", font=("Helvatica", 8, "bold"))
    about_label.place(x=900, y=7)
    about_label.bind("<Button-1>", lambda event: aboutUs())

    window.mainloop()

'''
# Future Scope:
> Complete Advanced Search Function
> Improve UI
> Reduce Complexity
'''


# 网络爬虫之爬取糗事百科段子实战
import urllib.request
# import urllib.error
import re
# 糗事百科官网网址
# url="https://www.qiushibaike.com/text/"
# 采用浏览器伪装技术，先设置报头
# 爬取前20网页信息
for i in range(0, 20):
    # 异常处理
    # try:
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69'}
        # 构造每一页的网址
    this_url="https://www.qiushibaike.com/text/page/"+str(i+1)+"/"
    req = urllib.request.Request(this_url,headers=headers)
        # 读取每一页的数据
    data=urllib.request.urlopen(req).read().decode('utf-8')
        # 设置正则表达式
    pat='
.*?(.*?)'
        # 进行信息提取，因为有换行符，所有要用re.S按.能匹配换行符
    this_data=re.compile(pat, re.S).findall(data)
    for d in this_data:
        print(d.strip())  # 字符串的前面和后面有的有换行符，可以用strip()方法去掉字符串首位处的换行符和空格
        print('--------------------------------------')
    # except urllib.error.HTTPError as e:
    #     if hasattr(e, 'code'):
    #         print(e.code)
    #     if hasattr(e, 'reason'):
    #         print(e.reason)
    #先放这里


from typing import Iterable, Union, List, Tuple, Match

from enum import Enum
from collections import namedtuple
import re

from .__util import *

class ScanMode(Enum):
    code = 'code', None, None
    raw = 'raw', "'", "'"
    bracket_comment = 'com', '{', '}'
    star_comment = 'com', '(*', '*)'
    line_comment = 'com', '//', ''

class Comment(str):
    __slots__ = ('args',)

    @classmethod
    def full(cls, arg, open_, close):
        return open_ + arg.strip() + close

    def __new__(cls, arg: str, open_, close):
        full = cls.full(arg, open_, close)
        ret = super().__new__(cls, full)
        ret.args = arg, open_, close
        return ret

    def __repr__(self):
        return type(self).__name__ + '(' + ', '.join(self.args) + ')'

class LineParts(List[str]):
    __slots__ = ('remove_inline_comments',)

    def __new__(cls, *args, remove_inline_comments=False, **kwargs):
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, remove_inline_comments=False, **kwargs):
        super().__init__(*args, **kwargs)
        self.remove_inline_comments = remove_inline_comments

    @property
    def last_is_comment(self):
        for i in reversed(self):
            if isinstance(i, Comment):
                return True
            if isblank(i):
                continue
            return False
        return False

    def append(self, o, mode=None):
        if mode and mode.value[0] == 'com':
            o = Comment(o, *mode.value[1:])
        if not isinstance(o, Comment) and not o.isspace():
            while self.last_is_comment:
                if self.remove_inline_comments:
                    self.pop()
                    continue
                raise NotImplementedError('inline comments must be at the end of a line')
        super().append(o)

    def join(self):
        return ''.join(str(i) for i in self)

Region = namedtuple('Region', 'start_pattern end_pattern mode')
regions = [
    Region(re.compile("'"), re.compile(r"(?<!\\)'"), ScanMode.raw),
    Region(re.compile('{(?!\$)'), re.compile('}'), ScanMode.bracket_comment),
    Region(re.compile('\(\*'), re.compile('\*\)'), ScanMode.star_comment),
    Region(re.compile('//'), re.compile('$'), ScanMode.line_comment),
]

def next_region_ind(line):
    """
    returns the index of the first matching region. If not found, returns the line's length
    >>> next_region_ind("abc{d'ef")
    3
    >>> next_region_ind("re'ge{x")
    2
    >>> next_region_ind("regex")
    5
    """
    matches = [reg.start_pattern.search(line) for reg in regions]
    match = min(matches,
                key=lambda m: float('inf') if m is None else m.start())
    if match is None:
        return len(line)

    return match.start()

def match_region(line) -> Tuple[Region, Match]:
    """
    returns the region that matches the first characters in the line, raises an error if there is more or less than one
    matching region. Also returns the match
    """
    matches = [(reg, reg.start_pattern.match(line)) for reg in regions]
    matches = [(k, v) for (k, v) in matches if v]
    if len(matches) != 1:
        raise AssertionError(f'expected 1 match, got {len(matches)}')
    return matches[0]

def filter_multiline_comments(source: Iterable[str], remove_inline_comments=False):
    """
    all this function does is wrap all lines in between multiline comments with a seperate multiline comment
        {a
        b
        c}
    to:
        {a}
        {b}
        {c}
    >>> assert list(filter_multiline_comments(["abcd","efgh"])) == ["abcd","efgh"]
    >>> assert list(filter_multiline_comments(["abc{def","def","def}"])) == ["abc{def}","{def}","{def}"]
    >>> assert list(filter_multiline_comments(["abc{def","   def","def}"])) == ["abc{def}","   {def}","{def}"]
    >>> assert list(filter_multiline_comments(["my mother said 'i need {some} dire frui{{t'" \
                                                ,"{she didn't actually","say","that}" \
                                                ,"but you {don't care} {do you?}"])) \
                                                 == ["my mother said 'i need {some} dire frui{{t'" \
                                                 ,"{she didn't actually}","{say}","{that}" \
                                                 ,"but you {don't care} {do you?}"]
    >>> assert list(filter_multiline_comments(['hi //there','how are you?','{i','am}','//fi{ne','thanks', \
                                                '{no //problem}'])) \
                                                == ['hi //there', 'how are you?', '{i}', '{am}', '//fi{ne', 'thanks',\
                                                 '{no //problem}']
    """

    region: Union[Region, type(...), None] = None
    # the region we are in, None means we are in code
    # ... means the next token is the start of a region, and we need to find out which
    for line in source:
        line_parts = LineParts(remove_inline_comments=remove_inline_comments)
        if region is not None:
            indent = re.match('\s*', line)
            line_parts.append(indent.group())
            line = line[indent.end():]
        capture = ''
        while line:
            if region is None:
                code_scan_length = next_region_ind(line)
                if code_scan_length == 0:
                    region = ...
                else:
                    line_parts.append(line[:code_scan_length])
                    line = line[code_scan_length:]
            elif region is ...:
                region, match = match_region(line)
                if region.mode.value[0] == 'com':
                    capture = ''
                elif region.mode == ScanMode.raw:
                    capture = match[0]
                else:
                    raise AssertionError('unhandled region mode: ' + str(region.mode))
                line = line[match.end():]
            else:
                # regular region (string or comment)
                end_token_match = region.end_pattern.search(line)
                mode = region.mode
                if not end_token_match:
                    # the rest of the line is the current region (it continues unto the next line but we split it here)
                    # technically, this is only allowed if we're in a comment, but I'm not about to start throwing
                    # pascal errors
                    capture += line
                    line = ''
                else:
                    if region.mode.value[0] == 'com':
                        end_cap_ind = end_token_match.start()
                    elif region.mode == ScanMode.raw:
                        end_cap_ind = end_token_match.end()
                    else:
                        raise AssertionError('unhandled region mode: ' + str(region.mode))
                    capture += line[:end_cap_ind]
                    line = line[end_token_match.end():]
                    region = None
                # either way, this region is done
                line_parts.append(capture, mode)
                capture = ''
        yield line_parts.join()


import pygame as pg
from pygame import image
import src.animation as animation
from .utils import *
from copy import copy

class UI:
    def __init__(self, screen, fruit, fps: int, player):
        self.screen, self.w, self.h = screen, screen.get_width(), screen.get_height()
        self.fruit_class = fruit
        self.time_left: int = 0
        self.fps: int = fps
        self.player = player
        self.clock_animation: animation.Animation = animation.Animation(
            544, 6, [load_alpha(f"data/assets/clock/clock{i+1}.png") for i in range(8)], 3
        )

        img = load_alpha("data/assets/frut_background.png")
        self.ui_fruits: pg.Surface = pg.transform.scale(img, (img.get_width()*2, int(img.get_height()*1.2)))

        self.font = pg.font.Font("data/fonts/Minecraft.ttf", 25)
        self.ui_fr_rect: pg.Rect = self.ui_fruits.get_rect(centerx=self.w//2, bottom=self.h-5)
        self.images = self.fruit_class.current_fruit
        self.images = [resizex(img, 0.8) for img in self.images]
        self.rects = [img.get_rect(center=(self.ui_fr_rect.centerx-(len(self.images)//2)*id_, self.ui_fr_rect.centery)) for id_, img in enumerate(self.images)]

    def reset(self, time_for_level):
        self.time_left: int = time_for_level

    def tick(self):
        self.time_left -= (1/self.fps) if self.time_left > 0 else 0

    def get_time(self):
        if self.time_left > 0:
            return f"""{
                int(self.time_left // 60) if len(str(int(self.time_left // 60))) == 2 else f"0{int(self.time_left // 60)}"
            }:{
                int(self.time_left - self.time_left // 60 * 60) if len(str(int(self.time_left - self.time_left // 60 * 60))) == 2 else f"0{int(self.time_left - self.time_left // 60 * 60)}"
            }"""

    def update(self):
        self.screen.blit(self.ui_fruits, self.ui_fr_rect)

        self.screen.blit(self.ui_fruits, (self.ui_fr_rect.x, 0))
        score = self.font.render(f'{"0" * (5 - len(str(self.player.score))) + str(int(self.player.score))}', True, (0, 0, 0))
        self.screen.blit(score, (self.ui_fr_rect.centerx - score.get_width()//2, 5))

        prev = copy(self.images)
        self.images = self.fruit_class.current_fruit

        if prev != self.images:
            # update images if they change (avoid doing this everytime and adding a func to init fruits)
            self.images = [resize(img, (int(img.get_width()*0.8), int(img.get_height()*0.8))) for img in self.images]
        self.rects = [img.get_rect(center=(self.ui_fr_rect.centerx-(((len(self.images)//2)-id_)*32), self.ui_fr_rect.centery)) for id_, img in enumerate(self.images)]

        for id_, img in enumerate(self.images):
            self.screen.blit(img, self.rects[id_])

        st_rct = self.rects[self.fruit_class.state]
        pg.draw.rect(self.screen, (0, 0, 0), [st_rct.x-2, st_rct.y-2, st_rct.width+4, st_rct.height+4], width=2)


#!/usr/bin/env python3
from dataclasses import dataclass
from typing import Dict, Optional

import torch

import transformers
from transformers import Trainer, TrainingArguments, TrainerCallback
from transformers.trainer_callback import TrainerState
from transformers import Seq2SeqTrainer, Seq2SeqTrainingArguments
from transformers.deepspeed import is_deepspeed_zero3_enabled
from transformers.utils import is_sagemaker_mp_enabled
from transformers.utils import is_apex_available
if is_sagemaker_mp_enabled():
    import smdistributed.modelparallel.torch as smp  # noqa
    from transformers.trainer_pt_utils import smp_forward_backward
    from transformers.trainer_pt_utils import smp_forward_only
    from transformers.trainer_pt_utils import smp_nested_concat
if is_apex_available():
    from apex import amp  # noqa
from transformers.trainer_pt_utils import nested_detach

@dataclass
class AugTrainerState(TrainerState):
    masked_lm_loss: Optional[torch.FloatTensor] = None
    real_length_loss: Optional[torch.FloatTensor] = None

class LogCallback(TrainerCallback):
    def on_evaluate(self, args, state, control, **kwargs):
        pass

    def on_train_begin(self, args, state, control, **kwargs):
        return super().on_train_begin(
            args, AugTrainerState(**(vars(state))), control, **kwargs)

    def on_step_end(self, args, state, control, **kwargs):
        # print(dict(
        #     masked_lm_loss=state.masked_lm_loss,
        #     real_length_loss=state.real_length_loss,
        # ))
        pass

    def on_log(self, args, state, control, **kwargs):
        pass

class AugTrainer(Trainer):
    def log(self, logs):
        # return super().log(logs)
        if self.state.epoch is not None:
            logs["epoch"] = round(self.state.epoch, 2)
        if getattr(self.state, "masked_lm_loss", None) is not None:
            logs["CE loss"] = round(self.state.masked_lm_loss, 3)
        if getattr(self.state, "real_length_loss", None) is not None:
            logs["real_length_loss"] = round(self.state.real_length_loss, 3)

        if "learning_rate" in logs:
            logs["learning_rate"] = eval("%.3e" % (logs["learning_rate"]))
        output = {
            **logs,
            **{"step": self.state.global_step},
        }
        self.state.log_history.append(output)
        self.control = self.callback_handler.on_log(
            self.args, self.state, self.control, logs)

    def training_step(self, model, inputs):
        model.train()
        inputs = self._prepare_inputs(inputs)

        if is_sagemaker_mp_enabled():
            scaler = self.scaler if self.do_grad_scaling else None
            loss_mb = smp_forward_backward(model, inputs, self.args.gradient_accumulation_steps, scaler=scaler)
            return loss_mb.reduce_mean().detach().to(self.args.device)

        with self.autocast_smart_context_manager():
            loss, other_outputs = self.compute_loss(model, inputs, return_outputs=True)
            if 'masked_lm_loss' in other_outputs:
                self.state.masked_lm_loss = other_outputs.get('masked_lm_loss').detach().item()
            if 'real_length_loss' in other_outputs:
                self.state.real_length_loss = other_outputs.get('real_length_loss').detach().item()

        if self.args.n_gpu > 1:
            loss = loss.mean()  # mean() to average on multi-gpu parallel training

        if self.args.gradient_accumulation_steps > 1 and not self.deepspeed:
            # deepspeed handles loss scaling by gradient_accumulation_steps in its `backward`
            loss = loss / self.args.gradient_accumulation_steps

        if self.do_grad_scaling:
            self.scaler.scale(loss).backward()
        elif self.use_apex:
            with amp.scale_loss(loss, self.optimizer) as scaled_loss:
                scaled_loss.backward()
        elif self.deepspeed:
            # loss gets scaled under gradient_accumulation_steps in deepspeed
            loss = self.deepspeed.backward(loss)
        else:
            loss.backward()

        return loss.detach()

    def prediction_step(
        self,
        model,
        inputs,
        prediction_loss_only,
        ignore_keys=None,
      ):
        has_labels = all(inputs.get(k) is not None for k in self.label_names)
        inputs = self._prepare_inputs(inputs)
        if ignore_keys is None:
            if hasattr(self.model, "config"):
                ignore_keys = getattr(self.model.config, "keys_to_ignore_at_inference", [])
            else:
                ignore_keys = []

        # labels may be popped when computing the loss (label smoothing for instance) so we grab them first.
        if has_labels:
            labels = nested_detach(tuple(inputs.get(name) for name in self.label_names))
            if len(labels) == 1:
                labels = labels[0]
        else:
            labels = None

        with torch.no_grad():
            if is_sagemaker_mp_enabled():  # nottodo~~
                raw_outputs = smp_forward_only(model, inputs)  # noqa
                if has_labels:
                    if isinstance(raw_outputs, dict):
                        loss_mb = raw_outputs["loss"]
                        logits_mb = tuple(v for k, v in raw_outputs.items() if k not in ignore_keys + ["loss"])
                    else:
                        loss_mb = raw_outputs[0]
                        logits_mb = raw_outputs[1:]

                    loss = loss_mb.reduce_mean().detach().cpu()
                    logits = smp_nested_concat(logits_mb)  # noqa
                else:
                    loss = None
                    if isinstance(raw_outputs, dict):
                        logits_mb = tuple(v for k, v in raw_outputs.items() if k not in ignore_keys)
                    else:
                        logits_mb = raw_outputs
                    logits = smp_nested_concat(logits_mb)  # noqa
            else:
                if has_labels:
                    with self.autocast_smart_context_manager():
                        loss, outputs = self.compute_loss(model, inputs, return_outputs=True)
                    loss = loss.mean().detach()
                    # @@
                    self.state.masked_lm_loss = outputs.masked_lm_loss.detach().item()
                    self.state.real_length_loss = outputs.real_length_loss.detach().item()
                    # $$

                    if isinstance(outputs, dict):
                        logits = tuple(v for k, v in outputs.items() if k not in ignore_keys + ["loss"])
                    else:
                        logits = outputs[1:]
                else:
                    loss = None
                    with self.autocast_smart_context_manager():
                        outputs = model(**inputs)
                    # @@
                    self.state.masked_lm_loss = outputs.masked_lm_loss.detach().item()
                    self.state.real_length_loss = outputs.real_length_loss.detach().item()
                    # $$
                    if isinstance(outputs, dict):
                        logits = tuple(v for k, v in outputs.items() if k not in ignore_keys)
                    else:
                        logits = outputs
                    # TODO: this needs to be fixed and made cleaner later.
                    if self.args.past_index >= 0:
                        self._past = outputs[self.args.past_index - 1]

        if prediction_loss_only:
            return (loss, None, None)

        logits = nested_detach(logits)
        if len(logits) == 1:
            logits = logits[0]

        return (loss, logits, labels)

class AugSeq2SeqTrainer(Seq2SeqTrainer, AugTrainer):
    def prediction_step(self,
        model,
        inputs,
        prediction_loss_only,
        ignore_keys=None,
      ):
        if not self.args.predict_with_generate or prediction_loss_only:
            return super(Seq2SeqTrainer, self).prediction_step(
                model, inputs, prediction_loss_only=prediction_loss_only, ignore_keys=ignore_keys
            )

        has_labels = "labels" in inputs
        inputs = self._prepare_inputs(inputs)

        # XXX: adapt synced_gpus for fairscale as well
        gen_kwargs = {
            "max_length": self._max_length if self._max_length is not None else self.model.config.max_length,
            "num_beams": self._num_beams if self._num_beams is not None else self.model.config.num_beams,
            "synced_gpus": True if is_deepspeed_zero3_enabled() else False,
        }

        if "attention_mask" in inputs:
            gen_kwargs["attention_mask"] = inputs.get("attention_mask", None)
        if "global_attention_mask" in inputs:
            gen_kwargs["global_attention_mask"] = inputs.get("global_attention_mask", None)
        if "word_length_tensor" in inputs:
            gen_kwargs["word_length_tensor"] = inputs.get("word_length_tensor", None)

        # prepare generation inputs
        # some encoder-decoder models can have varying encoder's and thus
        # varying model input names
        if hasattr(self.model, "encoder") and self.model.encoder.main_input_name != self.model.main_input_name:
            generation_inputs = inputs[self.model.encoder.main_input_name]
        else:
            generation_inputs = inputs[self.model.main_input_name]

        generated_tokens = self.model.generate(
            generation_inputs,
            **gen_kwargs,
        )
        # in case the batch is shorter than max length, the output should be padded
        if generated_tokens.shape[-1] < gen_kwargs["max_length"]:
            generated_tokens = self._pad_tensors_to_max_len(generated_tokens, gen_kwargs["max_length"])

        with torch.no_grad():
            with self.autocast_smart_context_manager():
                outputs = model(**inputs)
            if has_labels:
                if self.label_smoother is not None:
                    loss = self.label_smoother(outputs, inputs["labels"]).mean().detach()
                else:
                    loss = (outputs["loss"] if isinstance(outputs, dict) else outputs[0]).mean().detach()
            else:
                loss = None

        if self.args.prediction_loss_only:
            return (loss, None, None)

        if has_labels:
            labels = inputs["labels"]
            if labels.shape[-1] < gen_kwargs["max_length"]:
                labels = self._pad_tensors_to_max_len(labels, gen_kwargs["max_length"])
        else:
            labels = None

        return (loss, generated_tokens, labels)


import pandas as pd
from matplotlib import rcParams
import matplotlib.pyplot as plt
import numpy as np
import os
from sklearn.metrics import roc_auc_score, roc_curve
from sklearn.utils import resample
from inspect import signature
from delong import delong_roc_variance, delong_roc_test
from scipy import stats

def plotMultiPCR(trainpath, ext_testfiles, algo, rcut, her2, prefix='', output_folder=None):
    import os
    from aux_multiseed_training import getTrainingBands

    # Train
    train_pCR_means, train_pCR_std = getTrainingBands(trainpath, rcut, 'agnost', her2)

    print('Train band means:', train_pCR_means)
    print('Train band std:', train_pCR_std)

    ## Test
    test_pCR_bandmeans, test_pCR_bandsds, test_pCR_bandnoms = getWholeTestBand(ext_testfiles, rcut,her2,'pCR')

    print('pCR from band noms:', test_pCR_bandnoms)
    print('pCR from band means:', test_pCR_bandmeans)

    #
    fig0, ax0 = plt.subplots(1,1,figsize=(7,5))
    if her2=='agnost':
        # Draw training bands
        ax0.plot(range(6), train_pCR_means, ':', color='#20A39E')
        ax0.fill_between(range(6), train_pCR_means+train_pCR_std, train_pCR_means-train_pCR_std, alpha=0.1, color='#20A39E', label='Training')
    if her2=='agnost':
        ax0.set_ylim([0.58,0.93])
    ax0.set_ylabel('AUC')
    if her2!='agnost':
        ax0.set_title('HER2{} pCR'.format(her2))
    plt.setp(ax0.get_xticklabels(), visible=True)
    plt.setp(ax0, xticks=range(6), xticklabels=['\nClinical','+\nDNA','+\nRNA','+\nDNA\nRNA','+\nDNA\nRNA\nDigPath','+\nDNA\nRNA\nDigPath\nTreatment'])

    #ax0.plot(range(6), test_pCR_bandmeans, '--', color='#20A39E', label='External validation (BS mean)')
    ax0.plot(range(6), test_pCR_bandnoms, 'o-', color='#20A39E', label='External validation')
    ax0.plot(range(6), test_pCR_bandmeans+test_pCR_bandsds, '-', color='#20A39E', alpha=0.9)
    ax0.plot(range(6), test_pCR_bandmeans-test_pCR_bandsds, '-', color='#20A39E', alpha=0.9)

    ax0.legend(loc='lower right', fontsize=14)

    if output_folder!=None:
        plt.savefig(output_folder+'/aucs_vs_feats_her2{}.pdf'.format(her2), bbox_inches='tight', transparent=True)

    return train_pCR_means, test_pCR_bandnoms

def getPreds(csv_file, model, feats, algo, rcut):
    df = pd.read_csv(csv_file, delimiter=' ', header=None)
    patIds = df.iloc[1, 2:-1].values
    df = df.iloc[:, :-1] # The last column is just nans
    truths = df[(df.loc[:,0]==model) & (df.loc[:,1]=='resp')].values[0][2:]
    preds = (df[(df.loc[:,0]=='{}_{}_r{}'.format(model, feats,rcut)) & (df.loc[:,1]==algo)]).values[0][2:]
    preds = np.array([float(x) for x in preds if x is not np.nan])
    truths = np.array([int(x) for x in truths if x is not np.nan])

    pred_df = pd.DataFrame({'ID':patIds, 'preds':preds, 'truths':truths})
    return pred_df

def testBands(testfiles, algo, rcut, her2, feats='rna', model='pCR'):
    itf = {}
    pred_dfs_forseeds = []
    for random_state in [1,2,3,4,5]:
        itf['neg'] = testfiles['neg']+'_rs{}'.format(random_state)
        itf['pos'] = testfiles['pos']+'_rs{}'.format(random_state)
        if her2=='agnost':
            pred_df_list = []
            for her2_i in ['neg', 'pos']:
                outputs_name = itf[her2_i]+'/predictions.txt'
                pred_df_i = getPreds(outputs_name, model, feats, algo, rcut)
                pred_df_list.append(pred_df_i)
            pred_df = pd.concat(pred_df_list).reset_index()
        else:
            outputs_name = itf[her2]+'/predictions.txt'
            pred_df = getPreds(outputs_name, model, feats, algo, rcut)
        pred_dfs_forseeds.append(pred_df)

    # Average the predictions of the 5 random seeds
    merged_df = pred_dfs_forseeds[0].copy()
    for ii in [1,2,3,4]:
        suffix_df = pred_dfs_forseeds[ii].add_suffix('_rs{}'.format(ii+1))
        merged_df = pd.merge(merged_df, suffix_df, left_on='ID', right_on='ID_rs{}'.format(ii+1))

    series_final_truths = merged_df['truths']
    series_final_preds = (merged_df['preds']+merged_df['preds_rs2']+merged_df['preds_rs3']+merged_df['preds_rs4']+merged_df['preds_rs5'])/5
    merged_df['final_preds'] = series_final_preds

    truths = series_final_truths.values
    preds = series_final_preds.values
    nom_auc = roc_auc_score(truths, preds)

    # Bootstrapping
    indices = range(len(truths))
    bs_aucs = []
    for i in range(100):
        bs = resample(indices, replace=True, random_state=i, stratify=truths)
        auc = roc_auc_score(truths[bs], preds[bs])
        bs_aucs.append(auc)
    return np.mean(bs_aucs), np.std(bs_aucs), nom_auc, [truths,preds]

def getWholeTestBand(testfiles, rcut,her2,model,noBev=False):
    means = []
    sds = []
    noms = []
    for feats in ['clinical','dna','clin_rna','rna','imag','chemo']:
        m,sd,nom,_ = testBands(testfiles, 'avg',rcut,her2,feats=feats,model=model)
        means.append(m)
        sds.append(sd)
        noms.append(nom)
    return np.array(means), np.array(sds),  np.array(noms)

def getROC(testfiles,rcut,her2,model,feats_list,names_list,output_folder=None,suffix=''):
    plt.figure(figsize=(5,5))
    plt.plot([0,1],[0,1],':')
    line_styles = ['--','-','-.',':','-']
    for i,feats in enumerate(feats_list):
        _,_,_,v = testBands(testfiles,'avg',rcut,her2,feats=feats,model=model)
        truths = v[0]
        preds = v[1]
        auc = roc_auc_score(truths, preds)
        label = '{} (Validation AUC={:.2f})'.format(names_list[i], auc)
        fpr, tpr, thresholds = roc_curve(truths, preds)
        plt.plot(fpr, tpr, color='k', lw=2, alpha=.8, linestyle=line_styles[i], label=label)
    plt.legend(fontsize=14, loc='lower right')
    plt.xlabel('FPR')
    plt.ylabel('TPR')

    if output_folder!=None:
        plt.savefig(output_folder+'/roc_her2{}{}.pdf'.format(her2,suffix), bbox_inches='tight', transparent=True)

    plt.show()

########### Precision - recall ##############

from sklearn.metrics import precision_recall_curve, average_precision_score, auc

def makeSinglePrec(theax, truths, preds, label, color):
    precision, recall, thresholds = precision_recall_curve(truths, preds)
    avg_prec = average_precision_score(truths,preds)
    prec_auc = auc(recall, precision)
    print('Avg. prec = {}; AUC = {}'.format(avg_prec, prec_auc))
    step_kwargs = ({'step': 'post'} if 'step' in signature(plt.fill_between).parameters else {})
    theax.step(recall, precision, color=color, alpha=0.2, where='post')
    theax.fill_between(recall, precision, alpha=0.2, color=color, **step_kwargs, label=label)

def plotPrecRec(testfiles,rcut,her2,model,feats_list,names_list,output_folder=None):
    fig, ax = plt.subplots(1,1,figsize=(5,5))
    fig.subplots_adjust(hspace=0.4)
    colors = ['r','b']
    for i,feats in enumerate(feats_list):
        _,_,_,v = testBands(testfiles,'avg',rcut,her2,feats=feats,model=model)
        truths = v[0]
        preds = v[1]
        makeSinglePrec(ax, truths, preds, names_list[i], colors[i])
        if i==1:
            rand_perf = (truths==1).sum()/truths.shape[0]
            ax.plot([0, 1], [rand_perf, rand_perf], ':k')
            ax.text(0.01, rand_perf+0.02, 'Random performance', size=12)

    ax.legend(prop={'size': 12})
    ax.set_xlabel('Recall')
    ax.set_ylabel('Precision')

    if output_folder!=None:
        plt.savefig(output_folder+'/prec_recall_her2{}.pdf'.format(her2), bbox_inches='tight', transparent=True)

    plt.show()

########### DeLong p-values ############

def getLimits(y_true, y_pred):
    alpha = 0.95
    auc, auc_cov = delong_roc_variance(y_true,y_pred)
    auc_std = np.sqrt(auc_cov)
    lower_upper_q = np.abs(np.array([0, 1]) - (1 - alpha) / 2)
    ci = stats.norm.ppf(lower_upper_q,loc=auc,scale=auc_std)
    ci[ci > 1] = 1
    print('AUC:', auc)
    print('AUC COV:', auc_cov)
    print('95% AUC CI:', ci)
    return [auc,auc_cov,ci]

def plotDeLongCIs(testfiles,rcut,her2,model,feats_list,names_list,output_folder=None,suffix=''):
    plt.figure(figsize=(4,5))
    plt.plot([-0.5,len(names_list)],[0.5,0.5],':k')
    all_truths = []
    all_preds = []
    for i,feats in enumerate(feats_list):
        _,_,_,v = testBands(testfiles,'avg',rcut,her2,feats=feats,model=model)
        truths = v[0]
        preds = v[1]
        auc = getLimits(truths, preds)

        plt.vlines(i, auc[2][0], auc[2][1])
        plt.plot(i, auc[0], 'ok', label=names_list[i], markersize=9)

        all_truths.append(truths)
        all_preds.append(preds)

    # pval
    pval = delong_roc_test(all_truths[0], all_preds[0], all_preds[1])
    thepval = np.power(10,pval[0])[0]
    print(thepval)

    x1, x2 = 0, 1   # columns 'Sat' and 'Sun' (first column: 0, see plt.xticks())
    y, h, col = 1.01, 0.02, 'k'
    plt.plot([x1, x1, x2, x2], [y, y+h, y+h, y], lw=1.5, c=col)
    plt.text((x1+x2)*.5, y+h, 'p={:.2f}'.format(thepval), ha='center', va='bottom', color=col)

    plt.ylabel('AUC')
    plt.xlim([-0.5,len(names_list)-0.5])
    plt.text(0.55,0.51,'AUC=0.5',size=13)

    plt.xticks(range(len(names_list)),names_list,rotation=85)

    plt.savefig(output_folder+'/delong_her2{}{}.pdf'.format(her2,suffix), bbox_inches='tight', transparent=True)

    plt.show()


import os
import sys
from random import randint
from threading import Thread
from time import sleep

from scapy.arch import str2mac
from scapy.layers.dhcp import DHCP, BOOTP
from scapy.layers.inet import IP, UDP
from scapy.layers.l2 import Ether
from scapy.sendrecv import sendp, sniff
from scapy.utils import mac2str
from scapy.volatile import RandMAC

OFFER = 2
ACK = 5

TIMEOUT = 5

class Client(Thread):
    iface = None
    target = None
    lock = None
    persist = False
    addresses = {}  # all the address we have
    time_for_sleep = 1

    def __init__(self):
        Thread.__init__(self)
        # generate random mac address
        self.ch_mac = mac2str(str(RandMAC()))
        self.mac = str2mac(self.ch_mac)
        self.transaction_id = randint(0, 0xffffffff)
        self.ip = None

    def run(self):
        """
        Discover -> Offer -> Request -> ACK
        According to RFC 2131 (DHCPv4),
        after receiving an ack packet the client should wait for 50% of the lease time before sending a new request.
        If the client does not receive a response from the server,
        the next request from the client should be after 88.5% of the lease time.
        """
        self.discover()
        offer_packet = self.sniffer(OFFER)

        self.replace_ip(offer_packet[BOOTP].yiaddr)  # check if we get different ip address

        time_for_release = dict([ops for ops in offer_packet[DHCP].options if len(ops) == 2])['lease_time']

        self.request()
        ack_packet = self.sniffer(ACK)
        if not ack_packet:
            self.kill_client()

        # every loop we renew the same ip address
        while True:  # renew the lease infinite times
            time_for_release = dict([ops for ops in ack_packet[DHCP].options if len(ops) == 2])['lease_time']
            self.replace_ip(ack_packet[BOOTP].yiaddr)  # update the ip address

            sleep(time_for_release * 0.5)  # wait for 50% of the lease time
            self.request()
            ack_packet = self.sniffer(ACK)
            if ack_packet:  # receive ack packet successfully
                continue  # renew the lease
            else:  # not receiving ack
                sleep(time_for_release * (0.885 - 0.5))  # wait for 88.5% of the lease time
                self.request()
                ack_packet = self.sniffer(ACK)
                if not ack_packet:  # if not receive ack packet
                    self.kill_client()

    def sniffer(self, op):
        packets = sniff(
            count=1,
            iface=Client.iface,
            timeout=TIMEOUT,
            lfilter=lambda p:
            BOOTP in p and
            p[IP].src == Client.target and  # accept packets only from the target DHCP server
            p[BOOTP].xid == self.transaction_id and  # the packet is for the current client
            dict([ops for ops in p[DHCP].options if len(ops) == 2])['message-type'] == op,  # the packet type
        )

        if op == OFFER and len(packets) == 0:  # if timeout occurs for Offer
            if not Client.persist:
                # if not persistent the program terminated when the server is down
                Client.lock.acquire(blocking=True)  # catch the lock, stop creating new clients
                sleep(TIMEOUT * 3)  # wait for clients to finish their connections
                os._exit(0)  # terminate the program
            # all the threads that not receive answer while its lock are going to be killed
            elif Client.lock.acquire(blocking=True, timeout=TIMEOUT):
                # stop create clients, DHCP server is down
                print(f'========= LOCK Locked for {Client.time_for_sleep} seconds=========')
                sleep(Client.time_for_sleep)  # try again after time_for_sleep seconds (if real client disconnect)
                Client.time_for_sleep *= 2  # the time we sleep goes up exponentially
                Client.lock.release()
                print('========= LOCK Release =========')

            # close current thread
            self.kill_client()
        elif op == ACK and len(packets) == 0:
            return False
        else:  # successfully receive the packet
            print(f'{"A" if op == 5 else "O"}: 0x{self.transaction_id:08x}')
            return packets[0]

    def replace_ip(self, new_ip):
        """
        If we receive different ip, set self.ip and update the ips
        :param new_ip: the new ip address
        """
        if new_ip == self.ip:
            return
        if self.ip in Client.addresses:
            del Client.addresses[self.ip]
        self.ip = new_ip
        Client.addresses[new_ip] = self.mac

    def kill_client(self):
        """
        It deletes the client's IP address from the dictionary of addresses
         and exits the thread
        """
        if self.ip in Client.addresses:
            del Client.addresses[self.ip]
        sys.exit()

    def discover(self):
        print(f'D: 0x{self.transaction_id:08x}')
        packet = Ether(
            src=self.mac,
            dst='ff:ff:ff:ff:ff:ff'
        ) / IP(
            src='0.0.0.0', dst='255.255.255.255'
        ) / UDP(
            dport=67, sport=68
        ) / BOOTP(
            op=1, chaddr=self.ch_mac, xid=self.transaction_id
        ) / DHCP(
            options=[('message-type', 'discover'),
                     'end']
        )
        Thread(target=self.sleep_and_send, args=packet).start()

    @staticmethod
    def sleep_and_send(packet):
        sleep(0.1)
        sendp(packet, iface=Client.iface, verbose=0)

    def request(self):
        print(f'R: 0x{self.transaction_id:08x}')
        packet = Ether(
            src=self.mac,
            dst='ff:ff:ff:ff:ff:ff'
        ) / IP(
            src='0.0.0.0', dst='255.255.255.255'
        ) / UDP(
            dport=67, sport=68
        ) / BOOTP(
            op=3, chaddr=self.ch_mac, xid=self.transaction_id
        ) / DHCP(
            options=[('message-type', 'request'),
                     ('server_id', Client.target),
                     ('requested_addr', self.ip),
                     'end']
        )
        Thread(target=self.sleep_and_send, args=packet).start()


# -*- coding: utf-8 -*-
"""
AWS IoT Core Publisher 基底クラスを定義するモジュール。
"""
import donkeycar as dk
import numpy as np

class PublisherBase:
    """
    Tubデータ(イメージ、JSONデータ)をMQTTプロトコルで送信する
    パブリッシャパーツクラス。
    """
    def __init__(self, aws_iot_client_factory, name='Base', debug=False):
        """
        フィールドを初期化する。
        引数：
            aws_iot_client_factory  AWSIoTClientFactoryオブジェクト
            name                    ターゲット名
            debug                   デバッグフラグ
        戻り値：
            なし
        """
        self.system = aws_iot_client_factory.system
        self.thing_type = aws_iot_client_factory.thing_type
        self.thing_group = aws_iot_client_factory.thing_group
        self.thing_name = aws_iot_client_factory.thing_name
        self.name = name
        if debug:
            print('[{}Publisher] thing_name = {}'.format(
                self.name,self.thing_name))
        self.client = aws_iot_client_factory.get_mqtt_client()
        self.debug = debug

    def encode_image_message(self, image_array):
        """
        Tubデータ(イメージデータ)をpublish送信するメッセージを取得する。
        引数：
            image_array         イメージデータ
        戻り値：
            メッセージ(bytearray)
        """
        try:
            import donkeycar as dk
        except:
            raise
        return arr_to_bytearray(image_array)

    def shutdown(self):
        """
        実装なし。
        引数：
            なし
        戻り値：
            なし
        """
        self.client = None
        if self.debug:
            print('[{}Publisher] shutdown'.format(self.name))

'''ユーティリティ関数'''

def arr_to_bytearray(arr):
    """
    np.array(uint8, (120,160,3))オブジェクトをbytearrayオブジェクトに
    変換する。
    引数：
        arr     np.array(uint8, (120,160,3))オブジェクト
    戻り値：
        bytearray化されたオブジェクト
    """
    return bytearray(_arr_to_bin(arr))

def bytearray_to_arr(bytearray_data):
    """
    bytearrayオブジェクトをnp.array(uint8, (120,160,3))オブジェクトに
    変換する。
    引数：
        bytearray_data      bytearray化されたオブジェクト
    戻り値：
        np.array(uint8, (120,160,3))オブジェクト
    """
    return _bin_to_arr(bytes(bytearray_data))

def _arr_to_bin(arr):
    """
    イメージデータimage_arrayがnd.array型の場合、バイナリに変換する。
    引数：
        arr    イメージデータ
    戻り値：
        bin    バイナリ変換後のイメージデータ
    """
    if type(arr) is bytes or type(arr) is bytearray:
        return arr
    elif type(arr) is np.ndarray:
        return dk.utils.arr_to_binary(arr)
    else:
        raise ValueError('unknown type=' +str(type(arr)))

def _bin_to_arr(binary):
    """
    引数binaryの値がバイナリの場合のみ、image_array型式に変換する。
    引数：
        binary          バイナリデータ
    戻り値：
        arr             image_array 型式データ
    """
    if type(binary) is np.ndarray:
        return binary
    elif type(binary) is bytes:
        return dk.utils.img_to_arr(dk.utils.binary_to_img(binary))
    else:
        raise ValueError('unknown type=' +str(type(binary)))

def to_float(value):
    """
    float値に変換する。
    引数：
        value   対象値
    戻り値
        float値
    """
    if value is None or value == '':
        return 0.0
    return float(value)

def to_int(value):
    """
    int値に変換する。
    引数：
        value   対象値
    戻り値
        int値
    """
    if value is None or value == '':
        return 0.0
    return float(value)

def to_str(value):
    """
    文字列に変換する。
    引数：
        value   対象値
    戻り値
        str値
    """
    if value is None:
        return 'None'
    return str(value)