#ifndef WINDOW_H
#define WINDOW_H

#include "data/List.h"
#include "math/Vector.h"
#include "utils/Clock.h"
#include "utils/Error.h"
#include "utils/StringBuffer.h"

namespace Window {
    typedef bool (*ShouldRun)();
    typedef void (*Tick)();
    typedef void (*Render)(float);

    struct Options final {
        int majorVersion;
        int minorVersion;
        const IntVector2& size;
        bool fullscreen;
        bool es;
        bool vsync;
        const char* name;

        Options(int majorVersion, int minorVersion, const IntVector2& size,
                bool es, const char* name);
    };

    Error open(const Options& options);
    void close();

    float getTicksPerSecond();
    float getFramesPerSecond();

    void trapCursor();
    void freeCursor();
    bool isCursorTrapped();

    const IntVector2& getSize();
    bool hasSizeChanged();

    void show();
    bool shouldClose();

    Clock::Nanos startFrame();
    void endFrame();
    void tick();
    void postTick();

    template<ShouldRun SR, Tick T, Render R>
    void run(Clock::Nanos nanosPerTick) {
        Clock::Nanos lag = 0;
        while(SR()) {
            lag += startFrame();
            while(lag >= nanosPerTick) {
                lag -= nanosPerTick;
                tick();
                T();
                postTick();
            }
            R(static_cast<float>(lag) / nanosPerTick);
            endFrame();
        }
    }

    namespace Input {
        void setLimit(int limit);
        void reset();
        void enable();
        void disable();
        bool isEnabled();
        void fill(const char* s);
        int getCursor();
        void setCursor(int index);
        const List<uint32>& getUnicode();

        template<int N>
        void toString(StringBuffer<N>& s) {
            for(uint32 c : getUnicode()) {
                s.appendUnicode(c);
            }
        }
    }

    namespace Controls {
        typedef StringBuffer<32> ButtonName;
        typedef int ButtonId;

        ButtonId add(const ButtonName& name);
        void bindKey(ButtonId id, int key);
        void bindGamepad(ButtonId id, int gamepadButton);
        void bindMouse(ButtonId id, int mouseButton);

        Vector2 getLastMousePosition();
        Vector2 getMousePosition();
        Vector2 getLeftGamepadAxis();
        Vector2 getRightGamepadAxis();
        float getLeftGamepadTrigger();
        float getRightGamepadTrigger();

        bool isDown(ButtonId id);
        int getDownTime(ButtonId id);
        bool wasReleased(ButtonId id);
        const ButtonName& getName(ButtonId id);
    }
}

#endif