/* Name:
      stcschan-demo5.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 two STC-S descriptions from two disk files, and
      writes a new STC-S description to standard output. The new STC-S 
      covers the same region as the first STC-S, but expressed in the
      coordinate system of the second STC-S.

   Usage:
      % stcschan-demo5 <stcs-file1> <stcs-file2> 

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

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

   Example:
      % stcschan-demo5 stcs-ex1.txt stcs-ex2.txt

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

      % gcc -o stcschan-demo5 stcschan-demo5.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

/* Prototypes: */
const char *source( void );
AstRegion *ReadStcs( AstStcsChan *chan, const char *file, int *status );
void ReportWarnings( AstStcsChan *chan, const char *file, int *status );

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

/* Local variables: */
   AstFrameSet *fs;
   AstFrame *frame;
   AstMapping *map;
   AstRegion *region1;
   AstRegion *region2;
   AstRegion *region3;
   AstStcsChan *channel;
   int status;

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

/* Check two files were specified on the command line. */
   if( argc < 3 ) {
      printf( "Usage: stcschan-demo5 <stcs-file1> <stcs-file2> \n" );
      status = 1;
   } 

/* 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 between external 
   STC-S descriptions and the corresponding AST Objects. Tell it to use the
   "source" function for obtaining lines of text from the disk file. We
   supply a NULL pointer for the "sink" function so that objects written out
   through the channel will appear on standard output. 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" );
  
/* Attempt to read the STC-S description from the first file, and produce
   a corresponding AST Region object. The conversion is performed by the
   StcsChan created above. */
   region1 = ReadStcs( channel, argv[ 1 ], &status );

/* Now attempt to read the STC-S description from the second file,
   producing a corresponding AST Region object. We re-use the StcsChan 
   created above. */
   region2 = ReadStcs( channel, argv[ 2 ], &status );

/* If we have two Regions, get the mapping from the coordinate system of
   the first Region to the coordinate system of the second Region. If
   successful, this returns a FrameSet object that encapsulates both the 
   Mapping and the two coordinate Frames. */
   if( region1 && region2 ) {
      fs = astConvert( region1, region2, "" );

/* Issue a warning if the conversion cannot be done. */
      if( ! fs ) {
         printf( "Cannot determine the transformation from the first "
                 "coordinate system to the second coordinate system\n");
         status = 1;

/* Otherwise, extract the Mapping from the FrameSet, and then extract the
   Frame that describes the required coordinate system (it will be the
   current Frame in the FrameSet). */
      } else {
         map = astGetMapping( fs, AST__BASE, AST__CURRENT );
         frame = astGetFrame( fs, AST__CURRENT );
 
/* Create a new Region my mapping the first supplied Region using the
   above Mapping. Also supply the above Frame to indicate the nature of 
   the axis values produced by the Mapping (the new Region will be
   defined within this Frame). */
         region3 = astMapRegion( region1, map, frame );

/* Attempt to write out this new Region as an STC-S description. Report an 
   error if this fails. The channel writes text to standard output
   because no "sink" function was supplied when it was created above. */
         if( ! astWrite( channel, region3 ) && astOK ) {
            printf( "Failed to convert the new Region into an STC-S "
                    "description.\n" );

/* Report any warnings that were generated during the conversion to
   STC-S. */
         } else {
            ReportWarnings( channel, NULL, &status );
         }
      }
   }

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

/* 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 );
}





/* -------------------------------------------------------------------
 * This function reads an STC-S description from a given text file,
 * and attempts to convert them into an AST Region. If successful, a
 * pointer to the Region is returned. A NULL pointer is returned if
 * anything goes wrong. 
*/

AstRegion *ReadStcs( AstStcsChan *chan, const char *file, int *status ){
   AstObject *object;
   AstRegion *result;
   FILE *fd;

/* Initialise the returned pointer to indicate that no Region has yet
   been read. */
   result = NULL;

/* If an error has already occurred, return without action. */
   if( *status != 0 ) return result;

/* Attempt to open the STC-S file */
   fd = fopen( file, "r" );
   if( !fd ) {
      printf("Failed to open STC-S descrption file '%s'.\n", file );

/* If successful... */
   } else {

/* 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( chan, 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( chan );
      
/* 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 STC-S description "
                 "file '%s'.\n", file );

/* 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 STC-S description "
                 "file '%s'.\n", astGetC( object, "Class" ), file );

/* If the Object is a Region, return the Region pointer. */
      } else {
         result = (AstRegion *) object;
      }

/* Close the file. */
      fclose( fd );

/* If the StcsChan recorded any warnings that were generated whilst 
   converting the STC-S description into a corresponding AST Object,
   we now display them. */
      ReportWarnings( chan, file, status );
   }

   return result;
}

/* -------------------------------------------------------------------
 * This function extracts any warnings stored in the supplied Channel as
 * a result of he previous read or write operation, and displays them on
 * standard output. 
*/

void ReportWarnings( AstStcsChan *chan, const char *file, int *status ){
   AstKeyMap *warnings;
   char key[ 15 ];
   const char *message;
   int iwarn;

/* If an error has already occurred, return without action. */
   if( *status != 0 ) return;

/* If the StcsChan records any warnings that are generated whilst 
   converting between STC-S descriptions and corresponding AST Objects,
   display them. First test the ReportLevel attribute value to see
   if warnings were recored. */
   if( astGetI( chan, "ReportLevel" ) > 0 ) {

/* Any warnings recorded during the conversion performed by the previous
   invocation of astRead or astWrite are returned by the astWarnings method, 
   in the form of an AST "KeyMap" (a type of hash map ). */
      warnings = astWarnings( chan );

/* If any warnings were generated, and if no other error has occurred so
   far, display the warnings. */
      if( warnings && !status && astOK ) {

/* Indicate the context to the user. Assume we were reading an STC-S
   description if "file" was supplied, and writing an STC-S desciprion
   otherwise. */
         if( file ) {
            printf( "\nThe following warnings were issued reading file "
                    "'%s':\n", file );
         } else {
            printf( "\nThe following warnings were issued converting an "
                    "AST Region into an STC-S description:\n" );
         }

/* 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;
            }
         }             
      }
   }
}