#!/usr/bin/env python
#encoding=utf-8
# if you want to run on command mode to monitor kktix status, try:
# python kktix_status.py --silent true
try:
    # for Python2
    import tkMessageBox as messagebox
    import ttk
    from Tkinter import *
except ImportError:
    # for Python3
    try:
        from tkinter import *
        from tkinter import filedialog, messagebox, ttk
    except Exception as e:
        pass

import argparse
import base64
import json
import os
import platform
import subprocess
import sys
import threading
import time
import webbrowser
from datetime import datetime

import requests

CONST_APP_VERSION = "MaxBot (2024.03.10)"

CONST_MAXBOT_CONFIG_FILE = "settings.json"
CONST_MAXBOT_KKTIX_CONFIG_FILE = "kktix.json"
CONST_MAXBOT_KKTIX_LOG_FILE = "kktix.log"
CONST_CAPTCHA_SOUND_FILENAME_DEFAULT = "ding-dong.wav"

translate={}

URL_DONATE = 'https://max-everyday.com/about/#donate'
URL_HELP = 'https://max-everyday.com/2018/08/max-auto-reg-bot/'
URL_RELEASE = 'https://github.com/max32002/joint_bot/releases'
URL_FB = 'https://www.facebook.com/maxbot.ticket'
URL_CHROME_DRIVER = 'https://chromedriver.chromium.org/'
URL_FIREFOX_DRIVER = 'https://github.com/mozilla/geckodriver/releases'
URL_EDGE_DRIVER = 'https://developer.microsoft.com/zh-tw/microsoft-edge/tools/webdriver/'

USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"

def load_translate():
    translate = {}
    en_us={}

    zh_tw={}
    zh_tw["language"] = '語言'
    zh_tw["enable"] = '啟用'

    zh_tw["config_list"] = '設定檔管理'
    zh_tw["advanced"] = '進階設定'
    zh_tw["autofill"] = '自動填表單'
    zh_tw["about"] = '關於'

    zh_tw["url"] = '活動網址'
    zh_tw["check_interval"] = '檢查間隔(秒)'
    
    zh_tw["status_in_stock"] = "當狀態為 IN_STOCK"
    zh_tw["play_ticket_sound"] = '有票時播放音效'
    zh_tw["play_sound_filename"] = '音效檔'
    zh_tw["ticket_exec_maxbot"] = '有票時執行MaxBot'
    zh_tw["maxbot_config"] = 'MaxBot 設定檔'
    zh_tw["ticket_exec_command"] = '有票時執行指令'
    
    zh_tw["log"] = '事件記錄'
    zh_tw["last_status"] = '上一個狀態'

    zh_tw["browse"] = '瀏覽...'
    zh_tw["save"] = '存檔'
    zh_tw["exit"] = '關閉'
    zh_tw["copy"] = '複製'
    zh_tw["restore_defaults"] = '恢復預設值'
    zh_tw["done"] = '完成'

    zh_tw["maxbot_slogan"] = 'MaxRegBot是一個免費、開放原始碼的搶票機器人。\n祝您掛號成功。'
    zh_tw["donate"] = '打賞'
    zh_tw["release"] = '所有可用版本'
    zh_tw["help"] = '使用教學'

    zh_cn={}

    ja_jp={}

    translate['en_us']=en_us
    translate['zh_tw']=zh_tw
    translate['zh_cn']=zh_cn
    translate['ja_jp']=ja_jp
    return translate

# common functions.
def find_between( s, first, last ):
    ret = ""
    try:
        start = s.index( first ) + len( first )
        end = s.index( last, start )
        ret = s[start:end]
    except ValueError:
        pass
    return ret

def t_or_f(arg):
    ret = False
    ua = str(arg).upper()
    if 'TRUE'.startswith(ua):
        ret = True
    elif 'YES'.startswith(ua):
        ret = True
    return ret

def get_app_root():
    basis = ""
    if hasattr(sys, 'frozen'):
        basis = sys.executable
    else:
        basis = sys.argv[0]
    app_root = os.path.dirname(basis)
    return app_root

def load_json():
    app_root = get_app_root()
    config_filepath = os.path.join(app_root, CONST_MAXBOT_KKTIX_CONFIG_FILE)

    config_dict = None
    if os.path.isfile(config_filepath):
        with open(config_filepath) as json_data:
            config_dict = json.load(json_data)
    else:
        config_dict = get_default_config()
    return config_filepath, config_dict

def btn_restore_defaults_clicked(language_code):
    app_root = get_app_root()
    config_filepath = os.path.join(app_root, CONST_MAXBOT_KKTIX_CONFIG_FILE)
    if os.path.exists(str(config_filepath)):
        try:
            os.unlink(str(config_filepath))
        except Exception as exc:
            print(exc)
            pass

    config_dict = get_default_config()
    messagebox.showinfo(translate[language_code]["restore_defaults"], translate[language_code]["done"])

    global root
    load_GUI(root, config_dict)

