/*

  VOTable parser


  Copyright © 2010-2015, 2017 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/>.


  Reference:

  http://www.ivoa.net/Documents/VOTable/20091130/REC-VOTable-1.2.html


  I've doubts about determination of coordinate frame. Vizier offers
  a different structure for UCAC4 and UCAC5 catalogues. It means that
  determination of epoch of catalogue coordinates cann't be assumed
  as a simple parameter. Does it look as the maintainers proposed
  another way of processing?

*/

#include "votable.h"
#include "mfitsio.h"
#include <wx/wx.h>
#include <wx/xml/xml.h>
#include <wx/uri.h>
#include <wx/url.h>
#include <wx/filesys.h>
#include <wx/fs_inet.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include <wx/sstream.h>
#include <wx/file.h>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/regex.h>
#include <fitsio.h>
#include <cmath>
#include <map>
#include <vector>
#include <algorithm>
#include <utility>
#include <string>

using namespace std;



VOInfoBasic::VOInfoBasic(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();

  wxXmlAttribute *prop = root->GetAttributes();
  while(prop) {
    params[prop->GetName()] = prop->GetValue();
    //    wxLogDebug(_("Field: ")+prop->GetName()+_("= ")+prop->GetValue());
    prop = prop->GetNext();
  }


  while (node) {

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    /*
    else if( node->GetName() == wxT("VALUE") )
      values.push_back(node->GetNodeContent());
    */

    else if( node->GetName() == "LINK" )
      links.push_back(node->GetNodeContent());

    node = node->GetNext();
  }
}

wxString VOField::GetLabel() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "name" ) // ID ???
      return i->second;

  return wxEmptyString;
}

wxString VOField::GetType() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "datatype" )
      return i->second;

  return wxEmptyString;
}

wxString VOField::GetUnit() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "unit" )
      return i->second;

  return wxEmptyString;
}

wxString VOField::GetArraySize() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "arraysize" )
      return i->second;

  return wxEmptyString;
}


wxString VOCooSys::GetEquinox() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "equinox" )
      return i->second;

  return wxEmptyString;
}

wxString VOCooSys::GetCooSys() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i)
    if( i->first == "system" )
      return i->second;

  return wxEmptyString;
}

double VOCooSys::GetEpoch() const
{
  map<wxString,wxString>::const_iterator i;
  for(i = params.begin(); i != params.end(); ++i) {
    if( i->first == "epoch" ) {
      double e;
      if( (i->second).ToDouble(&e) )
	return e;
      else
	return 2000.0;
    }
  }
  return 2000.0;
}


VOGroup::VOGroup(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();
  while (node) {

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "PARAMref" )  // ????
      refs.push_back(node->GetNodeContent());

    node = node->GetNext();
  }
}



VOTableData::VOTableData(): nrows(0) {}

VOTableData::VOTableData(const wxXmlNode *root): nrows(0)
{
  wxASSERT(root);

  wxXmlNode *tr = root->GetChildren();
  while (tr) {

    if( tr->GetName() == "TR" ) {

      nrows++;
      rows.push_back(tr);

      wxXmlNode *td = tr->GetChildren();
      while (td) {
	elements.push_back(td->GetNodeContent());
	td = td->GetNext();
      }

    }

    tr = tr->GetNext();
  }
}




VOData::VOData() {}

VOData::VOData(const wxXmlNode *root)
{
  wxASSERT(root);

  wxXmlNode *node = root->GetChildren();
  while (node) {

    if( node->GetName() == "TABLEDATA" ) {
      tablenode = node;
      table = VOTableData(node);
    }
    else
      wxLogDebug("Only TABLEDATA element is supported.");

    node = node->GetNext();
  }
}




VOTableTable::VOTableTable(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();
  while (node) {

    //    wxLogDebug(node->GetName());

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "FIELD" )
      fields.push_back(VOField(node));

    else if( node->GetName() == "INFO" )
      infos.push_back(VOInfo(node));

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "GROUP" )
      groups.push_back(VOGroup(node));

    else if( node->GetName() == "LINK" )
      links.push_back(node->GetNodeContent());

    else if( node->GetName() == "DATA" )
      data = VOData(node);

    node = node->GetNext();
  }
}

