require 'xinput'


#init
err = XInput.check
unless err.nil?
  alert(err)
  exit
end

@@savedSettings = {}

XInput.pointerNames.each do |dev|
  @@savedSettings[dev] = XInput.getAccelSettings dev
end
#TODO: restore on exit?



#@@currentPointerName = ""

Shoes.app :width => 600, :height => 408 , :title => "No cats, please." do
  stack :margin_right => gutter do
    background darkgray
    flow :margin => 6 do
      background "#ffa100".."#ffc080", :curve => 10  #navy
      style :angle => 45
      #stroke white
      inscription "I'm domesticating my pets. Like "
      XInput.pointerNames.each do |n|
        para(link(n, :stroke => white) do
          #@@currentPointerName = n
          debug n + " requested"
          Tamer.doStuffWith @deviceStack, n          
        end)
        @lastOr = inscription " or "
      end
      @lastOr.replace ". How cute."
      
      inscription "You may, well, also ",
      (link("brainwash 'em ", :stroke => yellow) do
        @@savedSettings.each do |dev, acc|
          info "restoring #{dev}: #{acc.inspect}"
          XInput.applyAccelSettings(dev, acc)
          @deviceStack.clear
        end
      end)
      inscription " or screw ", 
      (link("even harder. ", :stroke => yellow) do 
        XInput.pointerNames.each do |dev|
          info "resetting #{dev}"
          XInput.resetAccelSettings dev
          @deviceStack.clear
        end
      end)
      inscription "No animals were harmed in this production.", :stroke => red
    end
    @deviceStack = stack :margin => 6
    #choose the lonely device
    if(XInput.pointerNames.length == 1)
      Tamer.doStuffWith @deviceStack, XInput.pointerNames[0]
    else
      Tamer.showNothinReal @deviceStack
    end
  end
end



module Tamer
  def Tamer.showNothinReal(stck)
    stck.app do
      stck.clear
      stck.append do
        background gray, :curve => 10
        para "no favourite, which one to pick? Bliss!"      
      end
    end
  end
  
  def Tamer.doStuffWith(stck, devName)
    stck.app do
      stck.clear
      stck.append do
        @currentSettings = XInput.getAccelSettings(devName)
        background "#99ee99".."#eeffee", :curve => 10
        tagline devName, :stroke => mintcream, :weight => "semibold"
        
        #make sure no acceleration happens
        button "tame!" do
          @currentSettings[:adaptiveDeceleration] = 1.0
          @currentSettings[:profile] = 0 #there are several profile/control combos which don't accelerate
          @currentSettings[:accelNum] = 1
          @currentSettings[:accelDenom] = 1
          @currentSettings[:threshold] = 0
          XInput.applyAccelSettings(devName, @currentSettings)
          @currentSettings = XInput.getAccelSettings(devName)
          #reset all controls
          @profile_box.choose(@@profile.index(@currentSettings[:profile]))
          accVal = @currentSettings[:accelNum].to_f / @currentSettings[:accelDenom].to_f
          @accelSlider.setSlider accVal
          @thrSlider.setSlider @currentSettings[:threshold]
          @cdecSlider.setSlider @currentSettings[:constantDeceleration]
          @adecSlider.setSlider @currentSettings[:adaptiveDeceleration]
          @velSlider.setSlider @currentSettings[:velocityScaling]
        end
        
        
        #profile
        @profile_box = list_box(:items => @@profiles_in_my_order) { |i|
          debug "profile " + i.text + @@profile[i.text].to_s
          if(XInput.setIntProp(devName, "Device Accel Profile", @@profile[i.text]))
            @currentSettings[:profile] = @@profile[i.text]
          else
            alert "The profile you selected was refused."
          end
        }
        @profile_box.choose(@@profile.index(@currentSettings[:profile]))
        inscription @@be_nice[:profile]
        
        
        #accel factor
        accVal = @currentSettings[:accelNum].to_f / @currentSettings[:accelDenom].to_f
        @accelSlider = slider :value => accVal, :lowBound => 1.0, :highBound => 5.0 do |sld, value|
          den = 10
          @currentSettings[:accelNum] = (value * den).round
          @currentSettings[:accelDenom] = den
          debug "changing accel factor to #{@currentSettings[:accelNum]}/#{den}"
          XInput.setFeedback(devName, @currentSettings)
        end
        inscription @@be_nice[:accelNum]
        
        
        #threshold
        @thrSlider = slider :value => @currentSettings[:threshold], :lowBound => 0.0, :highBound => 5.0 do |sld, value|
          @currentSettings[:threshold] = value.round
          debug "changing threshold to #{@currentSettings[:threshold]}"
          XInput.setFeedback(devName, @currentSettings)
          sld.setSlider(@currentSettings[:threshold])
        end
        inscription @@be_nice[:threshold]
        
        
        #constant deceleration
        @cdecSlider = slider :value => @currentSettings[:constantDeceleration], :lowBound => 1.0, :highBound => 10.0 do |sld, value|
          debug "changing constant decel to #{value}"
          @currentSettings[:constantDeceleration] = value
          XInput.setFloatProp(devName, "Device Accel Constant Deceleration", @currentSettings[:constantDeceleration])
        end
        inscription @@be_nice[:constantDeceleration]
        
        
        #adaptive deceleration
        @adecSlider = slider :value => @currentSettings[:adaptiveDeceleration], :lowBound => 1.0, :highBound => 10.0 do |sld, value|
          debug "changing adaptive decel to #{value}"
          @currentSettings[:adaptiveDeceleration] = value
          XInput.setFloatProp(devName, "Device Accel Adaptive Deceleration", @currentSettings[:adaptiveDeceleration])
        end
        inscription @@be_nice[:adaptiveDeceleration]
        
        
        #velocity scaling
        @velSlider = slider :value => @currentSettings[:velocityScaling], :lowBound => 1.0, :highBound => 20.0 do |sld, value|
          debug "changing velocity scaling to #{value}"
          @currentSettings[:velocityScaling] = value
          XInput.setFloatProp(devName, "Device Accel Velocity Scaling", @currentSettings[:velocityScaling])
        end
        inscription @@be_nice[:velocityScaling]
      end
    end
  end
  
  @@profile = { 
    "polynomial" => 2,
    "smooth linear" => 3,
    "linear" => 6,
    "simple" => 4,
    "power" => 5,
    "classic (simple/polynomial)" => 0,
    "device-dependent" => 1,
  }
  
  @@profiles_in_my_order = [ 
    "polynomial",
    "smooth linear",
    "linear",
    "simple",
    "power",
    "classic (simple/polynomial)",
    "device-dependent",
  ]
  
  @@be_nice = {
    :accelNum => "Acceleration is a input to the profile. Use it to tweak the profile.",
    :threshold => "Threshold is a input to the profile. Conventionally, it causes the profile to not accelerate below threshold velocity.",
    :profile => "The profile is what constitutes the 'feeling' aspect. More technically, it translates the device's velocity to pointer acceleration. They're in rough order of how much I like them.",
    :adaptiveDeceleration => "A great tool for enhancing precision. Basically, it slows down already slow motion. More technically, it allows the profile to slow down so much. Comes on top of constant deceleration.",
    :constantDeceleration => "Decelerates by the given factor. Equivalent to having a lower dpi device.",
    :velocityScaling => "A scale applied on velocity, before acceleration is determined. You should only need it with offbeat devices. Usually, effects it has can also be achieved using the other controls."
}
end

