#pragma once

#include "../defines.hpp"
#include <stack>
#include <vector>
#include "../SharedDefs.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/WLClasses.hpp"
#include <array>
#include <span>
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/CMType.hpp"
#include "IMonitorArrangeable.hpp"
#include "IMonitorQueryable.hpp"
#include "MonitorTypes.hpp"

#include <xf86drmMode.h>
#include "MonitorZoomController.hpp"
#include "../render/Texture.hpp"
#include "../render/Framebuffer.hpp"
#include "MonitorResources.hpp"
#include "../helpers/time/Timer.hpp"
#include "../helpers/math/Math.hpp"
#include "../desktop/reserved/ReservedArea.hpp"
#include <optional>
#include "../helpers/cm/ColorManagement.hpp"
#include "../helpers/signal/Signal.hpp"
#include "DamageRing.hpp"
#include <aquamarine/output/Output.hpp>
#include <aquamarine/allocator/Swapchain.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
#include <sys/types.h>

#include "../helpers/TransferFunction.hpp"
#include "../config/shared/monitor/MonitorRule.hpp"

class CSyncTimeline;
class CEventLoopTimer;

namespace Monitor {
    class CMonitorResources;
    class CMonitorFrameScheduler;
    class CMonitor;

    class CMonitorState {
      public:
        CMonitorState(CMonitor* owner);
        ~CMonitorState() = default;

        bool commit();
        bool test();
        bool updateSwapchain();
        void applyModeWithSwapchain(const SP<Aquamarine::SOutputMode>& mode);
        void applyCustomModeWithSwapchain(const SP<Aquamarine::SOutputMode>& mode);

      private:
        void      ensureBufferPresent();

        CMonitor* m_owner = nullptr;
    };

    class CMonitor : public IMonitorQueryable, public IMonitorArrangeable {
      public:
        CMonitor(SP<Aquamarine::IOutput> output);
        virtual ~CMonitor() override;

        Vector2D                    m_position         = Vector2D(-1, -1); // means unset
        Vector2D                    m_xwaylandPosition = Vector2D(-1, -1); // means unset
        Config::eAutoDirs           m_autoDir          = Config::DIR_AUTO_NONE;
        Vector2D                    m_size             = Vector2D(0, 0);
        Vector2D                    m_pixelSize        = Vector2D(0, 0);
        Vector2D                    m_transformedSize  = Vector2D(0, 0);

        MONITORID                   m_id                     = MONITOR_INVALID;
        PHLWORKSPACE                m_activeWorkspace        = nullptr;
        PHLWORKSPACE                m_activeSpecialWorkspace = nullptr;
        float                       m_setScale               = 1; // scale set by cfg
        float                       m_scale                  = 1; // real scale

        std::string                 m_name             = "";
        std::string                 m_description      = "";
        std::string                 m_shortDescription = "";

        drmModeModeInfo             m_customDrmMode = {};

        Desktop::CReservedArea      m_reservedArea;

        CMonitorState               m_state;
        CDamageRing                 m_damage;

        SP<Aquamarine::IOutput>     m_output;
        float                       m_refreshRate     = 60; // Hz
        int                         m_forceFullFrames = 0;
        bool                        m_scheduledRecalc = false;
        wl_output_transform         m_transform       = WL_OUTPUT_TRANSFORM_NORMAL;
        float                       m_xwaylandScale   = 1.f;

        std::optional<Vector2D>     m_forceSize;
        SP<Aquamarine::SOutputMode> m_currentMode;
        SP<Aquamarine::CSwapchain>  m_cursorSwapchain;
        uint32_t                    m_drmFormat     = DRM_FORMAT_INVALID;
        uint32_t                    m_prevDrmFormat = DRM_FORMAT_INVALID;

        CMonitorZoomController      m_zoomController;

        bool                        m_dpmsStatus       = true;
        bool                        m_vrrActive        = false; // this can be TRUE even if VRR is not active in the case that this display does not support it.
        bool                        m_enabled10bit     = false; // as above, this can be TRUE even if 10 bit failed.
        NCMType::eCMType            m_cmType           = NCMType::CM_SRGB;
        NTransferFunction::eTF      m_sdrEotf          = NTransferFunction::TF_DEFAULT;
        float                       m_sdrSaturation    = 1.0f;
        float                       m_sdrBrightness    = 1.0f;
        float                       m_sdrMinLuminance  = 0.2f;
        int                         m_sdrMaxLuminance  = 80;
        bool                        m_createdByUser    = false;
        bool                        m_isUnsafeFallback = false;

