/*****************************************************************************
   Major portions of this software are copyrighted by the Medical College
   of Wisconsin, 1994-2000, and are released under the Gnu General Public
   License, Version 2.  See the file README.Copyright for details.
******************************************************************************/

#ifndef __MAIN_PLUGIN_REORDER_C__
#define __MAIN_PLUGIN_REORDER_C__

/***************************************************
*  File:   plug_reorder.c
*
*  Author: Jay Brian Kummer, Medical College of WI
*          [kummer@its.mcw.edu]
*
*  Date:   May 8, 1997
*
*  Purpose:
*
*    Plugin to reorder the epochs (i.e., stimulus
*    presentation regions) in a 3D+Time data set.
****************************************************/

/***********************************************************************/
/* C O N D I T I O N A L   C O M P I L A T I O N   D I R E C T I V E S */
/***********************************************************************/

#define MAIN_PLUGIN_REORDER
/* Uncomment to make this plugin verbose:
#define DEBUG_PLUGIN_REORDER
*/

/*********************************/
/* D A T A   S T R U C T U R E S */
/*********************************/

typedef struct { /* returned from 'plug_reorder_parseMap' */
	char classKRH;  /* the class (i.e., [a-zA-Z]) of a time-course segment */
	int length;  /* the length of the class in time points */
	} ClassInfo;

static char helpstring[] = /* Help string for plugin interface */
		"Purpose: Reorders voxel time-courses within a 3D+time BRIK.\n"
		"Input	: Name of the 3D+time dataset to reorder.\n"
		"\n"
		"Output	: Prefix of filename for the new dataset.\n"
		"\n"
		"Map file : Text file with instructions a reordering the input dataset.\n"
		"	Sample map file:\n"
		"		# Experiment: Bogus\n"
		"		# Date: April 1, 1997\n"
		"		-\n"
		"		-\n"
		"		D1\n"
		"		D2\n"
		"		D3\n"
		"		B1\n"
		"		B2\n"
		"		B3\n"
		"		B4\n"
		"		-\n"
		"		a1\n"
		"		a2\n"
		"		a3\n"
		"		A1\n"
		"		A2\n"
		"		A3\n"
		"		A1\n"
		"		A2\n"
		"		A3\n"
		"		-\n"
		"\n"
		"	# : Comment line (ignored)\n"
		"	- : Point to be omitted from the output time-series. In the sample map,\n"
		"		time points 1, 2, 10, and 20 will be omitted.\n"
		"	Each non-comment line in a map file corresponds to a point in the time-series of\n"
		"	an input 3D+time dataset. Codes for each time point define the plug-in action for\n"
		"	those points. For example, the code in the fifth non-comment line of the sample\n"
		"	above, 'D3', means that the fifth point in the time-series is the third point in\n"
		"	the presentation of a stimulus named 'D'. \n"
		"		Stimulus codes are single alphabetic characters and are case-sensitive, with\n"
		"	uppercase preceding lowercase in the reordering (i.e., A-Z then a-z). Stimulus\n"
		"	regions are numbered starting from 1 (i.e., [ X0 X1 X2 ] and [ X1 X3 X5 ] will\n"
		"	cause the plug-in to abort), and, as shown in the sample with the code 'A', this\n"
		"	delimits duplicate instances of the same stimulus code. For the sample map, images\n"
		"	would be reordered so that the output time-series is:\n"
		"					[ A1 A2 A3 A1 A2 A3 B1 B2 B3 B4 C1 C2 C3 a1 a2 a3 ]\n"
		"\n"
		"Duplicates : If there are duplicates of a stimulus presentation within a map (e.g.,\n"
		"[ A1 A2 A3 ] in the sample), they can be:\n"
		"	1) Collated: Duplicate instances of a stimulus will be placed in the order in\n"
		"		which they occur in the map. In the case of the sample, this results in the\n"
		"		ordering [ A1 A2 A3 A1 A2 A3 ... ].\n"
		"	2) Averaged: Duplicate instances of a stimulus will be averaged. In the sample,\n"
		"		this would result in the ordering [ A1' A2' A3' ... ], where Ai' is the mean\n"
		"		of the i-th point of all of the stimulus A's present in the map file.\n"
		"	**Duplicate stimulus regions must have the same length! For example,\n"
		"	[ D1 D2 X1 D1 D2 D3 ] will cause the plug-in to abort.\n"
		;

static char *dupaction_strings[] = /* strings for duplicate action in plugin interface */ 
		{ "Collate", "Average" };

#define NUM_DUPACTION_STRINGS (sizeof(dupaction_strings)/sizeof(char *))

/***************************/
/* H E A D E R   F I L E S */
/***************************/

#include "afni.h"

#include <unistd.h>
#include "plug_reorder_parseMap.c"

/***********************************************************/
/* I N T E R N A L   F U N C T I O N   P R O T O T Y P E S */
/***********************************************************/

char *REORDER_main(PLUGIN_interface *);/* the entry point */

/*************************************/
/* S U P P O R T   F U N C T I O N S */
/*************************************/

