/*
 * 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_NNDEBUG 0
#define LOG_TAG "EmulatedCamera2_Sensor"

#ifdef LOG_NNDEBUG
#define ALOGVV(...) ALOGV(__VA_ARGS__)
#else
#define ALOGVV(...) ((void)0)
#endif

#include <utils/Log.h>

#include <cmath>
#include <cstdlib>
#include "../EmulatedFakeCamera2.h"
#include "Sensor.h"
#include "guest/libs/platform_support/api_level_fixes.h"
#include "system/camera_metadata.h"

namespace android {

// const nsecs_t Sensor::kExposureTimeRange[2] =
//    {1000L, 30000000000L} ; // 1 us - 30 sec
// const nsecs_t Sensor::kFrameDurationRange[2] =
//    {33331760L, 30000000000L}; // ~1/30 s - 30 sec
const nsecs_t Sensor::kExposureTimeRange[2] = {1000L,
                                               300000000L};  // 1 us - 0.3 sec
const nsecs_t Sensor::kFrameDurationRange[2] = {
    33331760L, 300000000L};  // ~1/30 s - 0.3 sec

const nsecs_t Sensor::kMinVerticalBlank = 10000L;

const uint8_t Sensor::kColorFilterArrangement =
    ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB;

// Output image data characteristics
const uint32_t Sensor::kMaxRawValue = 4000;
const uint32_t Sensor::kBlackLevel = 1000;

// Sensor sensitivity
const float Sensor::kSaturationVoltage = 0.520f;
const uint32_t Sensor::kSaturationElectrons = 2000;
const float Sensor::kVoltsPerLuxSecond = 0.100f;

const float Sensor::kElectronsPerLuxSecond = Sensor::kSaturationElectrons /
                                             Sensor::kSaturationVoltage *
                                             Sensor::kVoltsPerLuxSecond;

const float Sensor::kBaseGainFactor =
    (float)Sensor::kMaxRawValue / Sensor::kSaturationElectrons;

const float Sensor::kReadNoiseStddevBeforeGain = 1.177;  // in electrons
const float Sensor::kReadNoiseStddevAfterGain = 2.100;   // in digital counts
const float Sensor::kReadNoiseVarBeforeGain =
    Sensor::kReadNoiseStddevBeforeGain * Sensor::kReadNoiseStddevBeforeGain;
const float Sensor::kReadNoiseVarAfterGain =
    Sensor::kReadNoiseStddevAfterGain * Sensor::kReadNoiseStddevAfterGain;

const int32_t Sensor::kSensitivityRange[2] = {100, 1600};
const uint32_t Sensor::kDefaultSensitivity = 100;

/** A few utility functions for math, normal distributions */

// Take advantage of IEEE floating-point format to calculate an approximate
// square root. Accurate to within +-3.6%
float sqrtf_approx(float r) {
  // Modifier is based on IEEE floating-point representation; the
  // manipulations boil down to finding approximate log2, dividing by two, and
  // then inverting the log2. A bias is added to make the relative error
  // symmetric about the real answer.
  const int32_t modifier = 0x1FBB4000;

  int32_t r_i = *(int32_t *)(&r);
  r_i = (r_i >> 1) + modifier;

  return *(float *)(&r_i);
}

Sensor::Sensor(uint32_t width, uint32_t height)
    : Thread(false),
      mResolution{width, height},
      mActiveArray{0, 0, width, height},
      mRowReadoutTime(kFrameDurationRange[0] / height),
      mGotVSync(false),
      mExposureTime(kFrameDurationRange[0] - kMinVerticalBlank),
      mFrameDuration(kFrameDurationRange[0]),
      mGainFactor(kDefaultSensitivity),
      mNextBuffers(NULL),
      mFrameNumber(0),
      mCapturedBuffers(NULL),
      mListener(NULL),
      mScene(width, height, kElectronsPerLuxSecond) {
  ALOGV("Sensor created with pixel array %d x %d", width, height);
}

Sensor::~Sensor() { shutDown(); }

