// Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once

#include <carb/IObject.h>
#include <carb/Interface.h>
#include <carb/events/EventsUtils.h>
#include <carb/events/IEvents.h>
#include <carb/input/IInput.h>
#include <carb/windowing/IWindowing.h>

namespace omni
{
namespace kit
{

constexpr const char* kWindowEnabledPath = "/app/window/enabled";
constexpr const char* kWindowWidthPath = "/app/window/width";
constexpr const char* kWindowHeightPath = "/app/window/height";
constexpr const char* kpWindowWidthPath = "/persistent/app/window/width";
constexpr const char* kpWindowHeightPath = "/persistent/app/window/height";
constexpr const char* kWindowMaximizedPath = "/app/window/maximized";
constexpr const char* kpWindowMaximizedPath = "/persistent/app/window/maximized";
constexpr const char* kWindowXPath = "/app/window/x";
constexpr const char* kWindowYPath = "/app/window/y";
constexpr const char* kWindowTitlePath = "/app/window/title";
constexpr const char* kWindowFullscreenPath = "/app/window/fullscreen";
constexpr const char* kWindowNoResizePath = "/app/window/noResize";
constexpr const char* kWindowScaleToMonitorPath = "/app/window/scaleToMonitor";
constexpr const char* kWindowAlwaysOnTopPath = "/app/window/alwaysOnTop";
constexpr const char* kWindowNoDecorationsPath = "/app/window/noDecorations";
constexpr const char* kWindowHideMouseInFullscreenPath = "/app/window/hideMouseInFullscreen";
constexpr const char* kWindowDpiScaleOverridePath = "/app/window/dpiScaleOverride";
constexpr const char* kWindowBlockAllInputPath = "/app/window/blockAllInput";
constexpr const char* kWindowIconPath = "/app/window/iconPath";
constexpr const char* kWindowChildWindowsOnTopPath = "/app/window/childWindowsOnTop";
constexpr const char* kWindowCursorBlinkPath = "/app/window/cursorBlink";

constexpr const uint64_t kPositionCentered = (uint64_t)-1;
constexpr const uint64_t kPositionUnset = (uint64_t)-2;

enum class Decorations
{
    eFull,
    eNone
};

enum class Resize
{
    eAllowed,
    eNotAllowed
};

enum class Floating
{
    eRegular,
    eAlwaysOnTop
};

enum class Scaling
{
    eScaleToMonitor,
    eNotScaled
};

enum class Fullscreen
{
    eWindowed,
    eFullscreen,
};

enum class WindowState
{
    eNormal,
    eMaximized,
    eMinimized,
};

enum class CursorBlink
{
    eBlink,
    eNoBlink
};

struct WindowDesc
{
    size_t structSize;          // This field is needed to maintain ABI stability

    uint64_t width, height;
    uint64_t x, y;
    const char* title;
    Fullscreen fullscreen;
    Decorations decorations;
    Resize resize;
    Floating floating;
    Scaling scaling;
    float dpiScaleOverride;
    CursorBlink cursorBlink;
    WindowState windowState;
};

inline WindowDesc getDefaultWindowDesc()
{
    WindowDesc windowDesc;
    windowDesc.structSize = sizeof(WindowDesc);
    windowDesc.width = 1440;
    windowDesc.height = 900;
    windowDesc.x = kPositionUnset;
    windowDesc.y = kPositionUnset;
    windowDesc.title = "Application Window";
    windowDesc.fullscreen = Fullscreen::eWindowed;
    windowDesc.windowState = WindowState::eNormal;
    windowDesc.decorations = Decorations::eFull;
    windowDesc.resize = Resize::eAllowed;
    windowDesc.floating = Floating::eRegular;
    windowDesc.scaling = Scaling::eScaleToMonitor;
    windowDesc.dpiScaleOverride = -1.0f;
    windowDesc.cursorBlink = CursorBlink::eBlink;
    return windowDesc;
}

const carb::events::EventType kEventTypeWindowCreated = 1;
const carb::events::EventType kEventTypeWindowDestroyed = 2;
const carb::events::EventType kEventTypeWindowStartup = 3;
const carb::events::EventType kEventTypeWindowShutdown = 4;

enum class WindowType
{
    eVirtual,
    eOs,
};

class IAppWindow : public carb::IObject
{
public:
    /**
     * Initializes the window taking parameters from carb::settings.
     * @param name Name that identifies the window, can be nullptr.
     * @return Whether the startup operation was completed successfully.
     */
    virtual bool startup(const char* name) = 0;

