/* 
   xmunipack - value units (intensity, flux, etc..) conversions
   
   Copyright © 1997-2013 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/>.

*/

/*

   This routine represents physical value intensity and provides
   its represenattion in various physical units.

   Currently only HST magnitude - photometry convention is used.

*/


#include "fits.h"
#include <wx/wx.h>

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


using namespace std;

FitsValue::FitsValue(): type(UNIT_COUNT),hascal(false) {}

FitsValue::FitsValue(const FitsArray& a, const wxString& phsystemfile, 
		     const wxString& f1, const wxString& f2, 
		     const wxString& f3, const wxString& f4): 
  type(UNIT_COUNT),hascal(false),fits_key_area(f1), fits_key_gain(f2), 
  fits_key_exptime(f3), fits_key_filter(f4)
{
  arrays.push_back(a);
  hascal = init(arrays);
  if( hascal )
    init_phsystem(phsystemfile);
}

FitsValue::FitsValue(const FitsArray& r,const FitsArray& g,const FitsArray& b):
  type(UNIT_COUNT),hascal(false)
{
  arrays.push_back(r);
  arrays.push_back(g);
  arrays.push_back(b);
  hascal = init(arrays);
  //  if( hascal )
    //    init_phsystem(phsystemfile);
}

void FitsValue::init_phsystem(const wxString& phsystemfile)
{
  phsystems = FitsPhotosystems(phsystemfile);
  if( phsystems.IsOk() ) {
    for(size_t i = 0; i < filter.size(); i++) {
    
      PhotoFilter phfilter = phsystems.GetFilter(photsys,filter[i]);
      if( phfilter.IsOk() )
	phconv.push_back(PhotoConv(phfilter,area[i],exptime[i],gain[i],scale[i]));
    }
  }
}


bool FitsValue::init(const std::vector<FitsArray>& arrays)
{
  bool hc = true;

  for(vector<FitsArray>::const_iterator k = arrays.begin(); k != arrays.end(); ++k){
    FitsHdu a(*k);

    
    double ar = 1.0, g = 1.0, et = 1.0, sc =1.0, sc11, sc12;

    ar = GetKeyDouble(a,fits_key_area);
    g = GetKeyDouble(a,fits_key_gain);
    et = GetKeyDouble(a,fits_key_exptime);
    sc11 = GetKeyDouble(a,"CD1_1");
    sc12 = GetKeyDouble(a,"CD1_2");
    sc = sqrt(sc11*sc11 + sc12*sc12);

    area.push_back(ar);
    gain.push_back(g);
    exptime.push_back(et);
    scale.push_back(3600.0*sc);

    filter.push_back(a.GetKey(fits_key_filter));
    photsys = a.GetKey("PHOTSYS");
    wxString bunit(a.GetKey("BUNIT"));
    
    hc = ! photsys.IsEmpty();

  }
  return hc;
}

double FitsValue::GetKeyDouble(const FitsHdu& a, const wxString& key) const
{
  if( ! key.IsEmpty() ) {
    wxString s = a.GetKey(key);

    double x;
    if( !s.IsEmpty() &&  s.ToDouble(&x) ) 
      return x;
  }
  return 1.0;
}


void FitsValue::SetType(int t)
{
  if( hascal ) {
    if( UNIT_FIRST + 1 < t && t < UNIT_LAST )
      type = static_cast<units_type>(t);
    else
      type = UNIT_PHOTON;
  }
  else {
    if( UNIT_FIRST < t && t < UNIT_PHOTON )
      type = static_cast<units_type>(t);
    else
      type = UNIT_COUNT;
  }
}

void FitsValue::SetType(const wxString& a)
{
  for(int i = UNIT_FIRST+1; i < UNIT_LAST; i++)
    if( a == Label_str(i) ) {
      SetType(i);
      return;
    }
}


units_type FitsValue::GetType() const
{
  return type;
}

