require 'ams_SmoothZoom.rb'

unless defined?(AMS::SmoothZoom)
  msg = "Cannot load 'AMS Smooth Zoom' extension. "
  msg << "Ensure that SketchUp meets all compatibility demands of the plugin!"
  raise(LoadError, msg, caller)
end

module AMS::SmoothZoom

  MIN_TRANSITION_DIST = 1.0e-4
  MIN_HEIGHT = 0.001

  @block_event = 1
  @timer = nil

  @time = 0.0
  @dest_pt = nil
  @vec = nil
  @dist = 0
  @rot_dir = 1
  @speed = 0
  @orig_speed = 0
  @eye0 = nil
  @transition = false
  @view = Sketchup.active_model.active_view

  @options = {
    :version         => VERSION,
    :enabled         => true,
    :intensity       => 0.8,
    :transition_time => 1.5,
    :fps             => 25
  }

  @time_step = 1.0 / @options[:fps]

  @update_proc = Proc.new {
    next unless @transition
    view = Sketchup.active_model.active_view
    camera = @view.camera
    @time += @time_step
    if @time > @options[:transition_time]
      #~ camera.set(@dest_pt, @dest_pt + (camera.target - camera.eye), camera.up)
      stop_transition
      next
    end
    m = @options[:transition_time]
    r = @time.to_f / m
    d = @dist - @orig_speed * m * 0.5
    @speed = @rot_dir * Math::PI * d * 0.5 * Math.sin(Math::PI * r) / m + @orig_speed - @orig_speed * r
    cs = @speed * @time_step
    if camera.perspective?
      new_eye = camera.eye + Geom::Vector3d.new(cs * @vec.x, cs * @vec.y, cs * @vec.z)
    else
      camera.height -= cs
      r2 = @rot_dir * (0.5 - 0.5 * Math.cos(Math::PI * r))
      new_eye = @eye0 + Geom::Vector3d.new(r2 * @vec.x, r2 * @vec.y, r2 * @vec.z)
    end
    camera.set(new_eye, new_eye + (camera.target - camera.eye), camera.up)
  }

  class << self

    def swo_activate
      return if @timer
      @timer = UI.start_timer(@time_step, true) {
        @update_proc.call
      }
    end

    def swo_deactivate
      return unless @timer
      UI.stop_timer(@timer)
      @timer = nil
    end

    def swp_on_mouse_wheel_rotate(x, y, dir)
      view = Sketchup.active_model.active_view
      camera = view.camera
      ray = view.pickray(x,y)
      @rot_dir = dir
      @orig_speed = @speed
      @transition = true
      @time = 0.0
      @eye0 = camera.eye
      if camera.perspective?
        ip = view.inputpoint(x,y)
        dest_pt = ip.position
        # Sometimes the dest_pt may appear behind the camera. If that happens, we have to reverse it.
        cam_tra = Geom::Transformation.new(camera.xaxis, camera.zaxis, camera.yaxis, camera.eye)
        loc_dest_pt = dest_pt.transform(cam_tra.inverse)
        if loc_dest_pt.y < 0
          loc_dest_pt.y = -loc_dest_pt.y
          dest_pt = loc_dest_pt.transform(cam_tra)
        end
        d = camera.eye.distance(dest_pt)
        d = MIN_TRANSITION_DIST if d < MIN_TRANSITION_DIST
        @dist = (dir == 1 ? d * @options[:intensity] : d / (1 - @options[:intensity]) - d)
        @vec = ray[1]
        mag = @dist * @rot_dir
        @dest_pt = camera.eye + Geom::Vector3d.new(@vec.x * mag, @vec.y * mag, @vec.z * mag)
      else
        @vec = (ray[0] - camera.eye)
        d = camera.height - MIN_HEIGHT
        @dist = (dir == 1 ? d * @options[:intensity] : d / (1 - @options[:intensity]) - d)
        @dest_pt = ray[0]
      end
      @block_event
    end

    def swp_on_mbutton_down(x,y)
      stop_transition
      0
    end

    def swp_on_lbutton_down(x,y)
      stop_transition
      0
    end

    def swp_on_rbutton_down(x,y)
      stop_transition
      0
    end

    def swp_on_command(id)
      stop_transition
      0
    end