int VOTableTable::RecordCount() const
{
  return data.table.nrows;
}

vector<wxString> VOTableTable::GetRecord(int n) const
{
  vector<wxString> r;
  int ncol = fields.size();

  //  wxLogDebug(_("%d ")+data.table.elements[1],ncol);

  for(int i = 0; i < ncol; i++)
    r.push_back(data.table.elements[n*ncol+i]);

  return r;
}

vector<wxString> VOTableTable::GetColumn(int n) const
{
  vector<wxString> c;
  int ncol = fields.size();

  //  wxLogDebug(_("%d ")+data.table.elements[1],ncol);

  for(int i = 0; i < data.table.nrows; i++)
    c.push_back(data.table.elements[i*ncol+n]);

  return c;
}



VOResource::VOResource(const wxXmlNode *root)
{
  wxASSERT(root);

  const wxXmlNode *node = root->GetChildren();
  while (node) {

    //    wxLogDebug(node->GetName());

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "INFO" )
      infos.push_back(VOInfo(node));

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "GROUP" )
      groups.push_back(VOGroup(node));

    else if( node->GetName() == "LINK" )
      links.push_back(node->GetNodeContent());

    else if( node->GetName() == "COOSYS" )
      coosys.push_back(VOCooSys(node));

    else if( node->GetName() == "TABLE" )
      tables.push_back(VOTableTable(node));


    node = node->GetNext();
  }
}


VOTable::VOTable(const wxURL& url): wxXmlDocument(),index(0),timeout(60)
{
  canvas_width = 500;
  canvas_height = 500;
  mag_limit = 15.0;
  proj_scale = 1000.0;

  int twait = 1;
  bool try_again = true;
  while ( try_again ) {

    wxFileSystem fs;
    fs.AddHandler(new wxInternetFSHandler);
    //    wxLogDebug(url.BuildURI());
    wxFSFile *f = fs.OpenFile(url.BuildURI());
    if( f ) {
      wxInputStream *s = f->GetStream();
      Load(*s);
      try_again = false;
    }
    delete f;

    if( try_again ) {
      wxSleep(twait);
      twait = 2*twait;

      if( twait > timeout ) {
	wxLogError("Failed establish connection.");
	return;
      }
    }
  }

  if( IsOk() )
    Parse();
}

VOTable::VOTable(const wxXmlDocument& vt): wxXmlDocument(vt),index(0),timeout(120)
{
  canvas_width = 500;
  canvas_height = 500;
  mag_limit = 15.0;
  proj_scale = 1000.0;

  if( !( vt.IsOk() && vt.GetRoot()->GetName() == "VOTABLE" ) )
    return;
  Parse();
}

bool VOTable::Parse()
{
  description.Clear();
  params.clear();
  groups.clear();
  resources.clear();
  infos.clear();


  //wxASSERT(IsOk() && GetRoot()->GetName() == "VOTABLE");

  const wxXmlNode *node = GetRoot()->GetChildren();
  while (node) {

    if( node->GetName() == "DESCRIPTION" )
      description = node->GetNodeContent();

    else if( node->GetName() == "INFO" )
      infos.push_back(VOInfo(node));

    else if( node->GetName() == "PARAM" )
      params.push_back(VOParam(node));

    else if( node->GetName() == "GROUP" )
      groups.push_back(VOGroup(node));

    else if( node->GetName() == "RESOURCE" )
      resources.push_back(VOResource(node));

    node = node->GetNext();
  }

  //  wxASSERT(resources.size() > 0);
  //  isok = resources.size() > 0;
  return resources.size() > 0;
}

void VOTable::SetProjection(const wxString& a) { proj_type = a;}
void VOTable::SetProjectionCenter(double a,double d)
{
  proj_alpha = a;
  proj_delta = d;
}
void VOTable::SetCanvasSize(long w, long h)
{
  canvas_width = w;
  canvas_height = h;
}
void VOTable::SetScale(double c) { proj_scale = c; }
void VOTable::SetMaglim(double x) { mag_limit = x; }
void VOTable::SetMagkey(const wxString& a) { mag_key = a; }
void VOTable::SetAlphakey(const wxString& a) { alpha_key = a; }
void VOTable::SetDeltakey(const wxString& a) { delta_key = a; }