status_t Sensor::startUp() {
  ALOGV("%s: E", __FUNCTION__);

  int res;
  mCapturedBuffers = NULL;
  res = run("EmulatedFakeCamera2::Sensor", ANDROID_PRIORITY_URGENT_DISPLAY);

  if (res != OK) {
    ALOGE("Unable to start up sensor capture thread: %d", res);
  }
  return res;
}

status_t Sensor::shutDown() {
  ALOGV("%s: E", __FUNCTION__);

  int res;
  res = requestExitAndWait();
  if (res != OK) {
    ALOGE("Unable to shut down sensor capture thread: %d", res);
  }
  return res;
}

Scene &Sensor::getScene() { return mScene; }

void Sensor::setExposureTime(uint64_t ns) {
  Mutex::Autolock lock(mControlMutex);
  ALOGVV("Exposure set to %f", ns / 1000000.f);
  mExposureTime = ns;
}

void Sensor::setFrameDuration(uint64_t ns) {
  Mutex::Autolock lock(mControlMutex);
  ALOGVV("Frame duration set to %f", ns / 1000000.f);
  mFrameDuration = ns;
}

void Sensor::setSensitivity(uint32_t gain) {
  Mutex::Autolock lock(mControlMutex);
  ALOGVV("Gain set to %d", gain);
  mGainFactor = gain;
}

void Sensor::setDestinationBuffers(Buffers *buffers) {
  Mutex::Autolock lock(mControlMutex);
  mNextBuffers = buffers;
}

void Sensor::setFrameNumber(uint32_t frameNumber) {
  Mutex::Autolock lock(mControlMutex);
  mFrameNumber = frameNumber;
}

bool Sensor::waitForVSync(nsecs_t reltime) {
  int res;
  Mutex::Autolock lock(mControlMutex);

  mGotVSync = false;
  res = mVSync.waitRelative(mControlMutex, reltime);
  if (res != OK && res != TIMED_OUT) {
    ALOGE("%s: Error waiting for VSync signal: %d", __FUNCTION__, res);
    return false;
  }
  return mGotVSync;
}

bool Sensor::waitForNewFrame(nsecs_t reltime, nsecs_t *captureTime) {
  Mutex::Autolock lock(mReadoutMutex);

  if (mCapturedBuffers == NULL) {
    int res;
    res = mReadoutAvailable.waitRelative(mReadoutMutex, reltime);
    if (res == TIMED_OUT) {
      return false;
    } else if (res != OK || mCapturedBuffers == NULL) {
      ALOGE("Error waiting for sensor readout signal: %d", res);
      return false;
    }
  }
  mReadoutComplete.signal();

  *captureTime = mCaptureTime;
  mCapturedBuffers = NULL;
  return true;
}

Sensor::SensorListener::~SensorListener() {}

void Sensor::setSensorListener(SensorListener *listener) {
  Mutex::Autolock lock(mControlMutex);
  mListener = listener;
}

status_t Sensor::readyToRun() {
  ALOGV("Starting up sensor thread");
  mStartupTime = systemTime();
  mNextCaptureTime = 0;
  mNextCapturedBuffers = NULL;
  return OK;
}

