// Copyright (c) Wojciech Figat. All rights reserved.

#include "Screen.h"
#include "Engine.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/Nullable.h"
#include "Engine/Platform/Window.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#include "Editor/Managed/ManagedEditor.h"
#else
#include "Engine/Engine/Engine.h"
#endif

namespace
{
    Nullable<bool> Fullscreen;
    Nullable<Float2> Size;
    bool CursorVisible = true;
    CursorLockMode CursorLock = CursorLockMode::None;
    CursorLockMode PendingCursorLock = CursorLockMode::None;
    bool LastGameViewportFocus = false;
}

class ScreenService : public EngineService
{
public:
    ScreenService()
        : EngineService(TEXT("Screen"), 500)
    {
    }

    void Update() override;
    void Draw() override;
};

ScreenService ScreenServiceInstance;

bool Screen::GetIsFullscreen()
{
#if USE_EDITOR
    return false;
#else
	auto win = Engine::MainWindow;
	return win ? win->IsFullscreen() : false;
#endif
}

void Screen::SetIsFullscreen(bool value)
{
    Fullscreen = value;
}

Float2 Screen::GetSize()
{
#if USE_EDITOR
    return Editor::Managed->GetGameWindowSize();
#else
	auto win = Engine::MainWindow;
	return win ? win->GetClientSize() : Float2::Zero;
#endif
}

void Screen::SetSize(const Float2& value)
{
    if (value.X <= 0 || value.Y <= 0)
    {
        LOG(Error, "Invalid Screen size to set.");
        return;
    }

    Size = value;
}

Float2 Screen::ScreenToGameViewport(const Float2& screenPos)
{
#if USE_EDITOR
    return Editor::Managed->ScreenToGameViewport(screenPos);
#else
    auto win = Engine::MainWindow;
    return win ? win->ScreenToClient(screenPos) / win->GetDpiScale() : Float2::Minimum;
#endif
}

Float2 Screen::GameViewportToScreen(const Float2& viewportPos)
{
#if USE_EDITOR
    return Editor::Managed->GameViewportToScreen(viewportPos);
#else
    auto win = Engine::MainWindow;
    return win ? win->ClientToScreen(viewportPos * win->GetDpiScale()) : Float2::Minimum;
#endif
}

bool Screen::GetCursorVisible()
{
    return CursorVisible;
}

void Screen::SetCursorVisible(const bool value)
{
#if USE_EDITOR
    const auto win = Editor::Managed->GetGameWindow(true);
#else
	const auto win = Engine::MainWindow;
#endif
    if (win && Engine::HasGameViewportFocus())
        win->SetCursor(value ? CursorType::Default : CursorType::Hidden);
    else if (win)
        win->SetCursor(CursorType::Default);
    CursorVisible = value;
}

CursorLockMode Screen::GetCursorLock()
{
    return CursorLock;
}

void Screen::SetCursorLock(CursorLockMode mode)
{
#if USE_EDITOR
    const auto win = Editor::Managed->GetGameWindow(true);
    Rectangle bounds(Editor::Managed->GameViewportToScreen(Float2::Zero), Editor::Managed->GetGameWindowSize());
    if (win)
    {
        bounds = Rectangle(win->ScreenToClient(bounds.GetTopLeft()), bounds.Size);

        // Don't change lock mode in play mode when viewport doesn't have focus
        if (mode != CursorLockMode::None && Editor::IsPlayMode && !Engine::HasGameViewportFocus())
        {
            PendingCursorLock = mode;
            return;
        }
        PendingCursorLock = CursorLockMode::None;
    }
#else
    const auto win = Engine::MainWindow;
    Rectangle bounds = Rectangle::Empty;
    if (win)
        bounds.Size = win->GetClientSize();

    // Don't change lock mode when window is not focused
    if (mode != CursorLockMode::None && win && !win->IsFocused())
    {
        PendingCursorLock = mode;
        return;
    }
    PendingCursorLock = CursorLockMode::None;
#endif
    if (win)
    {
        bool inRelativeMode = Input::Mouse->IsRelative();
        if (mode == CursorLockMode::Clipped)
            win->StartClippingCursor(bounds);
#if PLATFORM_SDL
        else if (mode == CursorLockMode::Locked)
        {
            // Use mouse clip region to restrict the cursor in one spot
            win->StartClippingCursor(Rectangle(bounds.GetCenter(), Float2(1, 1)));
        }
        else if (CursorLock == CursorLockMode::Locked || CursorLock == CursorLockMode::Clipped)
            win->EndClippingCursor();
#else
        else if (CursorLock == CursorLockMode::Clipped)
            win->EndClippingCursor();
#endif

        // Enable relative mode when cursor is restricted
        if (mode != CursorLockMode::None)
            Input::Mouse->SetRelativeMode(true, win);
        else if (mode == CursorLockMode::None && inRelativeMode)
            Input::Mouse->SetRelativeMode(false, win);
    }
    CursorLock = mode;
}

GameWindowMode Screen::GetGameWindowMode()
{
    GameWindowMode result = GameWindowMode::Windowed;
#if !USE_EDITOR
    auto win = Engine::MainWindow;
    if (win)
    {
        if (GetIsFullscreen() || win->IsFullscreen())
            result = GameWindowMode::Fullscreen;
        else if (win->GetSettings().HasBorder)
            result = GameWindowMode::Windowed;
        else if (win->GetClientPosition().IsZero() && win->GetSize() == Platform::GetDesktopSize())
            result = GameWindowMode::FullscreenBorderless;
        else
            result = GameWindowMode::Borderless;
    }
#endif
    return result;
}

void Screen::SetGameWindowMode(GameWindowMode windowMode)
{
#if !USE_EDITOR
    auto win = Engine::MainWindow;
    if (!win)
        return;
    switch (windowMode)
    {
    case GameWindowMode::Windowed:
        if (GetIsFullscreen())
            SetIsFullscreen(false);
        win->SetBorderless(false, false);
        break;
    case GameWindowMode::Fullscreen:
        SetIsFullscreen(true);
        break;
    case GameWindowMode::Borderless:
        win->SetBorderless(true, false);
        break;
    case GameWindowMode::FullscreenBorderless:
        win->SetBorderless(true, true);
        break;
    }
#endif
}

Window* Screen::GetMainWindow()
{
    return Engine::MainWindow;
}

void ScreenService::Update()
{
#if USE_EDITOR
    // Sync current cursor state in Editor (eg. when viewport focus can change)
    const auto win = Editor::Managed->GetGameWindow(true);
    bool gameViewportFocus = win && Engine::HasGameViewportFocus();
    if (gameViewportFocus != LastGameViewportFocus)
        Screen::SetCursorVisible(CursorVisible);
    LastGameViewportFocus = gameViewportFocus;
#endif

    // Sync pending cursor lock mode
    if (PendingCursorLock != CursorLockMode::None && Engine::HasGameViewportFocus())
        Screen::SetCursorLock(PendingCursorLock);
}

void ScreenService::Draw()
{
#if USE_EDITOR

    // Not supported

#else

	if (Fullscreen.HasValue())
	{
		auto win = Engine::MainWindow;
		if (win)
		{
			win->SetIsFullscreen(Fullscreen.GetValue());
		}

		Fullscreen.Reset();
	}

	if (Size.HasValue())
	{
		auto win = Engine::MainWindow;
		if (win)
		{
			win->SetClientSize(Size.GetValue());
		}

		Size.Reset();
	}

#endif
}
