/*

  xmunipack - fits image histogram

  Copyright © 2018 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 <cfloat>
#include <wx/wx.h>
#include <algorithm>
#include <limits>

#ifdef __WXDEBUG__
#include <wx/debug.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#endif



using namespace std;

// ----------  FitsHisto

class FitsHistoData : public wxObjectRefData
{
public:
  FitsHistoData();
  FitsHistoData(int, int *, float *);
  FitsHistoData(const FitsHistoData&);
  FitsHistoData& operator = (const FitsHistoData&);
  virtual ~FitsHistoData();

  int nbin;
  int *hist;
  float *cents;
};


FitsHistoData::FitsHistoData(): nbin(0),hist(0),cents(0) {}

FitsHistoData::FitsHistoData(int n, int *h, float *t):
  nbin(n),hist(h),cents(t) {}


FitsHistoData::FitsHistoData(const FitsHistoData& copy)
{
  wxFAIL_MSG("FitsHistoData ----- WE ARE REALY NEED COPY CONSTRUCTOR -----");
}

FitsHistoData& FitsHistoData::operator = (const FitsHistoData& other)
{
  wxFAIL_MSG("*** FitsHistoData: WE ARE REALLY NEED ASSIGNMENT CONSTRUCTOR");
  return *this;
}


FitsHistoData::~FitsHistoData()
{
  delete[] hist;
  delete[] cents;
}


FitsHisto::FitsHisto(): wbin(0.0), xmin(0.0), xmax(0.0) {}

FitsHisto::FitsHisto(const FitsArray& a, int nb)
{
  wxASSERT(a.IsOk());

  // http://en.wikipedia.org/wiki/Histogram
  // By default, 32 bins is used, that corresponds,
  // by 1/3-rule to 32768 elements
  int nmax = 32768;
  int nbin = 32;

  if( nb > 0 ) {
    nbin = nb;
    nmax = nb*nb*nb;
  }

  const int skip = a.Npixels() / nmax;
  float *d = new float[nmax+1];
  int n = 0;
  for(long i = 0; i < a.Npixels() - skip && n < nmax; i += skip)
    d[n++] = a.Pixel(i);

  EmpiricalCDF ecdf(n,d);

  // quantiles are suitable for well distributed data
  xmin = ecdf.GetQuantile(0.01);
  xmax = ecdf.GetQuantile(0.99);

  // fall-back for a few datapoints
  if( xmax - xmin < FLT_EPSILON ) {
    FitsArrayStat as(a);
    xmin = as.GetMin();
    xmax = as.GetMax();
  }

  // unique values
  if( xmax - xmin < FLT_EPSILON ) {
    float x = a.Pixel(1);
    if( fabs(x) > FLT_EPSILON ) {
      xmin = x / 2;
      xmax = 2 * x;
    }
    else {
      xmin = 0;
      xmax = 1;
    }
  }

  wbin = (xmax - xmin) / nbin;

  wxLogDebug("hist: %f %f %d %f %f %d",xmin,xmax,nbin,wbin,ecdf.GetQuantile(0.5),n);

  Create(nbin,a);

  delete[] d;
}

wxObjectRefData *FitsHisto::CreateRefData() const
{
  return new FitsHistoData;
}


wxObjectRefData *FitsHisto::CloneRefData(const wxObjectRefData *that) const
{
  const FitsHistoData *olddata = static_cast<const FitsHistoData *>(that);
  wxASSERT(olddata);
  return new FitsHistoData(*olddata);
}


bool FitsHisto::IsOk() const
{
  const FitsHistoData *data = static_cast<const FitsHistoData *>(m_refData);
  return data && data->nbin > 0 && data->hist && data->cents;
}

void FitsHisto::Create(int nbin, const FitsArrayStat& a)
{
  wxASSERT(a.IsOk() && wbin > FLT_EPSILON);
  // http://en.wikipedia.org/wiki/Kernel_density_estimation ?

  const int skip = 100;

  int *hist = new int[nbin];
  float *cents = new float[nbin];

  for(int n = 0; n < nbin; n++) hist[n] = 0;
  for(int n = 0; n < nbin; n++) cents[n] = xmin + n*wbin;
  for(long i = 0; i < a.Npixels(); i += skip ) {
    int n = int((a.Pixel(i) - xmin)/wbin);
    if( 0 <= n  && n < nbin ) hist[n] += skip;
  }

  UnRef();
  SetRefData(new FitsHistoData(nbin,hist,cents));

  /*
#ifdef __WXDEBUG__
  wxFFileOutputStream file("/tmp/hist","w+");
  wxTextOutputStream cout(file);
  for(int i = 0; i < nbin; i++)
    cout << i << " " << cents[i] << " " << hist[i] << endl;
#endif
  */
}

int FitsHisto::Hist(int n) const
{
  const FitsHistoData *data = static_cast<const FitsHistoData *>(m_refData);
  wxASSERT(data && 0 <= n && n < data->nbin && data->hist);

  return data->hist[n];
}

float FitsHisto::Cents(int n) const
{
  const FitsHistoData *data = static_cast<const FitsHistoData *>(m_refData);
  wxASSERT(data && 0 <= n && n < data->nbin && data->cents);
  return data->cents[n];
}

float FitsHisto::CentsMin() const
{
  const FitsHistoData *data = static_cast<const FitsHistoData *>(m_refData);
  wxASSERT(data && data->nbin > 0 && data->cents && data->hist);

  for(int i = 0; i < data->nbin-1; i++)
    if( data->hist[i] > 0 )
      return data->cents[i];

  return data->cents[0];
}

float FitsHisto::CentsMax() const
{
  const FitsHistoData *data = static_cast<const FitsHistoData *>(m_refData);
  wxASSERT(data && data->nbin > 0 && data->cents && data->hist);

  for(int i = data->nbin-1; i > 0; i--)
    if( data->hist[i] > 0 )
      return data->cents[i];

  return data->cents[data->nbin-1];
}

float FitsHisto::BinWidth() const
{
  return wbin;
}


int FitsHisto::NBins() const
{
  const FitsHistoData *data = static_cast<const FitsHistoData *>(m_refData);
  if( data )
    return data->nbin;
  else
    return 0;
}
