import gdbremote_testcase
import lldbgdbserverutils
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

import json
import platform
import re


class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase):
    # Number of stderr lines to read from the simctl output.
    READ_LINES = 10

    def check_simulator_ostype(self, sdk, platform_name, arch=platform.machine()):
        cmd = ["xcrun", "simctl", "list", "-j", "devices"]
        cmd_str = " ".join(cmd)
        self.trace(cmd_str)
        sim_devices_str = subprocess.check_output(cmd).decode("utf-8")
        try:
            sim_devices = json.loads(sim_devices_str)["devices"]
        except json.decoder.JSONDecodeError:
            self.fail(
                "Could not parse '{}' output. Authorization denied?".format(cmd_str)
            )
        # Find an available simulator for the requested platform
        deviceUDID = None
        deviceRuntime = None
        for simulator in sim_devices:
            if isinstance(simulator, dict):
                runtime = simulator["name"]
                devices = simulator["devices"]
            else:
                runtime = simulator
                devices = sim_devices[simulator]
            if not platform_name in runtime.lower():
                continue
            for device in devices:
                if "availability" in device and device["availability"] != "(available)":
                    continue
                if "isAvailable" in device and device["isAvailable"] != True:
                    continue
                if deviceRuntime and runtime < deviceRuntime:
                    continue
                deviceUDID = device["udid"]
                deviceRuntime = runtime
                # Stop searching in this runtime
                break

        if not deviceUDID:
            self.skipTest(
                "Could not find a simulator for {} ({})".format(platform_name, arch)
            )

        # Launch the process using simctl
        exe_name = "test_simulator_platform_{}".format(platform_name)
        sdkroot = lldbutil.get_xcode_sdk_root(sdk)
        vers = lldbutil.get_xcode_sdk_version(sdk)
        clang = lldbutil.get_xcode_clang(sdk)

        # Older versions of watchOS (<7.0) only support i386
        if platform_name == "watchos":
            from distutils.version import LooseVersion

            if LooseVersion(vers) < LooseVersion("7.0"):
                arch = "i386"

        triple = "-".join([arch, "apple", platform_name + vers, "simulator"])
        version_min = "-m{}-simulator-version-min={}".format(platform_name, vers)
        self.build(
            dictionary={
                "EXE": exe_name,
                "CC": clang,
                "SDKROOT": sdkroot.strip(),
                "ARCH": arch,
                "ARCH_CFLAGS": "-target {} {}".format(triple, version_min),
                "USE_SYSTEM_STDLIB": 1,
            }
        )
        exe_path = os.path.realpath(self.getBuildArtifact(exe_name))
        cmd = [
            "xcrun",
            "simctl",
            "spawn",
            "-s",
            deviceUDID,
            exe_path,
            "print-pid",
            "sleep:10",
        ]
        self.trace(" ".join(cmd))
        sim_launcher = subprocess.Popen(cmd, stderr=subprocess.PIPE)
        # Get the PID from the process output
        pid = None

        # Read the first READ_LINES to try to find the PID.
        for _ in range(0, self.READ_LINES):
            stderr = sim_launcher.stderr.readline().decode("utf-8")
            if not stderr:
                continue
            match = re.match(r"PID: (.*)", stderr)
            if match:
                pid = int(match.group(1))
                break

        # Make sure we found the PID.
        self.assertIsNotNone(pid)

        # Launch debug monitor attaching to the simulated process
        server = self.connect_to_debug_monitor(attach_pid=pid)

        # Setup packet sequences
        self.do_handshake()
        self.add_process_info_collection_packets()
        self.test_sequence.add_log_lines(
            [
                "read packet: "
                + '$jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs" : true}]#ce',
                {
                    "direction": "send",
                    "regex": r"^\$(.+)#[0-9a-fA-F]{2}$",
                    "capture": {1: "dylib_info_raw"},
                },
            ],
            True,
        )

        # Run the stream
        context = self.expect_gdbremote_sequence()
        self.assertIsNotNone(context)

        # Gather process info response
        process_info = self.parse_process_info_response(context)
        self.assertIsNotNone(process_info)

        # Check that ostype is correct
        self.assertEquals(process_info["ostype"], platform_name + "simulator")

        # Now for dylibs
        dylib_info_raw = context.get("dylib_info_raw")
        dylib_info = json.loads(self.decode_gdbremote_binary(dylib_info_raw))
        images = dylib_info["images"]

        image_info = None
        for image in images:
            if image["pathname"] != exe_path:
                continue
            image_info = image
            break

        self.assertIsNotNone(image_info)
        self.assertEquals(image["min_version_os_name"], platform_name + "simulator")

    @apple_simulator_test("iphone")
    @skipIfRemote
    def test_simulator_ostype_ios(self):
        self.check_simulator_ostype(sdk="iphonesimulator", platform_name="ios")

    @apple_simulator_test("appletv")
    @skipIfRemote
    def test_simulator_ostype_tvos(self):
        self.check_simulator_ostype(sdk="appletvsimulator", platform_name="tvos")

    @apple_simulator_test("watch")
    @skipIfRemote
    def test_simulator_ostype_watchos(self):
        self.check_simulator_ostype(sdk="watchsimulator", platform_name="watchos")
