/*

  xmunipack - fits implementation

  Copyright © 1997 - 2014, 2017-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 <string>
#include <cstdio>
#include <fitsio.h>
#include <cmath>
#include <cfloat>
#include <algorithm>
#include <wx/wx.h>
#include <wx/tokenzr.h>
#include <wx/regex.h>
#include <wx/uri.h>
#include <wx/filesys.h>
#include <wx/txtstrm.h>

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

using namespace std;

// we know the types of images:
// * 1d (spectrum)
// * 2d grayscale
// * 3d colour (3d, unique sizes, header with CSPACE keyword)
// * 3d (3d spectrum) ?
// * multilayer (more layers, arbitrary sizes)
//




// ---  FitsHeader (HDU)

int FitsHeader::Bitpix() const
{
  wxString l = GetKey("BITPIX");
  long b;
  if( l.ToLong(&b) )
    return b;
  else
    return 0;
}


wxString FitsHeader::Bitpix_str() const
{
  int bitpix = Bitpix();

  switch (bitpix) {
  case 8: return   "0 .. 255";
  case 16: return  "0 .. 65535";
  case 32: return  "0 .. 4294967296";
  case -32:return L"0 .. ± 10^38, 6 digits";
  default:
    wxString a;
    a.Printf("Bitpix = %d",bitpix);
    return a;
  }
}

bool FitsHeader::ParseRecord(const wxString& record, wxString& key,
			     wxString& value, wxString& comment)
{
  int status = 0;
  const char *card = record.ToAscii();
  char keyname[FLEN_CARD],cval[FLEN_CARD],com[FLEN_CARD];
  int keylen;

  fits_get_keyname((char*)card,keyname,&keylen,&status);
  fits_parse_value((char*)card,cval,com,&status);

  if( status == 0 ) {
    key = keyname;
    comment = com;
    value = cval;

    /*
    if( value.Find('\'') ) {

      wxRegEx re("'(''|[^'])*'");
      wxASSERT(re.IsValid());
      if( re.Matches(value) )
	value = re.GetMatch(value,1);
    }
    */


    char *i1 = index(cval,'\'');
    char *i2 = rindex(cval,'\'');
    if( i1 != NULL && i2 != NULL && i1 != i2 ) {
      int l = i2 - i1 - 1;
      for(int i = 0; i < l; i++)
	cval[i] = *(i1 + i + 1);
      cval[l] = '\0';
    }

    value = cval;
    value.Trim();


    return true;
  }
  else
    return false;
}

bool FitsHeader::FindKey(const wxString& keyword, wxString& value, wxString& comment) const
{
  for(size_t i = 0; i < GetCount(); i++) {
    wxString key,val,com;
    if( ParseRecord(Item(i),key,val,com) ) {
      wxStringTokenizer tb = wxStringTokenizer(keyword,",");
      while( tb.HasMoreTokens() ) {
	wxString k = tb.GetNextToken();
	if( key == k ) {
	  value = val; comment = com;
	  return true;
	}
      }
    }
  }
  return false;
}

wxString FitsHeader::GetKey(const wxString& key) const
{
  wxString l,c;
  if( FindKey(key,l,c) )
    return l;
  return wxEmptyString;
}

wxString FitsHeader::GetUnit(const wxString& key) const
{
  wxString l,c;
  if( FindKey(key,l,c) ) {
    wxRegEx re("^\\s*\\[.*\\].*");
    wxASSERT(re.IsValid());
    if( re.Matches(c) ) {
      size_t start,len;
      re.GetMatch(&start,&len);
      wxString s = c.SubString(start,len);
      int i1 = s.Index('[') + 1;
      int i2 = s.Index(']') - 1;
      return c.SubString(i1,i2);
    }
  }
  return wxEmptyString;
}

wxString FitsHeader::Exposure_str(const wxString& key) const
{
  wxString exp = GetKey(key);
  double e;
  if( ! exp.IsEmpty() && exp.ToDouble(&e) ) {
    wxString line;
    line.Printf("%g",e);
    return line;
  }
  else
    return exp;
}

// ------ FitsHDU

size_t FitsHdu::GetCount() const
{
  return header.GetCount();
}

wxString FitsHdu::Item(size_t i) const
{
  return header.Item(i);
}

wxString FitsHdu::GetKey(const wxString& a) const
{
  return header.GetKey(a);
}

long FitsHdu::GetKeyLong(const wxString& key) const
{
  wxString l = GetKey(key);
  long i;
  return l.ToLong(&i) ? i : 0;
}

double FitsHdu::GetKeyDouble(const wxString& key) const
{
  wxString l = GetKey(key);
  double x;
  return l.ToDouble(&x) ? x : 0.0;
}

wxString FitsHdu::GetUnit(const wxString& a) const
{
  return header.GetUnit(a);
}

int FitsHdu::Bitpix() const
{
  return header.Bitpix();
}

wxString FitsHdu::Bitpix_str() const
{
  return header.Bitpix_str();
}

wxString FitsHdu::Exposure_str(const wxString& a) const
{
  return header.Exposure_str(a);
}

int FitsHdu::Type() const
{
  return type;
}

wxString FitsHdu::Type_str() const
{
  switch (Type()) {
  case HDU_HEAD:  return "Head";
  case HDU_IMAGE: return "Image";
  case HDU_TABLE: return "Table";
  default: return wxEmptyString;
  }
}

int FitsHdu::Flavour() const
{
  return HDU_DUMMY;
}

wxString FitsHdu::Flavour_str() const
{
  return wxEmptyString;
}

int FitsHdu::Naxis() const
{
  return 0;
}

long FitsHdu::Naxes(int n) const
{
  return 0;
}

long FitsHdu::Width() const
{
  return Naxes(0);
}

