import tkinter
import tkinter.ttk

import constants
from models import Programme
from widgets import ModuleWidget

from pprint import pprint

# app.py - Main GUI dialog

class App(tkinter.Tk):
    def __init__(self, db):
        super().__init__()
        self.__db = db

        self.__programme = None
        self.__modules = None

        self.refresh_db()
        self.setup_ui()

    # NOTE: Do not modify this method
    def report_callback_exception(self, *args):
        """When an error is occurred, raise the exception
        so main_gui.py will handle the display of error.
        """
        raise args[1].with_traceback(args[2])

    def refresh_db(self):
        # Fetch all program records from database, and store it in __programme as a list
        self.__programme = Programme.fetchall(self.__db)

    def setup_ui(self):
        self.title(constants.APP_NAME)
        self.minsize(640, 480)

        # Set weight of column 0 for auto resizing on X axis
        self.columnconfigure(0, weight=1)

        # Header
        frm_top_bar = tkinter.Frame(self)
        frm_top_bar.grid(row=0, sticky=tkinter.EW)
        tkinter.Label(
            frm_top_bar,
            text = constants.APP_NAME,
            font = ('bold', 16)
        ).pack(side = tkinter.LEFT, padx=8, pady=8)
        tkinter.ttk.Separator(self).grid(row=1, sticky=tkinter.EW)

        # Content
        frm_content = tkinter.Frame(self)
        frm_content.grid(row=2, sticky=tkinter.NSEW)
        self.rowconfigure(2, weight=1)

        # Options
        frm_filter = tkinter.LabelFrame(frm_content, text = "Options")
        frm_filter.pack(fill=tkinter.X, side=tkinter.TOP, padx=8, pady=(4, 0))
        frm_filter_container = tkinter.Frame(frm_filter)
        frm_filter_container.pack(fill=tkinter.BOTH, side=tkinter.TOP, padx=6, pady=(0, 4))

        # Options variables
        # Option widgets
        tkinter.Label(frm_filter_container,text = "Programme",).grid(row=0, column=0, padx=(0, 4))
        list_of_programme=[]
        for p in self.__programme:
            list_of_programme.append(p.get_name())

        self.selected_programme = tkinter.StringVar()
        self.__om_programme_filter = tkinter.ttk.Combobox(frm_filter_container,textvariable=self.selected_programme, width=50)
        self.__om_programme_filter.set('Please select')
        self.__om_programme_filter['values'] = list_of_programme
        self.__om_programme_filter['state'] = 'readonly'
        self.__om_programme_filter.grid(row=0, column=1, padx=(0, 4), sticky=tkinter.EW)
        self.__om_programme_filter.bind('<<ComboboxSelected>>', self.on_programme_changed )

        # Semester select
        tkinter.Label( frm_filter_container, text = "Semester").grid(row=0, column=2, padx=(0, 4))
        self.selected_semester = tkinter.StringVar()
        self.__om_semester_filter = tkinter.ttk.Combobox(frm_filter_container, textvariable=self.selected_semester)
        self.__om_semester_filter.set('Please select')
        self.__om_semester_filter['state'] = 'disabled'
        self.__om_semester_filter.grid(row=0, column=3, padx=(4, 4))
        self.__om_semester_filter.bind('<<ComboboxSelected>>', self.on_semester_changed)

        # Modules
        frm_modules = tkinter.LabelFrame(frm_content, text = "Modules")
        frm_modules.pack(fill=tkinter.BOTH, side=tkinter.TOP, expand=1, padx=8, pady=(4, 0))
        self.__frm_modules_container = tkinter.Frame(frm_modules)
        self.__frm_modules_container.pack(fill=tkinter.BOTH, side=tkinter.TOP, padx=6, pady=(0, 4))

        self.__module_widgets = list()
        for _ in range(constants.APP_MAX_MODULES):
            widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
            widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
            widget.set_module(None)

            self.__module_widgets.append(widget)

        # Bottom bar
        frm_bottom_bar = tkinter.Frame(self)
        frm_bottom_bar.grid(row=3, column=0,padx=(0, 10), pady=(4,4), sticky=tkinter.E)

        # Result Label
        self.semester_gpa = tkinter.StringVar()
        self.lbl_stu_list = tkinter.ttk.Label(frm_bottom_bar, text='Semester GPA: %.2f' % 0, font=("bold",11))
        self.lbl_stu_list.grid(row=0, column=0, sticky=tkinter.E)

    def setup_module_list(self):
        # Update the ModuleWidget in list __module_widgets to display
        # modules for the selected programme and semester
        current_select_idx = self.__om_semester_filter.current()
        selected_semester = list(sorted(self.__modules.keys()))[current_select_idx]
        module_in_semester = self.__modules[selected_semester]
        for widget in self.__frm_modules_container.winfo_children():
            widget.destroy()

        self.__module_widgets = list()
        i = 0
        for _ in range(constants.APP_MAX_MODULES):
            widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
            widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
            if (i < len(module_in_semester)):
                widget.set_module(module_in_semester[i])

            self.__module_widgets.append(widget)
            i = i + 1

    def on_programme_changed(self, _):
        # Get selected programme based on option menu index, and update GUI
        for program in self.__programme:
            if (self.selected_programme.get() == program.get_name()):
                self.__om_semester_filter['values'] = sorted(program.get_modules().keys())
                self.__modules = program.get_modules()
                break
        self.__om_semester_filter['state'] = 'readonly'

        self.__om_semester_filter.set('Please select')

        for widget in self.__frm_modules_container.winfo_children():
            widget.destroy()

        self.__module_widgets = list()
        for _ in range(constants.APP_MAX_MODULES):
            widget = ModuleWidget(self.__frm_modules_container, self.on_gpa_changed)
            widget.get_frame().pack(fill=tkinter.X, side=tkinter.TOP, expand=1, pady=8)
            widget.set_module(None)
            self.__module_widgets.append(widget)

        # reset gpa
        self.lbl_stu_list.configure(text='Semester GPA: %.2f' % (0))


    def on_semester_changed(self, _):
        # Get modules based on option menu value, and update GUI
        self.setup_module_list()

        # reset gpa
        self.lbl_stu_list.configure(text='Semester GPA: %.2f' % (0))


    def on_gpa_changed(self):
        # Calcuate semester GPA based on self.__modules

        # 1.	For each modules in self.__modules, App class
        # 2.	Obtain the module credit of current module and add it to the total credits
        # 3.	Calculate the module gpa by multiplying the current module gpa and module credit
        # 4.	Add the calculated module gpa to total module gpa
        # 5.	After all module gpa are calculated, divide the total module gpa by total credits

        total = 0
        current_select_idx = self.__om_semester_filter.current()
        selected_semester = list(sorted(self.__modules.keys()))[current_select_idx]
        module_in_semester = self.__modules[selected_semester]
        number_of_modules = len(module_in_semester)

        should_update = True
        all_credit = 0
        for i in range(0, number_of_modules):
            gpa = self.__module_widgets[i].get_gpa()
            if (gpa == -1):
                # not selected
                should_update = False
            else:
                credit =self.__module_widgets[i].get_module().get_credit()
                all_credit = all_credit + credit
                total = total + (gpa * credit)

        if should_update:
            self.lbl_stu_list.configure(text='Semester GPA: %.2f' % (total / all_credit))
