/**
 * paradiso - Paradigmen der Softwareentwicklung
 *
 * (c) Copyright 2023-2025 Hartmut Seichter and Contributors
 *
 */

#include <paradiso/bitmap.hpp>
#include <paradiso/bitmap_io.hpp>
#include <paradiso/context.hpp>
#include <paradiso/geometry.hpp>
#include <paradiso/renderer.hpp>
#include <paradiso/shader.hpp>
#include <paradiso/sprite.hpp>
#include <paradiso/utils.hpp>
#include <paradiso/vector.hpp>
#include <paradiso/window.hpp>

#include <chrono>
#include <print>
#include <thread>
#include <tuple>
#include <unordered_map>

namespace TappyPlane {

using SpriteName = std::tuple<std::string_view, std::string_view>;
using SpriteMap = std::unordered_map<std::string_view, paradiso::Bitmap>;

static const unsigned int FRAME_RATE = 60;

static const unsigned int WINDOW_WIDTH = 800;
static const unsigned int WINDOW_HEIGHT = 480;
static const unsigned int WINDOW_SCALE = 2;

static const char ACTION_KEY = ' ';

static constexpr float SCROLLING_SPEED_INC_FACTOR = 1.0002f;

// x,y+h --- x+w,y+h
//   |          |
//   |          |
//   |          |
//  x,y ----- x+w,y
struct AABB {
    paradiso::Vector2<float> position{};
    paradiso::Vector2<float> size{};

    bool is_colliding_with(const AABB& other) {
        return position.x() < other.position.x() + other.size.x() &&
               position.x() + size.x() > other.position.x() &&
               position.y() < other.position.y() + other.size.y() &&
               position.y() + size.y() > other.position.y();
    }
};

struct Background {
    static constexpr float INIT_SCROLLING_SPEED = 0.003f;

    float scrolling_speed = INIT_SCROLLING_SPEED;

    std::array<paradiso::Sprite, 2> sprites;
    paradiso::Renderer renderer{};

    void init(SpriteMap& sprite_map) {
        sprites = {paradiso::Sprite{
                       .bitmap = sprite_map["background"],
                       .pivot = {paradiso::Vector2<float>::make(0.0f, 0.0f)},
                       .scale = {paradiso::Vector2<float>::make(1.0f, 1.0f)}},
                   paradiso::Sprite{
                       .bitmap = sprite_map["background"],
                       .pivot = {paradiso::Vector2<float>::make(2.0f, 0.0f)},
                       .scale = {paradiso::Vector2<float>::make(1.0f, 1.0f)}}};
    }

    void reset() {
        scrolling_speed = INIT_SCROLLING_SPEED;

        sprites[0].pivot.x() = 0.0f;
        sprites[1].pivot.x() = 2.0f;
    }

    void update() {
        scrolling_speed *= SCROLLING_SPEED_INC_FACTOR;

        for (auto& sprite : sprites) {
            sprite.pivot.x() -= scrolling_speed;
            if (sprite.pivot.x() <= -2.0f) {
                sprite.pivot.x() += 4.0f;
            }
        }
    }

    void draw(const paradiso::Shader& shader) {
        for (const auto& sprite : sprites) {
            shader.set_uniform("pivot", sprite.pivot);
            shader.set_uniform("scale", sprite.scale);
            shader.set_uniform("rotation", sprite.rotation);
            renderer.draw(sprite, shader);
        }
    }
};

struct Ground {
    static constexpr float INIT_SCROLLING_SPEED = 0.009f;

    float scrolling_speed = INIT_SCROLLING_SPEED;

    std::array<paradiso::Sprite, 2> sprites;
    paradiso::Renderer renderer{};

    AABB aabb{};

    void init(SpriteMap& sprite_map) {
        // The image's height is 71 px.
        float scale_y = 71.0f / WINDOW_HEIGHT;

        float pivot_y = -1.0f + scale_y;

        sprites = {
            paradiso::Sprite{
                .bitmap = sprite_map["ground"],
                .pivot = {paradiso::Vector2<float>::make(0.0f, pivot_y)},
                .scale = {paradiso::Vector2<float>::make(1.0f, scale_y)}},
            paradiso::Sprite{
                .bitmap = sprite_map["ground"],
                .pivot = {paradiso::Vector2<float>::make(2.0f, pivot_y)},
                .scale = {paradiso::Vector2<float>::make(1.0f, scale_y)}}};

        // We'll make the height half the height of the texture.
        // (If you want 1:1 size, you need to double the scale of the sprite.)
        aabb = AABB{.position = {paradiso::Vector2<float>::make(-1.0f, -1.0f)},
                    .size = {paradiso::Vector2<float>::make(2.0f, scale_y)}};
    }

