module XInput
   @@toolPath = `which xinput`.chomp
  
  #test xinput presence, a device and give appropriate error
  def XInput.check
    return "Could not find xinput tool. Make sure it is installed and in path." unless test(?x, @@toolPath)
    return "#{@@toolPath} doesn't support float properties. You need a newer (git, probably) X.org" unless `#{@@toolPath} --help 2>&1`.include?("set-float-prop")
    nil
  end

  def XInput.fetchPointerList(force = false)
    return @@nameToId.keys unless (!defined? @@nameToId or force)
    rawlist = `#{@@toolPath} --list | grep XExtensionPointer`.split("\n")
    @@nameToId = {}
    @@idToName = {}
    
    rawlist.each { |entry|
      m = /\"(.*)\".*id=(\d+).*\[XExtensionPointer\]/.match entry
      @@nameToId[m[1]] = m[2]
      @@idToName[m[2]] = m[1]
    }
    @@nameToId.keys #names
  end

  #return an array of input device names for known pointers
  def XInput.pointerNames
    fetchPointerList
    @@nameToId.keys
  end
  
  #return an array of ids for each input device
  def XInput.pointerIds
    fetchPointerList
    @@nameToId.keys
  end
  
  #return device id as integer, or name as quoted string
  def XInput.devSpecToId(devSpec)
    fetchPointerList
    return devSpec.to_i if /\A\d+\Z/ =~ devSpec
    return '"' + devSpec + '"' if @@nameToId[devSpec] != nil
    raise "device specification #{devSpec} unknown."
  end
  
  def XInput.propsOf(devSpec)
    rawprops = `#{@@toolPath} list-props #{devSpecToId(devSpec)}`.split("\n")
    props = {}
    rawprops.each do |line|
      m = /\s+(.+)\s+\(\d+\):\s+([0-9\.]+)/.match line
      if(!m.nil? && !m[1].nil? && !m[2].nil?)
        props[m[1]] = m[2]
      end
    end
    props
  end

  #set an int property
  def XInput.setIntProp(devSpec, name, value, format = 16)
    o = `#{@@toolPath} set-int-prop #{devSpecToId(devSpec)} "#{name}" #{format} #{value} 2>&1`
    !o.include?("Error")
  end

  #set a float property
  def XInput.setFloatProp(devSpec, name, value)
    o = `#{@@toolPath} set-float-prop #{devSpecToId(devSpec)} "#{name}" #{value} 2>&1`
    !o.include? "Error"
  end
  
  #set classic feedback parameters
  def XInput.setFeedback(devSpec, fd)
    `#{@@toolPath} set-ptr-feedback #{devSpecToId(devSpec)} #{fd[:threshold]} #{fd[:accelNum]} #{fd[:accelDenom]}`
  end
  
  def XInput.getFeedback(devSpec)
    o = `#{@@toolPath} get-feedbacks #{devSpecToId(devSpec)}`
    {
      :threshold => /threshold is (\d+)/.match(o)[1].to_i,
      :accelNum  => /accelNum is (\d+)/.match(o)[1].to_i,
      :accelDenom => /accelDenom is (\d+)/.match(o)[1].to_i    
    }
  end
  
  #reset the accel props to factory default (which we have to know, sadly)
  def XInput.resetAccelSettings(devSpec)
    applyAccelSettings(devSpec, {
      :accelNum => 1,
      :accelDenom => 1,
      :threshold => 2,
      :profile => 0,
      :adaptiveDeceleration => 1,
      :constantDeceleration => 1,
      :velocityScaling => 10})
  end
   
  def XInput.getAccelSettings(devSpec)
    s = getFeedback(devSpec)
    p = propsOf(devSpec)
    #merge props into hash
    s[:profile] = p["Device Accel Profile"].to_i
    s[:adaptiveDeceleration] = p["Device Accel Adaptive Deceleration"].to_f
    s[:constantDeceleration] = p["Device Accel Constant Deceleration"].to_f
    s[:velocityScaling] = p["Device Accel Velocity Scaling"].to_f
    s
  end
  
  def XInput.applyAccelSettings(devSpec, s)
    setFeedback(devSpec, s)
    setIntProp(devSpec, "Device Accel Profile", s[:profile])
    setFloatProp(devSpec, "Device Accel Adaptive Deceleration", s[:adaptiveDeceleration])
    setFloatProp(devSpec, "Device Accel Constant Deceleration", s[:constantDeceleration])
    setFloatProp(devSpec, "Device Accel Velocity Scaling", s[:velocityScaling])
  end
  
end