long FitsHdu::Height() const
{
  return Naxes(1);
}

long FitsHdu::GetWidth() const
{
  return Naxes(0);
}

long FitsHdu::GetHeight() const
{
  return Naxes(1);
}

bool FitsHdu::IsOk() const
{
  return ! header.IsEmpty();
}

bool FitsHdu::IsColour() const
{
  return Naxis() == 3;
}

wxString FitsHdu::GetExtname() const
{
  return GetKey("EXTNAME");
}

bool FitsHdu::GetWCS(double& xpix, double& ypix, double& alpha, double& delta, double& scale, double& angle, double& reflex) const
{
  // projection is silently ignored
  wxString type(GetKey("CTYPE1"));

  if( type.IsEmpty() ) return false;

  xpix = GetKeyDouble("CRPIX1");
  ypix = GetKeyDouble("CRPIX2");

  alpha = GetKeyDouble("CRVAL1");
  delta = GetKeyDouble("CRVAL2");

  double cd11,cd12,cd21,cd22;
  cd11 = GetKeyDouble("CD1_1");
  cd12 = GetKeyDouble("CD1_2");
  cd21 = GetKeyDouble("CD2_1");
  cd22 = GetKeyDouble("CD2_2");

  reflex = -cd11*cd22 >= 0 ? 1.0 : -1.0;
  scale = 1.0/sqrt(cd11*cd11 + cd12*cd12);
  angle = (45.0/atan(1.0)) * atan2(cd21,cd22);

  return true;
}


// ------------   FitsArray


FitsArrayData::FitsArrayData(): naxis(0),naxes(0),array(0) {}

FitsArrayData::FitsArrayData(int n, long *ns, float *a):
  naxis(n),naxes(ns),array(a) {}


FitsArrayData::FitsArrayData(const FitsArrayData& other)
{  // COPY constructor
  long n = 1;
  for(long i = 0; i < other.naxis; i++)
    n = n * other.naxes[i];
  naxis = other.naxis;
  naxes = new long[naxis];
  array = new float[n];
  copy(other.naxes,other.naxes+naxis,naxes);
  copy(other.array,other.array+n,array);
}

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


FitsArrayData::~FitsArrayData()
{
  delete[] naxes;
  delete[] array;
}


FitsArray::FitsArray(): npixels(0) {}

FitsArray::FitsArray(const FitsHdu& h, int n, long *ns, float *a): FitsHdu(h)
{
  wxASSERT( n >= 0 && ns && a );
  type = HDU_IMAGE;

  UnRef();
  SetRefData(new FitsArrayData(n,ns,a));

  FitsArrayData *data = static_cast<FitsArrayData *>(GetRefData());
  wxASSERT(IsOk() && data);
  npixels = 1;
  for(int k = 0; k < data->naxis; k++)
    npixels = npixels*data->naxes[k];
}



FitsArray::FitsArray(const FitsHdu& h): FitsHdu(h)
{
  wxASSERT(h.Type() == HDU_IMAGE && h.IsOk());

  FitsArrayData *data = static_cast<FitsArrayData *>(GetRefData());
  wxASSERT(data);
  npixels = 1;
  for(int k = 0; k < data->naxis; k++)
    npixels = npixels*data->naxes[k];
}

/*
FitsArray::~FitsArray()
{
  UnRef();
}
*/

wxObjectRefData *FitsArray::CreateRefData() const
{
  return new FitsArrayData;
}


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

bool FitsArray::IsOk() const
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  if( ! data ) return false;
  return m_refData && data->naxis > 0 && data->naxes && data->array;
}


int FitsArray::Naxis() const
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data);
  return data->naxis;
}

long FitsArray::Naxes(int n) const
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data);

  if( 0 <= n && n < data->naxis )
    return data->naxes[n];
  else
    return 0;
}

long* FitsArray::Naxes() const
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data);

  long *ns = new long[data->naxis];
  for(int n = 0; n < data->naxis; n++)
    ns[n] = data->naxes[n];
  return ns;
}


const float *FitsArray::PixelData() const
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data);
  const float *array = data->array;
  wxASSERT(array);
  return array;
}



FitsArray FitsArray::Plane(int n) const
{
  wxASSERT(0 <= n && n < 3);

  int np = 2;
  long *ns = new long[np];
  ns[0] = Naxes(0);
  ns[1] = Naxes(1);
  long npixels = ns[0]*ns[1];

  const FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data && data->array);
  const float *array = data->array + n*npixels;

  float *a = new float[npixels];
  copy(array,array+npixels,a);
  return FitsArray(*this,np,ns,a);
}


int FitsArray::Flavour() const
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data && data->array && data->naxes);

  if( IsColour() )
    return HDU_IMAGE_COLOUR;

  switch(data->naxis) {
  case 1: return HDU_IMAGE_LINE;
  case 2: return HDU_IMAGE_FRAME;
  case 3: return HDU_IMAGE_CUBE;
  }
  return HDU_IMAGE_UNKNOWN;
}

wxString FitsArray::Flavour_str() const
{
  switch (Flavour()) {
  case HDU_IMAGE_LINE: return "Line";
  case HDU_IMAGE_FRAME:return "Gray";
  case HDU_IMAGE_CUBE: return "Cube";
  case HDU_IMAGE_COLOUR: return "Colour";
  }
  wxLogDebug("FitsArray::Type_str(): Image type unknown");
  return wxEmptyString;
}




// ----------    FitsTable

