"""
Test lldb-dap runInTerminal reverse request
"""


import dap_server
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbdap_testcase
import time
import os
import subprocess
import shutil
import json
from threading import Thread


class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase):
    def readPidMessage(self, fifo_file):
        with open(fifo_file, "r") as file:
            self.assertIn("pid", file.readline())

    def sendDidAttachMessage(self, fifo_file):
        with open(fifo_file, "w") as file:
            file.write(json.dumps({"kind": "didAttach"}) + "\n")

    def readErrorMessage(self, fifo_file):
        with open(fifo_file, "r") as file:
            return file.readline()

    def isTestSupported(self):
        # For some strange reason, this test fails on python3.6
        if not (sys.version_info.major == 3 and sys.version_info.minor >= 7):
            return False
        try:
            # We skip this test for debug builds because it takes too long parsing lldb's own
            # debug info. Release builds are fine.
            # Checking the size of the lldb-dap binary seems to be a decent proxy for a quick
            # detection. It should be far less than 1 MB in Release builds.
            if os.path.getsize(os.environ["LLDBDAP_EXEC"]) < 1000000:
                return True
        except:
            return False

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_runInTerminal(self):
        if not self.isTestSupported():
            return
        """
            Tests the "runInTerminal" reverse request. It makes sure that the IDE can
            launch the inferior with the correct environment variables and arguments.
        """
        program = self.getBuildArtifact("a.out")
        source = "main.c"
        self.build_and_launch(
            program, runInTerminal=True, args=["foobar"], env=["FOO=bar"]
        )

        self.assertEqual(
            len(self.dap_server.reverse_requests),
            1,
            "make sure we got a reverse request",
        )

        request = self.dap_server.reverse_requests[0]
        self.assertIn(self.lldbDAPExec, request["arguments"]["args"])
        self.assertIn(program, request["arguments"]["args"])
        self.assertIn("foobar", request["arguments"]["args"])
        self.assertIn("FOO", request["arguments"]["env"])

        breakpoint_line = line_number(source, "// breakpoint")

        self.set_source_breakpoints(source, [breakpoint_line])
        self.continue_to_next_stop()

        # We verify we actually stopped inside the loop
        counter = int(self.dap_server.get_local_variable_value("counter"))
        self.assertGreater(counter, 0)

        # We verify we were able to set the launch arguments
        argc = int(self.dap_server.get_local_variable_value("argc"))
        self.assertEqual(argc, 2)

        argv1 = self.dap_server.request_evaluate("argv[1]")["body"]["result"]
        self.assertIn("foobar", argv1)

        # We verify we were able to set the environment
        env = self.dap_server.request_evaluate("foo")["body"]["result"]
        self.assertIn("bar", env)

    @skipIf(archs=no_match(["x86_64"]))
    def test_runInTerminalWithObjectEnv(self):
        if not self.isTestSupported():
            return
        """
            Tests the "runInTerminal" reverse request. It makes sure that the IDE can
            launch the inferior with the correct environment variables using an object.
        """
        program = self.getBuildArtifact("a.out")
        self.build_and_launch(program, runInTerminal=True, env={"FOO": "BAR"})

        self.assertEqual(
            len(self.dap_server.reverse_requests),
            1,
            "make sure we got a reverse request",
        )

        request = self.dap_server.reverse_requests[0]
        request_envs = request["arguments"]["env"]

        self.assertIn("FOO", request_envs)
        self.assertEqual("BAR", request_envs["FOO"])

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_runInTerminalInvalidTarget(self):
        if not self.isTestSupported():
            return
        self.build_and_create_debug_adaptor()
        response = self.launch(
            "INVALIDPROGRAM",
            runInTerminal=True,
            args=["foobar"],
            env=["FOO=bar"],
            expectFailure=True,
        )
        self.assertFalse(response["success"])
        self.assertIn(
            "Could not create a target for a program 'INVALIDPROGRAM': 'INVALIDPROGRAM' does not exist",
            response["message"],
        )

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_missingArgInRunInTerminalLauncher(self):
        if not self.isTestSupported():
            return
        proc = subprocess.run(
            [self.lldbDAPExec, "--launch-target", "INVALIDPROGRAM"],
            capture_output=True,
            universal_newlines=True,
        )
        self.assertNotEqual(proc.returncode, 0)
        self.assertIn(
            '"--launch-target" requires "--comm-file" to be specified', proc.stderr
        )

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_FakeAttachedRunInTerminalLauncherWithInvalidProgram(self):
        if not self.isTestSupported():
            return
        comm_file = os.path.join(self.getBuildDir(), "comm-file")
        os.mkfifo(comm_file)

        proc = subprocess.Popen(
            [
                self.lldbDAPExec,
                "--comm-file",
                comm_file,
                "--launch-target",
                "INVALIDPROGRAM",
            ],
            universal_newlines=True,
            stderr=subprocess.PIPE,
        )

        self.readPidMessage(comm_file)
        self.sendDidAttachMessage(comm_file)
        self.assertIn("No such file or directory", self.readErrorMessage(comm_file))

        _, stderr = proc.communicate()
        self.assertIn("No such file or directory", stderr)

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self):
        if not self.isTestSupported():
            return
        comm_file = os.path.join(self.getBuildDir(), "comm-file")
        os.mkfifo(comm_file)

        proc = subprocess.Popen(
            [
                self.lldbDAPExec,
                "--comm-file",
                comm_file,
                "--launch-target",
                "echo",
                "foo",
            ],
            universal_newlines=True,
            stdout=subprocess.PIPE,
        )

        self.readPidMessage(comm_file)
        self.sendDidAttachMessage(comm_file)

        stdout, _ = proc.communicate()
        self.assertIn("foo", stdout)

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self):
        if not self.isTestSupported():
            return
        comm_file = os.path.join(self.getBuildDir(), "comm-file")
        os.mkfifo(comm_file)

        proc = subprocess.Popen(
            [self.lldbDAPExec, "--comm-file", comm_file, "--launch-target", "env"],
            universal_newlines=True,
            stdout=subprocess.PIPE,
            env={**os.environ, "FOO": "BAR"},
        )

        self.readPidMessage(comm_file)
        self.sendDidAttachMessage(comm_file)

        stdout, _ = proc.communicate()
        self.assertIn("FOO=BAR", stdout)

    @skipIfWindows
    @skipIf(archs=no_match(["x86_64"]))
    def test_NonAttachedRunInTerminalLauncher(self):
        if not self.isTestSupported():
            return
        comm_file = os.path.join(self.getBuildDir(), "comm-file")
        os.mkfifo(comm_file)

        proc = subprocess.Popen(
            [
                self.lldbDAPExec,
                "--comm-file",
                comm_file,
                "--launch-target",
                "echo",
                "foo",
            ],
            universal_newlines=True,
            stderr=subprocess.PIPE,
            env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"},
        )

        self.readPidMessage(comm_file)

        _, stderr = proc.communicate()
        self.assertIn("Timed out trying to get messages from the debug adaptor", stderr)
