// Copyright (C) 2016 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

#include "qandroidcamera_p.h"
#include "qandroidcamerasession_p.h"
#include "qandroidcapturesession_p.h"
#include "qandroidmediacapturesession_p.h"
#include <qmediadevices.h>
#include <qcameradevice.h>
#include <qtimer.h>
#include "qandroidmultimediautils_p.h"

QT_BEGIN_NAMESPACE

QAndroidCamera::QAndroidCamera(QCamera *camera)
    : QPlatformCamera(camera)
{
    Q_ASSERT(camera);
}

QAndroidCamera::~QAndroidCamera()
{
}

void QAndroidCamera::setActive(bool active)
{
    if (m_cameraSession) {
        m_cameraSession->setActive(active);
    } else {
        isPendingSetActive = active;
    }
}

bool QAndroidCamera::isActive() const
{
    return m_cameraSession ? m_cameraSession->isActive() : false;
}

void QAndroidCamera::setCamera(const QCameraDevice &camera)
{
    m_cameraDev = camera;

    if (m_cameraSession) {
        int id = 0;
        auto cameras = QMediaDevices::videoInputs();
        for (int i = 0; i < cameras.size(); ++i) {
            if (cameras.at(i) == camera) {
                id = i;
                break;
            }
        }
        if (id != m_cameraSession->getSelectedCameraId()) {
            m_cameraSession->setSelectedCameraId(id);
            reactivateCameraSession();
        }
    }
}

void QAndroidCamera::reactivateCameraSession()
{
    if (m_cameraSession->isActive()) {
        if (m_service->captureSession() &&
                m_service->captureSession()->state() == QMediaRecorder::RecordingState) {
            m_service->captureSession()->stop();
            qWarning() << "Changing camera during recording not supported";
        }
        m_cameraSession->setActive(false);
        m_cameraSession->setActive(true);
    }
}

bool QAndroidCamera::setCameraFormat(const QCameraFormat &format)
{
    m_cameraFormat = format;

    if (m_cameraSession)
        m_cameraSession->setCameraFormat(m_cameraFormat);

    return true;
}

void QAndroidCamera::setCaptureSession(QPlatformMediaCaptureSession *session)
{
    QAndroidMediaCaptureSession *captureSession = static_cast<QAndroidMediaCaptureSession *>(session);
    if (m_service == captureSession)
        return;

    m_service = captureSession;
    if (!m_service) {
        disconnect(m_cameraSession,nullptr,this,nullptr);
        m_cameraSession = nullptr;
        return;
    }

    m_cameraSession = m_service->cameraSession();
    Q_ASSERT(m_cameraSession);
    if (!m_cameraFormat.isNull())
        m_cameraSession->setCameraFormat(m_cameraFormat);

    setCamera(m_cameraDev);

    connect(m_cameraSession, &QAndroidCameraSession::activeChanged, this, &QAndroidCamera::activeChanged);
    connect(m_cameraSession, &QAndroidCameraSession::error, this, &QAndroidCamera::error);
    connect(m_cameraSession, &QAndroidCameraSession::opened, this, &QAndroidCamera::onCameraOpened);

    if (isPendingSetActive) {
        setActive(true);
        isPendingSetActive = false;
    }
}

void QAndroidCamera::setFocusMode(QCamera::FocusMode mode)
{
    if (!m_cameraSession || !m_cameraSession->camera())
        return;

    if (isFocusModeSupported(mode)) {
        QString focusMode;

        switch (mode) {
        case QCamera::FocusModeHyperfocal:
            focusMode = QLatin1String("edof");
            break;
        case QCamera::FocusModeInfinity: // not 100%, but close
            focusMode = QLatin1String("infinity");
            break;
        case QCamera::FocusModeManual:
            focusMode = QLatin1String("fixed");
            break;
        case QCamera::FocusModeAutoNear:
            focusMode = QLatin1String("macro");
            break;
        case QCamera::FocusModeAuto:
        case QCamera::FocusModeAutoFar:
            if (1) { // ###?
                focusMode = QLatin1String("continuous-video");
            } else {
                focusMode = QLatin1String("continuous-picture");
            }
            break;
        }

        m_cameraSession->camera()->setFocusMode(focusMode);

        // reset focus position
        m_cameraSession->camera()->cancelAutoFocus();

        focusModeChanged(mode);
    }
}

