﻿#include <cmath>
#include <string>

#include <DxLib.h>

#include <Windows.h>

#include "Config/ApplicationConfig.h"
#include "Shooting/Tuning/GameTuning.h"

#include "Shooting/Game.h"

namespace
{
    void ShowResourceLoadError(
        const wchar_t* resourceType,
        const std::wstring& failedPath)
    {
        std::wstring message = resourceType;
        message += L"の読み込みに失敗しました。\n\n対象: ";
        message += failedPath;
        message += L"\n\nAssetsフォルダーとファイル名を確認してください。";

        MessageBoxW(
            nullptr,
            message.c_str(),
            L"リソース読み込みエラー",
            MB_OK | MB_ICONERROR);
    }
}

namespace dxgame::shooting
{
    namespace config = dxgame::config;

    namespace
    {
        using SoundId = SoundManager::Id;
    }

    bool Game::Run()
    {
        const ImageManager::LoadResult imageResult = images.LoadAll();
        if (!imageResult.succeeded)
        {
            ShowResourceLoadError(L"画像", imageResult.failedPath);
            return false;
        }

        const SoundManager::LoadResult soundResult = sounds.LoadAll();
        if (!soundResult.succeeded)
        {
            ShowResourceLoadError(L"音声", soundResult.failedPath);
            return false;
        }

        ChangeState(State::Title);

        // 小数分の待ち時間を次フレームへ持ち越す
        double nextFrame = GetNowCount();

        while (ProcessMessage() == 0)
        {
            input.Update();
            if (input.IsDown(Input::Action::Exit))
                break;

            Update(config::DeltaTime);
            ClearDrawScreen();
            Draw();
            ScreenFlip();

            nextFrame += config::FrameMilliseconds;
            int waitMilliseconds = static_cast<int>(nextFrame - GetNowCount());

            if (waitMilliseconds > 0)
                WaitTimer(waitMilliseconds);
            else
                nextFrame = GetNowCount();
        }

        return true;
    }

    void Game::Update(float deltaTime)
    {
        switch (state)
        {
        case State::Title:
            UpdateTitle();
            break;

        case State::Playing:
            UpdatePlaying(deltaTime);
            break;

        case State::GameClear:
        case State::GameOver:
            UpdateResult();
            break;
        }
    }

    void Game::Draw() const
    {
        switch (state)
        {
        case State::Title:
            DrawTitle();
            break;

        case State::Playing:
            DrawPlaying();
            break;

        case State::GameClear:
        case State::GameOver:
            DrawPlaying();
            DrawResult();
            break;
        }
    }

    void Game::StartGame()
    {
        // 前回の結果を残さず、全オブジェクトを開始状態へ戻す
        player.Reset();
        playerShots.Reset();
        enemies.Reset();
        enemyShots.Reset();
        timeLeft = tuning::game::TimeLimit;
        score = 0;

        ChangeState(State::Playing);
    }

    void Game::ChangeState(State nextState)
    {
        state = nextState;

        // Stateに入ったときのBGMをここで切り替える
        switch (state)
        {
        case State::Title:
            sounds.PlayBgm(SoundId::TitleBgm);
            break;

        case State::Playing:
            sounds.PlayBgm(SoundId::PlayingBgm);
            break;

        case State::GameClear:
            sounds.StopBgm();
            sounds.PlaySe(SoundId::Clear);
            break;

        case State::GameOver:
            sounds.StopBgm();
            sounds.PlaySe(SoundId::GameOver);
            break;
        }
    }

    void Game::UpdateTitle()
    {
        if (input.IsPressed(Input::Action::Confirm))
            StartGame();
    }

