 /// Represents a SANE device.
 public class Device {
   //MARK: Types
-  enum State {
+  public enum DeviceError: Error {
+case noHandle
+}
+
+  public enum State {
     case disconnected
     case connected
-    case open(handle: SANE_Handle)
-  }
-
+    case open
+  }
+
+  /**
+   An additional information bitset which is returned after setting a device option. It is implemented as a OptionSet for better swift integration.
+   
+   - seealso: http://sane.alioth.debian.org/html/doc012.html
+   */
+  struct Info: OptionSet {
+    let rawValue: Int32
+    /// The given value couldn't be set exactly, but the nearest aproximate value is used
+    static let inexact = Info(rawValue: SANE_INFO_INEXACT)
+    /// The option descriptors may have changed
+    static let reloadOptions = Info(rawValue: SANE_INFO_RELOAD_OPTIONS)
+    /// the device parameters may have changed.
+    static let reloadParams = Info(rawValue: SANE_INFO_RELOAD_PARAMS)
+  }
+
+  /// This constant is used as maximal buffer size when reading image data
+  static private let bufferSize = 1024*1024
   //MARK: Properties
   /// Properties which describe a SANE device
   public let name, model, vendor, type: String
 
-  private(set) var state: State = .connected
+  public private(set) var state: State = .connected
   public private(set) var options = [String: BaseOption]()
+  private var handle: SANE_Handle? {
+    didSet {
+      state = handle == nil ? .connected : .open
+    }
+  }
 
   //MARK: Lifecycle Hooks
   init(_ name: String, model: String, vendor: String, type: String) {


  //MARK: Methods
  public func open() throws {
    var tmpHandle: SANE_Handle?
    let status = sane_open(name, &tmpHandle)
    guard status == SANE_STATUS_GOOD else {
      throw status
    }
    if let handle = tmpHandle {
      state = .open(handle: handle)
    }
    try getOptions()
  }

  public func close() {
    if case let .open(handle) = state {
      sane_close(handle)
    }
    state = .connected
  }

  func getOptions() throws {
    guard case let .open(handle) = state else {
      throw BaseOption.OptionError.noHandle
    }
    var count: Int32 = 1
    let status = sane_control_option(handle, Int32(0), SANE_Action(0), &count, nil)
    guard status == SANE_STATUS_GOOD else {
      throw status
    }
    guard count > 1 else {
      return
    }
    for index in Int32(1) ... count-1 {
      let saneOption = sane_get_option_descriptor(handle, index).pointee
      guard let descriptor = OptionDescriptor(from: saneOption) else {
        continue
      }
      var option: BaseOption
      switch descriptor.valueType {
      case .bool:
        option = SwitchOption(from: descriptor, at: index, of: self)
      case .int:
        option = IntOption(from: descriptor, at: index, of: self)
      case .fixed:
        option = NumericOption(from: descriptor, at: index, of: self)
      case .string:
        option = TextOption(from: descriptor, at: index, of: self)
      case .button:
        option = ButtonOption(from: descriptor, at: index, of: self)
      default:
        option = BaseOption(from: descriptor, at: index, of: self)
      }
      options[option.descriptor.name] = option
    }
  }

  func reloadOptions() {
  guard case let .open(handle) = state else {
  return
  }
  for (name, option) in options {
    let saneOption = sane_get_option_descriptor(handle, option.index).pointee
    guard let descriptor = OptionDescriptor(from: saneOption) else {
      break
    }
    options[name]?.descriptor = descriptor
  }
  }

}

//MARK: Extensions

  }
}

extension Device: Hashable {
  /// use the device name as hash value
  public var hashValue: Int {
    return name.hashValue
  }
}

extension Device: Comparable {
  /// Devices can be equated by their hash value
  static public func ==(lhs: Device, rhs: Device) -> Bool {
    return lhs.hashValue == rhs.hashValue
  }
  static public func <(lhs: Device, rhs: Device) -> Bool {
    return lhs.hashValue < rhs.hashValue
  }
}
