/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Contains implementation of an abstract class EmulatedCameraDevice that defines
 * functionality expected from an emulated physical camera device:
 *  - Obtaining and setting camera parameters
 *  - Capturing frames
 *  - Streaming video
 *  - etc.
 */

#define LOG_NDEBUG 0
#define LOG_TAG "EmulatedCamera_Device"
#include <cutils/log.h>
#include <sys/select.h>
#include <cmath>
#include "Alignment.h"
#include "EmulatedCamera.h"
#include "EmulatedCameraDevice.h"

#undef min
#undef max
#include <algorithm>

namespace android {

const float GAMMA_CORRECTION = 2.2f;
EmulatedCameraDevice::EmulatedCameraDevice(EmulatedCamera* camera_hal)
    : mObjectLock(),
      mCameraHAL(camera_hal),
      mExposureCompensation(1.0f),
      mWhiteBalanceScale(NULL),
      mSupportedWhiteBalanceScale(),
      mState(ECDS_CONSTRUCTED),
      mTriggerAutoFocus(false)
{
}

EmulatedCameraDevice::~EmulatedCameraDevice()
{
    ALOGV("EmulatedCameraDevice destructor");
    for (size_t i = 0; i < mSupportedWhiteBalanceScale.size(); ++i) {
        if (mSupportedWhiteBalanceScale.valueAt(i) != NULL) {
            delete[] mSupportedWhiteBalanceScale.valueAt(i);
        }
    }
}

/****************************************************************************
 * Emulated camera device public API
 ***************************************************************************/

status_t EmulatedCameraDevice::Initialize()
{
    if (isInitialized()) {
        ALOGW("%s: Emulated camera device is already initialized: mState = %d",
             __FUNCTION__, mState);
        return NO_ERROR;
    }

    mState = ECDS_INITIALIZED;

    return NO_ERROR;
}

status_t EmulatedCameraDevice::startDeliveringFrames(bool one_burst)
{
    ALOGV("%s", __FUNCTION__);

    if (!isStarted()) {
        ALOGE("%s: Device is not started", __FUNCTION__);
        return EINVAL;
    }

    /* Frames will be delivered from the thread routine. */
    const status_t res = startWorkerThread(one_burst);
    ALOGE_IF(res != NO_ERROR, "%s: startWorkerThread failed", __FUNCTION__);
    return res;
}

status_t EmulatedCameraDevice::stopDeliveringFrames()
{
    ALOGV("%s", __FUNCTION__);

    if (!isStarted()) {
        ALOGW("%s: Device is not started", __FUNCTION__);
        return NO_ERROR;
    }

    const status_t res = stopWorkerThread();
    ALOGE_IF(res != NO_ERROR, "%s: stopWorkerThread failed", __FUNCTION__);
    return res;
}

status_t EmulatedCameraDevice::setPreviewFrameRate(int framesPerSecond) {
    if (framesPerSecond <= 0) {
        return EINVAL;
    }
    mFramesPerSecond = framesPerSecond;
    return NO_ERROR;
}

void EmulatedCameraDevice::setExposureCompensation(const float ev) {
    ALOGV("%s", __FUNCTION__);

    if (!isStarted()) {
        ALOGW("%s: Fake camera device is not started.", __FUNCTION__);
    }

    mExposureCompensation = std::pow(2.0f, ev / GAMMA_CORRECTION);
    ALOGV("New exposure compensation is %f", mExposureCompensation);
}

void EmulatedCameraDevice::initializeWhiteBalanceModes(const char* mode,
                                                       const float r_scale,
                                                       const float b_scale) {
    ALOGV("%s with %s, %f, %f", __FUNCTION__, mode, r_scale, b_scale);
    float* value = new float[3];
    value[0] = r_scale; value[1] = 1.0f; value[2] = b_scale;
    mSupportedWhiteBalanceScale.add(String8(mode), value);
}

void EmulatedCameraDevice::setWhiteBalanceMode(const char* mode) {
    ALOGV("%s with white balance %s", __FUNCTION__, mode);
    mWhiteBalanceScale =
            mSupportedWhiteBalanceScale.valueFor(String8(mode));
}

/* Computes the pixel value after adjusting the white balance to the current
 * one. The input the y, u, v channel of the pixel and the adjusted value will
 * be stored in place. The adjustment is done in RGB space.
 */
void EmulatedCameraDevice::changeWhiteBalance(uint8_t& y,
                                              uint8_t& u,
                                              uint8_t& v) const {
    float r_scale = mWhiteBalanceScale[0];
    float b_scale = mWhiteBalanceScale[2];
    int r = static_cast<float>(YUV2R(y, u, v)) / r_scale;
    int g = YUV2G(y, u, v);
    int b = static_cast<float>(YUV2B(y, u, v)) / b_scale;

    y = RGB2Y(r, g, b);
    u = RGB2U(r, g, b);
    v = RGB2V(r, g, b);
}

void EmulatedCameraDevice::checkAutoFocusTrigger() {
    // The expected value is a reference so we need it to be a variable
    bool expectedTrigger = true;
    if (mTriggerAutoFocus.compare_exchange_strong(expectedTrigger, false)) {
        // If the compare exchange returns true then the value was the expected
        // 'true' and was successfully set to 'false'. So that means it's time
        // to trigger an auto-focus event and that we have disabled that trigger
        // so it won't happen until another request is received.
        mCameraHAL->autoFocusComplete();
    }
}

status_t EmulatedCameraDevice::getCurrentFrameImpl(const uint8_t* source,
                                                   uint8_t* dest,
                                                   uint32_t pixelFormat) const {
    if (pixelFormat == mPixelFormat) {
        memcpy(dest, source, mFrameBufferSize);
        return NO_ERROR;
    } else if (pixelFormat == V4L2_PIX_FMT_YUV420 &&
               mPixelFormat == V4L2_PIX_FMT_YVU420) {
        // Convert from YV12 to YUV420 without alignment
        const int ySize = mYStride * mFrameHeight;
        const int uvSize = mUVStride * (mFrameHeight / 2);
        if (mYStride == mFrameWidth) {
            // Copy Y straight up
            memcpy(dest, source, ySize);
        } else {
            // Strip alignment
            for (int y = 0; y < mFrameHeight; ++y) {
                memcpy(dest + y * mFrameWidth,
                       source + y * mYStride,
                       mFrameWidth);
            }
        }

        if (mUVStride == mFrameWidth / 2) {
            // Swap U and V
            memcpy(dest + ySize, source + ySize + uvSize, uvSize);
            memcpy(dest + ySize + uvSize, source + ySize, uvSize);
        } else {
            // Strip alignment
            uint8_t* uvDest = dest + mFrameWidth * mFrameHeight;
            const uint8_t* uvSource = source + ySize + uvSize;

            for (int i = 0; i < 2; ++i) {
                for (int y = 0; y < mFrameHeight / 2; ++y) {
                    memcpy(uvDest + y * (mFrameWidth / 2),
                           uvSource + y * mUVStride,
                           mFrameWidth / 2);
                }
                uvDest += (mFrameHeight / 2) * (mFrameWidth / 2);
                uvSource -= uvSize;
            }
        }
        return NO_ERROR;
    }
    ALOGE("%s: Invalid pixel format conversion [%.4s to %.4s] requested",
          __FUNCTION__, reinterpret_cast<const char*>(&mPixelFormat),
          reinterpret_cast<const char*>(&pixelFormat));
    return EINVAL;
}

status_t EmulatedCameraDevice::getCurrentFrame(void* buffer,
                                               uint32_t pixelFormat,
                                               int64_t* timestamp)
{
    if (!isStarted()) {
        ALOGE("%s: Device is not started", __FUNCTION__);
        return EINVAL;
    }
    if (buffer == nullptr) {
        ALOGE("%s: Invalid buffer provided", __FUNCTION__);
        return EINVAL;
    }

    FrameLock lock(*this);
    const void* source = mCameraThread->getPrimaryBuffer();
    if (source == nullptr) {
        ALOGE("%s: No framebuffer", __FUNCTION__);
        return EINVAL;
    }

    if (timestamp != nullptr) {
      *timestamp = mCameraThread->getPrimaryTimestamp();
    }

    return getCurrentFrameImpl(reinterpret_cast<const uint8_t*>(source),
                               reinterpret_cast<uint8_t*>(buffer),
                               pixelFormat);
}

status_t EmulatedCameraDevice::getCurrentPreviewFrame(void* buffer,
                                                      int64_t* timestamp)
{
    if (!isStarted()) {
        ALOGE("%s: Device is not started", __FUNCTION__);
        return EINVAL;
    }
    if (buffer == nullptr) {
        ALOGE("%s: Invalid buffer provided", __FUNCTION__);
        return EINVAL;
    }

    FrameLock lock(*this);
    const void* currentFrame = mCameraThread->getPrimaryBuffer();
    if (currentFrame == nullptr) {
        ALOGE("%s: No framebuffer", __FUNCTION__);
        return EINVAL;
    }

    if (timestamp != nullptr) {
      *timestamp = mCameraThread->getPrimaryTimestamp();
    }

    /* In emulation the framebuffer is never RGB. */
    switch (mPixelFormat) {
        case V4L2_PIX_FMT_YVU420:
            YV12ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight);
            return NO_ERROR;
        case V4L2_PIX_FMT_YUV420:
            YU12ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight);
            return NO_ERROR;
        case V4L2_PIX_FMT_NV21:
            NV21ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight);
            return NO_ERROR;
        case V4L2_PIX_FMT_NV12:
            NV12ToRGB32(currentFrame, buffer, mFrameWidth, mFrameHeight);
            return NO_ERROR;

        default:
            ALOGE("%s: Unknown pixel format %.4s",
                 __FUNCTION__, reinterpret_cast<const char*>(&mPixelFormat));
            return EINVAL;
    }
}

