"""
@file mt_utils.py

@brief The memory test utilities

Copyright (C) Atmosic 2023-2025
"""

import argparse
import getpass
import xml.etree.ElementTree as ET
import os
from os.path import dirname, realpath
from pathlib import Path
import sys
import shutil

ST_PASS = 0
ST_FAIL = 1
ST_FATAL_ERROR = 2
ST_FALSE_ERROR = 3
OPERATION_DIR = Path(dirname(realpath(__file__)))

PAD_TYPE_55 = 0
PAD_TYPE_AA = 1
PAD_TYPE_MAX = 2
PAD_TYPE_DICT = {
    "0x55": PAD_TYPE_55,
    "0xaa": PAD_TYPE_AA,
}

PAD_TYPE_NAME_DICT = {
    PAD_TYPE_55: "0x55",
    PAD_TYPE_AA: "0xaa",
}

PAD_TYPE_VALUE = {
    PAD_TYPE_55: b"\125",
    PAD_TYPE_AA: b"\252",
}

ITEM_PLARFORM = 0
ITEM_INFO = 1
ITEM_TYPE = 2
ITEM_SIZE = 3
ITEM_REG_START = 4
ITEM_REG_END = 5
ITEM_FILE = 6
ITEM_FILE_NAME = 7
ITEM_INFO_PREPARE = 8
ITEM_INFO_FW = 9
ITEM_TYPE_SRAM = 10
ITEM_TYPE_RRAM = 11
ITEM_TYPE_FLASH = 12
ITEM_FILE_BOOTLOADER = 13
ITEM_FILE_HCI_VENDOR = 14
ITEM_FILE_FLASHNVDS = 15
ITEM_DICT = {
    "prepare": ITEM_INFO_PREPARE,
    "firmware": ITEM_INFO_FW,
    "sram": ITEM_TYPE_SRAM,
    "rram": ITEM_TYPE_RRAM,
    "flash": ITEM_TYPE_FLASH,
    "bootloader": ITEM_FILE_BOOTLOADER,
    "HCI_vendor": ITEM_FILE_HCI_VENDOR,
    "flash_nvds": ITEM_FILE_FLASHNVDS,
}
ITEM_NAME_DICT = {
    ITEM_PLARFORM: "Platform",
    ITEM_INFO: "Info",
    ITEM_INFO_PREPARE: "prepare",
    ITEM_INFO_FW: "firmware",
    ITEM_TYPE: "Type",
    ITEM_TYPE_SRAM: "sram",
    ITEM_TYPE_RRAM: "rram",
    ITEM_TYPE_FLASH: "flash",
    ITEM_SIZE: "Size",
    ITEM_REG_START: "RegionStart",
    ITEM_REG_END: "RegionEnd",
    ITEM_FILE: "File",
    ITEM_FILE_NAME: "FileName",
}

MT_PRE_INFO_ATM33 = [
    {
        ITEM_TYPE: ITEM_NAME_DICT[ITEM_TYPE_SRAM],
        ITEM_SIZE: 131072,
        ITEM_REG_START: "0x20000000",
        ITEM_REG_END: "0x2001FFFF",
    },
    {
        ITEM_TYPE: ITEM_NAME_DICT[ITEM_TYPE_RRAM],
        ITEM_SIZE: 522240,
        ITEM_REG_START: "0x10000",
        ITEM_REG_END: "0x8F7FF",
    },
    {
        ITEM_TYPE: ITEM_NAME_DICT[ITEM_TYPE_FLASH],
        ITEM_SIZE: 524288,
        ITEM_REG_START: "0x200000",
        ITEM_REG_END: "0x27FFFF",
    },
]

MT_RFFW_INFO_ATM33 = [
    {
        ITEM_FILE_NAME: "bootloader.bin",
        ITEM_REG_START: "0x10000",
        ITEM_REG_END: "0x14FFF",
    },
    {
        ITEM_FILE_NAME: "HCI_vendor.bin",
        ITEM_REG_START: "0x15000",
        ITEM_REG_END: "0x8E7FF",
    },
    {
        ITEM_FILE_NAME: "flash_nvds.bin",
        ITEM_REG_START: "0x8E800",
        ITEM_REG_END: "0x8F7FF",
    },
]

MT_RFFW_INFO_ATM34 = [
    {
        ITEM_FILE_NAME: "bootloader.bin",
        ITEM_REG_START: "0x10000",
        ITEM_REG_END: "0x157FF",
    },
    {
        ITEM_FILE_NAME: "HCI_vendor.bin",
        ITEM_REG_START: "0x15800",
        ITEM_REG_END: "0x8E7FF",
    },
    {
        ITEM_FILE_NAME: "flash_nvds.bin",
        ITEM_REG_START: "0x8E800",
        ITEM_REG_END: "0x8F7FF",
    },
]