wxString VOTable::GetDescription() const { return description; }

double VOTable::GetEpoch() const
{
  if( resources.size() > (size_t) index && resources[index].coosys.size() == 1 )
    return resources[index].coosys[0].GetEpoch();
  else
    return 2000.0;
}


int VOTable::RecordCount() const
{
  wxASSERT(resources[index].tables.size() == 1);
  return resources[index].tables[0].RecordCount();
}

vector<wxString> VOTable::GetResources() const
{
  vector<wxString> fr;
  for(size_t i = 0; i < resources.size(); i++)
    fr.push_back(resources[i].description);
  return fr;
}

vector<wxString> VOTable::GetFields() const
{
  vector<wxString> fl;
  if( resources.empty() ) return fl;
  //  wxLogDebug(_("%d %d"),resources.size(),resources[index].tables.size());
  wxASSERT(resources[index].tables.size() == 1);

  VOTableTable t = resources[index].tables[0];

  for(size_t i = 0; i < t.fields.size(); i++)
    fl.push_back(t.fields[i].GetLabel());
  return fl;
}

vector<wxString> VOTable::GetTypes() const
{
  //  wxLogDebug(_("%d %d"),resources.size(),resources[index].tables.size());
  wxASSERT(resources[index].tables.size() == 1);

  VOTableTable t = resources[index].tables[0];

  vector<wxString> fl;
  for(size_t i = 0; i < t.fields.size(); i++)
    fl.push_back(t.fields[i].GetType());
  return fl;
}

vector<wxString> VOTable::GetUnits() const
{
  //  wxLogDebug(_("%d %d"),resources.size(),resources[index].tables.size());
  wxASSERT(resources[index].tables.size() == 1);

  VOTableTable t = resources[index].tables[0];

  vector<wxString> fl;
  for(size_t i = 0; i < t.fields.size(); i++)
    fl.push_back(t.fields[i].GetUnit());
  return fl;
}

vector<wxString> VOTable::GetRecord(int i) const
{
  wxASSERT(resources[index].tables.size() == 1);
  VOTableTable t = resources[index].tables[0];
  return t.GetRecord(i);
}


vector<wxString> VOTable::GetColumn(int i) const
{
  wxASSERT(resources[index].tables.size() == 1);
  VOTableTable t = resources[index].tables[0];
  return t.GetColumn(i);
}

bool VOTable::Save(const wxString& filename, const wxString& backup, const wxString& typ)
{
  wxASSERT(IsOk());

  wxString type(typ);

  // try, to determine output type
  if( type.IsEmpty() && ! filename.IsEmpty() ) {
    wxFileName fn(filename);
    if( fn.IsOk() )
      type = fn.GetExt().Upper();
  }

  if( type == "FITS" && ! filename.IsEmpty() )
    return SaveFITS(filename,backup);

  wxFFile ffile;
  if( filename.IsEmpty() )
    ffile.Attach(stdout);
  else {
    if( ! backup.IsEmpty() )
      wxRenameFile(filename,backup);
    ffile.Open(filename.c_str(),"w");
  }

  if( ! ffile.IsOpened() )
    return false;

  wxFFileOutputStream output(ffile);

  //  wxFFileOutputStream output(filename.IsEmpty() ? wxFile::fd_stdout : filename);

      //  wxFFileOutputStream o(filename);
  //  wxLogDebug("*****");

  return Save(output,type);
}

bool VOTable::Save(wxOutputStream& ostream, const wxString& type)
{
  if( type.IsEmpty() || type == "XML" )
    return wxXmlDocument::Save(ostream);

  else if( type == "CSV" )
    return SaveCSV(ostream);

  else if( type == "TXT" )
    return SaveText(ostream);

  else if( type == "SVG" )
    return SaveSVG(ostream);

  else {
    wxLogFatalError("Type `"+type+"' unrecognized.");
    return false;
  }
}


