// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// Copyright (c) 2018-2023 www.open3d.org
// SPDX-License-Identifier: MIT
// ----------------------------------------------------------------------------

#pragma once

#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>

#include "open3d/visualization/gui/Font.h"
#include "open3d/visualization/gui/Menu.h"

namespace open3d {

namespace geometry {
class Image;
}

namespace visualization {

namespace rendering {
class Renderer;
class View;
class Scene;
}  // namespace rendering

namespace gui {

struct Theme;
class Window;
class WindowSystem;

class Application {
public:
    static Application &GetInstance();

    virtual ~Application();

    /// Initializes the application, and in particular, finds the path for
    /// the resources. If you can provide the argc/argv arguments it is more
    /// reliable.
    void Initialize();
    /// Initializes the application, and in particular, finds the path for
    /// the resources. If you can provide the argc/argv arguments it is more
    /// reliable.
    void Initialize(int argc, const char *argv[]);
    /// Initializes the application, with a specific path to the resources.
    void Initialize(const char *resource_path);

    /// Identifier for font used by default for all UI elements
    static constexpr FontId DEFAULT_FONT_ID = 0;
    /// Adds a font. Must be called after Initialize() and before a window is
    /// created.
    FontId AddFont(const FontDescription &fd);
    /// Replaces font. Must be called after Initialize() and before a window is
    /// created.
    void SetFont(FontId id, const FontDescription &fd);

    /// Does not return until the UI is completely finished.
    void Run();
    /// Closes all the windows, which exits as a result
    void Quit();

    /// Runs \param f in a separate thread. Do NOT call UI functions in
    /// \p f; if you have a long running function that needs to call UI
    /// functions (e.g. updating a progress bar), have your function call
    /// PostToMainThread() with code that will do the UI (note: your function
    /// may finish before the code given to PostToMainThread will run, so if
    /// using lambdas, capture by copy and make sure whatever you use will
    /// still be alive).
    void RunInThread(std::function<void()> f);
    /// Runs \p f on the main thread at some point in the near future.
    /// Proper context will be setup for \p window. \p f will block the
    /// UI, so it should run quickly. If you need to do something slow
    /// (e.g. load a file) consider using RunInThread() and have the function
    /// pass off UI calls to PostToMainThread().
    void PostToMainThread(Window *window, std::function<void()> f);

    std::shared_ptr<Menu> GetMenubar() const;
    void SetMenubar(std::shared_ptr<Menu> menubar);

    /// Must be called on the same thread that calls Run()
    void AddWindow(std::shared_ptr<Window> window);
    /// Must be called on the same thread that calls Run(). This is normally
    /// called from Window::Close() and should not need to be called in user
    /// code.
    void RemoveWindow(Window *window);

    /// Creates a message box window the next time the event loop processes.
    /// This message box will be a separate window and not associated with any
    /// of the other windows shown with AddWindow().
    ///     THIS FUNCTION SHOULD BE USED ONLY AS A LAST RESORT!
    /// If you have a window, you should use Window::ShowMessageBox() so that
    /// the message box will be modal to that window. If you do not have a
    /// window it is better to use ShowNativeAlert(). If the platform does not
    /// have an alert (like Linux), then this can be used as a last resort.
    void ShowMessageBox(const char *title, const char *message);

    WindowSystem &GetWindowSystem() const;

    // (std::string not good in interfaces for ABI reasons)
    const char *GetResourcePath() const;

    /// This is primarily intended for use by the Window class. Any size-related
    /// fields (for example, fontSize) should be accessed through
    /// Window::GetTheme() as they are updated to reflect the pixel scaling
    /// on the monitor where the Window is displayed.
    const Theme &GetTheme() const;

    /// Returns high-resolution counter value (in seconds). Not valid
    /// until Initialize() is called.
    double Now() const;

    /// Delivers the itemId to the active window. Used internally.
    void OnMenuItemSelected(Menu::ItemId itemId);

    /// Cleanup everything right now. An example of usage is Cocoa's
    /// -applicationWillTermiate: AppDelegate message. Using Quit would result
    /// in a crash (and an unsightly message from macOS) due to destructing
    /// the windows at the wrong time.
    void OnTerminate();

    class EnvUnlocker {
    public:
        EnvUnlocker() {}
        virtual ~EnvUnlocker() {}
        virtual void unlock() {}
        virtual void relock() {}
    };
    /// For internal use. Returns true if the run loop has not finished, and
    /// false if the last window has closed or Quit() has been called.
    /// EnvUnlocker allows an external environment to provide
    /// a way to unlock the environment while we wait for the next event.
    /// This is useful to release the Python GIL, for example. Callers of
    /// of Open3D's GUI from languages such as scripting languages which do
    /// not expect the author to need to clean up after themselves may want to
    /// write their own Run() function that calls RunOneTick() with
    /// cleanup_if_no_windows=false and schedule a call to OnTerminate() with
    /// atexit().
    bool RunOneTick(EnvUnlocker &unlocker, bool cleanup_if_no_windows = true);

    /// Sets the WindowSystem to given object. Can be used to override the
    /// default GLFWWindowSystem (e.g. BitmapWindowSystem). Must be called
    /// before creating any Windows.
    void SetWindowSystem(std::shared_ptr<WindowSystem> ws);

    /// Returns true if using the native OS window system, false otherwise.
    /// This is useful for choosing to display some features that are only
    /// useful with native windows. For instance, when embedded in a Jupyter
    /// notebook, a "Close Window" menu item is not necessary.
    bool UsingNativeWindows() const;

    /// Verifies that Initialize() has been called, printing out an error and
    /// exiting if not.
    void VerifyIsInitialized();

    const std::vector<FontDescription> &GetFontDescriptions() const;

    /// Returns the scene rendered to an image. This MUST NOT be called while
    /// in Run(). It is intended for use when no windows are shown. If you
    /// need to render from a GUI, use Scene::RenderToImage().
    std::shared_ptr<geometry::Image> RenderToImage(
            rendering::Renderer &renderer,
            rendering::View *view,
            rendering::Scene *scene,
            int width,
            int height);

    // Same as RenderToImage(), but returns the depth values in a float image.
    std::shared_ptr<geometry::Image> RenderToDepthImage(
            rendering::Renderer &renderer,
            rendering::View *view,
            rendering::Scene *scene,
            int width,
            int height,
            bool z_in_view_space = false);

private:
    Application();

    enum class RunStatus { CONTINUE, DONE };
    RunStatus ProcessQueuedEvents(EnvUnlocker &unlocker);

private:
    struct Impl;
    std::unique_ptr<Impl> impl_;
};

}  // namespace gui
}  // namespace visualization
}  // namespace open3d