MT_PRE_INFO = {
    "atm33": {
        ITEM_INFO_PREPARE: MT_PRE_INFO_ATM33,
        ITEM_INFO_FW: MT_RFFW_INFO_ATM33,
    },
    "atm34": {
        ITEM_INFO_PREPARE: MT_PRE_INFO_ATM33,
        ITEM_INFO_FW: MT_RFFW_INFO_ATM34,
    },
}

# TODO clean unused boards
BOARD_NAME_DICT = {
    "ATMEVK_3330_QN": "ATM3330",
    "ATMEVK_3330e_QN": "ATM3330e",
    "ATMEVK_3325_QK": "ATM3325",
    "ATMEVK_3325_LQK": "ATM3325_StackFlash",
    "ATMEVK_3430e_WQN_2": "ATM3430",
    "ATMEVK_3430e_YQN_5": "ATM3430-5",
    "ATMEVK_3425_PQK_2": "ATM3425",
    "ATMEVK_3405_PQK_2": "ATM3405",
    "ATMEVK_3405_PQK_5": "ATM3405-5",
    "ATMEVK_3405_YBV_5": "ATM3405-5",
}

MT_ACTION_WRITE = 0
MT_ACTION_ERASE = 1
MT_ACTION_DICT = {MT_ACTION_WRITE: "write", MT_ACTION_ERASE: "erase"}


class ConfigHdlr:
    def __init__(self, config_file, board, debug=False):
        self.debug = debug
        self.config_file = config_file
        self.root = None
        self.config = None
        self.board = board

    def check(self):
        if not self.config_file:
            return (f"No Specified Config file, Use default settings", ST_PASS)
        if not os.path.exists(self.config_file):
            return (f"Cannot find {self.config_file}", ST_FAIL)
        try:
            with open(self.config_file, "rb") as in_file:
                root = ET.parse(in_file)
                self.root = root.getroot()
                return ("", ST_PASS)
        except ET.ParseError:
            return (f"Paring {self.config_file} Failed", ST_FAIL)

    def get_config(self, platform):
        if not self.root or not self.config_file:
            self.config = MT_PRE_INFO[platform]
            return ("", ST_PASS)
        # TODO can remove codes below, use default dict
        pre_config = []
        fw_config = []
        atm33_cfg = self.get_atrrib(self.root, ITEM_NAME_DICT[ITEM_PLARFORM], platform)
        if not atm33_cfg:
            return (f"Cannot Find {platform} in {self.config_file}", ST_FAIL)
        pre_info = self.get_atrrib(
            atm33_cfg, ITEM_NAME_DICT[ITEM_INFO], ITEM_NAME_DICT[ITEM_INFO_PREPARE]
        )
        fw_info = self.get_atrrib(
            atm33_cfg, ITEM_NAME_DICT[ITEM_INFO], ITEM_NAME_DICT[ITEM_INFO_FW]
        )
        if pre_info:
            info_list = self.get_atrribs(pre_info, ITEM_NAME_DICT[ITEM_TYPE])
            for info in info_list:
                type_name = info.get("name")
                size = int(self.get_value(info, ITEM_NAME_DICT[ITEM_SIZE]))
                reg_start = self.get_value(info, ITEM_NAME_DICT[ITEM_REG_START])
                reg_end = self.get_value(info, ITEM_NAME_DICT[ITEM_REG_END])
                pre_cfg = {
                    ITEM_TYPE: type_name,
                    ITEM_SIZE: size,
                    ITEM_REG_START: reg_start,
                    ITEM_REG_END: reg_end,
                }
                pre_config.append(pre_cfg)
        if fw_info:
            info_list = self.get_atrribs(fw_info, ITEM_NAME_DICT[ITEM_FILE])
            for info in info_list:
                filename = self.get_value(info, ITEM_NAME_DICT[ITEM_FILE_NAME])
                reg_start = self.get_value(info, ITEM_NAME_DICT[ITEM_REG_START])
                reg_end = self.get_value(info, ITEM_NAME_DICT[ITEM_REG_END])
                fw_cfg = {
                    ITEM_FILE_NAME: filename,
                    ITEM_REG_START: reg_start,
                    ITEM_REG_END: reg_end,
                }
                fw_config.append(fw_cfg)
        print("pre_config", pre_config)
        print("fw_config", fw_config)
        self.config = {
            ITEM_INFO_PREPARE: pre_config,
            ITEM_INFO_FW: fw_config,
        }
        return ("", ST_PASS)

    def get_atrribs(self, xml_obj, tag):
        if xml_obj:
            return xml_obj.iter(tag)
        return None

    def get_atrrib(self, xml_obj, tag, name):
        elems = self.get_atrribs(xml_obj, tag)
        for elem in elems:
            if elem.get("name") == name:
                return elem
        return None

    def get_value(self, attrib, name):
        return attrib.find(name).text