        SP<CEventLoopTimer>         m_dpmsRetryTimer;

        bool                        m_pendingFrame    = false; // if we schedule a frame during rendering, reschedule it after
        bool                        m_renderingActive = false;

        bool                        m_ratsScheduled = false;
        CTimer                      m_lastPresentationTimer;

        bool                        m_isBeingLeased = false;

        Config::CMonitorRule        m_activeMonitorRule;

        SP<Render::ITexture>        m_splash;
        SP<Render::ITexture>        m_background;

        // explicit sync
        Hyprutils::OS::CFileDescriptor m_inFence; // TODO: remove when aq uses CFileDescriptor

        PHLMONITORREF                  m_self;

        UP<CMonitorFrameScheduler>     m_frameScheduler;

        // mirroring
        PHLMONITORREF              m_mirrorOf;
        std::vector<PHLMONITORREF> m_mirrors;

        // ctm
        Mat3x3 m_ctm        = Mat3x3::identity();
        bool   m_ctmUpdated = false;

        // for tearing
        PHLWINDOWREF m_solitaryClient;

        // for direct scanout
        PHLWINDOWREF m_lastScanout;
        bool m_directScanoutIsActive    = false; // for cleanup logic. m_lastScanout.expired() can become true before the DS cleanup if client crashes/exits while DS is active.
        bool m_scanoutNeedsCursorUpdate = false;

        // for special fade/blur
        PHLANIMVAR<float> m_specialFade;
        PHLANIMVAR<float> m_specialDim;
        PHLANIMVAR<float> m_specialBlur;

        // for dpms off anim
        PHLANIMVAR<float> m_dpmsBlackOpacity;
        bool              m_pendingDpmsAnimation        = false;
        int               m_pendingDpmsAnimationCounter = 0;

        PHLANIMVAR<float> m_cursorZoom;

        // for fading in the wallpaper because it doesn't happen instantly (it's loaded async)
        PHLANIMVAR<float> m_backgroundOpacity;

        // for initial zoom anim
        PHLANIMVAR<float> m_zoomAnimProgress;
        CTimer            m_newMonitorAnimTimer;
        int               m_zoomAnimFrameCounter = 0;

        struct {
            bool canTear         = false;
            bool nextRenderTorn  = false;
            bool activelyTearing = false;

            bool busy                    = false;
            bool frameScheduledWhileBusy = false;
        } m_tearingState;

        struct {
            CSignalT<>                commit;
            CSignalT<>                destroy;
            CSignalT<>                connect;
            CSignalT<>                disconnect;
            CSignalT<>                dpmsChanged;
            CSignalT<>                modeChanged;
            CSignalT<Time::steady_tp> presented;
        } m_events;

        std::array<std::vector<PHLLSREF>, 4> m_layerSurfaceLayers;

        // keep in sync with socket1 output
        enum eDSBlockReason : uint16_t {
            DS_OK = 0,

            DS_BLOCK_UNKNOWN   = (1 << 0),
            DS_BLOCK_USER      = (1 << 1),
            DS_BLOCK_WINDOWED  = (1 << 2),
            DS_BLOCK_CONTENT   = (1 << 3),
            DS_BLOCK_MIRROR    = (1 << 4),
            DS_BLOCK_RECORD    = (1 << 5),
            DS_BLOCK_SW        = (1 << 6),
            DS_BLOCK_CANDIDATE = (1 << 7),
            DS_BLOCK_SURFACE   = (1 << 8),
            DS_BLOCK_TRANSFORM = (1 << 9),
            DS_BLOCK_DMA       = (1 << 10),
            DS_BLOCK_FAILED    = (1 << 11),
            DS_BLOCK_CM        = (1 << 12),

            DS_CHECKS_COUNT = 14,
        };

        // keep in sync with socket1 output
        enum eSolitaryCheck : uint32_t {
            SC_OK = 0,