vector<wxString> VOTable::TypeTrafo(const vector<wxString>& type)
{
  // perhaps obsoleted by FitsType

  vector<wxString> tform;
  for(vector<wxString>::const_iterator i = type.begin(); i != type.end(); ++i){

    if( *i == "char" )
      tform.push_back("A");
    else if( *i == "double" )
      tform.push_back("1D");
    else if( *i == "boolean" )
      tform.push_back("1L");
    else if( *i == "bit" )
      tform.push_back("1X");
    else if( *i == "unsignedByte" )
      tform.push_back("1B");
    else if( *i == "short" )
      tform.push_back("1I");
    else if( *i == "int" )
      tform.push_back("1J");
    else if( *i == "long" )
      tform.push_back("1K");
    else if( *i == "float" )
      tform.push_back("1E");
    else if( *i == "floatComplex" )
      tform.push_back("1C");
    else if( *i == "doubleComplex" )
      tform.push_back("1M");
    else if( *i == "unicodeChar" )
      tform.push_back("");
    else
      wxLogFatalError("Unsupported data type");

  }
  return tform;
}

vector<wxString> VOTable::FitsTypes()
{
  wxASSERT(resources[index].tables.size() == 1);

  vector<VOField> fields = resources[index].tables[0].fields;

  vector<wxString> tform;
  for(vector<VOField>::const_iterator f = fields.begin(); f != fields.end(); ++f){

    wxString datatype = f->GetType();
    wxString arraysize = f->GetArraySize();

    if( datatype == "char" ) {
      if( ! arraysize.empty() ) {
	arraysize.Replace("*","");
	long n;
	if( arraysize.ToLong(&n) ) {
	  wxString a;
	  a.Printf("%d",int(n));
	  tform.push_back(a+"A");
	}
      }
      else
	tform.push_back("1A");
    }
    else if( datatype == "double" )
      tform.push_back("1D");
    else if( datatype == "boolean" )
      tform.push_back("1L");
    else if( datatype == "bit" )
      tform.push_back("1X");
    else if( datatype == "unsignedByte" )
      tform.push_back("1B");
    else if( datatype == "short" )
      tform.push_back("1I");
    else if( datatype == "int" )
      tform.push_back("1J");
    else if( datatype == "long" )
      tform.push_back("1K");
    else if( datatype == "float" )
      tform.push_back("1E");
    else if( datatype == "floatComplex" )
      tform.push_back("1C");
    else if( datatype == "doubleComplex" )
      tform.push_back("1M");
    else if( datatype == "unicodeChar" )
      tform.push_back("");
    else
      wxLogFatalError("Unsupported data type");

  }
  return tform;
}


char **VOTable::GetArray(const vector<wxString>& vec)
{
  int n = vec.size();
  char **x = new char*[n];

  for(int j = 0; j < n; j++) {
    size_t l = vec[j].Len();
    char *a = new char[l + 1];
    if( l > 0 ) {
      for(size_t i = 0; i < l; i++)
	a[i] = vec[j].GetChar(i);
      a[l] = '\0';
    }
    else
      a[0] = '\0';
    x[j] = a;
  }
  return x;
}

bool VOTable::SaveText(wxOutputStream& output)
{
  if( ! IsOk() ) return false;
  /*
  wxFFile ffile;
  if( file.IsEmpty() )
    ffile.Attach(stdout);
  else
    ffile.Open(file.c_str(),_("w"));
  wxFFileOutputStream output(ffile);
  */
  wxTextOutputStream cout(output);

  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( i != fs.begin() ) cout << "\t";
    wxString a(*i);
    a.Replace("\n",""); a.Replace("\r","");
    cout << a;
  }
  cout << endl;

  for(int n = 0; n < RecordCount(); n++) {

    vector<wxString> r = GetRecord(n);

    for(vector<wxString>::const_iterator i = r.begin(); i != r.end(); ++i) {
      if( i != r.begin() ) cout << " \t";
      wxString a(*i);
      a.Replace("\n",""); a.Replace("\r","");
      cout << a;
    }
    cout << endl;
  }

  return true;
}

