


#include "ast_frameset.hpp"
#include "fits_header.hpp"
#include "io.hpp"
#include "my_assert.hpp"

#include <stdexcept>

/* for alomost_equal(double,double,int)*/
#include <cmath>
#include <limits>
#include <ostream>
#include <array>
#include <iterator>// begin() end()

#include <math.h> // M_PI

#define D2R (M_PI/180.0)
#define R2D (180.0/M_PI)

const int AXES_CNT{5};

using namespace std;

/* NOTE in memory management: */
/* AST 13.9 : [astGetFrame] 'would return a pointer (not a copy) to the base Frame' */
/* AST p.246: [astGetFrame] 'increments the RefCount attribute of the selected Frame by one.' */
/* AST p195: [astAnnul] 'function also decrements the Object’s RefCount attribute by one' */

string ast_status_string()
{
   switch(astStatus)
   {
      case AST__NODEF: return "AST__NODEF";
      default: return to_string(astStatus);

   }
}

string failed_with_status(const char * file, int line, string func)
{
   return string{file} + ":" + to_string(line) + " " + func + " failed with status: " + ast_status_string();
}


ast::frameset:: ~frameset()
{
   LOG_trace(__func__);
   astClearStatus;
   astEnd;
   LOG_STREAM << "~framset desctructor finished" << endl;
};

ast::frameset::frameset(string header)
   :m_NAXISn(AXES_CNT)
   ,m_hdr_fs((AstFrameSet*)AST__NULL)
   ,m_has_specframe{false}
   ,m_has_stokes_axis{false}
   ,m_has_time_axis{false}
   ,m_axis_type(AXES_CNT,' ')
{
   LOG_trace(__func__);

   astClearStatus;
   astBegin;

   AstFitsChan * fchan = astFitsChan( NULL, NULL, " " );

   astPutCards(fchan, header.c_str());
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astPutCards"));

   const char * encoding = astGetC(fchan,"Encoding");
   LOG_STREAM << __func__ << " : Encoding: " << (encoding == AST__NULL ? "NULL" : encoding) << endl;

   int NAXIS;
   astGetFitsI( fchan, "NAXIS", &NAXIS );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFitsI(NAXIS)"));

   if( (NAXIS > AXES_CNT) || (NAXIS < 0) )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"FITS NAXIS out or range : 1 .. " + to_string(AXES_CNT) ));

   m_NAXIS = NAXIS;


   int NAXISn[AXES_CNT];

   int ix;
   for( ix = 0; ix < NAXIS; ix++ )
   {
      char keyword[ 9 + 7 ];// +7 silence warning about ix being int can be too long
      sprintf( keyword, "NAXIS%d", ix + 1 );

      if( !astGetFitsI( fchan, keyword, &(NAXISn[ix]) ) )
         throw runtime_error(failed_with_status(__FILE__,__LINE__, "astGetFitsI(" + string(keyword) + ") "));
   }

//   std::vector<int> naxis_arr(AXES_CNT);
//   std::copy(std::begin(NAXISn), std::end(NAXISn), naxis_arr.begin());
   std::copy(std::begin(NAXISn), std::end(NAXISn), m_NAXISn.begin());


   astClear(fchan,"Card");// rewind channel to first card
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astClear(fchan)"));

   AstObject * object = (AstObject*)astRead( fchan );
   if ( (!astOK) || (object == AST__NULL))
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astReadCards"));


   if( !object )
   {
      LOG_STREAM << "Failed to read an AST Object from header" << endl;
   }
   else if( !astIsAFrameSet( object ) )
   {
      log_warnings(fchan);;

      const char * astclass = astGetC( object, "Class" );
      if ( (!astOK) || (astclass == AST__NULL))
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC(Class)"));
      else
         throw invalid_argument(string{__FILE__}+":"+string{__LINE__}+": expected a FrameSet but read a " + string{astclass});
   }
   else
   {
      log_warnings(fchan);

      m_hdr_fs = (AstFrameSet *) object;
   }

   astAnnul(fchan);

   m_has_specframe = has_specframe();
   LOG_STREAM << "m_has_specframe: " << boolalpha << m_has_specframe << endl;
   if(m_has_specframe) set_spec_axis();

   set_pol_time_sky_axis();

   assert_valid_state();

   serialize(LOG_STREAM); LOG_STREAM << endl;
}



bool is_specdomain(AstFrame * frm)
{
   const char * val = astGetC(frm,"Domain");
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( Domain )"));

   string domain_val{val};

   return ( (domain_val.compare("SPECTRUM") == 0) ||
            (domain_val.compare("DSBSPECTRUM") == 0) );
}



/* frame : AstCmpFrame or AstFrame */
bool ast::frameset::has_specframe(void)
{
   LOG_trace(__func__);

   AstFrame * frame = (AstFrame*)astGetFrame(m_hdr_fs, AST__CURRENT);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame"));

   AstCmpFrame * cmpframe = (AstCmpFrame*)frame;

   AstCmpFrame * first  = (AstCmpFrame*)AST__NULL;
   AstFrame * second = (AstFrame*)AST__NULL;

   int cnt = 0;
   do
   {
      int series,invfirst,invsecond;

      astDecompose((AstMapping*)cmpframe,
            (AstMapping**)&first,
            (AstMapping**)&second,
            &series,&invfirst,&invsecond);
      if ( !astOK ) 
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astDecompose"));

      AstFrame * frm = (AstFrame*)AST__NULL; 

      if(second == AST__NULL)
      {
         frm = (AstFrame*)first;
      }
      else
      {
         frm = second;;
      }

      int isspec = astIsASpecFrame((AstFrame*)frm);
      if(isspec && is_specdomain(frm)) return true;

      cmpframe = first;

      cnt++;

   } while(second != AST__NULL);

   return false;
}


void* ast::frameset::find_skyframe(void)
{
   LOG_trace(__func__);

   AstFrame * frame = (AstFrame*)astGetFrame(m_hdr_fs, AST__CURRENT);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame"));

   AstCmpFrame * cmpframe = (AstCmpFrame*)frame;

   AstCmpFrame * first  = (AstCmpFrame*)AST__NULL;
   AstFrame * second = (AstFrame*)AST__NULL;

   int cnt = 0;
   do
   {
      int series,invfirst,invsecond;

      astDecompose((AstMapping*)cmpframe,
            (AstMapping**)&first,
            (AstMapping**)&second,
            &series,&invfirst,&invsecond);
      if ( !astOK ) 
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astDecompose"));

      AstFrame * frm = (AstFrame*)AST__NULL; 

      if(second == AST__NULL)
      {
         frm = (AstFrame*)first;
      }
      else
      {
         frm = second;;
      }

      int issky = astIsASkyFrame((AstFrame*)frm);
      if(issky) return (void*) frm;

      cmpframe = first;

      cnt++;

   } while(second != AST__NULL);

   return nullptr;
}




