/*--------------------------------------------------------------------
 *	$Id: gmt_cdf.c,v 1.3.4.1 2002/01/17 17:42:50 pwessel Exp $
 *
 *	Copyright (c) 1991-2002 by P. Wessel and W. H. F. Smith
 *	See COPYING file for copying and redistribution conditions.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; version 2 of the License.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	Contact info: gmt.soest.hawaii.edu
 *--------------------------------------------------------------------*/
/*
 *
 *	G M T _ C D F . C   R O U T I N E S
 *
 * Takes care of all grd input/output built on NCAR's netCDF routines (which is
 * an XDR implementation)
 * Most functions will exit with error message if an internal error is returned.
 * There functions are only called indirectly via the GMT_* grdio functions.
 *
 * Author:	Paul Wessel
 * Date:	9-SEP-1992
 * Modified:	13-JAN-2001
 * Version:	3.4.1
 *
 * Functions include:
 *
 *	GMT_cdf_read_grd_info :		Read header from file
 *	GMT_cdf_read_grd :		Read header and data set from file
 *	GMT_cdf_write_grd_info :	Update header in existing file
 *	GMT_cdf_write_grd :		Write header and data set to new file
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

#include "gmt.h"

char cdf_file[BUFSIZ];

int check_nc_status(int status);
int GMT_cdf_read_grd_info (char *file, struct GRD_HEADER *header)
{

	int cdfid, nm[2];
	size_t start[2], edge[2];
	double dummy[2];
	char text[GRD_COMMAND_LEN+GRD_REMARK_LEN];
	
	/* variable ids */
	int  x_range_id, y_range_id, z_range_id, inc_id, nm_id, z_id;

	if (!strcmp (file,"=")) {	/* Check if piping is attempted */
		fprintf (stderr, "%s: GMT Fatal Error: netcdf-based i/o does not support piping - exiting\n", GMT_program);
		exit (EXIT_FAILURE);
	}
	
	/* Open file and get info */
	
	strcpy (cdf_file, file);
	check_nc_status (nc_open (file, NC_NOWRITE, &cdfid));

	memset ((void *)text, 0, (size_t)(GRD_COMMAND_LEN+GRD_REMARK_LEN));

	/* Get variable ids */
	check_nc_status (nc_inq_varid (cdfid, "x_range", &x_range_id));
	check_nc_status (nc_inq_varid (cdfid, "y_range", &y_range_id));
	check_nc_status (nc_inq_varid (cdfid, "z_range", &z_range_id));
        check_nc_status (nc_inq_varid (cdfid, "spacing", &inc_id));
        check_nc_status (nc_inq_varid (cdfid, "dimension", &nm_id));
        check_nc_status (nc_inq_varid (cdfid, "z", &z_id));

	/* Get attributes */
	check_nc_status (nc_get_att_text  (cdfid, x_range_id, "units", header->x_units));
        check_nc_status (nc_get_att_text  (cdfid, y_range_id, "units", header->y_units));
	check_nc_status (nc_get_att_text  (cdfid, z_range_id, "units", header->z_units));
        check_nc_status (nc_get_att_double  (cdfid, z_id, "scale_factor", &header->z_scale_factor));
        check_nc_status (nc_get_att_double  (cdfid, z_id, "add_offset", &header->z_add_offset));
        check_nc_status (nc_get_att_int  (cdfid, z_id, "node_offset", &header->node_offset));
        check_nc_status (nc_get_att_text  (cdfid, NC_GLOBAL, "title", header->title));
        check_nc_status (nc_get_att_text  (cdfid, NC_GLOBAL, "source", text));

	strncpy (header->command, text, GRD_COMMAND_LEN);
	strncpy (header->remark, &text[GRD_COMMAND_LEN], GRD_REMARK_LEN);
	
	/* Get variables */
	
	start[0] = 0;
	edge[0] = 2;
	
	check_nc_status (nc_get_vara_double(cdfid, x_range_id, start, edge, dummy));
	header->x_min = dummy[0];
	header->x_max = dummy[1];
	check_nc_status (nc_get_vara_double(cdfid, y_range_id, start, edge, dummy));
	header->y_min = dummy[0];
	header->y_max = dummy[1];
	check_nc_status (nc_get_vara_double(cdfid, inc_id, start, edge, dummy));
	header->x_inc = dummy[0];
	header->y_inc = dummy[1];
	check_nc_status (nc_get_vara_int(cdfid, nm_id, start, edge, nm));
	header->nx = nm[0];
	header->ny = nm[1];
	check_nc_status (nc_get_vara_double(cdfid, z_range_id, start, edge, dummy));
	header->z_min = dummy[0];
	header->z_max = dummy[1];

	check_nc_status (nc_close (cdfid));
	
	return (0);
}