#'improved' from
#http://github.com/greatseth/shoes-widgets/blob/ddc5a191dbb70c4b7c2d77cd1fee66416f89d747/slider.rb
class Slider < Widget
  attr_reader :percent
  
  def initialize(options = {}, &blk)
    style  :height => 50 #:width => 200,
    
    @blk = blk
    
    @bounds = stack :width => 200, :height => 40 do
      background gradient(white, gray(1.0,0.0))
      
      @knob = rect 0,1,10,29
      default_knob_fill
      
      @display = para ""
      
      @knob_half_width = @knob.width / 2
      
      @knob.hover { active_knob_fill }
      @knob.leave { default_knob_fill unless dragging? }
      
      @knob.click { start_dragging }
      @knob.release { stop_dragging }
      
      @timer = animate(48) { update_position if dragging?  }.stop #and within_bounds?
          
      @highBound = options[:highBound] || 100
      @lowBound = options[:lowBound]  || 0
      @value = options[:value] || @lowBound
      
      click { update_position; sync }
      #setSlider needs bounds to have valid size; postpone
      start { setSlider @value }
    end    
  end
  
  def dragging?
    @dragging
  end
  
  def default_knob_fill
    @knob.fill = yellow
  end
  
  def active_knob_fill
    @knob.fill = green
  end
  
  def start_dragging
    active_knob_fill
    debug "start_dragging"
    @dragging = true
    @timer.start
  end
  
  def stop_dragging
    debug "stop_dragging"
    default_knob_fill
    @dragging = false
    @timer.stop
    sync
  end
  
  def update_position
    @knob.left = (mouse[1] - @bounds.left) - (@knob.width) #this is somehow broken, but docs aren't exactly helpfule and it works ATM
    @knob.left = @bounds.left if @knob.left < @bounds.left
    @knob.left = @bounds.width - @knob.width if @knob.left > @bounds.width - @knob.width
    
    @percent = ((@knob.left - @bounds.left).to_f / (@bounds.width - @knob.width).to_f) * 100
  end
  
  def within_bounds?
    (mouse[1] > @bounds.left) and (mouse[1] < @bounds.width)
  end
  
  def setSlider(val)
    frac = (val-@lowBound)/(@highBound - @lowBound)
    @knob.left = @bounds.left + ((@bounds.width - @knob.width) * frac)
    @percent = (@knob.left.to_f / (@bounds.width - @knob.width).to_f) * 100
    @display.text = format("%.1f", sliderValue)
    @value = sliderValue
  end
  
  def sliderValue
    @lowBound + (@highBound - @lowBound) * (@percent/100)
  end
  
  def sync
    v = sliderValue
    setSlider v
    @blk && @blk.call(self, v)    
  end
  
end