std::vector<double> FitsValue::Get(int i, int j) const
{ 
  std::vector<double> vals;

  for(vector<FitsArray>::const_iterator k=arrays.begin(); k!=arrays.end();++k){
    FitsArray a(*k);

    if( 0 <= i && i < a.Width() && 0 <= j && j < a.Height() )
      vals.push_back(a.Pixel(i,j));
    else
      vals.push_back(0.0);
  }
  return vals;
}

wxString FitsValue::Get_str(int i, int j) const
{
  wxString line;

  for(size_t k = 0; k < arrays.size(); k++) {
    FitsArray d = arrays[k];

    wxString a;
    if( 0 <= i && i < d.Width() && 0 <= j && j < d.Height() ) {

      if( type == UNIT_PHOTON || type == UNIT_COUNT ) {
	if( d.Bitpix() > 0 )
	  a.Printf("%d",int(d.Pixel(i,j)+0.5));
	else
	  a.Printf("%.5g",d.Pixel(i,j));
      }

      if( hascal ) {


	if( type == UNIT_MAG )

	  a.Printf("%.5g",phconv[k].GetMag(d.Pixel(i,j)));

	else if( type == UNIT_FLUX )

	  a.Printf("%.5g",phconv[k].GetFlux(d.Pixel(i,j)));

	else if( type == UNIT_FNU )

	  a.Printf("%.5g",phconv[k].GetFnu(d.Pixel(i,j)));

	else if( type == UNIT_FLAM )

	  a.Printf("%.5g",phconv[k].GetFlam(d.Pixel(i,j)));

	else if( type == UNIT_STMAG )

	  a.Printf("%.5g",phconv[k].GetSTMag(d.Pixel(i,j)));

	else if( type == UNIT_ABMAG )

	  a.Printf("%.5g",phconv[k].GetABMag(d.Pixel(i,j)));

      }
      else { // instrumental data

	if( type == UNIT_MAG ) {
	  double f = (d.Pixel(i,j) * gain[k]) / (area[k] * exptime[k] * scale[k]);
	  a.Printf("%.3f",f > 0.0 ? 25.0-2.5*log10(f) : 99.999);
	}
      }

    }
    if( ! a.IsEmpty() )
      line += " " + a;
  }
  return line;
}

wxString FitsValue::Label_str(int n)
{
  switch(n){
  case UNIT_COUNT:  return  "count";
  case UNIT_MAG:    return L"mag/arcsec²";
  case UNIT_PHOTON: return  "photon";
  case UNIT_FLUX:   return L"W/m²/arcsec²";
  case UNIT_FNU:    return L"W/m²/Hz/arcsec²";
  case UNIT_FLAM:   return L"W/m²/nm/arcsec²";
  case UNIT_ABMAG:  return L"mag/Hz/arcsec²";
  case UNIT_STMAG:  return L"mag/nm/arcsec²";
  default:          return wxEmptyString;
  }
}

wxArrayString FitsValue::Label_str()
{
  wxArrayString a;

  for(int i = UNIT_FIRST+1; i < UNIT_LAST; i++)
    a.Add(Label_str(i));
	  
  return a;
}



// ----- Photometric Systems


PhotoConv::PhotoConv(const PhotoFilter& phfilter, double a, double e, double g, double s):
  area(a), exptime(e), gain(g), scale(s)
{
  hplanck = 6.62606957e-34;
  cspeed = 299792458.0;
  ABspflux = 3.631e-23; // [W/m2/Hz]
  STspflux = 3.6335e-10; // [W/m2/nm]

  flux0 = phfilter.flux0;
  leff = phfilter.leff;
  lwidth = phfilter.lwidth;
}

double PhotoConv::GetFlux(double photon) const
{
  double phi = photon / (area * exptime * scale * scale);
  return phi * hplanck * cspeed / leff;
}

double PhotoConv::GetFnu(double photon) const
{
  return GetFlux(photon) * cspeed / (leff * leff ) * lwidth;
}

double PhotoConv::GetFlam(double photon) const
{
  return GetFlux(photon) / lwidth / 1e-9;
}