FitsTableColumnData::FitsTableColumnData():
  typecode(0),nrows(0),otable(0),btable(0),stable(0),itable(0),
  ltable(0),ftable(0),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, float *d):
  typecode(t),nrows(n),otable(0),btable(0),stable(0),itable(0),
  ltable(0),ftable(d),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, int *d):
  typecode(t),nrows(n),otable(0),btable(0),stable(0),itable(d),
  ltable(0),ftable(0),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, char **d):
  typecode(t),nrows(n),otable(0),btable(0),stable(0),itable(0),
  ltable(0),ftable(0),dtable(0),ctable(d) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, char *d):
  typecode(t),nrows(n),otable(0),btable(d),stable(0),itable(0),
  ltable(0),ftable(0),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, bool *d):
  typecode(t),nrows(n),otable(d),btable(0),stable(0),itable(0),
  ltable(0),ftable(0),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, short *d):
  typecode(t),nrows(n),otable(0),btable(0),stable(d),itable(0),
  ltable(0),ftable(0),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, long *d):
  typecode(t),nrows(n),otable(0),btable(0),stable(0),itable(0),
  ltable(d),ftable(0),dtable(0),ctable(0) {}

FitsTableColumnData::FitsTableColumnData(int t, long n, double *d):
  typecode(t),nrows(n),otable(0),btable(0),stable(0),itable(0),
  ltable(0),ftable(0),dtable(d),ctable(0) {}

FitsTableColumnData::~FitsTableColumnData()
{
  delete[] ftable;
  delete[] itable;
  delete[] dtable;
  delete[] ltable;
  delete[] stable;
  delete[] btable;
  delete[] otable;

  if( ctable ) {
    for(int i = 0; i < nrows; i++)
      delete[] ctable[i];
    delete[] ctable;
  }
}


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

}

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


FitsTableColumn::FitsTableColumn() {}

FitsTableColumn::~FitsTableColumn() {}

FitsTableColumn::FitsTableColumn(int t, long nr, float *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, double *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, long *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, int *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, short *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, char *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, bool *d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

FitsTableColumn::FitsTableColumn(int t, long nr, char **d)
{
  UnRef();
  SetRefData(new FitsTableColumnData(t,nr,d));
}

/*
FitsTableColumn::FitsTableColumn(const FitsTableColumn& copy)
{
}

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

int FitsTableColumn::GetColType() const
{
  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
  wxASSERT(data);
  return data->typecode;
};

long FitsTableColumn::Nrows() const
{
  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
  wxASSERT(data);
  return data->nrows;
};


const float *FitsTableColumn::GetCol_float() const
{
  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
  wxASSERT(data);
  return data->ftable;
}

const double *FitsTableColumn::GetCol_double() const
{
  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
  wxASSERT(data);
  return data->dtable;
}

const long *FitsTableColumn::GetCol_long() const
{
  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
  wxASSERT(data);
  return data->ltable;
}

const char **FitsTableColumn::GetCol_char() const
{
  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
  wxASSERT(data);
  return (const char **) data->ctable;
}


wxObjectRefData *FitsTableColumn::CreateRefData() const
{
  return new FitsTableColumnData;
}


wxObjectRefData *FitsTableColumn::CloneRefData(const wxObjectRefData *that) const
{
  const FitsTableColumnData *olddata = static_cast<const FitsTableColumnData *>(that);
  wxASSERT(olddata);

  /*
  FitsTableData *newdata = new FitsTableData;
  newdata->ncols = olddata->ncols;
  newdata->nrows = olddata->nrows;
  long nelem = newdata->ncols*newdata->nrows;
  newdata->table = new float[nelem];
  memcpy(newdata->table,olddata->table,nelem*sizeof(float));
  return newdata;
  */

  FitsTableColumnData *newdata = new FitsTableColumnData;
  newdata->typecode = olddata->typecode;
  newdata->nrows = olddata->nrows;
  long nr = olddata->nrows;

  if( olddata->itable ) {
    newdata->itable = new int[nr];
    memcpy(newdata->itable,olddata->itable,nr*sizeof(int));
  }
  else if( olddata->ltable ) {
    newdata->ltable = new long[nr];
    memcpy(newdata->ltable,olddata->ltable,nr*sizeof(long));
  }
  else if( olddata->otable ) {
    newdata->otable = new bool[nr];
    memcpy(newdata->otable,olddata->otable,nr*sizeof(bool));
  }
  else if( olddata->btable ) {
    newdata->btable = new char[nr];
    memcpy(newdata->btable,olddata->btable,nr*sizeof(char));
  }
  else if( olddata->stable ) {
    newdata->stable = new short[nr];
    memcpy(newdata->stable,olddata->stable,nr*sizeof(short));
  }
  else if( olddata->ftable ) {
    newdata->ftable = new float[nr];
    memcpy(newdata->ftable,olddata->ftable,nr*sizeof(float));
  }
  else if( olddata->dtable ) {
    newdata->dtable = new double[nr];
    memcpy(newdata->dtable,olddata->dtable,nr*sizeof(double));
  }
  else if( olddata->ctable ) {
    newdata->ctable = new char*[nr];
    for(int i = 0; i < olddata->nrows; i++)
      newdata->ctable[i] = wxStrdup(olddata->ctable[i]);
  }
  else
    wxFAIL_MSG("FitsTableColumn::CloneRefData: Unknown data type.");

  return newdata;
}

FitsTableColumn FitsTableColumn::Copy() const
{
  FitsTableColumn new_col;
  new_col.m_refData = CloneRefData(m_refData);
  return new_col;
}

FitsTableData::FitsTableData() {}
FitsTableData::FitsTableData(const std::vector<FitsTableColumn>& cols): columns(cols) {}

// FitsTableData::FitsTableData(): ncols(0),columns(0) {}

// /*
// FitsTableData::FitsTableData(long nr, long nc, float *t):
//   nrows(nr),ncols(nc),table(t) {}
// */

// FitsTableData::FitsTableData(long nc): ncols(nc)
// {
//   columns = new FitsTableColumn[nc];
//   for(int i = 0; i < nc; i++)
//     columns[i] = 0;
// }

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

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


