###########################################################################
# tools/pynuttx/nxgdb/target.py
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################

import multiprocessing
import socket

import gdb
import nxstub
from nxreg.register import get_arch_name

from . import autocompeletion, utils


def get_unused_port():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("", 0))
        return s.getsockname()[1]


def is_port_in_use(port: int) -> bool:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        try:
            s.bind(("127.0.0.1", port))
        except OSError:
            return True
        return False


@autocompeletion.complete
class Target(gdb.Command):
    """Use nxstub to parse crash log dump, core dump or memory dump, as target."""

    parser = nxstub.get_argparser()

    def __init__(self):
        super().__init__("target stub", gdb.COMMAND_USER)
        utils.alias("target nxstub", "target stub")
        self.process = None

    @utils.dont_repeat_decorator
    def invoke(self, args, from_tty):
        self.dont_repeat()

        if "-e" not in args and "--elf" not in args:
            args += f" -e {gdb.objfiles()[0].filename}"

        arch = get_arch_name()  # Convert to nxstub arch name

        if "-a" not in args and "--arch" not in args:
            args += f" -a {arch}"
        else:
            print(f"Hint: no need to specify architecture, current arch: {arch}")

        args = gdb.string_to_argv(args)
        try:
            parsed = nxstub.parse_args(args)
        except SystemExit:
            return

        if parsed.arch != arch:
            print(f"Warning: current arch {arch} does not match nxstub {parsed.arch}")

        if is_port_in_use(parsed.port):
            print(f"Port {parsed.port} is already in use, try to use another port.")
            parsed.port = get_unused_port()
            print(f"Use port {parsed.port} instead.")

        if parsed.timeout:
            gdb.execute(f"set tcp connect-timeout {parsed.timeout}", from_tty=True)
            print(f"Set GDB timeout: {parsed.timeout}")

        # If currently has connection to target, disconnect it
        if utils.check_inferior_valid():
            gdb.execute("detach", from_tty=True)

        def kill(event=None):
            if self.process:
                self.process.kill()
                self.process.join()
                self.process = None
                print("nxstub process killed")

        if self.process:
            y = input("nxstub process already running, kill it? [y/n] ")
            if y.lower() != "y":
                return

            kill()

        def stub_main(args):
            import signal

            # Ignore the Ctrl+C signal
            signal.signal(signal.SIGINT, signal.SIG_IGN)
            nxstub.main(args)

        process = multiprocessing.Process(target=stub_main, args=(parsed,))
        process.start()
        self.process = process

        # Check if the process exits immediately
        process.join(timeout=1)
        if not process.is_alive():
            self.process = None
            return

        gdb.events.gdb_exiting.connect(kill)
        print("")

        # Wait server to start
        gdb.execute(f"target remote :{parsed.port}", from_tty=True)
