#ifndef GUI_H
#define GUI_H

#include <unordered_set>
#include "commandline.h"
#include "clipboard.h"

//Seamless conversion between contour::XY and ImGui's ImVec2
#define IM_VEC2_CLASS_EXTRA                                                              \
    explicit constexpr ImVec2(const XY &xy) noexcept : x(float(xy.x)), y(float(xy.y)) {} \
    explicit operator XY() const noexcept { return XY{x,y}; }                            \
    bool operator==(const ImVec2 &) const noexcept = default;

#include "imgui.h"

extern std::array<ImFont *, 8> MscGenFonts;//defined in gui.cpp, filled in backend
extern float MscGenEditorFontSize;         //defined in gui.cpp, filled in backend
extern float MscGenWindowFontSize;         //defined in gui.cpp, filled in backend
extern const char directory_separator;     //defined and filled in backend

void DestroyTexture(ImTextureID);
//Textures can only be destroyed at the end of the frame
//Below we create a RAII type for them (TextureID), which upon destructor, places
//the textures to a cemetery, which will be emptied at the end of the frame.

class TextureCemetery {
    std::unordered_set<ImTextureID> textures_to_destroy;
public:
    void Add(ImTextureID texture) { if (texture) textures_to_destroy.insert(texture); }
    void Cleanup() { for (ImTextureID t : textures_to_destroy) ::DestroyTexture(t); textures_to_destroy.clear(); }
    ~TextureCemetery() { Cleanup(); }
};

extern TextureCemetery DeadTextures;

class TextureID {
    ImTextureID texture = nullptr;
    TextureID(ImTextureID t) noexcept : texture(t) {}
    friend TextureID TextureFromSurface(cairo_surface_t*);
public:
    TextureID() noexcept = default;
    TextureID(const TextureID& t) = delete;
    TextureID(TextureID&& t) noexcept { texture = t.texture; t.texture = nullptr; }
    TextureID& operator=(const TextureID& t) = delete;
    TextureID& operator=(TextureID&& t) noexcept { DeadTextures.Add(texture); texture = t.texture; t.texture = nullptr; return *this; }
    ~TextureID() { DeadTextures.Add(texture); }
    void clear() noexcept { DeadTextures.Add(texture); texture = nullptr; }
    explicit operator bool() const noexcept { return texture; }
    ImTextureID operator*() const noexcept { return texture; }
};
TextureID TextureFromSurface(cairo_surface_t*);
void LoadFonts();
std::map<std::string, std::string> RegisterLibrariesGUI();
void GUIBeep();
std::vector<std::string> FileListProc(std::string_view prefix_, std::string_view included_from_);
std::optional<std::string> ShowDocumentation(const std::string& doc_name);///< Open the named doc, or return an error string
std::optional<std::string> ShowLink(std::string_view url); ///<Open the web browser with this URL (in UTF-8)

struct GUIReturn {
    enum Action {
        None,      ///<Normal window
        Exit,      ///<Exit the program
        FullScreen,///<Make the window full-screen (if not yet so)
        Focus      ///<Make the window normal and focus it
    };
    Action action;
    std::string window_title; // Do not update when empty
};

/** What events the host OS have collected before this frame. */
struct GUIEvents {
    struct Drop {
        std::vector<std::string> full_names; ///<The list of files dropped on us. We reject multi-file drops.
        ImVec2 pos;                          ///<The position of the drop - not used now.
        explicit operator bool() const noexcept { return !full_names.empty(); }
        void clear() noexcept { full_names.clear(); }
    };
    bool request_exit = false;                          ///<If the user requested exit via a window close action
    std::optional<std::chrono::microseconds> total_cpu; ///<What is the total CPU time (user+kernel) used by this process until now. Empty if cannot be determined.
    Drop dropped;                                       ///<Non-empty if files have been dropped to us. Includes the coordinates.
};

/** Pos and size of the main window in the backend's coordinates. */
struct GUIWindowGeometry {
    int x = 0, y = 0;
    int cx = 0, cy = 0;
    bool has_pos() const noexcept { return x>0 && y>0; }
    bool has_size() const noexcept { return cx>0 && cy>0; }
    void ensure_fit_in(int sx, int sy) noexcept {
        x = std::max(0, std::min(x, sx-cx));
        y = std::max(0, std::min(y, sy-cy));
        cx = std::min(cx, sx);
        cy = std::min(cy, sy);
    }
    GUIWindowGeometry scale_by(double dpi_mul) const noexcept {
        return { int(x*dpi_mul), int(y*dpi_mul), int(cx*dpi_mul), int(cy*dpi_mul) };
    }
};

/** Initialize the GUI. Call this before a call to DoGui()
 * @param [in] init The parameters parsed from command-line.
 * @param [in] example_dirs A list of path names for example directories.
 *             All filenames MUST be absolute UTF-8 paths and MUST end in a dir separator.
 *             Each dir is attempted to be searched for examples. The first dir we find
 *             examples in, will be read, the rest ignored.
 *             Can contain empty elements - those are skipped during the search.
 *             Can be empty if no examples.
 * @param [in] docs The short name of the documentation formats available.
 *             A call to ShowDocumentation() shall supply one of these values.
 * @returns the loaded window positions*/
GUIWindowGeometry InitGUI(const GUIInit& init, float dpi_mul_, const std::string& settings_dir, const std::string& current_dir,
                          const std::vector<std::string>& example_dir = {}, std::vector<string>&& docs = {});

/** Called before each ImGUI Frame.
 * It allows recompiling the font atlas on a DPI change.
 * @param [in] dpi_mul Multiplier of the monitor/display of the main window.
 *             On Linux the value of 1. shall correspond to 72 DPI, on Windows 96 DPI.
 *             Providing this value for each frame allows dynamic change.
 * @returns true if the DPI has changed.*/
bool PreFrameGUI(float dpi_mul);

/** Draw the entire GUI.
 * @param [in] events Information on what happened to the computer since the last frame.*/
GUIReturn DoGUI(const GUIEvents& events);

/** Shut down the GUI
 * The parameters are the geometry of the window to save. */
void ShutdownGUI(const GUIWindowGeometry &);

#endif //GUI_H