// FitsTableData::~FitsTableData()
// {
//   delete[] columns;

//   /*
//   for(int i = 0; i < ncols; i++) {
//     if( typecode[i] == TFLOAT ) {
//       float *d = (float *) table[i];
//       delete[] d;
//     }
//     else if( typecode[i] == TLONG ) {
//       long *d = (long *) table[i];
//       delete[] d;
//     }
//     else if( typecode[i] == TSTRING ) {
//       char *a = (char *) table[i];
//       delete[] a;
//     }
//   */

//     //    delete[] table[i];
//   //  }
//   //  delete[] typecode;
// }

/*
void FitsTableData::InsertColumn(long k, long nr, float *d)
{
  wxASSERT(0 <= k && k < ncols);
  columns[k] = FitsDataColumn(nr,d);
}

void FitsTableData::InsertColumn(long k, long nr, int *d)
{
  wxASSERT(0 <= k && k < ncols);
  columns[k] = FitsDataColumn(nr,d);
}

void FitsTableData::InsertColumn(long k, long nr, char **d)
{
  wxASSERT(0 <= k && k < ncols);
  columns[k] = FitsDataColumn(nr,d);
}
*/


FitsTable::FitsTable(){
  type = HDU_TABLE;
  UnRef();
}


/*
FitsTable::FitsTable(const FitsHdu& h, int ht, long nr, long nc, float *t):
  FitsHdu(h),fits_type(ht)
{
  type = HDU_TABLE;
  UnRef();
  SetRefData(new FitsTableData(nr,nc,t));
}
*/

FitsTable::FitsTable(const FitsHdu& h, int ht, const vector<FitsTableColumn>& cols):
  FitsHdu(h),fits_type(ht)
{
  type = HDU_TABLE;
  UnRef();
  SetRefData(new FitsTableData(cols));

  //  wxLogDebug("%d %d",(int)Nrows(),(int)Ncols());
}


FitsTable::FitsTable(const FitsHdu& h):
  FitsHdu(h),fits_type(h.Flavour())
  //  fits_type(static_cast<FitsTable>(h).fits_type)//,
  //  columns(static_cast<FitsTable>(h).columns)
  //  FitsHdu(h),fits_type(h.Flavour()),columns(static_cast<FitsTable>(h).columns)
{
  wxASSERT(h.Type() == HDU_TABLE && h.IsOk());

  //  wxLogDebug("FitsTable::FitsTable(const FitsHdu& h): ");

  //  wxLogDebug("%d %d",(int)Nrows(),(int)Ncols());

  //  fits_type = (const FitsTable) h.fits_type;

  /*
  fits_type = static_cast<FitsTable>(h).fits_type;
  columns = static_cast<FitsTable>(h).columns;
  */

  /*
  const FitsTable *t = static_cast<const FitsTable *>(&h);
  wxASSERT(t && t->IsOk());
  columns = t->columns;
  */
}

wxObjectRefData *FitsTable::CreateRefData() const
{
  return new FitsTableData;
}


wxObjectRefData *FitsTable::CloneRefData(const wxObjectRefData *that) const
{
  const FitsTableData *olddata = static_cast<const FitsTableData *>(that);
  wxASSERT(olddata);

  FitsTableData *newdata = new FitsTableData;
  newdata->columns = olddata->columns;

  return newdata;
}

//   /*
//   FitsTableData *newdata = new FitsTableData;
//   newdata->ncols = olddata->ncols;
//   newdata->nrows = olddata->nrows;
//   long nelem = newdata->ncols*newdata->nrows;
//   newdata->table = new float[nelem];
//   memcpy(newdata->table,olddata->table,nelem*sizeof(float));
//   return newdata;
//   */

//   FitsTableData *newdata = new FitsTableData;
//   newdata->ncols = olddata->ncols;
//   newdata->columns = new FitsDataColumn[newdata->ncols];
//   for(int i = 0; i < olddata->ncols; i++) {
//     newdata->columns[i] = olddata->columns[i];



//   /*
//   int len = 1;
//   for(int i = 0; i < olddata->ncols; i++) {
//     if( olddata->typecode[i] == TFLOAT )
//       len = len*sizeof(float);
//     else if( olddata->typecode[i] == TLONG )
//       len = len*sizeof(long);
//     else if( olddata->typecode[i] == TINT )
//       len = len*sizeof(int);
//     else if( olddata->typecode[i] == TLOGICAL )
//       len = len*sizeof(bool);
//     else
//       wxLogDebug("FitsTable::CloneRefData: Type `%d' not recognized.",olddata->typecode[i]);
//   }

//   long nelem = len*olddata->nrows;

//   newdata->typecode = new int[olddata->ncols];
//   memcpy(newdata->typecode,olddata->typecode,olddata->ncols*sizeof(int));

//   void *table = new void*[olddata->ncols];
//   for(int i = 0; i < olddata->ncols; i++) {

//     if( newdata->typecode[i] == TFLOAT ) {
//       table[i] = (void *) new float[olddata->nrows];
//       memcpy(table,olddata->table[i],olddata->nrows*sizeof(float));
//     }



//   }


//   newdata->table = new void**[];

//   memcpy(newdata->table,olddata->table,nelem);
//   return newdata;
//   */
// }


FitsTable FitsTable::Copy() const
{
  FitsTable new_table;
  new_table.m_refData = CloneRefData(m_refData);
  return new_table;
}



long FitsTable::Nrows() const
{
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  if( ! data )
    return 0;

  const vector<FitsTableColumn> columns(data->columns);
  long nrows = 0;

  wxASSERT(columns.size() > 0 );

  /*
  for(size_t i = 0; i < columns.size(); i++)
    nrows = columns[i].Nrows();
  */

  if( columns.size() > 0 )
    nrows = columns[0].Nrows();

  //    wxLogDebug("nrows = %d",(int)nrows);

  return nrows;

  /*
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  if( data )
    return data->nrows;
  else
    return 0;
  */
}


