# Internal representation of a tracker point with keyframes. A Tracker is an array of Keyframe objects
# with a few methods added for convenience
class Tracksperanto::Tracker
  include Tracksperanto::Casts, Tracksperanto::BlockInit, Comparable, Enumerable
  
  # Contains the name of the tracker
  attr_accessor :name
  cast_to_string :name
  
  class Dupe < RuntimeError
  end
  
  def initialize(object_attribute_hash = {})
    @name = "Tracker"
    @frame_table = {}
    super
  end
  
  # Replace all the keyframes of the tracker with new ones
  def keyframes=(new_kf_array)
    @frame_table = {}
    new_kf_array.each do | keyframe |
      @frame_table[keyframe.frame] = keyframe.abs_x, keyframe.abs_y, keyframe.residual
    end
  end
  
  # Returns an array of keyframes, ordered by their frame value.
  # WARNING: in older Tracksperanto versions the returned value was
  # a handle into the tracker object. Now it returns a copy of the tracker's keyframes
  # and modifications done to the array WILL NOT propagate to the tracker object itself.
  # If you need to replace a keyframe, use set(keyframe). If you need to replace the whole
  # keyframes array, use keyframes=(new_keyframes)
  def keyframes
    to_a
  end
  
  # Sets a keyframe. If an old keyframe exists at this frame offset it will be replaced.
  def set(kf)
    @frame_table[kf.frame] = [kf.abs_x, kf.abs_y, kf.residual]
  end
  
  # Iterates over keyframes
  def each
    ordered_frame_numbers.each do | frame |
      yield(extract_keyframe(frame))
    end
  end
  
  # Trackers sort by the position of the first keyframe
  def <=>(other_tracker)
    self.first_frame <=> other_tracker.first_frame
  end
  
  # Returns the first frame number this tracker contains (where the first keyframe is)
  def first_frame
    ordered_frame_numbers[0]
  end
  
  # Automatically truncate spaces in the tracker
  # name and replace them with underscores
  def name=(n)
    @name = n.to_s.gsub(/(\s+)/, '_')
  end
   
  # Create and save a keyframe in this tracker. The options hash is the same
  # as the one for the Keyframe constructor
  def keyframe!(options)
    kf = Tracksperanto::Keyframe.new(options)
    set(kf)
  end
  
  # Tells whether this tracker is empty or not
  def empty?
    @frame_table.empty?
  end
  
  # Fetch a keyframe at a spefiic offset.
  # NOTICE: not at a specific **frame** but at an offset in the frames table.
  # The frames table is ordered by frame order. If you need to fetch a keyframe at a specific frame,
  # use at_frame
  def [](offset)
    frame = ordered_frame_numbers[offset]
    return nil if frame.nil?
    
    extract_keyframe(frame)
  end
  
  # Fetch a keyframe at a specific frame. If no such frame exists nil will be returned
  def at_frame(at_frame)
    extract_keyframe(at_frame)
  end
  
  # Add a keyframe. Will raise a Dupe exception if the keyframe to be set will overwrite another one
  def push(kf)
    raise Dupe, "The tracker #{name.inspect} already contains a keyframe at #{kf.frame}" if @frame_table[kf.frame]
    set(kf)
  end
  
  def inspect
    "<T #{name.inspect} with #{length} keyframes>"
  end
  
  # Used in tests
  def to_ruby
    buf = []
    buf.push("Tracksperanto::Tracker.new(:name => %s) do |t|" % name.inspect)
    each do | kf |
      buf.push("  t.keyframe!(:frame => %d, :abs_x => %0.05f, :abs_y => %0.05f, :residual => %0.05f)" % [kf.frame, kf.abs_x, kf.abs_y, kf.residual])
    end
    buf.push("end")
    buf.join("\n")
  end
  
  # Tells how many keyframes this tracker contains
  def length
    @frame_table.length
  end
  
  # Removes all the keyframes in the tracker
  def clear
    @frame_table = {}
  end
  
  private
  
  def ordered_frame_numbers
    @frame_table.keys.sort
  end
  
  def extract_keyframe(frame)
    triplet = @frame_table[frame]
    return nil unless triplet
    
    Tracksperanto::Keyframe.new(:frame => frame, :abs_x => triplet[0], :abs_y => triplet[1], :residual => triplet[2])
  end
end