#include "Game.h"

#include "DxLib.h"

// ゲーム開始時にステージとプレイヤーを初期化し、ゲーム状態をPlayingに設定する
bool Game::Initialize()
{
    stage.Initialize();
    ResetPlay();
    gameState = Playing;
    return true;
}

// ゲームのメインループ
void Game::MainLoop()
{
    // ESCキーが押されるまで、更新 -> 描画 -> 画面反映を繰り返す
    while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        Update();

        ClearDrawScreen();
        Draw();
        ScreenFlip();
    }
}

// ゲームを最初からやり直す
void Game::ResetPlay()
{
    ResetPlayer();
}

// プレイヤーを開始位置に戻し、速度も0に初期化する
void Game::ResetPlayer()
{
    player.x = 64;
    player.y = 240;
    player.width = 26;
    player.height = 30;
    player.vx = 0;
    player.vy = 0;
    player.onGround = false;
}

// ゲームの更新処理
void Game::Update()
{
    // ゲーム状態に応じて更新処理を分岐する
    switch(gameState)
    {
    case Playing:
        UpdatePlaying();
        break;

    case GameClear:
    case GameOver:
        UpdateResult();
        break;
    }
}

// ゲームプレイ中の更新処理
void Game::UpdatePlaying()
{
    UpdatePlayerInput();
    UpdatePlayerPhysics();

    // ステージの下へ落ちたらゲームオーバー
    if (player.y > Stage::mapHeight * Stage::tileSize + 120)
    {
        gameState = GameOver;
        return;
    }

    // プレイヤーの矩形とゴールの矩形が重なったらクリア
    if (IsHitRect(PlayerRect(), stage.GetGoal()))
    {
        gameState = GameClear;
    }
}

// ゲームクリア後、またはゲームオーバー後の更新処理
void Game::UpdateResult()
{
    // クリア後、またはゲームオーバー後はRキーで最初からやり直す
    if (CheckHitKey(KEY_INPUT_R))
    {
        ResetPlay();
        gameState = Playing;
    }
}

// 入力を受け付けプレイヤーの速度を更新する
void Game::UpdatePlayerInput()
{
    // 左右キーで横方向の速度を変化させます。
    if (CheckHitKey(KEY_INPUT_LEFT))
    {
        player.vx -= accel;
    }
    else
    if (CheckHitKey(KEY_INPUT_RIGHT))
    {
        player.vx += accel;
    }
    else
    {
        if (player.vx > 0) player.vx -= friction;
        else
        if (player.vx < 0) player.vx += friction;
    }

    // 速度が大きくなりすぎないように上限をかける
    player.vx = Clamp(player.vx, -maxSpeed, maxSpeed);

    // 地面にいるときだけジャンプを受け付ける
    if (player.onGround && CheckHitKey(KEY_INPUT_Z))
    {
        player.vy = jumpPower;
        player.onGround = false;
    }
}

// プレイヤーの物理演算を行い、位置を更新する
void Game::UpdatePlayerPhysics()
{
    // 重力で落下速度を増やし、速く落ちすぎないように制限する
    player.vy += gravity;
    player.vy = Clamp(player.vy, -30, maxFallSpeed);

    // 横と縦を分けて動かすと、床や壁にめり込んだときの戻し処理が簡単になる
    MoveHorizontal(player.vx);

    player.onGround = false;
    MoveVertical(player.vy);
}

// 横方向の移動処理
void Game::MoveHorizontal(int move)
{
    if (move == 0) return;

    const int step = (move > 0) ? 1 : -1;

    // 1ピクセルずつ動かし、壁に当たった瞬間に直前の位置へ戻します。
    for (int i = 0; i < AbsInt(move); i++)
    {
        player.x += step;

        if (stage.IsSolidAtRect(PlayerRect()))
        {
            player.x -= step;
            player.vx = 0;
            break;
        }
    }
}

// 縦方向の移動処理
void Game::MoveVertical(int move)
{
    if (move == 0) return;

    const int step = (move > 0) ? 1 : -1;

    // 縦方向も1ピクセルずつ動かし、床や天井に当たったら止めます。
    for (int i = 0; i < AbsInt(move); i++)
    {
        player.y += step;

        if (stage.IsSolidAtRect(PlayerRect()))
        {
            player.y -= step;
            if (step > 0) player.onGround = true;
            player.vy = 0;
            break;
        }
    }
}


// プレイヤーの当たり判定用の矩形を取得する
Rect Game::PlayerRect() const
{
    // 現在のプレイヤー位置から当たり判定用の矩形を作る
    return { player.x, player.y, player.width, player.height };
}

// ゲームの描画処理
void Game::Draw()
{
    DrawBackground();
    DrawPlaying();

    if (gameState != Playing)
    {
        DrawResult();
    }
}

// 背景の描画処理
void Game::DrawBackground() const
{
    DrawBox(0, 0, screenWidth, screenHeight, GetColor(130, 190, 255), TRUE);
    DrawBox(0, 340, screenWidth, screenHeight, GetColor(170, 220, 160), TRUE);
}

// ゲームプレイ中の描画処理
void Game::DrawPlaying() const
{
    stage.Draw();
    DrawPlayer();
    DrawHud();
}

// ゲームクリア後、またはゲームオーバー後の描画処理
void Game::DrawResult() const
{
    DrawBox(90, 160, 550, 305, GetColor(255, 255, 255), TRUE);
    DrawBox(90, 160, 550, 305, GetColor(20, 20, 30), FALSE);

    switch (gameState)
    {
    case GameClear:
        DrawString(252, 190, L"GAME CLEAR", GetColor(20, 20, 30));
        break;
    case GameOver:
        DrawString(250, 190, L"GAME OVER", GetColor(20, 20, 30));
        break;
    }

    DrawString(170, 245, L"R: RETRY", GetColor(20, 20, 30));
}

// プレイヤーの描画処理
void Game::DrawPlayer() const
{
    DrawBox(player.x, player.y, player.x + player.width, player.y + player.height,
        GetColor(255, 120, 120), TRUE);
}

// ゲーム中の操作説明を画面左上に描画する
void Game::DrawHud() const
{
    DrawString(12, 10, L"LEFT/RIGHT: MOVE", GetColor(255, 255, 255));
    DrawString(12, 30, L"Z: JUMP", GetColor(255, 255, 255));
    DrawString(12, 50, L"R: RETRY AFTER CLEAR/OVER", GetColor(255, 255, 255));
}
