from os.path import exists
from os import getcwd
from threading import Thread
import tkinter.messagebox
import customtkinter
import brake, current, iso, wavelogger
from time import time, strftime, localtime
from urllib.request import urlopen
from socket import setdefaulttimeout

customtkinter.set_appearance_mode("System")  # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("blue")  # Themes: "blue" (standard), "green", "dark-blue"
customtkinter.set_widget_scaling(1.1)  # widget dimensions and text size
customtkinter.set_window_scaling(1.1)  # window geometry dimensions
setdefaulttimeout(10)
# global vars
btns = {
    'start': {'btn': '', 'row': 1, 'text': '开始运行'},
    'check': {'btn': '', 'row': 2, 'text': '检查参数'},
    'log': {'btn': '', 'row': 3, 'text': '保存日志'},
    'end': {'btn': '', 'row': 4, 'text': '结束运行'},
}
widgits = {
    'path': {'label': '', 'entry': '', 'row': 1, 'col': 2, 'text': '数据文件夹路径'},
    'av': {'label': '', 'entry': '', 'row': 2, 'col': 2, 'text': '角速度'},
    'rc': {'label': '', 'entry': '', 'row': 2, 'col': 4, 'text': '额定电流'},
    'rpm': {'label': '', 'entry': '', 'row': 2, 'col': 6, 'text': '额定转速'},
    'rr': {'label': '', 'entry': '', 'row': 2, 'col': 8, 'text': '减速比'},
    'dur': {'label': '', 'entry': '', 'row': 2, 'col': 10, 'text': '周期时间'},
    'axis': {'label': '', 'optionmenu': '', 'row': 3, 'col': 2, 'text': ''},
    'vel': {'label': '', 'optionmenu': '', 'row': 3, 'col': 4, 'text': ''},
    'trq': {'label': '', 'optionmenu': '', 'row': 3, 'col': 6, 'text': ''},
    'trqh': {'label': '', 'optionmenu': '', 'row': 3, 'col': 8, 'text': ''},
    'estop': {'label': '', 'optionmenu': '', 'row': 3, 'col': 10, 'text': ''},
    'rc1': {'label': '', 'entry': '', 'row': 4, 'col': 2, 'text': '额定电流'},
    'rc2': {'label': '', 'entry': '', 'row': 4, 'col': 4, 'text': '额定电流'},
    'rc3': {'label': '', 'entry': '', 'row': 4, 'col': 6, 'text': '额定电流'},
    'rc4': {'label': '', 'entry': '', 'row': 4, 'col': 8, 'text': '额定电流'},
    'rc5': {'label': '', 'entry': '', 'row': 4, 'col': 10, 'text': '额定电流'},
    'rc6': {'label': '', 'entry': '', 'row': 4, 'col': 12, 'text': '额定电流'},
}


