-- FlyFlapp のメイン処理
-- Satofumi KAMIMURA
-- $Id: FlyFlapp.lua 734 2009-04-15 13:36:29Z satofumi $

require("Scheduler")


-- リソースの読み出し
local a_fly = ImageSurface("resources/fly.bmp", true)
local flapper_base = ImageSurface("resources/flapper_base.bmp", true)
local flapper_hit = ImageSurface("resources/flapper_hit.bmp", true)
local flapper_radius = flapper_base:rect().w / 2
local fly_w = a_fly:rect().w
local fly_h = a_fly:rect().h

local flapp_se = SoundEffect("resources/flapp.wav")
local fall_se_file = "resources/fall.wav"


-- 画面の初期化
local screen = Screen()
screen:setClearColor(Color(0.4, 0.4, 0.4, 1.0))
screen:showCursor(false)

local input = InputHandler()

local layer_manager = LayerManager()
local layer = Layer()

-- カーソル位置を中心よりに設定する
local screen_rect = screen:rect()
input:setCursorPosition(Point(screen_rect.w / 2, screen_rect.h * 2 / 5))


-- システムの初期化
local scheduler = Scheduler:new()
math.randomseed(os.time())


function placeAFly()

   -- 画面枠付近でのクリックを避けるため、画面中心よりの位置から出現させる
   local x = ((screen_rect.w / 2) * math.random()) + (screen_rect.w / 4)
   local y = ((screen_rect.h / 2) * math.random()) + (screen_rect.h / 4)
   local fly_position = Point(x, y)

   local fly_degree = math.random() * 360.0

   return fly_position, fly_degree
end


function moveAFly(fly_position, fly_degree)

   -- !!! 未実装
   -- !!! fly_position.x, fly_position.y, fly_degree を更新すればよい
   -- !!! 画面外に出ないようにすること
end


function isFlapped(flapper, fly)

   local offset = 6             -- あたり判定の領域を少し大きくする

   if (fly.x - fly_w/2) < (flapper.x + flapper_radius/2 + offset) then
      if (fly.x + fly_w/2) > (flapper.x - flapper_radius/2 - offset) then
         if (fly.y - fly_h/2) < (flapper.y + flapper_radius/2 + offset) then
            if (fly.y + fly_h/2) > (flapper.y - flapper_radius/2 - offset) then
               return true
            end
         end
      end
   end
   return false
end


function fallFly(position)

   -- 効果音を鳴らす
   local fall_se = SoundEffect(fall_se_file)
   local x, y = se_position(position)
   fall_se:play(x, y, 0.1)

   -- 現在のハエの位置から落下させる
   while position.y < screen_rect.h do
      drawAFly(position, 0)
      position.y = position.y + 6
      Scheduler:yield()
   end
end


-- ゲーム処理
local now_playing = false
local flapper_cooldown = false
local flapped_count = 0

function game_main()

   local fly_degree = 0
   local fly_position = nil

   -- ゲームの開始待ち
   while true do

      if now_playing then
         if fly_position == nil then
            -- ハエが存在しなければ配置する
            fly_position, fly_degree = placeAFly()
         else
            -- ハエを移動させる
            moveAFly(fly_position, fly_degree)

            -- ハエを描画
            drawAFly(fly_position, fly_degree)
         end
      end

      -- ハエを叩く
      local cursor = input:cursorPosition()
      if input:leftClicked() and (not flapper_cooldown) then
         scheduler:insert(flapp)

         -- 効果音を鳴らす
         local x, y = se_position(cursor)
         flapp_se:play(x, y, 0.1)

         if now_playing then
            -- ハエとのあたり判定
            if fly_position ~= nil and isFlapped(cursor, fly_position) then
               flapped_count = flapped_count + 1

               -- ハエが落ちる処理を登録
               scheduler:insert(fallFly, fly_position)
               fly_position = nil
            end
         end
      end

      -- ハエ叩きを描画
      drawFlapper(cursor)

      Scheduler:yield()
   end