            SC_UNKNOWN      = (1 << 0),
            SC_NOTIFICATION = (1 << 1),
            SC_LOCK         = (1 << 2),
            SC_WORKSPACE    = (1 << 3),
            SC_WINDOWED     = (1 << 4),
            SC_DND          = (1 << 5),
            SC_SPECIAL      = (1 << 6),
            SC_ALPHA        = (1 << 7),
            SC_OFFSET       = (1 << 8),
            SC_CANDIDATE    = (1 << 9),
            SC_OPAQUE       = (1 << 10),
            SC_TRANSFORM    = (1 << 11),
            SC_OVERLAYS     = (1 << 12),
            SC_FLOAT        = (1 << 13),
            SC_WORKSPACES   = (1 << 14),
            SC_SURFACES     = (1 << 15),
            SC_ERRORBAR     = (1 << 16),
            SC_FADEOUT      = (1 << 17),

            SC_CHECKS_COUNT = 18,
        };

        // keep in sync with socket1 output
        enum eTearingCheck : uint8_t {
            TC_OK = 0,

            TC_UNKNOWN   = (1 << 0),
            TC_NOT_TORN  = (1 << 1),
            TC_USER      = (1 << 2),
            TC_ZOOM      = (1 << 3),
            TC_SUPPORT   = (1 << 4),
            TC_CANDIDATE = (1 << 5),
            TC_WINDOW    = (1 << 6),
            TC_HW_CURSOR = (1 << 7),

            TC_CHECKS_COUNT = 8,
        };

