﻿#include "DxLib.h"

#include "Game.h"

// ゲーム開始時にステージとプレイヤーを初期化し、ゲーム状態をPlayingに設定する
bool Game::Initialize()
{
    if (!images.LoadAll()) return false;
    if (!stage.Initialize(L"Assets/Maps/Stage01.csv")) return false;

    StartPlay();
    return true;
}

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

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

// ゲームを開始または最初からやり直す
void Game::StartPlay()
{
    ResetPlay();
    gameState = Playing;
}

// プレイ中に変化する値を初期状態へ戻す
void Game::ResetPlay()
{
    ResetPlayer();
    cameraX = 0;
}

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

// ゲームの更新処理
void Game::Update()
{
    UpdateDebugToggle();

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

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

// F1キーを押した瞬間にデバッグ表示を切り替える
void Game::UpdateDebugToggle()
{
    const bool currentF1 = CheckHitKey(KEY_INPUT_F1) != 0;
    if (currentF1 && !previousF1)
    {
        debugVisible = !debugVisible;
    }

    previousF1 = currentF1;
}

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

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

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

void Game::UpdateCamera()
{
    const int worldWidth = Stage::mapWidth * Stage::tileSize;
    const int target = player.x - screenWidth / 2;
    cameraX = Clamp(target, 0, worldWidth - screenWidth);
}

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

// 入力を受け付けプレイヤーの速度を更新する
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 = Clamp(player.vx - friction, 0.0f, maxSpeed);
        else
        if (player.vx < 0) player.vx = Clamp(player.vx + friction, -maxSpeed, 0.0f);
    }

    // 速度が大きくなりすぎないように上限をかける
    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.0f, maxFallSpeed);

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

    MoveVertical(player.vy);

    Rect groundProbe = PlayerRect();
    groundProbe.y += 1;
    player.onGround = stage.IsSolidAtRect(groundProbe);
    if (player.onGround && player.vy >= 0)
    {
        player.vy = 0;
        player.remainderY = 0;
    }
}

// 横方向の移動処理
void Game::MoveHorizontal(float move)
{
    player.remainderX += move;
    const int pixelMove = static_cast<int>(player.remainderX);
    player.remainderX -= pixelMove;
    if (pixelMove == 0)
    {
        Rect probe = PlayerRect();
        const int probeMove = (move > 0.0f) ? 1 : (move < 0.0f) ? -1 : 0;
        if (probeMove != 0 && stage.ResolveHorizontal(probe, probeMove))
        {
            player.vx = 0.0f;
            player.remainderX = 0.0f;
        }
        return;
    }

    Rect movedRect = PlayerRect();
    if (stage.ResolveHorizontal(movedRect, pixelMove))
    {
        player.vx = 0.0f;
        player.remainderX = 0.0f;
    }
    player.x = movedRect.x;
}

void Game::MoveVertical(float move)
{
    player.remainderY += move;
    const int pixelMove = static_cast<int>(player.remainderY);
    player.remainderY -= pixelMove;
    if (pixelMove == 0) return;

    Rect movedRect = PlayerRect();
    if (stage.ResolveVertical(movedRect, pixelMove))
    {
        if (pixelMove > 0) player.onGround = true;
        player.vy = 0.0f;
        player.remainderY = 0.0f;
    }
    player.y = movedRect.y;
}
Rect Game::PlayerRect() const
{
    // 現在のプレイヤー位置から当たり判定用の矩形を作る
    return { player.x, player.y, player.width, player.height };
}

Rect Game::PlayerImageRect() const
{
    const int drawX = player.x + player.width / 2 - playerDrawWidth / 2;
    const int drawY = player.y + player.height - playerDrawHeight;

    return { drawX, drawY, playerDrawWidth, playerDrawHeight };
}

// ゲームの描画処理
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(cameraX, images);
    DrawPlayer();
    DrawHud();
    DrawDebug();
}

// ゲームクリア後、またはゲームオーバー後の描画処理
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
{
    const ImageResource& image = images.Get(ImageId::Player);
    const Rect imageRect = PlayerImageRect();
    const int screenX = imageRect.x - cameraX;
    DrawExtendGraph(
        screenX, imageRect.y,
        screenX + imageRect.width,
        imageRect.y + imageRect.height,
        image.handle, 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));
    DrawString(12, 70, L"F1: DEBUG", GetColor(255, 255, 255));
}

// 第02回の表示へカメラ位置とCSV読込状態を追加する
void Game::DrawDebug() const
{
    if (!debugVisible) return;

    DrawBox(8, 92, 320, 194, GetColor(0, 0, 0), TRUE);
    DrawFormatString(14, 100, GetColor(255, 255, 255),
        L"x:%d y:%d vx:%.2f vy:%.2f", player.x, player.y, player.vx, player.vy);
    DrawFormatString(14, 120, GetColor(255, 255, 255),
        L"onGround:%d camera:%d", player.onGround ? 1 : 0, cameraX);
    DrawFormatString(14, 140, GetColor(255, 255, 255),
        L"csv:%d", stage.IsLoadedFromCsv() ? 1 : 0);
}