def btn_preview_sound_clicked():
    global txt_play_sound_filename
    new_sound_filename = txt_play_sound_filename.get().strip()
    #print("new_sound_filename:", new_sound_filename)
    app_root = get_app_root()
    new_sound_filename = os.path.join(app_root, new_sound_filename)
    play_mp3_async(new_sound_filename)

def play_mp3_async(sound_filename):
    threading.Thread(target=play_mp3, args=(sound_filename,)).start()

def play_mp3(sound_filename):
    from playsound import playsound
    try:
        playsound(sound_filename)
    except Exception as exc:
        msg=str(exc)
        print("play sound exeption:", msg)
        if platform.system() == 'Windows':
            import winsound
            try:
                winsound.PlaySound(sound_filename, winsound.SND_FILENAME)
            except Exception as exc2:
                pass

def btn_save_clicked():
    btn_save_act()

def get_config_dict_from_ui():
    app_root = get_app_root()
    config_filepath = os.path.join(app_root, CONST_MAXBOT_KKTIX_CONFIG_FILE)

    config_dict = get_default_config()
    language_code = get_language_code_by_name(config_dict["advanced"]["language"])

    is_all_data_correct = True
    
    #global combo_language
    if is_all_data_correct:
        #config_dict["advanced"]["language"] = combo_language.get().strip()
        # display as new language.
        language_code = get_language_code_by_name(config_dict["advanced"]["language"])

    global txt_file_name
    filelist = []
    for i in range(1):
        filelist.append(txt_file_name[i].get().strip())

    if is_all_data_correct:
        config_dict["list"]=filelist

    global txt_url
    global combo_check_interval

    global chk_state_play_ticket_sound
    global txt_play_sound_filename

    global chk_state_ticket_exec_maxbot
    
    global chk_state_ticket_exec_command
    global txt_new_process

    if is_all_data_correct:
        try:
            config_dict["url"] = txt_url.get().strip()
            config_dict["check_interval"] = int(combo_check_interval.get().strip())

            config_dict["advanced"]["play_sound"]["ticket"] = bool(chk_state_play_ticket_sound.get())
            config_dict["advanced"]["play_sound"]["filename"] = txt_play_sound_filename.get().strip()
            
            config_dict["ticket_exec_maxbot"] = bool(chk_state_ticket_exec_maxbot.get())
            
            config_dict["ticket_exec_command"] = bool(chk_state_ticket_exec_command.get())
            config_dict["new_process"] = txt_new_process.get().strip()
        except Exception as e:
            raise e

    return is_all_data_correct, config_dict

def btn_save_act(slience_mode=True):
    is_all_data_correct, config_dict = get_config_dict_from_ui()

    # slience
    app_root = get_app_root()
    config_filepath = os.path.join(app_root, CONST_MAXBOT_KKTIX_CONFIG_FILE)
    save_json(config_dict, config_filepath)
    
    if not slience_mode:
        messagebox.showinfo(translate[language_code]["save"], translate[language_code]["done"])

def save_json(config_dict, target_path):
    json_str = json.dumps(config_dict, indent=4)
    with open(target_path, 'w') as outfile:
        outfile.write(json_str)

def open_url(url):
    webbrowser.open_new(url)

def btn_exit_clicked():
    root.destroy()

def btn_donate_clicked():
    open_url.open(URL_DONATE)

def btn_help_clicked():
    open_url.open(URL_HELP)

def callbackLanguageOnChange(event):
    applyNewLanguage()

def get_language_code_by_name(new_language):
    language_code = "en_us"
    if u'繁體中文' in new_language:
        language_code = 'zh_tw'
    if u'簡体中文' in new_language:
        language_code = 'zh_cn'
    if u'日本語' in new_language:
        language_code = 'ja_jp'
    #print("new language code:", language_code)

    return language_code

def applyNewLanguage():
    global combo_language
    new_language = combo_language.get().strip()
    #print("new language value:", new_language)

    language_code=get_language_code_by_name(new_language)

    global lbl_language

    lbl_language.config(text=translate[language_code]["language"])

    global tabControl

    tabControl.tab(0, text=translate[language_code]["config_list"])
    tabControl.tab(1, text=translate[language_code]["advanced"])
    tabControl.tab(2, text=translate[language_code]["about"])

    global lbl_slogan
    global lbl_help
    global lbl_donate
    global lbl_release

    lbl_slogan.config(text=translate[language_code]["maxbot_slogan"])
    lbl_help.config(text=translate[language_code]["help"])
    lbl_donate.config(text=translate[language_code]["donate"])
    lbl_release.config(text=translate[language_code]["release"])

    global btn_browse
    for i in range(1):
        btn_browse[i].config(text=translate[language_code]['browse'] + " " + str(i+1))


