import os
import sys
import subprocess
from datetime import datetime
import tkinter as tk
from tkinter import messagebox, END
from tkinter import font as tkfont
from calendar import monthrange

import cv2
import pyautogui
from openpyxl import Workbook, load_workbook
import excel
from capture import capture
from capture import detect_faces
from face_train import FaceTrain
from students_list import StudentsList
from main_file import MainFile

class_codes = ['Marauders']
manager_id = 'ADMIN'
manager_pass = 'ubuntu'

current_class = 'Marauders'
if current_class != 'Marauders':
    current_class_obj = MainFile(current_class)
    FaceTrainObj = FaceTrain(current_class)
else:
    current_class_obj = None
    FaceTrainObj = None

# Dynamic variables
normal_width = 1920
normal_height = 1080
x, y = pyautogui.size()

percentage_width = x/(normal_width/100)
percentage_height = y/(normal_height/100)
scale_factor = ((percentage_width + percentage_height)/2)/100

title_fontsize = int(30 * scale_factor)
minimum_size = 24
title_fontsize = max(title_fontsize, minimum_size)

text_fontsize = int(18 * scale_factor)
minimum_textsize = 13
text_fontsize = max(minimum_textsize, text_fontsize)


class SampleApp(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        self.title_font = tkfont.Font(family='Helvetica', size=18,
                                      weight="bold", slant="italic")
        geo = str(int(0.43 * x)) + 'x' + str(int(0.52 * y))
        self.geometry(geo)
        self.resizable(False, False)
        self.title('Attendance Management App')
        self.protocol("WM_DELETE_WINDOW", self.on_closing)
        # The container is where we'll stack a bunch of frames
        # on top of each other, then the one we want visible
        # will be raised above the others
        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.createInitialDirectories()

        self.frames = {}
        for F in (StartPage, StudentPanelPage, ManagerPanelPage,
                  CreateNewBatchPage, AddStudentPage):
            page_name = F.__name__

            frame = F(parent=container, controller=self)
            self.frames[page_name] = frame

            # Put all of the pages in the same location;
            # the one on the top of the stacking order
            # will be the one that is visible.
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame("StartPage")

    def createInitialDirectories(self):

        temp_dir = os.path.join(os.getcwd(), 'images', '.temp')
        temp_dir_exists = os.path.isdir(temp_dir)

        extras_dir = os.path.join(os.getcwd(), 'extras')
        extras_dir_exists = os.path.isdir(extras_dir)

        studs_list_dir = os.path.join(os.getcwd(), "student's list")
        studs_list_dir_exists = os.path.isdir(studs_list_dir)

        if not extras_dir_exists:
            os.makedirs(extras_dir)
        if not temp_dir_exists:
            os.makedirs(temp_dir)
        if not studs_list_dir_exists:
            os.makedirs(studs_list_dir)

        classes_file = os.path.join(os.getcwd(), 'extras', 'classes.xlsx')
        try:
            global class_codes, current_class
            class_codes = set(['Marauders'])
            wb = load_workbook(classes_file)
            ws = wb.active
            number_of_classes = ws['A1'].value
            if number_of_classes != 0:
                current_class = ws['A2'].value
            for i in range(2, number_of_classes + 2):
                class_codes.add(ws['A' + str(i)].value)
        except:
            wb = Workbook()
            wb.guess_types = True
            ws = wb.active
            ws['A1'] = 0
            wb.save(classes_file)

    def show_frame(self, page_name):
        # Show a frame for the given page name
        frame = self.frames[page_name]
        frame.tkraise()

    def on_closing(self):
        self.destroy()
        self.quit()

    def validate(self, page_name, c_code, id, pwd):
        global class_codes
        if id == manager_id and pwd == manager_pass and c_code in class_codes:
            frame = self.frames[page_name]
            global current_class
            current_class = c_code
            print("current class", current_class)
            if current_class != 'Marauders':
                # frame.lb_class_code.config(text="Batch Code : {}".format(current_class))
                # frame.lb_class_code['text']="Batch Code : {}".format(current_class)
                StudentsList(current_class).make_pkl_file()
                if page_name == "ManagerPanelPage":
                    global FaceTrainObj
                    FaceTrainObj = FaceTrain(current_class)
                elif page_name == "StudentPanelPage":
                    global current_class_obj
                    current_class_obj = MainFile(current_class)
            elif current_class == 'Marauders' and page_name == 'StudentPanelPage':
                messagebox.showerror('Error', 'Only Manager Panel is valid for this class-code')
                return
            else:
                frame = self.frames['CreateNewBatchPage']
            frame.tkraise()
        else:
            messagebox.showerror('Error', 'Incorrect Credentials !!')


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.bkg = '#222629'
        self.text_color = '#65CCB8'
        StartPage.config(self, bg=self.bkg)

        entry_width = int(30 * scale_factor)
        min_entrywidth = 25
        entry_width = max(entry_width, min_entrywidth)
        self.label = tk.Label(self, width=25, text="Attendance Management",
                              bg=self.bkg, fg="#F8E9A1",
                              font=("Times", title_fontsize))
        self.label.place(x=130 * scale_factor, y=40 * scale_factor)

        self.lb_class = tk.Label(self, text="CLASS-CODE: ", bg=self.bkg,
                                 fg=self.text_color,
                                 font=("Courier", text_fontsize))
        self.lb_class.place(x=170 * scale_factor, y=155 * scale_factor)
        self.tv_class = tk.Entry(self, width=entry_width)
        self.tv_class.focus()
        self.tv_class.place(x=340 * scale_factor, y=160 * scale_factor)

        self.lb_username = tk.Label(self, text="USERNAME : ", bg=self.bkg,
                                    fg=self.text_color,
                                    font=("Courier", text_fontsize))
        self.lb_username.place(x=183 * scale_factor, y=210 * scale_factor)
        self.tv_username = tk.Entry(self, width=entry_width)
        self.tv_username.place(x=340 * scale_factor, y=215 * scale_factor)

        self.lb_pass = tk.Label(self, text="PASSWORD : ", bg=self.bkg,
                                fg=self.text_color,
                                font=("Courier", text_fontsize))
        self.lb_pass.place(x=183 * scale_factor, y=265 * scale_factor)
        self.tv_pass = tk.Entry(self, show="*", width=entry_width)
        self.tv_pass.place(x=340 * scale_factor, y=270 * scale_factor)
        self.showbtn = tk.Button(self, text="SHOW", bg="#ed3833",
                                 command=self.show, width=2,
                                 font=("", 8))  # 32ff6a
        self.show = False
        self.showbtn.place(x=(340 * scale_factor) + (entry_width * 9),
                           y=270*scale_factor)

        bt_width = int(16 * scale_factor)
        min_width = 12
        bt_width = max(min_width, bt_width)
        self.button1 = tk.Button(
            self, bg="#45056e", fg=self.text_color,
            text="Go to\nStudent Portal", width=bt_width, height=3,
            font=("", 15),
            command=lambda: self.doWork(
                "StudentPanelPage",
                self.tv_class.get(),
                self.tv_username.get(),
                self.tv_pass.get()
            ))
        self.button2 = tk.Button(
            self, bg="#5f1854", fg=self.text_color,
            text="Go to\nManager Portal",
            width=bt_width, height=3, font=("", 15),
            command=lambda: self.doWork(
                "ManagerPanelPage",
                self.tv_class.get(),
                self.tv_username.get(),
                self.tv_pass.get()
            ))
        self.button1.place(x=140 * scale_factor, y=340 * scale_factor)
        self.button2.place(x=410 * scale_factor, y=340 * scale_factor)
        self.bt_exit = tk.Button(self, bg="red", fg="yellow", text="Exit",
                                 width=10, command=self.exit)
        self.bt_exit.place(x=340 * scale_factor, y=490 * scale_factor)

    def show(self):
        if self.show is False:
            self.tv_pass.config(show="")
            self.show = True
            self.showbtn.config(bg="#32ff6a")
        elif self.show is True:
            self.tv_pass.config(show="*")
            self.show = False
            self.showbtn.config(bg="#ed3833")

    def doWork(self, page_name, c_code, id, pwd):
        self.tv_class.delete(0, END)
        self.tv_username.delete(0, END)
        self.tv_pass.delete(0, END)
        self.controller.validate(page_name, c_code, id, pwd)

    def exit(self):
        self.controller.destroy()
        self.controller.quit()


class StudentPanelPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.bkg = '#222629'
        self.text_color = '#65CCB8'
        StudentPanelPage.config(self, bg=self.bkg)

        label = tk.Label(self, width=25, text="Student Portal", bg=self.bkg,
                         fg="#F8E9A1", font=("Times", title_fontsize))
        label.place(x=130 * scale_factor, y=40 * scale_factor)
        bt_mark = tk.Button(self, bg="#45056e", fg=self.text_color,
                            text="Mark my\n ATTENDANCE", font=("", 18),
                            width=16, height=7, command=self.doWork)
        bt_mark.place(x=215 * scale_factor, y=150 * scale_factor)

        bt_back = tk.Button(self, bg="#af0404", fg="yellow", text="Back",
                            width=10, command=lambda: controller.show_frame(
                                "StartPage"))
        bt_back.place(x=115 * scale_factor, y=485 * scale_factor)
        bt_exit = tk.Button(self, bg="#af0404", fg="yellow", text="Exit",
                            width=10, command=self.exit)
        bt_exit.place(x=520 * scale_factor, y=485 * scale_factor)

    def doWork(self):
        global current_class_obj
        current_class_obj.capture_and_mark()

    def exit(self):
        self.controller.destroy()
        self.controller.quit()


class ManagerPanelPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.bkg = '#222629'
        self.text_color = '#65CCB8'
        ManagerPanelPage.config(self, bg=self.bkg)
        global current_class

        label = tk.Label(self, width=25, text="Manager Portal", bg=self.bkg,
                         fg="#F8E9A1", font=("Times", title_fontsize))
        label.place(x=130 * scale_factor, y=40 * scale_factor)

        bt_ht = int(5 * scale_factor)
        min_ht = 3
        bt_ht = max(min_ht, bt_ht)
        bt_width = int(15 * scale_factor)
        min_width = 12
        bt_width = max(min_width, bt_width)
        bt_train = tk.Button(self, text="Train the\n Recogniser",
                             bg="#45056e", fg=self.text_color, width=bt_width,
                             height=bt_ht, font=("", 15), command=self.doWork)
        bt_train.place(x=130 * scale_factor, y=160 * scale_factor)
        bt_addstud = tk.Button(self, text="Add a student\n to this class",
                               bg="#3b0944", width=bt_width,
                               fg=self.text_color, height=bt_ht,
                               font=("", 15), command=self.addStudent)
        bt_addstud.place(x=430 * scale_factor, y=160 * scale_factor)
        bt_view_register = tk.Button(self, text="View\nAttendance\nRegister",
                                     width=bt_width, bg="#581845",
                                     fg=self.text_color, height=bt_ht,
                                     font=("", 15), command=self.viewRegister)
        bt_view_register.place(x=265 * scale_factor, y=320 * scale_factor)

        bt_back = tk.Button(self, bg="#af0404", fg="yellow", text="Back",
                            width=10, command=lambda: controller.show_frame(
                                "StartPage"))
        bt_back.place(x=115 * scale_factor, y=485 * scale_factor)
        bt_exit = tk.Button(self, bg="#af0404", fg="yellow", text="Exit",
                            width=10, command=self.exit)
        bt_exit.place(x=520 * scale_factor, y=485 * scale_factor)

    def viewRegister(self):
        global current_class
        atten_register = os.path.join(os.getcwd(), 'extras', current_class, f'{current_class}.xlsx')
        try:
            opener = 'open' if sys.platform == 'darwin' else 'xdg-open'
            subprocess.call([opener, atten_register])
        except (subprocess.CalledProcessError, OSError):
            os.startfile(atten_register)

    def addStudent(self):
        global current_class
        students_list_file = os.path.join(os.getcwd(), "student's list", f'{current_class}.xlsx')
        self.controller.show_frame("AddStudentPage")

    def doWork(self):
        global FaceTrainObj, current_class
        if current_class == 'Marauders':
            wb = load_workbook(os.path.join(os.getcwd(), 'extras', 'classes.xlsx'))
            ws = wb.active
            number_of_classes = ws['A1'].value
            if number_of_classes == 0:
                messagebox.showerror('Error', 'No class in the database yet')
                return
            else:
                messagebox.showerror('Error', 'Please login again with a valid class-code')
                current_class = ws['A2'].value
                return

        FaceTrainObj.main()

    def exit(self):
        self.controller.destroy()
        self.controller.quit()


class CreateNewBatchPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.bkg = '#222629'
        self.text_color = '#65CCB8'
        CreateNewBatchPage.config(self, bg=self.bkg)

        self.label = tk.Label(self, width=25, text="Create a new Batch",
                              bg=self.bkg, fg="#F8E9A1",
                              font=("Times", title_fontsize))
        self.label.place(x=130 * scale_factor, y=40 * scale_factor)

        entry_width = int(30 * scale_factor)
        min_entrywidth = 23
        entry_width = max(min_entrywidth, entry_width)
        self.lb_class = tk.Label(self, text="CLASS-CODE: ", bg=self.bkg,
                                 fg=self.text_color,
                                 font=("Courier", text_fontsize))
        self.lb_class.place(x=320 * scale_factor, y=140 * scale_factor)
        self.tv_class = tk.Entry(self, width=entry_width, font=("", 16),
                                 justify='center')
        self.tv_class.focus()
        self.tv_class.place(x=190 * scale_factor, y=175 * scale_factor)

        self.lb_number = tk.Label(self, text="NUMBER OF STUDENTS: ",
                                  bg=self.bkg, fg=self.text_color,
                                  font=("Courier", text_fontsize))
        self.lb_number.place(x=265 * scale_factor, y=240 * scale_factor)
        vcmd = (self.controller.register(self.validate_number_field),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.tv_number = tk.Entry(self, width=entry_width, font=("", 16),
                                  justify='center', validate='key',
                                  validatecommand=vcmd)
        self.tv_number.place(x=190 * scale_factor, y=275 * scale_factor)

        bt_new_batch = tk.Button(self, bg="#45056e", fg=self.text_color,
                                 text="ADD BATCH", width=16, height=2,
                                 font=("", 15), command=self.create_new_batch)
        bt_new_batch.place(x=237 * scale_factor, y=350 * scale_factor)

        bt_back = tk.Button(self, bg="#ed3833", fg="yellow", text="Back",
                            width=10, command=lambda: controller.show_frame(
                                "StartPage"))
        bt_back.place(x=340 * scale_factor, y=490 * scale_factor)

    def validate_number_field(self, action, index, value_if_allowed,
                              prior_value, text, validation_type,
                              trigger_type, widget_name):
        if text in '0123456789':
            try:
                int(value_if_allowed)
                return True
            except (TypeError, ValueError):
                return False
        else:
            return False

    def create_new_batch(self):
        global class_codes
        class_name = self.tv_class.get()
        number_of_studs = int(self.tv_number.get())

        batchexists = os.path.exists(os.path.join(os.getcwd(), 'extras', class_name))
        if batchexists:
            messagebox.showerror('Error', 'This batch name already exists')
            return

        if number_of_studs < 1 or number_of_studs > 99:
            messagebox.showerror('Error', "Number of students not in allowed range!")
            return

        images_dir = os.path.join(os.getcwd(), 'images', class_name)
        unrecog_studs_dir = os.path.join(os.getcwd(), 'images', class_name,
                                         'unrecognized students')
        os.makedirs(unrecog_studs_dir)

        for i in range(number_of_studs):
            os.makedirs(os.path.join(images_dir, 's' + str(i).zfill(2)))

        studs_list_file = os.path.join(os.getcwd(), "student's list", f'{class_name}.xlsx')
        wb = Workbook()
        wb.guess_types = True
        ws = wb.active
        ws['A1'] = 0
        ws['B1'] = number_of_studs
        wb.save(studs_list_file)

        classes_file = os.path.join(os.getcwd(), 'extras', 'classes.xlsx')
        wb = load_workbook(classes_file)
        ws = wb.active
        row = ws['A1'].value
        ws['A1'] = row + 1
        ws['A' + str(row + 2)] = class_name
        wb.save(classes_file)

        sl = StudentsList(class_name)
        sl.make_pkl_file()

        atten_reg_dir = os.path.join(os.getcwd(), 'extras', class_name)
        os.makedirs(atten_reg_dir)
        wb = excel.attendance_workbook(class_name)

        messagebox.showinfo("Batch Successfully Created",
                            "All the necessary Directories and Files created"
                            f"for Batch-Code {class_name}")
        class_codes.add(class_name)
        self.tv_class.delete(0, END)
        self.tv_number.delete(0, END)
        self.tv_number.insert(0, "")
        self.controller.show_frame('StartPage')


class AddStudentPage(tk.Frame):
    """docstring for AddStudentPage"""
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        self.bkg = '#222629'
        self.text_color = '#65CCB8'
        AddStudentPage.config(self, bg=self.bkg)

        self.label = tk.Label(self, width=25, text="Add a new Student",
                              bg=self.bkg, fg="#F8E9A1",
                              font=("Times", title_fontsize))
        self.label.place(x=130 * scale_factor, y=40 * scale_factor)

        self.lb_name = tk.Label(self, text="Name of Student: ",
                                bg=self.bkg, fg=self.text_color,
                                font=("Courier", text_fontsize))
        self.lb_name.place(x=285 * scale_factor, y=175 * scale_factor)
        entry_width = int(30 * scale_factor)
        min_entrywidth = 23
        entry_width = max(min_entrywidth, entry_width)
        self.tv_name = tk.Entry(self, width=entry_width, justify='center',
                                font=("", 16))
        self.tv_name.focus()
        self.tv_name.place(x=180 * scale_factor, y=210 * scale_factor)

        self.bt_addstud = tk.Button(self, bg="#45056e", fg=self.text_color,
                                    text="ADD STUDENT", width=16, height=2,
                                    font=("", 15), command=self.doWork)
        self.bt_addstud.place(x=245 * scale_factor, y=300 * scale_factor)

        self.bt_back = tk.Button(self, bg="#af0404", fg="yellow", text="Back",
                                 width=10,
                                 command=lambda: controller.show_frame(
                                     "ManagerPanelPage"))
        self.bt_back.place(x=115 * scale_factor, y=485 * scale_factor)
        self.bt_exit = tk.Button(self, bg="#af0404", fg="yellow",
                                 text="Exit", width=10, command=self.exit)
        self.bt_exit.place(x=520 * scale_factor, y=485 * scale_factor)

    def doWork(self):
        global current_class

        students_list_file = os.path.join(os.getcwd(), "student's list", f'{current_class}.xlsx')
        wb = load_workbook(students_list_file)
        ws = wb.active
        total_studs = ws['B1'].value
        currently_studs = ws['A1'].value
        if currently_studs == total_studs:
            messagebox.showinfo("Batch Full", "No more accomodation for any new student")
            return
        ws['A1'] = currently_studs + 1
        row_here = currently_studs + 2
        student_name = self.tv_name.get()
        ws['A' + str(row_here)] = student_name
        roll_no = currently_studs
        roll_no = str(roll_no).zfill(2)
        ws['B' + str(row_here)] = current_class + roll_no
        wb.save(students_list_file)

        sl = StudentsList(current_class)
        sl.make_pkl_file()

        atten_reg = os.path.join(os.getcwd(), 'extras', current_class, f'{current_class}.xlsx')
        wb = load_workbook(atten_reg)
        today = datetime.now().date()
        month = today.strftime('%B')
        try:
            # If current month exists
            ws = wb[month]
            row_here = excel.gap + int(roll_no)
            ws.merge_cells(start_row=row_here, start_column=3,
                           end_row=row_here, end_column=4)
            ws.cell(row=row_here, column=1, value=str(int(roll_no) + 1))
            fill_roll = excel.PatternFill(fgColor='A5EFAF', fill_type='solid')
            fill_name = excel.PatternFill(fgColor='E9EFA5', fill_type='solid')
            ws.cell(row=row_here, column=2,
                    value=current_class + roll_no).fill = fill_roll
            ws.cell(row=row_here, column=3,
                    value=student_name).fill = fill_name
            cellrange = 'A' + str(row_here) + ':AN' + str(row_here)
            excel.set_border(ws, cellrange)
            first_date_column = excel.get_column_letter(excel.date_column)
            days_in_month = monthrange(today.year, today.month)[1]
            last_date_column = excel.get_column_letter(excel.date_column + days_in_month - 1)
            sum_cell_column = excel.date_column + days_in_month + 1
            fill_total = excel.PatternFill(fgColor='25F741', fill_type='solid')
            ws.cell(row=row_here, column=sum_cell_column,
                    value="=SUM(" + first_date_column + str(row_here) + ":" +
                    last_date_column + str(row_here) + ")").fill = fill_total
            wb.save(atten_reg)
        except:  # If current month does not exist
            wb = excel.attendance_workbook(current_class)

        image_dir = os.path.join(os.getcwd(), 'images', current_class, 's' + roll_no)
        messagebox.showinfo('Student Added',
                            f"{student_name} admitted!\n"
                            "Click OK to proceed for capturing training images. "
                            "Make sure that your surroundings are well lit")
        i = 5
        face_detected_right = False
        xml_file = os.path.join(os.getcwd(), 'haarcascade_frontalface_default.xml')
        while i > 0:
            while face_detected_right is False:
                img_path, frame = capture()
                face_detected_right = detect_faces(xml_file, frame)
            img_path = os.path.join(
                os.getcwd(), 'images', current_class,
                "s"+str(roll_no).zfill(2),
                os.path.basename(img_path)
            )
            cv2.imwrite(img_path, frame)
            cv2.destroyAllWindows()
            face_detected_right = False
            i = i - 1
        if i == 0:
            messagebox.showinfo("Message", "Training images added successfully!")
        messagebox.showinfo('Enrollment Number',
                            f'Roll Number of student: {current_class + roll_no}')
        self.tv_name.delete(0, END)

    def exit(self):
        self.controller.destroy()
        self.controller.quit()


if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()