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')