# PS: nothing need to do, at current process.
def callbackHomepageOnChange(event):
    showHideBlocks()

def showHideBlocks(all_layout_visible=False):
    pass

def btn_items_browse_event(event):
    working_dir = os.path.dirname(os.path.realpath(__file__))
    #btn_index = int(str(event.widget['text']).split(" ")[1])
    btn_index = 1
    global txt_file_name_value
    file_path = filedialog.askopenfilename(initialdir=working_dir, defaultextension=".json",filetypes=[("json Documents","*.json"),("All Files","*.*")])
    if not file_path is None:
        display_path = file_path
        if len(file_path) > len(working_dir):
            if file_path[:len(working_dir)]==working_dir:
                display_path = file_path[len(working_dir)+1:]
        #print("json file path:", file_path)
        txt_file_name_value[btn_index-1].set(display_path)
        #print("new json file path:", txt_file_name[btn_index-1].get().strip())

def btn_new_process_browse_event(event):
    working_dir = os.path.dirname(os.path.realpath(__file__))
    global txt_new_process_value
    file_path = filedialog.askopenfilename(initialdir=working_dir, filetypes=[("All Files","*.*")])
    if not file_path is None:
        display_path = file_path
        if len(file_path) > len(working_dir):
            if file_path[:len(working_dir)]==working_dir:
                display_path = file_path[len(working_dir)+1:]
        print(display_path)
        txt_new_process_value.set(display_path)

def btn_items_run_event(event):
    #btn_index = int(str(event.widget['text']).split(" ")[1])
    btn_index = 1
    global txt_file_name
    filename=txt_file_name[btn_index-1].get().strip()
    threading.Thread(target=launch_maxbot, args=(filename,)).start()

def launch_maxbot(filename):
    if True:
        working_dir = os.path.dirname(os.path.realpath(__file__))
        if hasattr(sys, 'frozen'):
            print("execute in frozen mode")

            # check platform here.
            if platform.system() == 'Darwin':
                print("execute MacOS python script")
                subprocess.Popen("./chrome_tixcraft --input %s" % (filename), shell=True, cwd=working_dir)
            if platform.system() == 'Linux':
                print("execute linux binary")
                subprocess.Popen("./chrome_tixcraft --input %s" % (filename), shell=True, cwd=working_dir)
            if platform.system() == 'Windows':
                print("execute .exe binary.")
                subprocess.Popen("chrome_tixcraft.exe --input %s" % (filename), shell=True, cwd=working_dir)
        else:
            interpreter_binary = 'python'
            interpreter_binary_alt = 'python3'
            if platform.system() == 'Darwin':
                # try python3 before python.
                interpreter_binary = 'python3'
                interpreter_binary_alt = 'python'
            print("execute in shell mode.")
            #print("script path:", working_dir)
            #messagebox.showinfo(title="Debug0", message=working_dir)

            # some python3 binary, running in 'python' command.
            try:
                print('try', interpreter_binary)
                s=subprocess.Popen([interpreter_binary, "chrome_tixcraft.py","--input", "%s" % (filename)], cwd=working_dir)
                #s=subprocess.Popen(['./chrome_tixcraft'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=working_dir)
                #s=subprocess.run(['python3', 'chrome_tixcraft.py'], cwd=working_dir)
                #messagebox.showinfo(title="Debug1", message=str(s))
            except Exception as exc:
                print('try', interpreter_binary_alt)
                try:
                    s=subprocess.Popen([interpreter_binary_alt, "chrome_tixcraft.py","--input", "%s" % (filename)], cwd=working_dir)
                except Exception as exc:
                    msg=str(exc)
                    print("exeption:", msg)
                    #messagebox.showinfo(title="Debug2", message=msg)
                    pass


