module Ray
  # Animations are changes progressively applied to an object.
  # This class should be subclassed by the actual animations.
  #
  # == Creating an animation object
  # Subclasses create a helper method in Ray::Helper to create the
  # animation:
  #   translation(:from => [0, 0], :to => [120, 120], :duration => 30)
  #   rotation(:from => 10, :to => 360, :duration => 2)
  #
  # Then, the animation isn't running yet. It is needed to call #start
  # with the target of the animation:
  #   @translation.start some_sprite
  #
  # You'd then need to call #update regularily to apply the changes.
  # For instance, in a scene, you'd do:
  #   always { @translation.update }
  #
  # (See also {AnimationList})
  #
  # Animations can be paused (#pause) and resumed (#resume) if needed.
  #
  # They also raise an :animation_end event you could use to call another event:
  #   on :animation_end, @translation do
  #     @rotation.start some_sprite
  #   end
  #
  # == Creating a custom animation class
  #   class MyOwnAnimation < Ray::Animation
  #     # Creates the helper method
  #     register_for :my_own_animation
  #
  #     # hash is the argument passed to the helper method.
  #     # target is still nil.
  #     def setup(hash)
  #       @some_param = hash[:param]
  #       self.duration = 10 # You *need* to set this
  #     end
  #
  #     # the animation has just started, target is set.
  #     def setup_target
  #     end
  #
  #     # update has been called
  #     def update_target
  #       # progression is a float between 0 and 1.
  #       target.x += @some_param * progression
  #     end
  #
  #     # the animation has just ended.
  #     def end_animation
  #     end
  #
  #     # Not required. You can get an animation cancelling the first one
  #     # using this in most animation classes.
  #     def -@
  #       my_own_animation(:param => -@some_param)
  #     end
  #   end
  class Animation
    include Ray::Helper

    def self.register_for(name)
      klass = self

      Ray::Helper.send :define_method, name do |*args|
        animation = klass.new
        animation.event_runner = event_runner
        animation.setup(*args)

        animation
      end
    end

    def initialize
      @duration = 0

      @start_time = nil
      @pause_time = nil
      @end_time   = nil

      @running = false

      @target = nil
    end

    # Starts the animation.
    # @param [Object] on The target of the animation
    # @return self
    def start(on)
      @target = on

      @start_time = Time.now
      @end_time   = @start_time + duration

      @running = true

      setup_target

      self
    end

    # Override this in subclasses.
    # Will be called by the helper method (when target is still nil).
    def setup(*args)
      raise NotImplementedError
    end

    # Method called when the animation is started.
    # Override it if needed.
    def setup_target
    end

    # Pauses the animation.
    def pause
      @pause_time = Time.now
      @running    = false

      pause_animation
    end

    # Resumes from the pause.
    # The time when the animation should end is also updated.
    def resume
      return unless @pause_time

      # Reset @start_time to compute the progression more easily.
      @end_time   = Time.now + duration - (@pause_time - @start_time)
      @start_time = @end_time - duration
      @pause_time = nil

      @running = true

      resume_animation
    end

    # Updates the target if the animation is running.
    # May also end the animation.
    def update
      if running?
        update_target

        if Time.now >= @end_time
          @running  = false
          @end_time = nil

          end_animation
          raise_event :animation_end, self if raiser_runner
        end
      end
    end

    # Registers an event making the animation repeat itself over and over
    def loop!
      on :animation_end, self do
        start @target
      end
    end

    # Registers an event making the animation bounce: when animation is
    # completed, -animation is run on the same target, and when -animation is
    # done running, animation starts again.
    #
    # @return [Ray::Animation] The reversed animation, which you need to update
    #   as well.
    def bounce!
      reverse = -self
      reverse.event_runner = event_runner

      on :animation_end, self do
        reverse.start @target
      end

      on :animation_end, reverse do
        start @target
      end

      reverse
    end

    # Override this method to apply changes to the target.
    def update_target
      raise NotImplementedError
    end

    # Override this method to do something at the end of the animation.
    def end_animation
    end

    # Override this if you need to do something when the animation is paused
    # (for instance, paussing animations used by this object).
    def pause_animation
    end

    # Override this if you need to do something when the animation is resumed
    def resume_animation
    end

    # @return [Float, nil] The progression of the animation, between 0 and 1.
    #   Nil if the animation isn't running.
    def progression
      if running?
        if @duration.zero?
          1.0
        else
          ret = (Time.now - @start_time) / duration
          ret > 1 ? 1.0 : ret
        end
      end
    end

    # @return [Ray::Animation::Combination] Combination of self and the
    #   argument.
    def +(other)
      animation_combination(self, other)
    end

    # @return [Ray::Animation::Combination] Combination of self and the oppsite
    #  of the argument.
    def -(other)
      animation_combination(self, -other)
    end

    # @return [true, false] True if the animation is running
    def running?; @running ; end

    # @return [true, false] True if the animation is paused
    def paused?; not @pause_time.nil?; end

    # @return [Object] The target of the animation.
    attr_reader :target

    # @return [Float] Duration of the animation, in seconds.
    attr_accessor :duration

    # @return [Time] Time when the animation should end.
    attr_reader :end_time
  end
end

require 'ray/animation/block_animation'
require 'ray/animation/float_variation'
require 'ray/animation/vector_variation'
require 'ray/animation/color_variation'
require 'ray/animation/circular_motion'
require 'ray/animation/combination'
require 'ray/animation/sequence'
require 'ray/animation/sprite_animation'
