/*

  xmunipack - pre-scaling

  Copyright © 2018-9 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "fits.h"
#include <algorithm>
#include <cfloat>

#define QBLACK 0.25
#define QSENSE 0.95

using namespace std;

FitsTone::FitsTone():
  initialised(false),
  black(0.0),qblack(QBLACK),sense(1.0),rsense(1.0),refsense(1.0) {}

FitsTone::FitsTone(const FitsArray& array):
  initialised(false),
  black(0.0),qblack(QBLACK),sense(1.0),rsense(1.0),refsense(1.0)
{
  Setup(array.Npixels(),array.PixelData());
}

FitsTone::FitsTone(long npix, const float *data):
  initialised(false),
  black(0.0),qblack(QBLACK),sense(1.0),rsense(1.0),refsense(1.0)
{
  Setup(npix,data);
}

bool FitsTone::IsOk() const
{
  return initialised && cdf_back.IsOk();
}

void FitsTone::Setup(long npix, const float *data)
{
  wxASSERT(npix > 0 && data);

  const long nmax = 32768;
  const long skip = max(npix / nmax, long(1));
  const long nd = npix / skip;
  wxLogDebug("FitsTone::Setup %ld %ld %f",nd,skip,qblack);

  float *d = new float[nd+1];

  // part I.
  // CDF over image grid,
  // if sparse field is analysed, it's CDF of sky and: Q(0.5) = sigma^2
  long m = 0;
  for(long i = 0; i < npix - skip && m < nd; i += skip)
    d[m++] = data[i];

  cdf_back = EmpiricalCDF(m,d);
  black = cdf_back.GetQuantile(QBLACK);
  float med = cdf_back.GetQuantile(0.5);
  float sig = (cdf_back.GetQuantile(0.75) - cdf_back.GetQuantile(0.25))
		     / (2*0.6745);
  float thresh = 3*sig;

  // part II
  // to estimate starlight, only pixels above the threshold `sig' are accepted,
  // the pixels are collected over an adaptive grid (the grid cell side),
  // to reach sufficient amount of data, limit 16 for the side corresponds
  // with default value of nmax 32768
  long n = 0;
  long side = 1;
  while( n < (nmax / 10) && side <= 16) {
    side = 2*side;
    long skip2 = max(skip / side,long(1));
    long imax = npix - skip2;
    for(long i = 0; n < nd && i < imax; i += skip2) {
      float r = data[i] - med;
      if( r > thresh )
	d[n++] = r;
    }
  }

  if( n > 0 ) {

    EmpiricalCDF cdf_light(n,d);
    refsense = cdf_light.GetQuantile(QSENSE);

    //    wxLogDebug("Estim: %f %f %f %f %ld %ld",black,med,sig,refsense,m,n);
    wxLogDebug("Median=%f MAD=%f Black=%f Qlight=%f n=%ld\n",
	       med, sig*0.6745, black, refsense, n);
  }
  else if( cdf_back.IsOk() && med > 100*FLT_EPSILON ) {
    refsense = 3*(cdf_back.GetQuantile(QSENSE)-cdf_back.GetQuantile(1-QSENSE));
  }
  else {
    // uniform intensity
    refsense = black > 0 ? sqrt(black) : 1;
  }
  delete[] d;

  sense = refsense;
  rsense = 1;

  initialised = true;
}



void FitsTone::SetBlack(float b)
{
  black = b;
  qblack = cdf_back.GetInverse(black);
}

void FitsTone::SetSense(float s)
{
  wxASSERT(s > 0);
  sense = s;
  rsense = refsense / sense;
}

void FitsTone::SetQblack(float q)
{
  qblack = q;
  black = cdf_back.GetQuantile(qblack);
}

void FitsTone::SetRsense(float r)
{
  wxASSERT(r > 0);
  rsense = r;
  sense = refsense / rsense;
}

float *FitsTone::Scale(long n, const float *a) const
{
  wxASSERT(initialised);

  float *f = new float[n];
  for(long i = 0; i < n; i++) {
    float t = (a[i] - black) / sense;
    f[i] = max(t, 0.0f);
  }
  return f;
}

float FitsTone::GetBlackMin() const
{
  return cdf_back.GetQuantile(0.01);
}

float FitsTone::GetBlackMax() const
{
  return cdf_back.GetQuantile(0.99);
}


void FitsTone::Reset()
{
  qblack = QBLACK;
  black = cdf_back.GetQuantile(qblack);
  sense = refsense;
  rsense = 1.0;
}