def ConfigListTab(root, config_dict, language_code, UI_PADDING_X):
    # output to GUI.
    row_count = 0

    frame_group_header = Frame(root)
    group_row_count = 0

    global lbl_file_name
    global txt_file_name
    global txt_file_name_value
    global btn_browse
    global btn_run
    lbl_file_name = {}
    txt_file_name = {}
    txt_file_name_value = {}
    btn_browse = {}
    btn_run = {}

    #print("config_dict[list]:",config_dict["list"])
    #print("config_dict[list]:",len(config_dict["list"]))

    # assign default value.
    play_sound_filename = config_dict["advanced"]["play_sound"]["filename"].strip()
    if play_sound_filename is None:
        play_sound_filename = ""
    if len(play_sound_filename)==0:
        play_sound_filename = play_sound_filename_default

    global lbl_url
    lbl_url = Label(frame_group_header, text=translate[language_code]['url'])
    lbl_url.grid(column=0, row=group_row_count, sticky = E)

    global txt_url
    txt_url_value = StringVar(frame_group_header, value=config_dict["url"])
    txt_url = Entry(frame_group_header, width=30, textvariable = txt_url_value)
    txt_url.grid(column=1, row=group_row_count, sticky = W)

    group_row_count +=1

    global lbl_check_interval
    lbl_check_interval = Label(frame_group_header, text=translate[language_code]['check_interval'])
    lbl_check_interval.grid(column=0, row=group_row_count, sticky = E)

    global combo_check_interval
    combo_check_interval = ttk.Combobox(frame_group_header, width=30)
    combo_check_interval['values']= ("1","3","8","15","30","60","180","300","600","3600")
    combo_check_interval.set(str(config_dict["check_interval"]))
    combo_check_interval.grid(column=1, row=group_row_count, sticky = W)

    group_row_count +=1

    global lbl_status_IN_STOCK_hr
    lbl_status_IN_STOCK_hr = Label(frame_group_header, text="")
    lbl_status_IN_STOCK_hr.grid(column=0, row=group_row_count, sticky = E)

    group_row_count +=1

    global lbl_status_IN_STOCK
    lbl_status_IN_STOCK = Label(frame_group_header, text=translate[language_code]['status_in_stock'])
    lbl_status_IN_STOCK.grid(column=0, row=group_row_count, sticky = E)

    group_row_count +=1

    global lbl_play_ticket_sound
    lbl_play_ticket_sound = Label(frame_group_header, text=translate[language_code]['play_ticket_sound'])
    lbl_play_ticket_sound.grid(column=0, row=group_row_count, sticky = E)

    global chk_state_play_ticket_sound
    chk_state_play_ticket_sound = BooleanVar()
    chk_state_play_ticket_sound.set(config_dict["advanced"]["play_sound"]["ticket"])

    global chk_play_ticket_sound
    chk_play_ticket_sound = Checkbutton(frame_group_header, text=translate[language_code]['enable'], variable=chk_state_play_ticket_sound)
    chk_play_ticket_sound.grid(column=1, row=group_row_count, sticky = W)

    group_row_count +=1

    global lbl_play_sound_filename
    lbl_play_sound_filename = Label(frame_group_header, text=translate[language_code]['play_sound_filename'])
    lbl_play_sound_filename.grid(column=0, row=group_row_count, sticky = E)

    global txt_play_sound_filename
    txt_play_sound_filename_value = StringVar(frame_group_header, value=play_sound_filename)
    txt_play_sound_filename = Entry(frame_group_header, width=30, textvariable = txt_play_sound_filename_value)
    txt_play_sound_filename.grid(column=1, row=group_row_count, sticky = W)

    icon_play_filename = "icon_play_1.gif"
    icon_play_img = PhotoImage(file=icon_play_filename)

    lbl_icon_play = Label(frame_group_header, image=icon_play_img, cursor="hand2")
    lbl_icon_play.image = icon_play_img
    lbl_icon_play.grid(column=2, row=group_row_count, sticky = W)
    lbl_icon_play.bind("<Button-1>", lambda e: btn_preview_sound_clicked())

    group_row_count +=1

    global lbl_ticket_exec_maxbot
    lbl_ticket_exec_maxbot = Label(frame_group_header, text=translate[language_code]['ticket_exec_maxbot'])
    lbl_ticket_exec_maxbot.grid(column=0, row=group_row_count, sticky = E)

    global chk_state_ticket_exec_maxbot
    chk_state_ticket_exec_maxbot = BooleanVar()
    chk_state_ticket_exec_maxbot.set(config_dict["ticket_exec_maxbot"])

    global chk_ticket_exec_maxbot
    chk_ticket_exec_maxbot = Checkbutton(frame_group_header, text=translate[language_code]['enable'], variable=chk_state_ticket_exec_maxbot)
    chk_ticket_exec_maxbot.grid(column=1, row=group_row_count, sticky = W)

    group_row_count +=1
    
    for i in range(1):
        filename = ""
        if i <= len(config_dict["list"])-1:
            filename = config_dict["list"][i]
        lbl_file_name[i] = Label(frame_group_header, text=translate[language_code]['maxbot_config'])
        lbl_file_name[i].grid(column=0, row=group_row_count, sticky = E)

        txt_file_name_value[i] = StringVar(frame_group_header, value=filename)
        txt_file_name[i] = Entry(frame_group_header, width=20, textvariable = txt_file_name_value[i])
        txt_file_name[i].grid(column=1, row=group_row_count, sticky = W)

        #btn_browse[i] = ttk.Button(frame_group_header, text=translate[language_code]['browse'] + " " + str(i+1))
        btn_browse[i] = ttk.Button(frame_group_header, text=translate[language_code]['browse'])
        btn_browse[i].grid(column=2, row=group_row_count, sticky = W)
        btn_browse[i].bind('<Button-1>', btn_items_browse_event)

        group_row_count+=1

    group_row_count +=1

    global lbl_ticket_exec_command
    lbl_ticket_exec_command = Label(frame_group_header, text=translate[language_code]['ticket_exec_command'])
    lbl_ticket_exec_command.grid(column=0, row=group_row_count, sticky = E)

    global chk_state_ticket_exec_command
    chk_state_ticket_exec_command = BooleanVar()
    chk_state_ticket_exec_command.set(config_dict["ticket_exec_command"])

    global chk_ticket_exec_command
    chk_ticket_exec_command = Checkbutton(frame_group_header, text=translate[language_code]['enable'], variable=chk_state_ticket_exec_command)
    chk_ticket_exec_command.grid(column=1, row=group_row_count, sticky = W)

    group_row_count +=1

    global lbl_new_processes
    lbl_new_processes = Label(frame_group_header, text=translate[language_code]['ticket_exec_command'])
    lbl_new_processes.grid(column=0, row=group_row_count, sticky = E)

    global txt_new_process
    global txt_new_process_value
    txt_new_process_value = StringVar(frame_group_header, value=config_dict["new_process"])
    txt_new_process = Entry(frame_group_header, width=30, textvariable = txt_new_process_value)
    txt_new_process.grid(column=1, row=group_row_count, sticky = W)

    global btn_new_process_browse
    btn_new_process_browse = ttk.Button(frame_group_header, text=translate[language_code]['browse'])
    btn_new_process_browse.grid(column=2, row=group_row_count, sticky = W)
    btn_new_process_browse.bind('<Button-1>', btn_new_process_browse_event)

    group_row_count +=1

    global lbl_status_log_hr
    lbl_status_log_hr = Label(frame_group_header, text="")
    lbl_status_log_hr.grid(column=0, row=group_row_count, sticky = E)

    group_row_count +=1

    global lbl_status_log
    lbl_status_log = Label(frame_group_header, text=translate[language_code]['log'])
    lbl_status_log.grid(column=0, row=group_row_count, sticky = E)

    group_row_count +=1

    global lbl_last_status
    lbl_last_status = Label(frame_group_header, text=translate[language_code]['last_status'])
    lbl_last_status.grid(column=0, row=group_row_count, sticky = E)

    global status_variable 
    status_variable = StringVar()
    status_variable.set("")

    global lbl_last_status_value
    lbl_last_status_value = Label(frame_group_header, textvariable=status_variable)
    lbl_last_status_value.grid(column=1, row=group_row_count, sticky = W)

    group_row_count +=1

    global txt_status_log
    txt_status_log = Text(frame_group_header, width=40, height=12)
    txt_status_log.grid(column=1, row=group_row_count, sticky = W, columnspan=2)
    txt_status_log.insert("1.0", "")

    # add first block to UI.
    frame_group_header.grid(column=0, row=row_count, sticky = W, padx=UI_PADDING_X)


