module Miyako
  class Unit
    extend Forwardable

    def initialize(name, ow, oh, dp, layout_x = :center, layout_y = :middle, amount = 8)
      @spr = Sprite.new(name)
      @spr.ow = ow
      @spr.oh = oh
      @spr.dp = dp
      @spr.set_layout(layout_x, layout_y)
      @move_amt = amount
      @live = true
    end

    def dead
      @live = false
      hide
    end

    def live
      @live = true
      show
    end

    def live?
      return @live
    end

    def update
    end

    def move(dx)
    end

    def_delegators(:@spr, :show, :hide)
  end

  class Shot < Unit
    extend Forwardable

    def initialize(fname, x = 0, y = 0, dir = -1)
      super(fname, 4, 8, 80)
      @spr.move_to(x, y - @spr.oh)
      @dir  = dir
      @wait = 2
      @cnt  = @wait
      @moving = false
      @se   = Audio::SE.new("shot.wav")
      @se.setVolume(64)
    end

    def set_position(x, y)
      @spr.move_to(x * @move_amt, y * @move_amt)
    end

    def post_shot(score)
      stop
      hide
      return score
    end

    def update(enemys)
      return 0 unless @moving
      score = enemys.inject(0){|r, e| r += collision_check(e) }
      return post_shot(score) unless score == 0
      @cnt -= 1
      return 0 unless @cnt == 0
      return post_shot(0) if move(@move_amt * @dir) == -1
      @cnt = @wait
      return 0
    end

    def shot(x, y)
      return if @moving
      @spr.move_to(x, y - @spr.oh)
      show
      @moving = true
      @cnt = @wait
      @se.play
    end

    def stop?
      return !(@moving)
    end

    def stop
      @moving = false
    end

    def move(dy)
      return @spr.round_y(@move_amt * dy, false)
    end

    def collision_check(enemy)
      return 0 unless enemy.live?
      if enemy.collision?(@spr)
        enemy.dead
        hide
        @moving = false
        return enemy.score
      end
      return 0
    end
  end

  class MyShip < Unit
    extend Forwardable

    def initialize
      super("myship.bmp", 32, 32, 100, :center, [:bottom, 32])
      @shot_num = 4
      @shots = Array.new(@shot_num){ Shot.new("shot.png", 0, @spr.y) }
    end

    def reset
      @shots.each{|s| s.stop}
    end

    def update(score, enemys)
      score += @shots.inject(0){|r, s| r += s.update(enemys)}
      if Input.pushed_any?(:btn1)
        sh = @shots.find{|s| s.stop? }
        sh.shot(@spr.x + (@spr.ow >> 1), @spr.y) if sh
      end
      move(Input.triggerAmount[0])
      return score
    end

    def move(dx)
      @spr.round_x(@move_amt * dx)
    end

    def_delegators(:@spr, :x, :y, :ow, :oh)
  end

  class Enemy < Unit
    extend Forwardable

    attr_reader :score

    def initialize(score = 100)
      super("enemy.bmp", 32, 32, 150)
      @pre_x = 0
      @pre_y = 0
      @score = score
      @wait = 4
      @cnt  = @wait
      @dx   = 1
      @se   = Audio::SE.new("exp.wav")
      @se.setVolume(64)
    end

    def set_position(x, y)
      @spr.move_to(x * @move_amt, y * @move_amt)
      @pre_x = x
      @pre_y = y
    end

    def reset
      @spr.move_to(@pre_x * @move_amt, @pre_y * @move_amt)
      @dx = 1
      self.show
    end

    def update
      @cnt -= 1
      return unless @cnt == 0
      @dx, dy = move(@dx, 0)
      @cnt = @wait
    end

    def move(dx, dy)
      px, py = @spr.round(@move_amt * dx, @move_amt * dy)
      return [px == 0 ? dx : -dx, py == 0 ? dy : -dy]
    end

    def collision?(spr)
      return @spr.collision?(spr)
    end

    def show
      super
      @live = true
    end

    def hide
      super
      @live = false
    end

    def dead
      super
      @se.play
    end
  end

  class ScoreBoard
    extend Forwardable

    def initialize
      @fsize = 16
      font = Font.sans_serif
      font.size = @fsize
      @spr = Sprite.new([(@fsize * 13) >> 1, font.line_skip])
      @spr.font = font
      @spr.font.setColor(Color::WHITE)
      @spr.dp = 200
      @spr.set_layout(:center, [:top, 16])
    end

    def update(score)
      @spr.draw_rect(Rect.new(0,0,@spr.ow,@spr.oh), Color::BLACK, :fill)
      @spr.draw_text(sprintf("SCORE %07d", score))
    end

    def_delegators(:@spr, :show, :hide)
  end

  class StageBoard
    extend Forwardable

    def initialize
      @fsize = 16
      font = Font.sans_serif
      font.size = @fsize
      @spr = Sprite.new([(@fsize * 9) >> 1, font.line_skip])
      @spr.font = font
      @spr.font.setColor(:green)
      @spr.dp = 200
      @spr.set_layout([:left, @fsize], [:top, @fsize])
      @stage  = 0
    end

    def update
      @stage += 1
      @spr.draw_rect(Rect.new(0,0,@spr.ow,@spr.oh), :black, :fill)
      @spr.draw_text(sprintf("STAGE %03d", @stage))
    end

    def_delegators(:@spr, :show, :hide)
  end

  class MainComponent
    include Singleton

    def initialize
      @space = Plane.new("space.bmp")
      @space.dp = -100
      @ship = MyShip.new
      @score = 0
      @score_board = ScoreBoard.new
      @stage_board = StageBoard.new
      @enemy_num = 20
      @enemy_x = 4
      @enemy_y = 8
      @enemy_w = 5
      @enemy_h = 4
      @enemys = Array.new(@enemy_num){ Enemy.new }
      @enemy_num.times{|e|
        x = e % @enemy_w
        y = e / @enemy_w
        @enemys[e].set_position(@enemy_x + x * 8 , @enemy_y + y * 8)
      }
    end

    def setup
      @space.show
      @score_board.show
      @ship.show
      @enemys.each{|e| e.live}
      @stage_board.update
      @stage_board.show
    end

    def reset
      @ship.reset
      @enemys.each{|e| e.reset}
    end

    def next_stage
      reset
      @stage_board.update
    end

    def update(now, prev, upper)
      return "End" if Input.pushed_any?(:esc) or Input.quit?
      @score = @ship.update(@score, @enemys)
      return "Clear" if @enemys.inject(0){|r, i| r += i.live? ? 1 : 0 } == 0
#      return "Clear" if @enemys.inject(0){|r, i| r += 1 if i.live } == 0 # error! why?
      @enemys.each{|e| e.update}
      @score_board.update(@score)
      scroll
      return now
    end

    def final
      @enemys.each{|e| e.hide}
      @ship.hide
      @stage_board.hide
      @score_board.hide
      @space.hide
    end

    def scroll
      @space.y = @space.y - 2
    end
  end
end