bool Sensor::threadLoop() {
  /**
   * Sensor capture operation main loop.
   *
   * Stages are out-of-order relative to a single frame's processing, but
   * in-order in time.
   */

  /**
   * Stage 1: Read in latest control parameters
   */
  uint64_t exposureDuration;
  uint64_t frameDuration;
  uint32_t gain;
  Buffers *nextBuffers;
  uint32_t frameNumber;
  SensorListener *listener = NULL;
  {
    Mutex::Autolock lock(mControlMutex);
    exposureDuration = mExposureTime;
    frameDuration = mFrameDuration;
    gain = mGainFactor;
    nextBuffers = mNextBuffers;
    frameNumber = mFrameNumber;
    listener = mListener;
    // Don't reuse a buffer set
    mNextBuffers = NULL;

    // Signal VSync for start of readout
    ALOGVV("Sensor VSync");
    mGotVSync = true;
    mVSync.signal();
  }

  /**
   * Stage 3: Read out latest captured image
   */

  Buffers *capturedBuffers = NULL;
  nsecs_t captureTime = 0;

  nsecs_t startRealTime = systemTime();
  // Stagefright cares about system time for timestamps, so base simulated
  // time on that.
  nsecs_t simulatedTime = startRealTime;
  nsecs_t frameEndRealTime = startRealTime + frameDuration;

  if (mNextCapturedBuffers != NULL) {
    ALOGVV("Sensor starting readout");
    // Pretend we're doing readout now; will signal once enough time has elapsed
    capturedBuffers = mNextCapturedBuffers;
    captureTime = mNextCaptureTime;
  }
  simulatedTime += mRowReadoutTime + kMinVerticalBlank;

  // TODO: Move this signal to another thread to simulate readout
  // time properly
  if (capturedBuffers != NULL) {
    ALOGVV("Sensor readout complete");
    Mutex::Autolock lock(mReadoutMutex);
    if (mCapturedBuffers != NULL) {
      ALOGV("Waiting for readout thread to catch up!");
      mReadoutComplete.wait(mReadoutMutex);
    }

    mCapturedBuffers = capturedBuffers;
    mCaptureTime = captureTime;
    mReadoutAvailable.signal();
    capturedBuffers = NULL;
  }

  /**
   * Stage 2: Capture new image
   */
  mNextCaptureTime = simulatedTime;
  mNextCapturedBuffers = nextBuffers;

  if (mNextCapturedBuffers != NULL) {
    if (listener != NULL) {
      listener->onSensorEvent(frameNumber, SensorListener::EXPOSURE_START,
                              mNextCaptureTime);
    }
    ALOGVV("Starting next capture: Exposure: %f ms, gain: %d",
           (float)exposureDuration / 1e6, gain);
    mScene.setExposureDuration((float)exposureDuration / 1e9);
    mScene.calculateScene(mNextCaptureTime);

    // Might be adding more buffers, so size isn't constant
    for (size_t i = 0; i < mNextCapturedBuffers->size(); i++) {
      const StreamBuffer &b = (*mNextCapturedBuffers)[i];
      ALOGVV(
          "Sensor capturing buffer %d: stream %d,"
          " %d x %d, format %x, stride %d, buf %p, img %p",
          i, b.streamId, b.width, b.height, b.format, b.stride, b.buffer,
          b.img);
      switch (b.format) {
#if VSOC_PLATFORM_SDK_AFTER(K)
        case HAL_PIXEL_FORMAT_RAW16:
          captureRaw(b.img, gain, b.stride);
          break;
#endif
        case HAL_PIXEL_FORMAT_RGB_888:
          captureRGB(b.img, gain, b.stride);
          break;
        case HAL_PIXEL_FORMAT_RGBA_8888:
          captureRGBA(b.img, gain, b.stride);
          break;
        case HAL_PIXEL_FORMAT_BLOB:
#if defined HAL_DATASPACE_DEPTH
          if (b.dataSpace != HAL_DATASPACE_DEPTH) {
#endif
            // Add auxillary buffer of the right size
            // Assumes only one BLOB (JPEG) buffer in
            // mNextCapturedBuffers
            StreamBuffer bAux;
            bAux.streamId = 0;
            bAux.width = b.width;
            bAux.height = b.height;
            bAux.format = HAL_PIXEL_FORMAT_RGB_888;
            bAux.stride = b.width;
            bAux.buffer = NULL;
            // TODO: Reuse these
            bAux.img = new uint8_t[b.width * b.height * 3];
            mNextCapturedBuffers->push_back(bAux);
#if defined HAL_DATASPACE_DEPTH
          } else {
            captureDepthCloud(b.img);
          }
#endif
          break;
        case HAL_PIXEL_FORMAT_YCrCb_420_SP:
        case HAL_PIXEL_FORMAT_YCbCr_420_888:
          captureNV21(b.img, gain, b.stride);
          break;
        case HAL_PIXEL_FORMAT_YV12:
          // TODO:
          ALOGE("%s: Format %x is TODO", __FUNCTION__, b.format);
          break;
        case HAL_PIXEL_FORMAT_Y16:
          captureDepth(b.img, gain, b.stride);
          break;
        default:
          ALOGE("%s: Unknown format %x, no output", __FUNCTION__, b.format);
          break;
      }
    }
  }

  ALOGVV("Sensor vertical blanking interval");
  nsecs_t workDoneRealTime = systemTime();
  const nsecs_t timeAccuracy = 2e6;  // 2 ms of imprecision is ok
  if (workDoneRealTime < frameEndRealTime - timeAccuracy) {
    timespec t;
    t.tv_sec = (frameEndRealTime - workDoneRealTime) / 1000000000L;
    t.tv_nsec = (frameEndRealTime - workDoneRealTime) % 1000000000L;

    int ret;
    do {
      ret = nanosleep(&t, &t);
    } while (ret != 0);
  }
  nsecs_t endRealTime __unused = systemTime();
  ALOGVV("Frame cycle took %d ms, target %d ms",
         (int)((endRealTime - startRealTime) / 1000000),
         (int)(frameDuration / 1000000));
  return true;
};

