/*--------------------------------------------------------------------
 *    $Id: blockmode.c,v 1.16 2004/04/25 09:10:45 pwessel Exp $
 *
 *	Copyright (c) 1991-2004 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
 *--------------------------------------------------------------------*/

/*
   blockmode.c
   Takes lon, lat, data, [weight] on GMT_stdin or file and writes out one value
   per cell, where cellular region is bounded by West East South North and
   cell dimensions are delta_x, delta_y.
      
   Author: 	Walter H. F. Smith
   Date:	28 October, 1998
   Version:	1st version based on GMT3.1b blockmedian.c, but with changes
   		to struct DATA and to sort comparison routines to facilitate
   		mode coding.
   Modified:	3.3.5: 10-JUL-2000 PW: Added -L option
   Version:	3.4 01-MAR-2001 by PW, Replace -N with -F, and added -C
   Version:	4 01-MAR-2003 by PW
*/

#include "gmt.h"

struct DATA {
	double	a[4];	/* a[0] = x, a[1] = y, a[2] = z, a[3] = w  */
        int     i;
}       *data;


int	compare_x(const void *point_1, const void *point_2);
int	compare_y(const void *point_1, const void *point_2);
int	compare_index_z(const void *point_1, const void *point_2);
double	weighted_mode (struct DATA *d, double wsum, int n, int k);

