/**
 **     $Header: /import/dev-vis/image/imtools/v2.0/imtools/src/RCS/imsplit.c,v 1.4 92/12/01 14:08:35 nadeau Exp $
 **     Copyright (c) 1989-1992  San Diego Supercomputer Center (SDSC)
 **             San Diego, California, USA
 **
 **     Users and possessors of this source code are hereby granted a
 **     nonexclusive, royalty-free copyright and design patent license to
 **     use this code in individual software.  License is not granted for
 **     commercial resale, in whole or in part, without prior written
 **     permission from SDSC.  This source is provided "AS IS" without express
 **     or implied warranty of any kind.
 **
 **     For further information contact:
 **             E-Mail:         info@sds.sdsc.edu
 **
 **             Surface Mail:   Information Center
 **                             San Diego Supercomputer Center
 **                             P.O. Box 85608
 **                             San Diego, CA  92138-5608
 **                             (619) 534-5000
 **/

#define HEADER  "    $Header: /import/dev-vis/image/imtools/v2.0/imtools/src/RCS/imsplit.c,v 1.4 92/12/01 14:08:35 nadeau Exp $"

/**
 **  FILE
 **	imsplit.c	-  Split multi-image files into multiple files
 **
 **  PROJECT
 **	IM		-  Image Manipulation Tools
 **
 **  DESCRIPTION
 **	imsplit takes an input multi-image file and splits it into multiple
 **	single-image output files.
 **
 **  PUBLIC CONTENTS
 **			d =defined constant
 **			f =function
 **			m =defined macro
 **			t =typedef/struct/union
 **			v =variable
 **			? =other
 **
 **	main		f  main program
 **
 **  PRIVATE CONTENTS
 **	toolCommand	v  tool-specific tool info
 **	toolHelp	v  tool-specific help
 **	toolFullHelp	v  tool-specific help
 **	toolOptions	v  tool-specific options
 **	toolEquivs	v  tool-specific equivalent keywords
 **
 **	toolInFilename	v  the name of the input file (could be 'stdin')
 **	toolOutFilename	v  the name of the output file (could be 'stdout')
 **
 **	toolInFormat	v  the name of the input file's format (could be '\0')
 **	toolOutFormat	v  the name of the output file's format (could be '\0')
 **
 **	toolInTable	v  a table for the storage of data read in
 **	toolFlagsTable	v  a table for the storage of read/write flags
 **
 **	toolInit	f  initialize things for the tool
 **
 **  HISTORY
 **     
 **	
 **
 **/

#include "imtools.h"


extern void toolInit( );		/* Initialize things		*/



/*
 *  GLOBALS
 *	toolCommand		-  tool-specific tool info
 *	toolHelp		-  tool-specific help
 *	toolOptions		-  tool-specific options
 *	toolEquivs		-  tool-specific equivalent keywords
 *
 *  DESCRIPTION
 *	toolCommand describes the command to the arg package.
 *
 *	toolHelp is the tool-specific help for the tool.  It will be
 *	concatenated with the generic image tools help message and
 *	added to the toolCommand structure as the help string to be
 *	printed after the option listing.
 *
 *	toolOptions is the tool-specific option list.  It will be merged
 *	with the generic image tools options, and the list of image file
 *	format names supported by the library.  This larger option list
 *	is then used as the list of options supported by the tool.
 *
 *	toolEquivs is the tool-specific option equivalent keyword list.
 *	It will be merged with the generic image tools equivs.  This large
 *	equivs list is then used as the list of equivs supported by the tool.
 */

