#ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H
#define OPENMW_MWRENDER_RENDERINGMANAGER_H

#include "objects.hpp"
#include "renderinginterface.hpp"
#include "rendermode.hpp"

#include <components/settings/settings.hpp>
#include <components/vfs/pathutil.hpp>

#include <osg/Light>
#include <osg/ref_ptr>

#include <osgUtil/IncrementalCompileOperation>

#include <deque>
#include <memory>
#include <span>
#include <unordered_map>

namespace osg
{
    class Group;
    class PositionAttitudeTransform;
}

namespace osgUtil
{
    class IntersectionVisitor;
    class Intersector;
}

namespace Resource
{
    class ResourceSystem;
}

namespace osgViewer
{
    class Viewer;
}

namespace ESM
{
    struct Cell;
    struct FormId;
    using RefNum = FormId;
}

namespace Terrain
{
    class World;
}

namespace Fallback
{
    class Map;
}

namespace SceneUtil
{
    class ShadowManager;
    class WorkQueue;
    class LightManager;
    class UnrefQueue;
}

namespace DetourNavigator
{
    struct Navigator;
    struct Settings;
    struct AgentBounds;
}

namespace MWWorld
{
    class GroundcoverStore;
    class Cell;
}

namespace Debug
{
    struct DebugDrawer;
}

namespace MWRender
{
    class StateUpdater;
    class SharedUniformStateUpdater;
    class PerViewUniformStateUpdater;
    class IntersectionVisitorWithIgnoreList;

    class EffectManager;
    class ScreenshotManager;
    class FogManager;
    class SkyManager;
    class NpcAnimation;
    class Pathgrid;
    class Camera;
    class Water;
    class TerrainStorage;
    class LandManager;
    class NavMesh;
    class ActorsPaths;
    class RecastMesh;
    class ObjectPaging;
    class Groundcover;
    class PostProcessor;

    class RenderingManager : public MWRender::RenderingInterface
    {
    public:
        RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
            Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
            DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore,
            SceneUtil::UnrefQueue& unrefQueue);
        ~RenderingManager();

        osgUtil::IncrementalCompileOperation* getIncrementalCompileOperation();

        MWRender::Objects& getObjects() override;

        Resource::ResourceSystem* getResourceSystem();

        SceneUtil::WorkQueue* getWorkQueue();
        Terrain::World* getTerrain();

        void preloadCommonAssets();

        double getReferenceTime() const;

        SceneUtil::LightManager* getLightRoot();

        void setNightEyeFactor(float factor);

        void setAmbientColour(const osg::Vec4f& colour);

        int skyGetMasserPhase() const;
        int skyGetSecundaPhase() const;
        void skySetMoonColour(bool red);

        void setSunDirection(const osg::Vec3f& direction);
        void setSunColour(const osg::Vec4f& diffuse, const osg::Vec4f& specular, float sunVis);
        void setNight(bool isNight) { mNight = isNight; }

        void configureAmbient(const MWWorld::Cell& cell);
        void configureFog(const MWWorld::Cell& cell);
        void configureFog(
            float fogDepth, float underwaterFog, float dlFactor, float dlOffset, const osg::Vec4f& colour);

        void addCell(const MWWorld::CellStore* store);
        void removeCell(const MWWorld::CellStore* store);

        void enableTerrain(bool enable, ESM::RefId worldspace);