int FitsTable::Ncols() const
{
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  const vector<FitsTableColumn> columns(data->columns);
  return columns.size();
  /*
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  if( data )
    return data->ncols;
  else
    return 0;
  */
}


long FitsTable::Naxes(int n) const
{
  //  wxLogDebug("Don't use Naxes for tables!");
  /*
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  wxASSERT(data);
  */
  switch(n) {
  case 0:  return Ncols(); break;
  case 1:  return Nrows(); break;
  default: return 0;           break;
  }
}


bool FitsTable::IsOk() const
{
  /*
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  if( ! data ) return false;
  return m_refData && data->nrows > 0 && data->ncols > 0;
  */
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  bool b = data != 0;
  if( data ) {
    const vector<FitsTableColumn> columns(data->columns);
    b = ! columns.empty();
  }
  return b;
}

int FitsTable::Flavour() const
{
  switch(fits_type) {
  case BINARY_TBL: return HDU_TABLE_BIN;
  case ASCII_TBL: return HDU_TABLE_ASCII;
  }
  return HDU_TABLE_UNKNOWN;
}

wxString FitsTable::Flavour_str() const
{
  switch (Flavour()) {
  case HDU_TABLE_ASCII: return "Ascii";
  case HDU_TABLE_BIN:   return "Binary";
  }
  return wxEmptyString;
}

int FitsTable::GetColType(int c) const
{
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  const vector<FitsTableColumn> columns(data->columns);
  /*
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  wxASSERT(data && 0 <= x && x < data->ncols);
  return data->typecode + c;
  */
  wxASSERT(0 <= c && c < (int) columns.size());
  return columns[c].GetColType();
}


// const float *FitsTable::GetCol_float(int c) const
// {
//   /*
//   FitsTableData *data = static_cast<FitsTableData *>(m_refData);
//   wxASSERT(data && 0 <= x && x < data->ncols && data->typecode[c] == TFLOAT);
//   return (float *) data->table[c];
//   */
//   /*
//   wxASSERT(0 <= c && c < (int) columns.size());
//   return columns[c].GetCol_float();
//   */
//   //  FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
//   FitsTableData *data = static_cast<FitsTableData *>(m_refData);
//   const vector<FitsTableColumn> columns(data->columns);
//   return data->ftable;
// }

// const int *FitsTable::GetCol_int(int c) const
// {
//   /*
//   FitsTableData *data = static_cast<FitsTableData *>(m_refData);
//   wxASSERT(data && 0 <= x && x < data->ncols && data->typecode[c] == TLONG);
//   return (int *) data->table[c];
//   */
//   wxASSERT(0 <= c && c < (int) columns.size());
//   //  return columns[c].GetCol_int();
//   FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
//   return data->itable;
// }

// const char **FitsTable::GetCol_char(int c) const
// {
//   /*
//   FitsTableData *data = static_cast<FitsTableData *>(m_refData);
//   wxASSERT(data && 0 <= x && x < data->ncols && data->typecode[c] == TSTRING);
//   return (char **) data->table[c];
//   */
//   FitsTableColumnData *data = static_cast<FitsTableColumnData *>(m_refData);
//   return (const char **) data->ctable;
//   /*
//   wxASSERT(0 <= c && c < (int) columns.size());
//   return columns[c].GetCol_char();
//   */
// }

FitsTableColumn FitsTable::GetColumn(int k) const
{
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  const vector<FitsTableColumn> columns(data->columns);
  wxASSERT(0 <= k && k < (int) columns.size());
  return columns[k];
}


FitsTableColumn FitsTable::GetColumn(const wxString& label) const
{
  return GetColumn(GetColIndex(label));
}

int FitsTable::GetColIndex(const wxString& label) const
{
  for(size_t i = 0; i < GetCount(); i++) {
    wxString key,val,com;

    FitsHeader::ParseRecord(Item(i),key,val,com);

    if( label.StartsWith(val) ) {

      wxRegEx re("TTYPE([0-9]+)");
      wxASSERT(re.IsValid());

      if( re.Matches(key) ) {
	wxString a(re.GetMatch(key,1));
	long i;
	if( a.ToLong(&i) )
	  return i - 1;
      }
    }
  }
  return -1;
}


wxArrayString FitsTable::GetColLabels() const
{
  wxArrayString cols;

  for(size_t i = 0; i < GetCount(); i++) {
    wxString key,val,com;

    FitsHeader::ParseRecord(Item(i),key,val,com);

    if( key.StartsWith("TTYPE") )
      cols.Add(val);
  }
  return cols;
}


/*
const float *FitsTable::GetColumn(int x) const
{
  FitsTableData *data = static_cast<FitsTableData *>(m_refData);
  wxASSERT(data && 0 <= x && x < data->ncols);
  return data->table+x*data->nrows;
}
*/


void FitsTable::GetStarChart(wxOutputStream& output)
{
  wxTextOutputStream cout(output);

  cout << "<svg xmlns=\"http://www.w3.org/2000/svg\">" << endl;

  for(int i = 0; i < Naxes(1); i++) {

    float x = Pixel(0,i);
    float y = Pixel(1,i);

    float f = Pixel(11,i);

    if( f > 0 ) {
      float r = wxMax(f/1e4,10.0);
      cout << "<circle cx=\"" << x << "\" cy=\"" << y
	   << "\" r=\"" << r << "\"/>" << endl;
    }
  }

  cout << "</svg>" << endl;
}



// ----------    FitsFile

FitsFile::FitsFile(): status(false),type(FITS_UNKNOWN) {}

