import sys
import importlib
import os
import json
import time
import math
import textwrap
import datetime
import glob

long_help_text = """
seastar --gui                           Launch the SeaSTAR GUI
seastar <job_name> [flags]              General structure

With any SeaSTAR job you can use --logfile <file_path> to send all log output
to a file in addition to the console. This log output will also include the date
and time, along with all arguments used to invoke SeaSTAR.
"""

def base_cli():
    global last_line_terminated
    last_line_terminated = True

    python_file_loc = os.path.dirname(os.path.realpath(__file__))

    job_dirs = os.scandir(os.path.join(python_file_loc, "jobs"))
    #found_job_modules = {}
    module_io_defs = {}
    for job_dir in job_dirs:
        if job_dir.is_dir():
            if not job_dir.name.startswith("__"):
                full_job_dir = os.path.join(python_file_loc, "jobs", job_dir.name)
                with open(os.path.join(full_job_dir, "io.json"), "r") as io_json_fp:
                    module_io_defs[job_dir.name] = json.loads(io_json_fp.read())
                    #print("loading jobs." + job_dir.name)
                    module_io_defs[job_dir.name]["job_module"] = "seastartool.jobs." + job_dir.name
                    #options = {}
                    #found_job_modules[job_dir.name].MainJob(options)



    #for
    #found_job_modules[module_name] = importlib.import_module(module_path)

    eargs = sys.argv[1:]
    help_flag = False
    explicit_help_flag = False
    ehelp_msg = "No command specified"


    mode = "command"
    mode_stack = []
    capture_heap = []
    command = None
    capture_option = None
    multiple_capture_switch = False
    options = {}
    io_def = None
    gui_flag = False
    logfile_path = None

    for arg in eargs:
        if arg.startswith("--"):
            if multiple_capture_switch:
                multiple_capture_switch = False
                mode = mode_stack.pop() # Break out of the current multiple capture
            if arg == "--help":
                ehelp_msg = None
                help_flag = True
                explicit_help_flag = True
                break
            if arg == "--gui":
                gui_flag = True
            elif arg == "--logfile":
                mode_stack.append(mode)
                mode = "logfile_capture"
            else:
                option_recognised = False
                if io_def is not None:
                    for io_def_key in io_def["inputs"].keys():
                        if "cli_arg" in io_def["inputs"][io_def_key].keys():
                            if io_def["inputs"][io_def_key]["cli_arg"] == arg[2:]:
                                capture_option = io_def_key
                                if "multiple" in io_def["inputs"][io_def_key].keys():
                                    multiple_capture_switch = io_def["inputs"][io_def_key]["multiple"]
                                if io_def["inputs"][io_def_key]["type"] == "BOOLEAN":
                                    options[io_def_key] = True
                                else:
                                    mode_stack.append(mode)
                                    if multiple_capture_switch:
                                        mode = "multi_capture"
                                    else:
                                        mode = "single_capture"
                                option_recognised = True
                                break

                if not option_recognised:
                    ehelp_msg = "Unrecognised option \"" + arg + "\""
                    help_flag = True
                    break
        elif arg.startswith("-"):
            if multiple_capture_switch:
                multiple_capture_switch = False
                mode = mode_stack.pop() # Break out of the current multiple capture
            if arg == "-h":
                ehelp_msg = None
                help_flag = True
                explicit_help_flag = True
                break
            else:
                option_recognised = False
                if io_def is not None:
                    for io_def_key in io_def["inputs"].keys():
                        if "cli_short" in io_def["inputs"][io_def_key].keys():
                            if io_def["inputs"][io_def_key]["cli_short"] == arg[1:]:
                                capture_option = io_def_key
                                if "multiple" in io_def["inputs"][io_def_key].keys():
                                    multiple_capture_switch = io_def["inputs"][io_def_key]["multiple"]
                                if io_def["inputs"][io_def_key]["type"] == "BOOLEAN":
                                    options[io_def_key] = True
                                else:
                                    mode_stack.append(mode)
                                    if multiple_capture_switch:
                                        mode = "multi_capture"
                                    else:
                                        mode = "single_capture"
                                option_recognised = True
                                break

                if not option_recognised:
                    ehelp_msg = "Unrecognised option \"" + arg + "\""
                    help_flag = True
                    break
        else:
            if mode == "command":
                if arg == "help":
                    ehelp_msg = None
                    help_flag = True
                    explicit_help_flag = True
                    break
                elif arg == "gui":
                    gui_flag = True
                    break
                else:
                    if arg in module_io_defs.keys():
                        io_def = module_io_defs[arg]
                        command = arg
                        ehelp_msg = None
                    else:
                        ehelp_msg = "Unrecognised command \"" + arg + "\""
                        help_flag = True
                        break
            elif mode == "multi_capture":
                capture_heap.append(arg)
                options[capture_option] = capture_heap
            elif mode == "single_capture":
                options[capture_option] = arg
                mode = mode_stack.pop()
            elif mode == "logfile_capture":
                logfile_path = arg
                mode = mode_stack.pop()

    if command is None:
        if not gui_flag:
            help_flag = True

    if not help_flag:
        if io_def is not None:
            for option_key in io_def["inputs"].keys():
                if option_key not in options.keys():
                    if "default" in io_def["inputs"][option_key].keys():
                        options[option_key] = io_def["inputs"][option_key]["default"]
                    elif "required" in io_def["inputs"][option_key].keys():
                        if io_def["inputs"][option_key]["required"]:
                            help_flag = True
                            ehelp_msg = "Missing required option \"" + option_key +  "\", specified by"
                            if "cli_arg" in io_def["inputs"][option_key]:
                                ehelp_msg += " \"--" + io_def["inputs"][option_key]["cli_arg"] + " <value>\""
                            if "cli_short" in io_def["inputs"][option_key]:
                                ehelp_msg += " \"-" + io_def["inputs"][option_key]["cli_short"] + " <value>\""
                            if "hint" in io_def["inputs"][option_key].keys():
                                ehelp_msg += "\nHint: " + io_def["inputs"][option_key]["hint"]

    if help_flag:
        print("")
        print("SeaSTAR")
        print("Sea-faring System for Tagging, Attribution and Redistribution")
        print("")
        print("Copyright 2025, A Baldwin <alewin@noc.ac.uk>, National Oceanography Centre")
        print("This program comes with ABSOLUTELY NO WARRANTY. This is free software,")
        print("and you are welcome to redistribute it under the conditions of the")
        print("GPL version 3 license.")
        print("")
        if ehelp_msg is not None:
            if not explicit_help_flag:
                print("ERROR")
                print(ehelp_msg)
                print("")
        if (command == "help") or (command is None):
            print("Common usage:")
            print(long_help_text)
            print("Use \"seastar <job_name> --help\" to get specific help for a given job.")
            print("The following jobs are avaliable:")
            print("")
            for command_name in module_io_defs.keys():
                title = module_io_defs[command_name]["name"]
                description = module_io_defs[command_name]["description"]
                print(command_name.ljust(40, " ") + title)
                print("")
                text_wrapper = textwrap.TextWrapper(width=79-4)
                print("    " + text_wrapper.fill(text=description).replace("\n", "\n    "))
                print("")
        else:

            io_def = module_io_defs[command]
            title = io_def["name"]
            description = io_def["description"]
            print(command.ljust(40, " ") + title)
            print("")
            text_wrapper = textwrap.TextWrapper(width=79)
            print(text_wrapper.fill(text=description))
            print("")

            text_wrapper = textwrap.TextWrapper(width=79-40-1)

            for input_def_key in io_def["inputs"]:

                input_def = io_def["inputs"][input_def_key]
                input_sig = "      "
                input_tail_lines = ""
                semantic_type = "TEXT"
                if "type" in input_def.keys():
                    if input_def["type"] == "BOOLEAN":
                        semantic_type = "SWITCH"
                if "semantic_type" in input_def.keys():
                    semantic_type = input_def["semantic_type"]
                input_sig_ed = "<text>"
                if semantic_type == "MULTIPLE_FILES":
                    input_sig_ed = "<file> [file [...]]"
                if semantic_type == "SINGLE_FILE":
                    input_sig_ed = "<file>"
                elif semantic_type == "SINGLE_FOLDER":
                    input_sig_ed = "<folder>"
                elif semantic_type == "SWITCH":
                    input_sig_ed = ""

                hint_text = input_def["hint"]

                if "cli_short" in input_def.keys():
                    input_sig = "  -" + input_def["cli_short"] + ", "
                if "cli_arg" in input_def.keys():
                    input_sig += "--" + input_def["cli_arg"]
                if "required" in input_def.keys():
                    if input_def["required"]:
                        hint_text = "[REQUIRED] " + hint_text

                input_sig = input_sig + " " + input_sig_ed

                if "hint" in input_def.keys():
                    input_tail_lines = text_wrapper.fill(hint_text).replace("\n", "\n" + (" " * 40))
                input_str_def = input_sig.ljust(40, " ") + input_tail_lines
                print(input_str_def)
    else:
        for option_key in options.keys():
            input_def = module_io_defs[command]["inputs"][option_key]
            if "semantic_type" in input_def.keys():
                #print(option_key + " is " + input_def["semantic_type"])
                if input_def["semantic_type"] == "MULTIPLE_FILES":
                    if "multiple" in input_def.keys():
                        if input_def["multiple"]:
                            new_option_arr = []
                            for input_str in options[option_key]:
                                new_option_arr = new_option_arr + glob.glob(input_str)

                            #print("GLOB RECONSTRUCTION TRIGGER")
                            #print(options[option_key])
                            #print(new_option_arr)

                            options[option_key] = new_option_arr




        if gui_flag:
            from .gui import SeaSTARGUI # Avoid loading the GUI if the user doesn't want it!
            gui = SeaSTARGUI(python_file_loc=python_file_loc, module_io_defs=module_io_defs)

            gui.render_start_page()
            gui.enter_mainloop()
        else:

            job_start_time = time.time()

            logfile_fh = None

            if logfile_path is not None:
                logfile_fh = open(logfile_path, "a")
                curr_datetime = datetime.datetime.now(datetime.timezone.utc) # Do not use datetime.UTC - only avail on Python 3.11 or newer
                logfile_fh.write("[LOG - " + curr_datetime.strftime("%Y-%m-%d %H:%M:%S") + "] SeaSTAR invoked with following command\n")
                logfile_fh.write("[LOG - " + curr_datetime.strftime("%Y-%m-%d %H:%M:%S") + "] " + " ".join(sys.argv[1:]) + "\n")

            def prf(prop, etr):
                bar_w = 16
                bar_x = round(bar_w * prop)
                bar_l = "#"*bar_x
                bar_r = "_"*(bar_w - bar_x)
                percent = f"{prop:.2%}"
                secs = round(etr)
                timestr = f"about {secs}s remaining..."
                if secs < 3:
                    timestr = "only a few seconds remaining..."
                if secs > 60:
                    mins = math.floor(secs / 60)
                    secs = secs - (mins * 60)
                    timestr = f"about {mins}min {secs}s remaining..."
                if secs > 3600:
                    hrs = math.floor(mins / 60)
                    mins = mins - (hrs * 60)
                    timestr = f"about {hrs}hr {mins}min remaining..."

                print(f"\r[{bar_l}{bar_r}] {percent} done, {timestr}".ljust(79, " "), end="")

            def log_function(txt):
                if last_line_terminated:
                    print("[LOG] " + txt)
                else:
                    print("\n[LOG] " + txt, end="")
                if logfile_fh is not None:
                    curr_datetime = datetime.datetime.now(datetime.timezone.utc)
                    logfile_fh.write("[LOG - " + curr_datetime.strftime("%Y-%m-%d %H:%M:%S") + "] " + txt + "\n")

            def error_function(txt, err_level):
                if last_line_terminated:
                    print("[ERR] " + txt)
                else:
                    print("\n[ERR] " + txt, end="")
                if logfile_fh is not None:
                    curr_datetime = datetime.datetime.now(datetime.timezone.utc)
                    logfile_fh.write("[ERR - " + curr_datetime.strftime("%Y-%m-%d %H:%M:%S") + "] " + txt + "\n")

            log_function("Preparing job...")

            main_job_object = importlib.import_module(module_io_defs[command]["job_module"]).MainJob(options, prf, log_function, error_function)
            log_function("Starting main processing script...")
            last_line_terminated = False
            main_job_object.execute()
            job_end_time = time.time()

            secs = round(job_end_time - job_start_time)
            timestr = f"{secs}s"
            if secs > 60:
                mins = math.floor(secs / 60)
                secs = secs - (mins * 60)
                timestr = f"{mins}min {secs}s"
            if secs > 3600:
                hrs = math.floor(mins / 60)
                mins = mins - (hrs * 60)
                timestr = f"{hrs}hr {mins}min"

            log_function(f"Finished in  {timestr}".ljust(79, " "))
            last_line_terminated = True
            print("")
            log_function("Done!")

            if logfile_fh is not None:
                logfile_fh.close()

