/*
 * 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 a class PreviewWindow that encapsulates
 * functionality of a preview window set via set_preview_window camera HAL API.
 */

#define LOG_NDEBUG 0
#define LOG_TAG "EmulatedCamera_Preview"
#include "PreviewWindow.h"
#include <cutils/log.h>
#include <hardware/camera.h>
#include "EmulatedCameraDevice.h"
#include "GrallocModule.h"

namespace android {

PreviewWindow::PreviewWindow()
    : mPreviewWindow(NULL),
      mLastPreviewed(0),
      mPreviewFrameWidth(0),
      mPreviewFrameHeight(0),
      mPreviewEnabled(false) {}

PreviewWindow::~PreviewWindow() {}

/****************************************************************************
 * Camera API
 ***************************************************************************/

status_t PreviewWindow::setPreviewWindow(struct preview_stream_ops* window,
                                         int preview_fps) {
  ALOGV("%s: current: %p -> new: %p", __FUNCTION__, mPreviewWindow, window);

  status_t res = NO_ERROR;
  Mutex::Autolock locker(&mObjectLock);

  /* Reset preview info. */
  mPreviewFrameWidth = mPreviewFrameHeight = 0;
  mPreviewAfter = 0;
  mLastPreviewed = 0;

  if (window != NULL) {
    /* The CPU will write each frame to the preview window buffer.
     * Note that we delay setting preview window buffer geometry until
     * frames start to come in. */
    res = window->set_usage(window, GRALLOC_USAGE_SW_WRITE_OFTEN);
    if (res == NO_ERROR) {
      /* Set preview frequency. */
      mPreviewAfter = 1000000 / preview_fps;
    } else {
      window = NULL;
      res = -res;  // set_usage returns a negative errno.
      ALOGE("%s: Error setting preview window usage %d -> %s", __FUNCTION__,
            res, strerror(res));
    }
  }
  mPreviewWindow = window;

  return res;
}

status_t PreviewWindow::startPreview() {
  ALOGV("%s", __FUNCTION__);

  Mutex::Autolock locker(&mObjectLock);
  mPreviewEnabled = true;

  return NO_ERROR;
}

void PreviewWindow::stopPreview() {
  ALOGV("%s", __FUNCTION__);

  Mutex::Autolock locker(&mObjectLock);
  mPreviewEnabled = false;
}

/****************************************************************************
 * Public API
 ***************************************************************************/

void PreviewWindow::onNextFrameAvailable(const void* /*frame*/,
                                         nsecs_t timestamp,
                                         EmulatedCameraDevice* camera_dev) {
  int res;
  Mutex::Autolock locker(&mObjectLock);

  if (!isPreviewEnabled() || mPreviewWindow == NULL || !isPreviewTime()) {
    return;
  }

  /* Make sure that preview window dimensions are OK with the camera device */
  if (adjustPreviewDimensions(camera_dev)) {
    /* Need to set / adjust buffer geometry for the preview window.
     * Note that in the emulator preview window uses only RGB for pixel
     * formats. */
    ALOGV("%s: Adjusting preview windows %p geometry to %dx%d", __FUNCTION__,
          mPreviewWindow, mPreviewFrameWidth, mPreviewFrameHeight);
    res = mPreviewWindow->set_buffers_geometry(
        mPreviewWindow, mPreviewFrameWidth, mPreviewFrameHeight,
        HAL_PIXEL_FORMAT_RGBA_8888);
    if (res != NO_ERROR) {
      ALOGE("%s: Error in set_buffers_geometry %d -> %s", __FUNCTION__, -res,
            strerror(-res));
      return;
    }
  }

  /*
   * Push new frame to the preview window.
   */

  /* Dequeue preview window buffer for the frame. */
  buffer_handle_t* buffer = NULL;
  int stride = 0;
  res = mPreviewWindow->dequeue_buffer(mPreviewWindow, &buffer, &stride);
  if (res != NO_ERROR || buffer == NULL) {
    ALOGE("%s: Unable to dequeue preview window buffer: %d -> %s", __FUNCTION__,
          -res, strerror(-res));
    return;
  }

  /* Let the preview window to lock the buffer. */
  res = mPreviewWindow->lock_buffer(mPreviewWindow, buffer);
  if (res != NO_ERROR) {
    ALOGE("%s: Unable to lock preview window buffer: %d -> %s", __FUNCTION__,
          -res, strerror(-res));
    mPreviewWindow->cancel_buffer(mPreviewWindow, buffer);
    return;
  }

  /* Now let the graphics framework to lock the buffer, and provide
   * us with the framebuffer data address. */
  void* img = NULL;
  res = GrallocModule::getInstance().lock(*buffer, GRALLOC_USAGE_SW_WRITE_OFTEN,
                                          0, 0, mPreviewFrameWidth,
                                          mPreviewFrameHeight, &img);
  if (res != NO_ERROR) {
    ALOGE("%s: gralloc.lock failure: %d -> %s", __FUNCTION__, res,
          strerror(res));
    mPreviewWindow->cancel_buffer(mPreviewWindow, buffer);
    return;
  }

  /* Frames come in in YV12/NV12/NV21 format. Since preview window doesn't
   * supports those formats, we need to obtain the frame in RGB565. */
  res = camera_dev->getCurrentPreviewFrame(img);
  if (res == NO_ERROR) {
    /* Show it. */
    mPreviewWindow->set_timestamp(mPreviewWindow, timestamp);
    mPreviewWindow->enqueue_buffer(mPreviewWindow, buffer);
  } else {
    ALOGE("%s: Unable to obtain preview frame: %d", __FUNCTION__, res);
    mPreviewWindow->cancel_buffer(mPreviewWindow, buffer);
  }
  GrallocModule::getInstance().unlock(*buffer);
}

/***************************************************************************
 * Private API
 **************************************************************************/

bool PreviewWindow::adjustPreviewDimensions(EmulatedCameraDevice* camera_dev) {
  /* Match the cached frame dimensions against the actual ones. */
  if (mPreviewFrameWidth == camera_dev->getFrameWidth() &&
      mPreviewFrameHeight == camera_dev->getFrameHeight()) {
    /* They match. */
    return false;
  }

  /* They don't match: adjust the cache. */
  mPreviewFrameWidth = camera_dev->getFrameWidth();
  mPreviewFrameHeight = camera_dev->getFrameHeight();

  return true;
}

bool PreviewWindow::isPreviewTime() {
  timeval cur_time;
  gettimeofday(&cur_time, NULL);
  const uint64_t cur_mks = cur_time.tv_sec * 1000000LL + cur_time.tv_usec;
  if ((cur_mks - mLastPreviewed) >= mPreviewAfter) {
    mLastPreviewed = cur_mks;
    return true;
  }
  return false;
}

}; /* namespace android */