# TODO: OppMove animations have their user and target swapped, and will need
#       them swapping back.

module AnimationConverter
  module_function

  def convert_old_animations_to_new
    list = pbLoadBattleAnimations
    raise "No animations found." if !list || list.length == 0

    last_move = nil   # For filename purposes
    last_version = 0
    last_type = :move

    list.each do |anim|
      next if !anim.name || anim.name == "" || anim.length <= 1

      # Get folder and filename for new PBS file
      folder = "Converted/"
      folder += (anim.name[/^Common:/]) ? "Common/" :  "Move/"
      filename = anim.name.gsub(/^Common:/, "")
      filename.gsub!(/^Move:/, "")
      filename.gsub!(/^OppMove:/, "")
      # Update record of move and version
      type = :move
      if anim.name[/^Common:/]
        type = :common
      elsif anim.name[/^OppMove:/]
        type = :opp_move
      elsif anim.name[/^Move:/]
        type = :move
      end
      if filename == anim.name
        last_version += 1
        type = last_type
        pbs_path = folder + last_move
      else
        last_move = filename
        last_version = 0
        last_type = type
        pbs_path = folder + filename
      end
      last_move = filename if !last_move
      # Generate basic animaiton properties

      new_anim = {
        :type      => type,
        :move      => last_move,
        :version   => last_version,
        :name      => filename,
        :particles => [],
        :pbs_path  => pbs_path
      }

      add_frames_to_new_anim_hash(anim, new_anim)
      add_bg_fg_commands_to_new_anim_hash(anim, new_anim)
      add_se_commands_to_new_anim_hash(anim, new_anim)

      new_anim[:particles].compact!
      GameData::Animation.register(new_anim)
      Compiler.write_battle_animation_file(new_anim[:pbs_path])
    end

  end

  def add_frames_to_new_anim_hash(anim, hash)
    # Lookup array for particle index using cel index
    index_lookup = []
    max_index = -1
    # Set up previous frame's values
    default_frame = []
    default_frame[AnimFrame::X]          = -999
    default_frame[AnimFrame::Y]          = -999
    default_frame[AnimFrame::ZOOMX]      = 100
    default_frame[AnimFrame::ZOOMY]      = 100
    default_frame[AnimFrame::BLENDTYPE]  = 0
    default_frame[AnimFrame::ANGLE]      = 0
    default_frame[AnimFrame::OPACITY]    = 255
    default_frame[AnimFrame::COLORRED]   = 0
    default_frame[AnimFrame::COLORGREEN] = 0
    default_frame[AnimFrame::COLORBLUE]  = 0
    default_frame[AnimFrame::COLORALPHA] = 0
    default_frame[AnimFrame::TONERED]    = 0
    default_frame[AnimFrame::TONEGREEN]  = 0
    default_frame[AnimFrame::TONEBLUE]   = 0
    default_frame[AnimFrame::TONEGRAY]   = 0
    default_frame[AnimFrame::PATTERN]    = 0
    default_frame[AnimFrame::PRIORITY]   = 0   # 0=back, 1=front, 2=behind focus, 3=before focus

    default_frame[AnimFrame::VISIBLE]    = 1   # Boolean
    default_frame[AnimFrame::MIRROR]     = 0   # Boolean

    default_frame[AnimFrame::FOCUS]      = 4   # 1=target, 2=user, 3=user and target, 4=screen

    default_frame[99]                    = anim.graphic

    last_frame_values = []
    # Go through each frame
    anim.length.times do |frame_num|
      frame = anim[frame_num]
      had_particles = []
      changed_particles = []
      frame.each_with_index do |cel, i|
        next if !cel
        # If the particle from the previous frame for this cel had a different
        # focus, start a new particle.
        if i > 1 && frame_num > 0 && index_lookup[i] && index_lookup[i] >= 0 &&
           last_frame_values[index_lookup[i]]
          this_graphic = (cel[AnimFrame::PATTERN] == -1) ? "USER" : (cel[AnimFrame::PATTERN] == -2) ? "TARGET" : anim.graphic
          if last_frame_values[index_lookup[i]][AnimFrame::FOCUS] != cel[AnimFrame::FOCUS] ||
             last_frame_values[index_lookup[i]][99] != this_graphic   # Graphic
            index_lookup[i] = -1
          end
        end
        # Get the particle index for this cel
        if !index_lookup[i] || index_lookup[i] < 0
          max_index += 1
          index_lookup[i] = max_index
        end
        idx = index_lookup[i]
        had_particles.push(idx)
        # i=0 for "User", i=1 for "Target"
        hash[:particles][idx] ||= { :name => "Particle #{idx}" }
        particle = hash[:particles][idx]
        last_frame = last_frame_values[idx] || default_frame.clone
        # User and target particles have specific names
        if idx == 0
          particle[:name] = "User"
        elsif idx == 1
          particle[:name] = "Target"
        else
          # Set graphic
          case cel[AnimFrame::PATTERN]
          when -1   # User's sprite
            particle[:graphic] = "USER"
            last_frame[99] = "USER"
          when -2   # Target's sprite
            particle[:graphic] = "TARGET"
            last_frame[99] = "TARGET"
          else
            particle[:graphic] = anim.graphic
            last_frame[99] = anim.graphic
          end
        end
        # Set focus for non-User/non-Target
        if idx > 1
          particle[:focus] = [:foreground, :target, :user, :user_and_target, :foreground][cel[AnimFrame::FOCUS]]
          last_frame[AnimFrame::FOCUS] = cel[AnimFrame::FOCUS]
        end

        # Copy commands across
        [
          [AnimFrame::X, :x],
          [AnimFrame::Y, :y],
          [AnimFrame::ZOOMX, :zoom_x],
          [AnimFrame::ZOOMY, :zoom_y],
          [AnimFrame::BLENDTYPE, :blending],
          [AnimFrame::ANGLE, :angle],
          [AnimFrame::OPACITY, :opacity],
          [AnimFrame::COLORRED, :color_red],
          [AnimFrame::COLORGREEN, :color_green],
          [AnimFrame::COLORBLUE, :color_blue],
          [AnimFrame::COLORALPHA, :color_alpha],
          [AnimFrame::TONERED, :tone_red],
          [AnimFrame::TONEGREEN, :tone_green],
          [AnimFrame::TONEBLUE, :tone_blue],
          [AnimFrame::TONEGRAY, :tone_gray],
          [AnimFrame::PATTERN, :frame],
          [AnimFrame::PRIORITY, :z],
          [AnimFrame::VISIBLE, :visible],   # Boolean
          [AnimFrame::MIRROR, :flip],   # Boolean
        ].each do |property|
          next if cel[property[0]] == last_frame[property[0]]
          particle[property[1]] ||= []
          val = cel[property[0]].to_i
          case property[1]
          when :x
            # TODO: What if the animation is an OppMove one? I think this should
            #       be the other way around.
            if particle[:focus] == :user_and_target
              fraction = (val - Battle::Scene::FOCUSUSER_X).to_f / (Battle::Scene::FOCUSTARGET_X - Battle::Scene::FOCUSUSER_X)
              val = (fraction * GameData::Animation::USER_AND_TARGET_SEPARATION[0]).to_i
            end
          when :y
            # TODO: What if the animation is an OppMove one? I think this should
            #       be the other way around.
            if particle[:focus] == :user_and_target
              fraction = (val - Battle::Scene::FOCUSUSER_Y).to_f / (Battle::Scene::FOCUSTARGET_Y - Battle::Scene::FOCUSUSER_Y)
              val = (fraction * GameData::Animation::USER_AND_TARGET_SEPARATION[1]).to_i
            end
          when :visible, :flip
            val = (val == 1)   # Boolean
          when :z
            next if idx <= 1   # User or target
            case val
            when 0 then val = -50 + i   # Back
            when 1 then val = 25 + i    # Front
            when 2 then val = -25 + i   # Behind focus
            when 3 then val = i         # Before focus
            end
          when :frame
            next if val < 0   # -1 is user, -2 is target
          end
          # TODO: Come up with a better way to set a particle's graphic being
          #       the user or target. Probably can't, due to overlapping cel
          #       numbers and user/target being the :graphic property which
          #       doesn't change.
          particle[property[1]].push([frame_num, 0, val])
          last_frame[property[0]] = cel[property[0]]
          changed_particles.push(idx) if !changed_particles.include?(idx)
        end
        # Remember this cel's values at this frame
        last_frame_values[idx] = last_frame
      end
      # End every particle lifetime that didn't have a corresponding cel this
      # frame
      hash[:particles].each_with_index do |particle, idx|
        next if !particle || had_particles.include?(idx)
        next if ["User", "Target"].include?(particle[:name])
        if last_frame_values[idx][AnimFrame::VISIBLE] == 1
          particle[:visible] ||= []
          particle[:visible].push([frame_num, 0, false])
          changed_particles.push(idx) if !changed_particles.include?(idx)
        end
        last_frame_values[idx][AnimFrame::VISIBLE] = 0
        next if !index_lookup.include?(idx)
        lookup_idx = index_lookup.index(idx)
        index_lookup[lookup_idx] = -1
      end
      # Add a dummy command to the user particle in the last frame if that frame
      # doesn't have any commands
      if frame_num == anim.length - 1 && changed_particles.empty?
        hash[:particles].each_with_index do |particle, idx|
          next if !particle || idx <= 1   # User or target
          # TODO: Making all non-user non-target particles invisible in the last
          #       frame isn't a perfect solution, but it's good enough to get
          #       example animation data.
          next if last_frame_values[idx][AnimFrame::VISIBLE] == 0
          particle[:visible] ||= []
          particle[:visible].push([frame_num + 1, 0, false])
        end
      end
    end

    hash[:particles][0][:focus] = :user
    hash[:particles][1][:focus] = :target

    # Adjust all x/y positions based on particle[:focus]
    hash[:particles].each do |particle|
      next if !particle
      offset_x = 0
      offset_y = 0
      case particle[:focus]
      when :user
        offset_x = -Battle::Scene::FOCUSUSER_X
        offset_y = -Battle::Scene::FOCUSUSER_Y
      when :target
        offset_x = -Battle::Scene::FOCUSTARGET_X
        offset_y = -Battle::Scene::FOCUSTARGET_Y
      when :user_and_target
        # NOTE: No change, done above.
      end
      next if offset_x == 0 && offset_y == 0
      particle[:x].each { |cmd| cmd[2] += offset_x }
      particle[:y].each { |cmd| cmd[2] += offset_y }
    end
  end

  # TODO: Haven't tested this as no Essentials animations use them.
  def add_bg_fg_commands_to_new_anim_hash(anim, new_anim)
    bg_particle = { :name => "Background", :focus => :background }
    fg_particle = { :name => "Foreground", :focus => :foreground }
    first_bg_frame = 999
    first_fg_frame = 999
    anim.timing.each do |cmd|
      case cmd.timingType
      when 1, 2, 3, 4   # BG graphic (set, move/recolour), FG graphic (set, move/recolour)
        is_bg = (cmd.timingType <= 2)
        particle = (is_bg) ? bg_particle : fg_particle
        duration = (cmd.timingType == 2) ? cmd.duration : 0
        added = false
        if cmd.name && cmd.name != ""
          particle[:graphic] ||= []
          particle[:graphic].push([cmd.frame, duration, cmd.name])
          added = true
        end
        if cmd.colorRed
          particle[:color_red] ||= []
          particle[:color_red].push([cmd.frame, duration, cmd.colorRed])
          added = true
        end
        if cmd.colorGreen
          particle[:color_green] ||= []
          particle[:color_green].push([cmd.frame, duration, cmd.colorGreen])
          added = true
        end
        if cmd.colorBlue
          particle[:color_blue] ||= []
          particle[:color_blue].push([cmd.frame, duration, cmd.colorBlue])
          added = true
        end
        if cmd.colorAlpha
          particle[:color_alpha] ||= []
          particle[:color_alpha].push([cmd.frame, duration, cmd.colorAlpha])
          added = true
        end
        if cmd.opacity
          particle[:opacity] ||= []
          particle[:opacity].push([cmd.frame, duration, cmd.opacity])
          added = true
        end
        if cmd.bgX
          particle[:x] ||= []
          particle[:x].push([cmd.frame, duration, cmd.bgX])
          added = true
        end
        if cmd.bgY
          particle[:y] ||= []
          particle[:y].push([cmd.frame, duration, cmd.bgY])
          added = true
        end
        if added
          if is_bg
            first_bg_frame = [first_bg_frame, cmd.frame].min
          else
            first_fg_frame = [first_fg_frame, cmd.frame].min
          end
        end
      end
    end
    if bg_particle.keys.length > 2
      if !bg_particle[:graphic]
        particle[:graphic] ||= []
        particle[:graphic].push([first_bg_frame, 0, "black_screen"])
      end
      new_anim[:particles].push(bg_particle)
    end
    if fg_particle.keys.length > 2
      if !fg_particle[:graphic]
        particle[:graphic] ||= []
        particle[:graphic].push([first_fg_frame, 0, "black_screen"])
      end
      new_anim[:particles].push(fg_particle)
    end
  end

  def add_se_commands_to_new_anim_hash(anim, new_anim)
    anim.timing.each do |cmd|
      next if cmd.timingType != 0   # Play SE
      particle = new_anim[:particles].last
      if particle[:name] != "SE"
        particle = { :name => "SE" }
        new_anim[:particles].push(particle)
      end
      # Add command
      if cmd.name && cmd.name != ""
        particle[:se] ||= []
        particle[:se].push([cmd.frame, 0, cmd.name, cmd.volume, cmd.pitch])
      else   # Play user's cry
        particle[:user_cry] ||= []
        particle[:user_cry].push([cmd.frame, 0, cmd.volume, cmd.pitch])
      end
    end
  end
end

#===============================================================================
# Add to Debug menu.
#===============================================================================
# MenuHandlers.add(:debug_menu, :convert_anims, {
#   "name"        => "Convert old animation to PBS files",
#   "parent"      => :main,
#   "description" => "This is just for the sake of having lots of example animation PBS files.",
#   "effect"      => proc {
#     AnimationConverter.convert_old_animations_to_new
#   }
# })