    void reset() {
        scrolling_speed = INIT_SCROLLING_SPEED;

        sprites[0].pivot.x() = 0.0f;
        sprites[1].pivot.x() = 2.0f;
    }

    void update() {
        scrolling_speed *= SCROLLING_SPEED_INC_FACTOR;

        for (auto& sprite : sprites) {
            sprite.pivot.x() -= scrolling_speed;
            if (sprite.pivot.x() <= -2.0f) {
                sprite.pivot.x() += 4.0f;
            }
        }
    }

    void draw(const paradiso::Shader& shader) {
        for (const auto& sprite : sprites) {
            shader.set_uniform("pivot", sprite.pivot);
            shader.set_uniform("scale", sprite.scale);
            shader.set_uniform("rotation", sprite.rotation);
            renderer.draw(sprite, shader);
        }
    }
};

struct Rocks {
    static constexpr float INIT_SCROLLING_SPEED = 0.006f;
    static constexpr float GAP_SIZE = 1.5f;

    // The image's size is 108x239 px.
    static constexpr float INIT_SCALE_X = 108.0f / WINDOW_WIDTH;
    static constexpr float INIT_SCALE_Y = 239.0f / WINDOW_HEIGHT;

    static constexpr paradiso::Vector2<float> INIT_SCALE{
        paradiso::Vector2<float>::make(INIT_SCALE_X, INIT_SCALE_Y)};

    float scrolling_speed = INIT_SCROLLING_SPEED;

    paradiso::Sprite top_sprite;
    paradiso::Sprite bottom_sprite;

    paradiso::Renderer top_renderer{};
    paradiso::Renderer bottom_renderer{};

    AABB top_aabb;
    AABB bottom_aabb;

    float init_pivot_x = 0.0f;

    auto get_random_top_position_y() -> float {
        return 1.0f - INIT_SCALE_Y + (std::rand() % 50) / 100.0f + 0.01f;
    }

    void init_aabbs() {
        // We'll make the width a quarter the width of the texture.
        // (If you want 1:1 size, you need to double the scale of the sprite.)
        auto size = paradiso::Vector2<float>{
            paradiso::Vector2<float>::make(INIT_SCALE_X / 2, INIT_SCALE_Y * 2)};

        auto top_position =
            paradiso::Vector2<float>{paradiso::Vector2<float>::make(
                top_sprite.pivot.x() - size.x() / 2,
                top_sprite.pivot.y() - size.y() / 2)};

        auto bottom_position =
            paradiso::Vector2<float>{paradiso::Vector2<float>::make(
                bottom_sprite.pivot.x() - size.x() / 2,
                bottom_sprite.pivot.y() - size.y() / 2)};

        top_aabb = AABB{top_position, size};
        bottom_aabb = AABB{bottom_position, size};
    }

    void init(SpriteMap& sprite_map, float pivot_x) {
        init_pivot_x = pivot_x;

        float top_position_y = get_random_top_position_y();
        float bottom_position_y = top_position_y - GAP_SIZE;

        top_sprite = paradiso::Sprite{.bitmap = sprite_map["rocks.top"],
                                      .pivot = {paradiso::Vector2<float>::make(
                                          init_pivot_x, top_position_y)},
                                      .scale = INIT_SCALE};

        bottom_sprite =
            paradiso::Sprite{.bitmap = sprite_map["rocks.bottom"],
                             .pivot = {paradiso::Vector2<float>::make(
                                 init_pivot_x, bottom_position_y)},
                             .scale = INIT_SCALE};

        init_aabbs();
    }

    void reset() {
        scrolling_speed = INIT_SCROLLING_SPEED;

        float top_position_y = get_random_top_position_y();
        float bottom_position_y = top_position_y - GAP_SIZE;

        top_sprite.pivot = paradiso::Vector2<float>{
            paradiso::Vector2<float>::make(init_pivot_x, top_position_y)};
        bottom_sprite.pivot = paradiso::Vector2<float>{
            paradiso::Vector2<float>::make(init_pivot_x, bottom_position_y)};

        init_aabbs();
    }