end


function se_position(position)

   -- 範囲を [-1.0, +1.0] にする
   local x = (2.0 * (position.x / screen_rect.w)) - 1.0
   local y = (2.0 * (position.y / screen_rect.h)) - 1.0

   -- 音の位置を少し中央よりにする
   return x * 0.85, y * 0.85
end


function setAlpha(alpha)

   layer:setAlpha(alpha)
   a_fly:setAlpha(alpha)
   flapper_base:setAlpha(alpha)
   flapper_hit:setAlpha(alpha)
end


local fade_loop = 25

function fadeIn()

   for i = 0, fade_loop do
      local alpha = i / fade_loop
      setAlpha(alpha)
      Scheduler:yield()
   end
end


function fadeOut()

   for i = 0, fade_loop do
      local alpha = 1.0 - (i / fade_loop)
      setAlpha(alpha)
      Scheduler:yield()
   end
end


function push_front(surface, position)

   local label = Label(surface)
   label:setPosition(position)
   layer:push_front(label)

   return label
end


-- メイン処理
function main_story()

   fadeIn()

   -- 開始メッセージの表示
   local font = Font("resources/font.ttf", 32, true)

   -- "Ready" の表示
   local ready_text = TextSurface(font, "Ready")
   local ready_label = push_front(ready_text, Point(0, 0))
   Scheduler:wait(1500)
   layer:remove(ready_label)

   -- "Go" の表示
   local go_text = TextSurface(font, "Go !")
   local go_label = push_front(go_text, Point(0, 0))
   Scheduler:wait(1300)
   layer:remove(go_label)

   -- ハエ叩き処理の開始
   now_playing = true
   -- !!! 残り何秒かを表示すべき
   Scheduler:wait(4000 * 2)
   now_playing = false

   -- "Time up!" の表示
   local timeup_text = TextSurface(font, "Timeup !")
   local timeup_label = push_front(timeup_text, Point(0, 0))
   Scheduler:wait(1300)
   layer:remove(timeup_label)

   -- "XX 匹のハエを退治しました！" の表示
   font:setFontSize(20)
   local result_text = TextSurface(font, "ハエの退治数: " .. flapped_count)
   local result_label = push_front(result_text, Point(0, 0))
   Scheduler:wait(1300)

   -- "右クリック で終了します" の表示
   local end_text = TextSurface(font, "右クリックで終了します")
   local end_position = result_label:position()
   end_position.y = end_position.y + 30
   local end_label = push_front(end_text, end_position)

   while not input:rightClicked() do
      Scheduler:yield()
   end

   fadeOut()
   layer:clear()
end


function flapp()

   flapper_cooldown = true
   Scheduler:wait(200)
   flapper_cooldown = false
end


function drawFlapper(cursor)

   local dest_rect = flapper_base:rect()
   dest_rect.x = cursor.x - flapper_radius
   dest_rect.y = cursor.y - flapper_radius

   -- flapper_cooldown で "通常", "叩き中" の切り替えを行う
   if not flapper_cooldown then
      flapper_base:draw(nil, dest_rect)
   else
      flapper_hit:draw(nil, dest_rect)
   end
end


function drawAFly(position, degree)

   local dest_rect = a_fly:rect()
   dest_rect.x = position.x - dest_rect.w / 2
   dest_rect.y = position.y - dest_rect.h / 2

   -- !!! 回転させて表示させる
   a_fly:draw(nil, dest_rect)
end


-- メインループ
local main_id = scheduler:insert(main_story)
scheduler:insert(game_main)

local quit = false
while not quit do

   screen:clear()
   input:update()

   scheduler:execute()
   if (not scheduler:isActive(main_id)) or input:isQuit() then
      quit = true
   end

   layer:draw()
   layer_manager:swap()
   delay(16)
end