def AboutTab(root, language_code):
    row_count = 0

    frame_group_header = Frame(root)
    group_row_count = 0

    logo_filename = "maxbot_logo2_single.ppm"
    logo_img = PhotoImage(file=logo_filename)

    lbl_logo = Label(frame_group_header, image=logo_img)
    lbl_logo.image = logo_img
    lbl_logo.grid(column=0, row=group_row_count, columnspan=2)

    group_row_count +=1

    global lbl_slogan
    global lbl_help
    global lbl_donate
    global lbl_release

    lbl_slogan = Label(frame_group_header, text=translate[language_code]['maxbot_slogan'], wraplength=400, justify="center")
    lbl_slogan.grid(column=0, row=group_row_count, columnspan=2)

    group_row_count +=1

    lbl_help = Label(frame_group_header, text=translate[language_code]['help'])
    lbl_help.grid(column=0, row=group_row_count, sticky = E)

    lbl_help_url = Label(frame_group_header, text=URL_HELP, fg="blue", cursor="hand2")
    lbl_help_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_help_url.bind("<Button-1>", lambda e: open_url(URL_HELP))

    group_row_count +=1

    lbl_donate = Label(frame_group_header, text=translate[language_code]['donate'])
    lbl_donate.grid(column=0, row=group_row_count, sticky = E)

    lbl_donate_url = Label(frame_group_header, text=URL_DONATE, fg="blue", cursor="hand2")
    lbl_donate_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_donate_url.bind("<Button-1>", lambda e: open_url(URL_DONATE))

    group_row_count +=1

    lbl_release = Label(frame_group_header, text=translate[language_code]['release'])
    lbl_release.grid(column=0, row=group_row_count, sticky = E)

    lbl_release_url = Label(frame_group_header, text=URL_RELEASE, fg="blue", cursor="hand2")
    lbl_release_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_release_url.bind("<Button-1>", lambda e: open_url(URL_RELEASE))

    group_row_count +=1

    lbl_fb_fans = Label(frame_group_header, text=u'Facebook')
    lbl_fb_fans.grid(column=0, row=group_row_count, sticky = E)

    lbl_fb_fans_url = Label(frame_group_header, text=URL_FB, fg="blue", cursor="hand2")
    lbl_fb_fans_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_fb_fans_url.bind("<Button-1>", lambda e: open_url(URL_FB))

    group_row_count +=1

    lbl_chrome_driver = Label(frame_group_header, text=u'Chrome Driver')
    lbl_chrome_driver.grid(column=0, row=group_row_count, sticky = E)

    lbl_chrome_driver_url = Label(frame_group_header, text=URL_CHROME_DRIVER, fg="blue", cursor="hand2")
    lbl_chrome_driver_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_chrome_driver_url.bind("<Button-1>", lambda e: open_url(URL_CHROME_DRIVER))

    group_row_count +=1

    lbl_firefox_driver = Label(frame_group_header, text=u'Firefox Driver')
    lbl_firefox_driver.grid(column=0, row=group_row_count, sticky = E)

    lbl_firefox_driver_url = Label(frame_group_header, text=URL_FIREFOX_DRIVER, fg="blue", cursor="hand2")
    lbl_firefox_driver_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_firefox_driver_url.bind("<Button-1>", lambda e: open_url(URL_FIREFOX_DRIVER))

    group_row_count +=1

    lbl_edge_driver = Label(frame_group_header, text=u'Edge Driver')
    lbl_edge_driver.grid(column=0, row=group_row_count, sticky = E)

    lbl_edge_driver_url = Label(frame_group_header, text=URL_EDGE_DRIVER, fg="blue", cursor="hand2")
    lbl_edge_driver_url.grid(column=1, row=group_row_count, sticky = W)
    lbl_edge_driver_url.bind("<Button-1>", lambda e: open_url(URL_EDGE_DRIVER))

    frame_group_header.grid(column=0, row=row_count)