class MemTestFile:
    def __init__(self, config, board, debug=False):
        self.debug = debug
        self.config = config
        self.mt_folder = os.path.join(OPERATION_DIR, "mem_test")
        self.jlink_folder = os.path.join(OPERATION_DIR, "jlink")
        self.fw_folder = os.path.join(OPERATION_DIR, "fw_image")
        self.log_folder = os.path.join(self.mt_folder, "mt_logs")
        self.jlink_fw_file = "rftest_fw.jlink"
        self.jlink_reset_file = "reset.jlink"
        self.jlink_settingscript = "JLinkSettings.JLinkScript"
        self.jlink_pwdresetscript = "JLinkAssertPWD.JLinkScript"
        self.user_name = getpass.getuser()
        # flash loader path
        self.floader_path = os.path.join(os.getenv("APPDATA"), "SEGGER")
        self.board = board

    def dprint(self, msg):
        if self.debug:
            print(f"[MemTest]{msg}")

    def check(self):
        if not os.path.exists(self.mt_folder):
            os.mkdir(self.mt_folder)
        if not os.path.exists(self.log_folder):
            os.mkdir(self.log_folder)

        jllink_src_sub_path = "ATM33"
        if "_34" in self.board:
            jllink_src_sub_path = "ATM34"

        dst_setting_script = os.path.join(self.mt_folder, self.jlink_settingscript)
        if not os.path.exists(dst_setting_script):
            src_setting_script = os.path.join(
                self.jlink_folder, jllink_src_sub_path, self.jlink_settingscript
            )
            shutil.copyfile(src_setting_script, dst_setting_script)
            self.dprint(f"Copy {self.jlink_settingscript} Done")

        dst_pwd_script = os.path.join(self.mt_folder, self.jlink_pwdresetscript)
        if not os.path.exists(dst_pwd_script):
            src_pwd_script = os.path.join(
                self.jlink_folder, jllink_src_sub_path, self.jlink_pwdresetscript
            )
            shutil.copyfile(src_pwd_script, dst_pwd_script)
            self.dprint(f"Copy {self.jlink_pwdresetscript} Done")

        for info in self.config[ITEM_INFO_FW]:
            dst_fw_image = os.path.join(self.mt_folder, info[ITEM_FILE_NAME])
            if not os.path.exists(dst_fw_image):
                src_fw_image = os.path.join(self.fw_folder, info[ITEM_FILE_NAME])
                shutil.copyfile(src_fw_image, dst_fw_image)
                self.dprint(f"Copy {info[ITEM_FILE_NAME]} Done")

        return ("", ST_PASS)

    def gen_mttest_files(self):
        for info in self.config[ITEM_INFO_PREPARE]:
            mt_type = info[ITEM_TYPE]
            pad_size = info[ITEM_SIZE]
            reg_start = info[ITEM_REG_START]
            reg_end = info[ITEM_REG_END]
            for pad_type in range(PAD_TYPE_MAX):
                pad_filename = f"{mt_type}_pad_{PAD_TYPE_NAME_DICT[pad_type]}.bin"
                pad_filepath = os.path.join(self.mt_folder, pad_filename)
                if not os.path.exists(pad_filepath):
                    self.dprint(f"Create {pad_filepath}")
                    cnt_pad_size = pad_size
                    pad_value = PAD_TYPE_VALUE[pad_type]
                    with open(
                        pad_filepath,
                        "wb",
                    ) as f:
                        while cnt_pad_size > 0:
                            f.write(pad_value)
                            cnt_pad_size -= 1
                    self.dprint(f"Create {pad_filepath} Done")
                w_filepath = os.path.join(
                    self.mt_folder,
                    f"{mt_type}_{PAD_TYPE_NAME_DICT[pad_type]}_write.jlink",
                )
                if not os.path.exists(w_filepath):
                    write_cmd = f"loadbin {pad_filename},{reg_start}\n"
                    self.gen_jlink_cmd_file(w_filepath, write_cmd)
            e_filepath = os.path.join(self.mt_folder, f"{mt_type}_erase.jlink")
            erase_cmd = f"erase {reg_start},{reg_end}\n"
            if not os.path.exists(e_filepath):
                self.gen_jlink_cmd_file(e_filepath, erase_cmd)
        return ("", ST_PASS)

    def gen_fwtest_files(self):
        fw_filepath = os.path.join(self.mt_folder, self.jlink_fw_file)
        fw_cmd = ""
        for info in self.config[ITEM_INFO_FW]:
            tmp_cmd = f"erase {info[ITEM_REG_START]},{info[ITEM_REG_END]}\n"
            fw_cmd = f"{fw_cmd}{tmp_cmd}"
            tmp_cmd = f"loadbin {info[ITEM_FILE_NAME]},{info[ITEM_REG_START]}\n"
            fw_cmd = f"{fw_cmd}{tmp_cmd}"
        if not os.path.exists(fw_filepath):
            self.gen_jlink_cmd_file(fw_filepath, fw_cmd)
        reset_filepath = os.path.join(self.mt_folder, self.jlink_reset_file)
        if not os.path.exists(reset_filepath):
            reset_cmd = "connect\n"
            self.gen_jlink_cmd_file(reset_filepath, reset_cmd, hault_and_reset=False)
        return ("", ST_PASS)

    def gen_jlink_cmd_file(self, filename, jlink_cmd, hault_and_reset=True):
        jlink_cmds = []
        if hault_and_reset:
            jlink_cmds.append("r\n")
            jlink_cmds.append("h\n")
        jlink_cmds.append(jlink_cmd)
        if hault_and_reset:
            jlink_cmds.append("r\n")
        jlink_cmds.append("exit\n")
        self.dprint(f"Create {filename}")
        with open(filename, "w", encoding="utf-8", errors="ignore") as f:
            for jlink_cmd in jlink_cmds:
                f.write(jlink_cmd)
        self.dprint(f"Create {filename} Done")

    def clean_up(self):
        if os.path.exists(self.mt_folder):
            self.dprint(f"Remove {self.mt_folder}")
            shutil.rmtree(self.mt_folder, ignore_errors=True)

    def setup_jlink_env(self):
        jlink_segger_path = self.floader_path.format(user_name=self.user_name)
        if not os.path.exists(jlink_segger_path):
            return (
                f"J-Link path {jlink_segger_path} not exist, "
                "please install J-Link to your system.",
                ST_FAIL,
            )
        jlink_dev_path = os.path.join(jlink_segger_path, "JLinkDevices")
        if not os.path.exists(jlink_dev_path):
            self.dprint(f"Create {jlink_dev_path}")
            os.mkdir(jlink_dev_path)
        atm_floader_path = os.path.join(jlink_dev_path, "Atmosic")
        if not os.path.exists(atm_floader_path):
            self.dprint(f"Create {atm_floader_path}")
            os.mkdir(atm_floader_path)
        atm33_floader_path = os.path.join(atm_floader_path, "ATM33")
        if not os.path.exists(atm33_floader_path):
            self.dprint(f"Create {atm33_floader_path}")
            os.mkdir(atm33_floader_path)
        atm34_floader_path = os.path.join(atm_floader_path, "ATM34")
        if not os.path.exists(atm34_floader_path):
            self.dprint(f"Create {atm34_floader_path}")
            os.mkdir(atm34_floader_path)

        if os.path.exists(self.jlink_folder):
            loader_files = os.listdir(self.jlink_folder)
            loader_files.sort()
            for file in loader_files:
                file_path = os.path.join(self.jlink_folder, file)
                if "ATM33" in file and os.path.isfile(file):
                    sub_floader_path = os.path.join(atm33_floader_path, file)
                    if os.path.exists(sub_floader_path):
                        os.remove(sub_floader_path)
                        self.dprint(f"Remove old {sub_floader_path}")
                    shutil.copyfile(file_path, sub_floader_path)
                    self.dprint(f"Copy {sub_floader_path} Done")
                if "ATM34" in file and os.path.isfile(file):
                    sub_floader_path = os.path.join(atm34_floader_path, file)
                    if os.path.exists(sub_floader_path):
                        os.remove(sub_floader_path)
                        self.dprint(f"Remove old {sub_floader_path}")
                    shutil.copyfile(file_path, sub_floader_path)
                    self.dprint(f"Copy {sub_floader_path} Done")
            return ("", ST_PASS)
        else:
            return ("Setup jlink env failed", ST_FAIL)