    void jump_back() {
        float top_position_y = get_random_top_position_y();
        float bottom_position_y = top_position_y - GAP_SIZE;

        top_sprite.pivot = paradiso::Vector2<float>{
            paradiso::Vector2<float>::make(1.2f, top_position_y)};
        bottom_sprite.pivot = paradiso::Vector2<float>{
            paradiso::Vector2<float>::make(1.2f, bottom_position_y)};

        init_aabbs();
    }

    void update() {
        scrolling_speed *= SCROLLING_SPEED_INC_FACTOR;

        top_sprite.pivot.x() -= scrolling_speed;
        bottom_sprite.pivot.x() -= scrolling_speed;

        // +----------+
        // |          |
        // |          |
        // |  #  #-------- the x it is.
        // |   \      |
        // |    \     |
        // +-----\----+
        //        \
        //         *------ the x we need.

        // @Efficiency: We could just scroll the AABBs with the sprites.
        top_aabb.position.x() = top_sprite.pivot.x() - top_aabb.size.x() / 2;
        bottom_aabb.position.x() =
            bottom_sprite.pivot.x() - bottom_aabb.size.x() / 2;

        if (top_sprite.pivot.x() <= -1.2f) {
            // Reset.
            jump_back();
            return;
        }
    }

    void draw(const paradiso::Shader& shader) {
        shader.set_uniform("pivot", top_sprite.pivot);
        shader.set_uniform("scale", top_sprite.scale);
        shader.set_uniform("rotation", top_sprite.rotation);
        top_renderer.draw(top_sprite, shader);

        shader.set_uniform("pivot", bottom_sprite.pivot);
        shader.set_uniform("scale", bottom_sprite.scale);
        shader.set_uniform("rotation", bottom_sprite.rotation);
        bottom_renderer.draw(bottom_sprite, shader);
    }
};

struct Plane {
    static constexpr float GRAVITY = -0.001f;
    static constexpr float JUMP_VELOCITY = 0.03f;
    static constexpr float MAX_VELOCITY = 0.03f;

    static constexpr float INIT_POSITION_Y = 0.0f;
    static constexpr float INIT_VELOCITY_Y = 0.0f;
    static constexpr float INIT_ROTATION = 0.0f;

    static const unsigned int ANIM_INTERVAL = 10;

    // The image's size is 88x73 px.
    static constexpr float INIT_SCALE_X = 88.0f / WINDOW_WIDTH;
    static constexpr float INIT_SCALE_Y = 73.0f / WINDOW_HEIGHT;

    static constexpr paradiso::Vector2<float> INIT_SCALE{
        paradiso::Vector2<float>::make(INIT_SCALE_X, INIT_SCALE_Y)};

    static constexpr paradiso::Vector2<float> INIT_PIVOT{
        paradiso::Vector2<float>::make(0.0f, INIT_POSITION_Y)};

    // Animation sprites
    std::array<paradiso::Sprite, 3> sprites;
    std::array<paradiso::Renderer, 3> renderers;

    unsigned int anim_counter = 0;

    // The direction where 'current_sprite' should go because the animation
    // shouldn't loop.
    int anim_direction = 1;

    unsigned int current_sprite = 0;

    float position_y = INIT_POSITION_Y;
    float velocity_y = INIT_VELOCITY_Y;
    float rotation = INIT_ROTATION;

    AABB aabb;

    void init_aabb() {
        // We'll make the size half the size of the texture.
        // (If you want 1:1 size, you need to double the scale of the sprite.)
        // We could use INIT_SCALE here, but we'll keep this for clarity.
        auto size = paradiso::Vector2<float>{
            paradiso::Vector2<float>::make(INIT_SCALE_X, INIT_SCALE_Y)};

        // Center it.
        auto position = paradiso::Vector2<float>{paradiso::Vector2<float>::make(
            INIT_PIVOT.x() - size.x() / 2, INIT_PIVOT.y() - size.y() / 2)};

        aabb = AABB{position, size};
    }

    void init(SpriteMap& sprite_map) {
        sprites = {paradiso::Sprite{.bitmap = sprite_map["plane.1"],
                                    .pivot = INIT_PIVOT,
                                    .scale = INIT_SCALE},
                   paradiso::Sprite{.bitmap = sprite_map["plane.2"],
                                    .pivot = INIT_PIVOT,
                                    .scale = INIT_SCALE},
                   paradiso::Sprite{.bitmap = sprite_map["plane.3"],
                                    .pivot = INIT_PIVOT,
                                    .scale = INIT_SCALE}};

        init_aabb();
    }