=begin
    def swo_on_mouse_move(x,y)
      return 0
      kbrd = AMS::Keyboard
      if kbrd.shift_down? && kbrd.control_up? && kbrd.menu_up? && kbrd.key_down?('space')
        # Add feature to look around.
      elsif kbrd.shift_up? && kbrd.control_down? && kbrd.menu_up? && kbrd.key_down?('space')
        # Add feature to tilt camera.
      end
    end
=end

    def swo_on_mouse_enter(x,y)
      @block_event = 1
    end

    def swo_on_mouse_leave(x,y)
      @block_event = 0
    end

    def intensity
      @options[:intensity]
    end

    def intensity=(value)
      @options[:intensity] = AMS.clamp(value.to_f, 0.01, 0.99)
    end

    def transition_time
      @options[:transition_time]
    end

    def transition_time=(value)
      @options[:transition_time] = AMS.clamp(value.to_f, 0.1, 100)
    end

    def fps
      @options[:fps]
    end

    def fps=(value)
      @options[:fps] = AMS.clamp(value.to_i, 1, 100)
      @time_step = 1.0 / @options[:fps]
      if @timer
        swo_deactivate
        swo_activate
      end
    end

    def save_options
      Sketchup.write_default('Plugins_ams', 'Smooth Zoom', @options.inspect.inspect[1..-2])
    end

    def load_options
      # Load options
      v = Sketchup.read_default('Plugins_ams', 'Smooth Zoom', '{}')
      begin
        res = eval(v)
        if res.is_a?(Hash) && res.size == @options.size && res[:version] == VERSION
          @options.merge!(res)
        end
      rescue Exception => e
        # Do nothing; use default options.
      end
      # Update options
      self.intensity = @options[:intensity]
      self.transition_time = @options[:transition_time]
      self.fps = @options[:fps]
      self.enabled = @options[:enabled]
    end

    def options
      @options
    end

    def enabled=(state)
      @options[:enabled] = state ? true : false
      if state
        AMS::Sketchup.add_observer(self)
      else
        AMS::Sketchup.remove_observer(self)
      end
    end

    def enabled?
      @options[:enabled]
    end

    def stop_transition
      @time = 0.0
      @dest_pt = nil
      @vec = nil
      @dist = 0
      @rot_dir = 1
      @speed = 0
      @orig_speed = 0
      @eye0 = nil
      @transition = false
    end

  end # class << self
end # module AMS::SmoothZoom


unless file_loaded?(__FILE__)

  smooth_zoom = AMS::SmoothZoom
  smooth_zoom.load_options

  menu = UI.menu('Plugins').add_submenu('AMS Smooth Zoom')

  item = menu.add_item('Enable'){
    smooth_zoom.enabled = !smooth_zoom.enabled?
    smooth_zoom.save_options
  }
  menu.set_validation_proc(item){
    smooth_zoom.enabled? ? MF_CHECKED : MF_UNCHECKED
  }

  item = menu.add_item('Options'){
    prompts = ['Intensity', 'Transition Time', 'Update FPS']
    defaults = [
      sprintf("%0.1f", smooth_zoom.options[:intensity]),
      sprintf("%0.2f", smooth_zoom.options[:transition_time]),
      smooth_zoom.options[:fps]
    ]
    list1 = '0.1|0.2|0.3|0.4|0.5|0.6|0.7|0.8|0.9'
    list2 = '0.10|0.25|0.50|0.75|1.00|1.25|1.50|1.75|2.00|3.00|4.00|5.00|6.00|7.00|8.00|9.00|10.00'
    list3 = '5|10|15|20|25|30|35|40|45|50|55|60'
    list = [list1, list2, list3]
    input = UI.inputbox(prompts, defaults, list, 'Smooth Zoom Options')
    next unless input
    smooth_zoom.intensity = input[0].to_f
    smooth_zoom.transition_time = input[1].to_f
    smooth_zoom.fps = input[2].to_i
    smooth_zoom.save_options
  }

  file_loaded(__FILE__)
end