class MemTest:
    def __init__(self, board, mt_type, pad_type, jlink_sn, debug):
        self.board = board
        self.mt_type = mt_type
        self.jlink_sn = jlink_sn
        if pad_type:
            self.pad_file = f"{self.mt_type}_pad_{pad_type}.bin"
            self.jlink_pad_write_file = f"{self.mt_type}_{pad_type}_write.jlink"
        else:
            self.pad_file = None
            self.jlink_pad_write_file = None
        self.jlink_erase_file = f"{self.mt_type}_erase.jlink"
        self.jlink_settingscript = "JLinkSettings.JLinkScript"
        self.jlink_pwdresetscript = "JLinkAssertPWD.JLinkScript"
        self.mt_folder = os.path.join(OPERATION_DIR, "mem_test")
        self.log_folder = os.path.join(self.mt_folder, "mt_logs")
        self.debug = debug

    def dprint(self, msg):
        if self.debug:
            print(f"[MemTest]{msg}")

    def check(self):
        if self.pad_file:
            pad_file_path = os.path.join(self.mt_folder, self.pad_file)
            if not os.path.exists(pad_file_path):
                return (f"Cannot find {pad_file_path}", ST_FAIL)
        if self.jlink_pad_write_file:
            jlink_pad_write_file_path = os.path.join(
                self.mt_folder, self.jlink_pad_write_file
            )
            if not os.path.exists(jlink_pad_write_file_path):
                return (f"Cannot find {jlink_pad_write_file_path}", ST_FAIL)
        jlink_settingscript_path = os.path.join(
            self.mt_folder, self.jlink_settingscript
        )
        if not os.path.exists(jlink_settingscript_path):
            return (f"Cannot find {jlink_settingscript_path}", ST_FAIL)
        jlink_pwdresetscript_path = os.path.join(
            self.mt_folder, self.jlink_pwdresetscript
        )
        if not os.path.exists(jlink_pwdresetscript_path):
            return (f"Cannot find {jlink_pwdresetscript_path}", ST_FAIL)
        if not os.path.exists(self.log_folder):
            return (f"Cannot find {self.log_folder}", ST_FAIL)
        self.jlink_dev = self.get_jlink_dev()
        return ("", ST_PASS)

    def run_test(self, action):
        os.chdir(self.mt_folder)
        if action == MT_ACTION_ERASE and ITEM_DICT[self.mt_type] == ITEM_TYPE_SRAM:
            return ("Erase for Sram Test Skip", ST_PASS)
        logfile = os.path.join(
            self.log_folder, f"{self.mt_type}_{MT_ACTION_DICT[action]}.log"
        )
        jlink_dev = self.get_jlink_dev()
        if action == MT_ACTION_WRITE:
            jlink_file = self.jlink_pad_write_file
        else:
            jlink_file = self.jlink_erase_file
        jlinkprog = JLinkProg(
            jlink_dev,
            self.jlink_sn,
            jlink_file,
            self.jlink_settingscript,
            logfile,
            self.debug,
        )

        if action == MT_ACTION_WRITE:
            (msg, ret) = jlinkprog.jlink_run(True)
        else:
            (msg, ret) = jlinkprog.jlink_run(False)
        if ret != ST_PASS:
            return (f"Test {MT_ACTION_DICT[action]} failed at jlink_run {msg}", ret)
        return (msg, ret)

    def get_jlink_dev(self):
        return BOARD_NAME_DICT[self.board]

    def clean_logs(self):
        if os.path.exists(self.log_folder):
            shutil.rmtree(self.log_folder, ignore_errors=True)