    void reset() {
        for (auto& sprite : sprites) {
            sprite.pivot = INIT_PIVOT;
            sprite.scale = INIT_SCALE;
        }

        position_y = INIT_POSITION_Y;
        velocity_y = INIT_VELOCITY_Y;
        rotation = INIT_ROTATION;

        init_aabb();
    }

    void update_animation() {
        if (anim_counter < ANIM_INTERVAL) {
            anim_counter++;
        } else {
            anim_counter = 0;

            current_sprite += anim_direction;
            if (current_sprite >= sprites.size() - 1) {
                anim_direction = -1;
            } else if (current_sprite == 0) {
                anim_direction = 1;
            }
        }
    }

    void update(const paradiso::Window::KeyboardInputStack& input) {
        update_animation();

        if (input.size() && input.top().key == ACTION_KEY &&
            input.top().action == 1) {
            velocity_y += JUMP_VELOCITY;
        } else {
            velocity_y += GRAVITY;
        }

        velocity_y = std::clamp(velocity_y, -MAX_VELOCITY, MAX_VELOCITY);
        position_y += velocity_y;

        // +-------------+
        // |             |
        // |             |
        // |             |
        // |      #--------- the y it is.
        // |             |
        // |      #      |
        // |       \     |
        // +--------\----+
        //           \   
        //            *--- the y we need.

        // @Efficiency: We could just add the velocity to the AABB's y position.
        aabb.position.y() = position_y - aabb.size.y() / 2;

        rotation = velocity_y * 500.0f;
    }

    void draw(const paradiso::Shader& shader) {
        auto sprite = sprites[current_sprite];
        sprite.rotation = rotation * (std::numbers::pi / 180.0f);
        sprite.pivot.y() = position_y;

        shader.set_uniform("pivot", sprite.pivot);
        shader.set_uniform("scale", sprite.scale);
        shader.set_uniform("rotation", sprite.rotation);
        renderers[current_sprite].draw(sprite, shader);
    }
};

struct StartUI {
    static const unsigned int TAP_ANIM_INTERVAL = 40;

    // textGetReady.png, tapLeft.png, tapRight.png
    std::array<paradiso::Sprite, 3> base_sprites;
    std::array<paradiso::Renderer, 3> base_renderers;

    // tap.png, tapTick.png
    std::array<paradiso::Sprite, 2> tap_sprites;
    std::array<paradiso::Renderer, 2> tap_renderers;

    unsigned int tap_anim_counter = 0;
    unsigned int current_tap_sprite = 0;

    void init(SpriteMap& sprite_map) {
        base_sprites = {
            paradiso::Sprite{
                .bitmap = sprite_map["start_ui.text"],
                .pivot = {paradiso::Vector2<float>::make(0.0f, 0.5f)},
                // The image's size is 400x73 px.
                .scale = {paradiso::Vector2<float>::make(
                    400.0f / WINDOW_WIDTH, 73.0f / WINDOW_HEIGHT)}},
            paradiso::Sprite{
                .bitmap = sprite_map["tap_sign.left"],
                .pivot = {paradiso::Vector2<float>::make(-0.5f, 0.0f)},
                // The image's size is 85x42 px.
                .scale = {paradiso::Vector2<float>::make(
                    85.0f / WINDOW_WIDTH, 42.0f / WINDOW_HEIGHT)}},
            paradiso::Sprite{
                .bitmap = sprite_map["tap_sign.right"],
                .pivot = {paradiso::Vector2<float>::make(0.5f, 0.0f)},
                // The image's size is 85x42 px.
                .scale = {paradiso::Vector2<float>::make(
                    85.0f / WINDOW_WIDTH, 42.0f / WINDOW_HEIGHT)}},
        };

        tap_sprites = {
            paradiso::Sprite{
                .bitmap = sprite_map["tap.normal"],
                .pivot = {paradiso::Vector2<float>::make(0.0f, -0.5f)},
                // The image's size is 59x59 px.
                .scale = {paradiso::Vector2<float>::make(
                    59.0f / WINDOW_WIDTH, 59.0f / WINDOW_HEIGHT)}},
            paradiso::Sprite{
                .bitmap = sprite_map["tap.tick"],
                .pivot = {paradiso::Vector2<float>::make(0.0f, -0.5f)},
                // The image's size is 59x59 px.
                .scale = {paradiso::Vector2<float>::make(
                    59.0f / WINDOW_WIDTH, 59.0f / WINDOW_HEIGHT)}},
        };
    }

