/* Name:
      stcschan-demo1.c

   Purpose:
      A demonstration of the facilities provided by the AST library 
      for reading STC metadata encoded using the STC-S linear string 
      format.

   Description:
      This program reads an STC-S description from a disk file, and
      tests a given position to see if it is inside or outside the 
      AstroCoordsArea specified by the STC-S description.

   Usage:
      % stcschan-demo1 <stcs-file> <axis1> <axis2> ...

      <stcs-file>: The path to the disk file containing the STC-S
      description.

      <axis1> <axis2> ...: The axis values at the position to be tested.   
      If insufficient values are supplied, a message describing the
      required values is displayed (label, units, etc).

   Example:
      % stcschan-demo1 stcs-ex1.txt 1996-01-01T00:00:15 11:56:00 -11:30:00 \
                       1420.4 1000 

   To compile and link:
      Assuming your starlink distribution is in "/star":

      % gcc -o stcschan-demo1 stcschan-demo1.c -L/star/lib \
            -I/star/include `ast_link`
*/

/* Include system headers. */
#include <stdio.h>
#include <string.h>

/* Include the AST library header. */
#include "ast.h"

/* Maximum number of axes in an STC-S AstroCoordSystem. */
#define MAX_AXES 5

/* Maximum allowed length for a single line of text form the disk file. */
#define MAX_LINE_LEN 500

/* Prototype for the function that reads text from the disk file. */
const char *source( void );


