/*
    SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include "core/backendoutput.h"
#include "core/colorpipeline.h"
#include "core/drm_formats.h"
#include "core/rendertarget.h"
#include "kwin_export.h"

#include <QObject>
#include <QPointer>

#include <chrono>
#include <optional>

namespace KWin
{

class SurfaceItem;
class DrmDevice;
class GraphicsBuffer;
class OutputFrame;
class GLTexture;

struct OutputLayerBeginFrameInfo
{
    RenderTarget renderTarget;
    Region repaint;
};

enum class OutputLayerType {
    /**
     * Required for driving an output
     */
    Primary,
    /**
     * Can only be used for cursor, or cursor-attached items,
     * as the layer may be moved asynchronously by a different process
     * (like the host compositor in a nested session)
     */
    CursorOnly,
    /**
     * Should be preferred to normal overlays when possible, as they're
     * often more efficient (but often come with size restrictions)
     */
    EfficientOverlay,
    /**
     * Generic over- or underlay
     */
    GenericLayer,
};

class KWIN_EXPORT OutputLayer : public QObject
{
    Q_OBJECT

public:
    explicit OutputLayer(BackendOutput *output, OutputLayerType type);
    explicit OutputLayer(BackendOutput *output, OutputLayerType type, int zpos, int minZpos, int maxZpos);

    OutputLayerType type() const;

    void setRenderLoop(RenderLoop *loop);
    void setOutput(BackendOutput *output);

    QPointF hotspot() const;
    void setHotspot(const QPointF &hotspot);

    /**
     * For some layers it can be beneficial to use specific sizes only.
     * This returns those specific sizes, if present
     */
    virtual QList<QSize> recommendedSizes() const;

    Region deviceRepaints() const;
    void resetRepaints();
    void scheduleRepaint(Item *item);
    void addDeviceRepaint(const Region &region);
    bool needsRepaint() const;

    /**
     * Enables or disables this layer. Note that disabling the primary layer will cause problems
     */
    void setEnabled(bool enable);
    bool isEnabled() const;

    /**
     * If the output backend needs to test presentations,
     * the layer should override this function to allocate buffers for the test
     */
    virtual bool preparePresentationTest();

    std::optional<OutputLayerBeginFrameInfo> beginFrame();
    bool endFrame(const Region &renderedDeviceRegion, const Region &damagedDeviceRegion, OutputFrame *frame);

    /**
     * Tries to import the newest buffer of the surface for direct scanout and does some early checks
     * for whether or not direct scanout *could* be successful
     * A presentation request on the output must however be used afterwards to find out if it's actually successful!
     */
    virtual bool importScanoutBuffer(GraphicsBuffer *buffer, const std::shared_ptr<OutputFrame> &frame);

    void setScanoutCandidate(SurfaceItem *item);

    virtual DrmDevice *scanoutDevice() const = 0;
    virtual FormatModifierMap supportedDrmFormats() const = 0;
    virtual FormatModifierMap supportedAsyncDrmFormats() const;

    /**
     * Returns the source rect this output layer should sample from, in buffer local coordinates
     */
    RectF sourceRect() const;
    void setSourceRect(const RectF &rect);
    /**
     * Returns the target rect this output layer should be shown at, in device coordinates
     */
    Rect targetRect() const;
    void setTargetRect(const Rect &rect);
    /**
     * Returns the transform this layer will apply to content passed to it
     */
    OutputTransform offloadTransform() const;
    void setOffloadTransform(const OutputTransform &transform);
    /**
     * Returns the transform a buffer passed into this layer already has
     */
    OutputTransform bufferTransform() const;
    void setBufferTransform(const OutputTransform &transform);

    const ColorPipeline &colorPipeline() const;
    const std::shared_ptr<ColorDescription> &colorDescription() const;
    RenderingIntent renderIntent() const;
    void setColor(const std::shared_ptr<ColorDescription> &color, RenderingIntent intent, const ColorPipeline &pipeline);

    /**
     * Set the required bits for compositing on this plane. Direct scanout is not affected.
     */
    void setRequiredAlphaBits(uint32_t bits);

    void setZpos(int zpos);
    int zpos() const;
    int minZpos() const;
    int maxZpos() const;

    static QList<FormatInfo> filterAndSortFormats(const FormatModifierMap &formats, uint32_t requiredAlphaBits, BackendOutput::ColorPowerTradeoff tradeoff);

    virtual void releaseBuffers() = 0;

Q_SIGNALS:
    void repaintScheduled();

protected:
    virtual std::optional<OutputLayerBeginFrameInfo> doBeginFrame() = 0;
    virtual bool doEndFrame(const Region &renderedDeviceRegion, const Region &damagedDeviceRegion, OutputFrame *frame) = 0;

    const OutputLayerType m_type;
    Region m_repaints;
    QPointF m_hotspot;
    RectF m_sourceRect;
    Rect m_targetRect;
    qreal m_scale = 1.0;
    bool m_enabled = false;
    OutputTransform m_offloadTransform = OutputTransform::Kind::Normal;
    OutputTransform m_bufferTransform = OutputTransform::Kind::Normal;
    ColorPipeline m_colorPipeline;
    std::shared_ptr<ColorDescription> m_color = ColorDescription::sRGB;
    RenderingIntent m_renderingIntent = RenderingIntent::Perceptual;
    QPointer<SurfaceItem> m_scanoutCandidate;
    QPointer<BackendOutput> m_output;
    uint32_t m_requiredAlphaBits = 0;
    bool m_repaintScheduled = false;
    RenderLoop *m_renderLoop = nullptr;
    int m_zpos = 0;
    int m_minZpos = 0;
    int m_maxZpos = 0;
};

} // namespace KWin