main (int argc, char **argv)
{	
	
	BOOLEAN	error, weighted, offset, report, nofile = TRUE, done = FALSE, first = TRUE;
	BOOLEAN use_center = FALSE, go_quickly = FALSE, mode_xy = TRUE;
	/* Default value for go_quickly = FALSE for backward compatibility with 3.0  */
	
	FILE *fp = NULL;
	
	double	west, east, south, north, delta_x, delta_y, del_off;
	double	*in, out[4], idx, idy, i_n_in_cell, xy_off;
	
	int	i, j, n_x, n_y, ix, iy, index, first_in_cell, first_in_new_cell, fno, n_files = 0, n_args, n_req;
	int	n_lost, n_read, n_pitched, n_cells_filled, n_alloc, n_expected_fields, n_fields, n_out;
	int	n_in_cell;
	
	char	modifier, buffer[BUFSIZ], format[BUFSIZ];

	argc = GMT_begin (argc, argv);
	
	west = east = south = north = delta_x = delta_y = 0.0;
	del_off = 0.5;
	error = weighted = offset = report = FALSE;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {
              
				/* Common parameters */
                      
				case 'H':
				case 'R':
				case 'V':
				case ':':
				case 'b':
				case 'f':
				case '\0':
                                      error += GMT_get_common_args (argv[i], &west, &east, &south, &north);
                                      break;
                              
				/* Supplemental parameters */
                              
				case 'C':
					mode_xy = FALSE;
					use_center = TRUE;
					break;
				case 'I':
					GMT_getinc (&argv[i][2], &delta_x, &delta_y);
					break;
				case 'L':	/* Obsolete, but backward compatibility prevails [use -f instead] */
					GMT_io.in_col_type[0] = GMT_io.out_col_type[0] = GMT_IS_LON;
					GMT_io.in_col_type[1] = GMT_io.out_col_type[1] = GMT_IS_LAT;
					fprintf (stderr, "%s: Option -L is obsolete (but is processed correctly).  Please use -f instead\n", GMT_program);
					break;
				case 'N':	/* Backward compatible with 3.3.6 */
				case 'F':
					offset = TRUE;
					break;
				case 'Q':
					go_quickly = TRUE;
					break;
				case 'W':
					if ( (modifier = argv[i][2]) == 'i' || modifier == 'I') {
						weighted = TRUE;
						report = FALSE;
					}
					else if (modifier == 'O' || modifier == 'o') {
						report = TRUE;
						weighted = FALSE;
					}
					else
						weighted = report = TRUE;
					break;
					
				default:
					error = TRUE;
                                        GMT_default_error (argv[i][1]);
					break;
			}
		}
		else
			n_files++;
	}

	if (argc == 1 || GMT_quick) {
		fprintf (stderr, "blockmode %s - Block averaging by mode estimates\n\n", GMT_VERSION);
		fprintf (stderr, "usage: blockmode [infile(s)] -I<xinc[m]>[/<yinc>[m]] -R<west/east/south/north>\n");
		fprintf (stderr, "\t[-C] [-F] [-H[<nrec>]] [-Q] [-V] [-W[i][o] ] [-:] [-bi[s][<n>]]\n\t[-bo[s][<n>]] [-f[i|o]<colinfo>]\n\n");
		
		if (GMT_quick) exit (EXIT_FAILURE);
		
		fprintf (stderr, "\t-I sets Increment of the grid; enter xinc, optionally xinc/yinc.\n");
		fprintf (stderr, "\t   Default is yinc = xinc.  Append an m [or s] to xinc or yinc to indicate minutes [or seconds],\n");
		fprintf (stderr, "\t   e.g., -I10m/5m grids longitude every 10 minutes, latitude every 5 minutes.\n");
		GMT_explain_option ('R');
		fprintf (stderr, "\n\tOPTIONS:\n");
		fprintf (stderr, "\t-C Output center of block and mode z-value  [Default is mode location (but see -Q)]\n");
		fprintf (stderr, "\t-F Offsets registration so block edges are on gridlines (pixel reg.).  [Default: grid reg.]\n");
		GMT_explain_option ('H');
		fprintf (stderr, "\t-Q Quicker; get mode z and mean x,y.  [Default gets mode x, mode y, mode z.]\n");
		GMT_explain_option ('V');
		fprintf (stderr, "\t-W sets Weight options.  -WI reads Weighted Input (4 cols: x,y,z,w) but writes only (x,y,z) Output.\n");
		fprintf (stderr, "\t   -WO reads unWeighted Input (3 cols: x,y,z) but reports sum (x,y,z,w) Output.\n");
		fprintf (stderr, "\t   -W with no modifier has both weighted Input and Output; Default is no weights used.\n");
		GMT_explain_option (':');
		GMT_explain_option ('i');
		GMT_explain_option ('n');
		fprintf (stderr, "\t   Default is 3 (or 4 if -W is set).\n");
		GMT_explain_option ('o');
		GMT_explain_option ('n');
		GMT_explain_option ('f');
		GMT_explain_option ('.');
		exit (EXIT_FAILURE);
	}
	
	if (!mode_xy && go_quickly == 1) {
		fprintf (stderr, "%s: GMT WARNING:  -C overrides -Q\n", GMT_program);
		go_quickly = 0;
	}
	if (!project_info.region_supplied) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR:  Must specify -R option\n", GMT_program);
		error++;
	}
	if (delta_x <= 0.0 || delta_y <= 0.0) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR -I option.  Must specify positive increment(s)\n", GMT_program);
		error = TRUE;
	}
	if (GMT_io.binary[GMT_IN] && gmtdefs.io_header[GMT_IN]) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR.  Binary input data cannot have header -H\n", GMT_program);
		error++;
	}
	n_req = (weighted) ? 4 : 3;
	if (GMT_io.binary[GMT_IN] && GMT_io.ncol[GMT_IN] == 0) GMT_io.ncol[GMT_IN] = n_req;
	if (GMT_io.binary[GMT_IN] && n_req > GMT_io.ncol[GMT_IN]) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR.  binary input data must have at least %d columns\n", GMT_program, n_req);
		error++;
	}
	
	if (error) exit (EXIT_FAILURE);

	GMT_put_history (argc, argv);	/* Update .gmtcommands4 */

	if (GMT_io.binary[GMT_IN] && gmtdefs.verbose) {
		char *type[2] = {"double", "single"};
		fprintf (stderr, "%s: Expects %d-column %s-precision binary data\n", GMT_program, GMT_io.ncol[GMT_IN], type[GMT_io.single_precision[GMT_IN]]);
	}

