/*
 * 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 "EmulatedCameraDevice.h"
#include <cutils/log.h>
#include <sys/select.h>
#include <algorithm>
#include <cmath>
#include "EmulatedCamera.h"

namespace android {

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

EmulatedCameraDevice::~EmulatedCameraDevice() {
  ALOGV("EmulatedCameraDevice destructor");
  if (mCurrentFrame != NULL) {
    delete[] mCurrentFrame;
  }
  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;
  }

  /* Instantiate worker thread object. */
  mWorkerThread = new WorkerThread(this);
  if (getWorkerThread() == NULL) {
    ALOGE("%s: Unable to instantiate worker thread object", __FUNCTION__);
    return ENOMEM;
  }

  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: startWorkerThread failed", __FUNCTION__);
  return res;
}

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));
}

void EmulatedCameraDevice::startAutoFocus() { mIsFocusing = true; }

/* 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::simulateAutoFocus() {
  if (mIsFocusing) {
    ALOGV("%s: Simulating auto-focus", __FUNCTION__);
    mCameraHAL->onCameraFocusAcquired();
    mIsFocusing = false;
  }
}

status_t EmulatedCameraDevice::getCurrentPreviewFrame(void* buffer) {
  if (!isStarted()) {
    ALOGE("%s: Device is not started", __FUNCTION__);
    return EINVAL;
  }
  if (mCurrentFrame == NULL || buffer == NULL) {
    ALOGE("%s: No framebuffer", __FUNCTION__);
    return EINVAL;
  }

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

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

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

status_t EmulatedCameraDevice::commonStartDevice(int width, int height,
                                                 uint32_t pix_fmt, int fps) {
  /* Validate pixel format, and calculate framebuffer size at the same time. */
  switch (pix_fmt) {
    case V4L2_PIX_FMT_YVU420:
    case V4L2_PIX_FMT_YUV420:
    case V4L2_PIX_FMT_NV21:
    case V4L2_PIX_FMT_NV12:
      mFrameBufferSize = (width * height * 12) / 8;
      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;
  mTargetFps = fps;

  /* Allocate framebuffer. */
  mCurrentFrame = new uint8_t[mFrameBufferSize];
  if (mCurrentFrame == NULL) {
    ALOGE("%s: Unable to allocate framebuffer", __FUNCTION__);
    return ENOMEM;
  }
  ALOGV("%s: Allocated %p %zu bytes for %d pixels in %.4s[%dx%d] frame",
        __FUNCTION__, mCurrentFrame, mFrameBufferSize, mTotalPixels,
        reinterpret_cast<const char*>(&mPixelFormat), mFrameWidth,
        mFrameHeight);
  return NO_ERROR;
}

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

  if (mCurrentFrame != NULL) {
    delete[] mCurrentFrame;
    mCurrentFrame = NULL;
  }
}

const CameraParameters* EmulatedCameraDevice::getCameraParameters() {
  return mCameraHAL->getCameraParameters();
}

/****************************************************************************
 * 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;
  }

  const status_t res = getWorkerThread()->startThread(one_burst);
  ALOGE_IF(res != NO_ERROR, "%s: Unable to start worker thread", __FUNCTION__);
  return res;
}

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

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

  const status_t res = getWorkerThread()->stopThread();
  ALOGE_IF(res != NO_ERROR, "%s: Unable to stop worker thread", __FUNCTION__);
  return res;
}

bool EmulatedCameraDevice::inWorkerThread() {
  /* This will end the thread loop, and will terminate the thread. Derived
   * classes must override this method. */
  return false;
}

/****************************************************************************
 * Worker thread implementation.
 ***************************************************************************/

status_t EmulatedCameraDevice::WorkerThread::readyToRun() {
  ALOGV("Starting emulated camera device worker thread...");

  ALOGW_IF(mThreadControl >= 0 || mControlFD >= 0,
           "%s: Thread control FDs are opened", __FUNCTION__);
  /* Create a pair of FDs that would be used to control the thread. */
  int thread_fds[2];
  status_t ret;
  Mutex::Autolock lock(mCameraDevice->mObjectLock);
  if (pipe(thread_fds) == 0) {
    mThreadControl = thread_fds[1];
    mControlFD = thread_fds[0];
    ALOGV("Emulated device's worker thread has been started.");
    ret = NO_ERROR;
  } else {
    ALOGE("%s: Unable to create thread control FDs: %d -> %s", __FUNCTION__,
          errno, strerror(errno));
    ret = errno;
  }

  mSetup.signal();
  return ret;
}