bool ast::frameset::has_timeaxis(void)
{
   LOG_trace(__func__);
   return m_has_time_axis;
}



/* FIXME what spectral types should be considered or look for sub-string SPECTRUM not full string match */
/* Or we just say NOT SUPPORTED and print the domain name */ 
void ast::frameset::set_spec_axis(void)
{
   LOG_trace(__func__);

   int naxes = astGetI( m_hdr_fs, "Naxes" );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Naxes )"));

   LOG_STREAM << "m_NAXIS vs Naxes :" << m_NAXIS << " vs " << naxes << endl;


   bool set_axis{false};
   LOG_STREAM << "Domains in header FrameSet(CURRENT): ";
   int axis;
   for(axis=1; axis<(naxes+1);axis++)
      //for(axis=1; axis<(m_NAXIS+1);axis++)
   {
      string domain{"Domain("+to_string(axis)+")"};
      const char * val =  astGetC(m_hdr_fs,domain.c_str());
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( " + domain + ")"));

      string domain_val{val};
      LOG_STREAM << " " << domain_val;
      if((domain_val.compare("SPECTRUM") == 0) ||
            (domain_val.compare("DSBSPECTRUM") == 0))
      {
         m_spec_axis = axis;
         m_axis_type[axis-1] = 'b';
         set_axis = true;
         break;
      }
   }
   LOG_STREAM << endl;

   if(!set_axis)
      my_assert(false, __FILE__,__LINE__,
            ": set_spec_axis may be called only if spec_axis exist; m_has_frame: " + to_string(m_has_specframe));
}



void ast::frameset::set_pol_time_sky_axis(void)
{
   LOG_trace(__func__);

   int naxes = astGetI( m_hdr_fs, "Naxes" );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Naxes )"));

   LOG_STREAM << "m_NAXIS vs Naxes :" << m_NAXIS << " vs " << naxes << endl;

   LOG_STREAM << "Domains/Symbols in header FrameSet(CURRENT): ";

   int has_n_sky_axis = 0;
   int sky_axis[2];

   int axis;
   for(axis=1; axis<(naxes+1);axis++)
   {
      string domain{"Domain("+to_string(axis)+")"};
      string symbol{"Symbol("+to_string(axis)+")"};
      const char * c_domain_val =  astGetC(m_hdr_fs,domain.c_str());
       if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( " + domain + ")"));
      const char * c_symbol_val =  astGetC(m_hdr_fs,symbol.c_str());
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( " + symbol + ")"));

      string domain_val{c_domain_val};
      string symbol_val{c_symbol_val};
      LOG_STREAM << " " << domain_val << "/" << symbol_val;
      if((domain_val.find("STOKES") != string::npos) && (symbol_val.compare("STOKES") == 0))
      {
         m_stokes_axis = axis;
         m_axis_type[axis-1] = 'p';
         m_has_stokes_axis = true;
         //break;
      }
      else if(domain_val.compare("TIME") == 0)
      {
         m_time_axis = axis;
         m_axis_type[axis-1] = 't';
         m_has_time_axis = true;
         //break;
      }
      else if(domain_val.compare("SKY") == 0)
      {
         if(has_n_sky_axis >= 2)
         {
            LOG_STREAM << "too many sky axes found. Alread have: "
               << has_n_sky_axis << " axes set" << endl; 
         }
         else
         {
            sky_axis[has_n_sky_axis] = axis;
            has_n_sky_axis++;
         }
         //break;
      }
   }
   LOG_STREAM << endl;

   // identify Lon Lat:
   // AST doc says Lon/LatAxis macros return 1,2 only
   // unclear what it returns when applied to FrameSet
   // and also IsLonAxis IsLatAxis is applicable to Frame only
   if(has_n_sky_axis == 2)
   {
      int ix_lon = astGetI(m_hdr_fs,"LonAxis") - 1;
      int ix_lat = astGetI(m_hdr_fs,"LatAxis") - 1;
      if ( !astOK || (ix_lon == ix_lat))
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Lon/LatAxis ) or Lon equals Lat"));

      bool lon_first = (ix_lon < ix_lat);
      m_sky_lon_axis = lon_first ? sky_axis[0] : sky_axis[1];
      m_sky_lat_axis = lon_first ? sky_axis[1] : sky_axis[0];
      m_axis_type[m_sky_lon_axis-1] = 'o';
      m_axis_type[m_sky_lat_axis-1] = 'a';
      m_has_skyframe = true;
   }
   else
   {
      m_has_skyframe = false;
      LOG_STREAM << "unexpected count of sky axes in frame set: " << has_n_sky_axis << endl; 
   }
}




void ast::frameset::set(skysystem skysys)
{
   LOG_trace(__func__);
   switch(skysys)
   {
      case skysystem::GALACTIC: set_skysystem("Galactic"); break;
      case skysystem::ICRS:     set_skysystem("ICRS"); break;
      case skysystem::NONE:     /* noop */ break;
   }
}



void ast::frameset::set(specsystem specsys)
{
   LOG_trace(__func__);
   if( m_has_specframe  )
   {
      /* p102: in CmpFrame attrib is set in _all_ frames which have it. FIXME why Unit(4) does not set to only axis=4 ? */
      switch(specsys)
      {
         case specsystem::VELO_LSRK:        set_specsystem("System=VELO,StdOfRest=LSRK,Unit=km/s"); break;
         case specsystem::WAVE_Barycentric: set_specsystem("System=WAVE,StdOfRest=Bary,Unit=m"); break;
         case specsystem::NONE:             /* noop */ break;
      }
   }
}



void ast::frameset::set(timesystem timesys)
{
   LOG_trace(__func__);
   if( m_has_time_axis )
   {
      switch(timesys)
      {
         case timesystem::MJD_UTC: set_timesystem("System=MJD,TimeScale=UTC"); break;
         case timesystem::NONE:    /* noop */ break;
      }
   }
}




void ast::frameset::set_skysystem(string skysys_str)
{
   LOG_trace(__func__);

   if(!skysys_str.empty())
   {
      //int status = 0;
      astSet( m_hdr_fs, "System=%s", skysys_str.c_str());//, &status );
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astSet(skysystem " + skysys_str + ")"));
   }

   assert_valid_state();
}