#ifdef SET_IO_MODE
	GMT_setmode (GMT_OUT);
#endif

	idx = 1.0 / delta_x;
	idy = 1.0 / delta_y;
	n_x = irint ((east - west) * idx) + 1;
	n_y = irint ((north - south) * idy) + 1;
	
	if (offset) {
		n_x--;
		n_y--;
		del_off = 0.0;
	}
	xy_off = 0.5 - del_off;
	
	
	if (gmtdefs.verbose) {
		sprintf (format, "%%s: W: %s E: %s S: %s N: %s nx: %%d ny: %%d\n", gmtdefs.d_format, gmtdefs.d_format, gmtdefs.d_format, gmtdefs.d_format);
		fprintf (stderr, format, GMT_program, west, east, south, north, n_x, n_y);
	}

	n_read = n_pitched = 0;
        n_alloc = GMT_CHUNK;
        data = (struct DATA *) GMT_memory (VNULL, (size_t)n_alloc, sizeof(struct DATA), GMT_program);
	
	/* Read the input data  */
	
	n_expected_fields = (GMT_io.binary[GMT_IN]) ? GMT_io.ncol[GMT_IN] : 3 + weighted;

	if (n_files > 0)
		nofile = FALSE;
	else
		n_files = 1;
	n_args = (argc > 1) ? argc : 2;
	
	for (fno = 1; !done && fno < n_args; fno++) {	/* Loop over input files, if any */
		if (!nofile && argv[fno][0] == '-') continue;
		
		if (nofile) {	/* Just read standard input */
			fp = GMT_stdin;
			done = TRUE;
#ifdef SET_IO_MODE
			GMT_setmode (GMT_IN);
#endif
		}
		else if ((fp = GMT_fopen (argv[fno], GMT_io.r_mode)) == NULL) {
			fprintf (stderr, "%s: Cannot open file %s\n", GMT_program, argv[fno]);
			continue;
		}

		if (!nofile && gmtdefs.verbose) fprintf (stderr, "%s: Working on file %s\n", GMT_program, argv[fno]);
		
		if (gmtdefs.io_header[GMT_IN]) {
			for (i = 0; i < gmtdefs.n_header_recs; i++) {
				GMT_fgets (buffer, BUFSIZ, fp);
				buffer[strlen(buffer)-1] = 0;
				if (first && gmtdefs.io_header[GMT_OUT]) (report && !(weighted)) ? fprintf (GMT_stdout, "%s weights\n", buffer) : fprintf (GMT_stdout, "%s\n", buffer);
			}
			first = FALSE;
		}
				
		while ((n_fields = GMT_input (fp, &n_expected_fields, &in)) >= 0 && !(GMT_io.status & GMT_IO_EOF)) {	/* Not yet EOF */
	
			if (GMT_io.status & GMT_IO_MISMATCH) {
				fprintf (stderr, "%s: Mismatch between actual (%d) and expected (%d) fields near line %d\n", GMT_program, n_fields,  n_expected_fields, n_read);
				exit (EXIT_FAILURE);
			}

			if (GMT_is_dnan (in[2])) continue;	/* Skip when z = NaN */

			n_read++;
		
			ix = (int)floor(((in[0] - west) * idx) + del_off);
			if ( ix < 0 || ix >= n_x ) continue;
			iy = (int)floor(((in[1] - south) * idy) + del_off);
			if ( iy < 0 || iy >= n_y ) continue;
		
			index = iy * n_x + ix;
		
			data[n_pitched].i = index;
			if (mode_xy) {
				data[n_pitched].a[0] = in[0];
				data[n_pitched].a[1] = in[1];
			}
			data[n_pitched].a[2] = in[2];
			data[n_pitched].a[3] = (weighted) ? in[3] : 1.0;

			n_pitched++;
			if (n_pitched == n_alloc) {
				n_alloc += GMT_CHUNK;
				data = (struct DATA *) GMT_memory ((void *)data, (size_t)n_alloc, sizeof(struct DATA), GMT_program);
			}
		}
		if (fp != GMT_stdin) GMT_fclose(fp);
		
	}
	
	data = (struct DATA *) GMT_memory ((void *)data, (size_t)n_pitched, sizeof(struct DATA), GMT_program);
	n_lost = n_read - n_pitched;
	if (gmtdefs.verbose) fprintf(stderr,"%s: N read: %d N used: %d N outside_area: %d\n", GMT_program, 
		n_read,n_pitched,n_lost);


	/* Ready to go. */
	
	n_out = (report) ? 4 : 3;
	
	/* Sort on index and Z value */
	
	qsort((void *)data, (size_t)n_pitched, sizeof (struct DATA), compare_index_z);
	
	/* Find n_in_cell and write appropriate output  */

	first_in_cell = 0;
	n_cells_filled = 0;
	while (first_in_cell < n_pitched) {
		out[3] = data[first_in_cell].a[3];
		if (use_center) {
			j = data[first_in_cell].i / n_x;
			i = data[first_in_cell].i % n_x;
			out[0] = west  + (i + xy_off) * delta_x;
			out[1] = south + (j + xy_off) * delta_y;
		}
		else {
			out[0] = data[first_in_cell].a[0];
			out[1] = data[first_in_cell].a[1];
		}
		first_in_new_cell = first_in_cell + 1;
		while ( (first_in_new_cell < n_pitched) && (data[first_in_new_cell].i == data[first_in_cell].i) ) {
			out[3] += data[first_in_new_cell].a[3];	/* Summing up weights */
			if (mode_xy) {
				out[0] += data[first_in_new_cell].a[0];
				out[1] += data[first_in_new_cell].a[1];
			}
			first_in_new_cell++;
		}
		n_in_cell = first_in_new_cell - first_in_cell;
		if (n_in_cell > 2) {
			/* data are already sorted on z; get z mode  */
			out[2] = weighted_mode (&data[first_in_cell], out[3], n_in_cell, 2);
			if (go_quickly) {
				i_n_in_cell = 1.0 / n_in_cell;
				out[0] *= i_n_in_cell;
				out[1] *= i_n_in_cell;
			}
			else if (mode_xy) {
				qsort((void *)&data[first_in_cell], (size_t)n_in_cell, sizeof (struct DATA), compare_x);
				out[0] = weighted_mode (&data[first_in_cell], out[3], n_in_cell, 0);
				
				qsort((void *)&data[first_in_cell], (size_t)n_in_cell, sizeof (struct DATA), compare_y);
				out[1] = weighted_mode (&data[first_in_cell], out[3], n_in_cell, 1);
			}
		}
		else if (n_in_cell == 2) {
			if (data[first_in_cell].a[3] > data[first_in_cell+1].a[3]) {
				out[2] = data[first_in_cell].a[2];
				if (go_quickly) {
					out[0] *= 0.5;
					out[1] *= 0.5;
				}
				else if (mode_xy) {
					out[0] = data[first_in_cell].a[0];
					out[1] = data[first_in_cell].a[1];
				}
			}
			else if (data[first_in_cell].a[3] < data[first_in_cell+1].a[3]) {
				out[2] = data[first_in_cell+1].a[2];
				if (go_quickly) {
					out[0] *= 0.5;
					out[1] *= 0.5;
				}
				else if (mode_xy) {
					out[0] = data[first_in_cell+1].a[0];
					out[1] = data[first_in_cell+1].a[1];
				}
			}
			else {
				if (mode_xy) {	/* Need average location */
					out[0] *= 0.5;
					out[1] *= 0.5;
				}
				out[2] = 0.5 * (data[first_in_cell].a[2] + data[first_in_cell+1].a[2]);
			}
		}
		else
			out[2] = data[first_in_cell].a[2];
		
		GMT_output (GMT_stdout, n_out, out);
		
		n_cells_filled++;
		first_in_cell = first_in_new_cell;
	}

	if (gmtdefs.verbose) fprintf(stderr,"%s: N_cells_filled: %d\n", GMT_program, n_cells_filled);

	GMT_free ((void *)data);
	
        GMT_end (argc, argv);
}