const void* EmulatedCameraDevice::getCurrentFrame() {
    if (mCameraThread.get()) {
        return mCameraThread->getPrimaryBuffer();
    }
    return nullptr;
}

EmulatedCameraDevice::FrameLock::FrameLock(EmulatedCameraDevice& cameraDevice)
    : mCameraDevice(cameraDevice) {
        mCameraDevice.lockCurrentFrame();
}

EmulatedCameraDevice::FrameLock::~FrameLock() {
    mCameraDevice.unlockCurrentFrame();
}

status_t EmulatedCameraDevice::setAutoFocus() {
    mTriggerAutoFocus = true;
    return NO_ERROR;
}

status_t EmulatedCameraDevice::cancelAutoFocus() {
    mTriggerAutoFocus = false;
    return NO_ERROR;
}

bool EmulatedCameraDevice::requestRestart(int width, int height,
                                          uint32_t pixelFormat,
                                          bool takingPicture, bool oneBurst) {
    if (mCameraThread.get() == nullptr) {
        ALOGE("%s: No thread alive to perform the restart, is preview on?",
              __FUNCTION__);
        return false;
    }
    mCameraThread->requestRestart(width, height, pixelFormat,
                                  takingPicture, oneBurst);
    return true;
}

/****************************************************************************
 * Emulated camera device private API
 ***************************************************************************/

