// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QPLATFORMCAMERA_H
#define QPLATFORMCAMERA_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtMultimedia/private/qerrorinfo_p.h>
#include <QtMultimedia/private/qplatformvideosource_p.h>
#include <QtMultimedia/qcamera.h>

// NOLINTBEGIN (bugprone-reserved-identifier)
typedef struct _GstElement GstElement;
// NOLINTEND (bugprone-reserved-identifier)

QT_BEGIN_NAMESPACE

class Q_MULTIMEDIA_EXPORT QPlatformCamera : public QPlatformVideoSource
{
    Q_OBJECT

public:
    // If the new camera-device is the same as the old camera-device,
    // do nothing.
    //
    // The implementation needs to handle other camera properties during a successful
    // camera-device-change. This should be done for every property, i.e focusMode,
    // flashMode, focusDistance, zoom etc.
    //
    // The properties should be handled in the following way:
    //  - If property is supported on new device, apply the property to the device immediately.
    //  - If property is supported on new device, but range of valid values has changed,
    //    clamp the property and apply it to the camera-device.
    //  - If property is NOT supported on new device, reset the value to default and do
    //    nothing to the camera-device.
    //
    // TODO: There is currently no rules on the order of how each signal should be triggered.
    // In the future we might want to add a rule that requires the implementation
    // to update all properties at once, then trigger all relevant signals.
    //
    // TODO: There are currently no rules in the public API on how we should handle
    // devices being disconnected or the user passing in an invalid device-id.
    virtual void setCamera(const QCameraDevice &camera) = 0;
    virtual bool setCameraFormat(const QCameraFormat &/*format*/) { return false; }
    QCameraFormat cameraFormat() const { return m_cameraFormat; }

    // FocusModeManual should only be reported as supported if the camera
    // backend is also able to apply the focusDistance setting.
    // This effectively means the backend should also report Feature::FocusDistance
    // as supported if this is the case.
    virtual bool isFocusModeSupported(QCamera::FocusMode mode) const { return mode == QCamera::FocusModeAuto; }

    // If the focusMode is the same as the current, ignore the function call.
    //
    // If the new focusMode is reported as unsupported, send a warning
    // and do nothing.
    //
    // FocusModeAuto should map to continuous autofocus mode in the backend.
    // FocusModeManual should be treated as fixed lens position
    // in the backend.
    //
    // If the new mode is FocusModeManual, apply the focusDistance setting.
    //
    // This function should call QPlatformCamera::focusModeChanged if
    // successful.
    virtual void setFocusMode(QCamera::FocusMode /*mode*/) {}

    virtual void setCustomFocusPoint(const QPointF &/*point*/) {}

    // If the new distance is the same as previous, ignore the function call.
    //
    // If supportedFeatures does not include the FocusDistance flag,
    // send a warning and do nothing.
    //
    // If the incoming value is out of bounds (outside [0,1]),
    // send a warning and do nothing.
    //
    // If focusMode is set to Manual, apply this focusDistance to the camera.
    // If not, accept the value but don't apply it to the camera.
    //
    // The value 0 maps to the distance closest to the camera.
    // The value 1 maps to the distance furthest away from the camera.
    //
    // This function should call QPlatformCamera::focusDistanceChanged()
    // if successful.
    virtual void setFocusDistance(float) {}

    // smaller 0: zoom instantly, rate in power-of-two/sec
    virtual void zoomTo(float /*newZoomFactor*/, float /*rate*/ = -1.) {}

    virtual void setFlashMode(QCamera::FlashMode /*mode*/) {}
    virtual bool isFlashModeSupported(QCamera::FlashMode mode) const { return mode == QCamera::FlashOff; }
    virtual bool isFlashReady() const { return false; }

    virtual void setTorchMode(QCamera::TorchMode /*mode*/) {}
    virtual bool isTorchModeSupported(QCamera::TorchMode mode) const { return mode == QCamera::TorchOff; }

    virtual void setExposureMode(QCamera::ExposureMode) {}
    virtual bool isExposureModeSupported(QCamera::ExposureMode mode) const { return mode == QCamera::ExposureAuto; }
    virtual void setExposureCompensation(float) {}
    virtual int isoSensitivity() const { return 100; }
    virtual void setManualIsoSensitivity(int) {}
    virtual void setManualExposureTime(float) {}
    virtual float exposureTime() const { return -1.; }

    virtual bool isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const { return mode == QCamera::WhiteBalanceAuto; }
    virtual void setWhiteBalanceMode(QCamera::WhiteBalanceMode /*mode*/) {}
    virtual void setColorTemperature(int /*temperature*/) {}

#if QT_CONFIG(gstreamer_qt_api)
    virtual GstElement *rawGstElement() const { return nullptr; }
#endif

    QVideoFrameFormat frameFormat() const override;

    // Note: Because FocusModeManual effectively cannot function without
    // being able to set FocusDistance, this feature flag is redundant.
    // Should be considered for deprecation in the future.
    QCamera::Features supportedFeatures() const { return m_supportedFeatures; }

    QCamera::FocusMode focusMode() const { return m_focusMode; }
    QPointF focusPoint() const { return m_customFocusPoint; }

    float minZoomFactor() const { return m_minZoom; }
    float maxZoomFactor() const { return m_maxZoom; }
    float zoomFactor() const { return m_zoomFactor; }
    QPointF customFocusPoint() const { return m_customFocusPoint; }
    float focusDistance() const { return m_focusDistance; }