    /**
     * Initializes the window with custom description.
     * @return Whether the startup operation was completed successfully.
     */
    virtual bool startupWithDesc(const char* name, const WindowDesc& desc) = 0;

    /**
     * Deinitializes the window.
     * @return Whether the shutdown operation was completed successfully.
     */
    virtual bool shutdown() = 0;

    /**
     * Call one update loop iteration on application.
     * Normally, explicitly calling update is not required, as in presence of IApp interface,
     * a subscription will be created that will be this function automatically.
     *
     * @ param dt Time elapsed since previous call. If <0 application ignores passed value and measures elapsed time
     * automatically.
     */
    virtual void update(float dt = -1.0f) = 0;

    /**
     * Returns the Carbonite window the editor is working with, or nullptr if headless.
     * @return Carbonite window the editor is working with, or nullptr if headless.
     */
    virtual carb::windowing::Window* getWindow() = 0;

    /**
     * @return The event stream that fires events on window resize.
     */
    virtual carb::events::IEventStream* getWindowResizeEventStream() = 0;
    /**
     * @return The event stream that fires events on window close.
     */
    virtual carb::events::IEventStream* getWindowCloseEventStream() = 0;
    /**
     * @return The event stream that fires events on window move.
     */
    virtual carb::events::IEventStream* getWindowMoveEventStream() = 0;
    /**
     * @return The event stream that fires events on window DPI scale change.
     * Content scale event stream provides DPI and "real DPI". The first one is affected by the DPI override, while
     * the second one is raw hardware DPI induced this event.
     */
    virtual carb::events::IEventStream* getWindowContentScaleEventStream() = 0;

    /**
     * @return The event stream that fires events on window drag-n-drop events.
     */
    virtual carb::events::IEventStream* getWindowDropEventStream() = 0;
    /**
     * @return The event stream that fires events on window focus change.
     */
    virtual carb::events::IEventStream* getWindowFocusEventStream() = 0;
    /**
     * @return The event stream that fires events on window minimize events.
     */
    virtual carb::events::IEventStream* getWindowMinimizeEventStream() = 0;

    /**
     * Sets fullscreen state of editor Window.
     *
     * @param fullscreen true to set Editor Window to fullscreen.
     */
    virtual void setFullscreen(bool fullscreen) = 0;

    /**
     * Gets the fullscreen state of editor Window.
     *
     * @return true if Editor is fullscreen.
     */
    virtual bool isFullscreen() = 0;

    /**
     * Resizes the window.
     *
     * @param width The width of the window.
     * @param height The height of the window.
     */
    virtual void resize(uint32_t width, uint32_t height) = 0;

    /**
     * @return window width.
     */
    virtual uint32_t getWidth() = 0;

    /**
     * @return window height.
     */
    virtual uint32_t getHeight() = 0;

    /**
     * @return window size.
     */
    virtual carb::Uint2 getSize() = 0;

    /**
     * Moves the window.
     *
     * @param x The x coordinate of the window.
     * @param y The y coordinate of the window.
     */
    virtual void move(int x, int y) = 0;

    /**
     * @return window position.
     */
    virtual carb::Int2 getPosition() = 0;

    /**
     * @return UI scale multiplier that is applied on top of the OS DPI scale.
     */
    virtual float getUiScaleMultiplier() const = 0;

    /**
     * Sets the UI scale multiplier that is applied on top of OS DPI scale.
     *
     * @param uiScaleCoeff UI scale coefficient.
     */
    virtual void setUiScaleMultiplier(float uiScaleMultiplier) = 0;

    /**
     * @return UI scale. Includes UI scale multiplier and OS DPI scale.
     */
    virtual float getUiScale() const = 0;


    /**
     * Gets current action mapping set settings path.
     *
     * @return action mapping set settings path.
     */
    virtual const char* getActionMappingSetPath() = 0;

    /**
     * Gets the keyboard associated with the window.
     *
     * @return The window keyboard.
     */
    virtual carb::input::Keyboard* getKeyboard() = 0;

    /**
     * Gets the mouse associated with the window.
     *
     * @return The window mouse.
     */
    virtual carb::input::Mouse* getMouse() = 0;

