/* ----------------------------- MNI Header -----------------------------------
@NAME       : copy_data
@DESCRIPTION: File containing routines to copy data when reshaping.
@METHOD     : 
@GLOBALS    : 
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   : 
 * $Log: copy_data.c,v $
 * Revision 6.8  2008-01-17 02:33:02  rotor
 *  * removed all rcsids
 *  * removed a bunch of ^L's that somehow crept in
 *  * removed old (and outdated) BUGS file
 *
 * Revision 6.7  2008/01/13 09:38:54  stever
 * Avoid compiler warnings about functions and variables that are defined
 * but not used.  Remove some such functions and variables,
 * conditionalize some, and move static declarations out of header files
 * into C files.
 *
 * Revision 6.6  2008/01/12 19:08:15  stever
 * Add __attribute__ ((unused)) to all rcsid variables.
 *
 * Revision 6.5  2006/05/19 00:35:58  bert
 * Add config.h to several files that might need it
 *
 * Revision 6.4  2004/11/01 22:38:39  bert
 * Eliminate all references to minc_def.h
 *
 * Revision 6.3  2001/04/17 18:40:24  neelin
 * Modifications to work with NetCDF 3.x
 * In particular, changed NC_LONG to NC_INT (and corresponding longs to ints).
 * Changed NC_UNSPECIFIED to NC_NAT.
 * A few fixes to the configure script.
 *
 * Revision 6.2  1999/10/19 14:45:28  neelin
 * Fixed Log subsitutions for CVS
 *
 * Revision 6.1  1998/08/19 13:05:28  neelin
 * Added code to free minmax buffer.
 *
 * Revision 6.0  1997/09/12  13:24:12  neelin
 * Release of minc version 0.6
 *
 * Revision 5.0  1997/08/21  13:25:10  neelin
 * Release of minc version 0.5
 *
 * Revision 4.0  1997/05/07  20:01:44  neelin
 * Release of minc version 0.4
 *
 * Revision 3.1  1995/10/03  13:34:14  neelin
 * Fixed bug in truncate_input_vectors - was not handling out-of-range
 * start values properly.
 *
 * Revision 3.0  1995/05/15  19:32:36  neelin
 * Release of minc version 0.3
 *
 * Revision 1.5  1995/03/20  13:32:03  neelin
 * Fixed -normalize option.
 *
 * Revision 1.4  1994/12/02  09:08:56  neelin
 * Moved nd_loop to proglib.
 *
 * Revision 1.3  94/11/23  11:46:38  neelin
 * Handle image-min/max properly when using icv for normalization.
 * 
 * Revision 1.2  94/11/22  08:45:11  neelin
 * Fixed handling of normalization for number of image dimensions > 2.
 * Added appropriate default values of image-max and image-min.
 * 
 * Revision 1.1  94/11/02  16:21:09  neelin
 * Initial revision
 * 
@COPYRIGHT  :
              Copyright 1994 Peter Neelin, McConnell Brain Imaging Centre, 
              Montreal Neurological Institute, McGill University.
              Permission to use, copy, modify, and distribute this
              software and its documentation for any purpose and without
              fee is hereby granted, provided that the above copyright
              notice appear in all copies.  The author and McGill University
              make no representations about the suitability of this
              software for any purpose.  It is provided "as is" without
              express or implied warranty.
---------------------------------------------------------------------------- */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <limits.h>
#include <string.h>
#include <math.h>
#include <minc.h>
#include <nd_loop.h>
#include "mincreshape.h"

#define VIO_ROUND( x ) ((long) ((x) + ( ((x) >= 0) ? 0.5 : (-0.5) ) ))

static void get_num_minmax_values(Reshape_info *reshape_info,
                                  long *block_start, long *block_count,
                                  long *num_min_values, long *num_max_values);
static void handle_normalization(Reshape_info *reshape_info,
                                 long *block_start,
                                 long *block_count,
                                 double *minmax_buffer,
                                 double *fillvalue);
static void get_block_min_and_max(Reshape_info *reshape_info,
                                  long *block_start,
                                  long *block_count,
                                  double *minmax_buffer,
                                  double *minimum,
                                  double *maximum);
static void truncate_input_vectors(Reshape_info *reshape_info,
                                   long *input_start,
				   long *input_count);