status_t EmulatedCameraDevice::commonStartDevice(int width,
                                                 int height,
                                                 uint32_t pix_fmt)
{
    /* Validate pixel format, and calculate framebuffer size at the same time. */
    switch (pix_fmt) {
        case V4L2_PIX_FMT_YVU420:
        case V4L2_PIX_FMT_YUV420:
            // For these pixel formats the strides have to be aligned to 16 byte
            // boundaries as per the format specification
            // https://developer.android.com/reference/android/graphics/ImageFormat.html#YV12
            mYStride = align(width, 16);
            mUVStride = align(mYStride / 2, 16);
            // The second term should use half the height but since there are
            // two planes the multiplication with two cancels that out
            mFrameBufferSize = mYStride * height + mUVStride * height;
            break;
        case V4L2_PIX_FMT_NV21:
        case V4L2_PIX_FMT_NV12:
            mYStride = width;
            // Because of interleaving the UV stride is the same as the Y stride
            // since it covers two pixels, one U and one V.
            mUVStride = mYStride;
            // Since the U/V stride covers both U and V we don't multiply by two
            mFrameBufferSize = mYStride * height + mUVStride * (height / 2);
            break;
        default:
            ALOGE("%s: Unknown pixel format %.4s",
                 __FUNCTION__, reinterpret_cast<const char*>(&pix_fmt));
            return EINVAL;
    }

    /* Cache framebuffer info. */
    mFrameWidth = width;
    mFrameHeight = height;
    mPixelFormat = pix_fmt;
    mTotalPixels = width * height;

    /* Allocate framebuffer. */
    mFrameBuffers[0].resize(mFrameBufferSize);
    mFrameBuffers[1].resize(mFrameBufferSize);
    ALOGV("%s: Allocated %zu bytes for %d pixels in %.4s[%dx%d] frame",
         __FUNCTION__, mFrameBufferSize, mTotalPixels,
         reinterpret_cast<const char*>(&mPixelFormat), mFrameWidth, mFrameHeight);
    return NO_ERROR;
}