double	weighted_mode (struct DATA *d, double wsum, int n, int k)
{
	/* Estimate mode by finding a maximum in the estimated
	   pdf of weighted data.  Estimate the pdf as the finite 
	   difference of the cumulative frequency distribution 
	   over points from i to j.  This has the form top/bottom,
	   where top is the sum of the weights from i to j, and
	   bottom is (data[j] - data[i]).  Strategy is to start
	   with i=0, j=n-1, and then move i or j toward middle
	   while j-i > n/2 and bottom > 0.  At end while, midpoint
	   of range from i to j is the mode estimate.  Choose 
	   to move either i or j depending on which one will
	   cause greatest increase in pdf estimate.  If a tie,
	   move both.
	   
	   Strictly, the pdf estimated this way would need to be
	   scaled by (1/wsum), but this is constant so we don't
	   use it here, as we are seeking a relative minimum.  
	   
	   I assumed n > 2 when I wrote this.  */
	   
	double	top, topj, topi, bottomj, bottomi, pj, pi;
	int	i, j, nh;

	i = 0;
	j = n - 1;
	nh = n / 2;
	top = wsum;
	
	while (j-i > nh) {
		topi = top - d[i].a[3];
		topj = top - d[j].a[3];
		bottomi = d[j].a[k] - d[i+1].a[k];
		bottomj = d[j-1].a[k] - d[i].a[k];

		if (bottomj == 0.0) {
			return (d[j-1].a[k]);
		}
		else if (bottomi == 0.0) {
			return (d[i+1].a[k]);
		}
		else {
			pi = topi/bottomi;
			pj = topj/bottomj;
			if (pi > pj) {
				i++;
				top = topi;
			}
			else if (pi < pj) {
				j--;
				top = topj;
			}
			else {
				top -= (d[i].a[3] + d[j].a[3]);
                                i++;
                                j--;
			}
		}
	}
	return(0.5*(d[j].a[k] + d[i].a[k]));
}