        void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated);

        void rotateObject(const MWWorld::Ptr& ptr, const osg::Quat& rot);
        void moveObject(const MWWorld::Ptr& ptr, const osg::Vec3f& pos);
        void scaleObject(const MWWorld::Ptr& ptr, const osg::Vec3f& scale);

        void removeObject(const MWWorld::Ptr& ptr);

        void setWaterEnabled(bool enabled);
        void setWaterHeight(float level);

        /// Take a screenshot of w*h onto the given image, not including the GUI.
        void screenshot(osg::Image* image, int w, int h);

        struct RayResult
        {
            bool mHit;
            osg::Vec3f mHitNormalWorld;
            osg::Vec3f mHitPointWorld;
            MWWorld::Ptr mHitObject;
            ESM::RefNum mHitRefnum;
            float mRatio;
        };

        RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer,
            bool ignoreActors = false, std::span<const MWWorld::Ptr> ignoreList = {});

        /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen
        /// coordinates, where (0,0) is the top left corner.
        RayResult castCameraToViewportRay(
            const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors = false);

        /// Get normalized screen coordinates of the bounding box's summit, where (0,0) is the top left corner
        osg::Vec2f getScreenCoords(const osg::BoundingBox& bb);

        void setSkyEnabled(bool enabled);

        bool toggleRenderMode(RenderMode mode);

        SkyManager* getSkyManager();

        void spawnEffect(VFS::Path::NormalizedView model, std::string_view texture, const osg::Vec3f& worldPosition,
            float scale = 1.f, bool isMagicVFX = true, bool useAmbientLight = true);

        /// Clear all savegame-specific data
        void clear();

        /// Clear all worldspace-specific data
        void notifyWorldSpaceChanged();

        void update(float dt, bool paused);

        Animation* getAnimation(const MWWorld::Ptr& ptr);
        const Animation* getAnimation(const MWWorld::ConstPtr& ptr) const;

        PostProcessor* getPostProcessor();

        void addWaterRippleEmitter(const MWWorld::Ptr& ptr);
        void removeWaterRippleEmitter(const MWWorld::Ptr& ptr);
        void emitWaterRipple(const osg::Vec3f& pos);

        void updatePlayerPtr(const MWWorld::Ptr& ptr);

        void removePlayer(const MWWorld::Ptr& player);
        void setupPlayer(const MWWorld::Ptr& player);
        void renderPlayer(const MWWorld::Ptr& player);

        void rebuildPtr(const MWWorld::Ptr& ptr);

        void processChangedSettings(const Settings::CategorySettingVector& settings);

        float getNearClipDistance() const { return mNearClip; }
        float getViewDistance() const { return mViewDistance; }

        void setViewDistance(float distance, bool delay = false);

        float getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace);

        // camera stuff
        Camera* getCamera() { return mCamera.get(); }

        /// temporarily override the field of view with given value.
        void overrideFieldOfView(float val);
        void setFieldOfView(float val);
        float getFieldOfView() const;
        /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file.
        void resetFieldOfView();

        osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& object) const;

        // Return local bounding box. Safe to be called in parallel with cull thread.
        osg::BoundingBox getCullSafeBoundingBox(const MWWorld::Ptr& ptr) const;

        void exportSceneGraph(
            const MWWorld::Ptr& ptr, const std::filesystem::path& filename, const std::string& format);

        Debug::DebugDrawer& getDebugDrawer() const { return *mDebugDraw; }

        LandManager* getLandManager() const;

        bool toggleBorders();

        void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
            const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const;

        void removeActorPath(const MWWorld::ConstPtr& actor) const;

        void setNavMeshNumber(const std::size_t value);

        void setActiveGrid(const osg::Vec4i& grid);

        bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled);
        void pagingBlacklistObject(int type, const MWWorld::ConstPtr& ptr);
        bool pagingUnlockCache();
        void getPagedRefnums(const osg::Vec4i& activeGrid, std::vector<ESM::RefNum>& out);

        void updateProjectionMatrix();

        void setScreenRes(int width, int height);

        void setNavMeshMode(Settings::NavMeshRenderMode value);

    private:
        void updateTextureFiltering();
        void updateAmbient();
        void setFogColor(const osg::Vec4f& color);

        struct WorldspaceChunkMgr
        {
            std::unique_ptr<Terrain::World> mTerrain;
            std::unique_ptr<ObjectPaging> mObjectPaging;
            std::unique_ptr<Groundcover> mGroundcover;
        };

        WorldspaceChunkMgr& getWorldspaceChunkMgr(ESM::RefId worldspace);

        void reportStats() const;

        void updateNavMesh();

        void updateRecastMesh();

        const bool mSkyBlending;

        osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector,
            bool ignorePlayer, bool ignoreActors, std::span<const MWWorld::Ptr> ignoreList = {});

        osg::ref_ptr<IntersectionVisitorWithIgnoreList> mIntersectionVisitor;

        osg::ref_ptr<osgViewer::Viewer> mViewer;
        osg::ref_ptr<osg::Group> mRootNode;
        osg::ref_ptr<SceneUtil::LightManager> mSceneRoot;
        Resource::ResourceSystem* mResourceSystem;

        osg::ref_ptr<SceneUtil::WorkQueue> mWorkQueue;

        osg::ref_ptr<osg::Light> mSunLight;

        DetourNavigator::Navigator& mNavigator;
        std::unique_ptr<NavMesh> mNavMesh;
        std::size_t mNavMeshNumber = 0;
        std::unique_ptr<ActorsPaths> mActorsPaths;
        std::unique_ptr<RecastMesh> mRecastMesh;
        std::unique_ptr<Pathgrid> mPathgrid;
        std::unique_ptr<Objects> mObjects;
        std::unique_ptr<Water> mWater;
        std::unordered_map<ESM::RefId, WorldspaceChunkMgr> mWorldspaceChunks;
        Terrain::World* mTerrain;
        std::unique_ptr<TerrainStorage> mTerrainStorage;
        ObjectPaging* mObjectPaging;
        Groundcover* mGroundcover;
        std::unique_ptr<SkyManager> mSky;
        std::unique_ptr<FogManager> mFog;
        std::unique_ptr<ScreenshotManager> mScreenshotManager;
        std::unique_ptr<EffectManager> mEffectManager;
        std::unique_ptr<SceneUtil::ShadowManager> mShadowManager;
        osg::ref_ptr<PostProcessor> mPostProcessor;
        osg::ref_ptr<NpcAnimation> mPlayerAnimation;
        osg::ref_ptr<SceneUtil::PositionAttitudeTransform> mPlayerNode;
        std::unique_ptr<Camera> mCamera;
        osg::ref_ptr<Debug::DebugDrawer> mDebugDraw;

        osg::ref_ptr<StateUpdater> mStateUpdater;
        osg::ref_ptr<SharedUniformStateUpdater> mSharedUniformStateUpdater;
        osg::ref_ptr<PerViewUniformStateUpdater> mPerViewUniformStateUpdater;

        osg::Vec4f mAmbientColor;
        float mNightEyeFactor;

        float mNearClip;
        float mViewDistance;
        bool mFieldOfViewOverridden;
        float mFieldOfViewOverride;
        float mFieldOfView;
        float mFirstPersonFieldOfView;
        bool mUpdateProjectionMatrix = false;
        bool mNight = false;
        const MWWorld::GroundcoverStore& mGroundCoverStore;

        void operator=(const RenderingManager&);
        RenderingManager(const RenderingManager&);
    };

}

#endif
