/*
 * Copyright (C) 2019 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 "JpegCompressor"

#include "JpegCompressor.h"

#include <camera_blob.h>
#include <cutils/properties.h>
#include <libyuv.h>
#include <utils/Log.h>
#include <utils/Trace.h>

namespace android {

using google_camera_hal::CameraBlob;
using google_camera_hal::CameraBlobId;
using google_camera_hal::ErrorCode;
using google_camera_hal::MessageType;
using google_camera_hal::NotifyMessage;

// All ICC profile data sourced from https://github.com/saucecontrol/Compact-ICC-Profiles
static constexpr uint8_t kIccProfileDisplayP3[] = {
    0x00, 0x00, 0x01, 0xe0, 0x6c, 0x63, 0x6d, 0x73, 0x04, 0x20, 0x00, 0x00,
    0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20,
    0x07, 0xe2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x1d,
    0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00,
    0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64,
    0x51, 0xb1, 0x0e, 0x57, 0x9c, 0x0c, 0x00, 0x19, 0x38, 0xb9, 0x93, 0x88,
    0x06, 0x61, 0xb8, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
    0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x22,
    0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x22,
    0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x14,
    0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x2c,
    0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x14,
    0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x14,
    0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xac, 0x00, 0x00, 0x00, 0x14,
    0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x20,
    0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x20,
    0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x20,
    0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x06,
    0x00, 0x00, 0x00, 0x1c, 0x00, 0x73, 0x00, 0x50, 0x00, 0x33, 0x00, 0x00,
    0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x06,
    0x00, 0x00, 0x00, 0x1c, 0x00, 0x43, 0x00, 0x43, 0x00, 0x30, 0x00, 0x21,
    0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x73, 0x66, 0x33, 0x32,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x42, 0x00, 0x00, 0x05, 0xde,
    0xff, 0xff, 0xf3, 0x25, 0x00, 0x00, 0x07, 0x93, 0x00, 0x00, 0xfd, 0x90,
    0xff, 0xff, 0xfb, 0xa1, 0xff, 0xff, 0xfd, 0xa2, 0x00, 0x00, 0x03, 0xdc,
    0x00, 0x00, 0xc0, 0x6e, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x83, 0xdf, 0x00, 0x00, 0x3d, 0xbf, 0xff, 0xff, 0xff, 0xbb,
    0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xbf,
    0x00, 0x00, 0xb1, 0x37, 0x00, 0x00, 0x0a, 0xb9, 0x58, 0x59, 0x5a, 0x20,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x38, 0x00, 0x00, 0x11, 0x0a,
    0x00, 0x00, 0xc8, 0xb9, 0x70, 0x61, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x66, 0x69, 0x00, 0x00, 0xf2, 0xa7,
    0x00, 0x00, 0x0d, 0x59, 0x00, 0x00, 0x13, 0xd0, 0x00, 0x00, 0x0a, 0x5b};

static constexpr uint8_t kIccProfileDciP3[] = {
    0x00, 0x00, 0x01, 0xd0, 0x6c, 0x63, 0x6d, 0x73, 0x04, 0x20, 0x00, 0x00,
    0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20,
    0x07, 0xe5, 0x00, 0x04, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x1b, 0x00, 0x00,
    0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00,
    0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64,
    0x79, 0xb3, 0x51, 0x32, 0xc4, 0xd7, 0x5b, 0x84, 0xf1, 0xbb, 0xcb, 0x58,
    0x53, 0xb0, 0xfa, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
    0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x22,
    0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x22,
    0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x14,
    0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x2c,
    0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x14,
    0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x14,
    0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xac, 0x00, 0x00, 0x00, 0x14,
    0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x10,
    0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x10,
    0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x10,
    0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x06,
    0x00, 0x00, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x50, 0x00, 0x33, 0x00, 0x00,
    0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x06,
    0x00, 0x00, 0x00, 0x1c, 0x00, 0x43, 0x00, 0x43, 0x00, 0x30, 0x00, 0x21,
    0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x73, 0x66, 0x33, 0x32,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 0xe6, 0x00, 0x00, 0x09, 0xef,
    0xff, 0xff, 0xf6, 0x8d, 0x00, 0x00, 0x0e, 0x3a, 0x00, 0x00, 0xf6, 0xc8,
    0xff, 0xff, 0xfc, 0x53, 0xff, 0xff, 0xfe, 0xe7, 0x00, 0x00, 0x01, 0x5b,
    0x00, 0x00, 0xdc, 0xdf, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x7c, 0x75, 0x00, 0x00, 0x3a, 0x08, 0xff, 0xff, 0xff, 0xcc,
    0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xe8,
    0x00, 0x00, 0xb5, 0xd8, 0x00, 0x00, 0x0b, 0x11, 0x58, 0x59, 0x5a, 0x20,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x79, 0x00, 0x00, 0x10, 0x20,
    0x00, 0x00, 0xc8, 0x50, 0x70, 0x61, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x99, 0x9a};

static constexpr uint8_t kIccProfileSrgb[] = {
    0x00, 0x00, 0x01, 0xe0, 0x6c, 0x63, 0x6d, 0x73, 0x04, 0x20, 0x00, 0x00,
    0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5a, 0x20,
    0x07, 0xe2, 0x00, 0x03, 0x00, 0x14, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x1d,
    0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00,
    0x73, 0x61, 0x77, 0x73, 0x63, 0x74, 0x72, 0x6c, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x68, 0x61, 0x6e, 0x64,
    0xa3, 0xb2, 0xab, 0xdf, 0x5c, 0xa7, 0x03, 0x12, 0xa8, 0x55, 0xa4, 0xec,
    0x35, 0x7a, 0xd1, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
    0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x24,
    0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x22,
    0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x14,
    0x63, 0x68, 0x61, 0x64, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x2c,
    0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x14,
    0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x14,
    0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0xac, 0x00, 0x00, 0x00, 0x14,
    0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x20,
    0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x20,
    0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x20,
    0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x08,
    0x00, 0x00, 0x00, 0x1c, 0x00, 0x73, 0x00, 0x52, 0x00, 0x47, 0x00, 0x42,
    0x6d, 0x6c, 0x75, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x0c, 0x65, 0x6e, 0x55, 0x53, 0x00, 0x00, 0x00, 0x06,
    0x00, 0x00, 0x00, 0x1c, 0x00, 0x43, 0x00, 0x43, 0x00, 0x30, 0x00, 0x21,
    0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x73, 0x66, 0x33, 0x32,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x3f, 0x00, 0x00, 0x05, 0xdd,
    0xff, 0xff, 0xf3, 0x26, 0x00, 0x00, 0x07, 0x90, 0x00, 0x00, 0xfd, 0x92,
    0xff, 0xff, 0xfb, 0xa1, 0xff, 0xff, 0xfd, 0xa2, 0x00, 0x00, 0x03, 0xdc,
    0x00, 0x00, 0xc0, 0x71, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x6f, 0xa0, 0x00, 0x00, 0x38, 0xf2, 0x00, 0x00, 0x03, 0x8f,
    0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x96,
    0x00, 0x00, 0xb7, 0x89, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x85,
    0x00, 0x00, 0xb6, 0xc4, 0x70, 0x61, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x66, 0x69, 0x00, 0x00, 0xf2, 0xa7,
    0x00, 0x00, 0x0d, 0x59, 0x00, 0x00, 0x13, 0xd0, 0x00, 0x00, 0x0a, 0x5b};

JpegCompressor::JpegCompressor() {
  ATRACE_CALL();
  char value[PROPERTY_VALUE_MAX];
  if (property_get("ro.product.manufacturer", value, "unknown") <= 0) {
    ALOGW("%s: No Exif make data!", __FUNCTION__);
  }
  exif_make_ = std::string(value);

  if (property_get("ro.product.model", value, "unknown") <= 0) {
    ALOGW("%s: No Exif model data!", __FUNCTION__);
  }
  exif_model_ = std::string(value);

  jpeg_processing_thread_ = std::thread([this] { this->ThreadLoop(); });
}

JpegCompressor::~JpegCompressor() {
  ATRACE_CALL();

  // Abort the ongoing compression and flush any pending jobs
  jpeg_done_ = true;
  condition_.notify_one();
  jpeg_processing_thread_.join();
  while (!pending_yuv_jobs_.empty()) {
    auto job = std::move(pending_yuv_jobs_.front());
    job->output->stream_buffer.status = BufferStatus::kError;
    pending_yuv_jobs_.pop();
  }
}

status_t JpegCompressor::QueueYUV420(std::unique_ptr<JpegYUV420Job> job) {
  ATRACE_CALL();

  if ((job->input.get() == nullptr) || (job->output.get() == nullptr) ||
      (job->output->format != PixelFormat::BLOB) ||
      (job->output->dataSpace != HAL_DATASPACE_V0_JFIF)) {
    ALOGE("%s: Unable to find buffers for JPEG source/destination",
          __FUNCTION__);
    return BAD_VALUE;
  }

  std::unique_lock<std::mutex> lock(mutex_);
  pending_yuv_jobs_.push(std::move(job));
  condition_.notify_one();

  return OK;
}

void JpegCompressor::ThreadLoop() {
  ATRACE_CALL();

  while (!jpeg_done_) {
    std::unique_ptr<JpegYUV420Job> current_yuv_job = nullptr;
    {
      std::lock_guard<std::mutex> lock(mutex_);
      if (!pending_yuv_jobs_.empty()) {
        current_yuv_job = std::move(pending_yuv_jobs_.front());
        pending_yuv_jobs_.pop();
      }
    }

    if (current_yuv_job.get() != nullptr) {
      CompressYUV420(std::move(current_yuv_job));
    }

    std::unique_lock<std::mutex> lock(mutex_);
    auto ret = condition_.wait_for(lock, std::chrono::milliseconds(10));
    if (ret == std::cv_status::timeout) {
      ALOGV("%s: Jpeg thread timeout", __FUNCTION__);
    }
  }
}

void JpegCompressor::CompressYUV420(std::unique_ptr<JpegYUV420Job> job) {
  const uint8_t* app1_buffer = nullptr;
  size_t app1_buffer_size = 0;
  std::vector<uint8_t> thumbnail_jpeg_buffer;
  size_t encoded_thumbnail_size = 0;
  if ((job->exif_utils.get() != nullptr) &&
      (job->result_metadata.get() != nullptr)) {
    if (job->exif_utils->Initialize()) {
      camera_metadata_ro_entry_t entry;
      size_t thumbnail_width = 0;
      size_t thumbnail_height = 0;
      std::vector<uint8_t> thumb_yuv420_frame;
      YCbCrPlanes thumb_planes;
      auto ret = job->result_metadata->Get(ANDROID_JPEG_THUMBNAIL_SIZE, &entry);
      if ((ret == OK) && (entry.count == 2)) {
        thumbnail_width = entry.data.i32[0];
        thumbnail_height = entry.data.i32[1];
        if ((thumbnail_width > 0) && (thumbnail_height > 0)) {
          thumb_yuv420_frame.resize((thumbnail_width * thumbnail_height * 3) /
                                    2);
          thumb_planes = {
              .img_y = thumb_yuv420_frame.data(),
              .img_cb = thumb_yuv420_frame.data() +
                        thumbnail_width * thumbnail_height,
              .img_cr = thumb_yuv420_frame.data() +
                        (thumbnail_width * thumbnail_height * 5) / 4,
              .y_stride = static_cast<uint32_t>(thumbnail_width),
              .cbcr_stride = static_cast<uint32_t>(thumbnail_width) / 2};
          // TODO: Crop thumbnail according to documentation
          auto stat = I420Scale(
              job->input->yuv_planes.img_y, job->input->yuv_planes.y_stride,
              job->input->yuv_planes.img_cb, job->input->yuv_planes.cbcr_stride,
              job->input->yuv_planes.img_cr, job->input->yuv_planes.cbcr_stride,
              job->input->width, job->input->height, thumb_planes.img_y,
              thumb_planes.y_stride, thumb_planes.img_cb,
              thumb_planes.cbcr_stride, thumb_planes.img_cr,
              thumb_planes.cbcr_stride, thumbnail_width, thumbnail_height,
              libyuv::kFilterNone);
          if (stat != 0) {
            ALOGE("%s: Failed during thumbnail scaling: %d", __FUNCTION__, stat);
            thumb_yuv420_frame.clear();
          }
        }
      }

      if (job->exif_utils->SetFromMetadata(
              *job->result_metadata, job->input->width, job->input->height)) {
        if (!thumb_yuv420_frame.empty()) {
          thumbnail_jpeg_buffer.resize(64 * 1024);  // APP1 is limited by 64k
          encoded_thumbnail_size = CompressYUV420Frame(
              {.output_buffer = thumbnail_jpeg_buffer.data(),
               .output_buffer_size = thumbnail_jpeg_buffer.size(),
               .yuv_planes = thumb_planes,
               .width = thumbnail_width,
               .height = thumbnail_height,
               .app1_buffer = nullptr,
               .app1_buffer_size = 0,
               .color_space = job->input->color_space});
          if (encoded_thumbnail_size > 0) {
            job->output->stream_buffer.status = BufferStatus::kOk;
          } else {
            ALOGE("%s: Failed encoding thumbail!", __FUNCTION__);
            thumbnail_jpeg_buffer.clear();
          }
        }

        job->exif_utils->SetMake(exif_make_);
        job->exif_utils->SetModel(exif_model_);
        job->exif_utils->SetColorSpace(COLOR_SPACE_ICC_PROFILE);
        if (job->exif_utils->GenerateApp1(thumbnail_jpeg_buffer.empty()
                                              ? nullptr
                                              : thumbnail_jpeg_buffer.data(),
                                          encoded_thumbnail_size)) {
          app1_buffer = job->exif_utils->GetApp1Buffer();
          app1_buffer_size = job->exif_utils->GetApp1Length();
        } else {
          ALOGE("%s: Unable to generate App1 buffer", __FUNCTION__);
        }
      } else {
        ALOGE("%s: Unable to generate EXIF section!", __FUNCTION__);
      }
    } else {
      ALOGE("%s: Unable to initialize Exif generator!", __FUNCTION__);
    }
  }

  auto encoded_size = CompressYUV420Frame(
      {.output_buffer = job->output->plane.img.img,
       .output_buffer_size = job->output->plane.img.buffer_size,
       .yuv_planes = job->input->yuv_planes,
       .width = job->input->width,
       .height = job->input->height,
       .app1_buffer = app1_buffer,
       .app1_buffer_size = app1_buffer_size,
       .color_space = job->input->color_space});
  if (encoded_size > 0) {
    job->output->stream_buffer.status = BufferStatus::kOk;
  } else {
    job->output->stream_buffer.status = BufferStatus::kError;
    return;
  }

  auto jpeg_header_offset =
      job->output->plane.img.buffer_size - sizeof(struct CameraBlob);
  if (jpeg_header_offset > encoded_size) {
    struct CameraBlob* blob = reinterpret_cast<struct CameraBlob*>(
        job->output->plane.img.img + jpeg_header_offset);
    blob->blob_id = CameraBlobId::JPEG;
    blob->blob_size = encoded_size;
  } else {
    ALOGW("%s: No space for jpeg header at offset: %u and jpeg size: %u",
          __FUNCTION__, static_cast<unsigned>(jpeg_header_offset),
          static_cast<unsigned>(encoded_size));
  }
}

size_t JpegCompressor::CompressYUV420Frame(YUV420Frame frame) {
  ATRACE_CALL();

  struct CustomJpegDestMgr : public jpeg_destination_mgr {
    JOCTET* buffer;
    size_t buffer_size;
    size_t encoded_size;
    bool success;
  } dmgr;

  // Set up error management
  jpeg_error_info_ = NULL;
  jpeg_error_mgr jerr;

  auto cinfo = std::make_unique<jpeg_compress_struct>();
  cinfo->err = jpeg_std_error(&jerr);
  cinfo->err->error_exit = [](j_common_ptr cinfo) {
    (*cinfo->err->output_message)(cinfo);
    if (cinfo->client_data) {
      auto& dmgr = *static_cast<CustomJpegDestMgr*>(cinfo->client_data);
      dmgr.success = false;
    }
  };

  jpeg_create_compress(cinfo.get());
  if (CheckError("Error initializing compression")) {
    return 0;
  }

  dmgr.buffer = static_cast<JOCTET*>(frame.output_buffer);
  dmgr.buffer_size = frame.output_buffer_size;
  dmgr.encoded_size = 0;
  dmgr.success = true;
  cinfo->client_data = static_cast<void*>(&dmgr);
  dmgr.init_destination = [](j_compress_ptr cinfo) {
    auto& dmgr = static_cast<CustomJpegDestMgr&>(*cinfo->dest);
    dmgr.next_output_byte = dmgr.buffer;
    dmgr.free_in_buffer = dmgr.buffer_size;
    ALOGV("%s:%d jpeg start: %p [%zu]", __FUNCTION__, __LINE__, dmgr.buffer,
          dmgr.buffer_size);
  };

  dmgr.empty_output_buffer = [](j_compress_ptr) {
    ALOGE("%s:%d Out of buffer", __FUNCTION__, __LINE__);
    return 0;
  };

  dmgr.term_destination = [](j_compress_ptr cinfo) {
    auto& dmgr = static_cast<CustomJpegDestMgr&>(*cinfo->dest);
    dmgr.encoded_size = dmgr.buffer_size - dmgr.free_in_buffer;
    ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__,
          dmgr.encoded_size);
  };

  cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(&dmgr);

  // Set up compression parameters
  cinfo->image_width = frame.width;
  cinfo->image_height = frame.height;
  cinfo->input_components = 3;
  cinfo->in_color_space = JCS_YCbCr;

  jpeg_set_defaults(cinfo.get());
  if (CheckError("Error configuring defaults")) {
    return 0;
  }

  jpeg_set_colorspace(cinfo.get(), JCS_YCbCr);
  if (CheckError("Error configuring color space")) {
    return 0;
  }

  cinfo->raw_data_in = 1;
  // YUV420 planar with chroma subsampling
  cinfo->comp_info[0].h_samp_factor = 2;
  cinfo->comp_info[0].v_samp_factor = 2;
  cinfo->comp_info[1].h_samp_factor = 1;
  cinfo->comp_info[1].v_samp_factor = 1;
  cinfo->comp_info[2].h_samp_factor = 1;
  cinfo->comp_info[2].v_samp_factor = 1;

  int max_vsamp_factor = std::max({cinfo->comp_info[0].v_samp_factor,
                                   cinfo->comp_info[1].v_samp_factor,
                                   cinfo->comp_info[2].v_samp_factor});
  int c_vsub_sampling =
      cinfo->comp_info[0].v_samp_factor / cinfo->comp_info[1].v_samp_factor;

  // Start compression
  jpeg_start_compress(cinfo.get(), TRUE);
  if (CheckError("Error starting compression")) {
    return 0;
  }

  if ((frame.app1_buffer != nullptr) && (frame.app1_buffer_size > 0)) {
    jpeg_write_marker(cinfo.get(), JPEG_APP0 + 1,
                      static_cast<const JOCTET*>(frame.app1_buffer),
                      frame.app1_buffer_size);
  }

  const uint8_t* icc_profile = nullptr;
  size_t icc_profile_size = 0;
  switch (frame.color_space) {
    case 0:  // sRGB
      icc_profile = kIccProfileSrgb;
      icc_profile_size = std::size(kIccProfileSrgb);
      break;
    case 7:  // DISPLAY_P3
      icc_profile = kIccProfileDisplayP3;
      icc_profile_size = std::size(kIccProfileDisplayP3);
      break;
    case 6:  // DCI_P3
      icc_profile = kIccProfileDciP3;
      icc_profile_size = std::size(kIccProfileDciP3);
      break;
  }

  if (icc_profile != nullptr && icc_profile_size > 0) {
    jpeg_write_icc_profile(cinfo.get(), static_cast<const JOCTET*>(icc_profile),
                           icc_profile_size);
  }

  // Compute our macroblock height, so we can pad our input to be vertically
  // macroblock aligned.

  size_t mcu_v = DCTSIZE * max_vsamp_factor;
  size_t padded_height = mcu_v * ((cinfo->image_height + mcu_v - 1) / mcu_v);

  std::vector<JSAMPROW> y_lines(padded_height);
  std::vector<JSAMPROW> cb_lines(padded_height / c_vsub_sampling);
  std::vector<JSAMPROW> cr_lines(padded_height / c_vsub_sampling);

  uint8_t* py = static_cast<uint8_t*>(frame.yuv_planes.img_y);
  uint8_t* pcr = static_cast<uint8_t*>(frame.yuv_planes.img_cr);
  uint8_t* pcb = static_cast<uint8_t*>(frame.yuv_planes.img_cb);

  for (uint32_t i = 0; i < padded_height; i++) {
    /* Once we are in the padding territory we still point to the last line
     * effectively replicating it several times ~ CLAMP_TO_EDGE */
    int li = std::min(i, cinfo->image_height - 1);
    y_lines[i] = static_cast<JSAMPROW>(py + li * frame.yuv_planes.y_stride);
    if (i < padded_height / c_vsub_sampling) {
      li = std::min(i, (cinfo->image_height - 1) / c_vsub_sampling);
      cr_lines[i] =
          static_cast<JSAMPROW>(pcr + li * frame.yuv_planes.cbcr_stride);
      cb_lines[i] =
          static_cast<JSAMPROW>(pcb + li * frame.yuv_planes.cbcr_stride);
    }
  }

  const uint32_t batch_size = DCTSIZE * max_vsamp_factor;
  while (cinfo->next_scanline < cinfo->image_height) {
    JSAMPARRAY planes[3]{&y_lines[cinfo->next_scanline],
                         &cb_lines[cinfo->next_scanline / c_vsub_sampling],
                         &cr_lines[cinfo->next_scanline / c_vsub_sampling]};

    jpeg_write_raw_data(cinfo.get(), planes, batch_size);
    if (CheckError("Error while compressing")) {
      return 0;
    }

    if (jpeg_done_) {
      ALOGV("%s: Cancel called, exiting early", __FUNCTION__);
      jpeg_finish_compress(cinfo.get());
      return 0;
    }
  }

  jpeg_finish_compress(cinfo.get());
  if (CheckError("Error while finishing compression")) {
    return 0;
  }

  return dmgr.encoded_size;
}

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

  return false;
}

}  // namespace android
