#pragma once

#include "OpenSimLoader.hpp"
#include "OrbitCameraController.hpp"

#include <Magnum/GL/Buffer.h>
#include <Magnum/GL/Mesh.h>
#include <Magnum/ImGuiIntegration/Context.hpp>
#include <Magnum/Math/Color.h>
#include <Magnum/Math/Matrix4.h>
#include <Magnum/Math/Time.h>
#include <Magnum/SceneGraph/Camera.h>
#include <Magnum/SceneGraph/Drawable.h>
#include <Magnum/SceneGraph/MatrixTransformation3D.h>
#include <Magnum/SceneGraph/Object.h>
#include <Magnum/SceneGraph/Scene.h>
#include <Magnum/Shaders/PhongGL.h>

#include <chrono>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#ifdef CORRADE_TARGET_ANDROID
#include <Magnum/Platform/AndroidApplication.h>
#elif defined(CORRADE_TARGET_EMSCRIPTEN)
#include <Magnum/Platform/EmscriptenApplication.h>
#else
#include <Magnum/Platform/Sdl2Application.h>
#endif

namespace osim_viewer {

struct CliOptions {
    std::string modelPath;
    std::string motionPath;
    std::vector<std::string> geometryDirs;
    float initialSpeed = 1.0f;
    bool startPaused = false;
};

class ViewerApp: public Magnum::Platform::Application {
public:
    explicit ViewerApp(const Arguments& arguments, CliOptions options);

    void drawEvent() override;
    void viewportEvent(ViewportEvent& event) override;
    void keyPressEvent(KeyEvent& event) override;
    void keyReleaseEvent(KeyEvent& event) override;
    void pointerPressEvent(PointerEvent& event) override;
    void pointerReleaseEvent(PointerEvent& event) override;
    void pointerMoveEvent(PointerMoveEvent& event) override;
    void scrollEvent(ScrollEvent& event) override;
    void textInputEvent(TextInputEvent& event) override;

private:
    using Object3D = Magnum::SceneGraph::Object<Magnum::SceneGraph::MatrixTransformation3D>;
    using Scene3D = Magnum::SceneGraph::Scene<Magnum::SceneGraph::MatrixTransformation3D>;
    using Camera3D = Magnum::SceneGraph::Camera3D;
    using Drawable3D = Magnum::SceneGraph::Drawable3D;
    using DrawableGroup3D = Magnum::SceneGraph::DrawableGroup3D;

    struct GpuMesh {
        Magnum::GL::Mesh mesh;
        Magnum::GL::Buffer vertexBuffer;
        Magnum::GL::Buffer indexBuffer;
        Magnum::Range3D bounds;
    };

    struct SceneGeometry {
        Object3D* object{};
        Magnum::Color4 color{1.0f};
    };

    struct SceneFrame {
        Object3D* object{};
        std::vector<SceneGeometry> geometries;
    };

    struct SceneMarker {
        Object3D* object{};
        Magnum::Color4 color{1.0f};
    };

    class MeshDrawable final: public Drawable3D {
    public:
        MeshDrawable(Object3D& object,
                     Magnum::Shaders::PhongGL& shader,
                     GpuMesh& mesh,
                     Magnum::Color4 color,
                     DrawableGroup3D& drawables);

    private:
        void draw(const Magnum::Matrix4& transformation, Camera3D& camera) override;

        Magnum::Shaders::PhongGL& _shader;
        GpuMesh& _mesh;
        Magnum::Color4 _color;
    };

    std::shared_ptr<GpuMesh> uploadMesh(const CpuMesh& mesh) const;
    void createSceneObjects();
    void updatePlayback();
    void updateSceneAtCurrentTime();
    void drawUi();
    std::size_t sampleIndexForTime(float time) const;

    CliOptions _options;
    LoadedScene _sceneData;

    Magnum::ImGuiIntegration::Context _imgui{Magnum::NoCreate};
    OrbitCameraController _orbit;

    Scene3D _scene;
    Object3D _cameraObject{&_scene};
    Camera3D _camera{_cameraObject};
    DrawableGroup3D _drawables;

    Magnum::Shaders::PhongGL _shader;
    std::vector<std::shared_ptr<GpuMesh>> _gpuMeshes;
    std::vector<std::unique_ptr<Drawable3D>> _drawableStorage;
    std::vector<std::unique_ptr<Object3D>> _objectStorage;
    std::vector<SceneFrame> _frames;
    std::vector<SceneMarker> _markers;

    std::shared_ptr<GpuMesh> _markerMesh;

    bool _isPlaying = false;
    float _playbackSpeed = 1.0f;
    float _currentTime = 0.0f;
    std::chrono::steady_clock::time_point _lastTick;

    std::optional<Magnum::Vector2> _lastPointerPosition;
    enum class DragMode {
        None,
        Orbit,
        Pan,
    } _dragMode = DragMode::None;
};

}
