/*--------------------------------------------------------------------
 *	$Id: gmtconvert.c,v 1.38 2006/04/13 06:20:35 pwessel Exp $
 *
 *	Copyright (c) 1991-2006 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
 *--------------------------------------------------------------------*/
/*
 * gmtconvert.c
 * reads a GMT table and writes it out in a different format.
  
   Author: 	P. Wessel
   Date:	13-JUL-2000.
   version:	4
   Update:	21-APR-2004 PW: Now has built-in cut/paste capabilities

*/

#define GMT_WITH_NO_PS
#include "gmt.h"

#define GMTCONVERT_MAX_COL BUFSIZ

struct GMTCONVERT_CTRL {
	struct A {	/* -A */
		BOOLEAN active;
	} A;
	struct D {	/* -D[<template>] */
		BOOLEAN active;
		char *template;
	} D;
	struct E {	/* -E */
		BOOLEAN active;
	} E;
	struct F {	/* -F<cols> */
		BOOLEAN active;
		short int *col;
	} F;
	struct L {	/* -L */
		BOOLEAN active;
	} L;
	struct I {	/* -I */
		BOOLEAN active;
	} I;
	struct N {	/* -N */
		BOOLEAN active;
	} N;
	struct S {	/* -S\"search string\" */
		BOOLEAN active;
		char *pattern;
	} S;
};