void Sensor::captureRaw(uint8_t *img, uint32_t gain, uint32_t stride) {
  float totalGain = gain / 100.0 * kBaseGainFactor;
  float noiseVarGain = totalGain * totalGain;
  float readNoiseVar =
      kReadNoiseVarBeforeGain * noiseVarGain + kReadNoiseVarAfterGain;

  int bayerSelect[4] = {Scene::R, Scene::Gr, Scene::Gb, Scene::B};  // RGGB
  mScene.setReadoutPixel(0, 0);
  for (unsigned int y = 0; y < mResolution[1]; y++) {
    int *bayerRow = bayerSelect + (y & 0x1) * 2;
    uint16_t *px = (uint16_t *)img + y * stride;
    for (unsigned int x = 0; x < mResolution[0]; x++) {
      uint32_t electronCount;
      electronCount = mScene.getPixelElectrons()[bayerRow[x & 0x1]];

      // TODO: Better pixel saturation curve?
      electronCount = (electronCount < kSaturationElectrons)
                          ? electronCount
                          : kSaturationElectrons;

      // TODO: Better A/D saturation curve?
      uint16_t rawCount = electronCount * totalGain;
      rawCount = (rawCount < kMaxRawValue) ? rawCount : kMaxRawValue;

      // Calculate noise value
      // TODO: Use more-correct Gaussian instead of uniform noise
      float photonNoiseVar = electronCount * noiseVarGain;
      float noiseStddev = sqrtf_approx(readNoiseVar + photonNoiseVar);
      // Scaled to roughly match gaussian/uniform noise stddev
      float noiseSample = std::rand() * (2.5 / (1.0 + RAND_MAX)) - 1.25;

      rawCount += kBlackLevel;
      rawCount += noiseStddev * noiseSample;

      *px++ = rawCount;
    }
    // TODO: Handle this better
    // simulatedTime += mRowReadoutTime;
  }
  ALOGVV("Raw sensor image captured");
}