FitsFile::FitsFile(const wxString& name):
  filename(name),status(false),type(FITS_UNKNOWN)
{
  fitsfile *f;
  int stat = 0;
  int dummy, htype, bitpix, naxis;

  int nhdu = 0;

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

  fits_get_num_hdus(f,&nhdu,&stat);
  if( stat ) goto crash;

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

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

    // load header
    int nhead;
    char h[FLEN_CARD];
    FitsHeader head;
    fits_get_hdrspace(f,&nhead,&dummy,&stat);
    for(int n = 0; stat == 0 && n < nhead; n++) {
      if( fits_read_record(f,n+1,h,&stat) == 0 )
	head.Add(wxString(h,wxConvUTF8));
    }
    if( stat ) goto crash;

    // load data
    if( htype == IMAGE_HDU ) {

      fits_get_img_type(f,&bitpix,&stat);
      fits_get_img_dim(f,&naxis,&stat);
      if( stat ) goto crash;

      if( naxis > 0 ) {

	long *naxes = new long[naxis];
	fits_get_img_size(f,naxis,naxes,&stat);
	if( stat ) { delete[] naxes; goto crash; }

	long ndata = 1; for(int i = 0; i < naxis; i++ ) ndata = ndata*naxes[i];
	long firstelem = 1;
	float nullval = 0.0;

	float *image = new float[ndata];
	wxASSERT(image);
	fits_read_img(f,TFLOAT,firstelem,ndata,&nullval,image,&dummy,&stat);
	if( stat ) { delete[] naxes; delete[] image; goto crash; }

	hdu.push_back(FitsArray(head,naxis,naxes,image));
      }
      else {
	hdu.push_back(FitsHdu(head));
      }

    }
    else if( htype == ASCII_TBL || htype == BINARY_TBL ) {

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

      ncols = nc;
      //      float *table = new float[nrows*ncols];
      //      void **table = new void*[ncols];

      //      FitsTable table(head,htype,nrows,ncols);
      vector<FitsTableColumn> cols;

      long frow = 1, felem = 1;
      for(int k = 0; k < ncols; k++ ) {

	int colnum = k + 1;
	int typecode;
	long repeat, width;

	fits_get_coltype(f,colnum,&typecode,&repeat,&width,&stat);
	if( stat ) goto crash;

	if( typecode == TSTRING ) {
	  int width;
	  fits_get_col_display_width(f,colnum,&width,&stat);
	  char **a = new char*[nrows];
	  for(int i = 0; i < nrows; i++)
	    a[i] = new char[width];
	  char nullval[width];
	  strcpy(nullval," ");
	  fits_read_col(f,TSTRING,colnum, frow, felem, nrows, &nullval,
			a,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,a));
	}
	else if( typecode == TLOGICAL ) {
	  bool *b = new bool[nrows];
	  bool nullval = false;
	  fits_read_col(f,TLOGICAL,colnum, frow, felem, nrows, &nullval,
			b,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,b));
	}
	else if( typecode == TBYTE || typecode == TBIT ) {
	  char *b = new char[nrows];
	  char nullval = 0;
	  fits_read_col(f,TBYTE,colnum, frow, felem, nrows, &nullval,
			b,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,b));
	}
	else if( typecode == TSHORT ) {
	  short *d = new short[nrows];
	  short nullval = 0;
	  fits_read_col(f,TSHORT,colnum, frow, felem, nrows, &nullval,
			d,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,d));
	}
	else if( typecode == TLONG || typecode == TINT32BIT ) {
	  long *d = new long[nrows];
	  long nullval = 0;
	  fits_read_col(f,TLONG,colnum, frow, felem, nrows, &nullval,
			d,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,d));
	}
	else if( typecode == TFLOAT ) {
	  float *d = new float[nrows];
	  float nullval = 0.0;
	  fits_read_col(f,TFLOAT,colnum, frow, felem, nrows, &nullval,
			d,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,d));
	}
	else if( typecode == TDOUBLE ) {
	  double *d = new double[nrows];
	  double nullval = 0.0;
	  fits_read_col(f,TDOUBLE,colnum, frow, felem, nrows, &nullval,
			d,&dummy,&stat);
	  cols.push_back(FitsTableColumn(typecode,nrows,d));
	}
	else
	  wxLogDebug("FitsFile::FitsFile: The type code `%d' not implemented yet.",typecode);

	/*
	fits_read_col(f, TFLOAT,colnum, frow, felem, nrows, &nullval,
		      table+k*nrows,&dummy,&stat);
	*/
#ifdef __WXDEBUG__
	if( stat ) { wxLogDebug("FITS unknown fail: %d (check!)",stat); }
#endif
	//	stat = 0;
      }
      //      FitsTable t(head,htype, nrows, ncols, table);
      //      hdu.push_back(t);
      //      FitsTable table(head,htype,cols);
      //      hdu.push_back(table);
      wxASSERT(stat == 0);
      hdu.push_back(FitsTable(head,htype,cols));
    }
    else {
      hdu.push_back(FitsHdu(head));
    }

    // don't delete image and naxes (both will deleted by FitsHdu) !!!!!
  }

  // close file
  fits_close_file(f, &stat);
  if( stat ) goto crash;

  // classify image
  Recognize();
  status = true;
  return;

 crash:

  // save error description
  char emsg[FLEN_ERRMSG];
  while( fits_read_errmsg(emsg) )
    errmsg.Add(wxString(emsg,wxConvUTF8));

  char msg[FLEN_STATUS];
  fits_get_errstatus(stat,msg);
  smsg = wxString(msg,wxConvUTF8);
  wxLogDebug("FitsFile: " + smsg);

  filename = "";
  status = false;
  fits_close_file(f, &stat);
}