void EmulatedCameraDevice::commonStopDevice()
{
    mFrameWidth = mFrameHeight = mTotalPixels = 0;
    mPixelFormat = 0;

    mFrameBuffers[0].clear();
    mFrameBuffers[1].clear();
    // No need to keep all that memory allocated if the camera isn't running
    mFrameBuffers[0].shrink_to_fit();
    mFrameBuffers[1].shrink_to_fit();
}

/****************************************************************************
 * Worker thread management.
 ***************************************************************************/

status_t EmulatedCameraDevice::startWorkerThread(bool one_burst)
{
    ALOGV("%s", __FUNCTION__);

    if (!isInitialized()) {
        ALOGE("%s: Emulated camera device is not initialized", __FUNCTION__);
        return EINVAL;
    }

    mCameraThread = new CameraThread(this, staticProduceFrame, this);
    if (mCameraThread == NULL) {
        ALOGE("%s: Unable to instantiate CameraThread object", __FUNCTION__);
        return ENOMEM;
    }
    status_t res = mCameraThread->startThread(one_burst);
    if (res != NO_ERROR) {
        ALOGE("%s: Unable to start CameraThread: %s",
              __FUNCTION__, strerror(res));
        return res;
    }

    return res;
}

status_t EmulatedCameraDevice::stopWorkerThread()
{
    ALOGV("%s", __FUNCTION__);

    if (!isInitialized()) {
        ALOGE("%s: Emulated camera device is not initialized", __FUNCTION__);
        return EINVAL;
    }

    status_t res = mCameraThread->stopThread();
    if (res != NO_ERROR) {
        ALOGE("%s: Unable to stop CameraThread", __FUNCTION__);
        return res;
    }
    res = mCameraThread->joinThread();
    if (res != NO_ERROR) {
        ALOGE("%s: Unable to join CameraThread", __FUNCTION__);
        return res;
    }

    // Destroy the thread as well
    mCameraThread.clear();
    return res;
}

EmulatedCameraDevice::CameraThread::CameraThread(EmulatedCameraDevice* dev,
                                                 ProduceFrameFunc producer,
                                                 void* producerOpaque)
    : WorkerThread("Camera_CameraThread", dev, dev->mCameraHAL),
      mCurFrameTimestamp(0),
      mProducerFunc(producer),
      mProducerOpaque(producerOpaque),
      mRestartWidth(0),
      mRestartHeight(0),
      mRestartPixelFormat(0),
      mRestartOneBurst(false),
      mRestartTakingPicture(false),
      mRestartRequested(false) {

}

const void* EmulatedCameraDevice::CameraThread::getPrimaryBuffer() const {
    if (mFrameProducer.get()) {
        return mFrameProducer->getPrimaryBuffer();
    }
    return nullptr;
}