bool VOTable::SaveCSV(wxOutputStream& output)
{
  // http://en.wikipedia.org/wiki/Comma-separated_values

  if( ! IsOk() ) return false;

  //  wxFFileOutputStream output(file);
  wxTextOutputStream cout(output);

  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( i != fs.begin() ) cout << ",";
    wxString a(*i);
    a.Replace("\n",""); a.Replace("\r","");
    cout << a;
  }
  cout << endl;

  for(int n = 0; n < RecordCount(); n++) {

    vector<wxString> r = GetRecord(n);

    for(vector<wxString>::const_iterator i = r.begin(); i != r.end(); ++i) {
      if( i != r.begin() ) cout << ",";
      wxString a(*i);
      a.Replace("\n",""); a.Replace("\r","");
      cout << a;
    }

    cout << endl;
  }

  return true;
}

bool VOTable::SaveFITS(const wxString& fitsname, const wxString& backup)
{
  if( ! IsOk() ) return false;

  fitsbackup(fitsname.ToStdString(),backup.ToStdString(),true);

  vector<wxString> fr = GetResources();
  vector<wxString> fs = GetFields();
  vector<wxString> ft = GetTypes();
  vector<wxString> fu = GetUnits();

  wxASSERT(fr.size() > 0);
  wxASSERT(fs.size() == ft.size() && ft.size() == fu.size());

  //  vector<wxString> tf = TypeTrafo(ft);
  vector<wxString> tf = FitsTypes();
  vector<wxString> tu = fu;

  // Vizier catalogues reports magnitude errors (e_Vmag) in an obscure
  // format as type character (or short in past) like '02' for 0.02 (!?).
  // These values are converted to more convenient form.
  for(size_t i = 0; i < tf.size(); i++) {
    if( fu[i] == "cmag" ) {
      ft[i] = "float";
      tu[i] == "mag";
      tf[i] = "1E";
    }
  }

  char **ttype = GetArray(fs);
  char **tform = GetArray(tf);
  char **tunit = GetArray(tu);

  fitsfile *file;
  int status = 0;
  const char *ehdu = fr[0].fn_str();
  fits_create_file(&file, fitsname.fn_str(), &status);
  fits_create_tbl(file,BINARY_TBL,0,fs.size(),ttype,tform,tunit,
		  (char *) ehdu,&status);

  for(size_t j = 0; j < fs.size(); j++) {
    delete[] ttype[j];
    delete[] tform[j];
    delete[] tunit[j];
  }
  delete[] ttype;
  delete[] tform;
  delete[] tunit;

  double epoch = GetEpoch();
  fits_write_key(file,TDOUBLE,"EPOCH",&epoch,"reference time (epoch)",&status);

  for(size_t i = 0; i < fs.size() && status == 0 ; i++) {

    vector<wxString> c = GetColumn(i);
    size_t n = c.size();

    if( ft[i] == "boolean" ) {
      char *x = new char[n];
      for(size_t j = 0; j < n; j++) {
	if( c[j]== "T" || c[j]== "t" || c[j]== "1" )
	  x[j] = 'T';
	else if( c[j]== "F" || c[j]== "f" || c[j]== "0" )
	  x[j] = 'F';
	else {
	  x[j] = ' ';
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to boolean.",(int)j);
	  }
	}
      }
      fits_write_col(file,TLOGICAL,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "bit" ) {
      char *x = new char[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	if( c[j].ToLong(&t) && (t == 0 || t == 1) )
	  x[j] = t;
	else {
	  x[j] = 0;
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to bit(s).",(int)j);
	  }
	}
      }
      fits_write_col(file,TBIT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "unsignedByte" ) {
      unsigned char *x = new unsigned char[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	if(  c[j].ToLong(&t) && (0 <= t && t < 256) )
	  x[j] = t;
	else {
	  x[j] = 0;
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to uchar.",(int)j);
	  }
	}
      }
      fits_write_col(file,TBYTE,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "short" ) {
      short *x = new short[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	if( c[j].ToLong(&t) )
	  x[j] = t;
	else {
	  x[j] = 0;
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to short.",(int)j);
	  }
	}
      }
      fits_write_col(file,TSHORT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "int" ) {
      int *x = new int[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	if( c[j].ToLong(&t) )
	  x[j] = t;
	else {
	  x[j] = 0;
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to int.",(int)j);
	  }
	}
      }
      fits_write_col(file,TINT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "long" ) {
      long *x = new long[n];
      for(size_t j = 0; j < n; j++) {
	long t;
	if( c[j].ToLong(&t) )
	  x[j] = t;
	else {
	  x[j] = 0;
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to long.",(int)j);
	  }
	}
      }
      fits_write_col(file,TLONG,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "char" || ft[i] == "unicodeChar" ) {
      char **x = GetArray(c);
      fits_write_col(file,TSTRING,i+1,1,1,n,x,&status);
      for(size_t j = 0; j < n; j++)
	delete[] x[j];
      delete[] x;
    }

    else if( ft[i] == "float" ) {
      float scale = fu[i] == "cmag" ? 0.01 : 1.0;
      float *x = new float[n];
      for(size_t j = 0; j < n; j++) {
	double t;
	if( c[j].ToDouble(&t) )
	  x[j] = scale*t;
	else {
	  x[j] = nan("");
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to float.",(int)j);
	  }
	}
      }
      fits_write_col(file,TFLOAT,i+1,1,1,n,x,&status);
      delete[] x;
    }

    else if( ft[i] == "double" ) {
      double scale = fu[i] == "cmag" ? 0.01 : 1.0;
      double *x = new double[n];
      for(size_t j = 0; j < n; j++) {
	double t;
	if( c[j].ToDouble(&t) )
	  x[j] = scale*t;
	else {
	  x[j] = nan("");
	  if( c[j] != "" ) {
	    wxLogError("Failed convert element `"+c[j]+"' at row=%d col="+fs[i]+
		       " to double.",(int)j);
	  }
	}
      }
      fits_write_col(file,TDOUBLE,i+1,1,1,n,x,&status);

      delete[] x;
    }

    else if( ft[i] == "floatComplex" ) {
      float *x = new float[2*n];
      for(size_t j = 0; j < n; j++) {
	wxStringInputStream is(c[j]);
	wxTextInputStream ts(is);
	ts >> x[2*j] >> x[2*j+1];
      }
      fits_write_col(file,TCOMPLEX,i+1,1,1,2*n,x,&status);
      delete[] x;
    }
    else if( ft[i] == "doubleComplex" ) {
      double *x = new double[2*n];
      for(size_t j = 0; j < n; j++) {
	wxStringInputStream is(c[j]);
	wxTextInputStream ts(is);
	ts >> x[2*j] >> x[2*j+1];
      }
      fits_write_col(file,TDBLCOMPLEX,i+1,1,1,2*n,x,&status);
      delete[] x;
    }

  }

  if( fits_close_file(file, &status) )
    fits_report_error(stderr,status);

  return status == 0;
}

