/*

  xmunipack - tone profiles

  Copyright © 2009-2011 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>

using namespace std;


FitsItt::FitsItt(int t):
  itt(t),kind(ITT_KIND_REL),
  black(0.0),sense(1.0),contrast(0.0),med(0.0),mad(1.0),amp(1.0),zero(0.0),rmax(1.0),rmin(0.0),
  Func(&FitsItt::Itt_linear)
{
  SetItt(itt);  
}

FitsItt::FitsItt(int t, int k, const FitsArrayStat& s):
  itt(t),kind(k),
  black(0.0),sense(1.0),contrast(0.0),med(0.0),mad(1.0),amp(1.0),zero(0.0),rmax(1.0),rmin(0.0),
  Func(&FitsItt::Itt_linear)
{
  SetItt(itt);
  Init(s);
}

// to be removed?
void FitsItt::Init(float x, float y)
{
  med = x;
  mad = y;
  rmax = med + 7.0*mad;
  rmin = med - 5.0*mad;
  Reset();
}

void FitsItt::Init(const FitsArrayStat& sarray)
{
  med = sarray.Med();
  mad = sarray.Mad();

  if( kind == ITT_KIND_REL ) {
    rmin = med - 5.0*mad;
    rmax = med + 7.0*mad;
    Reset();
  }
  else {
    rmin = sarray.GetMin();
    rmax = sarray.GetMax();
    SetBlack(0.0);
    SetContrast(0.0);
  }
}

void FitsItt::SetItt(int i)
{
  itt = ITT_FIRST < i && i < ITT_LAST ? i : ITT_LINE;

  switch (itt) {
  case ITT_ASINH: Func = &FitsItt::Itt_asinh; break;
  case ITT_GAMMA: Func = &FitsItt::Itt_gamma; break;
  case ITT_LOG:   Func = &FitsItt::Itt_log; break;
  case ITT_NORM:  Func = &FitsItt::Itt_norm; break;
  case ITT_SQRT:  Func = &FitsItt::Itt_sqrt; break;
  case ITT_SQR:   Func = &FitsItt::Itt_square; break;
  case ITT_LOGIS: Func = &FitsItt::Itt_logis; break;
  case ITT_ATAN:  Func = &FitsItt::Itt_atan; break;
  default:        Func = &FitsItt::Itt_linear; break;
  }
}

void FitsItt::SetItt(const wxString& a)
{
  for(int i = ITT_FIRST+1; i < ITT_LAST; i++)
    if( a == Type_str(i) ) {
      SetItt(i);
      return;
    }
}


int FitsItt::GetItt() const 
{ 
  return itt;
}

wxString FitsItt::GetItt_str() const 
{ 
  return Type_str(itt);
}

void FitsItt::SetKind(int i)
{
  kind = i;
}

int FitsItt::GetKind() const 
{ 
  return kind;
}

float FitsItt::GetRangeMin() const
{
  return rmin;
}

float FitsItt::GetRangeMax() const
{
  return rmax;
}

void FitsItt::SetBlack(float x)
{
  if( kind == ITT_KIND_ABS )
    black = x;
  else if( kind == ITT_KIND_REL ) {
    black = med - x*mad;
  }
}

void FitsItt::SetSensitivity(float x)
{
  sense = x;
}

void FitsItt::SetBrightness(float x)
{
  black = (rmax - rmin)*x + rmin;
}

void FitsItt::SetContrast(float x)
{
  contrast = x;

  if( kind == ITT_KIND_ABS ) {
    float r =  pow(10.0,contrast)*(rmax - rmin);
    sense = 1.0/r;
  }
  else if( kind == ITT_KIND_REL ) {
    sense = 1.0/x/mad;
  }
}

void FitsItt::SetAmp(float x)
{
  amp = x;
}

void FitsItt::SetZero(float x)
{
  zero = x;
}

void FitsItt::SetRangeMin(float x)
{
  rmin = x;
}

void FitsItt::SetRangeMax(float x)
{
  rmax = x;
}

bool FitsItt::IsLinear() const
{
  return itt == ITT_LINE;
}

float FitsItt::GetSensitivity() const
{
  return sense;
}

float FitsItt::GetContrast() const
{
  if( kind == ITT_KIND_ABS )
    return contrast;
  else if( kind == ITT_KIND_REL )
    return 1.0/sense/mad;

  return 1.0;
}

float FitsItt::GetBlack() const
{
  if( kind == ITT_KIND_ABS )
    return black;
  else if( kind == ITT_KIND_REL )
    return (med - black)/mad;

  return 0.0;
}

float FitsItt::GetAmp() const
{
  return amp;
}

float FitsItt::GetZero() const
{
  return zero;
}

float FitsItt::GetMed() const
{
  return med;
}

float FitsItt::GetMad() const
{
  return mad;
}

float FitsItt::Itt_linear(float r) const
{
  return r;
}

float FitsItt::Itt_asinh(float r) const
{
  return amp*asinhf(r) + zero;
}

float FitsItt::Itt_gamma(float r) const
{
  double x;
  if( r < 0.00304 )
    x = 12.92*r;
  else
    x = (1.055*powf(r,0.4166666666) - 0.055);
  return amp*x + zero;
}


float FitsItt::Itt_log(float r) const
{
  return r > 0.0 ? amp*logf(2.0*r + 1.0)+zero : 0.0;
}

float FitsItt::Itt_norm(float r) const
{
  float x = (r - 0.5)*2.0;
  return amp*0.5*(1.0 + erff(x))+zero;
}

float FitsItt::Itt_sqrt(float r) const
{
  return r > 0.0 ? amp*sqrtf(r)+zero : 0.0;
}

float FitsItt::Itt_atan(float r) const
{
  return amp*atanf(r)+zero;
}

float FitsItt::Itt_square(float r) const
{
  return amp*r*r+zero;
}

float FitsItt::Itt_logis(float r) const
{
  r = (r - 0.5)*6.0;
  return amp/(1.0 + expf(-r))+zero;
}


wxString FitsItt::Type_str(int n)
{
  switch(n){
  case ITT_LINE:  return "Linear";
  case ITT_ASINH: return "Asinh";
  case ITT_GAMMA: return "Gamma";
  case ITT_LOG:   return "Logarithm";
  case ITT_NORM:  return "Normal";
  case ITT_SQRT:  return "Sqrt";
  case ITT_SQR:   return "Square";
  case ITT_LOGIS: return "Logistic";
  case ITT_ATAN:  return "Atan";
  default:        return wxEmptyString;
  }
}


wxArrayString FitsItt::Type_str()
{
  wxArrayString a;

  for(int i = ITT_FIRST+1; i < ITT_LAST; i++)
    a.Add(Type_str(i));
	  
  return a;
}

unsigned char FitsItt::Fscale(float flux) const
{
  float f = 255.0*Scale(flux); 

  if( f < 0.0 )
    return 0;
  else if( f > 255.0 )
    return 255;
  else
    return int(f + 0.5);
}

float FitsItt::InvScale(int iflux) const
{
  float r = 0.0;
  float f;
  switch(itt){
  case ITT_LINE:  r = iflux; break;
  case ITT_ASINH: r = sinhf((iflux-zero)/amp); break;
  case ITT_GAMMA: f = (iflux-zero)/amp;
    if( f < 0.03928 ) 
      r = f/12.92;
    else
      r = powf((f + 0.055)/1.055,2.4);
    break;
  case ITT_LOG: r = expf((iflux-zero)/amp)/2.0; break;
  case ITT_NORM: r = 2.0*invnorm((iflux-zero)/amp)-1.0; r=r/4.0+0.5; break;
  case ITT_SQRT: r = powf((iflux-zero)/amp,2.0); break;
  case ITT_SQR: r = sqrtf(float(max(double((iflux-zero)/amp),0.0))); break;
  case ITT_LOGIS: 
    if( iflux == 1 )
      r = 1.0;
    else if( iflux == 0 )
      r = 0.0;
    else
      r = -2.0*log(1.0/iflux - 1.0); 
    break;
  case ITT_ATAN: r = tan((iflux-zero)/amp); break;
  default: r = 0.0; wxLogDebug("WRONG: %s %d reached",__FILE__,__LINE__);break;
  }

  return r/sense + black;
}


void FitsItt::Reset()
{
  black = med - 0.5*mad;
  if( mad > 0.0 )
    sense = (1.0/mad)/20.0;
  else
    sense = 1.0/FLT_EPSILON;

  contrast = 0.0;
  /*
  black = med - 3.0*mad;
  sense = (1.0/mad)/15.0;
  */
  /*
  black = med - 3.0/mad;
  sense = 0.0333333/mad;
  */
}