int compare_class(const void *i, const void *j)
/* qsort compare routine for
   sorting 'ClassInfo' array
*/
{
ClassInfo *iTmp = (ClassInfo *)i;
ClassInfo *jTmp = (ClassInfo *)j;

if(iTmp->classKRH == jTmp->classKRH) {
	return(0);
	}
else if(iTmp->classKRH < jTmp->classKRH) {
	return(-1);
	}
else {
	return(1);
	}
}

/***********************************************************************
*  Set up the interface to the user:
*  1) Create a new interface using "PLUTO_new_interface";
*  
*  2) For each line of inputs, create the line with "PLUTO_add_option"
*  	(this line of inputs can be optional or mandatory);
*  
*  3) For each item on the line, create the item with
*  	"PLUTO_add_dataset" for a dataset chooser,
*  	"PLUTO_add_string"for a string chooser,
*  	"PLUTO_add_number"for a number chooser.
************************************************************************/


DEFINE_PLUGIN_PROTOTYPE

PLUGIN_interface *PLUGIN_init(int ncall)
{
PLUGIN_interface *plint;  /* will be the output of this routine */

if(ncall >= 1) { /* only one interface */
	return NULL;
	}

   CHECK_IF_ALLOWED("REORDER","Reorder") ;  /* 30 Sep 2016 */

/*---------------- set titles and call point ----------------*/

plint = PLUTO_new_interface("Reorder",
							"Reorders voxel time-courses within a 3D+Time BRIK",
							helpstring,
							PLUGIN_CALL_VIA_MENU,
							(cptr_func *)REORDER_main);

   PLUTO_set_sequence( plint , "z:Kummer" ) ;

/*--------- 1st line: Input dataset ---------*/

PLUTO_add_option(plint,
				"Input",/* label at left of input line */
				"Input",/* tag to return to plugin */
				 TRUE		/* is this mandatory? */
				 );

PLUTO_add_dataset(plint,
				  "---->>",          /* label next to button	*/
				  ANAT_ALL_MASK,     /* take any anat datasets */
				  0,                 /* don't allow fim funcs*/
				  DIMEN_4D_MASK |    /* need 3D+time datasets*/
				  BRICK_ALLTYPE_MASK /* handle any type		*/
				  );

/*---------- 2nd line: Output dataset prefix ----------*/

PLUTO_add_option(plint,
				 "Output",  /* label at left of input line */
				 "Output",  /* tag to return to plugin */
				 TRUE       /* is this mandatory? */
				 );

PLUTO_add_string(plint,
				 "Prefix",  /* label next to textfield */
				 0,NULL,    /* no fixed strings to choose among */
				 19         /* 19 spaces for typing in value */
				 );

/*---------- 3rd line: Map file name ----------*/

PLUTO_add_option(plint,
				 "Map file",  /* label at left of input line */
				 "MapFile",   /* tag to return to plugin */
				 TRUE         /* is this mandatory? */
				 );

PLUTO_add_string(plint,
				 "FileName",  /* label next to textfield */
				 0,NULL,      /* no fixed strings to choose among */
				 19           /* 19 spaces for typing in value */
				 );

PLUTO_add_string(plint,
				 "Duplicates",          /* label next to textfield */
				 NUM_DUPACTION_STRINGS, /* number of strings to choose among */
				 dupaction_strings,     /* fixed strings to choose among */
				 0                      /* index of default (collate) */
				 );

/*--------- done with interface setup ---------*/

return plint;
}

/*************************************/
/* M A C R O   D E F I N I T I O N S */
/*************************************/

/* Free vectors  */
#undef  FREEUP
#define FREEUP(thing) if((thing) != NULL) {free((thing)); (thing) = NULL;}

#define FREE_WORKSPACE do{ FREEUP(bPtr); FREEUP(sPtr); FREEUP(fPtr); \
						   FREEUP(map); FREEUP(classKRH); } while(0)

/*****************************************************************
*  Main routine for this plugin (will be called from AFNI).
*  If the return string is not NULL, some error transpired, and
*  AFNI will popup the return string in a message box.
******************************************************************/