FitsFile::~FitsFile()
{
  hdu.clear();
}

bool FitsFile::Status() const
{
  return status;
}

size_t FitsFile::HduCount() const
{
  return hdu.size();
}

int FitsFile::size() const
{
  return hdu.size();
}

FitsHdu FitsFile::Hdu(size_t n) const
{
  wxASSERT(0 <= n && n < hdu.size());
  return hdu[n];
}

FitsHdu FitsFile::FindHdu(const wxString& extname) const
{
  for(size_t n = 0; n < hdu.size(); n++) {
    if( hdu[n].GetExtname() == extname )
      return hdu[n];
  }
  return FitsHdu();
}


void FitsFile::Recognize()
{
  type = FITS_UNKNOWN;
  int nhdu = hdu.size();

  if( nhdu == 1 && hdu[0].Type() == HDU_IMAGE ) {

    // simple, grayscale image
    type = FITS_GRAY;

    // colour image
    FitsArray a(hdu[0]);
    if( a.Naxis() == 3 )
      type = FITS_COLOUR;

  }

  else if( nhdu > 1 )

    // more hdus
    type = FITS_MULTI;

}

int FitsFile::Type() const
{
  return type;
}

wxString FitsFile::Type_str() const
{
  switch (type) {
  case FITS_GRAY: return  "Gray image";
  case FITS_COLOUR: return "Colour image";
  case FITS_MULTI:return  "Multi-extension";
  default: return wxEmptyString;
  }
}


bool FitsFile::HasImage() const
{
  // locate image hdu
  for(size_t k = 0; k < HduCount(); k++)
    if( hdu[k].Type() == HDU_IMAGE )
      return true;
  return false;
}

bool FitsFile::HasFind() const
{
  // locate FIND extension
  for(size_t k = 0; k < HduCount(); k++)
    if( hdu[k].GetExtname() == FINDEXTNAME )
      return true;
  return false;
}

bool FitsFile::HasPhotometry() const
{
  // locate APHOT extension
  for(size_t k = 0; k < HduCount(); k++)
    if( hdu[k].GetExtname() == APEREXTNAME )
      return true;
  return false;
}

bool FitsFile::HasPhcal() const
{
  // locate photometry extension
  for(size_t k = 0; k < HduCount(); k++)
    if( hdu[k].GetExtname() == PHCALNAME )
      return true;
  return false;
}

wxString FitsFile::GetURL() const
{
  return wxFileSystem::FileNameToURL(filename);
}

wxString FitsFile::GetName() const
{
  wxFileName name(filename);
  return name.GetName();
}

wxString FitsFile::GetFullName() const
{
  wxFileName name(filename);
  return name.GetFullName();
}

wxString FitsFile::GetPath() const
{
  wxFileName name(filename);
  if( name.GetPath().IsEmpty() )
    name.AssignCwd();
  return name.GetPath();
}

wxString FitsFile::GetFullPath() const
{
  wxFileName name(filename);
  if( name.GetPath().IsEmpty() ) {
    name.AssignCwd();
    name.SetFullName(filename);
  }
  return name.GetFullPath();
}

bool FitsFile::IsOk() const
{
  return ! filename.IsEmpty();
}

bool FitsFile::IsModified() const
{
  for(size_t k = 0; k < HduCount(); k++) {
    if( Hdu(k).IsModified() )
      return true;
  }
  return false;
}


wxArrayString FitsFile::GetErrorMessage() const
{
  return errmsg;
}

wxString FitsFile::GetErrorDescription() const
{
  return smsg;
}

void FitsFile::Clear()
{
  filename.Clear();
  status = false;
  type = FITS_UNKNOWN;
  hdu.clear();
  errmsg.Clear();
  smsg.Clear();
}


FitsFile::FitsFile(const FitsHdu& h):
  status(true),type(h.Type())
{
  hdu.push_back(h);
}