class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()
        self.my_font = customtkinter.CTkFont(family="Consolas", size=16, weight="bold")
        self.w_param = 84
        # =====================================================================
        # configure window
        self.title("AIO - All in one automatic toolbox")
        # self.iconbitmap('./icon.ico')
        self.geometry("1200x550+30+30")
        self.protocol("WM_DELETE_WINDOW", self.func_end_callback)
        self.config(bg='#E9E9E9')
        self.grid_rowconfigure(5, weight=1)
        self.grid_columnconfigure((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), weight=1)
        self.minsize(1200, 550)
        # =====================================================================
        # create frame sidebar(left)
        self.frame_func = customtkinter.CTkFrame(self, width=120, corner_radius=0, fg_color='#E9E9E9')
        self.frame_func.grid(row=0, column=0, rowspan=7, sticky='nsew')
        # create AIO logo
        self.label_logo = customtkinter.CTkLabel(self.frame_func, text="Rokae AIO", height=60, font=customtkinter.CTkFont(family="Segoe Script Bold", size=24, weight="bold"), text_color="#4F4F4F")
        self.label_logo.grid(row=0, column=0, padx=15, pady=15)
        # create buttons
        for func in btns:
            btns[func]['btn'] = customtkinter.CTkButton(self.frame_func, corner_radius=10, text=btns[func]['text'], fg_color='#4F4F4F', font=self.my_font)
            btns[func]['btn'].grid(row=btns[func]['row'], column=0, sticky='new', padx=10, pady=10, ipadx=5, ipady=5)
        btns['start']['btn'].configure(command=lambda: self.thread_it(self.func_start_callback))
        btns['check']['btn'].configure(command=lambda: self.thread_it(self.func_check_callback))
        btns['log']['btn'].configure(command=lambda: self.thread_it(self.func_log_callback))
        btns['end']['btn'].configure(command=lambda: self.thread_it(self.func_end_callback))
        # create version info
        self.label_version = customtkinter.CTkLabel(self.frame_func, justify='left', text="Vers: 0.1.6.2\nDate: 06/16/2024", font=self.my_font, text_color="#4F4F4F")
        self.frame_func.rowconfigure(6, weight=1)
        self.label_version.grid(row=6, column=0, padx=20, pady=20, sticky='s')
        # =====================================================================
        # create tabviews
        self.tabview = customtkinter.CTkTabview(self, width=10000, height=100, anchor='w', fg_color='#E9E9E9', border_width=2, border_color='#CDCDCD')
        self.tabview.grid(row=0, column=1, padx=10, pady=5, sticky="nsew")
        self.tabview.add("Data Process")
        self.tabview.add("Automatic Test")
        # create main menu
        self.menu_main_dp = customtkinter.CTkOptionMenu(self.tabview.tab('Data Process'), values=["init", "brake", "current", "iso", "wavelogger"], font=self.my_font, text_color='yellow', button_color='red', fg_color='green', command=self.func_main_callback)
        self.menu_main_dp.grid(row=1, column=1, sticky='we', padx=5, pady=5)
        self.menu_main_dp.set("Start Here!")
        # create sub menu
        self.menu_sub_dp = customtkinter.CTkOptionMenu(self.tabview.tab('Data Process'))
        # =====================================================================
        # create widgits
        for widgit in widgits:
            if widgit == 'path':
                widgits[widgit]['label'] = customtkinter.CTkLabel(self.tabview.tab('Data Process'), text=f'{widgit.upper()}', font=self.my_font)
                widgits[widgit]['label'].grid(row=widgits[widgit]['row'], column=widgits[widgit]['col'], sticky='e', pady=5)
                widgits[widgit]['entry'] = customtkinter.CTkEntry(self.tabview.tab('Data Process'), width=670, placeholder_text=widgits[widgit]['text'], font=self.my_font)
                widgits[widgit]['entry'].grid(row=widgits[widgit]['row'], column=widgits[widgit]['col']+1, columnspan=11, padx=(5, 10), pady=5, sticky='we')
                widgits[widgit]['entry'].configure(state='disabled')
            elif widgit in ['av', 'rc', 'rpm', 'rr', 'dur', 'rc1', 'rc2', 'rc3', 'rc4', 'rc5', 'rc6']:
                widgits[widgit]['label'] = customtkinter.CTkLabel(self.tabview.tab('Data Process'), text=f"{widgit.upper()}", font=self.my_font)
                widgits[widgit]['label'].grid(row=widgits[widgit]['row'], column=widgits[widgit]['col'], sticky='e', pady=5)
                widgits[widgit]['entry'] = customtkinter.CTkEntry(self.tabview.tab('Data Process'), width=self.w_param, placeholder_text=f"{widgits[widgit]['text']}", font=self.my_font)
                widgits[widgit]['entry'].grid(row=widgits[widgit]['row'], column=widgits[widgit]['col']+1, padx=(5, 10), pady=5, sticky='w')
                widgits[widgit]['entry'].configure(state='disabled')
            elif widgit in ['axis', 'vel', 'trq', 'trqh', 'estop']:
                widgits[widgit]['label'] = customtkinter.CTkLabel(self.tabview.tab('Data Process'), text=f"{widgit.upper()}", font=self.my_font)
                widgits[widgit]['label'].grid(row=widgits[widgit]['row'], column=widgits[widgit]['col'], sticky='e', pady=5)
                widgits[widgit]['optionmenu'] = customtkinter.CTkOptionMenu(self.tabview.tab('Data Process'), button_color='#708090', fg_color='#778899', values=["1", "2", "3", "4", "5", "6", "7"], width=self.w_param, font=self.my_font)
                widgits[widgit]['optionmenu'].grid(row=widgits[widgit]['row'], column=widgits[widgit]['col']+1, padx=(5, 10), pady=5, sticky='w')
                widgits[widgit]['optionmenu'].configure(state='disabled')
        # =====================================================================
        # create textbox
        self.textbox = customtkinter.CTkTextbox(self, wrap='none', font=customtkinter.CTkFont(family="consolas", size=14), text_color="blue", fg_color='#E9E9E9', border_width=2, border_color='#CDCDCD', border_spacing=5)
        self.textbox.grid(row=5, column=1, rowspan=2, columnspan=13, ipadx=10, ipady=10, padx=10, pady=(5, 10), sticky='nsew')
        self.textbox.configure(state='disabled')
        # =====================================================================
        # version check
        cur_vers = self.label_version.cget("text").replace('\n', ' @ ').replace("Vers: ", '').replace("Date: ", '')
        url_vers = 'http://10.2.23.150:10008/vers'
        try:
            new_vers = urlopen(url_vers).read().decode('utf-8')
            if cur_vers.strip() != new_vers.strip():
                msg = f"""当前版本：{cur_vers}\n更新版本：{new_vers}\n\n请及时更新 http://10.2.23.150:10003/s/jRfM"""
                tkinter.messagebox.showwarning(title="版本更新", message=msg)
        except:
            tkinter.messagebox.showwarning(title="版本更新", message="连接服务器失败，无法确认当前是否是最新版本......")

    def thread_it(self, func, *args):
        """ 将函数打包进线程 """
        self.myThread = Thread(target=func, args=args)
        self.myThread.daemon = True  # 主线程退出就直接让子线程跟随退出,不论是否运行完成。
        self.myThread.start()

    def initialization(self):
        for widgit in widgits:
            if widgit in ['path', 'av', 'rc', 'rpm', 'rr', 'dur', 'rc1', 'rc2', 'rc3', 'rc4', 'rc5', 'rc6']:
                widgits[widgit]['label'].configure(text=f'{widgit.upper()}', text_color='black')
                widgits[widgit]['entry'].configure(placeholder_text=widgits[widgit]['text'], state='disabled')
            elif widgit in ['axis', 'vel', 'trq', 'trqh', 'estop']:
                widgits[widgit]['label'].configure(text=f'{widgit.upper()}', text_color="black")
                widgits[widgit]['optionmenu'].configure(state='disabled')

        self.menu_sub_dp.grid_forget()
        self.textbox.delete(index1='1.0', index2='end')
        self.textbox.configure(state='disabled')

    def func_main_callback(self, func_name):
        self.initialization()

        if func_name == 'brake':
            for widgit in widgits:
                if widgit in ['path', 'av', 'rr']:
                    widgits[widgit]['label'].configure(text_color='red')
                    widgits[widgit]['entry'].configure(state='normal')
                elif widgit in ['axis', 'vel', 'trq', 'estop']:
                    widgits[widgit]['label'].configure(text_color="red")
                    widgits[widgit]['optionmenu'].configure(state='normal')
        elif func_name == 'current':
            self.menu_sub_dp = customtkinter.CTkOptionMenu(self.tabview.tab('Data Process'), values=["max", "avg", "cycle"], font=self.my_font, button_color='red', fg_color='green', command=self.func_sub_callback)
            self.menu_sub_dp.grid(row=2, column=1, sticky='we', padx=5, pady=5)
            self.menu_sub_dp.set("--select--")
            self.menu_sub_dp.configure(text_color='yellow')

            for widgit in widgits:
                if widgit in ['path', 'rc', 'rc1', 'rc2', 'rc3', 'rc4', 'rc5', 'rc6']:
                    color = 'blue' if widgit == 'rc' else 'red'
                    widgits[widgit]['label'].configure(text_color=color)
                    widgits[widgit]['entry'].configure(state='normal')
                elif widgit in ['trqh',]:
                    widgits[widgit]['label'].configure(text_color="red")
                    widgits[widgit]['optionmenu'].configure(state='normal')
        elif func_name == 'iso' or func_name == 'wavelogger':
            for widgit in widgits:
                if widgit in ['path',]:
                    widgits[widgit]['label'].configure(text_color='red')
                    widgits[widgit]['entry'].configure(state='normal')
        else:
            self.initialization()
            self.menu_main_dp.set("Start Here!")

    def func_sub_callback(self, func_name):
        if func_name == "max":
            for widgit in widgits:
                if widgit in ['rpm', 'dur']:
                    widgits[widgit]['label'].configure(text_color='black')
                    widgits[widgit]['entry'].configure(state='disabled')
                elif widgit in ['vel', 'trq']:
                    widgits[widgit]['label'].configure(text_color='black')
                    widgits[widgit]['optionmenu'].configure(state='disabled')
        elif func_name == 'avg':
            for widgit in widgits:
                if widgit in ['rpm', 'dur']:
                    widgits[widgit]['label'].configure(text_color='black')
                    widgits[widgit]['entry'].configure(state='disabled')
                elif widgit in ['vel', 'trq']:
                    widgits[widgit]['label'].configure(text_color='black')
                    widgits[widgit]['optionmenu'].configure(state='disabled')
        elif func_name == 'cycle':
            for widgit in widgits:
                if widgit in ['rpm', 'dur']:
                    widgits[widgit]['label'].configure(text_color='blue')
                    widgits[widgit]['entry'].configure(state='normal')
                elif widgit in ['vel', 'trq']:
                    widgits[widgit]['label'].configure(text_color="red")
                    widgits[widgit]['optionmenu'].configure(state='normal')

    def write2textbox(self, text, wait=0, exitcode=0, color='blue'):
        self.textbox.tag_add(color, 'insert', 'end')
        self.textbox.tag_config(tagName=color, foreground=color)

        if wait != 0:
            self.textbox.insert(index='end', text=text, tags=color)
            self.textbox.update()
            self.textbox.see('end')
        elif exitcode != 0:
            self.textbox.insert(index='end', text=text + '\n', tags=color)
            self.textbox.update()
            self.textbox.see('end')
            self.textbox.configure(state='disabled')
            raise Exception(f"Error code: {exitcode}")
        else:
            self.textbox.insert(index='end', text=text + '\n', tags=color)
            self.textbox.update()
            self.textbox.see('end')

    def is_float(self, flag, *args):
        for item in args:
            try:
                if flag == 'optional':
                    item = 0 if item == '' else item
                    _ = float(item)
                elif flag == 'required':
                    _ = float(item)
            except Exception as Err:
                tkinter.messagebox.showerror(title="参数错误", message="请检查对应参数是否填写正确！", )
                self.write2textbox(f"错误信息：{Err}\n参数数据缺失，或者数据类型错误，更正后重新运行...\n", 0, 3, 'red')
        return True

    def check_param(self):
        func_name = self.menu_main_dp.get()
        if func_name == 'brake':
            path = widgits['path']['entry'].get().strip()
            av = widgits['av']['entry'].get().strip('- ')
            rr = widgits['rr']['entry'].get().strip('- ')
            axis = widgits['axis']['optionmenu'].get()
            vel = widgits['vel']['optionmenu'].get()
            trq = widgits['trq']['optionmenu'].get()
            estop = widgits['estop']['optionmenu'].get()

            c1 = exists(path)
            c2 = self.is_float('required', av, rr)
            c3 = True if len({vel, trq, estop}) == 3 else False

            if c1 and c2 and c3:
                return 1, path, float(av), float(rr), int(axis), int(vel), int(trq), int(estop)
            else:
                return 0, 0
        # =======================================================
        elif func_name == 'current':
            path = widgits['path']['entry'].get().strip()
            rc = widgits['rc']['entry'].get().strip('- ')
            rpm = widgits['rpm']['entry'].get().strip()
            dur = widgits['dur']['entry'].get().strip()
            rc1 = widgits['rc1']['entry'].get().strip()
            rc2 = widgits['rc2']['entry'].get().strip()
            rc3 = widgits['rc3']['entry'].get().strip()
            rc4 = widgits['rc4']['entry'].get().strip()
            rc5 = widgits['rc5']['entry'].get().strip()
            rc6 = widgits['rc6']['entry'].get().strip()
            vel = widgits['vel']['optionmenu'].get()
            trq = widgits['trq']['optionmenu'].get()
            trqh = widgits['trqh']['optionmenu'].get()
            sub = self.menu_sub_dp.get()

            c1 = exists(path)
            c2 = sub in ['max', 'avg', 'cycle']
            c3 = self.is_float('optional', rc, rpm)
            c4 = self.is_float('required', rc1, rc2, rc3, rc4, rc5, rc6)

            c5 = c6 = True
            if sub == 'cycle':
                c5 = True if len({vel, trq}) == 2 else False
                c6 = self.is_float('optional', dur)

            if c1 and c2 and c3 and c4 and c5 and c6:
                rcs = []
                for x in [rc1, rc2, rc3, rc4, rc5, rc6]:
                    rcs.append(float(x))
                rc = 0 if rc == '' else rc
                dur = 0 if sub != 'cycle' or dur == '' else dur
                rpm = 0 if sub != 'cycle' or rpm == '' else rpm
                rcs.append(float(rc))
                return 2, path, sub, rcs, int(vel), int(trq), int(trqh), float(dur), float(rpm)
            else:
                return 0, 0
        # =======================================================
        elif func_name == 'iso':
            path = widgits['path']['entry'].get().strip()
            c1 = exists(path)
            if c1:
                return 3, path
            else:
                return 0, 0
        # =======================================================
        elif func_name == 'wavelogger':
            path = widgits['path']['entry'].get().strip()
            c1 = exists(path)
            if c1:
                return 4, path
            else:
                return 0, 0
        # =======================================================
        else:
            return 0, 0

    def func_start_callback(self):
        self.textbox.configure(state='normal')
        self.textbox.delete(index1='1.0', index2='end')

        flag, *args = self.check_param()
        func_dict = {1: brake.main, 2: current.main, 3: iso.main, 4: wavelogger.main}
        if flag == 1:
            func_dict[flag](path=args[0], av=args[1], rr=args[2],  axis=args[3], vel=args[4], trq=args[5], estop=args[6], w2t=self.write2textbox)
        elif flag == 2:
            func_dict[flag](path=args[0], sub=args[1], rcs=args[2], vel=args[3], trq=args[4], trqh=args[5], dur=args[6], rpm=args[7], w2t=self.write2textbox)
        elif flag == 3:
            func_dict[flag](path=args[0], w2t=self.write2textbox)
        elif flag == 4:
            func_dict[flag](path=args[0], w2t=self.write2textbox)
        else:
            tkinter.messagebox.showerror(title="参数错误", message="请检查对应参数是否填写正确！", )

        self.textbox.configure(state='disabled')

    def func_check_callback(self):
        self.textbox.configure(state='normal')
        self.textbox.delete(index1='1.0', index2='end')

        flag, *args = self.check_param()
        if flag:
            tkinter.messagebox.showinfo(title="参数正确", message="所有参数形式上填写无误，可以开始运行！")
        else:
            tkinter.messagebox.showerror(title="参数错误", message="需要检查对应参数是否填写正确！", )

        self.textbox.configure(state='disabled')

    def func_log_callback(self):
        content = self.textbox.get(index1='1.0', index2='end')
        if len(content) > 1:
            try:
                now = strftime('%Y%m%d%H%M%S', localtime(time()))
                log_name = f"{now}_aio.log"
                with open(f'{log_name}', 'w', encoding='utf-8') as objlog:
                    objlog.write(content)
                tkinter.messagebox.showinfo(title="保存成功", message=f'{log_name}已被保存存至↓↓↓\n{getcwd()}')

            except Exception as Err:
                self.write2textbox(Err)
                tkinter.messagebox.showerror(title="保存失败", message="未能保存本次日志，或未能完整保存，请准备好相关数据，联系fanmingfu@rokae.com查看详细信息！", )
        else:
            tkinter.messagebox.showwarning(title="未能保存", message="日志数据为空，不可保存！")

    def func_end_callback(self):
        if tkinter.messagebox.askyesno(title="关闭程序", message="相关数据可能未保存，正在运行程序时有概率会损坏数据文件，确定要终止程序运行吗？"):
            self.destroy()


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