class FwTest:
    def __init__(self, board, jlink_sn, debug):
        self.board = board
        self.jlink_sn = jlink_sn
        self.jlink_fw_file = f"rftest_fw.jlink"
        self.jlink_reset_file = f"reset.jlink"
        self.jlink_settingscript = "JLinkSettings.JLinkScript"
        self.jlink_pwdresetscript = "JLinkAssertPWD.JLinkScript"
        self.mt_folder = os.path.join(OPERATION_DIR, "mem_test")
        self.log_folder = os.path.join(self.mt_folder, "mt_logs")
        self.debug = debug

    def dprint(self, msg):
        if self.debug:
            print(f"[MemTest]{msg}")

    def check(self):
        if self.jlink_fw_file:
            jlink_fw_file_path = os.path.join(self.mt_folder, self.jlink_fw_file)
            if not os.path.exists(jlink_fw_file_path):
                return (f"Cannot find {jlink_fw_file_path}", ST_FAIL)
        if self.jlink_reset_file:
            jlink_reset_file_path = os.path.join(self.mt_folder, self.jlink_reset_file)
            if not os.path.exists(jlink_reset_file_path):
                return (f"Cannot find {jlink_reset_file_path}", ST_FAIL)
        jlink_settingscript_path = os.path.join(
            self.mt_folder, self.jlink_settingscript
        )
        if not os.path.exists(jlink_settingscript_path):
            return (f"Cannot find {jlink_settingscript_path}", ST_FAIL)
        jlink_pwdresetscript_path = os.path.join(
            self.mt_folder, self.jlink_pwdresetscript
        )
        if not os.path.exists(jlink_pwdresetscript_path):
            return (f"Cannot find {jlink_pwdresetscript_path}", ST_FAIL)
        if not os.path.exists(self.log_folder):
            return (f"Cannot find {self.log_folder}", ST_FAIL)
        self.jlink_dev = self.get_jlink_dev()
        return ("", ST_PASS)

    def run_test(self):
        os.chdir(self.mt_folder)
        logfile = os.path.join(self.log_folder, f"rftest_fw.log")
        jlink_dev = self.get_jlink_dev()
        jlinkprog = JLinkProg(
            jlink_dev,
            self.jlink_sn,
            self.jlink_fw_file,
            self.jlink_settingscript,
            logfile,
            self.debug,
        )
        (msg, ret) = jlinkprog.jlink_run(True)
        if ret != ST_PASS:
            return (f"Test rftest_fw failed at jlink_run {msg}", ret)
        return (msg, ret)

    def run_reset(self):
        os.chdir(self.mt_folder)
        logfile = os.path.join(self.log_folder, f"reset.log")
        jlink_dev = self.get_jlink_dev()
        jlinkprog = JLinkProg(
            jlink_dev,
            self.jlink_sn,
            self.jlink_reset_file,
            self.jlink_pwdresetscript,
            logfile,
            self.debug,
        )
        (msg, ret) = jlinkprog.jlink_run(False)
        if ret != ST_PASS:
            return (f"Test reset failed at jlink_run {msg}", ret)
        return (msg, ret)

    def get_jlink_dev(self):
        return BOARD_NAME_DICT[self.board]

    def clean_logs(self):
        if os.path.exists(self.log_folder):
            shutil.rmtree(self.log_folder, ignore_errors=True)