private ArgCommand toolCommand =
{
	/* command name */		"imsplit",

	/* major version # */		IMTOOLSMAJOR,
	/* minor version # */		IMTOOLSMINOR,
	/* subminor version # */	IMTOOLSSUBMINOR,

	/* -help pre-option list information				*/
"%command reads a multi-image input file and splits it into multiple single-\n\
image output files.  Input and output files may have different image file\n\
formats.\n\
",
	/* -help post-option list information				*/
	NULL,				/* filled in later on		*/

	/* -fullhelp pre-option list information			*/
	NULL,				/* Use same message as for -help*/
	/* -fullhelp post-option list information			*/
	NULL,				/* filled in later on		*/

	ARGFNONE,			/* No special flags		*/
	"[options...] infilename outfilename",
	"[options...] infilename outfilename",
	"SDSC Image Tools, October 1992.",
	"Copyright (c) 1989-1992  San Diego Supercomputer Center (SDSC), CA, USA",
	NULL,				/* filled in later on		*/
	NULL,				/* filled in later on		*/
};

private char *toolHelp = "\n\
Typical Invocations:\n\
    Split an HDF multi-image file into multiple RGB files:\n\
        %command many.hdf single%02d.rgb\n\
    Extract images 3, 4, and 5 and save them to Sun rasterfiles:\n\
        %command many.hdf -frames 3-5 single%02d.ras\n\
    Extract images 1, 6, and 9:\n\
        %command lots.tiff -frames 1 6 9 output%d.rgb\n\
    Extract images 1, 6, and 9 again:\n\
        %command lots.tiff -frame 1 -frame 6 -frame 9 output%d.rgb\n\
";

private char *toolFullHelp = "\n\
Files:\n\
    -infile names the multi-image input file from which images are to be\n\
    read.  -outfile gives an output image file name template with an\n\
    embedded \%d directive indicating where to insert a frame number.\n\
    An individual output file will be created for each extracted image.\n\
\n\
    By default all images in the input file are extracted into separate\n\
    output files.  -frames may be used to provide a list of frames to\n\
    extract.\n\
";

private char *toolNote = "\n\
Additional Help:\n\
    This is an abbreviated help listing.  For a full listing of options,\n\
    including a list of image file formats supported, type:\n\
        %command -fullhelp\n\
";

#define TOOLNOPTIONS	4
private ArgOption toolOptions[TOOLNOPTIONS] =
{
	{ "infile", "image_filename", "Specify an input image file name",
	  ARGFREQUIRED | ARGFIMPKEYWORD, 1, 1, ARGTSTRING },

	{ "outfile", "image_filename", "Specify an output image file name",
	  ARGFREQUIRED | ARGFIMPKEYWORD, 1, 1, ARGTSTRING },

	{ "frames", "frame_numbers...", "List frames to extract",
	  ARGFNONE | ARGFMULTIPLE, 1, ARGVNOMAX, ARGTINT | ARGTRANGE },

	{ "verbose", NULL, "Be verbose",
	  ARGFFULLHELP, 0, 0, ARGTNONE },
};

#define TOOLNEQUIVS	0
#if TOOLNEQUIVS == 0
private ArgEquiv *toolEquivs;
#else
private ArgEquiv toolEquivs[TOOLNEQUIVS] =
{
};
#endif





/*
 *  GLOBALS
 *	toolInFilename	-  the name of the input file (could be 'stdin')
 *	toolOutFilename	-  the name of the output file (could be 'stdout')
 *
 *	toolInFormat	-  the name of the input file's format (could be NULL)
 *	toolOutFormat	-  the name of the output file's format (could be NULL)
 *
 *	toolInTable	-  a table for the storage of data read in
 *	toolFlagsTable	-  a table for the storage of read/write flags
 *
 *  DESCRIPTION
 *	Data held by these variables is used throughout the tool.
 */

private char      toolInFilename[1024];	/* Input file name		*/
private char      toolInFormat[1024];	/* Input file's format name	*/

private char      toolOutFilename[1024];/* Output file name		*/
private char      toolOutFormat[1024];	/* Output file's format name	*/

private TagTable *toolInTable;		/* Data tag table		*/
private TagTable *toolOutTable;		/* Data tag table		*/
private TagTable *toolFlagsTable;	/* Flags tag table		*/

private ImVfb	 *toolInVfb;		/* input image			*/
private ImVfb	 *toolOutVfb;		/* output image			*/

