/****************************************************************************
**
** Copyright (C) 2013 Research In Motion
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 http://www.qt.io/terms-conditions. For further
** information use the contact form at http://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 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "bbcamerafocuscontrol.h"

#include "bbcamerasession.h"

#include <QDebug>

QT_BEGIN_NAMESPACE

BbCameraFocusControl::BbCameraFocusControl(BbCameraSession *session, QObject *parent)
    : QCameraFocusControl(parent)
    , m_session(session)
    , m_focusMode(QCameraFocus::FocusModes())
    , m_focusPointMode(QCameraFocus::FocusPointAuto)
    , m_customFocusPoint(QPointF(0, 0))
{
}

QCameraFocus::FocusModes BbCameraFocusControl::focusMode() const
{
    camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF;

    const camera_error_t result = camera_get_focus_mode(m_session->handle(), &focusMode);
    if (result != CAMERA_EOK) {
        qWarning() << "Unable to retrieve focus mode from camera:" << result;
        return QCameraFocus::FocusModes();
    }

    switch (focusMode) {
    case CAMERA_FOCUSMODE_EDOF:
        return QCameraFocus::HyperfocalFocus;
    case CAMERA_FOCUSMODE_MANUAL:
        return QCameraFocus::ManualFocus;
    case CAMERA_FOCUSMODE_AUTO:
        return QCameraFocus::AutoFocus;
    case CAMERA_FOCUSMODE_MACRO:
        return QCameraFocus::MacroFocus;
    case CAMERA_FOCUSMODE_CONTINUOUS_AUTO:
        return QCameraFocus::ContinuousFocus;
    case CAMERA_FOCUSMODE_CONTINUOUS_MACRO: // fall through
    case CAMERA_FOCUSMODE_OFF: // fall through
    default:
            return QCameraFocus::FocusModes();
    }
}

void BbCameraFocusControl::setFocusMode(QCameraFocus::FocusModes mode)
{
    if (m_focusMode == mode)
        return;

    camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF;

    if (mode == QCameraFocus::HyperfocalFocus)
        focusMode = CAMERA_FOCUSMODE_EDOF;
    else if (mode == QCameraFocus::ManualFocus)
        focusMode = CAMERA_FOCUSMODE_MANUAL;
    else if (mode == QCameraFocus::AutoFocus)
        focusMode = CAMERA_FOCUSMODE_AUTO;
    else if (mode == QCameraFocus::MacroFocus)
        focusMode = CAMERA_FOCUSMODE_MACRO;
    else if (mode == QCameraFocus::ContinuousFocus)
        focusMode = CAMERA_FOCUSMODE_CONTINUOUS_AUTO;

    const camera_error_t result = camera_set_focus_mode(m_session->handle(), focusMode);

    if (result != CAMERA_EOK) {
        qWarning() << "Unable to set focus mode:" << result;
        return;
    }

    m_focusMode = mode;
    emit focusModeChanged(m_focusMode);
}

bool BbCameraFocusControl::isFocusModeSupported(QCameraFocus::FocusModes mode) const
{
    if (m_session->state() == QCamera::UnloadedState)
        return false;

    if (mode == QCameraFocus::HyperfocalFocus)
        return false; //TODO how to check?
    else if (mode == QCameraFocus::ManualFocus)
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_MANUALFOCUS);
    else if (mode == QCameraFocus::AutoFocus)
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS);
    else if (mode == QCameraFocus::MacroFocus)
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_MACROFOCUS);
    else if (mode == QCameraFocus::ContinuousFocus)
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS);

    return false;
}

QCameraFocus::FocusPointMode BbCameraFocusControl::focusPointMode() const
{
    return m_focusPointMode;
}

void BbCameraFocusControl::setFocusPointMode(QCameraFocus::FocusPointMode mode)
{
    if (m_session->status() != QCamera::ActiveStatus)
        return;

    if (m_focusPointMode == mode)
        return;

    m_focusPointMode = mode;
    emit focusPointModeChanged(m_focusPointMode);

    if (m_focusPointMode == QCameraFocus::FocusPointAuto) {
        //TODO: is this correct?
        const camera_error_t result = camera_set_focus_regions(m_session->handle(), 0, 0);
        if (result != CAMERA_EOK) {
            qWarning() << "Unable to set focus region:" << result;
            return;
        }

        emit focusZonesChanged();
    } else if (m_focusPointMode == QCameraFocus::FocusPointCenter) {
        // get the size of the viewfinder
        int viewfinderWidth = 0;
        int viewfinderHeight = 0;

        if (!retrieveViewfinderSize(&viewfinderWidth, &viewfinderHeight))
            return;

        // define a 40x40 pixel focus region in the center of the viewfinder
        camera_region_t focusRegion;
        focusRegion.left = (viewfinderWidth / 2) - 20;
        focusRegion.top = (viewfinderHeight / 2) - 20;
        focusRegion.width = 40;
        focusRegion.height = 40;

        camera_error_t result = camera_set_focus_regions(m_session->handle(), 1, &focusRegion);
        if (result != CAMERA_EOK) {
            qWarning() << "Unable to set focus region:" << result;
            return;
        }

        // re-set focus mode to apply focus region changes
        camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF;
        result = camera_get_focus_mode(m_session->handle(), &focusMode);
        camera_set_focus_mode(m_session->handle(), focusMode);

        emit focusZonesChanged();

    } else if (m_focusPointMode == QCameraFocus::FocusPointFaceDetection) {
        //TODO: implement later
    } else if (m_focusPointMode == QCameraFocus::FocusPointCustom) {
        updateCustomFocusRegion();
    }
}

bool BbCameraFocusControl::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const
{
    if (m_session->state() == QCamera::UnloadedState)
        return false;

    if (mode == QCameraFocus::FocusPointAuto) {
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS);
    } else if (mode == QCameraFocus::FocusPointCenter) {
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_REGIONFOCUS);
    } else if (mode == QCameraFocus::FocusPointFaceDetection) {
        return false; //TODO: implement via custom region in combination with face detection in viewfinder
    } else if (mode == QCameraFocus::FocusPointCustom) {
        return camera_has_feature(m_session->handle(), CAMERA_FEATURE_REGIONFOCUS);
    }

    return false;
}

QPointF BbCameraFocusControl::customFocusPoint() const
{
    return m_customFocusPoint;
}

void BbCameraFocusControl::setCustomFocusPoint(const QPointF &point)
{
    if (m_customFocusPoint == point)
        return;

    m_customFocusPoint = point;
    emit customFocusPointChanged(m_customFocusPoint);

    updateCustomFocusRegion();
}

QCameraFocusZoneList BbCameraFocusControl::focusZones() const
{
    if (m_session->state() == QCamera::UnloadedState)
        return QCameraFocusZoneList();

    camera_region_t regions[20];
    int supported = 0;
    int asked = 0;
    camera_error_t result = camera_get_focus_regions(m_session->handle(), 20, &supported, &asked, regions);

    if (result != CAMERA_EOK) {
        qWarning() << "Unable to retrieve focus regions:" << result;
        return QCameraFocusZoneList();
    }

    // retrieve width and height of viewfinder
    int viewfinderWidth = 0;
    int viewfinderHeight = 0;
    if (m_session->captureMode() & QCamera::CaptureStillImage)
        result = camera_get_photovf_property(m_session->handle(),
                                             CAMERA_IMGPROP_WIDTH, &viewfinderWidth,
                                             CAMERA_IMGPROP_HEIGHT, &viewfinderHeight);
    else if (m_session->captureMode() & QCamera::CaptureVideo)
        result = camera_get_videovf_property(m_session->handle(),
                                             CAMERA_IMGPROP_WIDTH, &viewfinderWidth,
                                             CAMERA_IMGPROP_HEIGHT, &viewfinderHeight);

    if (result != CAMERA_EOK) {
        qWarning() << "Unable to retrieve viewfinder size:" << result;
        return QCameraFocusZoneList();
    }

    QCameraFocusZoneList list;
    for (int i = 0; i < asked; ++i) {
        const int x = regions[i].left;
        const int y = regions[i].top;
        const int width = regions[i].width;
        const int height = regions[i].height;

        QRectF rect(static_cast<float>(x)/static_cast<float>(viewfinderWidth),
                    static_cast<float>(y)/static_cast<float>(viewfinderHeight),
                    static_cast<float>(width)/static_cast<float>(viewfinderWidth),
                    static_cast<float>(height)/static_cast<float>(viewfinderHeight));

        list << QCameraFocusZone(rect, QCameraFocusZone::Focused); //TODO: how to know if a zone is unused/selected/focused?!?
    }

    return list;
}

void BbCameraFocusControl::updateCustomFocusRegion()
{
    // get the size of the viewfinder
    int viewfinderWidth = 0;
    int viewfinderHeight = 0;

    if (!retrieveViewfinderSize(&viewfinderWidth, &viewfinderHeight))
        return;

    // define a 40x40 pixel focus region around the custom focus point
    camera_region_t focusRegion;
    focusRegion.left = qMax(0, static_cast<int>(m_customFocusPoint.x() * viewfinderWidth) - 20);
    focusRegion.top = qMax(0, static_cast<int>(m_customFocusPoint.y() * viewfinderHeight) - 20);
    focusRegion.width = 40;
    focusRegion.height = 40;

    camera_error_t result = camera_set_focus_regions(m_session->handle(), 1, &focusRegion);
    if (result != CAMERA_EOK) {
        qWarning() << "Unable to set focus region:" << result;
        return;
    }

    // re-set focus mode to apply focus region changes
    camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF;
    result = camera_get_focus_mode(m_session->handle(), &focusMode);
    camera_set_focus_mode(m_session->handle(), focusMode);

    emit focusZonesChanged();
}

bool BbCameraFocusControl::retrieveViewfinderSize(int *width, int *height)
{
    if (!width || !height)
        return false;

    camera_error_t result = CAMERA_EOK;
    if (m_session->captureMode() & QCamera::CaptureStillImage)
        result = camera_get_photovf_property(m_session->handle(),
                                             CAMERA_IMGPROP_WIDTH, width,
                                             CAMERA_IMGPROP_HEIGHT, height);
    else if (m_session->captureMode() & QCamera::CaptureVideo)
        result = camera_get_videovf_property(m_session->handle(),
                                             CAMERA_IMGPROP_WIDTH, width,
                                             CAMERA_IMGPROP_HEIGHT, height);

    if (result != CAMERA_EOK) {
        qWarning() << "Unable to retrieve viewfinder size:" << result;
        return false;
    }

    return true;
}

QT_END_NAMESPACE