void Sensor::captureRGBA(uint8_t *img, uint32_t gain, uint32_t stride) {
  float totalGain = gain / 100.0 * kBaseGainFactor;
  // In fixed-point math, calculate total scaling from electrons to 8bpp
  int scale64x = 64 * totalGain * 255 / kMaxRawValue;
  uint32_t inc = ceil((float)mResolution[0] / stride);

  for (unsigned int y = 0, outY = 0; y < mResolution[1]; y += inc, outY++) {
    uint8_t *px = img + outY * stride * 4;
    mScene.setReadoutPixel(0, y);
    for (unsigned int x = 0; x < mResolution[0]; x += inc) {
      uint32_t rCount, gCount, bCount;
      // TODO: Perfect demosaicing is a cheat
      const uint32_t *pixel = mScene.getPixelElectrons();
      rCount = pixel[Scene::R] * scale64x;
      gCount = pixel[Scene::Gr] * scale64x;
      bCount = pixel[Scene::B] * scale64x;

      *px++ = rCount < 255 * 64 ? rCount / 64 : 255;
      *px++ = gCount < 255 * 64 ? gCount / 64 : 255;
      *px++ = bCount < 255 * 64 ? bCount / 64 : 255;
      *px++ = 255;
      for (unsigned int j = 1; j < inc; j++) mScene.getPixelElectrons();
    }
    // TODO: Handle this better
    // simulatedTime += mRowReadoutTime;
  }
  ALOGVV("RGBA sensor image captured");
}

void Sensor::captureRGB(uint8_t *img, uint32_t gain, uint32_t stride) {
  float totalGain = gain / 100.0 * kBaseGainFactor;
  // In fixed-point math, calculate total scaling from electrons to 8bpp
  int scale64x = 64 * totalGain * 255 / kMaxRawValue;
  uint32_t inc = ceil((float)mResolution[0] / stride);

  for (unsigned int y = 0, outY = 0; y < mResolution[1]; y += inc, outY++) {
    mScene.setReadoutPixel(0, y);
    uint8_t *px = img + outY * stride * 3;
    for (unsigned int x = 0; x < mResolution[0]; x += inc) {
      uint32_t rCount, gCount, bCount;
      // TODO: Perfect demosaicing is a cheat
      const uint32_t *pixel = mScene.getPixelElectrons();
      rCount = pixel[Scene::R] * scale64x;
      gCount = pixel[Scene::Gr] * scale64x;
      bCount = pixel[Scene::B] * scale64x;

      *px++ = rCount < 255 * 64 ? rCount / 64 : 255;
      *px++ = gCount < 255 * 64 ? gCount / 64 : 255;
      *px++ = bCount < 255 * 64 ? bCount / 64 : 255;
      for (unsigned int j = 1; j < inc; j++) mScene.getPixelElectrons();
    }
    // TODO: Handle this better
    // simulatedTime += mRowReadoutTime;
  }
  ALOGVV("RGB sensor image captured");
}

void Sensor::captureNV21(uint8_t *img, uint32_t gain, uint32_t stride) {
  float totalGain = gain / 100.0 * kBaseGainFactor;
  // Using fixed-point math with 6 bits of fractional precision.
  // In fixed-point math, calculate total scaling from electrons to 8bpp
  const int scale64x = 64 * totalGain * 255 / kMaxRawValue;
  // In fixed-point math, saturation point of sensor after gain
  const int saturationPoint = 64 * 255;
  // Fixed-point coefficients for RGB-YUV transform
  // Based on JFIF RGB->YUV transform.
  // Cb/Cr offset scaled by 64x twice since they're applied post-multiply
  const int rgbToY[] = {19, 37, 7};
  const int rgbToCb[] = {-10, -21, 32, 524288};
  const int rgbToCr[] = {32, -26, -5, 524288};
  // Scale back to 8bpp non-fixed-point
  const int scaleOut = 64;
  const int scaleOutSq = scaleOut * scaleOut;  // after multiplies

  // inc = how many pixels to skip while reading every next pixel
  // horizontally.
  uint32_t inc = ceil((float)mResolution[0] / stride);
  // outH = projected vertical resolution based on stride.
  uint32_t outH = mResolution[1] / inc;
  for (unsigned int y = 0, outY = 0; y < mResolution[1]; y += inc, outY++) {
    uint8_t *pxY = img + outY * stride;
    uint8_t *pxVU = img + (outH + outY / 2) * stride;
    mScene.setReadoutPixel(0, y);
    for (unsigned int outX = 0; outX < stride; outX++) {
      int32_t rCount, gCount, bCount;
      // TODO: Perfect demosaicing is a cheat
      const uint32_t *pixel = mScene.getPixelElectrons();
      rCount = pixel[Scene::R] * scale64x;
      rCount = rCount < saturationPoint ? rCount : saturationPoint;
      gCount = pixel[Scene::Gr] * scale64x;
      gCount = gCount < saturationPoint ? gCount : saturationPoint;
      bCount = pixel[Scene::B] * scale64x;
      bCount = bCount < saturationPoint ? bCount : saturationPoint;

      *pxY++ = (rgbToY[0] * rCount + rgbToY[1] * gCount + rgbToY[2] * bCount) /
               scaleOutSq;
      if (outY % 2 == 0 && outX % 2 == 0) {
        *pxVU++ = (rgbToCb[0] * rCount + rgbToCb[1] * gCount +
                   rgbToCb[2] * bCount + rgbToCb[3]) /
                  scaleOutSq;
        *pxVU++ = (rgbToCr[0] * rCount + rgbToCr[1] * gCount +
                   rgbToCr[2] * bCount + rgbToCr[3]) /
                  scaleOutSq;
      }

      // Skip unprocessed pixels from sensor.
      for (unsigned int j = 1; j < inc; j++) mScene.getPixelElectrons();
    }
  }
  ALOGVV("NV21 sensor image captured");
}