bool QAndroidCamera::isFocusModeSupported(QCamera::FocusMode mode) const
{
    return (m_cameraSession && m_cameraSession->camera()) ? m_supportedFocusModes.contains(mode) : false;
}

void QAndroidCamera::onCameraOpened()
{
    Q_ASSERT(m_cameraSession);
    connect(m_cameraSession->camera(), &AndroidCamera::previewSizeChanged, this, &QAndroidCamera::setCameraFocusArea);

    m_supportedFocusModes.clear();
    m_continuousPictureFocusSupported = false;
    m_continuousVideoFocusSupported = false;
    m_focusPointSupported = false;

    QStringList focusModes = m_cameraSession->camera()->getSupportedFocusModes();
    for (int i = 0; i < focusModes.size(); ++i) {
        const QString &focusMode = focusModes.at(i);
        if (focusMode == QLatin1String("continuous-picture")) {
            m_supportedFocusModes << QCamera::FocusModeAuto;
            m_continuousPictureFocusSupported = true;
        } else if (focusMode == QLatin1String("continuous-video")) {
            m_supportedFocusModes << QCamera::FocusModeAuto;
            m_continuousVideoFocusSupported = true;
        } else if (focusMode == QLatin1String("edof")) {
            m_supportedFocusModes << QCamera::FocusModeHyperfocal;
        } else if (focusMode == QLatin1String("fixed")) {
            m_supportedFocusModes << QCamera::FocusModeManual;
        } else if (focusMode == QLatin1String("infinity")) {
            m_supportedFocusModes << QCamera::FocusModeInfinity;
        } else if (focusMode == QLatin1String("macro")) {
            m_supportedFocusModes << QCamera::FocusModeAutoNear;
        }
    }

    if (m_cameraSession->camera()->getMaxNumFocusAreas() > 0)
        m_focusPointSupported = true;

    auto m = focusMode();
    if (!m_supportedFocusModes.contains(m))
        m = QCamera::FocusModeAuto;

    setFocusMode(m);
    setCustomFocusPoint(focusPoint());

    if (m_cameraSession->camera()->isZoomSupported()) {
        m_zoomRatios = m_cameraSession->camera()->getZoomRatios();
        qreal maxZoom = m_zoomRatios.last() / qreal(100);
        if (m_maximumZoom != maxZoom) {
            m_maximumZoom = maxZoom;
        }
        zoomTo(1, -1);
    } else {
        m_zoomRatios.clear();
        m_maximumZoom = 1.0;
    }

    m_minExposureCompensationIndex = m_cameraSession->camera()->getMinExposureCompensation();
    m_maxExposureCompensationIndex = m_cameraSession->camera()->getMaxExposureCompensation();
    m_exposureCompensationStep = m_cameraSession->camera()->getExposureCompensationStep();
    exposureCompensationRangeChanged(m_minExposureCompensationIndex*m_exposureCompensationStep,
                                     m_maxExposureCompensationIndex*m_exposureCompensationStep);

    m_supportedExposureModes.clear();
    QStringList sceneModes = m_cameraSession->camera()->getSupportedSceneModes();
    if (!sceneModes.isEmpty()) {
        for (int i = 0; i < sceneModes.size(); ++i) {
            const QString &sceneMode = sceneModes.at(i);
            if (sceneMode == QLatin1String("auto"))
                m_supportedExposureModes << QCamera::ExposureAuto;
            else if (sceneMode == QLatin1String("beach"))
                m_supportedExposureModes << QCamera::ExposureBeach;
            else if (sceneMode == QLatin1String("night"))
                m_supportedExposureModes << QCamera::ExposureNight;
            else if (sceneMode == QLatin1String("portrait"))
                m_supportedExposureModes << QCamera::ExposurePortrait;
            else if (sceneMode == QLatin1String("snow"))
                m_supportedExposureModes << QCamera::ExposureSnow;
            else if (sceneMode == QLatin1String("sports"))
                m_supportedExposureModes << QCamera::ExposureSports;
            else if (sceneMode == QLatin1String("action"))
                m_supportedExposureModes << QCamera::ExposureAction;
            else if (sceneMode == QLatin1String("landscape"))
                m_supportedExposureModes << QCamera::ExposureLandscape;
            else if (sceneMode == QLatin1String("night-portrait"))
                m_supportedExposureModes << QCamera::ExposureNightPortrait;
            else if (sceneMode == QLatin1String("theatre"))
                m_supportedExposureModes << QCamera::ExposureTheatre;
            else if (sceneMode == QLatin1String("sunset"))
                m_supportedExposureModes << QCamera::ExposureSunset;
            else if (sceneMode == QLatin1String("steadyphoto"))
                m_supportedExposureModes << QCamera::ExposureSteadyPhoto;
            else if (sceneMode == QLatin1String("fireworks"))
                m_supportedExposureModes << QCamera::ExposureFireworks;
            else if (sceneMode == QLatin1String("party"))
                m_supportedExposureModes << QCamera::ExposureParty;
            else if (sceneMode == QLatin1String("candlelight"))
                m_supportedExposureModes << QCamera::ExposureCandlelight;
            else if (sceneMode == QLatin1String("barcode"))
                m_supportedExposureModes << QCamera::ExposureBarcode;
        }
    }

    setExposureCompensation(exposureCompensation());
    setExposureMode(exposureMode());

    isFlashSupported = false;
    isFlashAutoSupported = false;
    isTorchSupported = false;

    QStringList flashModes = m_cameraSession->camera()->getSupportedFlashModes();
    for (int i = 0; i < flashModes.size(); ++i) {
        const QString &flashMode = flashModes.at(i);
        if (flashMode == QLatin1String("auto"))
            isFlashAutoSupported = true;
        else if (flashMode == QLatin1String("on"))
            isFlashSupported = true;
        else if (flashMode == QLatin1String("torch"))
            isTorchSupported = true;
    }

    setFlashMode(flashMode());

    m_supportedWhiteBalanceModes.clear();
    QStringList whiteBalanceModes = m_cameraSession->camera()->getSupportedWhiteBalance();
    for (int i = 0; i < whiteBalanceModes.size(); ++i) {
        const QString &wb = whiteBalanceModes.at(i);
        if (wb == QLatin1String("auto")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceAuto,
                                                QStringLiteral("auto"));
        } else if (wb == QLatin1String("cloudy-daylight")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceCloudy,
                                                QStringLiteral("cloudy-daylight"));
        } else if (wb == QLatin1String("daylight")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceSunlight,
                                                QStringLiteral("daylight"));
        } else if (wb == QLatin1String("fluorescent")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceFluorescent,
                                                QStringLiteral("fluorescent"));
        } else if (wb == QLatin1String("incandescent")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceTungsten,
                                                QStringLiteral("incandescent"));
        } else if (wb == QLatin1String("shade")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceShade,
                                                QStringLiteral("shade"));
        } else if (wb == QLatin1String("twilight")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceSunset,
                                                QStringLiteral("twilight"));
        } else if (wb == QLatin1String("warm-fluorescent")) {
            m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceFlash,
                                                QStringLiteral("warm-fluorescent"));
        }
    }

}