typedef struct toolFrames
{
	int frameStart;			/* Starting frame of range	*/
	int frameEnd;			/* Ending frame of range	*/
	struct toolFrames *frameNext;	/* Next frame in list		*/
	struct toolFrames *framePrev;	/* Previous frame in list	*/
} toolFrames;

private toolFrames *toolFramesList = NULL;	/* List of frames	*/





/*
 *  FUNCTION
 *	main	-  main program
 *
 *  DESCRIPTION
 *	Control things:
 *		-  Initialize things (parse arguments and set up tables).
 *		-  Read in the input file (put data into data table).
 *		-  Write out each output file (get data from data table).
 *	That's about it.
 *
 *  NOTES
 *	This 'main' is pretty much the same for each of the image tools.
 *	Differences between tools include:
 *		-  the number of input files read in
 *		-  the number of output files written out
 *		-  the actions taken on the data between read and write
 */

main( argc, argv )
	int argc;			/* Argument count		*/
	char *argv[];			/* Argument vector		*/
{
	int         nInVfb;		/* Number of images in file	*/
	int         i, j, k;		/* Counters			*/
	int	    n;			/* Number of frames to extract	*/
	TagEntry   *dataEntry;		/* Entry from data table	*/
	TagEntry   *reEntry;		/* copy of Entry from data table*/
	char	   *tag;		/* Entry tag name		*/
	int	    startFrame;		/* Starting frame of a range	*/
	int	    endFrame;		/* Ending frame of a range	*/
	toolFrames *pFrame;		/* Frame list entry		*/
	toolFrames *pFrame2;		/* Frame list pointer		*/
        char        outFilename[1024];  /* output file's name           */
        ArgValue   *value;              /* Argument value               */


	/*
	 *  Initialize things:
	 *	-  Prepare the arg parsing data, then parse the command-line.
	 *	-  Prepare the flags table based upon command-line args.
	 *	-  Determine the file formats for input and output files.
	 */
	toolInit( argc, argv );


	/*
	 *  Read in each input file.
	 *	-  Open the file (or stdin) and read data into the data table.
	 */
        ImToolsFileRead( toolInFilename, toolInFormat, toolFlagsTable,
		 toolInTable );


	/*
	 *  Check for errors
	 *	-  no input images
	 */
	nInVfb = TagTableQNEntry( toolInTable, "image vfb" );
	if ( nInVfb == 0 )
	{
		fprintf( stderr, "%s: Input file contains no images!\n",
			ImToolsProgram );
		exit( 1 );
	}



        /*
         *  Build up a sorted linked list of frames to extract.
         */
	for ( i = 0, n = 0; i < ArgQNOccur( "frames" ); i++ )
	{
		for ( j = 0; j < ArgQNValue( "frames", i ); j++ )
		{
			value = ArgQValue( "frames", i, j );
			switch ( value->arg_rform )
			{
			case ARGRCLOSED:        /* n-m  start and end values*/
				startFrame = value->arg_ir1;
				endFrame   = value->arg_ir2;
				break;

			case ARGROPEN:          /* n-   start and wildcard*/
				startFrame = value->arg_ir1;
				endFrame   = nInVfb - 1;
				break;

			case ARGRSINGLE:        /* n    one only	*/
				startFrame = value->arg_i;
				endFrame   = startFrame;
				break;
			}
			if ( startFrame > endFrame )
			{
				k = startFrame;
				startFrame = endFrame;
				endFrame = k;
			}
			if ( startFrame < 0 )
			{
				fprintf( stderr, "%s: Frame ranges must be positive!\n", ImToolsProgram );
				exit( 1 );
			}
			if ( endFrame >= nInVfb )
			{
				fprintf( stderr, "%s: There are only %d frames in the input file!\n", ImToolsProgram, nInVfb );
				exit( 1 );
			}
			n += endFrame - startFrame + 1;


			/*
			 *  Allocate a new list entry and initialize it.
			 */
			if ( (pFrame = (toolFrames *)malloc( sizeof( toolFrames ) )) == NULL )
			{
				perror( ImToolsProgram );
				exit( 1 );
			}
			pFrame->frameStart = startFrame;
			pFrame->frameEnd   = endFrame;
			pFrame->frameNext  = NULL;
			pFrame->framePrev  = NULL;


			/*
			 *  Insert the entry into the sorted list of frames.
			 */
			if ( toolFramesList == NULL )
			{
				toolFramesList = pFrame;
				continue;
			}
			pFrame2 = toolFramesList;
			while ( pFrame2->frameNext != NULL )
			{
				if ( startFrame < pFrame2->frameStart )
					break;
				pFrame2 = pFrame2->frameNext;
			}
			if ( pFrame2->frameNext == NULL &&
				startFrame > pFrame2->frameStart )
			{
				/* Add to the end of the list.		*/
				pFrame2->frameNext = pFrame;
				pFrame->framePrev  = pFrame2;
				continue;
			}
			if ( pFrame2->framePrev == NULL )
			{
				/* Add to the start of the list.	*/
				toolFramesList = pFrame;
				pFrame->frameNext = pFrame2;
				pFrame2->framePrev = pFrame;
				continue;
			}
			/* Add before this entry.			*/
			pFrame2->framePrev->frameNext = pFrame;
			pFrame->framePrev = pFrame2->framePrev;
			pFrame2->framePrev = pFrame;
			pFrame->frameNext = pFrame2;
		}
	}
	if ( toolFramesList == NULL )
	{
		if ( (toolFramesList = (toolFrames *)malloc( sizeof( toolFrames ) )) == NULL )
		{
			perror( ImToolsProgram );
			exit( 1 );
		}
		toolFramesList->frameStart = 0;
		toolFramesList->frameEnd   = nInVfb;
		toolFramesList->frameNext  = NULL;
		toolFramesList->framePrev  = NULL;
		n = nInVfb;
	}


	/*
	 *  Make sure we were given an output file name with a printf
	 *  directive when we're supposed to extract more than one file.
	 */
	if ( (strchr( toolOutFilename, '%' ) == NULL) &&
		(nInVfb != 1) && (n != 1) )
	{
		fprintf( stderr, "%s: Output file name template must include a %%d for the\n", ImToolsProgram );
		fprintf( stderr, "%s: frame number when extracting more than 1 image\n", ImToolsProgram );
		exit( 1 );
        }


	/*
	 *  Split!
	 *	-  Scan the tag table.  As we find tags, add them to the
	 *	   output tag table.
	 *		-  If a tag from the input tag table is already present
	 *	   	   in the output table, replace it.
	 *		-  If not, append it.
	 *	-  On each VFB tag, write out the output table if the image
	 *	   number is one of those in the frames list.
	 */
	pFrame = toolFramesList;
	for ( i = 0, k = -1;
		(dataEntry = TagTableQLinear( toolInTable, i )) != TAGENTRYNULL;
		i++ )
	{
		/*
		 *  Get the next tag of the data entry.
		 */
		tag = TagEntryQTag( dataEntry );


		/*
		 *  Add the entry to the output tag table.
		 */
	        reEntry = TagEntryDup( dataEntry );
	        if ( TagTableQNEntry( toolOutTable, tag ) != 0 )
		{
			/*
			 *  Tag is already in the output table.  Replace it.
			 */
			j = TagEntryQNthEntry( TagTableQDirect( toolOutTable, tag, 0 ) );
			TagTableReplace( toolOutTable, j, reEntry );
		}
	        else
		{
			/*
			 *  Tag isn't in output table yet.  Add it.
			 */
			TagTableAppend( toolOutTable, reEntry );
		}


		/*
		 *  Write out the current state of the output tag table
		 *  if the tag we just added was an image, and if it
		 *  is one of the images we want to extract!
		 */
		if ( strcmp( tag, "image vfb" ) != 0 )
			continue;		/* Not an image		*/
		k++;
		while ( pFrame && k > pFrame->frameEnd )
			pFrame = pFrame->frameNext;
		if ( !pFrame )
			break;			/* Done with list	*/
		if ( k < pFrame->frameStart )
			continue;		/* Not in range		*/


		if ( ImToolsVerbose )
		{
			fprintf( stderr, "%s: Extracting image %d of %d \n",
					ImToolsProgram, k+1, nInVfb );
		}

		sprintf( outFilename, toolOutFilename, k );
		ImToolsFileWrite( outFilename, toolOutFormat,
			 toolFlagsTable, toolOutTable );
	}
}





