// Gravity Stomp Copyright Kevin Poretti

#include "GSGameModeBase.h"

// GS includes
#include "EngineUtils.h"
#include "GSGameState.h"
#include "Player/GSPlayerController.h"
#include "Player/GSPlayerState.h"

AGSGameModeBase::AGSGameModeBase()
{
    ScoreLimit = 25;
    MaxNumTeams = 2; 
	TimeBetweenRounds = 20.0f;
	RoundTime = 600.0f;
	WarmupTime = 15.0f;
    bDelayedStart = true;

    PlayerControllerClass = AGSPlayerController::StaticClass();
    PlayerStateClass = AGSPlayerState::StaticClass();
    GameStateClass = AGSGameState::StaticClass();

    MinRespawnDelay = 5.0f;
}


void AGSGameModeBase::PreInitializeComponents()
{
    Super::PreInitializeComponents();

    // Setup timer for match tick
    GetWorldTimerManager().SetTimer(TimerHandle_DefaultTimer, this, &AGSGameModeBase::DefaultTimer, GetWorldSettings()->GetEffectiveTimeDilation(), true);
}


void AGSGameModeBase::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
{
    Super::InitGame(MapName, Options, ErrorMessage);

    bUseSeamlessTravel = !GetWorld()->IsPlayInEditor();
}


void AGSGameModeBase::InitGameState()
{
    Super::InitGameState();

    AGSGameState* GS = GetGameState<AGSGameState>();
    check(GS)
    GS->SetNumTeams(MaxNumTeams);
}


void AGSGameModeBase::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId,
                                FString& ErrorMessage)
{
    // reject player is match has ended
    AGSGameState* GS = GetGameState<AGSGameState>();
    bool bMatchEnded = GS && GS->HasMatchEnded();
    if(bMatchEnded)
    {
        ErrorMessage = TEXT("Can't join a match that has ended.");
    }
    else
    {
        Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
    }
}


void AGSGameModeBase::PostLogin(APlayerController* NewPlayer)
{
    Super::PostLogin(NewPlayer);

    PickTeam(NewPlayer->GetPlayerState<AGSPlayerState>());

    AGSPlayerController* PC = Cast<AGSPlayerController>(NewPlayer);

    if(PC && IsMatchInProgress())
    {
        PC->Client_GameStarted();
    }
}


void AGSGameModeBase::RestartPlayer(AController* NewPlayer)
{
    Super::RestartPlayer(NewPlayer);

    // inform player controller that game has started using client RPC
    AGSPlayerController* PC = Cast<AGSPlayerController>(NewPlayer);
    if(PC && IsMatchInProgress())
    {
        PC->Client_GameStarted();
    }
}

bool AGSGameModeBase::ShouldSpawnAtStartSpot(AController* Player)
{
    return false;
}

bool AGSGameModeBase::CanDamagePlayer(AGSPlayerController* Damager, AGSPlayerController* Victim)
{
    AGSPlayerState* DamagerPS = Damager ? Damager->GetPlayerState<AGSPlayerState>() : nullptr;
    AGSPlayerState* VictimPS = Victim ? Victim->GetPlayerState<AGSPlayerState>() : nullptr;

    // can damage if both instigator and victim are valid and are on different teams
    return DamagerPS && VictimPS && (DamagerPS->GetTeamNum() != VictimPS->GetTeamNum());
}


void AGSGameModeBase::PickTeam(AGSPlayerState* PlayerState)
{
    PlayerState->SetTeamNum(PlayerState->GetPlayerController()->GetLocalPlayer()->GetControllerId());
}


void AGSGameModeBase::OnPlayerKilled(AGSPlayerController* Killer, AGSPlayerController* Victim)
{
    // Only keep track of kills in an active round
    if(MatchState != MatchState::InProgress)
    {
        return;
    }

    AGSPlayerState* KillerPS = Killer ? Killer->GetPlayerState<AGSPlayerState>() : nullptr;
    AGSPlayerState* VictimPS = Victim ? Victim->GetPlayerState<AGSPlayerState>() : nullptr;

    if(KillerPS && VictimPS)
    {
        // keep track of player's K/D
        KillerPS->IncrementNumKills();
        Killer->Client_OnKill(KillerPS, VictimPS);

        VictimPS->IncrementNumDeaths();

        // inform all players of the death
        for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
        {
            AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
            if(PC)
            {
                PC->Client_OnDeath(KillerPS, VictimPS);
            }
        }

        // give killer's team a point and check if score limit is reached
        AGSGameState* GS = GetGameState<AGSGameState>();
        check(GS);
        int32 NewScore = GS->GiveTeamPoint(KillerPS->GetTeamNum());
        if(NewScore >= ScoreLimit)
        {
            FinishMatch();
        }
    }
}