int GMT_cdf_write_grd_info (char *file, struct GRD_HEADER *header)
{

	int  cdfid, nm[2];
	size_t start[2], edge[2];
	double dummy[2];
	char text[GRD_COMMAND_LEN+GRD_REMARK_LEN];
	
	/* variable ids */
	int  x_range_id, y_range_id, z_range_id, inc_id, nm_id, z_id;

	if (!strcmp (file,"=")) {	/* Check if piping is attempted */
		fprintf (stderr, "%s: GMT Fatal Error: netcdf-based i/o does not support piping - exiting\n", GMT_program);
		exit (EXIT_FAILURE);
	}

	/* Open file and get info */
	
	strcpy (cdf_file, file);
	check_nc_status (nc_open (file, NC_WRITE, &cdfid));

	/* Get variable ids */
	check_nc_status (nc_inq_varid (cdfid, "x_range", &x_range_id));
        check_nc_status (nc_inq_varid (cdfid, "y_range", &y_range_id));
        check_nc_status (nc_inq_varid (cdfid, "z_range", &z_range_id));
        check_nc_status (nc_inq_varid (cdfid, "spacing", &inc_id));
        check_nc_status (nc_inq_varid (cdfid, "dimension", &nm_id));
        check_nc_status (nc_inq_varid (cdfid, "z", &z_id));

	/* rewrite attributes */
	
	check_nc_status (nc_redef (cdfid));

	memset ((void *)text, 0, (size_t)(GRD_COMMAND_LEN+GRD_REMARK_LEN));
	
	strcpy (text, header->command);
	strcpy (&text[GRD_COMMAND_LEN], header->remark);
	check_nc_status (nc_put_att_text (cdfid, x_range_id, "units", GRD_UNIT_LEN, header->x_units));
        check_nc_status (nc_put_att_text (cdfid, y_range_id, "units", GRD_UNIT_LEN, header->y_units));
        check_nc_status (nc_put_att_text (cdfid, z_range_id, "units", GRD_UNIT_LEN, header->z_units));
        check_nc_status (nc_put_att_double (cdfid, z_id, "scale_factor", NC_DOUBLE, 1, &header->z_scale_factor));
        check_nc_status (nc_put_att_double (cdfid, z_id, "add_offset", NC_DOUBLE, 1, &header->z_add_offset));
        check_nc_status (nc_put_att_int (cdfid, z_id, "node_offset", NC_LONG, 1, &header->node_offset));
        check_nc_status (nc_put_att_text (cdfid, NC_GLOBAL, "title", GRD_TITLE_LEN, header->title));
        check_nc_status (nc_put_att_text (cdfid, NC_GLOBAL, "source", (GRD_COMMAND_LEN+GRD_REMARK_LEN), text));
	
	/* leave define mode */
	check_nc_status (nc_enddef (cdfid));

	/* rewrite header information */
	
	start[0] = 0;
	edge[0] = 2;
	dummy[0] = header->x_min;
	dummy[1] = header->x_max;
	check_nc_status (nc_put_vara_double(cdfid, x_range_id, start, edge, dummy));
	dummy[0] = header->y_min;
	dummy[1] = header->y_max;
	check_nc_status (nc_put_vara_double(cdfid, y_range_id, start, edge, dummy));
	dummy[0] = header->x_inc;
	dummy[1] = header->y_inc;
	check_nc_status (nc_put_vara_double(cdfid, inc_id, start, edge, dummy));
	nm[0] = header->nx;
	nm[1] = header->ny;
	check_nc_status (nc_put_vara_int(cdfid, nm_id, start, edge, nm));
	dummy[0] = header->z_min;
	dummy[1] = header->z_max;
	check_nc_status (nc_put_vara_double(cdfid, z_range_id, start, edge, dummy));

	check_nc_status (nc_close (cdfid));
	
	return (0);
}