int	compare_index_z (const void *point_1, const void *point_2)
{
	int	index_1, index_2;
	double	data_1, data_2;
	struct DATA *p1, *p2;
	
	p1 = (struct DATA *)point_1;
	p2 = (struct DATA *)point_2;
	index_1 = p1->i;
	index_2 = p2->i;

	if (index_1 < index_2)
		return (-1);
	else if (index_1 > index_2)
		return (1);
	else {
		data_1 = p1->a[2];
		data_2 = p2->a[2];
		if (data_1 < data_2)
			return (-1);
		else if (data_1 > data_2)
			return (1);
		else
			return (0);
	}
}
int	compare_x(const void *point_1, const void *point_2)
{
	int	index_1, index_2;
	double	x_1, x_2;
	struct DATA *p1, *p2;
	
	p1 = (struct DATA *)point_1;
	p2 = (struct DATA *)point_2;
	index_1 = p1->i;
	index_2 = p2->i;

	if (index_1 < index_2)
		return (-1);
	else if (index_1 > index_2)
		return (1);
	else {
		x_1 = p1->a[0];
		x_2 = p2->a[0];
		if (x_1 < x_2)
			return (-1);
		else if (x_1 > x_2)
			return (1);
		else
			return (0);
	}
}

int	compare_y(const void *point_1, const void *point_2)
{
	int	index_1, index_2;
	double	y_1, y_2;
	struct DATA *p1, *p2;
	
	p1 = (struct DATA *)point_1;
	p2 = (struct DATA *)point_2;
	index_1 = p1->i;
	index_2 = p2->i;

	if (index_1 < index_2)
		return (-1);
	else if (index_1 > index_2)
		return (1);
	else {
		y_1 = p1->a[1];
		y_2 = p2->a[1];
		if (y_1 < y_2)
			return (-1);
		else if (y_1 > y_2)
			return (1);
		else
			return (0);
	}
}