int main( int argc, char **argv ){

/* Local variables: */
   AstKeyMap *warnings;
   AstObject *object;
   AstRegion *region;
   AstStcsChan *channel;
   FILE *fd;
   char attrib[ 9 ];
   char key[ 15 ];
   const char *message;
   double inpos[ MAX_AXES ];
   double outpos[ MAX_AXES ];
   int axis;
   int iwarn;
   int naxis;
   int nc;
   int status;

/* Initialised the returned system status to indicate success. */
   status = 0;

/* Check a file was specified on the command line, and attempt to open it
   for read access. */
   if( argc < 2 ) {
      printf( "Usage: stcschan-demo1 <stcs-file> <axis1> <axis2> ...\n" );
      status = 1;
   } else {
      fd = fopen( argv[ 1 ], "r" );
      if( !fd ) {
         printf("Failed to open input file '%s'.\n", argv[ 1 ] );
         status = 1;
      }
   }

/* If a disk file was opened successfully... */
   if( !status ) {

/* Start an AST object context. This means we do not need to annull 
   each AST Object individually. Instead, all Objects created within 
   this context will be annulled automatically by the corresponding
   invocation of astEnd. */
      astBegin;

/* Create an StcsChan. This is the object that converts external STC-S
   descriptions into corresponding AST Objects. Tell it to use the
   "source" function for obtaining lines of text from the disk file. Also
   tell it to store all warnings generated by the conversion for later
   use. Other attributes of the StcsChan class retain their default
   values. */
      channel = astStcsChan( source, NULL, "ReportLevel=3" );
  
/* Associate the descriptor for the input disk file with the StcsChan.
   This makes it available to the "source" function. Since this
   application is single threaded, we could instead have made "fd" a 
   global variable, but the ChannelData facility is used here to illustrate 
   how to pass data to a source or sink function safely in a multi-threaded 
   application. */
      astPutChannelData( channel, fd );

/* The default behaviour of the astRead function when used on an StcsChan is 
   to read and return the AstroCoordArea as an AST Region. This behaviour
   can be changed by assigning appropriate values to the StcsChan attributes
   "StcsArea", "StcsCoords" and "StcsProps". Options exist to return the 
   AstroCoords as an AST PointList, and/or to return the individual
   property values read from the STC-S text in the form of an AST KeyMap
   (a sort of hashmap). For now, just take the default action of reading the 
   AstroCoordsArea. */
      object = astRead( channel );

/* The astRead function is a generic function and so returns a generic
   AstObject pointer. Check an Object was created successfully. */
      if( !object ) {
         printf( "Failed to read an AST Object from file '%s'.\n", 
                 argv[ 1 ] );
         status = 1;

/* Now check that the object read is actually an AST Region, rather than
   some other class of AST Object. */
      } else if( !astIsARegion( object ) ) {      
         printf( "Expected a Region but read a %s from file '%s'.\n", 
                 astGetC( object, "Class" ), argv[ 1 ] );
         status = 1;

/* We now now know we have a Region so it is safe to use the pointer
   returned by astRead as a Region pointer. Do the cast now to avoid
   repeated casting in future. */
      } else {
         region = (AstRegion *) object;      

/* Get the number of axes in the AstroCoordSystem, and check it is not
   larger than expected. */
         naxis = astGetI( region, "Naxes" );
         if( naxis > MAX_AXES ) {
            printf( "The coordinate system read from file '%s' has "
                    "too many axes (%d). Up to %d axes are allowed.\n", 
                    argv[ 1 ], naxis, MAX_AXES );
            status = 1;

/* Now check that the correct number of axis values were supplied on the
   command line. If not, issue a warning message and give details of the
   label and units for each axis. Note, AST axis indices are one-based, 
   in the range 1 to "Naxes". */
         } else if( argc != 2 + naxis ) {
            printf( "The coordinate system read from file '%s' has "
                    "%d axes, but %d axis values were supplied on the "
                    "command line. ", argv[ 1 ], naxis, argc - 2 );

            printf( "Values are required for the following axes:\n");

            for( axis = 1; axis <= naxis; axis++ ) {
               sprintf( attrib, "Label(%d)", axis );
               printf( "   Axis %d: %s ", axis, astGetC( region, attrib ) );
               sprintf( attrib, "Unit(%d)", axis );
               printf( "(%s)\n",  astGetC( region, attrib ) );
            }

            status = 1;

/* If the correct number of axis values was supplied on the command line,
   convert the supplied strings into floating point axis values. Each
   class of axis has its own formatting and unformatting rules that are
   controlled by various attributes such as "Format" and "Digits". Values
   for these attributes could be stored in the Region if different
   unformatting conventions were preferred. */
         } else {

            for( axis = 1; axis <= naxis; axis++ ) {

               nc = astUnformat( region, axis, argv[ axis + 1 ], 
                                 inpos + axis - 1 );

               if( nc != strlen( argv[ axis + 1 ] ) ) {
                  sprintf( attrib, "Label(%d)", axis );
                  printf( "Failed to interpret '%s' as a value for axis "
                          "%d (%s).\n", argv[ axis + 1 ], axis,
                          astGetC( region, attrib ) );
                  status = 1;
                  break;
               } else {
                  printf("%g ", inpos[ axis - 1 ] );
               }
            }
            printf("\n");
         }

/* If we have obtained a full set of floating point axis values, use the
   Region as a Mapping to transform the supplied position. When a Region
   is used as a Mapping, the transformation leaves all axis values
   unchanged for interior positions, but assigns the magic value AST__BAD 
   to all axes for exterior positions. */
         if( !status ) {
            astTranN( region, 1, naxis, 1, inpos, 1, naxis, 1, outpos );

/* Issue a message describing the position tested and indicating if it is
   inside or outside the AstroCoordsArea. */
            printf( "\nThe position ( %s=%s", astGetC( region, "Symbol(1)" ),
                    argv[ 2 ] );

            for( axis = 2; axis <= naxis; axis++ ) {
               sprintf( attrib, "Symbol(%d)", axis );
               printf(", %s=%s", astGetC( region, attrib ), argv[ axis + 1 ] );
            }

            printf( " ) is " );

            if( outpos[ 0 ] == AST__BAD ) {            
               printf( "OUTSIDE" );
            } else {
               printf( "INSIDE" );
            }

            printf( " the region read from file '%s'.\n\n", argv[ 1 ] );

         }
      }

/* We asked the StcsChan to record any warnings that were generated
   whilst converting the STC-S description into a corresponding AST
   Object (a Region in this case). We now see if any such warnings were
   generated by the earlier call to astRead. */
      warnings = astWarnings( channel );

/* If any warnings were generated, and if no other error has occurred so
   far, display the warnings. */
      if( warnings && !status && astOK ) {
         printf( "\nThe following warnings were issued reading file "
                 "'%s':\n", argv[ 1 ] );

/* The warnings are stored in an AST KeyMap (a sort of hashmap). Each
   warning message is associated with a key of the form "Warning_1",
   "Warning_2", etc. Loop round successive keys, obtaining a value for
   each key from the warnings KeyMap, and displaying it. */
         iwarn = 1;
         while( astOK ) {
            sprintf( key, "Warning_%d", iwarn++ );
            if( astMapGet0C( warnings, key, &message ) ) {
               printf( "\n- %s\n", message );
            } else {
               break;
            }
         }             
      }

/* End the AST Object context. All Objects created since the
   corresponding invocation of astbegin will be annulled automatically. */
      astEnd;

/* Close the disk file. */
      (void) fclose( fd );
   }

/* If an error occurred in the AST library, set the retiurns system
   status non-zero. */
   if( !astOK ) status = 1;
   return status;
}







/* This is a function that reads a line of text from the disk file and
   returns it to the AST library. It is called from within the astRead
   function. */
const char *source( void ){
   static char buffer[ MAX_LINE_LEN + 2 ];
   FILE *fd = astChannelData;
   return fgets( buffer, MAX_LINE_LEN + 2, fd );
}