void AGSGameModeBase::DefaultTimer()
{
    AGSGameState* GS = GetGameState<AGSGameState>();
    if(GS && GS->GetRemainingTime() > 0)
    {
        GS->SetRemainingTime(GS->GetRemainingTime() - 1);
        if(GS->GetRemainingTime() <= 0)
        {
            if (GetMatchState() == MatchState::InProgress)
            {
                FinishMatch();
            }
            else if (GetMatchState() == MatchState::WaitingToStart)
            {
                StartMatch();
            }
        }
    }
}


void AGSGameModeBase::FinishMatch()
{
    if(IsMatchInProgress())
    {
        EndMatch();

        // determine match winner
        AGSGameState* GS = GetGameState<AGSGameState>();
        check(GS);
        int32 WinningTeamIdx = GS->GetWinningTeam();

        // notify players that game has ended and notify winning players that they won
        for(FConstPlayerControllerIterator PCIt = GetWorld()->GetPlayerControllerIterator(); PCIt; ++PCIt)
        {
            // notify player if they won
            APlayerController* PC = PCIt->Get();
            PC->GameHasEnded(nullptr, PC->GetPlayerState<AGSPlayerState>()->GetTeamNum() == WinningTeamIdx);
        }

        // turn off all pawns
		for (APawn* Pawn : TActorRange<APawn>(GetWorld()))
		{
            Pawn->TurnOff();
        }

        GetWorldTimerManager().ClearTimer(TimerHandle_DefaultTimer);
        GS->ResetTeamScores();

        OnMatchFinished(WinningTeamIdx);
    }
}

void AGSGameModeBase::OnMatchRestartRequest()
{
    if(GetMatchState() == MatchState::WaitingPostMatch)
    {
        RestartGame();
    }
}

void AGSGameModeBase::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
{
    // If players should start as spectators, leave them in the spectator state
	if (!bStartPlayersAsSpectators && !MustSpectate(NewPlayer))
	{
		// If match is in progress, start the player
		if ((GetMatchState() == MatchState::WaitingToStart || IsMatchInProgress()) && PlayerCanRestart(NewPlayer))
		{
			RestartPlayer(NewPlayer);
		}
		// Check to see if we should start right away, avoids a one frame lag in single player games
		else if (GetMatchState() == MatchState::WaitingToStart)
		{
			// Check to see if we should start the match
			if (ReadyToStartMatch())
			{
				StartMatch();
			}
		}
	}
}


void AGSGameModeBase::HandleMatchIsWaitingToStart()
{
    Super::HandleMatchIsWaitingToStart();

    // setup warmup
    AGSGameState* GS = GetGameState<AGSGameState>();
    if(GS && GS->GetRemainingTime() <= 0)
    {
        if(bDelayedStart)
        {
            GS->SetRemainingTime(WarmupTime);
        }
    }

    // notify players warmup period has started
    for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
    {
        AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
        if(PC)
        {
            RestartPlayer(PC);
            PC->Client_WarmupStarted();
        }
    }
}


void AGSGameModeBase::HandleMatchHasStarted()
{
    Super::HandleMatchHasStarted();

    // setup in progress match
    AGSGameState* GS = GetGameState<AGSGameState>();
    if(GS && GS->GetRemainingTime() <= 0)
    {
        GS->SetRemainingTime(RoundTime);
    }

    // notify players match has started
    for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
    {
        AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
        if(PC)
        {
            PC->Client_GameStarted();
        }
    }
}


void AGSGameModeBase::RestartGame()
{
    // Setup timer for match tick
    GetWorldTimerManager().SetTimer(TimerHandle_DefaultTimer, this, &AGSGameModeBase::DefaultTimer, GetWorldSettings()->GetEffectiveTimeDilation(), true);

    // notify players game has restarted
    for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
    {
        AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
        if(PC)
        {
            PC->Client_GameRestarted();
        }
    }

    Super::RestartGame();
}