#!/bin/python3

import tkinter           as tk
import os                as os
import argparse          as ap
import time              as ti
import random            as ra
import matplotlib.pyplot as pl
from matplotlib.gridspec import GridSpec

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        # get cmd-line arguments 
        self.args = self.get_args()
   
        # properties
        self.resizable(False, False)
        self.title("Time^2")
        self.configure(bg="black")
        self.measure_missclick = False
        self.correct_number = 1 # the number that needs to be clicked next, aka the correct one
    
        # width, height of canvas and window
        self.w = 1000
        self.h = 1000
        self.screen_w = self.winfo_screenwidth()
        self.screen_h = self.winfo_screenheight()
        self.center_x = self.screen_w//2 - self.w//2
        self.center_y = self.screen_h//2 - self.h//2
        self.geometry(f"{self.w}x{self.h}+{self.center_x}+{self.center_y}")
        
        # start screen (init phase 1)
        self.btn_start = tk.Button(self, text="Start test", font=("Courier", 50))
        self.btn_start.config(command=lambda btn=self.btn_start: self.init_phase_2(btn))
        self.btn_start.pack(pady=450)
    
    def init_phase_2(self, btn):
        # buttons
        self.taken = []
        self.num_gone = 0
        self.area = self.draw()
        btn.destroy()
        self.get_random()

        # data needed for evaluation
        self.data = {
            "time_clicks": [],
            "time_missclicks": [],
            "time_test": 0,
            "num_clicks": 0,
            "num_missclicks": 0
        }
        self.start = ti.time()
        self.start2 = ti.time()
        self.start_entire_test = ti.time()
        self.end = ti.time()
        self.end_entire_test = ti.time()

    def get_args(self):
        p = ap.ArgumentParser(prog="Time^2")
        p.add_argument("-d", "--debug", action="store_true", help="print debug messages")
        return p.parse_args()

    # debug print
    def dp(self, payload: str):
        if self.args.debug:
            print(f"D: [{payload}]")

    # drawing buttons on the screen
    def draw(self) -> list[list]:
        area = []
        for i in range(5):
            temp = []
            for j in range(5):
                button = tk.Button(self, width=50, height=5, font=("Courier", 40), text="", bg="#ffffff", fg="#000000")
                button.config(command=lambda button=button, x=j*200, y=i*200, w=self.w//5, h=self.h//5: [self.button_callback(button, x, y, w, h)], activebackground="lightgray", activeforeground="black")
                button.place(x=j*200, y=i*200, width=self.w//5, height=self.h//5)
                self.dp(f"Creating a button => x: {j*200},  \ty: {i*200},\tw: {self.w//5},\th: {self.h//5}")
                temp.append(button)
            area.append(temp)
            
        return area

    # function for buttons to work
    def button_callback(self, instance, x, y, w, h):
        self.dp(f"Button clicked => {instance['text']}")
        
        # only if the number is correct
        if instance["text"] == str(self.correct_number):
            self.data["num_clicks"] += 1
            self.end = ti.time()
            instance.destroy()
            self.num_gone += 1
            self.dp(f"Correct number was => {self.correct_number}")
            self.correct_number += 1
                
            if self.measure_missclick:
                self.measure_missclick = False
                self.data["time_missclicks"].append(self.end - self.start)
                for i in range(5):
                    for j in range(5):
                        try:
                            if self.area[i][j]["bg"] == "#ff0000":
                                self.area[i][j].config(bg="#ffffff", fg="#000000", activebackground="lightgray", activeforeground="#000000")
                                self.area[i][j].place()
                        except:
                            pass

            self.data["time_clicks"].append(self.end - self.start2) 
            self.start = ti.time()
            self.start2 = ti.time()
        else:
            if instance["bg"] != "#ff0000":
                self.measure_missclick = True
                self.start = ti.time()
                self.data["num_clicks"] += 1
    
                self.data["num_missclicks"] += 1
                self.dp(f"Number of missclicks => {self.data['num_missclicks']}")
    
                instance.config(bg="#ff0000", fg="#ffffff", activebackground="darkred", activeforeground="#ffffff")
                instance.place(x=x, y=y, width=w, height=h)
        
        # check for test end
        if self.num_gone == 25:
            self.destroy()
            self.end_entire_test = ti.time()
            self.data["time_test"] = self.end_entire_test - self.start_entire_test
            self.show_data()

        self.dp("Data => ")
        for key in self.data:
            if type(self.data[key]) == float or type(self.data[key]) == int:
                self.dp("    "+key+" => "+
                                 "\n            "+str(self.data[key]))
            if type(self.data[key]) == list:
                self.dp("    "+key+" => "+
                        "".join(["\n            "+str(i) for i in self.data[key]]))
    
    # returns a random number that can be used as a text in a new button
    def get_random(self) -> str:
        for i in range(5):
            for j in range(5):
                r = ra.randint(1, 25)
                while r in self.taken:
                    r = ra.randint(1, 25)
                self.taken.append(r)
                self.area[i][j]["text"] = str(r)

    def show_data(self):
        if len(self.data["time_clicks"]) != 0:
            time_click_avg = sum(self.data["time_clicks"])/len(self.data["time_clicks"])
        else:
            time_click_avg = 0
        if self.data["time_clicks"] != []:
            time_click_longest = max(self.data["time_clicks"])
            time_click_shortest = min(self.data["time_clicks"])
        else:
            time_click_longest = 0
            time_click_shortest = 0
        
        if len(self.data["time_missclicks"]) != 0:
            time_missclick_avg = sum(self.data["time_missclicks"])/len(self.data["time_missclicks"])
        else:
            time_missclick_avg = 0
        if self.data["time_missclicks"] != []:
            time_missclick_longest = max(self.data["time_missclicks"])
            time_missclick_shortest = min(self.data["time_missclicks"])
        else:
            time_missclick_longest = 0
            time_missclick_shortest = 0

        pl.rcParams["toolbar"] = "None"

        ax1 = pl.subplot(2, 1, 1)
        ax2 = pl.subplot(2, 1, 2)

        ax1.set_title("Time between clicks on correct numbers", loc="left")
        ax1.plot(self.data["time_clicks"], marker="o")

        ax2.set_title("Time between missclicks and corrections", loc="left")
        ax2.plot(self.data["time_missclicks"], "o")

        pl.subplots_adjust(hspace=1)

        tb = pl.table(  cellText=[
                            [round(time_click_avg, 2), round(time_missclick_avg, 2), ""],
                            [round(time_click_longest, 2), round(time_missclick_longest, 2), ""],
                            [round(time_click_shortest, 2), round(time_missclick_shortest, 2), ""],
                            ["", self.data["num_missclicks"], ""],
                            ["", "", round(self.data["time_test"], 2)]],
                        rowLabels=
                            ["Average time", "Longest time", "Shortest time", "Count", "Time taken"],
                        colLabels=
                            ["Between clicks", "Between missclicks and corrections", "To complete the test"],
                        colColours=
                            ["lightgray", "lightgray", "lightgray"],
                        rowColours=
                            ["lightgray", "lightgray", "lightgray", "lightgray", "lightgray"],
                        loc=
                            "top"
                     )
        tb.set_fontsize(120)

        pl.show()

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