class JLinkProg:
    def __init__(
        self, jlink_dev, jlink_sn, jlink_file, jlink_settingscript, logfile, debug
    ):
        self.jlink_dev = jlink_dev
        self.jlink_sn = jlink_sn
        self.jlink_file = jlink_file
        self.logfile = logfile
        self.jlink_settingscript = jlink_settingscript
        self.program_info = []
        self.debug = debug
        self.jlink_exe_cmd = (
            "JLink.exe -device {jlink_dev} -if SWD {jlink_usb}"
            "-speed 4000 -CommandFile {jlink_file} "
            '-jlinkscriptfile {jlink_script} > "{logfile}"'
        )
        # Setup jlink environment in windows
        if sys.platform == "win32":
            jlink_paths = []
            possible_path = os.path.join(os.getenv("PROGRAMFILES"), "SEGGER")
            segger_x86_path = os.path.join(
                os.getenv("PROGRAMFILES(X86)"), "SEGGER", "JLink"
            )
            for dir_name in os.listdir(possible_path):
                if "JLink" in dir_name:
                    jlink_paths.append(os.path.join(possible_path, dir_name))
            if os.path.exists(segger_x86_path):
                jlink_paths.append(segger_x86_path)
            for jlink_path in jlink_paths[::-1]:
                path = os.environ["PATH"]
                if jlink_path not in path:
                    os.environ["PATH"] = f"{path};{jlink_path}"

    def dprint(self, msg):
        if self.debug:
            print(f"[JLinkProg]{msg}")

    def jlink_run(self, parse_program_info=True):
        jlink_usb = ""
        if self.jlink_sn:
            jlink_usb = f"-USB {self.jlink_sn} "
        sys_cmd = self.jlink_exe_cmd.format(
            jlink_dev=self.jlink_dev,
            jlink_usb=jlink_usb,
            jlink_file=self.jlink_file,
            jlink_script=self.jlink_settingscript,
            logfile=self.logfile,
        )
        self.exec_cmd(sys_cmd)
        (msg, ret) = self.verify_file(self.logfile)
        if ret != ST_PASS:
            return (f"JLinkProg failed at verify {msg}", ret)
        if parse_program_info:
            (msg, ret) = self.parse_program_info()
            return (msg, ret)
        else:
            return ("", ST_PASS)

    def verify_file(self, filepath):
        err = 0
        err_msg = ""
        with open(filepath, "r+", encoding="utf-8", errors="ignore") as f:
            temp = f.readlines()
            for line in temp:
                line_str = line.strip()
                self.dprint(line_str)
                err = self.check_err_logs(line_str)
                if err:
                    err_msg = line_str
                    break

        return (err_msg, err) if err else ("", ST_PASS)

    def check_program_logs(self, line):
        patternslist = [
            "Downloading file:",
            "J-Link: Flash download:",
            "Skipped. Contents already match",
        ]
        for pattern in patternslist:
            if pattern in line:
                return True

    def check_err_logs(self, line):
        false_error_pattern = ["Skipped. Contents already match"]

        for fpattern in false_error_pattern:
            if fpattern in line:
                print(f"[{line}] matched false error [{fpattern}]")
                return ST_FALSE_ERROR

        fatal_pattern = [
            "Error: Failed to verify @ address",
            "Error while programming flash: Verify failed.",
        ]

        for fpattern in fatal_pattern:
            if fpattern in line:
                print(f"[{line}] matched fatal error [{fpattern}]")
                return ST_FATAL_ERROR

        patternslist = [
            "error:",
            "錯誤 1",
            "錯誤 2",
            "returned 1 exit status",
            "Error",
            "returned 1 exit status",
            "Failure",
            "ERROR",
            "can't find",
            "Errors:",
            "make: command not found",
            "make: *** No targets",
            "USB...FAILED",
            "error -1",
            "Failed to open",
            "Failed to attach",
        ]

        for elc in patternslist:
            if elc in line:
                if " 0" not in line and "none" not in line:
                    return ST_FAIL

    def parse_program_info(self, parse_info=False):
        bank_info = None
        size_info = None
        total_info = None
        prepare_info = None
        compare_info = None
        speed_info = None
        erase_info = None
        program_info = None
        verify_info = None
        ret_msg = ""
        for line in self.program_info:
            info = line.strip()
            if "Skipped. Contents already match" in info:
                return ("Skipped. Contents already match", ST_FAIL)
            if "J-Link: Flash download: Bank" in info:
                self.dprint(info)
                ret_msg = f"{ret_msg} {info}\n"
                if not parse_info:
                    continue
                tmpbank = info.split(": ")
                bank_info = tmpbank[2]
                tmpsize = tmpbank[3].split(" (")[1]
                size_info = tmpsize[0 : (len(tmpsize) - 1)]
            if "J-Link: Flash download: Total:" in info:
                self.dprint(info)
                ret_msg = f"{ret_msg} {info}\n"
                if not parse_info:
                    continue
                tmptotal = info.split(": ")
                total_info = tmptotal[3].split(" ")[0]
                tmpprogram = info.split(" (")[1]
                tmpprogram = info.split(" (")[1].split(",")
                if len(tmpprogram[0].split("Prepare: ")):
                    prepare_info = tmpprogram[0].split("Prepare: ")[1]
                if len(tmpprogram[1].split("Compare: ")[1]):
                    compare_info = tmpprogram[1].split("Compare: ")[1]
                if len(tmpprogram[2].split("Erase: ")[1]):
                    erase_info = tmpprogram[2].split("Erase: ")[1]
                if len(tmpprogram[3].split("Program: ")[1]):
                    program_info = tmpprogram[3].split("Program: ")[1]
                if len(tmpprogram[4].split("Verify: ")[1]):
                    verify_info = tmpprogram[4].split("Verify: ")[1]
            if "J-Link: Flash download: Program speed:" in info:
                self.dprint(info)
                ret_msg = f"{ret_msg} {info}\n"
                if not parse_info:
                    continue
                speed_info = info.split(": ")[3]
        return (ret_msg, ST_PASS)

    def exec_cmd(self, cmd):
        try:
            if self.debug:
                print(f"Execute [{cmd}]")
            exitcode = os.system(cmd)
            if exitcode != ST_PASS:
                return (f"[{cmd}] Error ({exitcode})", exitcode)
            return ("", exitcode)
        except BaseException:
            return (f"{cmd} exception", ST_FAIL)


