/* adaptation.c: estimate static adaptation for tone mapping */

#include "adaptation.h"
#include "tonemapping.h"
#include "statistics.h"
#include "error.h"
#include "scene.h"
#include "radiance.h"
#include "raytracing.h"
#include "raycasting.h"
#include "render.h"


/* ---------------------------------------------------------------------------
  `LUMAREA'

  Stores luminance-area pairs for median area-weighted luminance
  selection.
  ------------------------------------------------------------------------- */
typedef struct
{
  float luminance;
  float area;
} LUMAREA;


static int _lumAreaComp(const void *la1, const void *la2)
{
  float l1 = ((LUMAREA*)la1)->luminance;
  float l2 = ((LUMAREA*)la2)->luminance;
  return l1 > l2 ? 1 : l1 == l2 ? 0 : -1;
}

/* ---------------------------------------------------------------------------
  `MeanAreaWeightedLuminance'

  Computes the static adaptation luminance value chosing the median value
  of area-weighted luminance values. Needs correct value of "total_area".
  ------------------------------------------------------------------------- */
float MeanAreaWeightedLuminance(LUMAREA *pairs, int numPairs)
{
  extern double total_area;
  float areaMax = total_area/2.0;
  float areaCnt = 0.0;

  qsort((void*)pairs, numPairs, sizeof(LUMAREA), _lumAreaComp);

  while (areaCnt < areaMax)
  {
    areaCnt += pairs->area;
    pairs++;
  }

  pairs--;

  return pairs->luminance;
}

/* Global variables local to this module */
static int _numEntries;
static double _logAreaLum, _totalLogArea;
static LUMAREA * _lumArea; 
static float _lumMin = HUGE;
static float _lumMax = 0.0;

/* a-priori estimate of a patch's radiance. */
static COLOR InitRadianceEstimate(PATCH *patch)
{
  COLOR E = PatchAverageEmittance(patch, ALL_COMPONENTS),
        R = PatchAverageNormalAlbedo(patch, BSDF_ALL_COMPONENTS),
        radiance;
  
  COLORPROD(R,estimated_average_radiance,radiance);
  COLORADDSCALED(radiance,(1./M_PI),E,radiance);
  return radiance;
}

static COLOR (*PatchRadianceEstimate)(PATCH *P) = InitRadianceEstimate;

static float PatchBrightnessEstimate(PATCH *patch)
{
  COLOR radiance = PatchRadianceEstimate(patch);
  float brightness = ColorLuminance(radiance);
  if (brightness < EPSILON) brightness = EPSILON;
  return brightness;
}

static void PatchComputeLogAreaLum(PATCH *patch)
{
  float brightness = PatchBrightnessEstimate(patch);
  _logAreaLum += patch->area*log(brightness);
}

static void PatchFillLumArea(PATCH *patch)
{
  float brightness = PatchBrightnessEstimate(patch);

  _lumArea->luminance = brightness;
  _lumArea->area      = patch->area;

  _lumMin = MIN(_lumMin, _lumArea->luminance);
  _lumMax = MAX(_lumMax, _lumArea->luminance);

  _lumArea++;
  _numEntries++;
}

/* for ReEstimateAdaptation() below */
static int last_is_scene = TRUE;

void EstimateSceneAdaptation(COLOR (*patch_radiance)(PATCH *))
{
  PatchRadianceEstimate = patch_radiance;
  last_is_scene = TRUE;

  switch (tmopts.statadapt) {
  case TMA_NONE:
    break;
  case TMA_AVERAGE:
    {
      /* Gibson's static adpatation after Tumblin[1993] */
      _logAreaLum = 0.0;
      PatchListIterate(Patches, PatchComputeLogAreaLum);
      tmopts.lwa = exp(_logAreaLum/total_area+0.84);
      break;
    }
  case TMA_MEDIAN:
    {
      /* Static adaptation inspired by Tumblin[1999] */
      LUMAREA  *la = malloc(nrpatches*sizeof(LUMAREA));
      
      _lumArea = la;
      PatchListIterate(Patches, PatchFillLumArea);
      tmopts.lwa = MeanAreaWeightedLuminance(la, nrpatches);
      
      free(la);
      break;
    }
  default:
    Error("ComputeSomeSceneStats", "unknown static adaptation method %d", tmopts.statadapt);
  }
}

void InitSceneAdaptation(void)
{
  EstimateSceneAdaptation(InitRadianceEstimate);
}

void EstimateViewAdaptation(void)
{
  last_is_scene = FALSE;

  if(tmopts.statadapt != TMA_NONE)
  {
    if(renderopts.render_raytraced_image && RayTracing)
      tmopts.lwa = RayTracing->AdaptationLuminance(tmopts.pow_bright_adjust);
    else if(Radiance)
      tmopts.lwa = RayCastAdaptationLuminance(tmopts.pow_bright_adjust);
    else
      EstimateSceneAdaptation(PatchRadianceEstimate);
  }
}

void ReEstimateAdaptation(void)
{
  if (last_is_scene)
    EstimateSceneAdaptation(PatchRadianceEstimate);
  else
    EstimateViewAdaptation();

  fprintf(stderr, "old ref. luminance = %g\n", reference_luminance);
  reference_luminance = 5.42 * tmopts.lwa;
  fprintf(stderr, "new ref. luminance = %g\n", reference_luminance);
}