void ast::frameset::set_specsystem(string specsys_str)
{
   LOG_trace(__func__);

   if(!specsys_str.empty())
   {
      int status = 0;
      astSet( m_hdr_fs, specsys_str.c_str(), &status );
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astSet(specsystem " + specsys_str + ")"));
   }

   assert_valid_state();
}



void ast::frameset::set_timesystem(string timesys_str)
{
   LOG_trace(string{__func__});

   if(!timesys_str.empty())
   {
      int status = 0;
      astSet( m_hdr_fs, timesys_str.c_str(), &status );
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astSet(timesystem " + timesys_str + ")"));
   }

   assert_valid_state();
}





AstRegion * ast::frameset::create_header_region(void)
{
   LOG_trace(__func__);

   /* get PIXEL -> WCS-FRAME mapping
    *
    * FIXME see whether simpler with Invert(BASE<->CURRENT) */

   AstFrame * wcsfrm = (AstFrame*)astGetFrame( m_hdr_fs, AST__CURRENT );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame( CURRENT )"));

   AstFrame * pixfrm = (AstFrame*)astGetFrame( m_hdr_fs, AST__BASE );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame( BASE )"));

   AstMapping * pix2wcs = (AstMapping*)astGetMapping( m_hdr_fs, AST__BASE, AST__CURRENT );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetMapping( BASE->CURRENT )"));


   /* Create AstBox from NAXISn */

   double p1[ AXES_CNT ];
   double p2[ AXES_CNT ];

   int ix;
   for(ix = 0; ix < m_NAXIS; ix++)
   {
      p1[ix] = 0.5;
      p2[ix] = 0.5 + m_NAXISn[ix];
   }

   AstBox * pixbox = astBox( pixfrm, 1, p1, p2, NULL, " " );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astBox( NAXISn )"));


   /* map header's pixels to WCS-region */

   AstRegion * wcsbox = (AstRegion*)astMapRegion( pixbox, pix2wcs, wcsfrm );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astMapRegion( pixbox -> wcsbox )"));

   return wcsbox;
}


vector<Bounds> ast::frameset::bounds(void)
{
   LOG_trace(__func__);

   AstRegion * wcsbox = create_header_region();

   /* finally, get the bounds */

   double low[ AXES_CNT ];
   double up[ AXES_CNT ];

   astGetRegionBounds(wcsbox, low, up);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds"));

   int ix;
   for(ix=0; ix<m_NAXIS; ix++) LOG_STREAM << "Before /bounds(): " << R2D * low[ix] << " " << R2D * up[ix] << endl;

   // FIXME use ? Bounds to return the bounds - do as in ast4vl.c::ast4vl_bouns_set() 

   AstFrame * rgn = (AstFrame*)wcsbox;// FIXME

   vector<Bounds> bounds_vec;

   char attrib[ 9 ];// FIXME length
   for( ix = 0; ix < m_NAXIS; ix++ )
   {
      /* read label */
      sprintf( attrib, "Label(%d)", ix + 1 );
      const char * lbl = astGetC( rgn, attrib );
      if ( !astOK || (lbl == AST__NULL) )
      {
         // throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( Label("+to_string(ix+1)+") )"));
         lbl = "<n/a>";
         astClearStatus;
      }

      const char * low_str = astFormat(rgn,ix+1,low[ ix ]);// ! AST manual p.239: returned pointer remains valid for 50 calls
      if ( !astOK || (low_str == AST__NULL) )
      {  
         // throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( Label("+to_string(ix+1)+") )"));
         low_str = "<n/a>";
         astClearStatus;
      }
      const char * up_str = astFormat(rgn,ix+1,up[ ix ]);// ! AST manual p.239: returned pointer remains valid for 50 calls
      if ( !astOK || (up_str == AST__NULL) )
      {  
         // throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( Label("+to_string(ix+1)+") )"));
         up_str = "<n/a>";
         astClearStatus;
      }

      /* read unit */
      sprintf( attrib, "Unit(%d)", ix + 1 );
      const char * unit = astGetC( rgn, attrib );
      if ( !astOK || (unit == AST__NULL) )
      {
         // throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC( Unit("+to_string(ix+1)+") )"));
         unit = "<n/a>";
         astClearStatus;
      }

      /* colect returne-data */
      string tlabel = string{lbl};
      string tlow_str = string{low_str};
      string tup_str  = string{up_str};
      string tunit  = string{unit};

      Bounds bounds{ tlabel, tlow_str, tup_str, tunit,   low[ix], up[ix], m_NAXISn[ix] };

      bounds_vec.push_back(bounds);
   }

   return bounds_vec;
}




void ast::frameset::write(std::ostream& ostrm, std::string header)
{
   LOG_trace(__func__);

   AstFitsChan * fchan = astFitsChan( NULL, NULL, "Encoding=FITS-WCS" );

   astPutCards(fchan, header.c_str());
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astPutCards"));

   astWrite(fchan, m_hdr_fs);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astWrite(fchan, m_hdr_fs)"));

   char card[ 81 ];
   astClear( fchan, "Card" );
   while ( astFindFits( fchan, "%f", card, 1 ) )
      ostrm << string(card) << endl;
}


void ast::frameset::write2(std::string fits_pathname, int hdunum)
{
   LOG_trace(__func__);

   AstFitsChan * fchan = astFitsChan( NULL, NULL, "Encoding=FITS-WCS" );

   astWrite(fchan, m_hdr_fs);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astWrite(fchan, m_hdr_fs)"));

   fits::header hdr(fits_pathname, hdunum, READWRITE);

   char card[ 81 ];
   astClear( fchan, "Card" );
   while ( astFindFits( fchan, "%f", card, 1 ) )
   {
      if(0 ==string(card).compare(0,7,"WCSAXES")) continue; // writes this key after other existing CRxxx keys, which is illegal
      cout << string(card) << endl;
      hdr.update(card);
   }
}





