#!/usr/bin/env python
# Copyright 2015 The Chromium 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 sys
if sys.platform == 'win32':
  raise ImportError('devil.utils.reset_usb only supported on unix systems.')

import argparse
import fcntl
import logging
import os
import re

if __name__ == '__main__':
  sys.path.append(
      os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))

from devil.android import device_errors
from devil.utils import lsusb
from devil.utils import run_tests_helper

logger = logging.getLogger(__name__)

_INDENTATION_RE = re.compile(r'^( *)')
_LSUSB_BUS_DEVICE_RE = re.compile(r'^Bus (\d{3}) Device (\d{3}):')
_LSUSB_ENTRY_RE = re.compile(r'^ *([^ ]+) +([^ ]+) *([^ ].*)?$')
_LSUSB_GROUP_RE = re.compile(r'^ *([^ ]+.*):$')

_USBDEVFS_RESET = ord('U') << 8 | 20


def reset_usb(bus, device):
  """Reset the USB device with the given bus and device."""
  usb_file_path = '/dev/bus/usb/%03d/%03d' % (bus, device)
  with open(usb_file_path, 'w') as usb_file:
    logger.debug('fcntl.ioctl(%s, %d)', usb_file_path, _USBDEVFS_RESET)
    fcntl.ioctl(usb_file, _USBDEVFS_RESET)


def reset_android_usb(serial):
  """Reset the USB device for the given Android device."""
  lsusb_info = lsusb.lsusb()

  bus = None
  device = None
  for device_info in lsusb_info:
    device_serial = lsusb.get_lsusb_serial(device_info)
    if device_serial == serial:
      bus = int(device_info.get('bus'))
      device = int(device_info.get('device'))

  if bus and device:
    reset_usb(bus, device)
  else:
    raise device_errors.DeviceUnreachableError(
        'Unable to determine bus(%s) or device(%s) for device %s' %
        (bus, device, serial))


def reset_all_android_devices():
  """Reset all USB devices that look like an Android device."""
  _reset_all_matching(lambda i: bool(lsusb.get_lsusb_serial(i)))


def _reset_all_matching(condition):
  lsusb_info = lsusb.lsusb()
  for device_info in lsusb_info:
    if int(device_info.get('device')) != 1 and condition(device_info):
      bus = int(device_info.get('bus'))
      device = int(device_info.get('device'))
      try:
        reset_usb(bus, device)
        serial = lsusb.get_lsusb_serial(device_info)
        if serial:
          logger.info('Reset USB device (bus: %03d, device: %03d, serial: %s)',
                      bus, device, serial)
        else:
          logger.info('Reset USB device (bus: %03d, device: %03d)', bus, device)
      except IOError:
        logger.error('Failed to reset USB device (bus: %03d, device: %03d)',
                     bus, device)


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('-v', '--verbose', action='count')
  parser.add_argument('-s', '--serial')
  parser.add_argument('--bus', type=int)
  parser.add_argument('--device', type=int)
  args = parser.parse_args()

  run_tests_helper.SetLogLevel(args.verbose)

  if args.serial:
    reset_android_usb(args.serial)
  elif args.bus and args.device:
    reset_usb(args.bus, args.device)
  else:
    parser.error('Unable to determine target. '
                 'Specify --serial or BOTH --bus and --device.')

  return 0


if __name__ == '__main__':
  sys.exit(main())