double VOTable::ToDouble(const wxString& a)
{
  double x;
  a.ToDouble(&x);
  return x;
}

bool VOTable::SaveSVG(wxOutputStream& output)
{
  int nra = -1, ndec = -1, nmag = -1;
  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( *i == alpha_key ) {
      nra = i - fs.begin();
    }
    else if( *i == delta_key ) {
      ndec = i - fs.begin();
    }
    else if( *i == mag_key ) {
      nmag = i - fs.begin();
    }
  }

  if( nra == -1 || ndec == -1 || nmag == -1 ) {
    wxLogError("RA, Dec of magnitude column has no data.");
    return false;
  }

  vector<wxString> xra = GetColumn(nra);
  vector<wxString> xdec = GetColumn(ndec);
  vector<wxString> xmag = GetColumn(nmag);

  wxTextOutputStream cout(output);
  cout << "<svg xmlns=\"http://www.w3.org/2000/svg\""
       << " width=\"" << int(canvas_width)
       << "\" height=\"" << int(canvas_height) << "\">" << endl;

  // WARNING: only simple gnonomical projection is implemented

  double c = xdec.size() > 0 ? cos(ToDouble(xdec[0])/57.3) : 1.0;
  for(size_t i = 0; i < xra.size(); i++) {
    double ra = ToDouble(xra[i]);
    double dec = ToDouble(xdec[i]);
    double mag = ToDouble(xmag[i]);
    double x = canvas_width/2 - proj_scale*(ra - proj_alpha)*c;
    double y = canvas_height/2 - proj_scale*(dec - proj_delta);//reverse y-axis!
    double r = 3.0*pow(10.0,0.11*(mag_limit - mag));

    if( 0 <= x && x <= canvas_width && 0 <= y && y <= canvas_height )
      cout << "<circle cx=\"" << x << "\" cy=\"" << y
	   << "\" r=\"" << r << "\"/>" << endl;
  }

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