#if 0
/* uses only NAXIS to create header region (however FITS header axes count can be more */
overlap_ranges ast::frameset::overlap_in_wcs(coordinates coord)
{
   LOG_trace(__func__);

   /* get all header bounds */

   AstRegion * wcsbox = create_header_region();

   double low[ AXES_CNT ];
   double up[ AXES_CNT ];

   astGetRegionBounds(wcsbox, low, up);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds"));


   /* overwrite bounds for sky-axis and spec axis if given by coord input */

   int ix_lon = astGetI(m_hdr_fs,"LonAxis") - 1;
   int ix_lat = astGetI(m_hdr_fs,"LatAxis") - 1;
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Lon/LatAxis )"));

   LOG_STREAM << "LonAxis LatAxis indeces (zero-based): " << to_string(ix_lon) << " " << to_string(ix_lat) << endl; 

   low[ix_lon] = D2R * (coord.lon_deg - coord.dlon_deg/2.0); 
   low[ix_lat] = D2R * (coord.lat_deg - coord.dlat_deg/2.0);
   up[ix_lon]  = D2R * (coord.lon_deg + coord.dlon_deg/2.0); 
   up[ix_lat]  = D2R * (coord.lat_deg + coord.dlat_deg/2.0);

   if(m_has_specframe && (coord.specsys != specsystem::NONE))
   {
      /* FIXME assumes m_frm_fs is VELO and unit is km/s How to align - if set(specsystem not called) ? */

      string unit_key{"Unit("+to_string(m_spec_axis)+")"};
      const char * cunit = astGetC(m_hdr_fs, unit_key.c_str());
      string unit(cunit);

      string std_of_rest_key{"StdOfRest("+to_string(m_spec_axis)+")"};
      const char * cstd_of_rest = astGetC(m_hdr_fs, std_of_rest_key.c_str());
      string std_of_rest(cstd_of_rest);

      LOG_STREAM << "SpectralAxisUnit:       " << unit << endl;
      LOG_STREAM << "SpectralAxis StdOfRest: " << std_of_rest << endl;

      low[m_spec_axis-1] = coord.vl_kmps;
      up[m_spec_axis-1] = coord.vu_kmps;
   }

   LOG_STREAM << string{__func__} << ": create input coord-region (angles in rad): low-bnd up-bnd" << endl;
   int ix;
   for(ix=0; ix<m_NAXIS; ix++) LOG_STREAM << "AstBox rad: " << low[ix] << " " << up[ix] 
      << " deg: " << R2D*low[ix] << " " << R2D*up[ix] << endl;

   /* FIXME ignored coord.shape; add circle later : metters for ovelap-code (but not for cut, which is alwazs rect) */
   AstRegion * crgn = (AstRegion *)astBox( wcsbox, 1, low, up , (AstRegion*)AST__NULL," ");
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astBox( coord )"));


   /* overlap */

   int ov_code = astOverlap( wcsbox, crgn );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astOverlap( header, caller-coord )"));


   /* if overlap, calc pixel renages */

   vector<double_xy> pix_ranges;

   bool no_overlap = ((ov_code == 1) || (ov_code == 6));
   if(!no_overlap)
   {
      AstCmpRegion * wcsOverlap = astCmpRegion( wcsbox, crgn, AST__AND, " ");
      if ( !astOK || (wcsOverlap == AST__NULL) )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astCmpRegion( header AND coord )"));

      AstFrame    * pixfrm;
      AstMapping  * wcs2pix;
      pixfrm = (AstFrame*)astGetFrame( m_hdr_fs, AST__BASE );
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame( header-fs )"));

      wcs2pix = (AstMapping*)astGetMapping( m_hdr_fs, AST__CURRENT, AST__BASE );
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetMapping( CURRENT->BASE )"));

      AstCmpRegion * pixOverlap = (AstCmpRegion*)astMapRegion( wcsOverlap, wcs2pix, pixfrm );
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astMapRegion( wcsOverlap -> pixOverlap )"));

      double lbnd[AXES_CNT];
      double ubnd[AXES_CNT];
      astGetRegionBounds(pixOverlap, lbnd, ubnd);
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds( pixOverlap )"));

      LOG_STREAM << "Overlap in pixels: " << endl;
      int ix;
      for(ix=0; ix<m_NAXIS; ix++)
      {
         LOG_STREAM << "PIX " << lbnd[ix] << " .. " << ubnd[ix] << endl;
         pix_ranges.push_back({lbnd[ix],ubnd[ix]});
      }
   }

   return overlap_ranges{ov_code, pix_ranges };
}
#else
void log_bounds(string prefix, int length, double low[], double up[])
{
   int ix;
   LOG_STREAM << prefix << endl;
   for(ix=0; ix<length; ix++) LOG_STREAM << prefix << low[ix] << " " << up[ix] 
      << " deg: " << R2D*low[ix] << " " << R2D*up[ix] << endl;
}




void ast::frameset::set_bounds_to_rect(coordinates& coord)
{
   LOG_trace(__func__);

   switch(coord.shape)
   {
      case area::CIRCLE:
      case area::RECT:
         /* both set already */
         break;
      case area::POLYGON:
         {
            my_assert(coord.p_lon_deg.size()==coord.p_lat_deg.size(),
                  __FILE__,__LINE__,"coord::p_lon and p_lat sizes differ");

            constexpr int int_max_value {std::numeric_limits<int>::max()};
            my_assert(coord.p_lon_deg.size() > int_max_value,
                  __FILE__,__LINE__,"coord-polygon too long for astPolygon");

            int npnt = (int)coord.p_lon_deg.size();
            int dim  = npnt;
            double points[2][dim];
            const double * pts = &(points[0][0]);

            LOG_STREAM << "polygon ";
            int ii;
            for(ii = 0; ii<dim; ii++)
            {
               points[0][ii] = D2R * coord.p_lon_deg[ii];
               points[1][ii] = D2R * coord.p_lat_deg[ii];

               LOG_STREAM << "(" << R2D * points[0][ii] << ", " << R2D * points[1][ii] << ")";
            }
            LOG_STREAM << endl;

            AstSkyFrame * sky_frm = (AstSkyFrame*)find_skyframe();

            my_assert(sky_frm != nullptr, __FILE__,__LINE__,"sky frame not found in header frameset");

            AstPolygon * astPoly = astPolygon(sky_frm, npnt, dim, pts, NULL, " ");
            if ( !astOK )
               throw runtime_error(failed_with_status(__FILE__,__LINE__,"astPolygon( npoints=" + to_string(npnt) + "...)"));

            double lbnd[2];
            double ubnd[2];
            astGetRegionBounds( (AstRegion*)astPoly, lbnd, ubnd );
            if ( !astOK )
               throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds(astPolygon ...)"));
            /* NOTE AST-manual:
             * lbnd < ubnd -> ok, note: if axis has no lower/upper limit largest-negative/largest-positive value is returned
             * lbnd = ubnd -> axis has one value
             * lbnd > ubnd -> region has no extent on that axis
             * lbnd = ubnd = AST__NULL -> bounds on that axis cannot be determined */

            LOG_STREAM << "polygon bounds lon: " << R2D * lbnd[0] << " .. " << R2D * ubnd[0] << endl;
            LOG_STREAM << "polygon bounds lat: " << R2D * lbnd[1] << " .. " << R2D * ubnd[1] << endl;

            // (mis)use RECT in coord to store bounds:
            coord.lon_deg =  R2D * (lbnd[0] + ubnd[0])/2.0;
            coord.lat_deg =  R2D * (lbnd[1] + ubnd[1])/2.0;
            coord.dlon_deg = R2D * (ubnd[0] - lbnd[0]);
            coord.dlat_deg = R2D * (ubnd[1] - lbnd[1]);
         }
         break;

      default:
         my_assert(false, __FILE__,__LINE__,"coord::shape invalid");
   }
}