//void QAndroidCameraFocusControl::onCameraCaptureModeChanged()
//{
//    if (m_cameraSession->camera() && m_focusMode == QCamera::FocusModeAudio) {
//        QString focusMode;
//        if ((m_cameraSession->captureMode().testFlag(QCamera::CaptureVideo) && m_continuousVideoFocusSupported)
//                || !m_continuousPictureFocusSupported) {
//            focusMode = QLatin1String("continuous-video");
//        } else {
//            focusMode = QLatin1String("continuous-picture");
//        }
//        m_cameraSession->camera()->setFocusMode(focusMode);
//        m_cameraSession->camera()->cancelAutoFocus();
//    }
//}

static QRect adjustedArea(const QRectF &area)
{
    // Qt maps focus points in the range (0.0, 0.0) -> (1.0, 1.0)
    // Android maps focus points in the range (-1000, -1000) -> (1000, 1000)
    // Converts an area in Qt coordinates to Android coordinates
    return QRect(-1000 + qRound(area.x() * 2000),
                 -1000 + qRound(area.y() * 2000),
                 qRound(area.width() * 2000),
                 qRound(area.height() * 2000))
        .intersected(QRect(-1000, -1000, 2000, 2000));
}

void QAndroidCamera::setCameraFocusArea()
{
    if (!m_cameraSession)
        return;

    QList<QRect> areas;
    auto focusPoint = customFocusPoint();
    if (QRectF(0., 0., 1., 1.).contains(focusPoint)) {
        // in FocusPointAuto mode, leave the area list empty
        // to let the driver choose the focus point.
        QSize viewportSize = m_cameraSession->camera()->previewSize();

        if (!viewportSize.isValid())
            return;

        // Set up a 50x50 pixel focus area around the focal point
        QSizeF focusSize(50.f / viewportSize.width(), 50.f / viewportSize.height());
        float x = qBound(qreal(0),
                         focusPoint.x() - (focusSize.width() / 2),
                         1.f - focusSize.width());
        float y = qBound(qreal(0),
                         focusPoint.y() - (focusSize.height() / 2),
                         1.f - focusSize.height());

        QRectF area(QPointF(x, y), focusSize);

        areas.append(adjustedArea(area));
    }
    m_cameraSession->camera()->setFocusAreas(areas);
}

