/*
 * 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 "EmulatedCamera_Scene"
#include "Scene.h"
#include <stdlib.h>
#include <utils/Log.h>
#include <cmath>

// TODO: This should probably be done host-side in OpenGL for speed and better
// quality

namespace android {

// Define single-letter shortcuts for scene definition, for directly indexing
// mCurrentColors
#define G (Scene::GRASS * Scene::NUM_CHANNELS)
#define S (Scene::GRASS_SHADOW * Scene::NUM_CHANNELS)
#define H (Scene::HILL * Scene::NUM_CHANNELS)
#define W (Scene::WALL * Scene::NUM_CHANNELS)
#define R (Scene::ROOF * Scene::NUM_CHANNELS)
#define D (Scene::DOOR * Scene::NUM_CHANNELS)
#define C (Scene::CHIMNEY * Scene::NUM_CHANNELS)
#define I (Scene::WINDOW * Scene::NUM_CHANNELS)
#define U (Scene::SUN * Scene::NUM_CHANNELS)
#define K (Scene::SKY * Scene::NUM_CHANNELS)
#define M (Scene::MOON * Scene::NUM_CHANNELS)

const int Scene::kSceneWidth = 20;
const int Scene::kSceneHeight = 20;

const uint8_t Scene::kScene[Scene::kSceneWidth * Scene::kSceneHeight] = {
    //      5         10        15        20
    K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K,
    K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K,
    K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K,
    K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K,
    K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K,  // 5
    K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K, K,
    K, K, K, K, K, K, K, K, H, H, H, H, H, H, H, H, H, H, H, H,
    K, K, K, K, K, K, K, K, H, H, H, H, H, H, H, C, C, H, H, H,
    K, K, K, K, K, K, H, H, H, H, H, H, H, H, H, C, C, H, H, H,
    H, K, K, K, K, K, H, R, R, R, R, R, R, R, R, R, R, R, R, H,  // 10
    H, K, K, K, K, H, H, R, R, R, R, R, R, R, R, R, R, R, R, H,
    H, H, H, K, K, H, H, R, R, R, R, R, R, R, R, R, R, R, R, H,
    H, H, H, K, K, H, H, H, W, W, W, W, W, W, W, W, W, W, H, H,
    S, S, S, G, G, S, S, S, W, W, W, W, W, W, W, W, W, W, S, S,
    S, G, G, G, G, S, S, S, W, I, I, W, D, D, W, I, I, W, S, S,  // 15
    G, G, G, G, G, G, S, S, W, I, I, W, D, D, W, I, I, W, S, S,
    G, G, G, G, G, G, G, G, W, W, W, W, D, D, W, W, W, W, G, G,
    G, G, G, G, G, G, G, G, W, W, W, W, D, D, W, W, W, W, G, G,
    G, G, G, G, G, G, G, G, S, S, S, S, S, S, S, S, S, S, G, G,
    G, G, G, G, G, G, G, G, S, S, S, S, S, S, S, S, S, S, G, G,  // 20
    //      5         10        15        20
};

#undef G
#undef S
#undef H
#undef W
#undef R
#undef D
#undef C
#undef I
#undef U
#undef K
#undef M

Scene::Scene(int sensorWidthPx, int sensorHeightPx, float sensorSensitivity)
    : mSensorWidth(sensorWidthPx),
      mSensorHeight(sensorHeightPx),
      mHour(12),
      mExposureDuration(0.033f),
      mSensorSensitivity(sensorSensitivity) {
  // Map scene to sensor pixels
  if (mSensorWidth > mSensorHeight) {
    mMapDiv = (mSensorWidth / (kSceneWidth + 1)) + 1;
  } else {
    mMapDiv = (mSensorHeight / (kSceneHeight + 1)) + 1;
  }
  mOffsetX = (kSceneWidth * mMapDiv - mSensorWidth) / 2;
  mOffsetY = (kSceneHeight * mMapDiv - mSensorHeight) / 2;

  // Assume that sensor filters are sRGB primaries to start
  mFilterR[0] = 3.2406f;
  mFilterR[1] = -1.5372f;
  mFilterR[2] = -0.4986f;
  mFilterGr[0] = -0.9689f;
  mFilterGr[1] = 1.8758f;
  mFilterGr[2] = 0.0415f;
  mFilterGb[0] = -0.9689f;
  mFilterGb[1] = 1.8758f;
  mFilterGb[2] = 0.0415f;
  mFilterB[0] = 0.0557f;
  mFilterB[1] = -0.2040f;
  mFilterB[2] = 1.0570f;
}

Scene::~Scene() {}

void Scene::setColorFilterXYZ(float rX, float rY, float rZ, float grX,
                              float grY, float grZ, float gbX, float gbY,
                              float gbZ, float bX, float bY, float bZ) {
  mFilterR[0] = rX;
  mFilterR[1] = rY;
  mFilterR[2] = rZ;
  mFilterGr[0] = grX;
  mFilterGr[1] = grY;
  mFilterGr[2] = grZ;
  mFilterGb[0] = gbX;
  mFilterGb[1] = gbY;
  mFilterGb[2] = gbZ;
  mFilterB[0] = bX;
  mFilterB[1] = bY;
  mFilterB[2] = bZ;
}

void Scene::setHour(int hour) {
  ALOGV("Hour set to: %d", hour);
  mHour = hour % 24;
}

int Scene::getHour() { return mHour; }

void Scene::setExposureDuration(float seconds) { mExposureDuration = seconds; }

void Scene::calculateScene(nsecs_t time) {
  // Calculate time fractions for interpolation
  int timeIdx = mHour / kTimeStep;
  int nextTimeIdx = (timeIdx + 1) % (24 / kTimeStep);
  const nsecs_t kOneHourInNsec = 1e9 * 60 * 60;
  nsecs_t timeSinceIdx = (mHour - timeIdx * kTimeStep) * kOneHourInNsec + time;
  float timeFrac = timeSinceIdx / (float)(kOneHourInNsec * kTimeStep);

  // Determine overall sunlight levels
  float sunLux =
      kSunlight[timeIdx] * (1 - timeFrac) + kSunlight[nextTimeIdx] * timeFrac;
  ALOGV("Sun lux: %f", sunLux);

  float sunShadeLux = sunLux * (kDaylightShadeIllum / kDirectSunIllum);

  // Determine sun/shade illumination chromaticity
  float currentSunXY[2];
  float currentShadeXY[2];

  const float *prevSunXY, *nextSunXY;
  const float *prevShadeXY, *nextShadeXY;
  if (kSunlight[timeIdx] == kSunsetIllum ||
      kSunlight[timeIdx] == kTwilightIllum) {
    prevSunXY = kSunsetXY;
    prevShadeXY = kSunsetXY;
  } else {
    prevSunXY = kDirectSunlightXY;
    prevShadeXY = kDaylightXY;
  }
  if (kSunlight[nextTimeIdx] == kSunsetIllum ||
      kSunlight[nextTimeIdx] == kTwilightIllum) {
    nextSunXY = kSunsetXY;
    nextShadeXY = kSunsetXY;
  } else {
    nextSunXY = kDirectSunlightXY;
    nextShadeXY = kDaylightXY;
  }
  currentSunXY[0] = prevSunXY[0] * (1 - timeFrac) + nextSunXY[0] * timeFrac;
  currentSunXY[1] = prevSunXY[1] * (1 - timeFrac) + nextSunXY[1] * timeFrac;

  currentShadeXY[0] =
      prevShadeXY[0] * (1 - timeFrac) + nextShadeXY[0] * timeFrac;
  currentShadeXY[1] =
      prevShadeXY[1] * (1 - timeFrac) + nextShadeXY[1] * timeFrac;

  ALOGV("Sun XY: %f, %f, Shade XY: %f, %f", currentSunXY[0], currentSunXY[1],
        currentShadeXY[0], currentShadeXY[1]);

  // Converting for xyY to XYZ:
  // X = Y / y * x
  // Y = Y
  // Z = Y / y * (1 - x - y);
  float sunXYZ[3] = {
      sunLux / currentSunXY[1] * currentSunXY[0], sunLux,
      sunLux / currentSunXY[1] * (1 - currentSunXY[0] - currentSunXY[1])};
  float sunShadeXYZ[3] = {sunShadeLux / currentShadeXY[1] * currentShadeXY[0],
                          sunShadeLux,
                          sunShadeLux / currentShadeXY[1] *
                              (1 - currentShadeXY[0] - currentShadeXY[1])};
  ALOGV("Sun XYZ: %f, %f, %f", sunXYZ[0], sunXYZ[1], sunXYZ[2]);
  ALOGV("Sun shade XYZ: %f, %f, %f", sunShadeXYZ[0], sunShadeXYZ[1],
        sunShadeXYZ[2]);

  // Determine moonlight levels
  float moonLux =
      kMoonlight[timeIdx] * (1 - timeFrac) + kMoonlight[nextTimeIdx] * timeFrac;
  float moonShadeLux = moonLux * (kDaylightShadeIllum / kDirectSunIllum);

  float moonXYZ[3] = {
      moonLux / kMoonlightXY[1] * kMoonlightXY[0], moonLux,
      moonLux / kMoonlightXY[1] * (1 - kMoonlightXY[0] - kMoonlightXY[1])};
  float moonShadeXYZ[3] = {
      moonShadeLux / kMoonlightXY[1] * kMoonlightXY[0], moonShadeLux,
      moonShadeLux / kMoonlightXY[1] * (1 - kMoonlightXY[0] - kMoonlightXY[1])};

  // Determine starlight level
  const float kClearNightXYZ[3] = {
      kClearNightIllum / kMoonlightXY[1] * kMoonlightXY[0], kClearNightIllum,
      kClearNightIllum / kMoonlightXY[1] *
          (1 - kMoonlightXY[0] - kMoonlightXY[1])};

  // Calculate direct and shaded light
  float directIllumXYZ[3] = {
      sunXYZ[0] + moonXYZ[0] + kClearNightXYZ[0],
      sunXYZ[1] + moonXYZ[1] + kClearNightXYZ[1],
      sunXYZ[2] + moonXYZ[2] + kClearNightXYZ[2],
  };

  float shadeIllumXYZ[3] = {kClearNightXYZ[0], kClearNightXYZ[1],
                            kClearNightXYZ[2]};

  shadeIllumXYZ[0] += (mHour < kSunOverhead) ? sunXYZ[0] : sunShadeXYZ[0];
  shadeIllumXYZ[1] += (mHour < kSunOverhead) ? sunXYZ[1] : sunShadeXYZ[1];
  shadeIllumXYZ[2] += (mHour < kSunOverhead) ? sunXYZ[2] : sunShadeXYZ[2];

  // Moon up period covers 23->0 transition, shift for simplicity
  int adjHour = (mHour + 12) % 24;
  int adjMoonOverhead = (kMoonOverhead + 12) % 24;
  shadeIllumXYZ[0] +=
      (adjHour < adjMoonOverhead) ? moonXYZ[0] : moonShadeXYZ[0];
  shadeIllumXYZ[1] +=
      (adjHour < adjMoonOverhead) ? moonXYZ[1] : moonShadeXYZ[1];
  shadeIllumXYZ[2] +=
      (adjHour < adjMoonOverhead) ? moonXYZ[2] : moonShadeXYZ[2];

  ALOGV("Direct XYZ: %f, %f, %f", directIllumXYZ[0], directIllumXYZ[1],
        directIllumXYZ[2]);
  ALOGV("Shade XYZ: %f, %f, %f", shadeIllumXYZ[0], shadeIllumXYZ[1],
        shadeIllumXYZ[2]);

  for (int i = 0; i < NUM_MATERIALS; i++) {
    // Converting for xyY to XYZ:
    // X = Y / y * x
    // Y = Y
    // Z = Y / y * (1 - x - y);
    float matXYZ[3] = {
        kMaterials_xyY[i][2] / kMaterials_xyY[i][1] * kMaterials_xyY[i][0],
        kMaterials_xyY[i][2],
        kMaterials_xyY[i][2] / kMaterials_xyY[i][1] *
            (1 - kMaterials_xyY[i][0] - kMaterials_xyY[i][1])};

    if (kMaterialsFlags[i] == 0 || kMaterialsFlags[i] & kSky) {
      matXYZ[0] *= directIllumXYZ[0];
      matXYZ[1] *= directIllumXYZ[1];
      matXYZ[2] *= directIllumXYZ[2];
    } else if (kMaterialsFlags[i] & kShadowed) {
      matXYZ[0] *= shadeIllumXYZ[0];
      matXYZ[1] *= shadeIllumXYZ[1];
      matXYZ[2] *= shadeIllumXYZ[2];
    }  // else if (kMaterialsFlags[i] * kSelfLit), do nothing

    ALOGV("Mat %d XYZ: %f, %f, %f", i, matXYZ[0], matXYZ[1], matXYZ[2]);
    float luxToElectrons =
        mSensorSensitivity * mExposureDuration / (kAperture * kAperture);
    mCurrentColors[i * NUM_CHANNELS + 0] =
        (mFilterR[0] * matXYZ[0] + mFilterR[1] * matXYZ[1] +
         mFilterR[2] * matXYZ[2]) *
        luxToElectrons;
    mCurrentColors[i * NUM_CHANNELS + 1] =
        (mFilterGr[0] * matXYZ[0] + mFilterGr[1] * matXYZ[1] +
         mFilterGr[2] * matXYZ[2]) *
        luxToElectrons;
    mCurrentColors[i * NUM_CHANNELS + 2] =
        (mFilterGb[0] * matXYZ[0] + mFilterGb[1] * matXYZ[1] +
         mFilterGb[2] * matXYZ[2]) *
        luxToElectrons;
    mCurrentColors[i * NUM_CHANNELS + 3] =
        (mFilterB[0] * matXYZ[0] + mFilterB[1] * matXYZ[1] +
         mFilterB[2] * matXYZ[2]) *
        luxToElectrons;

    ALOGV("Color %d RGGB: %d, %d, %d, %d", i,
          mCurrentColors[i * NUM_CHANNELS + 0],
          mCurrentColors[i * NUM_CHANNELS + 1],
          mCurrentColors[i * NUM_CHANNELS + 2],
          mCurrentColors[i * NUM_CHANNELS + 3]);
  }
  // Shake viewpoint; horizontal and vertical sinusoids at roughly
  // human handshake frequencies
  mHandshakeX = (kFreq1Magnitude * std::sin(kHorizShakeFreq1 * timeSinceIdx) +
                 kFreq2Magnitude * std::sin(kHorizShakeFreq2 * timeSinceIdx)) *
                mMapDiv * kShakeFraction;

  mHandshakeY = (kFreq1Magnitude * std::sin(kVertShakeFreq1 * timeSinceIdx) +
                 kFreq2Magnitude * std::sin(kVertShakeFreq2 * timeSinceIdx)) *
                mMapDiv * kShakeFraction;

  // Set starting pixel
  setReadoutPixel(0, 0);
}

void Scene::setReadoutPixel(int x, int y) {
  mCurrentX = x;
  mCurrentY = y;
  mSubX = (x + mOffsetX + mHandshakeX) % mMapDiv;
  mSubY = (y + mOffsetY + mHandshakeY) % mMapDiv;
  mSceneX = (x + mOffsetX + mHandshakeX) / mMapDiv;
  mSceneY = (y + mOffsetY + mHandshakeY) / mMapDiv;
  mSceneIdx = mSceneY * kSceneWidth + mSceneX;
  mCurrentSceneMaterial = &(mCurrentColors[kScene[mSceneIdx]]);
}

const uint32_t *Scene::getPixelElectrons() {
  const uint32_t *pixel = mCurrentSceneMaterial;
  mCurrentX++;
  mSubX++;
  if (mCurrentX >= mSensorWidth) {
    mCurrentX = 0;
    mCurrentY++;
    if (mCurrentY >= mSensorHeight) mCurrentY = 0;
    setReadoutPixel(mCurrentX, mCurrentY);
  } else if (mSubX > mMapDiv) {
    mSceneIdx++;
    mSceneX++;
    mCurrentSceneMaterial = &(mCurrentColors[kScene[mSceneIdx]]);
    mSubX = 0;
  }
  return pixel;
}

// Handshake model constants.
// Frequencies measured in a nanosecond timebase
const float Scene::kHorizShakeFreq1 = 2 * M_PI * 2 / 1e9;   // 2 Hz
const float Scene::kHorizShakeFreq2 = 2 * M_PI * 13 / 1e9;  // 13 Hz
const float Scene::kVertShakeFreq1 = 2 * M_PI * 3 / 1e9;    // 3 Hz
const float Scene::kVertShakeFreq2 = 2 * M_PI * 11 / 1e9;   // 1 Hz
const float Scene::kFreq1Magnitude = 5;
const float Scene::kFreq2Magnitude = 1;
const float Scene::kShakeFraction = 0.03;  // As a fraction of a scene tile

// RGB->YUV, Jpeg standard
const float Scene::kRgb2Yuv[12] = {
    0.299f, 0.587f, 0.114f, 0.f,       -0.16874f, -0.33126f,
    0.5f,   -128.f, 0.5f,   -0.41869f, -0.08131f, -128.f,
};

// Aperture of imaging lens
const float Scene::kAperture = 2.8;

// Sun illumination levels through the day
const float Scene::kSunlight[24 / kTimeStep] = {0,  // 00:00
                                                0,
                                                0,
                                                kTwilightIllum,  // 06:00
                                                kDirectSunIllum,
                                                kDirectSunIllum,
                                                kDirectSunIllum,  // 12:00
                                                kDirectSunIllum,
                                                kDirectSunIllum,
                                                kSunsetIllum,  // 18:00
                                                kTwilightIllum,
                                                0};

// Moon illumination levels through the day
const float Scene::kMoonlight[24 / kTimeStep] = {kFullMoonIllum,  // 00:00
                                                 kFullMoonIllum,
                                                 0,
                                                 0,  // 06:00
                                                 0,
                                                 0,
                                                 0,  // 12:00
                                                 0,
                                                 0,
                                                 0,  // 18:00
                                                 0,
                                                 kFullMoonIllum};

const int Scene::kSunOverhead = 12;
const int Scene::kMoonOverhead = 0;

// Used for sun illumination levels
const float Scene::kDirectSunIllum = 100000;
const float Scene::kSunsetIllum = 400;
const float Scene::kTwilightIllum = 4;
// Used for moon illumination levels
const float Scene::kFullMoonIllum = 1;
// Other illumination levels
const float Scene::kDaylightShadeIllum = 20000;
const float Scene::kClearNightIllum = 2e-3;
const float Scene::kStarIllum = 2e-6;
const float Scene::kLivingRoomIllum = 50;

const float Scene::kIncandescentXY[2] = {0.44757f, 0.40745f};
const float Scene::kDirectSunlightXY[2] = {0.34842f, 0.35161f};
const float Scene::kDaylightXY[2] = {0.31271f, 0.32902f};
const float Scene::kNoonSkyXY[2] = {0.346f, 0.359f};
const float Scene::kMoonlightXY[2] = {0.34842f, 0.35161f};
const float Scene::kSunsetXY[2] = {0.527f, 0.413f};

const uint8_t Scene::kSelfLit = 0x01;
const uint8_t Scene::kShadowed = 0x02;
const uint8_t Scene::kSky = 0x04;

// For non-self-lit materials, the Y component is normalized with 1=full
// reflectance; for self-lit materials, it's the constant illuminance in lux.
const float Scene::kMaterials_xyY[Scene::NUM_MATERIALS][3] = {
    {0.3688f, 0.4501f, .1329f},                                  // GRASS
    {0.3688f, 0.4501f, .1329f},                                  // GRASS_SHADOW
    {0.3986f, 0.5002f, .4440f},                                  // HILL
    {0.3262f, 0.5040f, .2297f},                                  // WALL
    {0.4336f, 0.3787f, .1029f},                                  // ROOF
    {0.3316f, 0.2544f, .0639f},                                  // DOOR
    {0.3425f, 0.3577f, .0887f},                                  // CHIMNEY
    {kIncandescentXY[0], kIncandescentXY[1], kLivingRoomIllum},  // WINDOW
    {kDirectSunlightXY[0], kDirectSunlightXY[1], kDirectSunIllum},  // SUN
    {kNoonSkyXY[0], kNoonSkyXY[1],
     kDaylightShadeIllum / kDirectSunIllum},            // SKY
    {kMoonlightXY[0], kMoonlightXY[1], kFullMoonIllum}  // MOON
};

const uint8_t Scene::kMaterialsFlags[Scene::NUM_MATERIALS] = {
    0,         kShadowed, kShadowed, kShadowed, kShadowed, kShadowed,
    kShadowed, kSelfLit,  kSelfLit,  kSky,      kSelfLit,
};

}  // namespace android