/* calcs overlap in pixel coords */
overlap_ranges ast::frameset::overlap(coordinates coord)
{
   LOG_trace(__func__);

   /* ---------- get all header bounds ---------------------- */

   /*   AstRegion * wcsbox = create_header_region(); 
    *   NOTE: not used because it creates header-region based on NAXIS only, not Naxes:
    *   FITS header defines number of axes as max of ( NAXIS,WCSAXIS and highest wcs-index in a wcs-key) */

   AstFrame * wcsfrm = (AstFrame*)astGetFrame( m_hdr_fs, AST__CURRENT );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame( CURRENT )"));

   AstFrame * pixfrm = (AstFrame*)astGetFrame( m_hdr_fs, AST__BASE );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetFrame( BASE )"));

   AstMapping * pix2wcs = (AstMapping*)astGetMapping( m_hdr_fs, AST__BASE, AST__CURRENT );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetMapping( BASE->CURRENT )"));


   /* Create AstBox from NAXISn */

   double p1[ AXES_CNT ];
   double p2[ AXES_CNT ];

   my_assert(m_NAXIS <= AXES_CNT, __FILE__,__LINE__,
         "This build supports "  + to_string(AXES_CNT) + " axes, but NAXIS is bigger : " + to_string(m_NAXIS));

   int ix;
   for(ix = 0; ix < m_NAXIS; ix++)
   {
      p1[ix] = 0.5;
      p2[ix] = 0.5 + m_NAXISn[ix];
   }

   int naxes = astGetI( m_hdr_fs, "Naxes" );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Naxes )"));

   LOG_STREAM << "Naxes / NAXIS : " << naxes << " / " << m_NAXIS << endl;

   my_assert(naxes <= AXES_CNT, __FILE__,__LINE__,
         "This build supports "  + to_string(AXES_CNT)
         + " axes, but Naxes returned from astGetI(header-frameset, Naxes) is bigger : " + to_string(m_NAXIS));

   for(ix = m_NAXIS; ix < naxes; ix++)
   {
      p1[ix] = 0.5;
      p2[ix] = 0.5;
   }

   AstBox * pixbox = astBox( pixfrm, 1, p1, p2, NULL, " " );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astBox( NAXISn )"));


   /* DEBUG only */
   double xlbnd[AXES_CNT];
   double xubnd[AXES_CNT];
   astGetRegionBounds(pixbox, xlbnd, xubnd);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds( pixbox )"));
   log_bounds("XX BOUNDS header_pix: ", naxes, xlbnd, xubnd);
   /* DEBUG only */


   /* map header's pixels to WCS-region */

   AstRegion * wcsbox = (AstRegion*)astMapRegion( pixbox, pix2wcs, wcsfrm );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astMapRegion( pixbox -> wcsbox )"));


   /* ------------ get bounds of the header region -------------------------- */

   double low[ AXES_CNT ];
   double up[ AXES_CNT ];

   astGetRegionBounds(wcsbox, low, up);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds"));

   log_bounds("XX BOUNDS header: ", naxes, low, up);


   /* overwrite bounds for sky-axis and spec axis if given by coord input */

   int ix_lon = m_sky_lon_axis - 1; //astGetI(m_hdr_fs,"LonAxis") - 1;
   int ix_lat = m_sky_lat_axis - 1; //astGetI(m_hdr_fs,"LatAxis") - 1;
                                    //if ( !astOK )
                                    //   throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Lon/LatAxis )"));

   LOG_STREAM << "lon/lat axes indeces (zero-based): "
      << to_string(ix_lon) << " " << to_string(ix_lat) << endl; 

   set_bounds_to_rect(coord);

   low[ix_lon] = D2R * (coord.lon_deg - coord.dlon_deg/2.0);
   low[ix_lat] = D2R * (coord.lat_deg - coord.dlat_deg/2.0);
   up[ix_lon]  = D2R * (coord.lon_deg + coord.dlon_deg/2.0); 
   up[ix_lat]  = D2R * (coord.lat_deg + coord.dlat_deg/2.0);

   vector<double_xy> pix_ranges;

   if(m_has_specframe && (coord.specsys != specsystem::NONE))
   {
      /* FIXME assumes m_frm_fs is VELO and unit is km/s How to align - if set(specsystem not called) ? */

      string unit_key{"Unit("+to_string(m_spec_axis)+")"};
      const char * cunit = astGetC(m_hdr_fs, unit_key.c_str());
      string unit(cunit);

      string std_of_rest_key{"StdOfRest("+to_string(m_spec_axis)+")"};
      const char * cstd_of_rest = astGetC(m_hdr_fs, std_of_rest_key.c_str());
      string std_of_rest(cstd_of_rest);

      LOG_STREAM << "SpectralAxisUnit:       " << unit << endl;
      LOG_STREAM << "SpectralAxis StdOfRest: " << std_of_rest << endl;

      /* FIXME  ast-overlap computation breaks if bands do not overlap - suspect AST bug ? */
      if((( up[m_spec_axis-1] < coord.vl_kmps) && ( up[m_spec_axis-1] < coord.vu_kmps)) ||
            ((low[m_spec_axis-1] > coord.vl_kmps) && (low[m_spec_axis-1] > coord.vu_kmps)))
      {
         /* set values only to get correct debug print for client */
         low[m_spec_axis-1] = coord.vl_kmps;
         up [m_spec_axis-1] = coord.vu_kmps;
         log_bounds("XX BOUNDS client: ", naxes, low, up);
         LOG_STREAM << "XX BOUNDS no overlap in spectrum axis, returning ov_code=1" << endl;
         return overlap_ranges{1, pix_ranges };
      }
      else // at least partial overlap -> cut coord to min/max of header values
      {
         /* FIXME if() is needed becuase if coord bounds bigger then header's -> overlap yields 1 - suspect bug in AST? */
         if(low[m_spec_axis-1] < coord.vl_kmps)  low[m_spec_axis-1] = coord.vl_kmps;
         if(up [m_spec_axis-1] > coord.vu_kmps)  up [m_spec_axis-1] = coord.vu_kmps;
      }
   }

   if(m_has_time_axis && (coord.timesys != timesystem::NONE))
   {
      // FIXME see if() in spec bounds above: test verify and eventually removea comments
      /*if(low[m_time_axis-1] < coord.time_value[0])*/  low[m_time_axis-1] = coord.time_value[0];
      /*if(up [m_time_axis-1] > coord.time_value[1])*/  up [m_time_axis-1] = coord.time_value[1];
   }

   if(m_has_stokes_axis && (!coord.pol.empty()))
   {
      // FIXME implement properly ranges 1,...4 and -1,...-8 (correspond to ranges I...V and RR...YY respectively)
      low[m_stokes_axis-1] = min_pol_state(coord.pol);
      up [m_stokes_axis-1] = max_pol_state(coord.pol);
   }

   log_bounds("XX BOUNDS client: ", naxes, low, up);

   /* FIXME ignored coord.shape; add circle later : metters for ovelap-code (but not for cut, which is always rect) */
   AstRegion * crgn = (AstRegion *)astBox( wcsbox, 1, low, up , (AstRegion*)AST__NULL," ");
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astBox( coord )"));

   astInvert(pix2wcs);
   AstCmpRegion * crgn_pix = (AstCmpRegion*)astMapRegion( crgn, pix2wcs, pixfrm );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astMapRegion( client-region WCS -> PIX by header-mapping )"));

   /* DBG only */
   double cllow[ AXES_CNT ];
   double clup[ AXES_CNT ];
   astGetRegionBounds(crgn_pix, cllow, clup);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds"));

   log_bounds("XX BOUNDS client_pix: ", naxes, cllow, clup);
   /* DBG only */


   /* overlap */

   /* FIXME returns "5"= exact macth however swapping coords returns seemingly correct overlap code ; bug ?
    * FIXME TODO workround: if 5 returned -> swap regions and use that value
    * accept 5 only if returned twice: original oreder and swapped both return 5 */
   //int ov_code = astOverlap( crgn_pix, pixbox );
   int ov_code = astOverlap( pixbox, crgn_pix );
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astOverlap( header, caller-coord )"));

   /* if overlap, calc pixel ranges */


   bool no_overlap = ((ov_code == 1) || (ov_code == 6));
   if(!no_overlap)
   {
      AstCmpRegion * pixOverlap = astCmpRegion( pixbox, crgn_pix, AST__AND, " ");
      if ( !astOK || (pixOverlap == AST__NULL) )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astCmpRegion( header AND coord )"));

      double lbnd[AXES_CNT];
      double ubnd[AXES_CNT];
      astGetRegionBounds(pixOverlap, lbnd, ubnd);
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetRegionBounds( pixOverlap )"));

      log_bounds("XX BOUNDS overlap_pix: ", m_NAXIS, lbnd, ubnd);

      int ix;
      for(ix=0; ix<m_NAXIS; ix++)
      {
         pix_ranges.push_back({lbnd[ix],ubnd[ix],m_axis_type[ix]});
      }
   }

   return overlap_ranges{ov_code, pix_ranges };
}
#endif




