# This file is part of Libusb for Ruby.
#
# Libusb for Ruby is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Libusb for Ruby is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Libusb for Ruby.  If not, see <http://www.gnu.org/licenses/>.

require 'libusb/call'

module LIBUSB
  Call::ClassCodes.to_h.each{|k,v| const_set(k,v) }
  Call::TransferTypes.to_h.each{|k,v| const_set(k,v) }
  Call::StandardRequests.to_h.each{|k,v| const_set(k,v) }
  Call::RequestTypes.to_h.each{|k,v| const_set(k,v) }
  Call::DescriptorTypes.to_h.each{|k,v| const_set(k,v) }
  Call::EndpointDirections.to_h.each{|k,v| const_set(k,v) }
  Call::RequestRecipients.to_h.each{|k,v| const_set(k,v) }
  Call::IsoSyncTypes.to_h.each{|k,v| const_set(k,v) }
  Call::Speeds.to_h.each{|k,v| const_set(k,v) }
  Call::Capabilities.to_h.each{|k,v| const_set(k,v) }
  Call::SupportedSpeeds.to_h.each{|k,v| const_set(k,v) }
  Call::Usb20ExtensionAttributes.to_h.each{|k,v| const_set(k,v) }
  Call::SsUsbDeviceCapabilityAttributes.to_h.each{|k,v| const_set(k,v) }
  Call::BosTypes.to_h.each{|k,v| const_set(k,v) }
  Call::HotplugEvents.to_h.each{|k,v| const_set(k,v) }
  Call::HotplugFlags.to_h.each{|k,v| const_set(k,v) }

  # Base class of libusb errors
  class Error < RuntimeError
    # The data already transferred before the exception was raised
    # @return [Fixnum] Number of bytes sent for an outgoing transfer
    # @return [String] Received data for an ingoing transfer
    attr_reader :transferred

    def initialize(msg=nil, transferred=nil)
      super(msg)
      @transferred = transferred
    end
  end

  # @private
  ErrorClassForResult = {}

  # define an exception class for each error code
  Call::Errors.to_h.each do |k,v|
    klass = Class.new(Error)
    klass.send(:define_method, :code){ v }
    const_set(k, klass)
    ErrorClassForResult[v] = klass
  end

  def self.raise_error(res, text)
    klass = ErrorClassForResult[res]
    raise klass, "#{klass} #{text}"
  end

  CONTROL_SETUP_SIZE = 8
  DT_DEVICE_SIZE = 18
  DT_CONFIG_SIZE = 9
  DT_INTERFACE_SIZE = 9
  DT_ENDPOINT_SIZE = 7
  DT_ENDPOINT_AUDIO_SIZE = 9 # Audio extension
  DT_HUB_NONVAR_SIZE = 7

  ENDPOINT_ADDRESS_MASK = 0x0f    # in bEndpointAddress
  ENDPOINT_DIR_MASK = 0x80
  TRANSFER_TYPE_MASK = 0x03    # in bmAttributes
  ISO_SYNC_TYPE_MASK = 0x0C
  ISO_USAGE_TYPE_MASK = 0x30

  POLLIN = 1
  POLLOUT = 4

  # Wildcard matching for hotplug events.
  HOTPLUG_MATCH_ANY = -1


  # http://www.usb.org/developers/defined_class
  # @private
  CLASS_CODES = [
    [0x01, nil, nil, "Audio"],
    [0x02, nil, nil, "Comm"],
    [0x03, nil, nil, "HID"],
    [0x05, nil, nil, "Physical"],
    [0x06, 0x01, 0x01, "StillImaging"],
    [0x06, nil, nil, "Image"],
    [0x07, nil, nil, "Printer"],
    [0x08, 0x01, nil, "MassStorage RBC Bulk-Only"],
    [0x08, 0x02, 0x50, "MassStorage ATAPI Bulk-Only"],
    [0x08, 0x03, 0x50, "MassStorage QIC-157 Bulk-Only"],
    [0x08, 0x04, nil, "MassStorage UFI"],
    [0x08, 0x05, 0x50, "MassStorage SFF-8070i Bulk-Only"],
    [0x08, 0x06, 0x50, "MassStorage SCSI Bulk-Only"],
    [0x08, nil, nil, "MassStorage"],
    [0x09, 0x00, 0x00, "Full speed Hub"],
    [0x09, 0x00, 0x01, "Hi-speed Hub with single TT"],
    [0x09, 0x00, 0x02, "Hi-speed Hub with multiple TTs"],
    [0x09, nil, nil, "Hub"],
    [0x0a, nil, nil, "CDC"],
    [0x0b, nil, nil, "SmartCard"],
    [0x0d, 0x00, 0x00, "ContentSecurity"],
    [0x0e, nil, nil, "Video"],
    [0xdc, 0x01, 0x01, "Diagnostic USB2"],
    [0xdc, nil, nil, "Diagnostic"],
    [0xe0, 0x01, 0x01, "Bluetooth"],
    [0xe0, 0x01, 0x02, "UWB"],
    [0xe0, 0x01, 0x03, "RemoteNDIS"],
    [0xe0, 0x02, 0x01, "Host Wire Adapter Control/Data"],
    [0xe0, 0x02, 0x02, "Device Wire Adapter Control/Data"],
    [0xe0, 0x02, 0x03, "Device Wire Adapter Isochronous"],
    [0xe0, nil, nil, "Wireless Controller"],
    [0xef, 0x01, 0x01, "Active Sync"],
    [0xef, 0x01, 0x02, "Palm Sync"],
    [0xef, 0x02, 0x01, "Interface Association Descriptor"],
    [0xef, 0x02, 0x02, "Wire Adapter Multifunction Peripheral"],
    [0xef, 0x03, 0x01, "Cable Based Association Framework"],
    [0xef, nil, nil, "Miscellaneous"],
    [0xfe, 0x01, 0x01, "Device Firmware Upgrade"],
    [0xfe, 0x02, 0x00, "IRDA Bridge"],
    [0xfe, 0x03, 0x00, "USB Test and Measurement"],
    [0xfe, 0x03, 0x01, "USB Test and Measurement (USBTMC USB488)"],
    [0xfe, nil, nil, "Application Specific"],
    [0xff, nil, nil, "Vendor specific"],
  ]
  # @private
  CLASS_CODES_HASH1 = {}
  # @private
  CLASS_CODES_HASH2 = {}
  # @private
  CLASS_CODES_HASH3 = {}
  CLASS_CODES.each {|base_class, sub_class, protocol, desc|
    if protocol
      CLASS_CODES_HASH3[[base_class, sub_class, protocol]] = desc
    elsif sub_class
      CLASS_CODES_HASH2[[base_class, sub_class]] = desc
    else
      CLASS_CODES_HASH1[base_class] = desc
    end
  }

  def self.dev_string(base_class, sub_class, protocol)
    if desc = CLASS_CODES_HASH3[[base_class, sub_class, protocol]]
      desc
    elsif desc = CLASS_CODES_HASH2[[base_class, sub_class]]
      desc + " (%02x)" % [protocol]
    elsif desc = CLASS_CODES_HASH1[base_class]
      desc + " (%02x,%02x)" % [sub_class, protocol]
    else
      "Unknown(%02x,%02x,%02x)" % [base_class, sub_class, protocol]
    end
  end
end