bool FitsFile::Save(const wxString& name)
{
  int status = 0;
  fitsfile *f;

  fits_create_file(&f,name.fn_str(),&status);
  if( status != 0 ) return false;

  for(size_t k = 0; k < HduCount(); k++) {
    int type = Hdu(k).Type();

    if( type == HDU_IMAGE ) {

      const FitsArray image(Hdu(k));

      long naxis = image.Naxis();
      long *naxes = new long[naxis];
      long fpixel = 1;
      long nelements = 1;
      for(int i = 0; i < naxis; i++) {
	naxes[i] = image.Naxes(i);
	nelements = nelements * naxes[i];
      }

      fits_create_img(f, FLOAT_IMG, naxis, naxes, &status);
      merge_head(f,Hdu(k),&status);
      fits_write_img(f, TFLOAT, fpixel, nelements, (float *) image.PixelData(), &status);


    }
    else if( type == HDU_TABLE ) {

      const FitsTable table(Hdu(k));

      int tfields = table.Ncols();
      char **ttype = new char*[tfields];
      char **tform = new char*[tfields];
      char **tunit = new char*[tfields];

      for(int i = 0; i < table.Ncols(); i++) {
	wxString key;
	key.Printf("TTYPE%d",i+1);
	ttype[i] = wxStrdup(table.GetKey(key));
	key.Printf("TFORM%d",i+1);
	tform[i] = wxStrdup(table.GetKey(key));
	key.Printf("TUNIT%d",i+1);
	tunit[i] = wxStrdup(table.GetKey(key));
      }

      wxString extname(table.GetExtname());

      fits_create_tbl(f,BINARY_TBL,table.Nrows(),table.Ncols(), ttype, tform,
		      tunit, extname.fn_str(), &status);

      delete[] ttype;
      delete[] tform;
      delete[] tunit;

      merge_head(f,table,&status);

      /*
      for(size_t i = 0; i < table.GetCount(); i++) {
	fits_write_record(f,table.Item(i).fn_str(),&status);
      }
      */

      //      wxLogDebug("%d",(int) status);

      long firstrow  = 1;
      long firstelem = 1;
      long nelements = table.Nrows();

      for(int colnum = 0; colnum < table.Ncols(); colnum++) {

	int typecode = table.GetColType(colnum);
	//	wxString form(tform[colnum]);

	//	wxLogDebug(form+" %d",(int)status);
	const FitsTableColumn col(table.GetColumn(colnum));

	if( typecode == TSTRING ) {
	  /*
	  char **a = new char*[nelements];
	  for(int i = 0; i < nelements; i++)
	    a[i] = strdup("");
	  */
	  char **a = (char **) col.GetCol_char();
	  fits_write_col(f, TSTRING, colnum+1, firstrow, firstelem,
			 nelements, a, &status);
//  delete[] a;
	}
	//	else if( form.Find("D") != wxNOT_FOUND ) {
	else if( typecode == TDOUBLE ) {
	  /*
	  double *u = new double[nelements];
	  const float *col = table.GetCol(colnum);
	  for(int i = 0; i < nelements; i++)
	    u[i] = col[i];
	  */
	  //	  wxLogDebug("%f %d %d",col[0],(int)nelements, (int)status);
	  double *d = (double *) col.GetCol_double();
	  fits_write_col(f, TDOUBLE, colnum+1, firstrow, firstelem,
			 nelements, d, &status);
	  //	  delete[] u;
	}
	//	else if( form.Find("J") != wxNOT_FOUND ) {
	else if( typecode == TLONG ) {
	  /*
	  long *d = new long[nelements];
	  const float *col = table.GetCol(colnum);
	  for(int i = 0; i < nelements; i++)
	    d[i] = (int) col[i];
	  */
	  long *d = (long *) col.GetCol_long();
	  fits_write_col(f, TLONG, colnum+1, firstrow, firstelem,
			 nelements, d, &status);
	  //	  delete[] d;
	}
      }
    }
  }

  fits_close_file(f,&status);


  bool s = status == 0;

  if( s )
    filename = name;

  return s;
}

int FitsFile::merge_head(fitsfile *f, const FitsHdu& hdu, int *status) const
{
  int nhead,dummy;
  char h[FLEN_CARD];
  wxArrayString head;

  fits_get_hdrspace(f,&nhead,&dummy,status);

  for(int n = 0; *status == 0 && n < nhead; n++) {
    if( fits_read_record(f,n+1,h,status) == 0 )
      head.Add(wxString(h,wxConvUTF8));
  }

  for(size_t i = 0; i < hdu.GetCount(); i++) {

    wxString record(hdu.Item(i));
    bool presented = false;
    wxString xkey,ykey,value,com;

    FitsHeader::ParseRecord(record,xkey,value,com);
    for(size_t j = 0; j < head.GetCount(); j++) {
      FitsHeader::ParseRecord(head[j],ykey,value,com);
      if( xkey == ykey ) {
	presented = true;
	break;
      }
    }

    if( ! presented )
      fits_write_record(f,record.fn_str(),status);
  }

  return *status;
}



// auxiliary  functions

bool FitsCopyFile(const wxString& in, const wxString& out)
{
  fitsfile *fin,*fout;
  int stat = 0;
  bool result = false;

  fits_open_file(&fin, in.fn_str(), READONLY, &stat);
  fits_create_file(&fout, out.fn_str(), &stat);
  result = fits_copy_file(fin,fout,1,1,1,&stat) == 0;
  fits_close_file(fout,&stat);
  fits_close_file(fin,&stat);

  fits_report_error(stderr,stat);

  return result && stat == 0;
}

bool FitsCopyHdu(const wxString& in, const wxString& out, const wxString& hdu)
{
  fitsfile *fin,*fout;
  int stat = 0;
  int hdutype;
  bool result = false;
  const char *extname = hdu.ToAscii();

  fits_open_file(&fin, in.fn_str(), READONLY, &stat);
  fits_open_file(&fout, out.fn_str(), READWRITE, &stat);
  fits_movnam_hdu(fin,BINARY_TBL,(char*)extname,0,&stat);
  if( fits_movnam_hdu(fout,BINARY_TBL,(char*)extname,0,&stat) == 0 )
    fits_delete_hdu(fout,&hdutype,&stat);
  if( stat == BAD_HDU_NUM )
    stat = 0;
  result = fits_copy_hdu(fin,fout,0,&stat) == 0;
  fits_close_file(fout,&stat);
  fits_close_file(fin,&stat);

  fits_report_error(stderr,stat);

  return result && stat == 0;
}


wxArrayString FitsColumns(const wxString& name)
{
  fitsfile *f;
  int stat = 0;
  int keysexist, morekeys;
  char keyname[FLEN_KEYWORD],value[FLEN_VALUE],com[FLEN_COMMENT];
  wxArrayString cols;

  fits_open_table(&f, name.fn_str(), READONLY, &stat);
  fits_get_hdrspace(f,&keysexist,&morekeys,&stat);
  for(int i = 1; i <= keysexist; i++) {
    fits_read_keyn(f,i,keyname,value,com,&stat);
    if( stat == 0 && strncmp(keyname,"TTYPE",5) == 0 ) {

      char *i1 = index(value,'\'');
      char *i2 = rindex(value,'\'');
      if( i1 != NULL && i2 != NULL && i1 != i2 ) {
	int l = i2 - i1 - 1;
	for(int i = 0; i < l; i++)
	  value[i] = *(i1 + i + 1);
	value[l] = '\0';
      }
      cols.Add(value);
    }
  }
  fits_close_file(f,&stat);
  fits_report_error(stderr,stat);

  return cols;
}