        // methods
        void         onConnect(bool noRule);
        void         onDisconnect(bool destroy = false);
        void         applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf);
        void         addDamage(const pixman_region32_t* rg);
        void         addDamage(const CRegion& rg);
        void         addDamage(const CBox& box);
        void         scheduleFrame(Aquamarine::IOutput::scheduleFrameReason reason = Aquamarine::IOutput::AQ_SCHEDULE_CLIENT_UNKNOWN);
        bool         shouldSkipScheduleFrameOnMouseEvent();
        void         setMirror(const std::string&);
        bool         isMirror();
        float        getDefaultScale();
        void         changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false);
        void         changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false);
        void         setSpecialWorkspace(const PHLWORKSPACE& pWorkspace, bool noFocus = false);
        void         setSpecialWorkspace(const WORKSPACEID& id, bool noFocus = false);
        PHLWORKSPACE getCurrentWorkspace();
        WORKSPACEID  activeWorkspaceID();
        WORKSPACEID  activeSpecialWorkspaceID();
        void         scheduleDone();
        uint32_t     isSolitaryBlocked(bool full = false);
        void         recheckSolitary();
        uint8_t      isTearingBlocked(bool full = false);
        void         updateSurfaceScaleTransformDetails();
        bool         updateTearing();
        uint16_t     isDSBlocked(bool full = false);
        bool         attemptDirectScanout();
        void         handleDSleave();
        bool         canAttemptDirectScanoutFast() const;
        bool         isMultiGPU();
        void         setCTM(const Mat3x3& ctm);
        void         onCursorMovedOnMonitor();
        void         setDPMS(bool on);
        bool         shouldUseSoftwareCursors();

        // IMonitorQueryable / IMonitorArrangeable
        virtual MONITORID                   id() const override;
        virtual std::string_view            name() const override;
        virtual std::string_view            description() const override;
        virtual std::string_view            shortDescription() const override;
        virtual bool                        matchesStaticSelector(std::string_view selector) const override;
        virtual Vector2D                    position() const override;
        virtual Vector2D                    size() const override;
        virtual Vector2D                    pixelSize() const override;
        virtual Vector2D                    transformedSize() const override;
        virtual float                       scale() const override;
        virtual Hyprutils::Math::eTransform transform() const override;
        virtual CBox                        logicalBox() const override;
        virtual CBox                        logicalBoxMinusReserved() const override;
        virtual Vector2D                    middle() const override;
        virtual void                        moveTo(const Vector2D& pos) override;
        virtual bool                        enabled() const override;
        virtual bool                        hasOutput() const override;
        virtual SP<Aquamarine::IOutput>     output() const override;
        virtual std::optional<Vector2D>     explicitPosition() const override;
        virtual Config::eAutoDirs           autoDirection() const override;
        virtual Vector2D                    xwaylandPosition() const override;
        virtual float                       xwaylandScale() const override;
        virtual void                        setXWaylandPosition(const Vector2D& pos) override;
        virtual void                        setXWaylandScale(float scale) override;

        //
        bool applyMonitorRule(Config::CMonitorRule&& pMonitorRule);
        bool applyMonitorRuleSoft(Config::CMonitorRule&& pMonitorRule);

        //
        const Mat3x3&                                               getTransformMatrix();
        const Mat3x3&                                               getScaleMatrix();

        void                                                        debugLastPresentation(const std::string& message);

        bool                                                        supportsWideColor();
        bool                                                        supportsHDR();
        float                                                       minLuminance(float defaultValue = 0);
        int                                                         maxLuminance(int defaultValue = 80);
        int                                                         maxAvgLuminance(int defaultValue = 80);
        float                                                       maxFALL();
        float                                                       maxCLL();

        bool                                                        wantsWideColor();
        bool                                                        wantsHDR();

        bool                                                        inHDR();
        bool                                                        gammaRampsInUse();

        std::optional<NColorManagement::PImageDescription>          getFSImageDescription();

        NColorManagement::SPCPRimaries                              getMasteringPrimaries();
        NColorManagement::SImageDescription::SPCMasteringLuminances getMasteringLuminances();

        uint32_t                                                    getPreferredReadFormat();

        bool                                                        needsCM();
        /// Can do CM without shader (forDSmode ? check output image description : check workbuffer image description)
        bool                                                               canNoShaderCM(bool forDSmode = false);
        bool                                                               doesNoShaderCM();

        bool                                                               m_enabled             = false;
        bool                                                               m_renderingInitPassed = false;

        PHLWINDOWREF                                                       m_previousFSWindow;
        bool                                                               m_needsHDRupdate         = false;
        bool                                                               m_hdrMetadataFromSurface = false;

        std::optional<dev_t>                                               m_cachedAllocatorDRMDev;
        std::optional<dev_t>                                               m_cachedCompositorDRMDev;
        int                                                                m_cachedAllocatorDRMFD  = -1;
        int                                                                m_cachedCompositorDRMFD = -1;
        std::optional<bool>                                                m_cachedSameGPU;

        NColorManagement::PImageDescription                                m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{});
        bool                                                               m_noShaderCTM      = false; // sets drm CTM, restore needed

        bool                                                               m_blurFBDirty        = true;
        bool                                                               m_blurFBShouldRender = false;
        std::vector<std::pair<WP<CWLSurfaceResource>, CHLBufferReference>> m_usedAsyncBuffers;

        // For the list lookup

        bool operator==(const CMonitor& rhs) {
            return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name;
        }

        bool                                needsACopyFB();
        bool                                needsUnmodifiedCopy();
        bool                                useFP16();
        NColorManagement::PImageDescription workBufferImageDescription();
        WP<Monitor::CMonitorResources>      resources();

      private:
        void                    updateMatrix();
        Mat3x3                  m_projMatrix;
        Mat3x3                  m_projOutputMatrix;

        void                    setupDefaultWS(const Config::CMonitorRule&);
        WORKSPACEID             findAvailableDefaultWS();
        void                    setSpecialWorkspaceVisualState(bool active);
        void                    commitDPMSState(bool state);
        void                    scheduleModeRetry();
        void                    clearModeRetry();
        void                    updateVCGTRamps();
        bool                    trySetFormat(std::span<const uint32_t> formats);

        bool                    m_doneScheduled  = false;
        bool                    m_vcgtRampsSet   = false;
        int                     m_modeRetryCount = 0;
        SP<CEventLoopTimer>     m_modeRetryTimer;

        std::stack<WORKSPACEID> m_prevWorkSpaces;

        // Resources
        UP<Monitor::CMonitorResources> m_resources;
        // cached should contain one of predefined descriptions for FP16: sRGB primaries with either linear TF by default and in HDR mode or monitor's TF in SDR with render:fp16_sdr_tf = 0
        // avoids lookup for an id when ::from is used
        NColorManagement::PImageDescription m_cachedInternalDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{});

        struct {
            CHyprSignalListener frame;
            CHyprSignalListener destroy;
            CHyprSignalListener state;
            CHyprSignalListener needsFrame;
            CHyprSignalListener presented;
            CHyprSignalListener commit;
        } m_listeners;

        int   m_supportsWideColor = 0;
        int   m_supportsHDR       = 0;
        float m_minLuminance      = -1.0f;
        int   m_maxLuminance      = -1;
        int   m_maxAvgLuminance   = -1;
    };
}