/*
 *  FUNCTION
 *	toolInit	-  initialize things for the tool
 *
 *  DESCRIPTION
 *	The tool's argument parsing data structures are set up to include:
 *		- the full help message (generic help + tool-specific help)
 *		- the full option list (generic options + tool-specific options)
 *		- the full equivs list (generic equivs + tool-specific equivs)
 *
 *	Command-line arguments are then parsed.  The results are used to
 *	set up the flags table (the generic -out* options).
 *
 *	Input and output file's names and formats are determined from the
 *	command-line arguments.
 *
 *  NOTES
 *	This function is included in most of the image tools and differs
 *	only in slight ways.  Typical differences include:
 *		-  the number of input and output file names found
 *		-  the number of input and output file formats found
 *		-  the number of command-line arg flags checked for
 */

private void				/* Returns nothing		*/
toolInit( argc, argv )
	int argc;			/* Argument count		*/
	char *argv[ ];			/* Argument vector		*/
{
	int	    i;			/* Counter			*/
	int	    noccur;		/* Number of option occurrences	*/
	int         nOpt;		/* Number of options		*/
	int         nEquiv;		/* Number of equivalences	*/
	ArgOption  *options1;		/* Argument options		*/
	ArgOption  *options;		/* Argument options		*/
	ArgEquiv   *equivs1;		/* Argument equivalent keywords	*/
	ArgEquiv   *equivs;		/* Argument equivalent keywords	*/

	char       *tmp;		/* Temporary string holder	*/
	char       *tmpFormat;		/* Tmp format name		*/


	/*
	 *  Save the name of the program, as invoked.
	 */
	ImToolsProgram = argv[0];


	/*
	 *  Make a data table to hold the incomming data.
	 */
	if ( (toolInTable = TagTableAlloc( )) == TAGTABLENULL )
	{
		TagPError( ImToolsProgram );
		exit( 1 );
	}
	if ( (toolOutTable = TagTableAlloc( )) == TAGTABLENULL )
	{
		TagPError( ImToolsProgram );
		exit( 1 );
	}


	/*
	 *  Use the standard Image Tools user registration and feedback forms.
	 */
	toolCommand.arg_register = ImToolsRegister;
	toolCommand.arg_feedback = ImToolsFeedback;


	/*
	 *  Allocate space for the total help string for the tool.  Copy the
	 *  tool-specific help in, then concatenate on the generic help text
	 *  used by most of the image tools.
	 */
	if ( (tmp = (char *)malloc( sizeof( char ) * (strlen( toolNote ) +
		strlen( toolHelp ) + 1) )) == NULL )
	{
		perror( ImToolsProgram );
		exit( 1 );
	}
	strcpy( tmp, toolHelp );
	strcat( tmp, toolNote );
	toolCommand.arg_help2 = tmp;

	if ( (tmp = (char *)malloc( sizeof( char ) * (strlen( ImToolsBaseHelp) +
		strlen( toolHelp ) + strlen( toolFullHelp ) + 1) )) == NULL )
	{
		perror( ImToolsProgram );
		exit( 1 );
	}
	strcpy( tmp, toolHelp );
	strcat( tmp, toolFullHelp );
	strcat( tmp, ImToolsBaseHelp );
	toolCommand.arg_fullhelp2 = tmp;


	/*
	 *  Build up an option list by merging the tool-specific options,
	 *  the standard (base) tool options, and those for the various
	 *  image file formats.
	 */
	nOpt = ImToolsMergeOptions( TOOLNOPTIONS, toolOptions,
		IMTOOLSNBASEOPTIONS, ImToolsBaseOptions, &options1 );
	if ( (nOpt = ImFileFormatOptions( nOpt, options1, &options )) == -1)
	{
		ImPError( ImToolsProgram );
		exit( 1 );
	}


	/*
	 *  Build up an equivalent keyword list by merging the tool-specific
	 *  equivalences, the standard (base) tool equivalences, and those
	 *  for the various image file formats.
	 */
	nEquiv = ImToolsMergeEquivs( TOOLNEQUIVS, toolEquivs,
		 IMTOOLSNBASEEQUIVS, ImToolsBaseEquivs, &equivs1 );
	if ( (nEquiv = ImFileFormatEquivs( nEquiv, equivs1, &equivs )) == -1)
	{
		ImPError( ImToolsProgram );
		exit( 1 );
	}

	/*
	 *  Parse the command line!
	 */
	nOpt = ArgParse( argc, argv, &toolCommand, nOpt, options,
		nEquiv, equivs );
	if ( ArgQNOccur( "verbose" ) != 0 )
		ImToolsVerbose = TRUE;


	/*
	 *  Set up the flags table based upon command-line arguments.
	 *  This is primarily derived from the -out* directives part of the
	 *  standard image tool option set (see ImToolsBaseOptions above).
	 *  Also included are flags to direct error messages to stderr and
	 *  a flag giving the program's name for later use in error messages.
	 */
	toolFlagsTable = ImToolsBuildFlagsTable( );


	/*
	 *  Get the input file's name (-infile), and search backwards in the
	 *  command-line option list to find the last format selection (if
	 *  any).  Stop the search on the beginning of the command-line, or
	 *  on -outfile.
	 */
	strcpy( toolInFilename, ArgQValue( "infile", 0, 0 )->arg_s );
	tmpFormat = NULL;
	for ( i = ArgQOccurOpt( "infile", 0 ) - 1; i >= 0; i-- )
	{
		tmp = ArgQOpt( i, &noccur );


		/*
		 *  Stop looking backward when we reach any other file name
		 *  argument.
		 */
		if ( strcmp( tmp, "outfile" ) == 0 )
			break;


		/*
		 *  Skip it if it isn't the name of a file format.
		 */
		if ( !ImToolsIsFormat( tmp ) )
			continue;


		if ( tmpFormat != NULL )
		{
			fprintf( stderr, "%s:  Only 1 file format selection may precede -infile.\n",
				ImToolsProgram );
			exit( 1 );
		}
		tmpFormat = tmp;
	}
	if ( tmpFormat == NULL )
		*toolInFormat = '\0';
	else
		strcpy( toolInFormat, tmpFormat );

	/*
	 *  Get the output file's name (-outfile), and search backwards in the
	 *  command-line option list to find the last format selection (if
	 *  any).  Stop the search on the beginning of the command-line, or
	 *  on -infile.
	 */
	strcpy( toolOutFilename, ArgQValue( "outfile", 0, 0 )->arg_s );
	tmpFormat = NULL;
	for ( i = ArgQOccurOpt( "outfile", 0 ) - 1; i >= 0; i-- )
	{
		tmp = ArgQOpt( i, &noccur );


		/*
		 *  Stop looking backward when we reach any other file name
		 *  argument.
		 */
		if ( strcmp( tmp, "infile" ) == 0 )
			break;


		/*
		 *  Skip it if it isn't the name of a file format.
		 */
		if ( !ImToolsIsFormat( tmp ) )
			continue;


		if ( tmpFormat != NULL )
		{
			fprintf( stderr, "%s:  Only 1 file format selection may precede -outfile.\n",
				ImToolsProgram );
			exit( 1 );
		}
		tmpFormat = tmp;
	}
	if ( tmpFormat == NULL )
		*toolOutFormat = '\0';
	else
		strcpy( toolOutFormat, tmpFormat );

}