    /**
     * Gets one of the gamepads available.
     *
     * @return The window gamepad or nullptr if index is invalid.
     */
    virtual carb::input::Gamepad* getGamepad(size_t index) = 0;

    /**
     * @return Window title.
     */
    virtual const char* getTitle() = 0;

    /**
     * @return DPI scale.
     */
    virtual float getDpiScale() const = 0;

    /**
     * Sets the DPI scale override. Negative value means no override.
     */
    virtual void setDpiScaleOverride(float dpiScaleOverride) = 0;

    /**
     * @return DPI scale override.
     */
    virtual float getDpiScaleOverride() const = 0;

    /**
     * Sets the forced rejecting of all input events from a certain device type.
     */
    virtual void setInputBlockingState(carb::input::DeviceType deviceType, bool shouldBlock) = 0;
    /**
     * @return whether the input for a certain device types is being blocked.
     */
    virtual bool getInputBlockingState(carb::input::DeviceType deviceType) const = 0;

    void broadcastInputBlockingState(bool shouldBlock)
    {
        for (size_t i = 0; i < (size_t)carb::input::DeviceType::eCount; ++i)
        {
            setInputBlockingState((carb::input::DeviceType)i, shouldBlock);
        }
    }

    /**
     * @brief Virtual/OS window
     */
    virtual WindowType getWindowType() const = 0;

    /**
     * @return True if cursor (caret) blinks in input fields.
     */
    virtual bool getCursorBlink() const = 0;

    /**
     * Gets the text from the clipboard associated with the window.
     *
     * @return The text in the window's clipboard.
     */
    virtual const char* getClipboard() = 0;

    /**
     * Sets the text in the clipboard associated with the window.
     */
    virtual void setClipboard(const char* text) = 0;

    /**
     * Maximize the editor window.
     */
    virtual void maximizeWindow() = 0;

    /**
     * Restore the editor window (exit maximize/minimize).
     */
    virtual void restoreWindow() = 0;

    /**
     * Gets the maxinized state of editor Window.
     *
     * @return true if Editor is maximized.
     */
    virtual bool isMaximized() = 0;

    /**
     * @brief Allows to replace the keyboard.
     *
     * @param keyboard the keyboard this AppWindow should follow.
     */
    virtual void setKeyboard(carb::input::Keyboard* keyboard) = 0;
};

using IAppWindowPtr = carb::ObjectPtr<IAppWindow>;

class IAppWindowFactory
{
public:
    CARB_PLUGIN_INTERFACE("omni::kit::IAppWindowFactory", 2, 4);

    /**
     * Create new application window.
     */
    IAppWindowPtr createWindowFromSettings();
    virtual IAppWindow* createWindowPtrFromSettings() = 0;
    virtual void destroyWindowPtr(IAppWindow* appWindow) = 0;

    /**
     * Set and get default application window.
     */
    virtual IAppWindow* getDefaultWindow() = 0;
    virtual void setDefaultWindow(IAppWindow*) = 0;

    virtual size_t getWindowCount() = 0;
    virtual IAppWindow* getWindowAt(size_t index) = 0;

    virtual void startupActionMappingSet() = 0;
    virtual void shutdownActionMappingSet() = 0;
    virtual carb::input::ActionMappingSet* getActionMappingSet() const = 0;

    virtual bool startup() = 0;
    virtual bool shutdown() = 0;

    virtual carb::events::IEventStream* getWindowCreationEventStream() = 0;
    virtual IAppWindow* getAppWindowFromHandle(int64_t appWindowHandle) = 0;

    IAppWindowPtr createWindowByType(WindowType windowType);
    virtual IAppWindow* createWindowPtrByType(WindowType windowType) = 0;
};

inline IAppWindowPtr IAppWindowFactory::createWindowFromSettings()
{
    return carb::stealObject(this->createWindowPtrFromSettings());
}
inline IAppWindowPtr IAppWindowFactory::createWindowByType(WindowType windowType)
{
    return carb::stealObject(this->createWindowPtrByType(windowType));
}

inline IAppWindow* getDefaultAppWindow()
{
    IAppWindowFactory* factory = carb::getCachedInterface<IAppWindowFactory>();
    return factory ? factory->getDefaultWindow() : nullptr;
}

}
}