def parse_args(args=None, namespace=None):
    base_parser = argparse.ArgumentParser(add_help=False)
    base_parser.add_argument(
        "-d", "--debug", action="store_true", help="debug default false"
    )
    parser = argparse.ArgumentParser(description="Atmosic RFTool for Memmory Test")
    subparsers = parser.add_subparsers(dest="opcode")
    prepare_parser = subparsers.add_parser(
        "prepare", parents=[base_parser], help="Prepare memory test files"
    )
    prepare_parser.add_argument(
        "-c", "--config", type=str, help="config file in xml format"
    )
    prepare_parser.add_argument(
        "platform", type=str, help="Platform, value value: [atm2, atm3, atm33, atm34]"
    )
    prepare_parser.add_argument("board", type=str, help="EVK board names")
    test_parser = subparsers.add_parser(
        "test", parents=[base_parser], help="memory test"
    )
    test_parser.add_argument("board", type=str, help="ATMEVK board name")
    test_parser.add_argument(
        "type", type=str, help="test memory type, valid value: [sram, rram, flash]"
    )
    test_parser.add_argument(
        "pad", type=str, help="test memory image pad type, valid value: [0x55, 0xaa]"
    )
    test_parser.add_argument(
        "-jlink_sn", "--jlink_sn", type=str, help="optional, jLink serial number"
    )
    erase_parser = subparsers.add_parser(
        "erase", parents=[base_parser], help="memory erase"
    )
    erase_parser.add_argument("board", type=str, help="ATMEVK board name")
    erase_parser.add_argument(
        "type", type=str, help="test memory type, valid value: [sram, rram, flash]"
    )
    erase_parser.add_argument(
        "-jlink_sn", "--jlink_sn", type=str, help="optional,  jLink serial number"
    )
    fwtest_parser = subparsers.add_parser(
        "fwtest", parents=[base_parser], help="program rftool test fw"
    )
    fwtest_parser.add_argument("board", type=str, help="ATMEVK board name")
    fwtest_parser.add_argument(
        "-jlink_sn", "--jlink_sn", type=str, help="optional, jLink serial number"
    )
    reset_parser = subparsers.add_parser(
        "reset", parents=[base_parser], help="perform PWD reset"
    )
    reset_parser.add_argument("board", type=str, help="ATMEVK board name")
    reset_parser.add_argument(
        "-jlink_sn", "--jlink_sn", type=str, help="optional, jLink serial number"
    )
    clean_parser = subparsers.add_parser(
        "cleanup", parents=[base_parser], help="cleanup prepared memory test files"
    )
    return parser.parse_args(args, namespace)


