#
# Copyright (C) 2023-2024 Siemens AG
#
# SPDX-License-Identifier: GPL-2.0-only
#
"""Devtool ide-sdk generic IDE plugin"""

import os
import logging
import stat
from bb.utils import mkdirhier
from devtool.ide_plugins import IdeBase, GdbCrossConfig, GdbServerModes

logger = logging.getLogger('devtool')


class GdbCrossConfigNone(GdbCrossConfig):
    def __init__(self, image_recipe, modified_recipe, binary,
                 gdbserver_default_mode=GdbServerModes.MULTI):
        super().__init__(image_recipe, modified_recipe, binary,
                         gdbserver_default_mode)

    def _target_gdbserver_stop_cmd(self, gdbserver_mode):
        """Kill a gdbserver process"""
        # This is the usual behavior: gdbserver is stopped on demand
        if gdbserver_mode == GdbServerModes.MULTI:
            gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % (
                self.gdbserver_pid_file(gdbserver_mode), self.gdbserver_pid_file(gdbserver_mode))
            gdbserver_cmd_stop += " rm -rf %s" % self.gdbserver_tmp_dir(gdbserver_mode)
        # This is unexpected since gdbserver should terminate after each debug session
        # Just kill all gdbserver instances to keep it simple
        else:
            gdbserver_cmd_stop = "killall gdbserver"
        return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\""

    def _gen_gdbserver_start_script(self, gdbserver_mode=None):
        """Generate a shell script starting the gdbserver on the remote device via ssh"""
        if gdbserver_mode is None:
            gdbserver_mode = self.gdbserver_default_mode
        gdbserver_cmd_start = self._target_gdbserver_start_cmd(gdbserver_mode)
        gdbserver_cmd_stop = self._target_gdbserver_stop_cmd(gdbserver_mode)
        remote_ssh = "%s %s" % (self.gdb_cross.target_device.ssh_sshexec,
                                " ".join(self._target_ssh_gdbserver_args()))
        gdbserver_cmd = ['#!/bin/sh']
        gdbserver_cmd.append('if [ "$1" = "stop" ]; then')
        gdbserver_cmd.append('  shift')
        gdbserver_cmd.append("  %s %s" % (remote_ssh, gdbserver_cmd_stop))
        gdbserver_cmd.append('else')
        gdbserver_cmd.append("  %s %s" % (remote_ssh, gdbserver_cmd_start))
        gdbserver_cmd.append('fi')
        GdbCrossConfigNone.write_file(self.gdbserver_script(gdbserver_mode), gdbserver_cmd, True)

    def _gen_gdbinit_config(self, gdbserver_mode=None):
        """Generate a gdbinit file for this binary and the corresponding gdbserver configuration"""
        if gdbserver_mode is None:
            gdbserver_mode = self.gdbserver_default_mode
        gdbinit_lines = ['# This file is generated by devtool ide-sdk']
        if gdbserver_mode == GdbServerModes.MULTI:
            target_help = '#   gdbserver --multi :%d' % self.gdbserver_port
            remote_cmd = 'target extended-remote'
        else:
            target_help = '#   gdbserver :%d %s' % (
                self.gdbserver_port, self.binary)
            remote_cmd = 'target remote'
        gdbinit_lines.append('# On the remote target:')
        gdbinit_lines.append(target_help)
        gdbinit_lines.append('# On the build machine:')
        gdbinit_lines.append('#   cd ' + self.modified_recipe.real_srctree)
        gdbinit_lines.append(
            '#   ' + self.gdb_cross.gdb + ' -ix ' + self.gdbinit)
        gdbinit_lines.append('set sysroot ' + self.modified_recipe.d)

        if self.image_recipe.rootfs_dbg:
            gdbinit_lines.append(
                'set solib-search-path "' + self.modified_recipe.solib_search_path_str(self.image_recipe) + '"')

        gdbinit_lines.append('set substitute-path "/usr/include" "' +
                             os.path.join(self.modified_recipe.recipe_sysroot, 'usr', 'include') + '"')
        if self.image_recipe.rootfs_dbg:
            # First: Search for sources of this recipe in the workspace folder
            # If compiled with DEBUG_PREFIX_MAP = "", no reverse map is is needed. The binaries
            # contain the full path to the source files. But by default there is a reverse map.
            src_file_map = dict(self.modified_recipe.reverse_debug_prefix_map)

            # Second: Search for sources of other recipes in the rootfs-dbg
            # We expect that /usr/src/debug/${PN}/${PV} or no mapping is used for the recipes
            # own sources and we can use the key "/usr/src/debug" for the rootfs-dbg.
            if "/usr/src/debug" in src_file_map:
                logger.error(
                    'Key "/usr/src/debug" already exists in src_file_map. '
                    'Something with DEBUG_PREFIX_MAP looks unexpected and finding sources '
                    'in the rootfs-dbg will not work as expected.'
                )
            else:
                src_file_map["/usr/src/debug"] = os.path.join(
                    self.image_recipe.rootfs_dbg, "usr", "src", "debug")

            for target_path, host_path in src_file_map.items():
                gdbinit_lines.append('set substitute-path "%s" "%s"' % (target_path, host_path))
        else:
            logger.warning(
                "Cannot setup debug symbols configuration for GDB. IMAGE_GEN_DEBUGFS is not enabled.")
        # Disable debuginfod for now, the IDE configuration uses rootfs-dbg from the image workdir.
        gdbinit_lines.append('set debuginfod enabled off')

        # Enable pretty-printing for gdb for resolving STL types with help of python scripts
        pretty_printing_cmd = self.modified_recipe.gdb_pretty_print_scripts
        if pretty_printing_cmd:
            gdbinit_lines.append(os.linesep +"python")
            gdbinit_lines += pretty_printing_cmd
            gdbinit_lines.append("end" + os.linesep)

        gdbinit_lines.append(
            '%s %s:%d' % (remote_cmd, self.gdb_cross.host, self.gdbserver_port))
        gdbinit_lines.append('set remote exec-file ' + self.binary.binary_path)
        gdbinit_lines.append('run ' + self.binary.binary_path)

        GdbCrossConfigNone.write_file(self.gdbinit, gdbinit_lines)

    def _gen_gdb_start_script(self):
        """Generate a script starting GDB with the corresponding gdbinit configuration."""
        cmd_lines = ['#!/bin/sh']
        cmd_lines.append('cd ' + self.modified_recipe.real_srctree)
        cmd_lines.append(self.gdb_cross.gdb + ' -ix ' +
                         self.gdbinit + ' "$@"')
        GdbCrossConfigNone.write_file(self.gdb_script, cmd_lines, True)

    def initialize(self):
        self._gen_gdbserver_start_script()
        if self.binary.runs_as_service and self.gdbserver_default_mode != GdbServerModes.ATTACH:
            self._gen_gdbserver_start_script(GdbServerModes.ATTACH)
        self._gen_gdbinit_config()
        self._gen_gdb_start_script()

    @staticmethod
    def write_file(script_file, cmd_lines, executable=False):
        script_dir = os.path.dirname(script_file)
        mkdirhier(script_dir)
        with open(script_file, 'w') as script_f:
            script_f.write(os.linesep.join(cmd_lines))
            script_f.write(os.linesep)
        if executable:
            st = os.stat(script_file)
            os.chmod(script_file, st.st_mode | stat.S_IEXEC)
        logger.info("Created: %s" % script_file)