def AdvancedTab(root, config_dict, language_code, UI_PADDING_X):
    row_count = 0

    frame_group_header = Frame(root)
    group_row_count = 0

    # for kktix
    print("==[advanced]==")
    print(config_dict["advanced"])

    global lbl_language
    lbl_language = Label(frame_group_header, text=translate[language_code]['language'])
    lbl_language.grid(column=0, row=group_row_count, sticky = E)

    global combo_language
    combo_language = ttk.Combobox(frame_group_header, state="readonly")
    combo_language['values']= ("English","繁體中文","簡体中文","日本語")
    combo_language.set(config_dict["advanced"]['language'])
    combo_language.bind("<<ComboboxSelected>>", callbackLanguageOnChange)
    combo_language.grid(column=1, row=group_row_count, sticky = W)

    frame_group_header.grid(column=0, row=row_count, padx=UI_PADDING_X)


def get_action_bar(root, language_code):
    frame_action = Frame(root)

    #global btn_save
    global btn_restore_defaults

    #btn_save = ttk.Button(frame_action, text=translate[language_code]['save'], command= lambda: btn_save_clicked() )
    #btn_save.grid(column=1, row=0)

    btn_restore_defaults = ttk.Button(frame_action, text=translate[language_code]['restore_defaults'], command= lambda: btn_restore_defaults_clicked(language_code))
    btn_restore_defaults.grid(column=2, row=0)

    return frame_action

def clearFrame(frame):
    # destroy all widgets from frame
    for widget in frame.winfo_children():
       widget.destroy()

def load_GUI(root, config_dict):
    clearFrame(root)

    language_code="zh_tw"
    #language_code = get_language_code_by_name(config_dict["advanced"]["language"])

    row_count = 0

    global tabControl
    tabControl = ttk.Notebook(root)
    tab1 = Frame(tabControl)
    tabControl.add(tab1, text=translate[language_code]['config_list'])

    #tab2 = Frame(tabControl)
    #tabControl.add(tab2, text=translate[language_code]['advanced'])

    tab3 = Frame(tabControl)
    tabControl.add(tab3, text=translate[language_code]['about'])
    tabControl.grid(column=0, row=row_count)
    
    tabControl.select(tab1)

    row_count+=1
    frame_action = get_action_bar(root, language_code)
    frame_action.grid(column=0, row=row_count)

    global UI_PADDING_X
    ConfigListTab(tab1, config_dict, language_code, UI_PADDING_X)
    #AdvancedTab(tab2, config_dict, language_code, UI_PADDING_X)
    AboutTab(tab3, language_code)