void QAndroidCamera::zoomTo(float factor, float rate)
{
    Q_UNUSED(rate);

    if (zoomFactor() == factor)
        return;

    if (!m_cameraSession || !m_cameraSession->camera())
        return;

    factor = qBound(qreal(1), factor, maxZoomFactor());
    int validZoomIndex = qt_findClosestValue(m_zoomRatios, qRound(factor * 100));
    float newZoom = m_zoomRatios.at(validZoomIndex) / qreal(100);
    m_cameraSession->camera()->setZoom(validZoomIndex);
    zoomFactorChanged(newZoom);
}

void QAndroidCamera::setFlashMode(QCamera::FlashMode mode)
{
    if (!m_cameraSession || !m_cameraSession->camera())
        return;

    if (!isFlashModeSupported(mode))
        return;

    QString flashMode;
    if (mode == QCamera::FlashAuto)
        flashMode = QLatin1String("auto");
    else if (mode == QCamera::FlashOn)
        flashMode = QLatin1String("on");
    else // FlashOff
        flashMode = QLatin1String("off");

    m_cameraSession->camera()->setFlashMode(flashMode);
    flashModeChanged(mode);
}

bool QAndroidCamera::isFlashModeSupported(QCamera::FlashMode mode) const
{
    if (!m_cameraSession || !m_cameraSession->camera())
        return false;
    switch (mode) {
    case QCamera::FlashOff:
        return true;
    case QCamera::FlashOn:
        return isFlashSupported;
    case QCamera::FlashAuto:
        return isFlashAutoSupported;
    }
}

bool QAndroidCamera::isFlashReady() const
{
    // Android doesn't have an API for that
    return true;
}

void QAndroidCamera::setTorchMode(QCamera::TorchMode mode)
{
    if (!m_cameraSession)
        return;
    auto *camera = m_cameraSession->camera();
    if (!camera || !isTorchSupported || mode == QCamera::TorchAuto)
        return;

    if (mode == QCamera::TorchOn) {
        camera->setFlashMode(QLatin1String("torch"));
    } else if (mode == QCamera::TorchOff) {
        // if torch was enabled, it first needs to be turned off before restoring the flash mode
        camera->setFlashMode(QLatin1String("off"));
        setFlashMode(flashMode());
    }
    torchModeChanged(mode);
}