    void update() {
        if (tap_anim_counter < TAP_ANIM_INTERVAL) {
            tap_anim_counter++;
        } else {
            tap_anim_counter = 0;
            current_tap_sprite = (current_tap_sprite + 1) % tap_sprites.size();
        }
    }

    void draw(const paradiso::Shader& shader) {
        for (unsigned int i = 0; i < base_sprites.size(); i++) {
            auto sprite = base_sprites[i];
            auto& renderer = base_renderers[i];

            shader.set_uniform("pivot", sprite.pivot);
            shader.set_uniform("scale", sprite.scale);
            shader.set_uniform("rotation", sprite.rotation);
            renderer.draw(sprite, shader);
        }

        auto tap_sprite = tap_sprites[current_tap_sprite];
        auto& tap_renderer = tap_renderers[current_tap_sprite];

        shader.set_uniform("pivot", tap_sprite.pivot);
        shader.set_uniform("scale", tap_sprite.scale);
        shader.set_uniform("rotation", tap_sprite.rotation);
        tap_renderer.draw(tap_sprite, shader);
    }
};

struct GameOverUI {
    paradiso::Sprite sprite;
    paradiso::Renderer renderer{};

    void init(SpriteMap& sprite_map) {
        // The image's size is 412x78 px.
        float scale_x = 412.0f / WINDOW_WIDTH;
        float scale_y = 78.0f / WINDOW_HEIGHT;

        sprite = {.bitmap = sprite_map["game_over_ui.text"],
                  .pivot = {paradiso::Vector2<float>::make(0.0f, 0.0f)},
                  .scale = {paradiso::Vector2<float>::make(scale_x, scale_y)}};
    }

    void update() {
        // @ToDo: Make the sprite slide down.
    }

    void draw(const paradiso::Shader& shader) {
        shader.set_uniform("pivot", sprite.pivot);
        shader.set_uniform("scale", sprite.scale);
        shader.set_uniform("rotation", sprite.rotation);
        renderer.draw(sprite, shader);
    }
};

enum class GameState { Start, Playing, GameOver };

struct App {
    SpriteMap sprites;

    static auto create() -> App {
        auto app = App{};

        paradiso::BitmapIO::get().set_path(
            paradiso::get_executable_path().parent_path().string() + "/assets");

        auto assets = std::array{
            SpriteName{"background", "PNG/background.png"},
            SpriteName{"ground", "PNG/groundGrass.png"},
            SpriteName{"rocks.top", "PNG/rockGrassDown.png"},
            SpriteName{"rocks.bottom", "PNG/rockGrass.png"},
            SpriteName{"plane.1", "PNG/Planes/planeRed1.png"},
            SpriteName{"plane.2", "PNG/Planes/planeRed2.png"},
            SpriteName{"plane.3", "PNG/Planes/planeRed3.png"},
            SpriteName{"tap_sign.left", "PNG/UI/tapRight.png"},
            SpriteName{"tap_sign.right", "PNG/UI/tapLeft.png"},
            SpriteName{"tap.normal", "PNG/UI/tap.png"},
            SpriteName{"tap.tick", "PNG/UI/tapTick.png"},
            SpriteName{"start_ui.text", "PNG/UI/textGetReady.png"},
            SpriteName{"game_over_ui.text", "PNG/UI/textGameOver.png"},
        };

        for (const auto& [name, filename] : assets) {
            std::print("{} : {} -> ", name, filename);

            auto bitmap = paradiso::BitmapIO::get().load(filename);
            app.sprites[name] = bitmap;
        }

        return app;
    }