    void Game::UpdatePlaying(float deltaTime)
    {
        timeLeft -= deltaTime;
        if (timeLeft < 0.0f)
            timeLeft = 0.0f;

        player.Update(input, deltaTime);
        playerShots.Update(deltaTime);
        enemies.Update(deltaTime);
        enemyShots.Update(deltaTime);

        if (player.TryShoot(input, playerShots))
        {
            sounds.PlaySe(SoundId::Shot);
        }

        ResolveCollisions();

        if (!player.IsAlive())
        {
            ChangeState(State::GameOver);
            return;
        }

        if (score >= tuning::game::ClearScore || timeLeft <= 0.0f)
        {
            ChangeState(State::GameClear);
            return;
        }

        // 発射時点のプレイヤー位置を、すべての敵が狙う位置として共有する
        Vector2 playerPosition = player.GetHitbox().GetCenter();
        for (Enemy& enemy : enemies.GetAll())
        {
            enemy.TryShoot(enemyShots, playerPosition);
        }
    }

    void Game::UpdateResult()
    {
        if (input.IsPressed(Input::Action::Confirm))
            StartGame();
        else
        if (input.IsPressed(Input::Action::Back))
            ChangeState(State::Title);
    }

    void Game::ResolveCollisions()
    {
        // プレイヤーの弾は最初に重なった敵だけに命中させる
        for (PlayerShot& playerShot : playerShots.GetAll())
        {
            if (!playerShot.IsActive())
                continue;

            for (Enemy& enemy : enemies.GetAll())
            {
                if (!enemy.IsActive())
                    continue;

                if (!playerShot.GetHitbox().Overlaps(enemy.GetHitbox()))
                    continue;

                enemy.TakeDamage(tuning::playerShot::Damage);
                playerShot.Deactivate();
                if (!enemy.IsActive())
                    ++score;
                sounds.PlaySe(SoundId::Hit);
                break;
            }
        }

        for (EnemyShot& enemyShot : enemyShots.GetAll())
        {
            if (!enemyShot.IsActive())
                continue;

            if (!enemyShot.GetHitbox().Overlaps(player.GetHitbox()))
                continue;

            enemyShot.Deactivate();
            if (player.TryTakeDamage())
                sounds.PlaySe(SoundId::Damage);
        }

        for (Enemy& enemy : enemies.GetAll())
        {
            if (!enemy.IsActive())
                continue;

            if (!enemy.GetHitbox().Overlaps(player.GetHitbox()))
                continue;

            enemy.Deactivate();
            if (player.TryTakeDamage())
                sounds.PlaySe(SoundId::Damage);
        }
    }

    void Game::DrawTitle() const
    {
        DrawString(194, 128, L"VERTICAL SHOOTING", GetColor(80, 220, 255));
        DrawString(248, 210, L"PRESS ENTER", GetColor(255, 230, 80));
        DrawString(208, 276, L"ARROW / WASD : MOVE", GetColor(210, 218, 232));
        DrawString(238, 306, L"Z / SPACE : SHOT", GetColor(210, 218, 232));
    }

    void Game::DrawPlaying() const
    {
        // 描画順はプレイヤー、プレイヤーの弾、敵、敵の弾の順
        player.Draw(images);
        playerShots.Draw(images);
        enemies.Draw(images);
        enemyShots.Draw(images);
        DrawHud();
    }

    void Game::DrawResult() const
    {
        SetDrawBlendMode(DX_BLENDMODE_ALPHA, 168);
        DrawBox(0, 0, tuning::ScreenWidth, tuning::ScreenHeight, GetColor(0, 0, 0), TRUE);
        SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);

        if (state == State::GameClear)
            DrawString(254, 164, L"GAME CLEAR", GetColor(100, 255, 160));
        else
            DrawString(254, 164, L"GAME OVER", GetColor(255, 96, 112));

        DrawFormatString(276, 216, GetColor(255, 255, 255), L"SCORE %02d", score);
        DrawString(170, 282, L"ENTER : RETRY    T : TITLE", GetColor(255, 230, 80));
    }

    void Game::DrawHud() const
    {
        int secondsLeft = static_cast<int>(std::ceil(timeLeft));
        DrawFormatString(16, 16, GetColor(255, 255, 255), L"SCORE %02d / %02d", score, tuning::game::ClearScore);
        DrawFormatString(16, 42, GetColor(255, 255, 255), L"HP %d", player.GetHp());
        DrawFormatString(532, 16, GetColor(255, 255, 255), L"TIME %02d", secondsLeft);
    }
}