    QCamera::FlashMode flashMode() const { return m_flashMode; }
    QCamera::TorchMode torchMode() const { return m_torchMode; }

    QCamera::ExposureMode exposureMode() const { return m_exposureMode; }
    float exposureCompensation() const { return m_exposureCompensation; }
    int manualIsoSensitivity() const { return m_iso; }
    int minIso() const { return m_minIso; }
    int maxIso() const { return m_maxIso; }
    float manualExposureTime() const { return m_exposureTime; }
    float minExposureTime() const { return m_minExposureTime; }
    float maxExposureTime() const { return m_maxExposureTime; }
    QCamera::WhiteBalanceMode whiteBalanceMode() const { return m_whiteBalance; }
    int colorTemperature() const { return m_colorTemperature; }

    void supportedFeaturesChanged(QCamera::Features);
    void minimumZoomFactorChanged(float factor);
    void maximumZoomFactorChanged(float);
    void focusModeChanged(QCamera::FocusMode mode);
    void customFocusPointChanged(const QPointF &point);
    void focusDistanceChanged(float d);
    void zoomFactorChanged(float zoom);
    void flashReadyChanged(bool);
    void flashModeChanged(QCamera::FlashMode mode);
    void torchModeChanged(QCamera::TorchMode mode);
    void exposureModeChanged(QCamera::ExposureMode mode);
    void exposureCompensationChanged(float compensation);
    void exposureCompensationRangeChanged(float min, float max);
    void isoSensitivityChanged(int iso);
    void minIsoChanged(int iso) { m_minIso = iso; }
    void maxIsoChanged(int iso) { m_maxIso = iso; }
    void exposureTimeChanged(float speed);
    void minExposureTimeChanged(float secs) { m_minExposureTime = secs; }
    void maxExposureTimeChanged(float secs) { m_maxExposureTime = secs; }
    void whiteBalanceModeChanged(QCamera::WhiteBalanceMode mode);
    void colorTemperatureChanged(int temperature);

    static int colorTemperatureForWhiteBalance(QCamera::WhiteBalanceMode mode);

    QCamera::Error error() const { return m_error.code(); }
    QString errorString() const final { return m_error.description(); }

    void updateError(QCamera::Error error, const QString &errorString);

Q_SIGNALS:
    void errorOccurred(QCamera::Error error, const QString &errorString);

protected:
    explicit QPlatformCamera(QObject *parent);

    virtual int cameraPixelFormatScore(QVideoFrameFormat::PixelFormat /*format*/,
                                       QVideoFrameFormat::ColorRange /*colorRange*/) const
    {
        return 0;
    }

    QCameraFormat findBestCameraFormat(const QCameraDevice &camera) const;
    QCameraFormat m_cameraFormat;
    QVideoFrameFormat::PixelFormat m_framePixelFormat = QVideoFrameFormat::Format_Invalid;

    // Helper functions to allow backends to reset properties to default values.
    // i.e using focusModeChanged(defaultFocusMode());
    static constexpr int defaultColorTemperature() { return 0; }
    static constexpr QPointF defaultCustomFocusPoint() { return { -1, -1 }; }
    static constexpr float defaultExposureCompensation() { return 0.f; }
    static constexpr QCamera::ExposureMode defaultExposureMode() { return QCamera::ExposureAuto; }
    static constexpr float defaultExposureTime() { return -1.f; }
    static constexpr QCamera::FlashMode defaultFlashMode() { return QCamera::FlashOff; }
    static constexpr bool defaultFlashReady() { return false; }
    static constexpr float defaultFocusDistance() { return 1.f; }
    static constexpr QCamera::FocusMode defaultFocusMode() { return QCamera::FocusModeAuto; }
    static constexpr int defaultIso() { return -1; }
    static constexpr QCamera::TorchMode defaultTorchMode() { return QCamera::TorchOff; }
    static constexpr QCamera::WhiteBalanceMode defaultWhiteBalanceMode() { return QCamera::WhiteBalanceAuto; }
    static constexpr float defaultZoomFactor() { return 1.f; }

private:
    QCamera *m_camera = nullptr;
    QCamera::Features m_supportedFeatures = {};
    QCamera::FocusMode m_focusMode = defaultFocusMode();
    float m_minZoom = 1.;
    float m_maxZoom = 1.;
    float m_zoomFactor = defaultZoomFactor();
    float m_focusDistance = defaultFocusDistance();
    QPointF m_customFocusPoint = defaultCustomFocusPoint();
    bool m_flashReady = defaultFlashReady();
    QCamera::FlashMode m_flashMode = defaultFlashMode();
    QCamera::TorchMode m_torchMode = defaultTorchMode();
    QCamera::ExposureMode m_exposureMode = defaultExposureMode();
    float m_exposureCompensation = defaultExposureCompensation();
    float m_minExposureCompensation = 0.;
    float m_maxExposureCompensation = 0.;
    int m_iso = defaultIso();
    int m_minIso = -1;
    int m_maxIso = -1;
    float m_exposureTime = defaultExposureTime();
    float m_minExposureTime = -1.;
    float m_maxExposureTime = -1.;
    QCamera::WhiteBalanceMode m_whiteBalance = defaultWhiteBalanceMode();
    int m_colorTemperature = defaultColorTemperature();
    QErrorInfo<QCamera::Error> m_error;
};

QT_END_NAMESPACE


#endif  // QPLATFORMCAMERA_H