static void translate_output_to_input(Reshape_info *reshape_info,
                                      long *output_start,
                                      long *output_count,
                                      long *input_start,
                                      long *input_count);
static void translate_input_to_output(Reshape_info *reshape_info,
                                      long *input_start,
                                      long *input_count,
                                      long *output_start,
                                      long *output_count);
static void copy_the_chunk(Reshape_info *reshape_info,
                           long chunk_start[],
                           long chunk_count[],
                           void *chunk_data,
                           double fillvalue);
static void convert_value_from_double(double dvalue,
                                      nc_type datatype, int is_signed,
                                      void *ptr);



/* ----------------------------- MNI Header -----------------------------------
@NAME       : copy_data
@INPUT      : reshape_info - information for reshaping volume
@OUTPUT     : (none)
@RETURNS    : (none)
@DESCRIPTION: Copies data from one input volume to another, reorganizing
              it according to the reshaping info.
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
void copy_data(Reshape_info *reshape_info)
{
   int idim, odim, out_ndims;
   long block_begin[MAX_VAR_DIMS], block_end[MAX_VAR_DIMS];
   long block_count[MAX_VAR_DIMS];
   long block_cur_start[MAX_VAR_DIMS], block_cur_count[MAX_VAR_DIMS];
   long chunk_begin[MAX_VAR_DIMS], chunk_end[MAX_VAR_DIMS];
   long chunk_count[MAX_VAR_DIMS];
   long chunk_cur_start[MAX_VAR_DIMS], chunk_cur_count[MAX_VAR_DIMS];
   long total_size;
   long num_min_values, num_max_values, num_values;
   double fillvalue, *minmax_buffer;
   void *chunk_data;

   /* Get number of dimensions */
   out_ndims = reshape_info->output_ndims;

   /* Set up variables for looping through blocks */
   for (odim=0; odim < out_ndims; odim++) {
      idim = reshape_info->map_out_to_in[odim];
      block_begin[odim] = 0;
      block_end[odim] = ABS(reshape_info->input_count[idim]);
      if (reshape_info->dim_used_in_block[odim])
         block_count[odim] = ABS(reshape_info->input_count[idim]);
      else
         block_count[odim] = 1;
   }

   /* Figure out size of chunks and allocate space */
   total_size = nctypelen(reshape_info->output_datatype);
   for (odim=0; odim < out_ndims; odim++) {
      total_size *= reshape_info->chunk_count[odim];
   }
   chunk_data = malloc(total_size);

   /* Get enough space for image-min and max values for a block */
   get_num_minmax_values(reshape_info, NULL, block_count, 
                         &num_min_values, &num_max_values);
   num_values = ((num_min_values > num_max_values) ?
                 num_min_values : num_max_values);
   if (num_values > 0)
      minmax_buffer = malloc(num_values * sizeof(double));
   else
      minmax_buffer = NULL;

   /* Print log message */
   if (reshape_info->verbose) {
      (void) fprintf(stderr, "Copying chunks:");
      (void) fflush(stderr);
   }

   /* Loop through blocks */

   nd_begin_looping(block_begin, block_cur_start, out_ndims);
   while (!nd_end_of_loop(block_cur_start, block_end, out_ndims)) {

      /* Set up count for current block */
      nd_update_current_count(block_cur_start, block_count, block_end,
                              block_cur_count, out_ndims);

      /* Set up chunk begin, end and count */
      for (odim=0; odim < out_ndims; odim++) {
         chunk_begin[odim] = block_cur_start[odim];
         chunk_end[odim] = chunk_begin[odim] + block_cur_count[odim];
         chunk_count[odim] = reshape_info->chunk_count[odim];
      }

      /* Set up icv for normalization, set output image-max/min and 
         calculate pixel fill value to use for current block */
      handle_normalization(reshape_info, block_cur_start, block_cur_count,
                           minmax_buffer, &fillvalue);

      /* Loop through chunks */

      nd_begin_looping(chunk_begin, chunk_cur_start, out_ndims);
      while (!nd_end_of_loop(chunk_cur_start, chunk_end, out_ndims)) {

         /* Set up count for current chunk */
         nd_update_current_count(chunk_cur_start, chunk_count, chunk_end,
                                 chunk_cur_count, out_ndims);

         /* Print log message for chunk */
         if (reshape_info->verbose) {
            (void) fprintf(stderr, ".");
            (void) fflush(stderr);
         }

         /* Copy the chunk */
         copy_the_chunk(reshape_info, 
                        chunk_cur_start, chunk_cur_count, chunk_data,
                        fillvalue);

         /* Increment chunk loop count */
         nd_increment_loop(chunk_cur_start, chunk_begin, chunk_count,
                           chunk_end, out_ndims);

      }

      /* Increment block loop count */
      nd_increment_loop(block_cur_start, block_begin, block_count,
                        block_end, out_ndims);

   }

   /* Free the chunk space */
   free(chunk_data);

   /* Free minmax buffer */
   if (minmax_buffer != NULL) {
      free(minmax_buffer);
   }

   /* Print ending log message */
   if (reshape_info->verbose) {
      (void) fprintf(stderr, "Done.\n");
      (void) fflush(stderr);
   }


}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : get_num_minmax_values
@INPUT      : reshape_info - information for reshaping volume
              block_start - start for a block (or NULL)
              block_count - count for a block