char *REORDER_main(PLUGIN_interface *plint)
{
MCW_idcode *idc;            /* input dataset idcode */
THD_3dim_dataset *old_dset; /* input dataset  */
THD_3dim_dataset *new_dset; /* output dataset */

ClassInfo *classKRH;           /* array of ClassInfo structures, returned by reference from 'parseMap' */

char *new_prefix;           /* pointer to the output dataset prefix */
char *mapFile;              /* pointer to the name of map file to open */
char *dupstr;               /* pointer to duplicate action label string */
char currentClass;          /* character sentinel */

register int ii;            /* register counter variables */
register int jj;
register int kk;
register int mm;

int perc;                   /* percentage value for progress meter */
int ninp;                   /* number of time points */
int nvox;                   /* number of voxels */
int old_datum;              /* data type of input dataset */
int error;                  /* general status variable */
int startIndex;             /* index sentinel */
int currentLength;          /* length of the current epoch */
int newLength;              /* length of the time-course after averaging duplicates */
int instances;              /* number of instances of the current epoch class */
int timePoint;              /* current time point being processed */
int *map;                   /* array of indices for remapping time points, returned from 'parseMap' */
int mapLength;              /* length of 'map' array, returned by reference from 'parseMap' */
int dupaction;              /* 0: collate duplicate epochs, !0: average duplicate epochs */
int classCount;             /* length of 'ClassInfo' array, returned by reference from 'parseMap' */

double sum;                 /* sum variable for averaging */
double divisor;             /* divisor for calculating the mean, this will equal 'instances' */

byte  **bPtr = NULL;        /* for byte data, will point to the 3D+time data to be reordered */
short **sPtr = NULL;        /* for short data, will point to the 3D+time data to be reordered */
float **fPtr = NULL;        /* for float data, will point to the 3D+time data to be reordered */

byte  **bData = NULL;       /* for byte data, will point to the reordered 3D+time data */
short **sData = NULL;       /* for short data, will point to the reordered 3D+time data */
float **fData = NULL;       /* for float data, will point to the reordered 3D+time data */

Bool dupsToAverage = False; /* flag to indicate whether there are segments to average */

/**********************************************************************/
/****** Check inputs from AFNI to see if they are reasonable-ish ******/

/*--------- go to first input line ---------*/

PLUTO_next_option(plint);

idc	= PLUTO_get_idcode(plint);    /* get dataset item */

old_dset = PLUTO_find_dset(idc);  /* get Ptr to dataset */
if(NULL == old_dset) {
	return "*************************\n"
			"Cannot find Input Dataset\n"
			"*************************";
	}

/*--------- go to second input line ---------*/

PLUTO_next_option(plint);

new_prefix = PLUTO_get_string(plint);   /* get string item (the output prefix) */
if(! PLUTO_prefix_ok(new_prefix)) {     /* check if it is OK */
	return "************************\n"
			"Output Prefix is illegal\n"
			"************************";
	}

/*--------- go to next input lines ---------*/

PLUTO_next_option(plint); /* skip to next line */

mapFile = PLUTO_get_string(plint);                  /* get the map filename */
if(NULL == mapFile || access(mapFile, R_OK) < 0) {  /* check if it is OK and readable */
	return "****************************\n"
			"Epoch map file is unreadable\n"
			"****************************";
	}

dupstr = PLUTO_get_string(plint);         /* get the string item */
dupaction = PLUTO_string_index(dupstr,    /* find it in the list it came from */
							   NUM_DUPACTION_STRINGS,
							   dupaction_strings);

/********************************************************/
/*********** At this point, the inputs are OK ***********/

PLUTO_popup_meter(plint);  /* popup a progress meter */

/*--------- set up pointers to each sub-brick in the input dataset ---------*/

#ifdef DEBUG_PLUGIN_REORDER
printf("[Reorder] Loading dataset into memory...\n");
#endif
DSET_load(old_dset);      /* must be in memory before we get pointers to it */

nvox = /* number of voxels per sub-brick */
	old_dset->daxes->nxx * old_dset->daxes->nyy * old_dset->daxes->nzz;

#ifdef DEBUG_PLUGIN_REORDER
printf("[Reorder] %d voxels per subBRIK [%dx%dx%d].\n"
	, nvox, old_dset->daxes->nxx, old_dset->daxes->nyy, old_dset->daxes->nzz);
#endif

ninp = DSET_NUM_TIMES(old_dset); /* get number of time points in old dataset */
#ifdef DEBUG_PLUGIN_REORDER
printf("[Reorder] %d time points [subBRIK].\n", ninp);
#endif

/********************************************************/
/*************** Parse the epoch map file ***************/

mapLength = ninp; /* initialize with the expected length for mapFile entries */
if(NULL == (map = REORDER_parseMap(mapFile
								, &mapLength
								, &classKRH
								, &classCount))) {
	return "*******************************\n"
	 	   "Critical error parsing map file\n"
		   "*******************************";
	}

#ifdef DEBUG_PLUGIN_REORDER0
printf("\n[Reorder] Indices for epoch remapping:\n");
for(ii = 0; ii < mapLength; ii++) {
	printf("%d\n", map[ii]);
	}

printf("\n[Reorder] Meta-sequence of epoch classes is:\n");
for(ii = 0; ii < classCount; ii++) {
	printf("[%d] Class %c [Width %d TRs]\n", ii, classKRH[ii].classKRH, classKRH[ii].length);
	}
#endif

/* Sort 'class' to reflect the final order
   to aid in any epoch averaging required */
qsort((void *)classKRH, classCount, sizeof(ClassInfo), compare_class);

#ifdef DEBUG_PLUGIN_REORDER
printf("\n[Reorder] Sorted meta-sequence of epoch classes is:\n");
for(ii = 0; ii < classCount; ii++) {
	printf("[%d] Class %c [Width %d TRs]\n", ii, classKRH[ii].classKRH, classKRH[ii].length);
	}
#endif

old_datum /* get old dataset datum type */
	= DSET_BRICK_TYPE(old_dset, 0);

switch(old_datum) { /* pointer type depends on input datum type */

	default:
		return "*******************************\n"
				"Illegal datum in Input Dataset\n"
				"******************************";

	/**create array of pointers into old dataset sub-bricks **/
	/**Note that we skip the first 'ignore' sub-bricks here **/

	/*---------- input is bytes -----------*/
	/* voxel #i at time #k is bPtr[k][i]   */
	/* for i = 0..nvox-1 and k = 0..ninp-1.*/

	case MRI_byte:
		/* Workspace for 3D+time data */
		bPtr = (byte **)calloc(sizeof(byte *), ninp);
		if(NULL == bPtr) {
			return "************************\n"
				   "!! Allocation Failure !!\n"
				   "************************";
			}

		for(kk = 0; kk < ninp; kk++) {
			bPtr[kk] = (byte *)DSET_ARRAY(old_dset,kk);
			}

		/* Array of pointers for reordering subBRIKS */
		bData = (byte **)calloc(sizeof(byte *), mapLength);
		if(NULL == bData) {
			FREEUP(bPtr);
			return "************************\n"
				   "!! Allocation Failure !!\n"
				   "************************";
			}
		/* Allocate memory for subBRIKS */
		for(kk = 0; kk < mapLength; kk++) {
			bData[kk] = (byte *)calloc(sizeof(byte), nvox);
			if(NULL == bData[kk]) {
				FREEUP(bPtr);
				FREEUP(bData);
				return "************************\n"
					   "!! Allocation Failure !!\n"
					   "************************";
				}
			}
		break;

	/*---------- input is shorts ----------*/
	/* voxel #i at time #k is sPtr[k][i]   */
	/* for i = 0..nvox-1 and k = 0..ninp-1.*/

	case MRI_short:
		/* Workspace for 3D+time data */
		sPtr = (short **)calloc(sizeof(short *), ninp);
		if(NULL == sPtr) {
			return "************************\n"
				   "!! Allocation Failure !!\n"
				   "************************";
			}

		for(kk = 0; kk < ninp; kk++) {
			sPtr[kk] = (short *)DSET_ARRAY(old_dset,kk);
			}

		/* Array of pointers for reordering subBRIKS */
		sData = (short **)calloc(sizeof(short *), mapLength);
		if(NULL == sData) {
			FREEUP(sPtr);
			return "************************\n"
				   "!! Allocation Failure !!\n"
				   "************************";
			}
		/* Allocate memory for subBRIKS */
		for(kk = 0; kk < mapLength; kk++) {
			sData[kk] = (short *)calloc(sizeof(short), nvox);
			if(NULL == sData[kk]) {
				FREEUP(sPtr);
				FREEUP(sData);
				return "************************\n"
					   "!! Allocation Failure !!\n"
					   "************************";
				}
			}
		break;

	/*---------- input is floats ----------*/
	/* voxel #i at time #k is fPtr[k][i]   */
	/* for i = 0..nvox-1 and k = 0..ninp-1.*/

	case MRI_float:
		/* Workspace for 3D+time data */
		fPtr = (float **)calloc(sizeof(float *), ninp);
		if(NULL == fPtr) {
			return "************************\n"
				   "!! Allocation Failure !!\n"
				   "************************";
			}

		for(kk = 0; kk < ninp; kk++) {
			fPtr[kk] = (float *)DSET_ARRAY(old_dset,kk);
			}

		/* Array of pointers for reordering subBRIKS */
		fData = (float **)calloc(sizeof(float *), mapLength);
		if(NULL == fData) {
			FREEUP(fPtr);
			return "************************\n"
				   "!! Allocation Failure !!\n"
				   "************************";
			}
		/* Allocate memory for subBRIKS */
		for(kk = 0; kk < mapLength; kk++) {
			fData[kk] = (float *)calloc(sizeof(float), nvox);
			if(NULL == fData[kk]) {
				FREEUP(fPtr);
				FREEUP(fData);
				return "************************\n"
					   "!! Allocation Failure !!\n"
					   "************************";
				}
			}
		break;

	} /* end of switch on input type */

/*********************** Make a new dataset ***********************/

new_dset /* start with copy of old one */
	= EDIT_empty_copy(old_dset);

      { char * his = PLUTO_commandstring(plint) ;
        tross_Copy_History(old_dset,new_dset) ;
        tross_Append_History(new_dset,his) ; free(his);
      }

/*-- edit some of its internal parameters --*/

ii = EDIT_dset_items(
		new_dset,
			ADN_prefix     , new_prefix,           /* filename prefix */
			ADN_malloc_type, DATABLOCK_MEM_MALLOC, /* store in memory */
			ADN_nvals      , mapLength,            /* # time points */
			ADN_ntt        , mapLength,            /* # time points */
		ADN_none);
if(0 != ii) {
	THD_delete_3dim_dataset(new_dset, False);
	switch(old_datum) {
		case MRI_byte:
			FREEUP(bPtr);
			FREEUP(bData);
			break;
		case MRI_short:
			FREEUP(sPtr);
			FREEUP(sData);
			break;
		case MRI_float:
			FREEUP(fPtr);
			FREEUP(fData);
			break;
		}
	FREE_WORKSPACE;
	return "***********************************\n"
			"Error while creating output dataset\n"
			"***********************************";
	}

/*****************************************************/
/****** Setup has ended. Now do some real work. ******/

switch(old_datum) {

	/*****************************************/
	/* voxel #i at time #k is array[kk][ii]  */
	/* for ii = 0..nvox-1 and kk = 0..ninp-1.*/

	case MRI_byte:
		#ifdef DEBUG_PLUGIN_REORDER
		printf("\n[Reorder] Collating/byte data...\n");
		#endif
		
		/* Reorder the time-course of each voxel */
		for(kk = 0; kk < mapLength; kk++) {
			if(NULL == memcpy((void *)bData[kk]
							, (void *)bPtr[map[kk]]
							, sizeof(byte)*nvox)) {
				THD_delete_3dim_dataset(new_dset, False);
				FREEUP(bPtr);
				FREEUP(bData);
				FREE_WORKSPACE;
				return "***********************************\n"
						"!! Error performing memory copy !!\n"
						"**********************************";
				}
			perc = (100 * kk) / mapLength; /* display percentage done */
			PLUTO_set_meter(plint, perc); /* on the progress meter */
			}
		
		PLUTO_set_meter(plint, 100);      /* set progress meter to 100% */
		
		if(dupaction) { /* new_dset may change in length again */
			/* Verify that averaging is possible:
			     All duplicates must have the same length.
			*/
			error = 0; /* start with no error */
			for(ii = 0; ii < classCount; ii++) {
				for(kk = 1; kk < (classCount-1); kk++) {
					if(classKRH[ii].classKRH == classKRH[kk].classKRH) {
						if(classKRH[ii].length != classKRH[kk].length) {
							error = 1; /* lengths differ! */
							break;
							}
						dupsToAverage = True; /* there is at least one duplicate to average */
						}
					}
				if(error) {
					PLUTO_popup_message(plint
							,"*********************************************************\n" 
							 "* Duplicate stimulus regions in map file must have the  *\n"
							 "* same length for averaging. Returning collated regions.*\n"
							 "*********************************************************");
					break;
					}
				}
		
			if(!error) { /* average the crap */
				#ifdef DEBUG_PLUGIN_REORDER
				printf("[Reorder] All duplicate epochs have the same length...\n");
				#endif
			
				if(dupsToAverage) {
					#ifdef DEBUG_PLUGIN_REORDER
					printf("[Reorder] Averaging duplicate epochs...\n");
					printf("[Reorder] Initial time-length is %d...\n", mapLength);
					#endif
			
					newLength = mapLength; /* length may change after averaging, track new length */
		
					/* Perform averaging */
					/* For each class, do: */
					for(ii = 0, startIndex = 0, timePoint = 0; ii < classCount;) {
						#ifdef DEBUG_PLUGIN_REORDER
						printf("[Reorder] Processing instances of class %c...\n"
							, classKRH[ii].classKRH);
						#endif
			
						/* Get the number of duplicates of the current class */
						for(jj = ii + 1, instances = 1; jj < classCount; jj++, instances++) {
							if(classKRH[ii].classKRH != classKRH[jj].classKRH) {
								break;
								}
							}
			
						#ifdef DEBUG_PLUGIN_REORDER
						printf("	%d instances of class %c...\n", instances, classKRH[ii].classKRH);
						#endif
			
						if(instances > 1) {
							#ifdef DEBUG_PLUGIN_REORDER
							printf("	Averaging %d contiguous epochs...\n", instances);
							#endif
		
							divisor = (double)instances;
		
							/* For each time point in the duplicated classes: */
							for(jj = startIndex; jj < (classKRH[ii].length+startIndex); jj++, timePoint++) {
								/* For each voxel: */
								for(kk = 0; kk < nvox; kk++) {
									/* Average the duplicate time-points for the current voxel */
									for(mm = 0, sum = 0.0; mm < instances; mm++) {
										sum = sum + (double)bData[jj+(mm*classKRH[ii].length)][kk];
										}
									bData[timePoint][kk] = (byte)((sum / divisor) + 0.5);
									}
								}
		
							/* Update final length of time-course */
							newLength = newLength - (classKRH[ii].length * (instances - 1));
		
							/* Jump over the indices of all instances of the current class */
							startIndex = startIndex + (instances *classKRH[ii].length);
		
							/* Go to the next class to process */
							ii += instances;
							}
						else { /* just copy the data points */
							#ifdef DEBUG_PLUGIN_REORDER
							printf("	Copying single epoch...\n");
							#endif
		
							for(jj = startIndex; jj < (classKRH[ii].length+startIndex); jj++, timePoint++) {
								for(kk = 0; kk < nvox; kk++) {
									bData[timePoint][kk] = bData[jj][kk];
									}
								}
		
							/* Jump to the index of the next class in the time-course */
							startIndex = startIndex + classKRH[ii].length;
		
							/* eat up another class */
							++ii;
							}
		
						/* Update progress meter with the percentage done */
						perc = (100 * timePoint) / mapLength;
						PLUTO_set_meter(plint, perc);
						}
			
					/* Set progress meter to 100% */
					PLUTO_set_meter(plint, 100);
			
					#ifdef DEBUG_PLUGIN_REORDER
					printf("[Reorder] Final time-length is %d...\n", newLength);
					#endif
					ii = EDIT_dset_items(
								new_dset,
									ADN_nvals, newLength,  /* # time points */
									ADN_ntt,   newLength,  /* # time points */
								ADN_none);
					if(0 != ii) {
						THD_delete_3dim_dataset(new_dset, False);
						FREEUP(bPtr);
						FREEUP(bData);
						FREE_WORKSPACE;
						return "******************************************\n"
								"!! Error while creating output dataset !!\n"
								"*****************************************";
						}
			
					/* Free unused subBRIKS */
					if(newLength < mapLength) {
						#ifdef DEBUG_PLUGIN_REORDER
						printf("[Reorder] Freeing %d unused subBRIKs...\n", mapLength-newLength);
						#endif
						for(ii = newLength; ii < mapLength; ii++) {
							FREEUP(bData[ii]);
							}
						bData = (byte **)realloc((void *)bData, sizeof(byte *)*newLength);
						if(NULL == bData) {
							THD_delete_3dim_dataset(new_dset, False);
							FREEUP(bPtr);
							FREE_WORKSPACE;
							return "************************\n"
								   "!! Reallocation error !!\n"
								   "************************";
							}
						}
					mapLength = newLength;
					}
				}
			}
		
		/* Write results into new dataset */
		#ifdef DEBUG_PLUGIN_REORDER
		printf("[Reorder] Writing %d subBRIKS into new data set...\n", mapLength);
		#endif
		for(kk = 0; kk < mapLength; kk++) {
			EDIT_substitute_brick(new_dset, kk, MRI_byte, bData[kk]);
			}
		break;

	case MRI_short:
		#ifdef DEBUG_PLUGIN_REORDER
		printf("\n[Reorder] Collating/short data...\n");
		#endif
		
		/* Reorder the time-course of each voxel */
		for(kk = 0; kk < mapLength; kk++) {
			if(NULL == memcpy((void *)sData[kk]
							, (void *)sPtr[map[kk]]
							, sizeof(short)*nvox)) {
				THD_delete_3dim_dataset(new_dset, False);
				FREEUP(sPtr);
				FREEUP(sData);
				FREE_WORKSPACE;
				return "***********************************\n"
						"!! Error performing memory copy !!\n"
						"**********************************";
				}
			perc = (100 * kk) / mapLength; /* display percentage done */
			PLUTO_set_meter(plint, perc); /* on the progress meter */
			}
		
		PLUTO_set_meter(plint, 100);      /* set progress meter to 100% */
		
		if(dupaction) { /* new_dset may change in length again */
			/* Verify that averaging is possible:
			     All duplicates must have the same length.
			*/
			error = 0; /* start with no error */
			for(ii = 0; ii < classCount; ii++) {
				for(kk = 1; kk < (classCount-1); kk++) {
					if(classKRH[ii].classKRH == classKRH[kk].classKRH) {
						if(classKRH[ii].length != classKRH[kk].length) {
							error = 1; /* lengths differ! */
							break;
							}
						dupsToAverage = True; /* there is at least one duplicate to average */
						}
					}
				if(error) {
					PLUTO_popup_message(plint
							,"*********************************************************\n" 
							 "* Duplicate stimulus regions in map file must have the  *\n"
							 "* same length for averaging. Returning collated regions.*\n"
							 "*********************************************************");
					break;
					}
				}
		
			if(!error) { /* average the crap */
				#ifdef DEBUG_PLUGIN_REORDER
				printf("[Reorder] All duplicate epochs have the same length...\n");
				#endif
			
				if(dupsToAverage) {
					#ifdef DEBUG_PLUGIN_REORDER
					printf("[Reorder] Averaging duplicate epochs...\n");
					printf("[Reorder] Initial time-length is %d...\n", mapLength);
					#endif
			
					newLength = mapLength; /* length may change after averaging, track new length */
		
					/* Perform averaging */
					/* For each class, do: */
					for(ii = 0, startIndex = 0, timePoint = 0; ii < classCount;) {
						#ifdef DEBUG_PLUGIN_REORDER
						printf("[Reorder] Processing instances of class %c...\n"
							, classKRH[ii].classKRH);
						#endif
			
						/* Get the number of duplicates of the current class */
						for(jj = ii + 1, instances = 1; jj < classCount; jj++, instances++) {
							if(classKRH[ii].classKRH != classKRH[jj].classKRH) {
								break;
								}
							}
			
						#ifdef DEBUG_PLUGIN_REORDER
						printf("	%d instances of class %c...\n", instances, classKRH[ii].classKRH);
						#endif
			
						if(instances > 1) {
							#ifdef DEBUG_PLUGIN_REORDER
							printf("	Averaging %d contiguous epochs...\n", instances);
							#endif
		
							divisor = (double)instances;
		
							/* For each time point in the duplicated classes: */
							for(jj = startIndex; jj < (classKRH[ii].length+startIndex); jj++, timePoint++) {
								/* For each voxel: */
								for(kk = 0; kk < nvox; kk++) {
									/* Average the duplicate time-points for the current voxel */
									for(mm = 0, sum = 0.0; mm < instances; mm++) {
										sum = sum + (double)sData[jj+(mm*classKRH[ii].length)][kk];
										}
									sData[timePoint][kk] = (short)((sum / divisor) + 0.5);
									}
								}
		
							/* Update final length of time-course */
							newLength = newLength - (classKRH[ii].length * (instances - 1));
		
							/* Jump over the indices of all instances of the current class */
							startIndex = startIndex + (instances *classKRH[ii].length);
		
							/* Go to the next class to process */
							ii += instances;
							}
						else { /* just copy the data points */
							#ifdef DEBUG_PLUGIN_REORDER
							printf("	Copying single epoch...\n");
							#endif
		
							for(jj = startIndex; jj < (classKRH[ii].length+startIndex); jj++, timePoint++) {
								for(kk = 0; kk < nvox; kk++) {
									sData[timePoint][kk] = sData[jj][kk];
									}
								}
		
							/* Jump to the index of the next class in the time-course */
							startIndex = startIndex + classKRH[ii].length;
		
							/* eat up another class */
							++ii;
							}
		
						/* Update progress meter with the percentage done */
						perc = (100 * timePoint) / mapLength;
						PLUTO_set_meter(plint, perc);
						}
			
					/* Set progress meter to 100% */
					PLUTO_set_meter(plint, 100);
			
					#ifdef DEBUG_PLUGIN_REORDER
					printf("[Reorder] Final time-length is %d...\n", newLength);
					#endif
					ii = EDIT_dset_items(
								new_dset,
									ADN_nvals, newLength,  /* # time points */
									ADN_ntt,   newLength,  /* # time points */
								ADN_none);
					if(0 != ii) {
						THD_delete_3dim_dataset(new_dset, False);
						FREEUP(sPtr);
						FREEUP(sData);
						FREE_WORKSPACE;
						return "******************************************\n"
								"!! Error while creating output dataset !!\n"
								"*****************************************";
						}
			
					/* Free unused subBRIKS */
					if(newLength < mapLength) {
						#ifdef DEBUG_PLUGIN_REORDER
						printf("[Reorder] Freeing %d unused subBRIKs...\n", mapLength-newLength);
						#endif
						for(ii = newLength; ii < mapLength; ii++) {
							FREEUP(sData[ii]);
							}
						sData = (short **)realloc((void *)sData, sizeof(short *)*newLength);
						if(NULL == sData) {
							THD_delete_3dim_dataset(new_dset, False);
							FREEUP(sPtr);
							FREE_WORKSPACE;
							return "*************************\n"
								   "!! Reallocation error !!\n"
								   "************************";
							}
						}
					mapLength = newLength;
					}
				}
			}
		
		/* Write results into new dataset */
		#ifdef DEBUG_PLUGIN_REORDER
		printf("[Reorder] Writing %d subBRIKS into new data set...\n", mapLength);
		#endif
		for(kk = 0; kk < mapLength; kk++) {
			EDIT_substitute_brick(new_dset, kk, MRI_short, sData[kk]);
			}
		break;

	case MRI_float:
		#ifdef DEBUG_PLUGIN_REORDER
		printf("\n[Reorder] Collating/float data...\n");
		#endif
		
		/* Reorder the time-course of each voxel */
		for(kk = 0; kk < mapLength; kk++) {
			if(NULL == memcpy((void *)fData[kk]
							, (void *)fPtr[map[kk]]
							, sizeof(float)*nvox)) {
				THD_delete_3dim_dataset(new_dset, False);
				FREEUP(fPtr);
				FREEUP(fData);
				FREE_WORKSPACE;
				return "***********************************\n"
						"!! Error performing memory copy !!\n"
						"**********************************";
				}
			perc = (100 * kk) / mapLength; /* display percentage done */
			PLUTO_set_meter(plint, perc); /* on the progress meter */
			}
		
		PLUTO_set_meter(plint, 100);      /* set progress meter to 100% */
		
		if(dupaction) { /* new_dset may change in length again */
			/* Verify that averaging is possible:
			     All duplicates must have the same length.
			*/
			error = 0; /* start with no error */
			for(ii = 0; ii < classCount; ii++) {
				for(kk = 1; kk < (classCount-1); kk++) {
					if(classKRH[ii].classKRH == classKRH[kk].classKRH) {
						if(classKRH[ii].length != classKRH[kk].length) {
							error = 1; /* lengths differ! */
							break;
							}
						dupsToAverage = True; /* there is at least one duplicate to average */
						}
					}
				if(error) {
					PLUTO_popup_message(plint
							,"*********************************************************\n" 
							 "* Duplicate stimulus regions in map file must have the  *\n"
							 "* same length for averaging. Returning collated regions.*\n"
							 "*********************************************************");
					break;
					}
				}
		
			if(!error) { /* average the crap */
				#ifdef DEBUG_PLUGIN_REORDER
				printf("[Reorder] All duplicate epochs have the same length...\n");
				#endif
			
				if(dupsToAverage) {
					#ifdef DEBUG_PLUGIN_REORDER
					printf("[Reorder] Averaging duplicate epochs...\n");
					printf("[Reorder] Initial time-length is %d...\n", mapLength);
					#endif
			
					newLength = mapLength; /* length may change after averaging, track new length */
		
					/* Perform averaging */
					/* For each class, do: */
					for(ii = 0, startIndex = 0, timePoint = 0; ii < classCount;) {
						#ifdef DEBUG_PLUGIN_REORDER
						printf("[Reorder] Processing instances of class %c...\n"
							, classKRH[ii].classKRH);
						#endif
			
						/* Get the number of duplicates of the current class */
						for(jj = ii + 1, instances = 1; jj < classCount; jj++, instances++) {
							if(classKRH[ii].classKRH != classKRH[jj].classKRH) {
								break;
								}
							}
			
						#ifdef DEBUG_PLUGIN_REORDER
						printf("	%d instances of class %c...\n", instances, classKRH[ii].classKRH);
						#endif
			
						if(instances > 1) {
							#ifdef DEBUG_PLUGIN_REORDER
							printf("	Averaging %d contiguous epochs...\n", instances);
							#endif
		
							divisor = (double)instances;
		
							/* For each time point in the duplicated classes: */
							for(jj = startIndex; jj < (classKRH[ii].length+startIndex); jj++, timePoint++) {
								/* For each voxel: */
								for(kk = 0; kk < nvox; kk++) {
									/* Average the duplicate time-points for the current voxel */
									for(mm = 0, sum = 0.0; mm < instances; mm++) {
										sum = sum + (double)fData[jj+(mm*classKRH[ii].length)][kk];
										}
									fData[timePoint][kk] = (float)((sum / divisor) + 0.5);
									}
								}
		
							/* Update final length of time-course */
							newLength = newLength - (classKRH[ii].length * (instances - 1));
		
							/* Jump over the indices of all instances of the current class */
							startIndex = startIndex + (instances *classKRH[ii].length);
		
							/* Go to the next class to process */
							ii += instances;
							}
						else { /* just copy the data points */
							#ifdef DEBUG_PLUGIN_REORDER
							printf("	Copying single epoch...\n");
							#endif
		
							for(jj = startIndex; jj < (classKRH[ii].length+startIndex); jj++, timePoint++) {
								for(kk = 0; kk < nvox; kk++) {
									fData[timePoint][kk] = fData[jj][kk];
									}
								}
		
							/* Jump to the index of the next class in the time-course */
							startIndex = startIndex + classKRH[ii].length;
		
							/* eat up another class */
							++ii;
							}
		
						/* Update progress meter with the percentage done */
						perc = (100 * timePoint) / mapLength;
						PLUTO_set_meter(plint, perc);
						}
			
					/* Set progress meter to 100% */
					PLUTO_set_meter(plint, 100);
			
					#ifdef DEBUG_PLUGIN_REORDER
					printf("[Reorder] Final time-length is %d...\n", newLength);
					#endif
					ii = EDIT_dset_items(
								new_dset,
									ADN_nvals, newLength,  /* # time points */
									ADN_ntt,   newLength,  /* # time points */
								ADN_none);
					if(0 != ii) {
						THD_delete_3dim_dataset(new_dset, False);
						FREEUP(fPtr);
						FREEUP(fData);
						FREE_WORKSPACE;
						return "******************************************\n"
								"!! Error while creating output dataset !!\n"
								"*****************************************";
						}
			
					/* Free unused subBRIKS */
					if(newLength < mapLength) {
						#ifdef DEBUG_PLUGIN_REORDER
						printf("[Reorder] Freeing %d unused subBRIKs...\n", mapLength-newLength);
						#endif
						for(ii = newLength; ii < mapLength; ii++) {
							FREEUP(fData[ii]);
							}
						fData = (float **)realloc((void *)fData, sizeof(float *)*newLength);
						if(NULL == fData) {
							THD_delete_3dim_dataset(new_dset, False);
							FREEUP(fPtr);
							FREE_WORKSPACE;
							return "*************************\n"
								   "!! Reallocation error !!\n"
								   "************************";
							}
						}
					mapLength = newLength;
					}
				}
			}
		
		/* Write results into new dataset */
		#ifdef DEBUG_PLUGIN_REORDER
		printf("[Reorder] Writing %d subBRIKS into new data set...\n", mapLength);
		#endif
		for(kk = 0; kk < mapLength; kk++) {
			EDIT_substitute_brick(new_dset, kk, MRI_float, fData[kk]);
			}
		break;

	} /* end of switch over input type */

DSET_unload(old_dset);

/*************** Cleanup and go home *****************/

PLUTO_add_dset(plint, new_dset, DSET_ACTION_MAKE_CURRENT);

#ifdef DEBUG_PLUGIN_REORDER
printf("[Reorder] Cleaning up workspace...\n");
#endif
FREE_WORKSPACE;

return NULL;    /* NULL string returned means all was OK */
}

#endif