    void run() {
        auto canvas_size =
            paradiso::Size{.width = WINDOW_WIDTH * WINDOW_SCALE,
                           .height = WINDOW_HEIGHT * WINDOW_SCALE};

        // Unser Fenster, auf das wir rendern.
        auto window = paradiso::Window();
        window
            .set_size(canvas_size)                             // ... Größe
            .set_position(paradiso::Point{.x = 200, .y = 200}) // ... Position
            .set_title("ParadiSO.TappyPlane")                  // ... Titel
            .set_visible(true); // ... und jetzt anzeigen!

        // Der Fenster Context.
        auto context = paradiso::Context{};

        // Ein Shader (Schattierungsprogramm).
        auto shader = paradiso::Shader{};

        // Wir nutzen einen vorgefertigten Shader, der speziell für Sprites ist.
        shader.load_preset(paradiso::Shader::Preset::Sprite);

        // Ein Viewport stellt die Sicht der Kamera dar, d.h. bei quadratischen
        // Pixeln sollte hier auch eine dementsprechende Größe eingestellt
        // werden.
        context.set_viewport(paradiso::Rectangle{
            .position = paradiso::Point{.x = 0, .y = 0}, .size = canvas_size});

        // Conflower blue
        context.set_clearcolor(paradiso::RGBA::from_rgb(0x64, 0x95, 0xED));

        // Der Asset Loader bekommt den Pfad.
        paradiso::BitmapIO::get().set_path("assets");

        // Wir initialisieren unsere Sachen.
        auto game_state = GameState::Start;

        auto background = Background{};
        auto ground = Ground{};
        auto rocks1 = Rocks{};
        auto rocks2 = Rocks{};
        auto plane = Plane{};
        auto start_ui = StartUI{};
        auto game_over_ui = GameOverUI{};

        background.init(sprites);
        ground.init(sprites);
        rocks1.init(sprites, 1.2f);
        rocks2.init(sprites, 2.4f);
        plane.init(sprites);
        start_ui.init(sprites);
        game_over_ui.init(sprites);

        auto top_aabb =
            AABB{.position = {paradiso::Vector2<float>::make(-1.0f, 1.0f)},
                 .size = {paradiso::Vector2<float>::make(2.0f, 0.5f)}};

        while (window.update([&](paradiso::Window& window) -> bool {
            auto t1 = std::chrono::high_resolution_clock::now();

            if (window.keyboard_input().size() &&
                window.keyboard_input().top().key == 'Q') {
                // Update-Loop beenden.
                return false;
            }

            if (game_state == GameState::Start) {
                if (window.keyboard_input().size() &&
                    window.keyboard_input().top().key == ACTION_KEY &&
                    window.keyboard_input().top().action == 1) {
                    game_state = GameState::Playing;

                    // Make the plane jump for the first time.
                    // @ToDo: The plane jumps too high because there was no
                    // gravity before.
                    plane.update(window.keyboard_input());
                }

                background.update();
                plane.update_animation(); // Only the animation.
                ground.update();
                start_ui.update();
            } else if (game_state == GameState::Playing) {
                background.update();
                ground.update();
                rocks1.update();
                rocks2.update();

                plane.update(window.keyboard_input());

                if (plane.aabb.is_colliding_with(rocks1.top_aabb) ||
                    plane.aabb.is_colliding_with(rocks1.bottom_aabb) ||
                    plane.aabb.is_colliding_with(rocks2.top_aabb) ||
                    plane.aabb.is_colliding_with(rocks2.bottom_aabb) ||
                    plane.aabb.is_colliding_with(top_aabb) ||
                    plane.aabb.is_colliding_with(ground.aabb)) {
                    game_state = GameState::GameOver;
                }
            } else if (game_state == GameState::GameOver) {
                if (window.keyboard_input().size() &&
                    window.keyboard_input().top().key == ACTION_KEY) {

                    // Reset everything.
                    background.reset();
                    ground.reset();
                    rocks1.reset();
                    rocks2.reset();
                    plane.reset();
                    game_state = GameState::Start;
                }
            }

            context.set_viewport(
                paradiso::Rectangle{.position = paradiso::Point{.x = 0, .y = 0},
                                    .size = canvas_size});
            context.clear();

            background.draw(shader);
            plane.draw(shader);
            rocks1.draw(shader);
            rocks2.draw(shader);
            ground.draw(shader);

            // Kinda dumb that we check the game state here the second time but
            // I like to seperate 'update' from 'draw'.
            if (game_state == GameState::Start) {
                start_ui.draw(shader);
            } else if (game_state == GameState::GameOver) {
                game_over_ui.draw(shader);
            }

            // Einen kurzen Moment warten, um auf 60 FPS zu kommen.
            auto t2 = std::chrono::high_resolution_clock::now();
            auto duration =
                std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1);
            auto wait =
                std::chrono::microseconds(1000000 / FRAME_RATE) - duration;
            std::this_thread::sleep_for(wait);

            return true;
        })) {
        };
    }
};

} // namespace TappyPlane

auto main() -> int {
    std::srand(std::time(nullptr));
    auto app = TappyPlane::App::create();
    app.run();
    return 0;
}