// needs to add other types
bool cmp(pair<int,double> a, pair<int,double> b)
{
  //  wxLogDebug("%f %f",b.second,a.second);
  return b.second > a.second;
}

bool VOTable::Sort(const wxString& key)
{
  int n = -1;
  vector<wxString> fs = GetFields();
  for(vector<wxString>::const_iterator i = fs.begin(); i != fs.end(); ++i) {
    if( *i == key ) {
      n = i - fs.begin();
      break;
    }
  }

  if( n == -1 )
    return false;

  vector< pair<int,double> > d;

  vector<wxString> col = GetColumn(n);

  for(vector<wxString>::const_iterator i = col.begin(); i != col.end(); ++i) {
    wxString a(*i);
    double x;
    a.ToDouble(&x);
    d.push_back(make_pair(i-col.begin(),x));
  }

  sort(d.begin(),d.end(),cmp);

  /*
  for(vector< pair<int,double> >::const_iterator i = d.begin(); i != d.end(); ++i)
  wxLogDebug("%d %f",i->first,i->second);
  */

  wxASSERT(resources[index].tables.size() == 1);

  if( ! (resources[index].tables[0].RecordCount() > 1) )
    return true;

  wxXmlNode *t = resources[index].tables[0].data.tablenode;
  wxXmlNode *nt = new wxXmlNode(t->GetParent(),t->GetType(),
				t->GetName(),t->GetContent(),
				t->GetAttributes(),t->GetNext(),
				t->GetLineNumber());

  vector<wxXmlNode *> rows(resources[index].tables[0].data.table.rows);

  for(vector< pair<int,double> >::const_iterator i=d.begin(); i!=d.end(); ++i)
    nt->AddChild(rows[i->first]);

  wxXmlNode *parent = t->GetParent();
  parent->RemoveChild(t);

  return Parse();
}



// ------------------------------------------------------------------
// Common functions

// it would be nice to create a fortran I/O interoperability functions (library)?


wxString GetString(const wxString& line)
{
  wxRegEx re(".+ = '(.*)'");
  // double quotes in string:  "'(''|[^'])*'" - perhaps unfunctional
  //  wxRegEx re(".+ = '(''|[^']*)'");
  wxASSERT(re.IsValid());
  if( re.Matches(line) )
    return re.GetMatch(line,1);
  else
    return "";
}

double GetDouble(const wxString& line)
{
  double x;
  wxString a = line.AfterFirst('=');

  if( a.ToCDouble(&x) )
    return x;

  wxLogFatalError("Failed to read the number: "+a);
  return 666; // formally
}

long GetLong(const wxString& line)
{
  long l;
  wxString a = line.AfterFirst('=');

  if( a.ToCLong(&l) )
    return l;

  wxLogFatalError("Failed to read the number: "+a);
  return 0; // formally
}

bool GetBool(const wxString& line)
{
  wxRegEx re(".+ = (.*)");
  wxASSERT(re.IsValid());
  if( re.Matches(line) ) {
    wxString a = re.GetMatch(line,1);
    a.Upper();
    if( a.StartsWith("T") )
      return true;
    else if( a.StartsWith("F") )
      return false;
  }
  wxLogFatalError("Failed to parse boolean (logical) expression: `"+line+"'.");
  return false; // formally
}

void GetOutput(const wxString& line, wxString& output, wxString& backup)
{
  wxRegEx re(".+ = '(.*)' '(.*)'");
  wxASSERT(re.IsValid());
  if( re.Matches(line) ) {
    output = re.GetMatch(line,1);
    backup = re.GetMatch(line,2);
  }
}