int64_t EmulatedCameraDevice::CameraThread::getPrimaryTimestamp() const {
    if (mFrameProducer.get()) {
        return mFrameProducer->getPrimaryTimestamp();
    }
    return 0L;
}

void EmulatedCameraDevice::CameraThread::lockPrimaryBuffer() {
    mFrameProducer->lockPrimaryBuffer();
}

void EmulatedCameraDevice::CameraThread::unlockPrimaryBuffer() {
    mFrameProducer->unlockPrimaryBuffer();
}

bool
EmulatedCameraDevice::CameraThread::waitForFrameOrTimeout(nsecs_t timeout) {
    // Keep waiting until the frame producer indicates that a frame is available
    // This does introduce some unnecessary latency to the first frame delivery
    // but avoids a lot of thread synchronization.
    do {
        // We don't have any specific fd we want to select so we pass in -1
        // timeout is in nanoseconds but Select expects microseconds
        Mutex::Autolock lock(mRunningMutex);
        mRunningCondition.waitRelative(mRunningMutex, timeout);
        if (!mRunning) {
            ALOGV("%s: CameraThread has been terminated.", __FUNCTION__);
            return false;
        }
        // Set a short timeout in case there is no frame available and we are
        // going to loop. This way we ensure a sleep but keep a decent latency
        timeout = milliseconds(5);
    } while (!mFrameProducer->hasFrame());

    return true;
}

bool EmulatedCameraDevice::CameraThread::inWorkerThread() {
    /* Wait till FPS timeout expires, or thread exit message is received. */
    nsecs_t wakeAt =
        mCurFrameTimestamp + 1000000000.0 / mCameraDevice->mFramesPerSecond;
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
    nsecs_t timeout = std::max<nsecs_t>(0, wakeAt - now);

    if (!waitForFrameOrTimeout(timeout)) {
        return false;
    }

    /* Check if a restart and potentially apply the requested changes */
    if (!checkRestartRequest()) {
        return false;
    }

    /* Check if an auto-focus event needs to be triggered */
    mCameraDevice->checkAutoFocusTrigger();

    mCurFrameTimestamp = systemTime(SYSTEM_TIME_MONOTONIC);
    mCameraHAL->onNextFrameAvailable(mCurFrameTimestamp, mCameraDevice);

    return true;
}

status_t EmulatedCameraDevice::CameraThread::onThreadStart() {
    void* primaryBuffer = mCameraDevice->getPrimaryBuffer();
    void* secondaryBuffer = mCameraDevice->getSecondaryBuffer();
    mFrameProducer = new FrameProducer(mCameraDevice,
                                       mProducerFunc, mProducerOpaque,
                                       primaryBuffer, secondaryBuffer);
    if (mFrameProducer.get() == nullptr) {
        ALOGE("%s: Could not instantiate FrameProducer object", __FUNCTION__);
        return ENOMEM;
    }
    return mFrameProducer->startThread(mOneBurst);
}

void EmulatedCameraDevice::CameraThread::onThreadExit() {
    if (mFrameProducer.get()) {
        if (mFrameProducer->stopThread() == NO_ERROR) {
            mFrameProducer->joinThread();
            mFrameProducer.clear();
        }
    }
}

EmulatedCameraDevice::CameraThread::FrameProducer::FrameProducer(
        EmulatedCameraDevice* dev,
        ProduceFrameFunc producer,
        void* opaque,
        void* primaryBuffer,
        void* secondaryBuffer)
    : WorkerThread("Camera_FrameProducer", dev, dev->mCameraHAL),
      mProducer(producer),
      mOpaque(opaque),
      mPrimaryBuffer(primaryBuffer),
      mSecondaryBuffer(secondaryBuffer),
      mPrimaryTimestamp(0L),
      mSecondaryTimestamp(0L),
      mLastFrame(0),
      mHasFrame(false) {

}

const void*
EmulatedCameraDevice::CameraThread::FrameProducer::getPrimaryBuffer() const {
    return mPrimaryBuffer;
}

int64_t
EmulatedCameraDevice::CameraThread::FrameProducer::getPrimaryTimestamp() const {
    return mPrimaryTimestamp;
}

