#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
import sys
from textwrap import indent

IDF_PATH = os.getenv('IDF_PATH', '')
ROMS_JSON = os.path.join(IDF_PATH, 'tools', 'idf_py_actions', 'roms.json')  # type: ignore


# Direct string extraction and comparison is not feasible due to:
# 1. ROM ELF binaries for esp32XX chips are little-endian. The byte order in
#    the binary may differ from the system's endianness. We must ensure the
#    bytes are read correctly for the system where the script runs.
# 2. GDB lacks built-in string comparison functionality. To work around this,
#    strings are converted to numeric values for comparison.
def get_rom_if_condition_str(date_addr: int, date_str: str) -> str:
    r = []
    for i in range(0, len(date_str), 4):
        value = hex(int.from_bytes(bytes(date_str[i:i + 4], 'utf-8'), 'little'))
        r.append(f'(*(int*) {hex(date_addr + i)}) == {value}')
    return 'if ' + ' && '.join(r)


def generate_gdbinit_rom_add_symbols(target: str) -> str:
    base_ident = '  '
    rom_elfs_dir = os.getenv('ESP_ROM_ELF_DIR')
    if not rom_elfs_dir:
        raise EnvironmentError(
            'ESP_ROM_ELF_DIR environment variable is not defined. Please try to run IDF "install" and "export" scripts.')
    if os.name == 'nt':
        # convert to posix-path for windows
        rom_elfs_dir = rom_elfs_dir.replace('\\', '/')
    with open(ROMS_JSON, 'r') as f:
        roms = json.load(f)
    if target not in roms:
        msg_body = f'Warning: ROM ELF is not supported yet for "{target}".'  # noqa: E713
        return f'echo {msg_body}\\n\n'
    r = ['', f'# Load {target} ROM ELF symbols']
    r.append('define target hookpost-remote')
    r.append('set confirm off')
    # Since GDB does not have 'else if' statement than we use nested 'if..else' instead.
    for i, k in enumerate(roms[target], 1):
        indent_str = base_ident * i
        rom_file = f'{target}_rev{k["rev"]}_rom.elf'
        build_date_addr = int(k['build_date_str_addr'], base=16)
        r.append(indent(f'# if $_streq((char *) {hex(build_date_addr)}, "{k["build_date_str"]}")', indent_str))
        r.append(indent(get_rom_if_condition_str(build_date_addr, k['build_date_str']), indent_str))
        r.append(indent(f'add-symbol-file {rom_elfs_dir}{rom_file}', indent_str + base_ident))
        r.append(indent('else', indent_str))
        if i == len(roms[target]):
            # In case no one known ROM ELF fits - print warning
            indent_str += base_ident
            msg_body = f'Warning: Unknown {target} ROM revision.'
            r.append(indent(f'echo {msg_body}\\n', indent_str))
    # Close 'else' operators
    for i in range(len(roms[target]), 0, -1):
        r.append(indent('end', base_ident * i))
    r.append('set confirm on')
    r.append('end')
    return '\n'.join(r)[1:]


if __name__ == '__main__':
    if len(sys.argv) != 2:
        raise ValueError('Please pass only one argument (target).')

    target = sys.argv[1]
    gdbinit_lines = generate_gdbinit_rom_add_symbols(target)
    print(gdbinit_lines)