class IdeNone(IdeBase):
    """Generate some generic helpers for other IDEs

    Modified recipe mode:
    Generate some helper scripts for remote debugging with GDB

    Shared sysroot mode:
    A wrapper for bitbake meta-ide-support and bitbake build-sysroots
    """

    def __init__(self):
        super().__init__()

    def setup_shared_sysroots(self, shared_env):
        real_multimach_target_sys = shared_env.ide_support.real_multimach_target_sys
        deploy_dir_image = shared_env.ide_support.deploy_dir_image
        env_script = os.path.join(
            deploy_dir_image, 'environment-setup-' + real_multimach_target_sys)
        logger.info(
            "To use this SDK please source this: %s" % env_script)

    def setup_modified_recipe(self, args, image_recipe, modified_recipe):
        """generate some helper scripts and config files

        - Execute the do_install task
        - Execute devtool deploy-target
        - Generate a gdbinit file per executable
        - Generate the oe-scripts sym-link
        """
        script_path = modified_recipe.gen_install_deploy_script(args)
        logger.info("Created: %s" % script_path)

        self.initialize_gdb_cross_configs(
            image_recipe, modified_recipe, GdbCrossConfigNone)

        IdeBase.gen_oe_scripts_sym_link(modified_recipe)


def register_ide_plugin(ide_plugins):
    ide_plugins['none'] = IdeNone