status_t EmulatedCameraDevice::WorkerThread::stopThread() {
  ALOGV("Stopping emulated camera device's worker thread...");

  status_t res = EINVAL;

  // Limit the scope of the Autolock
  {
    // If thread is running and readyToRun() has not finished running,
    //    then wait until it is done.
    Mutex::Autolock lock(mCameraDevice->mObjectLock);
#if VSOC_PLATFORM_SDK_AFTER(J_MR2)
    if (isRunning() && (mThreadControl < 0 || mControlFD < 0)) {
#else
    if (getTid() != -1 && (mThreadControl < 0 || mControlFD < 0)) {
#endif
      mSetup.wait(mCameraDevice->mObjectLock);
    }
  }

  if (mThreadControl >= 0) {
    /* Send "stop" message to the thread loop. */
    const ControlMessage msg = THREAD_STOP;
    const int wres =
        TEMP_FAILURE_RETRY(write(mThreadControl, &msg, sizeof(msg)));
    if (wres == sizeof(msg)) {
      /* Stop the thread, and wait till it's terminated. */
      res = requestExitAndWait();
      if (res == NO_ERROR) {
        /* Close control FDs. */
        if (mThreadControl >= 0) {
          close(mThreadControl);
          mThreadControl = -1;
        }
        if (mControlFD >= 0) {
          close(mControlFD);
          mControlFD = -1;
        }
        ALOGV("Emulated camera device's worker thread has been stopped.");
      } else {
        ALOGE("%s: requestExitAndWait failed: %d -> %s", __FUNCTION__, res,
              strerror(-res));
      }
    } else {
      ALOGE("%s: Unable to send THREAD_STOP message: %d -> %s", __FUNCTION__,
            errno, strerror(errno));
      res = errno ? errno : EINVAL;
    }
  } else {
    ALOGE("%s: Thread control FDs are not opened", __FUNCTION__);
  }

  return res;
}

EmulatedCameraDevice::WorkerThread::SelectRes
EmulatedCameraDevice::WorkerThread::Select(int fd, int timeout) {
  fd_set fds[1];
  struct timeval tv, *tvp = NULL;

  mCameraDevice->simulateAutoFocus();

  const int fd_num = (fd >= 0) ? std::max(fd, mControlFD) + 1 : mControlFD + 1;
  FD_ZERO(fds);
  FD_SET(mControlFD, fds);
  if (fd >= 0) {
    FD_SET(fd, fds);
  }
  if (timeout) {
    tv.tv_sec = timeout / 1000000;
    tv.tv_usec = timeout % 1000000;
    tvp = &tv;
  }
  int res = TEMP_FAILURE_RETRY(select(fd_num, fds, NULL, NULL, tvp));
  if (res < 0) {
    ALOGE("%s: select returned %d and failed: %d -> %s", __FUNCTION__, res,
          errno, strerror(errno));
    return ERROR;
  } else if (res == 0) {
    /* Timeout. */
    return TIMEOUT;
  } else if (FD_ISSET(mControlFD, fds)) {
    /* A control event. Lets read the message. */
    ControlMessage msg;
    res = TEMP_FAILURE_RETRY(read(mControlFD, &msg, sizeof(msg)));
    if (res != sizeof(msg)) {
      ALOGE("%s: Unexpected message size %d, or an error %d -> %s",
            __FUNCTION__, res, errno, strerror(errno));
      return ERROR;
    }
    /* THREAD_STOP is the only message expected here. */
    if (msg == THREAD_STOP) {
      ALOGV("%s: THREAD_STOP message is received", __FUNCTION__);
      return EXIT_THREAD;
    } else {
      ALOGE("Unknown worker thread message %d", msg);
      return ERROR;
    }
  } else {
    /* Must be an FD. */
    ALOGW_IF(fd < 0 || !FD_ISSET(fd, fds), "%s: Undefined 'select' result",
             __FUNCTION__);
    return READY;
  }
}

}; /* namespace android */