int GMT_cdf_read_grd (char *file, struct GRD_HEADER *header, float *grid, double w, double e, double s, double n, int *pad, BOOLEAN complex)
{	/* file:	File name	*/
	/* header:	grid structure header */
	/* grid:	array with final grid */
	/* w,e,s,n:	Sub-region to extract  [Use entire file if 0,0,0,0] */
	/* padding:	# of empty rows/columns to add on w, e, s, n of grid, respectively */
	/* complex:	TRUE if array is to hold real and imaginary parts (read in real only) */
	/*		Note: The file has only real values, we simply allow space in the array */
	/*		for imaginary parts when processed by grdfft etc. */

	/*
	 * Reads a subset of a grdfile and optionally pads the array with extra rows and columns
	 * header values for nx and ny are reset to reflect the dimensions of the logical array,
	 * not the physical size (i.e., the padding is not counted in nx and ny)
	 */
	 
	int  cdfid, z_id;
	size_t start[2], edge[2];
	int first_col, last_col, first_row, last_row;
	int i, j, ij, j2, width_in, width_out, height_in, i_0_out, kk, inc = 1;
	int *k;
	BOOLEAN check = !GMT_is_fnan (GMT_grd_in_nan_value);
		
	float *tmp;
		
	/* Open file and get info */
	
	if (!strcmp (file,"=")) {	/* Check if piping is attempted */
		fprintf (stderr, "%s: GMT Fatal Error: netcdf-based i/o does not support piping - exiting\n", GMT_program);
		exit (EXIT_FAILURE);
	}
	
	strcpy (cdf_file, file);
 	check_nc_status (nc_open (file, NC_NOWRITE, &cdfid));

	/* Get variable id */
	check_nc_status (nc_inq_varid (cdfid, "z", &z_id));

	k = GMT_grd_prep_io (header, &w, &e, &s, &n, &width_in, &height_in, &first_col, &last_col, &first_row, &last_row);
	
	width_out = width_in;		/* Width of output array */
	if (pad[0] > 0) width_out += pad[0];
	if (pad[1] > 0) width_out += pad[1];
	
	i_0_out = pad[0];		/* Edge offset in output */
	if (complex) {	/* Need twice as much space and load every 2nd cell */
		width_out *= 2;
		i_0_out *= 2;
		inc = 2;
	}
	
	tmp = (float *) GMT_memory (VNULL, (size_t)header->nx, sizeof (float), "GMT_cdf_read_grd");
	edge[0] = header->nx;
		
	header->z_min = DBL_MAX;	header->z_max = -DBL_MAX;
	for (j = first_row, j2 = 0; j <= last_row; j++, j2++) {
		start[0] = j * header->nx;
		/* Get one row */
		check_nc_status (nc_get_vara_float (cdfid, z_id, start, edge, tmp));
		ij = (j2 + pad[3]) * width_out + i_0_out;	/* Already has factor of 2 in it if complex */
		for (i = 0; i < width_in; i++) {
			kk = ij+i*inc;
			grid[kk] = tmp[k[i]];
			if (check && grid[kk] == GMT_grd_in_nan_value) grid[kk] = GMT_f_NaN;
			if (GMT_is_fnan (grid[kk])) continue;
			if ((double)grid[kk] < header->z_min) header->z_min = (double)grid[kk];
			if ((double)grid[kk] > header->z_max) header->z_max = (double)grid[kk];
		}
	}
	
	header->nx = width_in;
	header->ny = height_in;
	header->x_min = w;
	header->x_max = e;
	header->y_min = s;
	header->y_max = n;

	check_nc_status (nc_close (cdfid));
	
	GMT_free ((void *)tmp);
	GMT_free ((void *)k);

	return (0);
}