void Sensor::captureDepth(uint8_t *img, uint32_t gain, uint32_t stride) {
  float totalGain = gain / 100.0 * kBaseGainFactor;
  // In fixed-point math, calculate scaling factor to 13bpp millimeters
  int scale64x = 64 * totalGain * 8191 / kMaxRawValue;
  uint32_t inc = ceil((float)mResolution[0] / stride);

  for (unsigned int y = 0, outY = 0; y < mResolution[1]; y += inc, outY++) {
    mScene.setReadoutPixel(0, y);
    uint16_t *px = ((uint16_t *)img) + outY * stride;
    for (unsigned int x = 0; x < mResolution[0]; x += inc) {
      uint32_t depthCount;
      // TODO: Make up real depth scene instead of using green channel
      // as depth
      const uint32_t *pixel = mScene.getPixelElectrons();
      depthCount = pixel[Scene::Gr] * scale64x;

      *px++ = depthCount < 8191 * 64 ? depthCount / 64 : 0;
      for (unsigned int j = 1; j < inc; j++) mScene.getPixelElectrons();
    }
    // TODO: Handle this better
    // simulatedTime += mRowReadoutTime;
  }
  ALOGVV("Depth sensor image captured");
}

void Sensor::captureDepthCloud(uint8_t * /*img*/) {
#if defined HAL_DATASPACE_DEPTH
  android_depth_points *cloud = reinterpret_cast<android_depth_points *>(img);

  cloud->num_points = 16;

  // TODO: Create point cloud values that match RGB scene
  const int FLOATS_PER_POINT = 4;
  const float JITTER_STDDEV = 0.1f;
  for (size_t y = 0, i = 0; y < 4; y++) {
    for (size_t x = 0; x < 4; x++, i++) {
      float randSampleX = std::rand() * (2.5f / (1.0f + RAND_MAX)) - 1.25f;
      randSampleX *= JITTER_STDDEV;

      float randSampleY = std::rand() * (2.5f / (1.0f + RAND_MAX)) - 1.25f;
      randSampleY *= JITTER_STDDEV;

      float randSampleZ = std::rand() * (2.5f / (1.0f + RAND_MAX)) - 1.25f;
      randSampleZ *= JITTER_STDDEV;

      cloud->xyzc_points[i * FLOATS_PER_POINT + 0] = x - 1.5f + randSampleX;
      cloud->xyzc_points[i * FLOATS_PER_POINT + 1] = y - 1.5f + randSampleY;
      cloud->xyzc_points[i * FLOATS_PER_POINT + 2] = 3.f + randSampleZ;
      cloud->xyzc_points[i * FLOATS_PER_POINT + 3] = 0.8f;
    }
  }

  ALOGVV("Depth point cloud captured");
#endif
}

}  // namespace android