bool almost_equal(double x, double y, int ulp)
{
   // the machine epsilon has to be scaled to the magnitude of the values used
   // and multiplied by the desired precision in ULPs (units in the last place)
   return std::fabs(x-y) <= std::numeric_limits<double>::epsilon() * std::fabs(x+y) * ulp
      // unless the result is subnormal

      || std::fabs(x-y) < std::numeric_limits<double>::min();

   /* also note std::numeric_limits<double>::epsilon() cannot be used with a value having a magnitude different of 1.0.
    * Instead, std::nextafter can be used to get the epsilon of a value with any magnitude.
    *  double nextafter (double x , double y );
    *  returns the next representable value after x in the direction of y.
    */
}

/* 2**N */
int my_pow_int (int x, int p)
{
   int i = 1;
   for (int j = 1; j <= p; j++)  i *= x;
   return i;
}


std::vector<point2d> ast::frameset::sky_vertices(void)
{
   LOG_trace(__func__);

   const int naxes = astGetI(m_hdr_fs, "Naxes");
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Naxes )"));

   my_assert(naxes >= m_NAXIS, __FILE__,__LINE__,"AST-naxes is less then FITS-NAXIS");

   const int ncoord_in  = naxes;
   const int ncoord_out = naxes; // FIXME for now we assume Nin = Nout


   /* generate vertices in form (0.5, NAXISi+0.5) in 'naxes' dimensions */

   /* NOTE 1: lon lat coordinates can be identified only in WCS-coordinate due to
    * possible permutations and transformations (rotation), so in pixel-grid
    * we need to create complete vertex-array for each dimension */

   /* NOTE 2: this generates unordered set of vertices.
    * If we could generate set of vertexes ordered by polar angle (from center of the 4-vertices)
    * then polygons could be generated without later re-ordering.
    * Is it possible at least in specific cases ? */

   const int npoint = my_pow_int(2,m_NAXIS);

   LOG_STREAM << "VERT Nin Nout: " << ncoord_in  << " " << ncoord_out << " npoint: " << npoint << endl;

   double vecs_in [ncoord_in ][npoint];
   double vecs_out[ncoord_out][npoint];

   const double MIN_VAL = 0.5;

   int ic,ip;
   for(ip=0; ip < npoint; ip++)
   {
      for(ic=0; ic < ncoord_in; ic++)
      {
         const int bit = 1 << ic;

         if(ic < m_NAXIS)
            vecs_in[ic][ip] = (bit & ip) ? MIN_VAL : (MIN_VAL + m_NAXISn[ic]);
         else
            vecs_in[ic][ip] = (bit & ip) ? MIN_VAL : (MIN_VAL + 1);
      }
   }

   for(ip=0; ip<npoint; ip++)
   {
      LOG_STREAM << "Pi[" << ip << "]: ";
      for(ic=0; ic<ncoord_in; ic++)
         LOG_STREAM << " " << vecs_in[ic][ip];
      LOG_STREAM << endl;
   }


   /* transform the generated vertices in pixels to WCS and identify lon, lat axis */

   double * ptr_in [ncoord_in ];
   double * ptr_out[ncoord_out];

   int ix;
   for(ix=0; ix<ncoord_in ; ix++) ptr_in [ix] = &(vecs_in [ix][0]);
   for(ix=0; ix<ncoord_out; ix++) ptr_out[ix] = &(vecs_out[ix][0]);


   astTranP(m_hdr_fs, npoint, ncoord_in, (const double**)ptr_in, 1, ncoord_out, ptr_out);
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astTranP(pix -> wcs vertices)"));


   const int ixlon = m_sky_lon_axis - 1; //astGetI(m_hdr_fs,"LonAxis") - 1;
   const int ixlat = m_sky_lat_axis - 1; //astGetI(m_hdr_fs,"LatAxis") - 1;
                                         //if ( !astOK )
                                         //   throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI( Lon/LatAxis )"));

   LOG_STREAM << "VERT ix_lon ix_lat: " << ixlon  << " " << ixlat << endl;


   /* select the 4 sky-vertices in case Naxes > 2 */

   double vertlon[npoint];
   double vertlat[npoint];

   int in = 0;
   if(npoint <= 4)
   {
      for(ip=0; ip<npoint; ip++)
      {
         vertlon[ip] = ptr_out[ixlon][ip];
         vertlat[ip] = ptr_out[ixlat][ip];
         in++;
      }
   }
   else
   {
      vertlon[in] = ptr_out[ixlon][0];
      vertlat[in] = ptr_out[ixlat][0];
      in++;

      LOG_STREAM << "Po[0] cnt: <ref>" << endl; 

      /* ip=0 used as reference */
      for(ip=1; ip<npoint; ip++)
      {
         int ic;
         int cnt = 0;
         for(ic=0; ic<ncoord_out; ic++)
         {
            if( (ic != ixlon) && (ic != ixlat) && (ptr_out[ic][ip] == ptr_out[ic][0]) ) cnt++;
            /* asking for exact bit-pattern match on floating point numbers ptr_out[][] possible
             * because axes are independent and so the same machine-code computation
             * is performed on them which must yield the same result down to last bit */
         }

         bool coord_match = (cnt == (ncoord_out - 2));

         LOG_STREAM << "Po[" << ip << "] cnt: " << cnt << endl; 

         if(coord_match)
         {
            vertlon[in] = ptr_out[ixlon][ip];
            vertlat[in] = ptr_out[ixlat][ip];
            in++;
         }
#if 0
         double lon = ptr_out[ixlon][ip];
         double lat = ptr_out[ixlat][ip];

         int ii;
         bool found = false;
         for(ii=0; ii<in; ii++)
         {
            int ulp = 2; // precision in units in the last place
            if( almost_equal(vertlon[ii], lon, ulp) && almost_equal(vertlat[ii], lat, ulp) )
            {
               found = true;
               break;
            }
         } 

         if(!found)
         {
            vertlon[in] = ptr_out[ixlon][ip];
            vertlat[in] = ptr_out[ixlat][ip];
            in++;
         }
#endif
      }
   }

   my_assert((in==4), __FILE__,__LINE__,"expected 4 vertices, but found " + to_string(in) + " from npoint: " + to_string(npoint));

   vector<point2d> ret_vert;

   for(ix=0; ix<in; ix++)
   {
      ret_vert.push_back(point2d{(R2D)*vertlon[ix], (R2D)*vertlat[ix]});

      LOG_STREAM << "VERTx[" << ix << "] (deg): " << (R2D)*vertlon[ix] << " " << (R2D)*vertlat[ix] << endl;
   }

   return ret_vert;
}






