# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import os
import pyudev
import re

from autotest_lib.client.bin import test, utils
from autotest_lib.client.common_lib import error
from collections import defaultdict
from operator import attrgetter

def natural_key(string_):
    """
    Derive key for natural sorting.
    @param string_: String to derive sort key for.
    From http://stackoverflow.com/questions/34518/natural-sorting-algorithm.
    """
    return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]


class platform_UdevVars(test.test):
    """Verify ChromeOS-specific udev variables."""
    version = 1


    def _input_devices(self):
        """Obtain a list of all /dev/input/event* udev devices."""
        devices = self.udev.list_devices(subsystem='input')
        # only consider the event devices
        devices = filter(attrgetter('device_node'), devices)
        devices = sorted(devices, key=lambda device: natural_key(device.device_node))
        return devices


    def _get_roles(self):
        """Get information on input devices and roles from udev."""
        self.devices_with_role = defaultdict(list)

        logging.debug('Input devices:')
        for device in self._input_devices():
            name = device.parent.attributes.get('name', '')
            logging.debug('  %s [%s]', device.device_node, name)
            role = device.get('POWERD_ROLE', None)
            if role:
                logging.debug('    POWERD_ROLE=%s', role)
                self.devices_with_role[role].append(device)


    def _dump_roles(self):
        """Log devices grouped by role for easier debugging."""
        logging.info('Roles:')
        for role in sorted(self.devices_with_role.keys()):
            for device in self.devices_with_role[role]:
                path = device.device_node
                name = device.parent.attributes.get('name', '')
                logging.info('  %-21s %s [%s]', role + ':', path, name)


    def _dump_udev_attrs(self):
        """Log udev attributes for selected devices to the debug directory."""
        for device in self._input_devices():
            devname = os.path.basename(device.device_node)

            outfile = os.path.join(self.debugdir, "udevattrs.%s" % devname)
            utils.system('udevadm info --attribute-walk --path=%s > %s' % (
                    device.sys_path, outfile))

            outfile = os.path.join(self.debugdir, "udevprops.%s" % devname)
            utils.system('udevadm info --query=property --path=%s > %s' % (
                    device.sys_path, outfile))


    def _verify_roles(self):
        """Verify that POWERD_ROLE was set on devices as expected."""

        # TODO(chromium:410968): Consider moving this to USE flags instead of
        # listing devices here.
        boards_with_touchscreen = ['link', 'samus']
        boards_maybe_touchscreen = ['rambi', 'peppy', 'glimmer', 'clapper',
                                    'nyan_big', 'nyan_blaze', 'expresso']
        boards_chromebox = ['beltino', 'guado', 'mccloud', 'panther', 'rikku', 
                            'stumpy', 'tidus', 'tricky', 'zako']
        boards_aio = ['nyan_kitty', 'tiny', 'anglar', 'monroe']

        expect_keyboard = None
        expect_touchpad = None
        expect_touchscreen = None

        board = utils.get_board()
        if board in boards_chromebox or board in boards_aio:
            expect_keyboard = [0]
            expect_touchpad = [0]
        else:
            expect_keyboard = [1]
            expect_touchpad = [1]

        if board in boards_with_touchscreen:
            expect_touchscreen = [1]
        elif board in boards_maybe_touchscreen:
            expect_touchscreen = [0, 1]
        else:
            expect_touchscreen = [0]

        expected_num_per_role = [
                ('internal_keyboard', expect_keyboard),
                ('internal_touchpad', expect_touchpad),
                ('internal_touchscreen', expect_touchscreen),
            ]

        for role, expected_num in expected_num_per_role:
            num = len(self.devices_with_role[role])
            if num not in expected_num:
                self.errors += 1
                logging.error('POWERD_ROLE=%s is present %d times, expected '
                              'one of %s', role, num, repr(expected_num))

        if len(self.devices_with_role['external_input']) != 0:
            logging.warn('%d external input devices detected',
                         len(self.devices_with_role['external_input']))


    def initialize(self):
        self.udev = pyudev.Context()


    def run_once(self):
        """
        Check that udev variables are assigned correctly by udev rules. In
        particular, verifies that powerd tags are set correctly.
        """
        logging.debug('Board: %s', utils.get_board())
        self._get_roles()
        self._dump_roles()
        self._dump_udev_attrs()

        self.errors = 0
        self._verify_roles()

        if self.errors != 0:
            raise error.TestFail('Verification of udev variables failed; see '
                                 'logs for details')