def get_default_config():
    config_dict={}

    config_dict["list"] = [CONST_MAXBOT_CONFIG_FILE]
    config_dict["check_interval"]=3
    config_dict["url"]=""

    config_dict["advanced"] = {}
    config_dict["advanced"]["language"] = "繁體中文"

    config_dict["advanced"]["play_sound"] = {}
    config_dict["advanced"]["play_sound"]["ticket"] = True
    config_dict["advanced"]["play_sound"]["filename"] = CONST_CAPTCHA_SOUND_FILENAME_DEFAULT

    config_dict["ticket_exec_maxbot"] = True
    config_dict["ticket_exec_command"] = True
    config_dict["new_process"] = ""

    return config_dict

def main_gui():
    global translate
    # only need to load translate once.
    translate = load_translate()

    global config_filepath
    global config_dict
    # only need to load json file once.
    config_filepath, config_dict = load_json()

    global root
    root = Tk()
    root.title(CONST_APP_VERSION)

    global UI_PADDING_X
    UI_PADDING_X = 15

    load_GUI(root, config_dict)

    GUI_SIZE_WIDTH = 580
    GUI_SIZE_HEIGHT = 600

    GUI_SIZE_MACOS = str(GUI_SIZE_WIDTH) + 'x' + str(GUI_SIZE_HEIGHT)
    GUI_SIZE_WINDOWS=str(GUI_SIZE_WIDTH-60) + 'x' + str(GUI_SIZE_HEIGHT-55)

    GUI_SIZE =GUI_SIZE_MACOS

    if platform.system() == 'Windows':
        GUI_SIZE = GUI_SIZE_WINDOWS

    root.geometry(GUI_SIZE)

    # for icon.
    icon_filepath = 'tmp.ico'

    # icon format.
    iconImg = ' '
    if platform.system() == 'Linux':
        # PNG format.
        iconImg = ' '

    tmpIcon = open(icon_filepath, 'wb+')
    tmpIcon.write(base64.b64decode(iconImg))
    tmpIcon.close()
    if platform.system() == 'Windows':
        root.iconbitmap(icon_filepath)
    if platform.system() == 'Darwin':
        #from PIL import Image, ImageTk
        #logo = ImageTk.PhotoImage(Image.open(icon_filepath).convert('RGB'))
        #root.call('wm', 'iconphoto', root._w, logo)
        pass
    if platform.system() == 'Linux':
        logo = PhotoImage(file=icon_filepath)
        root.call('wm', 'iconphoto', root._w, logo)
    os.remove(icon_filepath)

    root.mainloop()

def kktix_get_registerStatus(event_code):
    html_result = None

    url = "https://kktix.com/g/events/%s/register_info" % (event_code)
    #print('event_code:',event_code)
    #print("url:", url)

    headers = {"Accept-Language": "zh-TW,zh;q=0.5", 'User-Agent': USER_AGENT}
    try:
        html_result = requests.get(url , headers=headers, timeout=0.7, allow_redirects=False)
    except Exception as exc:
        html_result = None
        print("send reg_info request fail:")
        print(exc)

    registerStatus = ""
    if not html_result is None:
        status_code = html_result.status_code
        #print("status_code:",status_code)
        if status_code == 200:
            html_text = html_result.text
            #print("html_text:", html_text)
            try:
                jsLoads = json.loads(html_text)
                if 'inventory' in jsLoads:
                    if 'registerStatus' in jsLoads['inventory']:
                        registerStatus = jsLoads['inventory']['registerStatus']
            except Exception as exc:
                print("load reg_info json fail:")
                print(exc)
                pass

    #print("registerStatus:", registerStatus)
    return registerStatus

def kktix_get_event_code(url):
    event_code = ""
    if '/registrations/new' in url:
        prefix_list = ['.com/events/','.cc/events/']
        postfix = '/registrations/new'

        for prefix in prefix_list:
            event_code = find_between(url,prefix,postfix)
            if len(event_code) > 0:
                break

    #print('event_code:',event_code)
    return event_code

def kktix_in_stock_play_sound():
    global chk_state_play_ticket_sound
    if 'chk_state_play_ticket_sound' in globals():
        try:
            if bool(chk_state_play_ticket_sound.get()):
                btn_preview_sound_clicked()
        except Exception as e:
            pass

def get_kktix_status_by_url(url):
    registerStatus = ""
    if len(url) > 0:
        event_code = kktix_get_event_code(url)
        #print(event_code)
        if len(event_code) > 0:
            registerStatus = kktix_get_registerStatus(event_code)
            #print(registerStatus)
    return registerStatus