void ast::frameset::assert_valid_state(void)
{
   LOG_trace(__func__);

   /* check NAXIS */

   my_assert((m_NAXIS >= 0)&&(m_NAXIS < AXES_CNT), __FILE__,__LINE__, ": NAXIS must be > 0 and <= " + to_string(AXES_CNT));
   int ix;
   for(ix=0; ix<m_NAXIS;ix++)
      my_assert((m_NAXISn[ix] >= 0), __FILE__,__LINE__, ": NAXIS["+to_string(ix)+"]: " + to_string(m_NAXISn[ix]));

   /* check spec-axis identification */

   if(m_has_specframe)
      my_assert( (m_spec_axis >= 1)/*&&(m_spec_axis <=m_NAXIS) FIXME should be Naxes of m_hdr_fs */,
            __FILE__,__LINE__, ": m_spec_axis is " + to_string(m_spec_axis)
            + " and must be 1 .. " + to_string(m_NAXIS) );

   if(m_has_skyframe)
   {
      my_assert( (m_sky_lon_axis >= 1),
            __FILE__,__LINE__, ": m_sky_lon_axis is " + to_string(m_sky_lon_axis)
            + " and must be 1 .. " + to_string(m_NAXIS) );
      my_assert( (m_sky_lat_axis >= 1),
            __FILE__,__LINE__, ": m_sky_lat_axis is " + to_string(m_sky_lat_axis)
            + " and must be 1 .. " + to_string(m_NAXIS) );
   }

   /* check AstFrameSet */

   my_assert((m_hdr_fs != AST__NULL), __FILE__,__LINE__, ": AstFrameSet is NULL");
   /* FIXME add check internal to AstFrameSet:
    * - FrameSet must have at least 2 Frames 
    * - AST__BASE Frame must have only GRID domain <-- verify this
    * - AST__CURRENT Frame must have SKY domain 
    * - if AST__CURRENT Frame has also SpecFrame -> its axis must match m_spec_axis 
    * - if spectral present and is VELO unit should be km/s FIXME see in overlap  
    */
} 



std::ostream& operator<<(std::ostream& out, const ast::frameset& p)
{
   return p.serialize(out);
}