void EmulatedCameraDevice::CameraThread::FrameProducer::lockPrimaryBuffer() {
    mBufferMutex.lock();
}
void EmulatedCameraDevice::CameraThread::FrameProducer::unlockPrimaryBuffer() {
    mBufferMutex.unlock();
}

void EmulatedCameraDevice::CameraThread::requestRestart(int width,
                                                        int height,
                                                        uint32_t pixelFormat,
                                                        bool takingPicture,
                                                        bool oneBurst) {
    Mutex::Autolock lock(mRequestMutex);
    mRestartWidth = width;
    mRestartHeight = height;
    mRestartPixelFormat = pixelFormat;
    mRestartTakingPicture = takingPicture;
    mRestartOneBurst = oneBurst;
    mRestartRequested = true;
}

bool EmulatedCameraDevice::CameraThread::FrameProducer::hasFrame() const {
    return mHasFrame;
}

bool EmulatedCameraDevice::CameraThread::checkRestartRequest() {
    Mutex::Autolock lock(mRequestMutex);
    if (mRestartRequested) {
        mRestartRequested = false;
        status_t res = mFrameProducer->stopThread();
        if (res != NO_ERROR) {
            ALOGE("%s: Could not stop frame producer thread", __FUNCTION__);
            mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
            return false;
        }
        res = mFrameProducer->joinThread();
        if (res != NO_ERROR) {
            ALOGE("%s: Could not join frame producer thread", __FUNCTION__);
            mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
            return false;
        }
        mFrameProducer.clear();
        res = mCameraDevice->stopDevice();
        if (res != NO_ERROR) {
            ALOGE("%s: Could not stop device", __FUNCTION__);
            mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
            return false;
        }
        res = mCameraDevice->startDevice(mRestartWidth,
                                         mRestartHeight,
                                         mRestartPixelFormat);
        if (res != NO_ERROR) {
            ALOGE("%s: Could not start device", __FUNCTION__);
            mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
            return false;
        }
        if (mRestartTakingPicture) {
            mCameraHAL->setTakingPicture(true);
        }
        mOneBurst = mRestartOneBurst;

        // Pretend like this a thread start, performs the remaining setup
        if (onThreadStart() != NO_ERROR) {
            mCameraDevice->stopDevice();
            mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
            return false;
        }

        // Now wait for the frame producer to start producing before we proceed
        return waitForFrameOrTimeout(0);
    }
    return true;
}

bool EmulatedCameraDevice::CameraThread::FrameProducer::inWorkerThread() {
    nsecs_t nextFrame =
        mLastFrame + 1000000000 / mCameraDevice->mFramesPerSecond;
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
    nsecs_t timeout = std::max<nsecs_t>(0, nextFrame - now);

    {
        Mutex::Autolock lock(mRunningMutex);
        mRunningCondition.waitRelative(mRunningMutex, timeout);
        if (!mRunning) {
            ALOGV("%s: FrameProducer has been terminated.", __FUNCTION__);
            return false;
        }
    }

    // Produce one frame and place it in the secondary buffer
    mLastFrame = systemTime(SYSTEM_TIME_MONOTONIC);
    if (!mProducer(mOpaque, mSecondaryBuffer, &mSecondaryTimestamp)) {
        ALOGE("FrameProducer could not produce frame, exiting thread");
        mCameraHAL->onCameraDeviceError(CAMERA_ERROR_SERVER_DIED);
        return false;
    }

    {
        // Switch buffers now that the secondary buffer is ready
        Mutex::Autolock lock(mBufferMutex);
        std::swap(mPrimaryBuffer, mSecondaryBuffer);
        std::swap(mPrimaryTimestamp, mSecondaryTimestamp);
    }
    mHasFrame = true;
    return true;
}

void EmulatedCameraDevice::lockCurrentFrame() {
    mCameraThread->lockPrimaryBuffer();
}

void EmulatedCameraDevice::unlockCurrentFrame() {
    mCameraThread->unlockPrimaryBuffer();
}

};  /* namespace android */