class SpriteAnimation
  @@_animations      = []
  @@_reference_count = {}

  def initialize(sprite)
    @sprite = sprite
  end

  %w[
     x y ox oy viewport flash src_rect opacity tone
  ].each_with_index do |s, _i|
  eval <<-__END__

  def #{s}(*arg)
    @sprite.#{s}(*arg)
  end

  __END__
  end

  def self.clear
    @@_animations.clear
  end

  def dispose
    dispose_animation
    dispose_loop_animation
  end

  def animation(animation, hit, height = 3)
    dispose_animation
    @_animation = animation
    return if @_animation == nil
    @_animation_hit      = hit
    @_animation_height   = height
    @_animation_duration = @_animation.frame_max
    fr = 20
    if @_animation.name[/\[\s*(\d+?)\s*\]\s*$/]
      fr = $~[1].to_i
    end
    @_animation_frame_skip = Graphics.frame_rate / fr
    animation_name = @_animation.animation_name
    animation_hue  = @_animation.animation_hue
    bitmap = pbGetAnimation(animation_name, animation_hue)
    if @@_reference_count.include?(bitmap)
      @@_reference_count[bitmap] += 1
    else
      @@_reference_count[bitmap] = 1
    end
    @_animation_sprites = []
    if @_animation.position != 3 || !@@_animations.include?(animation)
      16.times do
        sprite = ::Sprite.new(self.viewport)
        sprite.bitmap = bitmap
        sprite.visible = false
        @_animation_sprites.push(sprite)
      end
      unless @@_animations.include?(animation)
        @@_animations.push(animation)
      end
    end
    update_animation
  end

  def loop_animation(animation)
    return if animation == @_loop_animation
    dispose_loop_animation
    @_loop_animation = animation
    return if @_loop_animation == nil
    @_loop_animation_index = 0
    fr = 20
    if @_animation.name[/\[\s*(\d+?)\s*\]\s*$/]
      fr = $~[1].to_i
    end
    @_loop_animation_frame_skip = Graphics.frame_rate / fr
    animation_name = @_loop_animation.animation_name
    animation_hue  = @_loop_animation.animation_hue
    bitmap = pbGetAnimation(animation_name, animation_hue)
    if @@_reference_count.include?(bitmap)
      @@_reference_count[bitmap] += 1
    else
      @@_reference_count[bitmap] = 1
    end
    @_loop_animation_sprites = []
    16.times do
      sprite = ::Sprite.new(self.viewport)
      sprite.bitmap = bitmap
      sprite.visible = false
      @_loop_animation_sprites.push(sprite)
    end
    update_loop_animation
  end

  def dispose_animation
    return if @_animation_sprites == nil
    sprite = @_animation_sprites[0]
    if sprite != nil
      @@_reference_count[sprite.bitmap] -= 1
      if @@_reference_count[sprite.bitmap] == 0
        sprite.bitmap.dispose
      end
    end
    for sprite in @_animation_sprites
      sprite.dispose
    end
    @_animation_sprites = nil
    @_animation = nil
  end

  def dispose_loop_animation
    return if @_loop_animation_sprites == nil
    sprite = @_loop_animation_sprites[0]
    if sprite != nil
      @@_reference_count[sprite.bitmap] -= 1
      if @@_reference_count[sprite.bitmap] == 0
        sprite.bitmap.dispose
      end
    end
    for sprite in @_loop_animation_sprites
      sprite.dispose
    end
    @_loop_animation_sprites = nil
    @_loop_animation = nil
  end

  def active?
    return @_loop_animation_sprites != nil || @_animation_sprites != nil
  end

  def effect?
    return @_animation_duration > 0
  end

  def update
    if @_animation != nil
      quick_update = true
      if Graphics.frame_count % @_animation_frame_skip == 0
        @_animation_duration -= 1
        quick_update = false
      end
      update_animation(quick_update)
    end
    if @_loop_animation != nil
      quick_update = (Graphics.frame_count % @_loop_animation_frame_skip != 0)
      update_loop_animation(quick_update)
      if !quick_update
        @_loop_animation_index += 1
        @_loop_animation_index %= @_loop_animation.frame_max
      end
    end
  end

  def update_animation(quick_update = false)
    if @_animation_duration <= 0
      dispose_animation
      return
    end
    frame_index = @_animation.frame_max - @_animation_duration
    cell_data   = @_animation.frames[frame_index].cell_data
    position    = @_animation.position
    animation_set_sprites(@_animation_sprites, cell_data, position, quick_update)
    return if quick_update
    for timing in @_animation.timings
      next if timing.frame != frame_index
      animation_process_timing(timing, @_animation_hit)
    end
  end

  def update_loop_animation(quick_update = false)
    frame_index = @_loop_animation_index
    cell_data   = @_loop_animation.frames[frame_index].cell_data
    position    = @_loop_animation.position
    animation_set_sprites(@_loop_animation_sprites, cell_data, position, quick_update)
    return if quick_update
    for timing in @_loop_animation.timings
      next if timing.frame != frame_index
      animation_process_timing(timing, true)
    end
  end

  def animation_set_sprites(sprites, cell_data, position, quick_update = false)
    sprite_x = 320
    sprite_y = 240
    if position == 3
      if self.viewport != nil
        sprite_x = self.viewport.rect.width / 2
        sprite_y = self.viewport.rect.height - 160
      end
    else
      sprite_x = self.x - self.ox + self.src_rect.width / 2
      sprite_y = self.y - self.oy
      sprite_y += self.src_rect.height / 2 if position == 1
      sprite_y += self.src_rect.height if position == 2
    end
    for i in 0..15
      sprite = sprites[i]
      pattern = cell_data[i, 0]
      if sprite == nil || pattern == nil || pattern == -1
        sprite.visible = false if sprite != nil
        next
      end
      sprite.x          = sprite_x + cell_data[i, 1]
      sprite.y          = sprite_y + cell_data[i, 2]
      next if quick_update
      sprite.visible    = true
      sprite.src_rect.set(pattern % 5 * 192, pattern / 5 * 192, 192, 192)
      case @_animation_height
      when 0 then sprite.z  = 1
      when 1 then sprite.z  = sprite.y+32+15
      when 2 then sprite.z  = sprite.y+32+32+17
      else        sprite.z  = 2000
      end
      sprite.ox         = 96
      sprite.oy         = 96
      sprite.zoom_x     = cell_data[i, 3] / 100.0
      sprite.zoom_y     = cell_data[i, 3] / 100.0
      sprite.angle      = cell_data[i, 4]
      sprite.mirror     = (cell_data[i, 5] == 1)
      sprite.tone       = self.tone
      sprite.opacity    = cell_data[i, 6] * self.opacity / 255.0
      sprite.blend_type = cell_data[i, 7]
    end
  end

  def animation_process_timing(timing, hit)
    if timing.condition == 0 ||
       (timing.condition == 1 && hit == true) ||
       (timing.condition == 2 && hit == false)
      if timing.se.name != ""
        se = timing.se
        pbSEPlay(se)
      end
      case timing.flash_scope
      when 1
        self.flash(timing.flash_color, timing.flash_duration * 2)
      when 2
        if self.viewport != nil
          self.viewport.flash(timing.flash_color, timing.flash_duration * 2)
        end
      when 3
        self.flash(nil, timing.flash_duration * 2)
      end
    end
  end

  def x=(x)
    sx = x - self.x
    return if sx == 0
    if @_animation_sprites != nil
      for i in 0..15
        @_animation_sprites[i].x += sx
      end
    end
    if @_loop_animation_sprites != nil
      for i in 0..15
        @_loop_animation_sprites[i].x += sx
      end
    end
  end

  def y=(y)
    sy = y - self.y
    return if sy == 0
    if @_animation_sprites != nil
      for i in 0..15
        @_animation_sprites[i].y += sy
      end
    end
    if @_loop_animation_sprites != nil
      for i in 0..15
        @_loop_animation_sprites[i].y += sy
      end
    end
  end