std::ostream& ast::frameset::serialize(std::ostream& ostrm) const
{
   ostrm << "ast::frameset: ";

   ostrm << "NAXIS[";
   int ix;
   for(ix=0; ix<(m_NAXIS-1); ix++) ostrm << m_NAXISn[ix] << " ";
   ostrm << m_NAXISn[m_NAXIS-1];
   ostrm << "]";

   ostrm << " Ast:";

   const int n_frame = astGetI(m_hdr_fs,"Nframe");
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetI(Nframe)"));

   const char * domain = astGetC(m_hdr_fs,"Domain");
   if ( !astOK )
      throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetC(Domain)"));

   ostrm << "Domain(" << domain << ") ";


   for(ix=0; ix<n_frame; ix++)
   {
      AstFrame * fr =  (AstFrame*)astGetFrame(m_hdr_fs, ix+1);

      const char *astclass = astGetC( fr, "Class" );
      //const char *title = astGetC( fr, "Title" );
      const char *domain = astGetC( fr, "Domain" );
      //const char *system = astGetC( fr, "System" );
      const int naxes = astGetI(fr,"Naxes");
      if ( !astOK )
         throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetX()"));


      ostrm << " ";
      ostrm << astclass<< "[" << ix+1 << "]{";
      ostrm << "Domain(" << domain << ")";

      ostrm << " Axes[";
      int axis;
      for(axis=1; axis<=naxes; axis++)
      {
         const string domain_key{"Domain(" + to_string(axis) + ")"};
         const string symbol_key{"Symbol(" + to_string(axis) + ")"};
         const string system_key{"System(" + to_string(axis) + ")"};
         const string unit_key{"Unit(" + to_string(axis) + ")"};

         const char * domain  = astGetC(fr, domain_key.c_str());
         const char * symbol  = astGetC(fr, symbol_key.c_str());
         const char * system  = astGetC(fr, system_key.c_str());
         const char * unit    = astGetC(fr, unit_key.c_str());
         if ( !astOK )
            throw runtime_error(failed_with_status(__FILE__,__LINE__,"astGetX()"));

         ostrm <<domain << " " << symbol << " " << system << " " << unit;
         if(axis != naxes) ostrm << " | ";
      }
      ostrm << "]";
      ostrm << "}";
   }


   ostrm << " has specframe: " << m_has_specframe;
   if(m_has_specframe)
   {
      ostrm << " at axis (one-based) " << to_string(m_spec_axis) << " ";

      string unit_key{"Unit("+to_string(m_spec_axis)+")"};
      const char * cunit = astGetC(m_hdr_fs, unit_key.c_str());
      string unit(cunit);
      LOG_STREAM << "SpecUnit(" << unit << ") ";

      string std_of_rest_key{"StdOfRest("+to_string(m_spec_axis)+")"};
      const char * cstd_of_rest = astGetC(m_hdr_fs, std_of_rest_key.c_str());
      string std_of_rest(cstd_of_rest);
      LOG_STREAM << "StdOfRest(" << std_of_rest << ")";
   }

   ostrm << " | ";

   ostrm << "has stokes axis: " << m_has_stokes_axis;
   if(m_has_stokes_axis)
   {
      ostrm << " at axis (one-based) " << to_string(m_stokes_axis) << " ";
   }

   ostrm << " | ";

   ostrm << "has time axis: " << m_has_time_axis;
   if(m_has_time_axis)
   {
      ostrm << " at axis (one-based) " << to_string(m_time_axis) << " ";

      string system_key{"System("+to_string(m_time_axis)+")"};
      const char * csystem = astGetC(m_hdr_fs, system_key.c_str());
      string system(csystem);
      LOG_STREAM << "System(" << system << ")";

      string unit_key{"Unit("+to_string(m_time_axis)+")"};
      const char * cunit = astGetC(m_hdr_fs, unit_key.c_str());
      string unit(cunit);
      LOG_STREAM << "TimeUnit(" << unit << ") ";
   }

   ostrm << " | ";

   ostrm << "has skyframe: " << m_has_skyframe;
   if(m_has_skyframe)
   {
      ostrm << " [lon,lat] at axes (one-based) ["
         << to_string(m_sky_lon_axis) << ","
         << to_string(m_sky_lat_axis) << "] ";

      string system_key_lon{"System("+to_string(m_sky_lon_axis)+")"};
      const char * csystem_lon = astGetC(m_hdr_fs, system_key_lon.c_str());
      string system_lon(csystem_lon);
      LOG_STREAM << "System[Lon](" << system_lon << ") ";

      string system_key_lat{"System("+to_string(m_sky_lat_axis)+")"};
      const char * csystem_lat = astGetC(m_hdr_fs, system_key_lat.c_str());
      string system_lat(csystem_lat);
      LOG_STREAM << "System[Lat](" << system_lat << ") ";

      string unit_key_lon{"Unit("+to_string(m_sky_lon_axis)+")"};
      const char * cunit_lon = astGetC(m_hdr_fs, unit_key_lon.c_str());
      string unit_lon(cunit_lon);
      LOG_STREAM << "Unit[Lon](" << unit_lon << ") ";

      string unit_key_lat{"Unit("+to_string(m_sky_lat_axis)+")"};
      const char * cunit_lat = astGetC(m_hdr_fs, unit_key_lat.c_str());
      string unit_lat(cunit_lat);
      LOG_STREAM << "Unit[Lat](" << unit_lat << ") ";
   }

   ostrm << " axes types: >";
   //for(char cc : m_axis_type) ostrm << cc;
   for(int ix=0; ix<AXES_CNT;ix++) ostrm << m_axis_type[ix];
   ostrm << "<";

   ostrm << endl; 

   return ostrm;
}

void ast::frameset::log_NAXISn(void)
{
   LOG_STREAM << "NAXISn[AXES_CNT]: ";
   int ix;
   for(ix=0; ix<AXES_CNT;ix++) LOG_STREAM << m_NAXISn[ix] << " ";
   LOG_STREAM << endl;
}




void ast::frameset::log_warnings(const AstFitsChan * fchan)
{
   LOG_trace(__func__);

   // reports warnings of the last astRead or astWrite invocation

   AstKeyMap *warnings;
#define KEYLEN (15)
   char key[ KEYLEN ];
   short int iwarn;
   const char *message;

   if( astGetI( fchan, "ReportLevel" ) > 0 )
   {
      warnings = (AstKeyMap*)astWarnings( fchan );

      if( warnings && astOK )
      {
         LOG_STREAM <<  "The following warnings were issued" << endl;

         iwarn = 1;
         while( astOK )
         {
            snprintf( key,KEYLEN, "Warning_%d", iwarn++ );
            key[KEYLEN-1]='\0';
            if( astMapGet0C( warnings, key, &message ) )
            {
               LOG_STREAM <<  string{key} << " - " <<  string{message} << endl;
            }
            else
            {
               break;
            }
         }
      }
      /* FIXME why clear ? */
      astClearStatus;
   }
}