@OUTPUT     : num_min_values - number of image-min values to be read
              num_max_values - number of image-max values to be read
@RETURNS    : (nothing)
@DESCRIPTION: Gets the number of image-min and image-max values that
              correspond to a block. If block_start is NULL, then
              it is assumed to translate to an input start of [0,0,...]. 
              Note that only the true number of values for the specified 
              block is computed (specifying a hyperslab that goes beyond 
              file extents does not give a bigger number of values).
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void get_num_minmax_values(Reshape_info *reshape_info,
                                  long *block_start, long *block_count,
                                  long *num_min_values, long *num_max_values)
{
   int iloop, idim, ndims;
   int varid, inimgid;
   long size;
   long minmax_count[MAX_VAR_DIMS];
   long input_block_start[MAX_VAR_DIMS];
   long input_block_count[MAX_VAR_DIMS];
   long *num_values;

   /* Check for icv normalization */
   if (reshape_info->do_icv_normalization) {
      *num_min_values = 0;
      *num_max_values = 0;
      return;
   }

   /* Translate output block count to input count */
   translate_output_to_input(reshape_info, block_start, block_count, 
                             input_block_start, input_block_count);
   if (block_start != NULL) {
      truncate_input_vectors(reshape_info, input_block_start, 
                             input_block_count);
   }
   inimgid = ncvarid(reshape_info->inmincid, MIimage);

   /* Loop over image-min and image-max */

   for (iloop=0; iloop < 2; iloop++) {

      /* Get varid and pointer to return value */
      ncopts = 0;
      switch (iloop) {
      case 0: 
         varid = ncvarid(reshape_info->inmincid, MIimagemin); 
         num_values = num_min_values;        /* Pointer to long */
         break;
      case 1: 
         varid = ncvarid(reshape_info->inmincid, MIimagemax);
         num_values = num_max_values;        /* Pointer to long */
         break;
      }
      ncopts = NCOPTS_DEFAULT;

      /* Translate block count to min or max count and work out the
         total number of values. */
      size = 0;
      if (varid != MI_ERROR) {
         (void) ncvarinq(reshape_info->inmincid, varid, NULL, NULL, 
                         &ndims, NULL, NULL);
         (void) mitranslate_coords(reshape_info->inmincid,
                                   inimgid, input_block_count,
                                   varid, minmax_count);
         size = 1;
         for (idim=0; idim < ndims; idim++) {
            size *= minmax_count[idim];
         }
      }
      *num_values = size;
   }

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : handle_normalization
@INPUT      : reshape_info - information for reshaping volume
              block_start - start of current block
              block_count - count for current block
              minmax_buffer - buffer space for getting min and max
                 values
@OUTPUT     : fillvalue - pixel fill value to use for this block
@RETURNS    : (none)
@DESCRIPTION: Sets up icv for normalization to ensure that block is
              internally normalized. Output image-max and min are set.
              The appropriate pixel fill value is calculated for this
              min and max (applies to the whole block).
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void handle_normalization(Reshape_info *reshape_info,
                                 long *block_start,
                                 long *block_count,
                                 double *minmax_buffer,
                                 double *fillvalue)
{
   int iloop;
   int inmincid, inimgid, varid, icvid;
   long minmax_start[MAX_VAR_DIMS];
   double minimum, maximum, *extreme, valid_min, valid_max, denom;
   char *varname;

   /* Get input minc id, image id and icv id*/
   inmincid = reshape_info->inmincid;
   inimgid = ncvarid(inmincid, MIimage);
   icvid = reshape_info->icvid;

   /* Get input min and max for block */
   get_block_min_and_max(reshape_info, block_start, block_count,
                         minmax_buffer, &minimum, &maximum);

   /* Calculate the pixel fill value */
   *fillvalue = ((reshape_info->fillvalue == NOFILL) ? 0.0 :
                 reshape_info->fillvalue);

   /* If the fillvalue for uninitialized data lies outside the
    * pre-existing data range, we need to adjust the range
    * accordingly, which implies renormalization.
    */
   if (*fillvalue < minimum) {
     minimum = *fillvalue;
     reshape_info->do_block_normalization = TRUE;
   }
   if (*fillvalue > maximum) {
     maximum = *fillvalue;
     reshape_info->do_block_normalization = TRUE;
   }

   /* Modify the icv if necessary */
   if (reshape_info->do_block_normalization) {
      (void) miicv_detach(icvid);
      (void) miicv_setdbl(icvid, MI_ICV_IMAGE_MIN, minimum);
      (void) miicv_setdbl(icvid, MI_ICV_IMAGE_MAX, maximum);
      (void) miicv_setint(icvid, MI_ICV_USER_NORM, TRUE);
      (void) miicv_setint(icvid, MI_ICV_DO_NORM, TRUE);
      (void) miicv_attach(icvid, inmincid, inimgid);
   }

   /* Save the image max and min for the block */
   for (iloop=0; iloop < 2; iloop++) {

      /* Get varid and pointer to min or max value */
      switch (iloop) {
      case 0: 
         varname = MIimagemin;
         extreme = &minimum;
         break;
      case 1: 
         varname = MIimagemax;
         extreme = &maximum;
         break;
      }

      /* Save the value */
      ncopts = 0;
      varid = ncvarid(reshape_info->outmincid, varname);
      ncopts = NCOPTS_DEFAULT;
      if (varid != MI_ERROR) {
         (void) mitranslate_coords(reshape_info->outmincid, 
                                   reshape_info->outimgid, block_start,
                                   varid, minmax_start);
         (void) mivarput1(reshape_info->outmincid, varid, 
                          minmax_start, NC_DOUBLE, NULL, extreme);
      }
   }

   if ((reshape_info->output_datatype != NC_FLOAT) &&
       (reshape_info->output_datatype != NC_DOUBLE) &&
       (*fillvalue != FILL)) {
      (void) miicv_inqdbl(icvid, MI_ICV_VALID_MIN, &valid_min);
      (void) miicv_inqdbl(icvid, MI_ICV_VALID_MAX, &valid_max);
      denom = maximum - minimum;
      if (denom == 0.0) {
         *fillvalue = valid_min;
      }
      else {
         *fillvalue = (*fillvalue - minimum) * 
            (valid_max - valid_min) / denom + valid_min;
      }
   }


}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : get_block_min_and_max
@INPUT      : reshape_info - information for reshaping volume
              block_start - start of current block
              block_count - count for current block
              minmax_buffer - buffer space for getting min and max
                 values
@OUTPUT     : minimum - input minimum for block
              maximum - input maximum for block
@RETURNS    : (none)
@DESCRIPTION: Gets the min and max for the input file for a given output
              block.
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void get_block_min_and_max(Reshape_info *reshape_info,
                                  long *block_start,
                                  long *block_count,
                                  double *minmax_buffer,
                                  double *minimum,
                                  double *maximum)
{
   int iloop;
   long num_min_values, num_max_values, ivalue;
   int inmincid, inimgid, varid, icvid;
   long minmax_start[MAX_VAR_DIMS], minmax_count[MAX_VAR_DIMS];
   long input_block_start[MAX_VAR_DIMS], input_block_count[MAX_VAR_DIMS];
   double *extreme;
   long num_values;
   char *varname;
   double sign, default_extreme;

   /* Get input minc id, image id and icv id*/
   inmincid = reshape_info->inmincid;
   inimgid = ncvarid(inmincid, MIimage);
   icvid = reshape_info->icvid;

   /* Is the icv doing the normalization? */
   if (reshape_info->do_icv_normalization) {
      (void) miicv_inqdbl(icvid, MI_ICV_NORM_MIN, minimum);
      (void) miicv_inqdbl(icvid, MI_ICV_NORM_MAX, maximum);
      return;
   }

   /* Translate output block count to input count */
   translate_output_to_input(reshape_info, block_start, block_count,
                             input_block_start, input_block_count);
   truncate_input_vectors(reshape_info, input_block_start, input_block_count);

   /* Get number of min and max values */
   get_num_minmax_values(reshape_info, block_start, block_count, 
                         &num_min_values, &num_max_values);

   /* Loop over image-min and image-max getting block min and max */

   for (iloop=0; iloop < 2; iloop++) {

      /* Get varid and pointer to min or max value */
      switch (iloop) {
      case 0: 
         num_values = num_min_values;
         varname = MIimagemin;
         extreme = minimum;
         sign = -1.0;
         default_extreme = 0.0;
         break;
      case 1: 
         num_values = num_max_values;
         varname = MIimagemax;
         extreme = maximum;
         sign = +1.0;
         default_extreme = 1.0;
         break;
      }

      /* Get values from file */
      if (num_values > 0) {
         varid = ncvarid(inmincid, varname);
         (void) mitranslate_coords(inmincid,
                                   inimgid, input_block_start,
                                   varid, minmax_start);
         (void) mitranslate_coords(inmincid,
                                   inimgid, input_block_count,
                                   varid, minmax_count);
         (void) mivarget(reshape_info->inmincid, varid, 
                         minmax_start, minmax_count, 
                         NC_DOUBLE, NULL, minmax_buffer);
         *extreme = minmax_buffer[0];
         for (ivalue=1; ivalue < num_values; ivalue++) {
            if ((minmax_buffer[ivalue] * sign) > (*extreme * sign))
               *extreme = minmax_buffer[ivalue];
         }
      }
      else {
         *extreme = default_extreme;
      }
      if (reshape_info->need_fillvalue && 
          (reshape_info->fillvalue == NOFILL) && 
          (0.0 > (*extreme * sign)))
         *extreme = 0.0;
      
   }
}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : truncate_input_vectors
@INPUT      : reshape_info - information for reshaping volume
              input_start - start of input hyperslab (or NULL)
              input_count - count for input hyperslab
@OUTPUT     : input_start  - start of input hyperslab (or NULL)
              input_count  - count for input hyperslab
@RETURNS    : (nothing)
@DESCRIPTION: Input_start and input_count are truncated to specify a 
              legal hyperslab for the input file (if not specified, 
              input_start is assumed to be [0,0,...]).
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void truncate_input_vectors(Reshape_info *reshape_info,
                                   long *input_start,
                                   long *input_count)
{
   int idim;
   long first, last;        /* last is actually last_index+1 */

   /* Check for NULL vectors */
   if (input_count == NULL) return;

   /* Loop through input dimensions */
   for (idim=0; idim < reshape_info->input_ndims; idim++) {
      first = ( (input_start != NULL) ? input_start[idim] : 0 );
      last = first + input_count[idim];
      if (first < 0)
         first = 0;
      else if (first >= reshape_info->input_size[idim])
         first = reshape_info->input_size[idim] - 1;
      if (last < 0)
         last = 0;
      else if (last >= reshape_info->input_size[idim])
         last = reshape_info->input_size[idim];
      if (input_start != NULL)
         input_start[idim] = first;
      input_count[idim] = last - first;
   }

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : translate_output_to_input
@INPUT      : reshape_info - information for reshaping volume
              output_start - start of output hyperslab (or NULL)
              output_count - count for output hyperslab
@OUTPUT     : input_start  - start of input hyperslab (or NULL)
              input_count  - count for input hyperslab
@RETURNS    : (nothing)
@DESCRIPTION: Translates an output start and count to an input start and 
              count. If output_start or input_start are NULL, then only the
              count is translated.
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void translate_output_to_input(Reshape_info *reshape_info,
                                      long *output_start,
                                      long *output_count,
                                      long *input_start,
                                      long *input_count)
{
   int idim, odim;

   /* Check for NULL vectors */
   if ((input_count == NULL) || (output_count == NULL)) return;

   /* Loop through input dimensions */
   for (idim=0; idim < reshape_info->input_ndims; idim++) {
      odim = reshape_info->map_in_to_out[idim];
      input_count[idim] = ((odim >= 0) ? output_count[odim] : 1);
      if ((input_start != NULL) && (output_start != NULL)) {
         input_start[idim] = reshape_info->input_start[idim];
         if (odim >= 0) {
            if (reshape_info->input_count[idim] > 0)
               input_start[idim] += output_start[odim];
            else
               input_start[idim] -=
                  (output_start[odim] + output_count[odim] - 1);
         }
      }
   }

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : translate_input_to_output
@INPUT      : reshape_info - information for reshaping volume
              input_start  - start of output hyperslab (or NULL)
              input_count  - count for output hyperslab
@OUTPUT     : output_start - start of input hyperslab (or NULL)
              output_count - count for input hyperslab
@RETURNS    : (nothing)
@DESCRIPTION: Translates an input start and count to an output start and 
              count. If output_start or input_start are NULL, then only the
              count is translated.
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void translate_input_to_output(Reshape_info *reshape_info,
                                      long *input_start,
                                      long *input_count,
                                      long *output_start,
                                      long *output_count)
{
   int idim, odim;

   /* Check for NULL vectors */
   if ((input_count == NULL) || (output_count == NULL)) return;

   /* Loop through output dimensions */
   for (odim=0; odim < reshape_info->output_ndims; odim++) {
      idim = reshape_info->map_out_to_in[odim];
      output_count[odim] = input_count[idim];
      if ((input_start != NULL) && (output_start != NULL)) {
         if (reshape_info->input_count[idim] > 0)
            output_start[odim] = input_start[idim] - 
               reshape_info->input_start[idim];
         else
            output_start[odim] = reshape_info->input_start[idim] - 
               (input_start[idim] + input_count[idim] - 1);
      }
   }

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : copy_the_chunk
@INPUT      : reshape_info - information for reshaping volume
              chunk_start - start of current block
              chunk_count - count for current block
              chunk_data - pointer to enough space for chunk
              fillvalue - pixel value to zero volume, if necessary.
@OUTPUT     : (none)
@RETURNS    : (nothing)
@DESCRIPTION: Copies the chunk from the input file to the output file.
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void copy_the_chunk(Reshape_info *reshape_info,
                           long chunk_start[],
                           long chunk_count[],
                           void *chunk_data,
                           double fillvalue)
{
   int idim, odim, in_ndims, out_ndims;
   long input_start[MAX_VAR_DIMS], input_count[MAX_VAR_DIMS];
   long output_start[MAX_VAR_DIMS], output_count[MAX_VAR_DIMS];
   long input_imap[MAX_VAR_DIMS], output_imap[MAX_VAR_DIMS];
   void *output_origin;
   int datatype_size;
   long total_size, ipix, first, last;
   int zero_data, really_copy_the_data;
   union {
      char c; short s; long l; float f; double d;
   } value_buffer;

   /* Get number of dimensions */
   out_ndims = reshape_info->output_ndims;
   in_ndims = reshape_info->input_ndims;

   /* Get size of output datatype */
   datatype_size = nctypelen(reshape_info->output_datatype);

   /* Create input start and count */
   translate_output_to_input(reshape_info, chunk_start, chunk_count,
                             input_start, input_count);

   /* Find out if we need to zero the volume and if we need to copy any
      data */
   zero_data = FALSE;
   really_copy_the_data = TRUE;
   total_size = 1;
   for (idim=0; idim < in_ndims; idim++) {
      first = input_start[idim];
      last = input_start[idim] + input_count[idim] - 1;
      if ((first < 0) || (last >= reshape_info->input_size[idim]))
         zero_data = TRUE;
      if ((last < 0) || (first >= reshape_info->input_size[idim]))
         really_copy_the_data = FALSE;
      total_size *= input_count[idim];
   }

   /* Make sure that input vectors are legal and translate them back 
      to output */
   truncate_input_vectors(reshape_info, input_start, input_count);
   translate_input_to_output(reshape_info, input_start, input_count,
                             output_start, output_count);

   /* Write out zero data if needed */
   if (zero_data) {
      convert_value_from_double(fillvalue, 
                                reshape_info->output_datatype,
                                reshape_info->output_is_signed,
                                &value_buffer);
      for (ipix=0; ipix < total_size; ipix++) {
         (void) memcpy((char *)chunk_data + ipix*datatype_size,
                       &value_buffer, datatype_size);
      }
      (void) ncvarput(reshape_info->outmincid, reshape_info->outimgid,
                      chunk_start, chunk_count, chunk_data);
   }

   /* Set up hypothetical imap variable for input */
   for (idim=in_ndims-1; idim >= 0; idim--) {
      input_imap[idim] = ((idim == in_ndims-1) ? 
                          datatype_size :
                          input_imap[idim+1] * input_count[idim+1]);
   }

   /* Create output imap variable from input one (re-ordering dimensions and
      flipping). Also work out the chunk origin (point to byte for output
      [0,0,0...]). */
   output_origin = chunk_data;
   for (odim=0; odim < out_ndims; odim++) {
      idim = reshape_info->map_out_to_in[odim];
      if (reshape_info->input_count[idim] > 0) {
         output_imap[odim] = input_imap[idim];
      }
      else {
         output_imap[odim] = -input_imap[idim];
         output_origin = 
            (void *) ((char *)output_origin - 
                      (output_count[odim] - 1) * output_imap[odim]);
      }
   }

   /* Should we really copy the data? */
   if (really_copy_the_data) {

      /* Read in the data */
      (void) miicv_get(reshape_info->icvid, input_start, input_count, 
                       chunk_data);
   
      /* Write it out */
      (void) ncvarputg(reshape_info->outmincid, reshape_info->outimgid,
                       output_start, output_count, NULL, output_imap, 
                       output_origin);

   }

}

/* ----------------------------- MNI Header -----------------------------------
@NAME       : convert_value_from_double
@INPUT      : dvalue - double value to convert
              datatype - type of desired value
              is_signed - TRUE if desired value is signed
@OUTPUT     : ptr - pointer to converted value
@RETURNS    : (nothing)
@DESCRIPTION: Converts a value from double to some other value.
@METHOD     :
@GLOBALS    :
@CALLS      :
@CREATED    : October 25, 1994 (Peter Neelin)
@MODIFIED   :
---------------------------------------------------------------------------- */
static void convert_value_from_double(double dvalue, 
                                      nc_type datatype, int is_signed,
                                      void *ptr)
{
   switch (datatype) {
   case NC_BYTE :
      if (!is_signed) {
         dvalue = MAX(0, dvalue);
         dvalue = MIN(UCHAR_MAX, dvalue);
         *((unsigned char *) ptr) = VIO_ROUND(dvalue);
      }
      else {
         dvalue = MAX(SCHAR_MIN, dvalue);
         dvalue = MIN(SCHAR_MAX, dvalue);
         *((signed char *) ptr) = VIO_ROUND(dvalue);
      }
      break;
   case NC_SHORT :
      if (!is_signed) {
         dvalue = MAX(0, dvalue);
         dvalue = MIN(USHRT_MAX, dvalue);
         *((unsigned short *) ptr) = VIO_ROUND(dvalue);
      }
      else {
         dvalue = MAX(SHRT_MIN, dvalue);
         dvalue = MIN(SHRT_MAX, dvalue);
         *((signed short *) ptr) = VIO_ROUND(dvalue);
      }
      break;
   case NC_INT :
      if (!is_signed) {
         dvalue = MAX(0, dvalue);
         dvalue = MIN(UINT_MAX, dvalue);
         *((unsigned int *) ptr) = VIO_ROUND(dvalue);
      }
      else {
         dvalue = MAX(INT_MIN, dvalue);
         dvalue = MIN(INT_MAX, dvalue);
         *((signed int *) ptr) = VIO_ROUND(dvalue);
      }
      break;
   case NC_FLOAT :
      dvalue = MAX(-FLT_MAX,dvalue);
      *((float *) ptr) = MIN(FLT_MAX,dvalue);
      break;
   case NC_DOUBLE :
      *((double *) ptr) = dvalue;
      break;
   }
}