def safe_exit(msg, exit_code):
    print(f"exit_code: {exit_code}")
    if len(msg):
        print(f"msg: {msg}")
    sys.exit(exit_code)


def handle_cleanup(args):
    mt_folder = os.path.join(OPERATION_DIR, "mem_test")
    if os.path.exists(mt_folder):
        shutil.rmtree(mt_folder, ignore_errors=True)
    safe_exit("", ST_PASS)


def handle_test(args):
    jlink_sn = None
    if args.jlink_sn:
        jlink_sn = args.jlink_sn
    mt = MemTest(args.board, args.type, args.pad, jlink_sn, args.debug)
    (msg, ret) = mt.check()
    if ret != ST_PASS:
        safe_exit(msg, ret)
    (msg, ret) = mt.run_test(MT_ACTION_WRITE)
    safe_exit(msg, ret)


def handle_erase(args):
    jlink_sn = None
    if args.jlink_sn:
        jlink_sn = args.jlink_sn
    mt = MemTest(args.board, args.type, None, jlink_sn, args.debug)
    (msg, ret) = mt.check()
    if ret != ST_PASS:
        safe_exit(msg, ret)
    (msg, ret) = mt.run_test(MT_ACTION_ERASE)
    safe_exit(msg, ret)


def handle_fwtest(args):
    jlink_sn = None
    if args.jlink_sn:
        jlink_sn = args.jlink_sn
    fwtest = FwTest(args.board, jlink_sn, args.debug)
    (msg, ret) = fwtest.check()
    if ret != ST_PASS:
        safe_exit(msg, ret)
    (msg, ret) = fwtest.run_test()
    safe_exit(msg, ret)


def handle_reset(args):
    jlink_sn = None
    if args.jlink_sn:
        jlink_sn = args.jlink_sn
    fwtest = FwTest(args.board, jlink_sn, args.debug)
    (msg, ret) = fwtest.check()
    if ret != ST_PASS:
        safe_exit(msg, ret)
    (msg, ret) = fwtest.run_reset()
    safe_exit(msg, ret)


def handle_prepare(args):
    config_file = None
    if args.config:
        config_file = args.config
    cfghdlr = ConfigHdlr(config_file, args.debug)
    (msg, ret) = cfghdlr.check()
    if ret != ST_PASS:
        safe_exit(msg, ret)
    (msg, ret) = cfghdlr.get_config(args.platform)
    if ret != ST_PASS:
        return safe_exit(msg, ret)
    mt_file = MemTestFile(cfghdlr.config, args.board, args.debug)
    (msg, ret) = mt_file.check()
    if ret != ST_PASS:
        return safe_exit(msg, ST_FAIL)
    (msg, ret) = mt_file.gen_mttest_files()
    if ret != ST_PASS:
        return safe_exit(msg, ret)
    (msg, ret) = mt_file.gen_fwtest_files()
    if ret != ST_PASS:
        return safe_exit(msg, ST_FAIL)
    (msg, ret) = mt_file.setup_jlink_env()
    safe_exit(msg, ret)


def handle_unknown_opcode(args):
    print("Unkown opcode")
    safe_exit("Unkown opcode", ST_FAIL)


def handle_opcode(opcode, args):
    opSwitcher = {
        "prepare": handle_prepare,
        "test": handle_test,
        "erase": handle_erase,
        "fwtest": handle_fwtest,
        "reset": handle_reset,
        "cleanup": handle_cleanup,
    }
    return opSwitcher.get(opcode, handle_unknown_opcode)(args)


def main(args=None, namespace=None):
    args = parse_args(args, namespace)
    return handle_opcode(args.opcode, args)


if __name__ == "__main__":
    if sys.version_info[0] < 3:
        safe_exit(f"Requires Python 3", 2)
    main()