double PhotoConv::GetMag(double f, double f0) const
{
  if( f0 <= 0.0 ) return 99.999;
  double x = f/f0;
  return x > 0.0 ? -2.5*log10(x) : 99.999;
}

double PhotoConv::GetMag(double photon) const
{
  return GetMag(GetFlux(photon),flux0);
}

double PhotoConv::GetABMag(double photon) const
{
  return GetMag(GetFnu(photon),ABspflux);
}

double PhotoConv::GetSTMag(double photon) const
{
  return GetMag(GetFlam(photon),STspflux);
}



FitsPhotosystems::FitsPhotosystems(const wxString& name)
{
  fitsfile *f;
  int status = 0;
  float nullval = 0.0;
  int dummy,htype;
  char extname[FLEN_CARD],comment[FLEN_CARD];
  const char *cols[] = {"FILTER","LAM_EFF","LAM_FWHM","FLAM_REF"};
  int col[4];

  int nhdu = 0;

  // open file
  status = 0;
  fits_open_file(&f, name.fn_str(), READONLY, &status);
  fits_get_num_hdus(f,&nhdu,&status);
  if( status ) goto crash;

  for(int k = 1; k < nhdu; k++) {

    fits_movabs_hdu(f,k+1,&htype,&status);
    if( status ) goto crash;

    fits_read_keyword(f,"EXTNAME",extname,comment,&status);

    // removing aphostrophes
    wxString e(extname);
    wxString e1 = e.SubString(1,e.Len()-2);
    e = e1.Trim();
    strcpy(extname,e.ToAscii());
    
    if( htype == BINARY_TBL ) {

      long nrows;
      int ncols;
      fits_get_num_rows(f,&nrows,&status);
      fits_get_num_cols(f,&ncols,&status);
      if( status ) goto crash;

      for(int i = 0; i < 4; i++)
	fits_get_colnum(f,CASESEN,(char*)cols[i],&col[i],&status);
	
      double *x1 = new double[nrows];
      double *x2 = new double[nrows];
      double *x3 = new double[nrows];

      long frow = 1, felem = 1; 

      int width;
      fits_get_col_display_width(f,col[0],&width,&status);
      char **fs = new char*[nrows];
      for(int i = 0; i < nrows; i++)
	fs[i] = new char[width];
      fits_read_col(f,TSTRING,col[0], frow, felem, nrows, &nullval,fs,&dummy,&status);

      fits_read_col(f,TDOUBLE,col[1], frow, felem, nrows, &nullval,x1,&dummy,&status);
      fits_read_col(f,TDOUBLE,col[2], frow, felem, nrows, &nullval,x2,&dummy,&status);
      fits_read_col(f,TDOUBLE,col[3], frow, felem, nrows, &nullval,x3,&dummy,&status);


      if( status ) goto crash;

      vector<PhotoFilter> phfs;
      for(int i = 0; i < nrows; i++)
	phfs.push_back(PhotoFilter(fs[i],x1[i],x2[i],x3[i]));

      phsystems.push_back(Photosys(extname,phfs));

      delete[] x1;
      delete[] x2;
      delete[] x3;
      for(int i = 0; i < nrows; i++)
	delete[] fs[i];
      delete[] fs;

    }
  }

  fits_close_file(f, &status);

 crash:
  
  // show error description
  char emsg[FLEN_ERRMSG];
  while( fits_read_errmsg(emsg) )
    wxLogDebug(wxString(emsg,wxConvUTF8));


}

bool FitsPhotosystems::IsOk() const
{
  return ! phsystems.empty();
}

PhotoFilter FitsPhotosystems::GetFilter(const wxString& name, 
					const wxString& filter) const
{
  for(size_t i = 0; i < phsystems.size(); i++) {
    if( phsystems[i].GetName() == name ) {
      return phsystems[i].GetFilter(filter);
    }
  }
  return PhotoFilter();
}


PhotoFilter Photosys::GetFilter(const wxString& filter) const
{
  for(size_t i = 0; i < filters.size(); i++) {
    if( filters[i].name == filter ) {
      return filters[i];
    }
  }
  return PhotoFilter();
}