bool QAndroidCamera::isTorchModeSupported(QCamera::TorchMode mode) const
{
    if (!m_cameraSession || !m_cameraSession->camera())
        return false;
    switch (mode) {
    case QCamera::TorchOff:
        return true;
    case QCamera::TorchOn:
        return isTorchSupported;
    case QCamera::TorchAuto:
        return false;
    }
}

void QAndroidCamera::setExposureMode(QCamera::ExposureMode mode)
{
    if (exposureMode() == mode)
        return;

    if (!m_cameraSession || !m_cameraSession->camera())
        return;

    if (!m_supportedExposureModes.contains(mode))
        return;

    QString sceneMode;
    switch (mode) {
    case QCamera::ExposureAuto:
        sceneMode = QLatin1String("auto");
        break;
    case QCamera::ExposureSports:
        sceneMode = QLatin1String("sports");
        break;
    case QCamera::ExposurePortrait:
        sceneMode = QLatin1String("portrait");
        break;
    case QCamera::ExposureBeach:
        sceneMode = QLatin1String("beach");
        break;
    case QCamera::ExposureSnow:
        sceneMode = QLatin1String("snow");
        break;
    case QCamera::ExposureNight:
        sceneMode = QLatin1String("night");
        break;
    case QCamera::ExposureAction:
        sceneMode = QLatin1String("action");
        break;
    case QCamera::ExposureLandscape:
        sceneMode = QLatin1String("landscape");
        break;
    case QCamera::ExposureNightPortrait:
        sceneMode = QLatin1String("night-portrait");
        break;
    case QCamera::ExposureTheatre:
        sceneMode = QLatin1String("theatre");
        break;
    case QCamera::ExposureSunset:
        sceneMode = QLatin1String("sunset");
        break;
    case QCamera::ExposureSteadyPhoto:
        sceneMode = QLatin1String("steadyphoto");
        break;
    case QCamera::ExposureFireworks:
        sceneMode = QLatin1String("fireworks");
        break;
    case QCamera::ExposureParty:
        sceneMode = QLatin1String("party");
        break;
    case QCamera::ExposureCandlelight:
        sceneMode = QLatin1String("candlelight");
        break;
    case QCamera::ExposureBarcode:
        sceneMode = QLatin1String("barcode");
        break;
    default:
        sceneMode = QLatin1String("auto");
        mode = QCamera::ExposureAuto;
        break;
    }

    m_cameraSession->camera()->setSceneMode(sceneMode);
    exposureModeChanged(mode);
}

bool QAndroidCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
{
    return m_supportedExposureModes.contains(mode);
}

void QAndroidCamera::setExposureCompensation(float bias)
{
    if (exposureCompensation() == bias || !m_cameraSession || !m_cameraSession->camera())
        return;

    int biasIndex = qRound(bias / m_exposureCompensationStep);
    biasIndex = qBound(m_minExposureCompensationIndex, biasIndex, m_maxExposureCompensationIndex);
    float comp = biasIndex * m_exposureCompensationStep;
    m_cameraSession->camera()->setExposureCompensation(biasIndex);
    exposureCompensationChanged(comp);
}

bool QAndroidCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
{
    return m_supportedWhiteBalanceModes.contains(mode);
}

void QAndroidCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
{
    if (!m_cameraSession)
        return;
    auto *camera = m_cameraSession->camera();
    if (!camera)
        return;
    QString wb = m_supportedWhiteBalanceModes.value(mode, QString());
    if (!wb.isEmpty()) {
        camera->setWhiteBalance(wb);
        whiteBalanceModeChanged(mode);
    }
}

QT_END_NAMESPACE

#include "moc_qandroidcamera_p.cpp"
