﻿#include <Windows.h>

#include "DxLib.h"

#include "Game.h"

// 必須データを読み込み、プレイを開始する
bool Game::Initialize()
{
    if (!images.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 (!sounds.LoadAll()) return false;
    if (!stage.Initialize(L"Assets/Maps/Stage01.csv")) return false;

    StartPlay();
    return true;
}

// ウィンドウが閉じられるかESCキーが押されるまで、更新と描画を繰り返す
void Game::MainLoop()
{
    while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
    {
        Update();

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

// プレイ開始時にデータを初期化し、状態をPlayingへ変更する
void Game::StartPlay()
{
    ResetPlay();
    gameState = Playing;
    sounds.PlayBgm(SoundId::PlayingBgm);
}

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

void Game::ResetPlayer()
{
    player.x = 64;
    player.y = 240;
    player.width = 26;
    player.height = 30;
    player.vx = 0.0f;
    player.vy = 0.0f;
    player.remainderX = 0.0f;
    player.remainderY = 0.0f;
    player.onGround = false;
    player.animation = PlayerAnimation::Idle;
    player.animationFrame = 0;
    player.animationTimer = 0;
    player.facingRight = true;
}

void Game::ResetEnemies()
{
    enemyCount = stage.GetEnemySpawnCount();

    for (int i = 0; i < enemyCount; i++)
    {
        const SpawnPoint spawn = stage.GetEnemySpawn(i);
        Enemy& enemy = enemies[i];
        enemy.x = spawn.x + 2;
        enemy.y = spawn.y + Stage::tileSize - 28;
        enemy.width = 28;
        enemy.height = 28;
        enemy.vx = (i % 2 == 0) ? 2.0f : -2.0f;
        enemy.remainderX = 0.0f;
        enemy.minX = enemy.x - 64;
        enemy.maxX = enemy.x + 64;
        enemy.isActive = 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;
}

// プレイ中の1フレームを更新し、失敗またはクリア条件を判定する
void Game::UpdatePlaying()
{
    UpdatePlayerInput();
    UpdatePlayerPhysics();
    UpdatePlayerAnimation();
    UpdateEnemies();
    UpdateCamera();

    if (player.y > Stage::mapHeight * Stage::tileSize + 120)
    {
        gameState = GameOver;
        sounds.StopBgm();
        sounds.PlaySe(SoundId::GameOver);
        return;
    }

    for (int i = 0; i < enemyCount; i++)
    {
        const Enemy& enemy = enemies[i];
        if (enemy.isActive && IsHitRect(PlayerRect(), EnemyRect(enemy)))
        {
            gameState = GameOver;
            sounds.StopBgm();
            sounds.PlaySe(SoundId::GameOver);
            return;
        }
    }

    if (IsHitRect(PlayerRect(), stage.GetGoal()))
    {
        gameState = GameClear;
        sounds.StopBgm();
        sounds.PlaySe(SoundId::Goal);
    }
}

// 結果画面からプレイをやり直す
void Game::UpdateResult()
{
    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.0f) player.vx = Clamp(player.vx - friction, 0.0f, maxSpeed);
        else if (player.vx < 0.0f) player.vx = Clamp(player.vx + friction, -maxSpeed, 0.0f);
    }

    player.vx = Clamp(player.vx, -maxSpeed, maxSpeed);

    if (CheckHitKey(KEY_INPUT_Z) && player.onGround)
    {
        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.0f)
    {
        player.vy = 0.0f;
        player.remainderY = 0.0f;
    }

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

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

    if (!player.onGround)
    {
        next = (player.vy < 0.0f) ? 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::UpdateEnemies()
{
    for (int i = 0; i < enemyCount; i++)
    {
        Enemy& enemy = enemies[i];
        if (!enemy.isActive) continue;

        enemy.remainderX += enemy.vx;
        const int pixelMove = static_cast<int>(enemy.remainderX);
        enemy.remainderX -= pixelMove;
        enemy.x += pixelMove;

        if (enemy.x < enemy.minX)
        {
            enemy.x = enemy.minX;
            enemy.vx = -enemy.vx;
            enemy.remainderX = 0.0f;
        }
        else if (enemy.x > enemy.maxX)
        {
            enemy.x = enemy.maxX;
            enemy.vx = -enemy.vx;
            enemy.remainderX = 0.0f;
        }
    }
}

// プレイヤーを画面中央へ追従させ、マップの端でカメラを止める
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::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::EnemyRect(const Enemy& enemy) const
{
    return { enemy.x, enemy.y, enemy.width, enemy.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;
}

Rect Game::EnemyImageRect(const Enemy& enemy) const
{
    const ImageResource& image = images.Get(ImageId::Enemy);
    const int drawX = enemy.x + enemy.width / 2 - image.width / 2;
    const int drawY = enemy.y + enemy.height - image.height;

    return { drawX, drawY, image.width, image.height };
}

// 現在のゲーム状態に対応する画面を描画する
void Game::Draw()
{
    DrawBackground();

    switch (gameState)
    {
    case Playing:
        DrawPlaying();
        break;

    case GameClear:
    case GameOver:
        DrawPlaying();
        DrawResult();
        break;
    }
}

void Game::DrawBackground() const
{
    DrawBox(0, 0, screenWidth, screenHeight, GetColor(130, 190, 255), TRUE);
    DrawBox(0, 380, screenWidth, screenHeight, GetColor(170, 220, 160), TRUE);
}

void Game::DrawPlaying() const
{
    stage.Draw(cameraX, images);
    DrawEnemies();
    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);

    if (gameState == GameClear)
    {
        DrawString(252, 190, L"GAME CLEAR", GetColor(20, 20, 30));
    }
    else
    {
        DrawString(250, 190, L"GAME OVER", GetColor(20, 20, 30));
    }

    DrawString(170, 250, 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::DrawEnemies() const
{
    const ImageResource& image = images.Get(ImageId::Enemy);

    for (int i = 0; i < enemyCount; i++)
    {
        const Enemy& enemy = enemies[i];
        if (!enemy.isActive) continue;

        const Rect imageRect = EnemyImageRect(enemy);
        const int screenX = imageRect.x - cameraX;
        if (screenX < -imageRect.width || screenX > screenWidth) continue;

        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"F1: DEBUG", GetColor(255, 255, 255));
}

void Game::DrawDebug() const
{
    if (!debugVisible) return;

    DrawBox(8, 72, 320, 174, GetColor(0, 0, 0), TRUE);
    DrawFormatString(14, 80, GetColor(255, 255, 255),
        L"x:%d y:%d vx:%.2f vy:%.2f", player.x, player.y, player.vx, player.vy);
    DrawFormatString(14, 100, GetColor(255, 255, 255),
        L"onGround:%d camera:%d", player.onGround ? 1 : 0, cameraX);
    DrawFormatString(14, 120, GetColor(255, 255, 255),
        L"game:%d player:%d enemies:%d",
        gameState, static_cast<int>(player.animation), enemyCount);
    DrawFormatString(14, 140, GetColor(255, 255, 255),
        L"csv:%d", stage.IsLoadedFromCsv() ? 1 : 0);
}

