/*
 * Copyright (C) 2012 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "EmulatedCamera2_JpegCompressor"

#include <utils/Log.h>

#include "../EmulatedFakeCamera2.h"
#include "../EmulatedFakeCamera3.h"
#include "JpegCompressor.h"

namespace android {

JpegCompressor::JpegCompressor()
    : Thread(false),
      mIsBusy(false),
      mSynchronous(false),
      mBuffers(NULL),
      mListener(NULL) {}

JpegCompressor::~JpegCompressor() { Mutex::Autolock lock(mMutex); }

status_t JpegCompressor::reserve() {
  Mutex::Autolock busyLock(mBusyMutex);
  if (mIsBusy) {
    ALOGE("%s: Already processing a buffer!", __FUNCTION__);
    return INVALID_OPERATION;
  }
  mIsBusy = true;
  return OK;
}

status_t JpegCompressor::start(Buffers *buffers, JpegListener *listener) {
  if (listener == NULL) {
    ALOGE("%s: NULL listener not allowed!", __FUNCTION__);
    return BAD_VALUE;
  }
  ALOGV("%s: Starting JPEG compression thread", __FUNCTION__);
  Mutex::Autolock lock(mMutex);
  {
    Mutex::Autolock busyLock(mBusyMutex);

    if (!mIsBusy) {
      ALOGE("Called start without reserve() first!");
      return INVALID_OPERATION;
    }
    mSynchronous = false;
    mBuffers = buffers;
    mListener = listener;
  }

  status_t res;
  res = run("EmulatedFakeCamera2::JpegCompressor");
  if (res != OK) {
    ALOGE("%s: Unable to start up compression thread: %s (%d)", __FUNCTION__,
          strerror(-res), res);
    delete mBuffers;
  }
  return res;
}

status_t JpegCompressor::compressSynchronous(Buffers *buffers) {
  status_t res;

  Mutex::Autolock lock(mMutex);
  {
    Mutex::Autolock busyLock(mBusyMutex);

    if (mIsBusy) {
      ALOGE("%s: Already processing a buffer!", __FUNCTION__);
      return INVALID_OPERATION;
    }

    mIsBusy = true;
    mSynchronous = true;
    mBuffers = buffers;
  }

  res = compress();

  cleanUp();

  return res;
}

status_t JpegCompressor::cancel() {
  requestExitAndWait();
  return OK;
}

status_t JpegCompressor::readyToRun() { return OK; }

bool JpegCompressor::threadLoop() {
  status_t res;
  ALOGV("%s: Starting compression thread", __FUNCTION__);

  res = compress();

  mListener->onJpegDone(mJpegBuffer, res == OK);

  cleanUp();

  return false;
}

status_t JpegCompressor::compress() {
  // Find source and target buffers. Assumes only one buffer matches
  // each condition!
  ALOGV("%s: Compressing start", __FUNCTION__);
  bool mFoundAux = false;
  for (size_t i = 0; i < mBuffers->size(); i++) {
    const StreamBuffer &b = (*mBuffers)[i];
    if (b.format == HAL_PIXEL_FORMAT_BLOB) {
      mJpegBuffer = b;
      mFoundJpeg = true;
    } else if (b.streamId <= 0) {
      mAuxBuffer = b;
      mFoundAux = true;
    }
    if (mFoundJpeg && mFoundAux) break;
  }
  if (!mFoundJpeg || !mFoundAux) {
    ALOGE("%s: Unable to find buffers for JPEG source/destination",
          __FUNCTION__);
    return BAD_VALUE;
  }

  // Set up error management

  mJpegErrorInfo = NULL;
  JpegError error;
  error.parent = this;

  mCInfo.err = jpeg_std_error(&error);
  mCInfo.err->error_exit = jpegErrorHandler;

  jpeg_create_compress(&mCInfo);
  if (checkError("Error initializing compression")) return NO_INIT;

  // Route compressed data straight to output stream buffer

  JpegDestination jpegDestMgr;
  jpegDestMgr.parent = this;
  jpegDestMgr.init_destination = jpegInitDestination;
  jpegDestMgr.empty_output_buffer = jpegEmptyOutputBuffer;
  jpegDestMgr.term_destination = jpegTermDestination;

  mCInfo.dest = &jpegDestMgr;

  // Set up compression parameters

  mCInfo.image_width = mAuxBuffer.width;
  mCInfo.image_height = mAuxBuffer.height;
  mCInfo.input_components = 3;
  mCInfo.in_color_space = JCS_RGB;

  jpeg_set_defaults(&mCInfo);
  if (checkError("Error configuring defaults")) return NO_INIT;

  // Do compression

  jpeg_start_compress(&mCInfo, TRUE);
  if (checkError("Error starting compression")) return NO_INIT;

  size_t rowStride = mAuxBuffer.stride * 3;
  const size_t kChunkSize = 32;
  while (mCInfo.next_scanline < mCInfo.image_height) {
    JSAMPROW chunk[kChunkSize];
    for (size_t i = 0; i < kChunkSize; i++) {
      chunk[i] =
          (JSAMPROW)(mAuxBuffer.img + (i + mCInfo.next_scanline) * rowStride);
    }
    jpeg_write_scanlines(&mCInfo, chunk, kChunkSize);
    if (checkError("Error while compressing")) return NO_INIT;
    if (exitPending()) {
      ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
      return TIMED_OUT;
    }
  }

  jpeg_finish_compress(&mCInfo);
  if (checkError("Error while finishing compression")) return NO_INIT;

  // All done
  ALOGV("%s: Compressing done", __FUNCTION__);

  return OK;
}

bool JpegCompressor::isBusy() {
  Mutex::Autolock busyLock(mBusyMutex);
  return mIsBusy;
}

bool JpegCompressor::isStreamInUse(uint32_t id) {
  Mutex::Autolock lock(mBusyMutex);

  if (mBuffers && mIsBusy) {
    for (size_t i = 0; i < mBuffers->size(); i++) {
      if ((*mBuffers)[i].streamId == (int)id) return true;
    }
  }
  return false;
}

bool JpegCompressor::waitForDone(nsecs_t timeout) {
  Mutex::Autolock lock(mBusyMutex);
  while (mIsBusy) {
    status_t res = mDone.waitRelative(mBusyMutex, timeout);
    if (res != OK) return false;
  }
  return true;
}

bool JpegCompressor::checkError(const char *msg) {
  if (mJpegErrorInfo) {
    char errBuffer[JMSG_LENGTH_MAX];
    mJpegErrorInfo->err->format_message(mJpegErrorInfo, errBuffer);
    ALOGE("%s: %s: %s", __FUNCTION__, msg, errBuffer);
    mJpegErrorInfo = NULL;
    return true;
  }
  return false;
}

void JpegCompressor::cleanUp() {
  jpeg_destroy_compress(&mCInfo);
  Mutex::Autolock lock(mBusyMutex);

  if (mFoundAux) {
    if (mAuxBuffer.streamId == 0) {
      delete[] mAuxBuffer.img;
    } else if (!mSynchronous) {
      mListener->onJpegInputDone(mAuxBuffer);
    }
  }
  if (!mSynchronous) {
    delete mBuffers;
  }

  mBuffers = NULL;

  mIsBusy = false;
  mDone.signal();
}

void JpegCompressor::jpegErrorHandler(j_common_ptr cinfo) {
  JpegError *error = static_cast<JpegError *>(cinfo->err);
  error->parent->mJpegErrorInfo = cinfo;
}

void JpegCompressor::jpegInitDestination(j_compress_ptr cinfo) {
  JpegDestination *dest = static_cast<JpegDestination *>(cinfo->dest);
  ALOGV("%s: Setting destination to %p, size %zu", __FUNCTION__,
        dest->parent->mJpegBuffer.img, kMaxJpegSize);
  dest->next_output_byte = (JOCTET *)(dest->parent->mJpegBuffer.img);
  dest->free_in_buffer = kMaxJpegSize;
}

boolean JpegCompressor::jpegEmptyOutputBuffer(j_compress_ptr /*cinfo*/) {
  ALOGE("%s: JPEG destination buffer overflow!", __FUNCTION__);
  return true;
}

void JpegCompressor::jpegTermDestination(j_compress_ptr cinfo) {
  ALOGV("%s: Done writing JPEG data. %zu bytes left in buffer", __FUNCTION__,
        cinfo->dest->free_in_buffer);
}

JpegCompressor::JpegListener::~JpegListener() {}

}  // namespace android