int main (int argc, char **argv)
{
	int i, j, k, pos, start, stop, n_total_read = 0, n_files = 0, n_tot_cols = 0, n_hdr_delay = 0, fno;
	int n_out = 0, n_rev_alloc = 0, n_rev = 0, error = 0, n_seg = 0, n_in_seg = 0, n_nan, out_seg;
	BOOLEAN on, ascii_in_bin_out;

	double	*in, *out = VNULL, *R_out = (double *)NULL;

	char record_str[BUFSIZ], ptr[BUFSIZ], header[BUFSIZ], filename[BUFSIZ];
	
	FILE *fp_out;

	struct GMTCONVERT_CTRL *Ctrl;

	void *New_Gmtconvert_Ctrl (), Free_Gmtconvert_Ctrl (struct GMTCONVERT_CTRL *C);
	
	argc = GMT_begin (argc, argv);

	Ctrl = (struct GMTCONVERT_CTRL *)New_Gmtconvert_Ctrl ();	/* Allocate and initialize a new control structure */
	
	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			switch (argv[i][1]) {


				/* Common parameters */

				case 'H':
				case 'V':
				case 'b':
				case 'f':
				case ':':
				case '\0':
					error += GMT_parse_common_options (argv[i], 0, 0, 0, 0);
					break;

				/* Supplemental parameters */

				case 'A':
					Ctrl->A.active = TRUE;
					break;
				case 'D':               /* Write each segment to a separate output file */
					if (argv[i][2]) Ctrl->D.template = strdup (&argv[i][2]);
					Ctrl->D.active = TRUE;
					break;
				case 'E':
					Ctrl->E.active = TRUE;
					break;
				case 'F':
					pos = 0;
					Ctrl->F.active = TRUE;
					while ((GMT_strtok (&argv[i][2], ",", &pos, ptr))) {	/* Proces all tokens */
						if (strchr (ptr, '-'))	/* Range of columns given. e.g., 7-9 */
							sscanf (ptr, "%d-%d", &start, &stop);
						else if (isdigit ((int)ptr[0]))	/* Just a single column, e.g., 13 */
							start = stop = atoi (ptr);
						else {				/* Badness! */
							fprintf (stderr, "%s: GMT SYNTAX ERROR.  -F: Give cols or col-ranges separated by commas!\n", GMT_program);
							error++;
						}

						for (j = start; j <= stop; j++) Ctrl->F.col[n_out++] = j;
					}
					break;
				case 'L':
					Ctrl->L.active = TRUE;
					break;
				case 'I':
					Ctrl->I.active = TRUE;
					break;
				case 'M':
					GMT_multisegment (&argv[i][2]);
					break;
				case 'N':
					Ctrl->N.active = TRUE;
					break;
				case 'S':
					Ctrl->S.pattern = strdup (&argv[i][2]);
					Ctrl->S.active = TRUE;
					break;
				default:
					error = TRUE;
					GMT_default_error (argv[i][1]);
					break;
			}
		}
		else
			n_files++;
	}

	if (argc == 1 || GMT_give_synopsis_and_exit) {
		fprintf (stderr, "gmtconvert %s - Convert format, Paste, and/or Extract columns from table data\n\n", GMT_VERSION);
		fprintf(stderr,"usage:	gmtconvert [files] [-A] [-D[<template>]] [-E] [-F<cols>] [%s] [-I]\n", GMT_H_OPT);
		fprintf(stderr,"\t[%s] [-N] [-S\"search string\"] [-V] [%s]\n\t[%s] [%s]\n\n", GMT_M_OPT, GMT_t_OPT, GMT_b_OPT, GMT_f_OPT);

		if (GMT_give_synopsis_and_exit) exit (EXIT_FAILURE);

		fprintf (stderr, "\n\tOPTIONS:\n");
		fprintf (stderr, "\t-A Input files should be pAsted, not concatenated [Default is concatenated]\n");
		fprintf (stderr, "\t-D writes individual segments to separate files [Default writes one multisegment file to stdout].\n");
		fprintf (stderr, "\t   Append file name template which MUST contain a C-format specified for an integer (e.g., %%d).\n");
		fprintf (stderr, "\t   [Default uses gmtconvert_segment_%%d.d]\n");
		fprintf (stderr, "\t-E Extract first and last point per segment only [Default outputs all points]\n");
		fprintf(stderr,"\t-F Give comma-separated list of desired columns or ranges (0 is first column) [Default is all]\n");
		GMT_explain_option ('H');
		fprintf (stderr,"\t-I Invert order of rows, i.e., outputting in reverse order\n");
		fprintf (stderr, "\t-L Output all multisegment headers only, no data records (requires -M and ASCII data)\n");
		GMT_explain_option ('M');
		fprintf (stderr, "\t-N Skip output records where all fields == NaN [Default writes all records]\n");
		fprintf (stderr, "\t-S Only output segments whose segment headers contains the pattern \"string\".\n");
		fprintf (stderr, "\t   (Requires -M and ASCII data) [All segments]\n");
		GMT_explain_option ('V');
		GMT_explain_option ('i');
		GMT_explain_option ('n');
		GMT_explain_option ('o');
		GMT_explain_option ('f');
		exit (EXIT_FAILURE);
	}

	if (GMT_io.binary[GMT_IN] && GMT_io.ncol[GMT_IN] == 0) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR.  Must specify number of columns in binary input data (-bi)\n", GMT_program);
		error++;
	}
	if (GMT_io.binary[GMT_IN] && (Ctrl->L.active || Ctrl->S.active)) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR.  -L or -S requires ASCII input data\n", GMT_program);
		error++;
	}
	if (!GMT_io.multi_segments[GMT_IN] && (Ctrl->L.active || Ctrl->S.active)) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR.  -L or -S requires multisegment files\n", GMT_program);
		error++;
	}
	if (Ctrl->D.active && Ctrl->D.template && !strstr (Ctrl->D.template, "%")) {
		fprintf (stderr, "%s: GMT SYNTAX ERROR:  output template must contain %%d\n", GMT_program);
		error++;
	}
	if (error) exit (EXIT_FAILURE);

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

	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
	on = (Ctrl->L.active) ? FALSE : TRUE;
	ascii_in_bin_out = (!GMT_io.binary[GMT_IN] && GMT_io.binary[GMT_OUT]);
	fp_out = (Ctrl->D.active) ? NULL : GMT_stdout;
	if (Ctrl->D.active && !Ctrl->D.template) Ctrl->D.template = strdup ("gmtconvert_segment_%d.d");
	out_seg = -1;

	if (Ctrl->A.active) {	/* Do paste and probably cut as well */
		int *n_expected_fields, *n_fields, n_alloc = GMT_SMALL_CHUNK, all_set = 0;
		double *val;
		FILE **fp = NULL;

		fp = (FILE **) GMT_memory (VNULL, n_files, sizeof (FILE *), GMT_program);
		n_expected_fields = (int *) GMT_memory (VNULL, n_files, sizeof (int), GMT_program);
		n_fields = (int *) GMT_memory (VNULL, n_files, sizeof (int), GMT_program);

		for (fno = 1, k = 0; fno < argc; fno++) {	/* Loop over input files, if any */
			if (argv[fno][0] == '-') continue;
			if ((fp[k] = GMT_fopen (argv[fno], GMT_io.r_mode)) == NULL) {
				fprintf (stderr, "%s: Cannot open file %s\n", GMT_program, argv[fno]);
				continue;
			}
			k++;
		}

		if (GMT_io.binary[GMT_IN]) {	/* If binary input then we know what to store */
			n_tot_cols = GMT_io.ncol[GMT_IN] * n_files;
			val = (double *) GMT_memory (VNULL, sizeof (double), (size_t)n_tot_cols, GMT_program);
			all_set = 2;
			if (n_out == 0) {	/* No cut action, just paste */
				n_out = n_tot_cols;
				for (k = 0; k < n_tot_cols; k++) Ctrl->F.col[k] = k;
			}
			out = (double *) GMT_memory (VNULL, sizeof (double), (size_t)n_out, GMT_program);
		}
		else {			/* Must find out by examining the input ascii data */
			val = (double *) GMT_memory (VNULL, sizeof (double), (size_t)n_alloc, GMT_program);
			all_set = 0;
		}

		if (!GMT_io.binary[GMT_IN] && GMT_io.io_header[GMT_IN]) {	/* Only ascii data can have header records */
			for (i = 0; i < GMT_io.n_header_recs; i++) {
				for (k = 0; k < n_files; k++) {
					GMT_fgets (record_str, BUFSIZ, fp[k]);
					if (k < (n_files-1)) record_str[strlen(record_str)-1] = '\0';
					if (k == 0)
						strcpy (header, record_str);
					else
						strcat (header, &record_str[1]);
				}
				n_total_read ++;
				if (!GMT_io.binary[GMT_OUT] && GMT_io.io_header[GMT_OUT]) fprintf (GMT_stdout, "%s", header);
			}
		}

		for (k = j = 0; k < n_files; k++) {	/* Get first record from each file */
			n_expected_fields[k] = (GMT_io.ncol[GMT_IN]) ? GMT_io.ncol[GMT_IN] : BUFSIZ; /* BUFSIZ -> Ascii table, must first find the number of fields */
			n_fields[k] = GMT_input (fp[k], &n_expected_fields[k], &in);
			if (! GMT_io.status & GMT_IO_SEGMENT_HEADER) {	/* Good, a data record, update number of cols etc */
				all_set = 1;
				n_tot_cols += n_expected_fields[k];
				if (n_tot_cols > n_alloc) {
					n_alloc += GMT_SMALL_CHUNK;
					val = (double *) GMT_memory ((char *)val, (size_t)n_alloc, sizeof (double), GMT_program);
				}
				for (i = 0; i < n_fields[k]; i++) val[j++] = in[i];	/* Copy the input values */
			}
			else {	/* No, just a multisegment header */
				if (k < (n_files-1)) GMT_io.segment_header[strlen(GMT_io.segment_header)-1] = '\0';
				if (k == 0)
					strcpy (header, GMT_io.segment_header);
				else
					strcat (header, &GMT_io.segment_header[1]);
			}
		}
		n_total_read ++;
		if (all_set == 1) {	/* Means we know how many total colums and can now allocate memory */
			all_set = 2;
			if (n_out == 0) {	/* No cut action, just paste and return all columns */
				n_out = n_tot_cols;
				for (k = 0; k < n_tot_cols; k++) Ctrl->F.col[k] = k;
			}
			out = (double *) GMT_memory (VNULL, sizeof (double), (size_t)n_out, GMT_program);
			if (n_tot_cols < n_out) {
				fprintf (stderr, "%s: Columns requested (%d) exceed actual (%d) - aborting\n", GMT_program, n_out, n_tot_cols);
				exit (EXIT_FAILURE);
			}
		}
		while (! (GMT_io.status & GMT_IO_EOF)) {	/* Not yet EOF */

			while (GMT_io.status & GMT_IO_SEGMENT_HEADER && !(GMT_io.status & GMT_IO_EOF)) {	/* As long as we have read a segment header */
				strcpy (GMT_io.segment_header, header);
				if (ascii_in_bin_out && n_out == 0)
					n_hdr_delay++;
				else if (Ctrl->D.active) {
					out_seg++;
					sprintf (filename, Ctrl->D.template, out_seg);
					if (fp_out != NULL) GMT_fclose (fp_out);
					if ((fp_out = GMT_fopen (filename, GMT_io.w_mode)) == NULL ) {
						fprintf (stderr, "%s: Error creating file %s\n", GMT_program, filename);
						exit (EXIT_FAILURE);
					}
				}
				else
					GMT_write_segmentheader (fp_out, n_out);
				for (k = 0; k < n_files; k++) {	/* Get next record */
					n_fields[k] = GMT_input (fp[k],  &n_expected_fields[k], &in);
					if (! GMT_io.status & GMT_IO_SEGMENT_HEADER) {	/* Data record */
						if (! all_set) {	/* Very first data record */
							all_set = 1;
							n_tot_cols += n_expected_fields[k];
							if (n_tot_cols > n_alloc) {
								n_alloc += GMT_SMALL_CHUNK;
								val = (double *) GMT_memory ((char *)val, (size_t)n_alloc, sizeof (double), GMT_program);
							}
						}
						for (i = 0; i < n_fields[k]; i++) val[j++] = in[i];	/* Copy the input values */
					}
					else {	/* Segment header again */
						if (k < (n_files-1)) GMT_io.segment_header[strlen(GMT_io.segment_header)-1] = '\0';
						if (k == 0)
							strcpy (header, GMT_io.segment_header);
						else
							strcat (header, &GMT_io.segment_header[1]);
					}
				}
				n_total_read ++;
			}

			if (all_set == 1) {	/* Well, we still havent allocated space etc */
				all_set = 2;
				if (n_out == 0) {	/* No cut action, just paste */
					n_out = n_tot_cols;
					for (k = 0; k < n_tot_cols; k++) Ctrl->F.col[k] = k;
					if (n_hdr_delay) {	/* Delayed issue of segment header record(s) */
						for (k = 0; k < n_hdr_delay; k++) GMT_write_segmentheader (GMT_stdout, n_out);
						n_hdr_delay = 0;
					}
				}
				out = (double *) GMT_memory (VNULL, (size_t)n_out, sizeof (double), GMT_program);
				if (n_tot_cols < n_out) {
					fprintf (stderr, "%s: Columns requested (%d) exceed actual (%d) - aborting\n", GMT_program, n_out, n_tot_cols);
					exit (EXIT_FAILURE);
				}
			}
			while (! (GMT_io.status & (GMT_IO_SEGMENT_HEADER | GMT_IO_EOF))) {	/* Keep going until FALSE or = 2 segment header */
				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[0], n_expected_fields[0], n_total_read);
					exit (EXIT_FAILURE);
				}

				/* Now shuffle and output the chosen columns */

				for (i = n_nan = 0; i < n_out; i++) {
					out[i] = val[Ctrl->F.col[i]];
					if (GMT_is_dnan (out[i])) n_nan++;
				}

				if (Ctrl->I.active) {	/* Collect all rows an write in reverse order */
					if (n_rev == n_rev_alloc) {
						n_rev_alloc += (n_out * GMT_CHUNK);
						R_out = (double *) GMT_memory ((void *)R_out, n_rev_alloc, sizeof (double), GMT_program);
					}
					if (!Ctrl->N.active || n_nan < n_out) {
						for (i = 0; i < n_out; i++) R_out[n_rev++] = out[i];
					}
				}
				else if (!Ctrl->N.active || n_nan < n_out)
					GMT_output (fp_out, n_out, out);

				for (k = j = 0; k < n_files; k++) {	/* Get next record */
					n_fields[k] = GMT_input (fp[k], &n_expected_fields[k], &in);
					if (GMT_io.status & GMT_IO_SEGMENT_HEADER) {	/* ANother segment header */
						if (k < (n_files-1)) GMT_io.segment_header[strlen(GMT_io.segment_header)-1] = '\0';
						if (k == 0)
							strcpy (header, GMT_io.segment_header);
						else
							strcat (header, &GMT_io.segment_header[1]);
					}
					else 
						for (i = 0; i < n_fields[k]; i++) val[j++] = in[i];	/* Copy the input values */
				}
				n_total_read ++;
			}
		}

		for (k = 0; k < n_files; k++) GMT_fclose(fp[k]);

		if (gmtdefs.verbose) fprintf(stderr, "gmtconvert: %d records passed (input cols = %d; output cols = %d)\n", n_total_read, n_tot_cols, n_out);
	}
	else {	/* Just cutting out columns */
		int n_expected_fields, n_fields, n_args;
		BOOLEAN nofile = TRUE, done = FALSE;
		FILE *fp = NULL;

		if (GMT_io.binary[GMT_IN] && n_out == 0) {
			n_out = GMT_io.ncol[GMT_IN];
			for (k = 0; k < n_out; k++) Ctrl->F.col[k] = k;
			out = (double *) GMT_memory (VNULL, (size_t)n_out, sizeof (double), GMT_program);
		}
		else if (n_out)	/* Set by -F */
			out = (double *) GMT_memory (VNULL, (size_t)n_out, sizeof (double), GMT_program);

		if (n_files > 0)
			nofile = FALSE;
		else
			n_files = 1;

		n_args = (argc > 1) ? argc : 2;	/* So it will go through the loop at least once (when we pipe) */

		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 (!GMT_io.binary[GMT_IN] && GMT_io.io_header[GMT_IN]) {
				for (i = 0; i < GMT_io.n_header_recs; i++) {
					GMT_fgets (record_str, BUFSIZ, fp);
					n_total_read ++;
					if (!GMT_io.binary[GMT_OUT] && GMT_io.io_header[GMT_OUT]) fputs (record_str, GMT_stdout);
				}
			}

			n_expected_fields = (GMT_io.ncol[GMT_IN]) ? GMT_io.ncol[GMT_IN] : BUFSIZ; /* BUFSIZ -> Ascii table, must first find the number of fields */
			n_fields = GMT_input (fp, &n_expected_fields, &in);

			n_seg = n_in_seg = 0;
			while (! (GMT_io.status & GMT_IO_EOF)) {	/* Not yet EOF */

				while (GMT_io.status & GMT_IO_SEGMENT_HEADER && !(GMT_io.status & GMT_IO_EOF)) {
					if (Ctrl->E.active && n_in_seg > 0) fprintf (GMT_stdout, "%s", record_str);
					if (Ctrl->S.active) on = (strstr (GMT_io.segment_header, Ctrl->S.pattern) != NULL);	/* If match then we turn output ON */
					if (ascii_in_bin_out && n_out == 0)
						n_hdr_delay++;	/* Unable to write header yet since we dont know the field count... */
					else if ((Ctrl->L.active || on) && Ctrl->D.active) {
						out_seg++;
						sprintf (filename, Ctrl->D.template, out_seg);
						if (fp_out != NULL) GMT_fclose (fp_out);
						if ((fp_out = GMT_fopen (filename, GMT_io.w_mode)) == NULL ) {
							fprintf (stderr, "%s: Error creating file %s\n", GMT_program, filename);
							exit (EXIT_FAILURE);
						}
					}
					else if (Ctrl->L.active || on)
						GMT_write_segmentheader (GMT_stdout, n_out);
					n_seg++;
					n_in_seg = 0;
					n_fields = GMT_input (fp,  &n_expected_fields, &in);
					n_total_read ++;
				}

				while (! (GMT_io.status & (GMT_IO_SEGMENT_HEADER | GMT_IO_EOF))) {	/* Keep going until FALSE or = 2 segment header */
					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_total_read);
						exit (EXIT_FAILURE);
					}
					if (n_out == 0) {
						n_out = n_fields;
						if (n_hdr_delay) {	/* Delayed issue of segment header record(s) */
							for (k = 0; k < n_hdr_delay; k++) GMT_write_segmentheader (GMT_stdout, n_out);
							n_hdr_delay = 0;
						}
						for (k = 0; k < n_out; k++) Ctrl->F.col[k] = k;
						out = (double *) GMT_memory (VNULL, (size_t)n_out, sizeof (double), GMT_program);
					}
					if (n_expected_fields < n_out) {
						fprintf (stderr, "%s: Columns requested (%d) exceed actual (%d) - aborting\n", GMT_program, n_expected_fields, n_out);
						exit (EXIT_FAILURE);
					}

					/* Now output the chosen columns */

					for (i = n_nan = 0; i < n_out; i++) {
						out[i] = in[Ctrl->F.col[i]];
						if (GMT_is_dnan (out[i])) n_nan++;
					}

					if (Ctrl->I.active) {	/* Collect all rows an write in reverse order */
						if (n_rev == n_rev_alloc) {
							n_rev_alloc += (n_out * GMT_CHUNK);
							R_out = (double *) GMT_memory ((void *)R_out, n_rev_alloc, sizeof (double), GMT_program);
						}
						if (!Ctrl->N.active || n_nan < n_out) {	/* OK to use this record */
							for (i = 0; i < n_out; i++) R_out[n_rev++] = out[i];
						}
					}
					else if (Ctrl->E.active) {
						if (n_in_seg == 0) fprintf (GMT_stdout, "%s", GMT_io.current_record);
						strcpy (record_str, GMT_io.current_record);
						n_in_seg++;
					}
					else if (on)
						if (!Ctrl->N.active || n_nan < n_out) GMT_output (fp_out, n_out, out);

					n_total_read ++;

					n_fields = GMT_input (fp, &n_expected_fields, &in);
				}
			}
			if (Ctrl->E.active && n_in_seg > 1) fprintf (GMT_stdout, "%s", record_str);

			if (fp != GMT_stdin) GMT_fclose(fp);
		}
		if (gmtdefs.verbose) fprintf(stderr, "%s: %d records passed (input cols = %d; output cols = %d)\n", GMT_program, n_total_read, n_expected_fields, n_out);
		if (gmtdefs.verbose && Ctrl->S.active) fprintf (stderr, "%s: Extracted %d segments\n", argv[0], n_seg);
	}

	if (Ctrl->I.active) {	/* Time to write out rows in reverse order */
		int n_rows;
		n_rows = n_rev / n_out;
		for (j = n_rows - 1; j >= 0; j--) GMT_output (fp_out, n_out, &R_out[j*n_out]);
		GMT_free ((void *)R_out);
	}

	if (Ctrl->D.active && fp_out != NULL) GMT_fclose (fp_out);
	
	GMT_free ((void *)out);

	Free_Gmtconvert_Ctrl (Ctrl);	/* Deallocate control structure */

	GMT_end (argc, argv);

	exit (EXIT_SUCCESS);
}

void *New_Gmtconvert_Ctrl () {	/* Allocate and initialize a new control structure */
	struct GMTCONVERT_CTRL *C;
	
	C = (struct GMTCONVERT_CTRL *) GMT_memory (VNULL, 1, sizeof (struct GMTCONVERT_CTRL), "New_Gmtconvert_Ctrl");
	
	/* Initialize values whose defaults are not 0/FALSE/NULL */
	
	C->F.col = (short int *) GMT_memory (VNULL, GMTCONVERT_MAX_COL, sizeof (short int), GMT_program);
	
	return ((void *)C);
}

void Free_Gmtconvert_Ctrl (struct GMTCONVERT_CTRL *C) {	/* Deallocate control structure */
	if (C->F.col) GMT_free ((void *)C->F.col);	
	if (C->S.pattern) GMT_free ((void *)C->S.pattern);	
	GMT_free ((void *)C);	
}
