﻿#include <Windows.h>

#include "DxLib.h"
#include "Game.h"

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

    const ImageResource& playerImage = images.Get(ImageId::Player);
    if (playerImage.width % playerFrameCount != 0)
    {
        MessageBoxW(nullptr,
            L"Player.pngの横幅を8で割り切れません。",
            L"Player Image Error", MB_OK);
        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;
    sounds.PlayBgm(SoundId::PlayingBgm);
}

// プレイ中に変化する値を初期状態へ戻す
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;
    player.animation = PlayerAnimation::Idle;
    player.animationFrame = 0;
    player.animationTimer = 0;
    player.facingRight = true;
}

// ゲームの更新処理
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();
    UpdatePlayerAnimation();
    UpdateCamera();

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

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

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;
        sounds.PlaySe(SoundId::Jump);
    }
}

// プレイヤーの物理演算を行い、位置を更新する
void Game::UpdatePlayerPhysics()
{
    const bool wasOnGround = player.onGround;

    // 重力で落下速度を増やし、速く落ちすぎないように制限する
    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;
    }

    if (!wasOnGround && player.onGround)
    {
        sounds.PlaySe(SoundId::Land);
    }
}

// 接地状態と速度から再生する動きを決め、表示フレームを進める
void Game::UpdatePlayerAnimation()
{
    PlayerAnimation next = PlayerAnimation::Idle;

    if (!player.onGround)
    {
        next = (player.vy < 0) ? PlayerAnimation::Jump : PlayerAnimation::Fall;
    }
    else if (player.vx < -0.1f || player.vx > 0.1f)
    {
        next = PlayerAnimation::Run;
    }

    if (player.vx < -0.1f) player.facingRight = false;
    if (player.vx > 0.1f) player.facingRight = true;

    if (next != player.animation)
    {
        player.animation = next;
        player.animationFrame = 0;
        player.animationTimer = 0;
    }

    int frameCount = 1;
    int frameDuration = 1;

    if (player.animation == PlayerAnimation::Idle)
    {
        frameCount = 2;
        frameDuration = 20;
    }
    else if (player.animation == PlayerAnimation::Run)
    {
        frameCount = 4;
        frameDuration = 6;
    }

    player.animationTimer++;
    if (player.animationTimer >= frameDuration)
    {
        player.animationTimer = 0;
        player.animationFrame = (player.animationFrame + 1) % frameCount;
    }
}

// 横方向の移動処理
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 };
}

// 横一列のスプライトシートから、現在表示するフレーム番号を返す
int Game::PlayerFrameIndex() const
{
    switch (player.animation)
    {
    case PlayerAnimation::Idle:
        return player.animationFrame;
    case PlayerAnimation::Run:
        return 2 + player.animationFrame;
    case PlayerAnimation::Jump:
        return 6;
    case PlayerAnimation::Fall:
        return 7;
    }

    return 0;
}

// ゲームの描画処理
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;
    const int sourceWidth = image.width / playerFrameCount;
    const int sourceHeight = image.height;
    const int sourceX = PlayerFrameIndex() * sourceWidth;
    const double scaleX =
        static_cast<double>(imageRect.width) / sourceWidth;
    const double scaleY =
        static_cast<double>(imageRect.height) / sourceHeight;

    DrawRectRotaGraph3(
        screenX + imageRect.width / 2,
        imageRect.y + imageRect.height / 2,
        sourceX, 0, sourceWidth, sourceHeight,
        sourceWidth / 2, sourceHeight / 2,
        scaleX, scaleY, 0.0,
        image.handle, TRUE, player.facingRight ? FALSE : 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));
}

// 速度、接地状態、アニメーションフレームを確認する
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);
    DrawFormatString(14, 160, GetColor(255, 255, 255),
        L"animation:%d frame:%d",
        static_cast<int>(player.animation), PlayerFrameIndex());
}