def update_kktix_status(registerStatus):
    global status_variable 
    if 'status_variable' in globals():
        try:
            status_variable.set(registerStatus)
        except Exception as e:
            pass

def append_kktix_status_log(output_log):
    global txt_status_log
    if 'txt_status_log' in globals():
        try:
            txt_status_log.insert("1.0", output_log + "\n")
        except Exception as e:
            pass

def append_kktix_status_log_file(output_log):
    app_root = get_app_root()
    log_filepath = os.path.join(app_root, CONST_MAXBOT_KKTIX_LOG_FILE)
    file1 = open(log_filepath, "a")  # append mode
    file1.write(output_log)
    file1.close()

def kktix_status_query(config_dict, last_status, log_file=False):
    url = config_dict["url"]
    datetime_string = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
    registerStatus = ""
    if len(url) > 0:
        registerStatus = get_kktix_status_by_url(url)
        update_kktix_status(registerStatus)

    if len(registerStatus) > 0:
        output_log = datetime_string + ", " + registerStatus
        print(output_log)
        if log_file:
            append_kktix_status_log_file(output_log + "\n")

        if registerStatus=="IN_STOCK":
            # each time to play sound.
            kktix_in_stock_play_sound()

        if registerStatus != last_status:
            # run once.
            append_kktix_status_log(output_log)

            if registerStatus=="IN_STOCK":
                btn_index = 1
                try:
                    print(config_dict["ticket_exec_maxbot"])
                    if config_dict["ticket_exec_maxbot"]:
                        filename=config_dict["list"][btn_index-1]
                        print("filename", filename)
                        if len(filename) > 0:
                            threading.Thread(target=launch_maxbot, args=(filename,)).start()

                    print(config_dict["ticket_exec_command"], config_dict["new_process"])
                    if config_dict["ticket_exec_command"]:
                        if len(config_dict["new_process"]) > 0:
                            working_dir = os.path.dirname(os.path.realpath(__file__))
                            subprocess.Popen(config_dict["new_process"] , shell=True, cwd=working_dir)
                except Exception as e:
                    print(e)
                    pass

    return registerStatus

def collect_ui_config():
    global combo_check_interval
    global txt_url
    
    global chk_state_ticket_exec_maxbot
    global txt_file_name
    
    global chk_state_ticket_exec_command
    global txt_new_process

    is_ui_ready = True
    check_interval = 3
    config_filepath, config_dict = load_json()

    if 'combo_check_interval' in globals():
        try:
            check_interval = int(combo_check_interval.get().strip())
            config_dict["check_interval"] = check_interval
        except Exception as e:
            pass
    else:
        is_ui_ready = False

    url = ""
    filename = ""
    #print("is_ui_ready:", is_ui_ready)
    if is_ui_ready:
        try:
            if 'txt_url' in globals():
                url = txt_url.get().strip()
                config_dict["url"] = url

            if 'chk_state_ticket_exec_maxbot' in globals():
                config_dict["ticket_exec_maxbot"] = bool(chk_state_ticket_exec_maxbot.get())
            
            # current only 1 config file.
            btn_index = 1
            if 'txt_file_name' in globals():
                filename=txt_file_name[btn_index-1].get().strip()
                config_dict["list"] = [filename]

            if 'chk_state_ticket_exec_command' in globals():
                config_dict["ticket_exec_command"] = bool(chk_state_ticket_exec_command.get())
            
            if 'txt_new_process' in globals():
                config_dict["new_process"] = txt_new_process.get().strip()
        except Exception as e:
            pass

    return is_ui_ready, config_dict

def resetful_api_timer(log_file=False):
    checkpoint_time = time.time()
    last_status = ""

    config_filepath, config_dict = load_json()
    json_str_old = json.dumps(config_dict)

    while True:
        elapsed_time = time.time() - checkpoint_time
        is_ui_ready, config_dict = collect_ui_config()
        #print(is_ui_ready, config_dict)

        if elapsed_time > config_dict["check_interval"]:
            # main program.
            last_status = kktix_status_query(config_dict, last_status, log_file=log_file)
            checkpoint_time = time.time()

        if is_ui_ready:
            json_str_new = json.dumps(config_dict)
            if json_str_new != json_str_old:
                save_json(config_dict, config_filepath)
                json_str_old = json_str_new

        time.sleep(0.5)

def main(args):
    silent_flag = False
    if not args.silent is None:
        silent_flag = t_or_f(args.silent)
    #print("silent_flag:",silent_flag)

    if not silent_flag:
        threading.Thread(target=resetful_api_timer, daemon=True).start()
        main_gui()
    else:
        resetful_api_timer(log_file=True)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
            description="MaxBot Aggument Parser")

    parser.add_argument("--silent",
        help="command line mode",
        default='False',
        type=str)

    args = parser.parse_args()
    main(args)