int GMT_cdf_write_grd (char *file, struct GRD_HEADER *header, float *grid, double w, double e, double s, double n, int *pad, BOOLEAN complex, nc_type nc_type)
{	/* file:	File name	*/
	/* header:	grid structure header */
	/* grid:	array with final grid */
	/* w,e,s,n:	Sub-region to write out  [Use entire file if 0,0,0,0] */
	/* padding:	# of empty rows/columns to add on w, e, s, n of grid, respectively */
	/* complex:	TRUE if array is to hold real and imaginary parts (read in real only) */
	/*		Note: The file has only real values, we simply allow space in the array */
	/*		for imaginary parts when processed by grdfft etc. */

	size_t start[2], edge[2];
	int cdfid, nm[2];
	int i, i2, inc = 1, *k;
	int j, ij, j2, width_in, width_out, height_out;
	int first_col, last_col, first_row, last_row;
	
	double dummy[2];
	
	char text[GRD_COMMAND_LEN+GRD_REMARK_LEN];
	
	float *tmp;
	BOOLEAN check = !GMT_is_fnan (GMT_grd_out_nan_value);
	
	/* dimension ids */
	int  side_dim, xysize_dim;

	/* variable ids */
	int  x_range_id, y_range_id, z_range_id, inc_id, nm_id, z_id;

	/* variable shapes */
	int dims[1];

	if (!strcmp (file,"=")) {	/* Check if piping is attempted */
		fprintf (stderr, "%s: GMT Fatal Error: netcdf-based i/o does not support piping - exiting\n", GMT_program);
		exit (EXIT_FAILURE);
	}
	
	/* Create file and enter define mode */
	
	check_nc_status (nc_create (file, NC_CLOBBER,&cdfid));

	k = GMT_grd_prep_io (header, &w, &e, &s, &n, &width_out, &height_out, &first_col, &last_col, &first_row, &last_row);

	width_in = width_out;		/* Physical width of input array */
	if (pad[0] > 0) width_in += pad[0];
	if (pad[1] > 0) width_in += pad[1];
	
	edge[0] = width_out;
	if (complex) inc = 2;

	header->x_min = w;
	header->x_max = e;
	header->y_min = s;
	header->y_max = n;
	
	/* define dimensions */
	check_nc_status (nc_def_dim(cdfid, "side", 2, &side_dim));
	check_nc_status (nc_def_dim(cdfid, "xysize", (int) (width_out * height_out), &xysize_dim));

	/* define variables */

	dims[0]		= side_dim;
	check_nc_status (nc_def_var (cdfid, "x_range", NC_DOUBLE, 1, dims, &x_range_id));
        check_nc_status (nc_def_var (cdfid, "y_range", NC_DOUBLE, 1, dims, &y_range_id));
        check_nc_status (nc_def_var (cdfid, "z_range", NC_DOUBLE, 1, dims, &z_range_id));
        check_nc_status (nc_def_var (cdfid, "spacing", NC_DOUBLE, 1, dims, &inc_id));
        check_nc_status (nc_def_var (cdfid, "dimension", NC_LONG, 1, dims, &nm_id));


	dims[0]		= xysize_dim;
	check_nc_status (nc_def_var (cdfid, "z", nc_type, 1, dims, &z_id));

	/* assign attributes */
	
	memset ((void *)text, 0, (size_t)(GRD_COMMAND_LEN+GRD_REMARK_LEN));
	
	strcpy (text, header->command);
	strcpy (&text[GRD_COMMAND_LEN], header->remark);
	check_nc_status (nc_put_att_text (cdfid, x_range_id, "units", GRD_UNIT_LEN, header->x_units));
        check_nc_status (nc_put_att_text (cdfid, y_range_id, "units", GRD_UNIT_LEN, header->y_units));
        check_nc_status (nc_put_att_text (cdfid, z_range_id, "units", GRD_UNIT_LEN, header->z_units));
        check_nc_status (nc_put_att_double (cdfid, z_id, "scale_factor", NC_DOUBLE, 1, &header->z_scale_factor));
        check_nc_status (nc_put_att_double (cdfid, z_id, "add_offset", NC_DOUBLE, 1, &header->z_add_offset));
        check_nc_status (nc_put_att_int (cdfid, z_id, "node_offset", NC_LONG, 1, &header->node_offset));
        check_nc_status (nc_put_att_text (cdfid, NC_GLOBAL, "title", GRD_TITLE_LEN, header->title));
        check_nc_status (nc_put_att_text (cdfid, NC_GLOBAL, "source", (GRD_COMMAND_LEN+GRD_REMARK_LEN), text));
	
	/* leave define mode */
	check_nc_status (nc_enddef (cdfid));

	/* Find zmin/zmax */
	
	header->z_min = DBL_MAX;	header->z_max = -DBL_MAX;
	for (j = first_row, j2 = pad[3]; j <= last_row; j++, j2++) {
		for (i = first_col, i2 = pad[0]; i <= last_col; i++, i2++) {
			ij = (j2 * width_in + i2) * inc;
			if (GMT_is_fnan (grid[ij])) {
				if (check) grid[ij] = (float)GMT_grd_out_nan_value;
				continue;
			}
			header->z_min = MIN (header->z_min, grid[ij]);
			header->z_max = MAX (header->z_max, grid[ij]);
		}
	}
	
	/* store header information and array */
	
	start[0] = 0;
	edge[0] = 2;
	dummy[0] = header->x_min;
	dummy[1] = header->x_max;
	check_nc_status (nc_put_vara_double(cdfid, x_range_id, start, edge, dummy));
	dummy[0] = header->y_min;
	dummy[1] = header->y_max;
	check_nc_status (nc_put_vara_double(cdfid, y_range_id, start, edge, dummy));
	dummy[0] = header->x_inc;
	dummy[1] = header->y_inc;
	check_nc_status (nc_put_vara_double(cdfid, inc_id, start, edge, dummy));
	nm[0] = width_out;
	nm[1] = height_out;
	check_nc_status (nc_put_vara_int(cdfid, nm_id, start, edge, nm));
	dummy[0] = header->z_min;
	dummy[1] = header->z_max;
	check_nc_status (nc_put_vara_double(cdfid, z_range_id, start, edge, dummy));

	tmp = (float *) GMT_memory (VNULL, (size_t)width_in, sizeof (float), "GMT_cdf_write_grd");
		
	edge[0] = width_out;
	i2 = first_col + pad[0];
	for (j = 0, j2 = first_row + pad[3]; j < height_out; j++, j2++) {
		ij = j2 * width_in + i2;
		start[0] = j * width_out;
		for (i = 0; i < width_out; i++) tmp[i] = grid[inc * (ij+k[i])];
		check_nc_status (nc_put_vara_float (cdfid, z_id, start, edge, tmp));
	}
	check_nc_status (nc_close (cdfid));

	GMT_free ((void *)k);
	GMT_free ((void *)tmp);
	
	return (0);
}

/* This function checks the return status of a netcdf function and takes
 * appropriate action if the status != NC_NOERR
 */

int check_nc_status (int status)/*
int status;*/
{
	if (status != NC_NOERR) {
		fprintf (stderr, "%s: %s [%s]\n", GMT_program, nc_strerror(status), cdf_file);
		exit (EXIT_FAILURE);
	}
	
	return 0;
}
