////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2026 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

#pragma once


#include <ovito/core/Core.h>
#include <ovito/core/rendering/FrameGraph.h>

namespace Ovito {

/**
 * Stores information about the image in a FrameBuffer.
 */
class OVITO_CORE_EXPORT ImageInfo
{
public:

    /// Default constructor.
    ImageInfo() = default;

    /// Comparison operator.
    bool operator==(const ImageInfo& other) const {
        if(this->_imageWidth != other._imageWidth) return false;
        if(this->_imageHeight != other._imageHeight) return false;
        if(this->_filename != other._filename) return false;
        if(this->_format != other._format) return false;
        return true;
    }

    /// Returns the width of the image in pixels.
    [[nodiscard]] int imageWidth() const { return _imageWidth; }

    /// Sets the width of the image in pixels.
    void setImageWidth(int width) { OVITO_ASSERT(width >= 0); _imageWidth = width; }

    /// Returns the height of the image in pixels.
    [[nodiscard]] int imageHeight() const { return _imageHeight; }

    /// Sets the height of the image to be rendered in pixels.
    void setImageHeight(int height) { OVITO_ASSERT(height >= 0); _imageHeight = height; }

    /// Returns the filename of the image on disk.
    [[nodiscard]] const QString& filename() const { return _filename; }

    /// Sets the filename of the image on disk.
    void setFilename(const QString& filename) {
        _filename = filename;
        guessFormatFromFilename();
    }

    /// Returns the format of the image on disk.
    [[nodiscard]] const QByteArray& format() const { return _format; }

    /// Sets the format of the image on disk.
    void setFormat(const QByteArray& format) { _format = format; }

    /// Detects the file format based on the filename suffix.
    bool guessFormatFromFilename();

    /// Returns whether the selected file format is a video format.
    [[nodiscard]] bool isMovie() const;

private:

    /// The width of the image in pixels.
    int _imageWidth = 0;

    /// The height of the image in pixels.
    int _imageHeight = 0;

    /// The filename of the image on disk.
    QString _filename;

    /// The format of the image on disk.
    QByteArray _format;

    friend OVITO_CORE_EXPORT SaveStream& operator<<(SaveStream& stream, const ImageInfo& i);
    friend OVITO_CORE_EXPORT LoadStream& operator>>(LoadStream& stream, ImageInfo& i);
};

/// Writes an ImageInfo to an output stream.
/// \relates ImageInfo
OVITO_CORE_EXPORT SaveStream& operator<<(SaveStream& stream, const ImageInfo& i);

/// Reads an ImageInfo from an input stream.
/// \relates ImageInfo
OVITO_CORE_EXPORT LoadStream& operator>>(LoadStream& stream, ImageInfo& i);

/**
 * A buffer for storing rendered output images, which is backed by a QImage object.
 * The RenderingJob::renderFrame() method renders the scene into this framebuffer.
 *
 * The contents of the framebuffer can be modified by direct pixel access to the internal QImage
 * returned by the image() method. Alternatively, 2D graphics primitives can be rendered into
 * the framebuffer using the renderPrimitives() method.
 *
 * After modifying the contents of the framebuffer, the update() method must be called
 * to notify any listeners about the changed region of the framebuffer.
 *
 * During rendering, the contents of the framebuffer can be displayed by a FrameBufferWidget
 * or FrameBufferWindow from the GUI module.
 *
 * After rendering, the contents of the framebuffer can be saved to an image file using
 * the Qt QImage::save() method, or passed to the VideoEncoder::writeFrame() method for
 * inclusion in a video file.
 */
class OVITO_CORE_EXPORT FrameBuffer : public QObject
{
    Q_OBJECT

public:

    /// Constructor.
    explicit FrameBuffer(QObject* parent = nullptr) : QObject(parent) {}

    /// Constructor.
    explicit FrameBuffer(int width, int height, QObject* parent = nullptr);

    /// Constructor.
    explicit FrameBuffer(const QSize& size, QObject* parent = nullptr) : FrameBuffer(size.width(), size.height(), parent) {}

    /// Returns the internal QImage that is used to store the pixel data.
    QImage& image() {
        commitChanges();
        return _image;
    }

    /// Returns the internal QImage that is used to store the pixel data.
    const QImage& image() const {
        const_cast<FrameBuffer*>(this)->commitChanges();
        return _image;
    }

    /// Returns the internal QImage to be displayed in the frame buffer window.
    const QImage& displayImage() const {
        return _image;
    }

    /// Returns the width of the image.
    int width() const { return _image.width(); }

    /// Returns the height of the image.
    int height() const { return _image.height(); }

    /// Returns the size of the image.
    QSize size() const { return _image.size(); }

    /// Sets the size of the frame buffer image.
    void setSize(const QSize& newSize) {
        commitChanges();
        if(newSize == size())
            return;
        _info.setImageWidth(newSize.width());
        _info.setImageHeight(newSize.height());
        _image = _image.copy(0, 0, newSize.width(), newSize.height());
        _viewportRect = QRect(QPoint(0,0), newSize);
        Q_EMIT bufferResized(newSize);
    }

    /// Returns the descriptor of the image.
    const ImageInfo& info() const { return _info; }

	/// Returns the target area currently being rendered into.
	const QRect& viewportRect() const { return _viewportRect; }

    /// Sets the target area currently being rendered into.
    void setViewportRect(const QRect& rect) { _viewportRect = rect; }

    /// Clears the framebuffer with a uniform color.
    void clear(const ColorA& color = ColorA(0,0,0,0), bool delayed = false);

    /// This method must be called each time the contents of the frame buffer have been modified.
    /// Fires the contentChanged() signal.
    void update(const QRect& changedRegion) {
        commitChanges();
        Q_EMIT contentChanged(changedRegion | _delayedUpdateRect);
        _delayedUpdateRect = {};
    }

    /// Removes unnecessary pixels along the outer edges of the image.
    bool autoCrop();

	/// Renders 2d graphics primitives specified in a frame graph into the frame buffer.
	void renderPrimitives(FrameGraph::RenderLayerType layerType, const FrameGraph& frameGraph);

    /// Renders an image primitive directly into the framebuffer.
    void renderImagePrimitive(const ImagePrimitive& primitive, bool update = true);

    /// Renders a text primitive directly into the framebuffer.
    void renderTextPrimitive(const TextPrimitive& primitive, bool update = true);

    /// Renders a line primitive directly into the framebuffer (without depth-testing).
    void renderLinePrimitive(const LinePrimitive& primitive, const AffineTransformation& worldTransform, const ViewProjectionParameters& projectionParams, bool update = true);

    /// Applies a delayed clear buffer operation.
    void commitChanges();

    /// Discards a delayed clear buffer operation.
    void discardChanges() {
        _delayedClearRect = {};
    }

Q_SIGNALS:

    /// This signal is emitted by the framebuffer when a part of its content has changed.
    void contentChanged(const QRect& changedRegion);

    /// This signal is emitted by the framebuffer when the backing store has changed its size.
    void bufferResized(QSize size);

private:

    /// The internal pixel data storage.
    QImage _image;

    /// The descriptor of the image.
    ImageInfo _info;

    /// The target area currently being rendered into.
	QRect _viewportRect;

    /// Saved rect to be cleared at some later time.
    QRect _delayedClearRect;

    /// Uniform color for delayed buffer clearing.
    ColorA _delayedClearColor;

    /// Saved rect to be updated at some later time.
    QRect _delayedUpdateRect;
};

}   // End of namespace
