/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "avfvideoencodersettingscontrol.h"

#include "avfcameraservice.h"
#include "avfcamerautility.h"
#include "avfcamerasession.h"
#include "avfcamerarenderercontrol.h"

#include <AVFoundation/AVFoundation.h>

QT_BEGIN_NAMESPACE

Q_GLOBAL_STATIC_WITH_ARGS(QStringList, supportedCodecs, (QStringList() << QLatin1String("avc1")
                                                                       << QLatin1String("jpeg")
                                                         #ifdef Q_OS_OSX
                                                                       << QLatin1String("ap4h")
                                                                       << QLatin1String("apcn")
                                                         #endif
                                                         ))

static bool format_supports_framerate(AVCaptureDeviceFormat *format, qreal fps)
{
    if (format && fps > qreal(0)) {
        const qreal epsilon = 0.1;
        for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
            if (range.maxFrameRate - range.minFrameRate < epsilon) {
                if (qAbs(fps - range.maxFrameRate) < epsilon)
                    return true;
            }

            if (fps >= range.minFrameRate && fps <= range.maxFrameRate)
                return true;
        }
    }

    return false;
}

static bool real_list_contains(const QList<qreal> &list, qreal value)
{
    for (qreal r : list) {
        if (qFuzzyCompare(r, value))
            return true;
    }
    return false;
}

AVFVideoEncoderSettingsControl::AVFVideoEncoderSettingsControl(AVFCameraService *service)
    : QVideoEncoderSettingsControl()
    , m_service(service)
    , m_restoreFormat(nil)
{
}

QList<QSize> AVFVideoEncoderSettingsControl::supportedResolutions(const QVideoEncoderSettings &settings,
                                                                  bool *continuous) const
{
    Q_UNUSED(settings)

    if (continuous)
        *continuous = true;

    // AVFoundation seems to support any resolution for recording, with the following limitations:
    // - The recording resolution can't be higher than the camera's active resolution
    // - On OS X, the recording resolution is automatically adjusted to have the same aspect ratio as
    //   the camera's active resolution
    QList<QSize> resolutions;
    resolutions.append(QSize(32, 32));

    AVCaptureDevice *device = m_service->session()->videoCaptureDevice();
    if (device) {
        int maximumWidth = 0;
        const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(device,
                                                                                m_service->session()->defaultCodec()));
        for (int i = 0; i < formats.size(); ++i) {
            const QSize res(qt_device_format_resolution(formats[i]));
            if (res.width() > maximumWidth)
                maximumWidth = res.width();
        }

        if (maximumWidth > 0)
            resolutions.append(QSize(maximumWidth, maximumWidth));
    }

    if (resolutions.count() == 1)
        resolutions.append(QSize(3840, 3840));

    return resolutions;
}

QList<qreal> AVFVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings,
                                                                 bool *continuous) const
{
    QList<qreal> uniqueFrameRates;

    AVCaptureDevice *device = m_service->session()->videoCaptureDevice();
    if (!device)
        return uniqueFrameRates;

    if (continuous)
        *continuous = false;

    QVector<AVFPSRange> allRates;

    if (!settings.resolution().isValid()) {
        const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(device, 0));
        for (int i = 0; i < formats.size(); ++i) {
            AVCaptureDeviceFormat *format = formats.at(i);
            allRates += qt_device_format_framerates(format);
        }
    } else {
        AVCaptureDeviceFormat *format = qt_find_best_resolution_match(device,
                                                                      settings.resolution(),
                                                                      m_service->session()->defaultCodec());
        if (format)
            allRates = qt_device_format_framerates(format);
    }

    for (int j = 0; j < allRates.size(); ++j) {
        if (!real_list_contains(uniqueFrameRates, allRates[j].first))
            uniqueFrameRates.append(allRates[j].first);
        if (!real_list_contains(uniqueFrameRates, allRates[j].second))
            uniqueFrameRates.append(allRates[j].second);
    }

    return uniqueFrameRates;
}

QStringList AVFVideoEncoderSettingsControl::supportedVideoCodecs() const
{
    return *supportedCodecs;
}

QString AVFVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const
{
    if (codecName == QLatin1String("avc1"))
        return QStringLiteral("H.264");
    else if (codecName == QLatin1String("jpeg"))
        return QStringLiteral("M-JPEG");
#ifdef Q_OS_OSX
    else if (codecName == QLatin1String("ap4h"))
        return QStringLiteral("Apple ProRes 4444");
    else if (codecName == QLatin1String("apcn"))
        return QStringLiteral("Apple ProRes 422 Standard Definition");
#endif

    return QString();
}

QVideoEncoderSettings AVFVideoEncoderSettingsControl::videoSettings() const
{
    return m_actualSettings;
}

void AVFVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings)
{
    if (m_requestedSettings == settings)
        return;

    m_requestedSettings = m_actualSettings = settings;
}

NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection *connection)
{
    if (m_service->session()->state() != QCamera::LoadedState &&
        m_service->session()->state() != QCamera::ActiveState) {
        return nil;
    }

    AVCaptureDevice *device = m_service->session()->videoCaptureDevice();
    if (!device)
        return nil;

    AVFPSRange currentFps = qt_current_framerates(device, connection);
    const bool needFpsChange = m_requestedSettings.frameRate() > 0
                               && m_requestedSettings.frameRate() != currentFps.second;

    NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary];

    // -- Codec

    // AVVideoCodecKey is the only mandatory key
    QString codec = m_requestedSettings.codec().isEmpty() ? supportedCodecs->first() : m_requestedSettings.codec();
    if (!supportedCodecs->contains(codec)) {
        qWarning("Unsupported codec: '%s'", codec.toLocal8Bit().constData());
        codec = supportedCodecs->first();
    }
    [videoSettings setObject:codec.toNSString() forKey:AVVideoCodecKey];
    m_actualSettings.setCodec(codec);

    // -- Resolution

    int w = m_requestedSettings.resolution().width();
    int h = m_requestedSettings.resolution().height();

    if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) {
        CMFormatDescriptionRef formatDesc = currentFormat.formatDescription;
        CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc);

        // We have to change the device's activeFormat in 3 cases:
        // - the requested recording resolution is higher than the current device resolution
        // - the requested recording resolution has a different aspect ratio than the current device aspect ratio
        // - the requested frame rate is not available for the current device format
        AVCaptureDeviceFormat *newFormat = nil;
        if ((w <= 0 || h <= 0)
                && m_requestedSettings.frameRate() > 0
                && !format_supports_framerate(currentFormat, m_requestedSettings.frameRate())) {

            newFormat = qt_find_best_framerate_match(device,
                                                     m_service->session()->defaultCodec(),
                                                     m_requestedSettings.frameRate());

        } else if (w > 0 && h > 0) {
            AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device,
                                                                     m_requestedSettings.resolution(),
                                                                     m_service->session()->defaultCodec());

            if (f) {
                CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription);
                qreal fAspectRatio = qreal(d.width) / d.height;

                if (w > dim.width || h > dim.height
                        || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) {
                    newFormat = f;
                }
            }
        }

        if (qt_set_active_format(device, newFormat, !needFpsChange)) {
            m_restoreFormat = [currentFormat retain];
            formatDesc = newFormat.formatDescription;
            dim = CMVideoFormatDescriptionGetDimensions(formatDesc);
        }

        if (w > 0 && h > 0) {
            // Make sure the recording resolution has the same aspect ratio as the device's
            // current resolution
            qreal deviceAspectRatio = qreal(dim.width) / dim.height;
            qreal recAspectRatio = qreal(w) / h;
            if (qAbs(deviceAspectRatio - recAspectRatio) > 0.01) {
                if (recAspectRatio > deviceAspectRatio)
                    w = qRound(h * deviceAspectRatio);
                else
                    h = qRound(w / deviceAspectRatio);
            }

            // recording resolution can't be higher than the device's active resolution
            w = qMin(w, dim.width);
            h = qMin(h, dim.height);
        }
    }

    if (w > 0 && h > 0) {
        // Width and height must be divisible by 2
        w += w & 1;
        h += h & 1;

        [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey];
        [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey];
        m_actualSettings.setResolution(w, h);
    } else {
        m_actualSettings.setResolution(qt_device_format_resolution(device.activeFormat));
    }

    // -- FPS

    if (needFpsChange) {
        m_restoreFps = currentFps;
        const qreal fps = m_requestedSettings.frameRate();
        qt_set_framerate_limits(device, connection, fps, fps);
    }
    m_actualSettings.setFrameRate(qt_current_framerates(device, connection).second);

    // -- Codec Settings

    NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary];
    int bitrate = -1;
    float quality = -1.f;

    if (m_requestedSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) {
        if (m_requestedSettings.quality() != QMultimedia::NormalQuality) {
            if (codec != QLatin1String("jpeg")) {
                qWarning("ConstantQualityEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData());
            } else {
                switch (m_requestedSettings.quality()) {
                case QMultimedia::VeryLowQuality:
                    quality = 0.f;
                    break;
                case QMultimedia::LowQuality:
                    quality = 0.25f;
                    break;
                case QMultimedia::HighQuality:
                    quality = 0.75f;
                    break;
                case QMultimedia::VeryHighQuality:
                    quality = 1.f;
                    break;
                default:
                    quality = -1.f; // NormalQuality, let the system decide
                    break;
                }
            }
        }
    } else if (m_requestedSettings.encodingMode() == QMultimedia::AverageBitRateEncoding){
        if (codec != QLatin1String("avc1"))
            qWarning("AverageBitRateEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData());
        else
            bitrate = m_requestedSettings.bitRate();
    } else {
        qWarning("Encoding mode is not supported");
    }

    if (bitrate != -1)
        [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey];
    if (quality != -1.f)
        [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey];

    [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey];

    return videoSettings;
}

void AVFVideoEncoderSettingsControl::unapplySettings(AVCaptureConnection *connection)
{
    m_actualSettings = m_requestedSettings;

    AVCaptureDevice *device = m_service->session()->videoCaptureDevice();
    if (!device)
        return;

    const bool needFpsChanged = m_restoreFps.first || m_restoreFps.second;

    if (m_restoreFormat) {
        qt_set_active_format(device, m_restoreFormat, !needFpsChanged);
        [m_restoreFormat release];
        m_restoreFormat = nil;
    }

    if (needFpsChanged) {
        qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second);
        m_restoreFps = AVFPSRange();
    }
}

QT_END_NAMESPACE

#include "moc_avfvideoencodersettingscontrol.cpp"