float FitsItt::invnorm(float x) const
{
  // An approximation of inverse function to the normal (Gauss) distribution
  // by the book: J.Andel: Matematicka Statistika, Matfyz Press 199x, Prague

  if( x <= 0 )
    return 0.0;
  else if( x >= 1.0 )
    return 1.0;
  else {
    bool interval = x < 0.5;
    if( ! interval ) x = 1.0 - x + FLT_EPSILON;
    float  w = sqrt(-2.0*log(x));
    float f = -w + (2.515517 + w*(0.802853 + w*0.010328))/
      (1.0 + w*(1.432788 + w*(0.189269 + w*0.001308)));
    if( interval )

      return f;
    else
      return -f;
  }
}

float FitsItt::Scale(float flux) const
{
  return (this->*Func)((flux - black)*sense);
}



float *FitsItt::Scale(int n, const float *a)
{
  //  wxLogDebug("%f %f %f %f",black,1.0/sense,rmax,rmin);

  float *f = new float[n];

  if( itt == ITT_LINE )
    for(int i = 0; i < n; i++)
      f[i] = (a[i] - black)*sense;

  else if( itt == ITT_ASINH )
    for(int i = 0; i < n; i++)
      f[i] = amp*asinhf((a[i] - black)*sense) + zero;

  else if( itt == ITT_LOG )
    for(int i = 0; i < n; i++) {
      float r = (a[i] - black)*sense;
      f[i] = r > 0.0f ? amp*logf(2.0f*r + 1.0f) + zero : 0.0f;
    }

  else if( itt == ITT_SQRT )
    for(int i = 0; i < n; i++) {
      float r = (a[i] - black)*sense;
      f[i] = r > 0.0 ? amp*sqrtf(r) + zero : 0.0f;
    }
  else if( itt == ITT_GAMMA )
    for(int i = 0; i < n; i++) {
      float r = (a[i] - black)*sense;
      float x;
      if( r < 0.00304f )
	x = 12.92f*r;
      else
	x = (1.055f*powf(r,0.4166666666f) - 0.055f);
      f[i] = amp*x + zero;
    }
  else if( itt == ITT_NORM )
    for(int i = 0; i < n; i++) {
      float r = (a[i] - black)*sense;
      f[i] = amp*0.5f*(1.0f + erff(2.0f*(r - 0.5f)))+zero;
    }
  else if( itt == ITT_LOGIS )
    for(int i = 0; i < n; i++) {
      float r = (a[i] - black)*sense;
      f[i] = amp/(1.0f + expf(-6.0f*(r - 0.5f)))+zero;
    }
  else if( itt == ITT_ATAN )
    for(int i = 0; i < n; i++)
      f[i] = amp*atanf((a[i] - black)*sense) + zero;

  else if( itt == ITT_SQR )
    for(int i = 0; i < n; i++) {
      float r = (a[i] - black)*sense;
      f[i] = amp*r*r + zero;
    }
  else
    wxFAIL_MSG("An unknown ITT");

  return f;
}

