"""
mbed SDK
Copyright (c) 2011-2015 ARM Limited

Licensed 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 re
import os
import sys
import string

from .lstools_base import MbedLsToolsBase

import logging
logger = logging.getLogger("mbedls.lstools_win7")
DEBUG = logging.DEBUG
del logging

if sys.version_info[0] < 3:
    import _winreg as winreg
else:
    import winreg


class MbedLsToolsWin7(MbedLsToolsBase):
    """ mbed-enabled platform detection for Windows
    """
    def __init__(self, **kwargs):
        MbedLsToolsBase.__init__(self, **kwargs)
        self.os_supported.append('Windows7')

    def find_candidates(self):
        return [
            {
                'mount_point': mnt,
                'target_id_usb_id': id,
                'serial_port': self._com_port(id)
            }
            for mnt, id in self.get_mbeds()
        ]

    def _com_port(self, tid):
        """! Function checks mbed serial port in Windows registry entries
        @param tid TargetID
        @return Returns None if port is not found. In normal circumstances it should never return None
        @details This goes through a whole new loop, but this assures that even if serial port (COM)
                 is not detected, we still get the rest of info like mount point etc.
        """
        winreg.Enum = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                                     r'SYSTEM\CurrentControlSet\Enum')
        usb_devs = winreg.OpenKey(winreg.Enum, 'USB')
        logger.debug("_com_port usb_devs %r",
                     list(self.iter_keys_as_str(usb_devs)))

        logger.debug("_com_port looking up usb id in all usb devices")
        dev_keys = []
        for name, vid in zip(self.iter_keys_as_str(usb_devs),
                             self.iter_keys(usb_devs)):
            try:
                dev_keys.append(winreg.OpenKey(vid, tid))
                if logger.isEnabledFor(DEBUG):
                    logger.debug(
                        "Found usb id %s in %s with subkeys %r", tid, name,
                        list(self.iter_keys_as_str(winreg.OpenKey(vid, tid))))
            except OSError:
                pass

        logger.debug("_com_port Detecting port with Device Parameter")
        for key in dev_keys:
            try:
                param = winreg.OpenKey(key, "Device Parameters")
                port, regtype = winreg.QueryValueEx(param, 'PortName')
                logger.debug('_com_port port %r regtype %r', port, regtype)
                return port
            except OSError as e:
                logger.debug("Exception %r %s", key, str(e))
                pass

        logger.debug("_com_port Detecting port by following symlinks")
        for key in dev_keys:
            try:
                ports = []
                parent_id, regtype = winreg.QueryValueEx(key, 'ParentIdPrefix')
                logger.debug("_com_port parent_id %r regtype %r")
                for VID in self.iter_keys(usb_devs):
                    candidate_keys = [k for k in self.iter_keys_as_str(VID)
                                      if parent_id in k]
                    for dev in candidate_keys:
                        logger.debug(
                            "_com_port recursive port detection with %r", dev)
                        maybe_port = self._com_port(dev)
                        if maybe_port:
                            return maybe_port
            except OSError as e:
                logger.debug("Exception %r %s", key, str(e))
                pass

        # If everything fails, return None
        return None

    def _drive_letters(self):
        """! Function for finding all drive letters
        @return Returns an array of all drive letters present
        """
        cmd = ['fsutil', 'fsinfo', 'drives']
        logger.debug('running command: %s' % (' '.join(cmd)))
        stdout, _, retval = self._run_cli_process(cmd)
        logger.debug('return code: %d' % retval)
        logger.debug('output: %s' % stdout)

        if not retval:
            drive_list = re.match('\s*Drives: (.*)', stdout.decode('utf-8')).group(1)
            return drive_list.strip().replace('\\', '').split(' ')
        return []


    def get_mbeds(self):
        """! Function filters devices' mount points for valid TargetID
        @return Returns [(<mbed_mount_point>, <mbed_id>), ..]
        @details TargetID should be a hex string with 10-48 chars
        """
        mbeds = []
        drive_letters = set(self._drive_letters())
        logger.debug('Found drive letters: %s' % ', '.join(drive_letters))
        for mbed in self.get_mbed_devices():
            mountpoint = re.match('.*\\\\(.:)$', mbed[0]).group(1)
            if mountpoint in drive_letters:
                # TargetID is a hex string with 10-48 chars
                m = re.search('[&#]([0-9A-Za-z]{10,48})[&#]', mbed[1])
                if not m:
                    continue
                tid = m.group(1)
                mbeds += [(mountpoint, tid)]
                logger.debug("get_mbeds mount_point %s usb_id %s", mountpoint, tid)
        return mbeds

    # =============================== Registry ====================================

    def iter_keys_as_str(self, key):
        """! Iterate over subkeys of a key returning subkey as string
        """
        for i in range(winreg.QueryInfoKey(key)[0]):
            yield winreg.EnumKey(key, i)

    def iter_keys(self, key):
        """! Iterate over subkeys of a key
        """
        for i in range(winreg.QueryInfoKey(key)[0]):
            yield winreg.OpenKey(key, winreg.EnumKey(key, i))

    def iter_vals(self, key):
        """! Iterate over values of a key
        """
        logger.debug("iter_vals %r", key)
        for i in range(winreg.QueryInfoKey(key)[1]):
            yield winreg.EnumValue(key, i)

    def get_mbed_devices(self):
        """! Get MBED devices (connected or not)
        @return List of devices
        @details Note: We will detect also non-standard MBED devices mentioned on 'usb_vendor_list' list.
                 This will help to detect boards like EFM boards.
        """
        upper_ven = [ven.upper() for ven in self.usb_vendor_list]
        mounts_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SYSTEM\MountedDevices')
        for point, label, _ in self.iter_vals(mounts_key):
            printable_label = label.decode('utf-16le', 'ignore')
            if (b'DosDevices' in point and
                any(v in printable_label.upper() for v in upper_ven)):
                logger.debug("Found Mount point %s with usb ID %s",point,
                             printable_label)
                yield (point.decode('utf-8', 'ignore'), printable_label)
            else:
                logger.debug("Skipping Mount point %r label %r", point, label)