end



module RPG
  class Sprite < ::Sprite
    def initialize(viewport = nil)
      super(viewport)
      @_whiten_duration    = 0
      @_appear_duration    = 0
      @_escape_duration    = 0
      @_collapse_duration  = 0
      @_damage_duration    = 0
      @_animation_duration = 0
      @_blink              = false
      @animations     = []
      @loopAnimations = []
    end

    def dispose
      dispose_damage
      dispose_animation
      dispose_loop_animation
      super
    end

    def whiten
      self.blend_type     = 0
      self.color.set(255, 255, 255, 128)
      self.opacity        = 255
      @_whiten_duration   = 16
      @_appear_duration   = 0
      @_escape_duration   = 0
      @_collapse_duration = 0
    end

    def appear
      self.blend_type     = 0
      self.color.set(0, 0, 0, 0)
      self.opacity        = 0
      @_appear_duration   = 16
      @_whiten_duration   = 0
      @_escape_duration   = 0
      @_collapse_duration = 0
    end

    def escape
      self.blend_type     = 0
      self.color.set(0, 0, 0, 0)
      self.opacity        = 255
      @_escape_duration   = 32
      @_whiten_duration   = 0
      @_appear_duration   = 0
      @_collapse_duration = 0
    end

    def collapse
      self.blend_type     = 1
      self.color.set(255, 64, 64, 255)
      self.opacity        = 255
      @_collapse_duration = 48
      @_whiten_duration   = 0
      @_appear_duration   = 0
      @_escape_duration   = 0
    end

    def damage(value, critical)
      dispose_damage
      damage_string = (value.is_a?(Numeric)) ? value.abs.to_s : value.to_s
      bitmap = Bitmap.new(160, 48)
      bitmap.font.name = "Arial Black"
      bitmap.font.size = 32
      bitmap.font.color.set(0, 0, 0)
      bitmap.draw_text(-1, 12-1, 160, 36, damage_string, 1)
      bitmap.draw_text(+1, 12-1, 160, 36, damage_string, 1)
      bitmap.draw_text(-1, 12+1, 160, 36, damage_string, 1)
      bitmap.draw_text(+1, 12+1, 160, 36, damage_string, 1)
      if value.is_a?(Numeric) && value < 0
        bitmap.font.color.set(176, 255, 144)
      else
        bitmap.font.color.set(255, 255, 255)
      end
      bitmap.draw_text(0, 12, 160, 36, damage_string, 1)
      if critical
        bitmap.font.size = 20
        bitmap.font.color.set(0, 0, 0)
        bitmap.draw_text(-1, -1, 160, 20, "CRITICAL", 1)
        bitmap.draw_text(+1, -1, 160, 20, "CRITICAL", 1)
        bitmap.draw_text(-1, +1, 160, 20, "CRITICAL", 1)
        bitmap.draw_text(+1, +1, 160, 20, "CRITICAL", 1)
        bitmap.font.color.set(255, 255, 255)
        bitmap.draw_text(0, 0, 160, 20, "CRITICAL", 1)
      end
      @_damage_sprite = ::Sprite.new(self.viewport)
      @_damage_sprite.bitmap = bitmap
      @_damage_sprite.ox     = 80
      @_damage_sprite.oy     = 20
      @_damage_sprite.x      = self.x
      @_damage_sprite.y      = self.y - self.oy / 2
      @_damage_sprite.z      = 3000
      @_damage_duration      = 40
    end

    def pushAnimation(array, anim)
      for i in 0...array.length
        next if array[i] && array[i].active?
        array[i] = anim
        return
      end
      array.push(anim)
    end

    def animation(animation, hit, height = 3)
      anim = SpriteAnimation.new(self)
      anim.animation(animation,hit,height)
      pushAnimation(@animations,anim)
    end

    def loop_animation(animation)
      anim = SpriteAnimation.new(self)
      anim.loop_animation(animation)
      pushAnimation(@loopAnimations,anim)
    end

    def dispose_damage
      return if @_damage_sprite == nil
      @_damage_sprite.bitmap.dispose
      @_damage_sprite.dispose
      @_damage_sprite   = nil
      @_damage_duration = 0
    end

    def dispose_animation
      for a in @animations
        a.dispose_animation if a
      end
      @animations.clear
    end

    def dispose_loop_animation
      for a in @loopAnimations
        a.dispose_loop_animation if a
      end
      @loopAnimations.clear
    end

    def blink_on
      return if @_blink
      @_blink = true
      @_blink_count = 0
    end

    def blink_off
      return unless @_blink
      @_blink = false
      self.color.set(0, 0, 0, 0)
    end

    def blink?
      return @_blink
    end

    def effect?
      return true if @_whiten_duration > 0
      return true if @_appear_duration > 0
      return true if @_escape_duration > 0
      return true if @_collapse_duration > 0
      return true if @_damage_duration > 0
      for a in @animations
        return true if a.effect?
      end
      return false
    end

    def update
      super
      if @_whiten_duration > 0
        @_whiten_duration -= 1
        self.color.alpha = 128 - (16 - @_whiten_duration) * 10
      end
      if @_appear_duration > 0
        @_appear_duration -= 1
        self.opacity = (16 - @_appear_duration) * 16
      end
      if @_escape_duration > 0
        @_escape_duration -= 1
        self.opacity = 256 - (32 - @_escape_duration) * 10
      end
      if @_collapse_duration > 0
        @_collapse_duration -= 1
        self.opacity = 256 - (48 - @_collapse_duration) * 6
      end
      if @_damage_duration > 0
        @_damage_duration -= 1
        case @_damage_duration
        when 38..39
          @_damage_sprite.y -= 4
        when 36..37
          @_damage_sprite.y -= 2
        when 34..35
          @_damage_sprite.y += 2
        when 28..33
          @_damage_sprite.y += 4
        end
        @_damage_sprite.opacity = 256 - (12 - @_damage_duration) * 32
        if @_damage_duration == 0
          dispose_damage
        end
      end
      for a in @animations
        a.update
      end
      for a in @loopAnimations
        a.update
      end
      if @_blink
        @_blink_count = (@_blink_count + 1) % 32
        if @_blink_count < 16
          alpha = (16 - @_blink_count) * 6
        else
          alpha = (@_blink_count - 16) * 6
        end
        self.color.set(255, 255, 255, alpha)
      end
      SpriteAnimation.clear
    end

    def update_animation
      for a in @animations
        a.update_animation if a && a.active?
      end
    end

    def update_loop_animation
      for a in @loopAnimations
        a.update_loop_animation if a && a.active?
      end
    end

    def x=(x)
      for a in @animations
        a.x = x if a
      end
      for a in @loopAnimations
        a.x = x if a
      end
      super
    end

    def y=(y)
      for a in @animations
        a.y = y if a
      end
      for a in @loopAnimations
        a.y = y if a
      end
      super
    end
  end
end
