/* cavestats.c */
/* Print statistics on a cave */
/* Copyright (C) 2004 David Loeffler
 * Based on 3dtopos.c by Olly Betts
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cmdline.h"
#include "filelist.h"
#include "filename.h"
#include "img.h"
#include "message.h"
#include "osalloc.h"

int cmp_station(const void *a, const void *b);

static const struct option long_opts[] = {
	/* const char *name; int has_arg (0 no_argument, 1 required_*, 2 optional_*); int *flag; int val; */
	{"survey", required_argument, 0, 's'},
	{"help", no_argument, 0, HLP_HELP},
	{"version", no_argument, 0, HLP_VERSION},
	{"compact", no_argument, 0, 'c'},
	{0, 0, 0, 0}
};

#define short_opts "s:c"

static struct help_msg help[] = {
/*				<-- */
	{HLP_ENCODELONG(0),	/*only load the sub-survey with this prefix*/199, 0},
	{0, 0, 0}
};

typedef struct {
	const char *name;
	img_point pt;
} station;

static station basept;

static int separator;

int
cmp_station(const void *a, const void *b)
{
	img_point pta, ptb;
	double crossprod;
	pta = ((const station *)a)->pt;
	ptb = ((const station *)b)->pt;

	if(pta.x == basept.pt.x && pta.y == basept.pt.y) return -1;
	if(ptb.x == basept.pt.x && ptb.y == basept.pt.y) return 1;
	/* These two lines mean that the basepoint should compare as being before everything.
	Otherwise somewhat unpredictable behaviour occurs! */
	crossprod = (pta.x - basept.pt.x)*(ptb.y - basept.pt.y) - (pta.y - basept.pt.y)*(ptb.x - basept.pt.x);
	if(crossprod > 0) return 1;
	if(crossprod < 0) return -1;
	else return 0;
}

static station *stns;
static OSSIZE_T c_stns = 0;
static station *hull;
static OSSIZE_T c_hull = 0;

int
main(int argc, char **argv)
{
	char *fnm;
	img *pimg;
	int result;
	OSSIZE_T c_labels = 0;
	OSSIZE_T c_totlabel = 0;
	char *p, *p_end;
	const char *survey = NULL;
	bool compact_output = 0;
	double totlen = 0, planlen = 0;
	img_point lastpt;
	int num_legs = 0;
	
	msg_init(argv);

	cmdline_set_syntax_message(/*3D_FILE*/217, 0, NULL); /* TRANSLATE */
	cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 1, 1);
	while (1) {
		int opt = cmdline_getopt();
		if (opt == EOF) break;
		if (opt == 's') survey = optarg;
		if (opt == 'c') compact_output = 1;
	}

	fnm = argv[optind++];

	pimg = img_open_survey(fnm, survey);
	if (!pimg) fatalerror(img_error(), fnm);
	separator = pimg->separator;

	/* Output headings line */
	if (!compact_output) printf("Statistics for %s (prefix %s)\n", fnm, survey);
	
	do {
		img_point pt;
		result = img_read_item(pimg, &pt);
		switch (result) {
			case img_LINE:
					if(!pimg->flags)
					{
						planlen += sqrt((pt.x - lastpt.x)*(pt.x - lastpt.x) + (pt.y - lastpt.y)*(pt.y - lastpt.y));
						totlen += sqrt((pt.x - lastpt.x)*(pt.x - lastpt.x) + (pt.y - lastpt.y)*(pt.y - lastpt.y) + (pt.z - lastpt.z)*(pt.z - lastpt.z));
					}
					num_legs++;
			case img_MOVE:
					lastpt = pt;
				break;
			case img_LABEL:
				if(pimg->flags & img_SFLAG_UNDERGROUND)
				{
					c_labels++;
					c_totlabel += strlen(pimg->label) + 1;
				}
				break;
			case img_BAD:
				img_close(pimg);
				fatalerror(img_error(), fnm);
		}
	} while (result != img_STOP);

	/* + 1 to allow for reading coordinates of legs after the last label */
	stns = osmalloc(ossizeof(station) * (c_labels + 1));
	p = osmalloc(c_totlabel);
	p_end = p + c_totlabel;

	img_rewind(pimg);

	do {
	  result = img_read_item(pimg, &(stns[c_stns].pt));
	  switch (result) {
		case img_LINE:
		case img_MOVE:
	 break;
		case img_LABEL:
	if (!(pimg->flags & img_SFLAG_UNDERGROUND)) break; 
	if (c_stns < c_labels) {
		OSSIZE_T len = strlen(pimg->label) + 1;
		if (p + len <= p_end) {
			memcpy(p, pimg->label, len);
			stns[c_stns++].name = p;
			p += len;
			break;
		}
	 }
	 /* FALLTHRU */
		case img_BAD:
	 img_close(pimg);
	 fatalerror(/*Bad 3d image file `%s'*/106, fnm);
	  }
	} while (result != img_STOP);

	if (c_stns != c_labels || p != p_end) {
	  img_close(pimg);
	  fatalerror(/*Bad 3d image file `%s'*/106, fnm);
	}

	img_close(pimg);
	if (c_stns == 0)
	{
		printf("No survey data!\n");
		return EXIT_SUCCESS;
	}
	station wmost, nmost, emost, smost, umost, lmost;
	wmost = emost = nmost = smost = umost = lmost = stns[0];
	{
		OSSIZE_T i;
		for(i = 0; i < c_stns; i++)
		{
			if(stns[i].pt.x < wmost.pt.x) wmost = stns[i];
			if(stns[i].pt.x > emost.pt.x) emost = stns[i];
			if(stns[i].pt.y > nmost.pt.y) nmost = stns[i];
			if(stns[i].pt.y < smost.pt.y) smost = stns[i];
			if(stns[i].pt.z > umost.pt.z) umost = stns[i];
			if(stns[i].pt.z < lmost.pt.z) lmost = stns[i]; 
		}
	}
	
	basept = wmost; /* any extreme point will do */
	
	qsort(stns, c_stns, sizeof(station), cmp_station);

	/* Now, having sorted the stations, any duplicate stations caused by *equates will appear consecutive. */
	
	{
			OSSIZE_T i;
			int real_stn_count = 1;
			for(i = 1; i < c_stns; i++)
					if(stns[i].pt.x != stns[i-1].pt.x || stns[i].pt.y != stns[i-1].pt.y || stns[i].pt.z != stns[i-1].pt.z) 
							real_stn_count++;
			if (!compact_output) 
			{
				printf((real_stn_count == 1 ? msg(172) : msg(173)), real_stn_count);
				printf((num_legs == 1 ? msg(174) : msg(175)), num_legs);
				putnl();
				//printf((num_legs - real_stn_count == 0 ? msg(138) : msg(139)), num_legs - real_stn_count + 1);
				// Can't do this, as we don't know the number of connected components
			}
	}
	

	if (!compact_output)
	{
		printf("Total length of survey legs = %.2fm\n", totlen);

		printf(msg(133), planlen);
		putnl();
	
		printf(msg(135), (umost.pt.z - lmost.pt.z));
		printf(umost.name);
		printf(msg(136), umost.pt.z);
		printf(lmost.name);
		printf(msg(137), lmost.pt.z);
		putnl();
	
		printf(msg(148), (nmost.pt.y - smost.pt.y));
		printf(nmost.name);
		printf(msg(136), nmost.pt.y);
		printf(smost.name);
		printf(msg(137), smost.pt.y);
		putnl();
	
		printf(msg(149), (emost.pt.x - wmost.pt.x));
		printf(emost.name);
		printf(msg(136), emost.pt.x);
		printf(wmost.name);
		printf(msg(137), wmost.pt.x);
		putnl();
	}
	if(c_stns == 1)
	{
		if (!compact_output) printf("Horizontal extent = 0.00m (from %s to %s)\n", stns[0].name, stns[0].name);
		else printf("0.00\t0.00\t0.00\n");
		return EXIT_SUCCESS;
	}
	
	if(c_stns == 2)
	{
		if (!compact_output) printf("Horizontal extent = %.2fm (from %s to %s)\n", planlen, stns[0].name, stns[1].name);
		else printf("%.2f\t%.2f\t%.2f", totlen, umost.pt.z-lmost.pt.z, planlen);
		return EXIT_SUCCESS;
	}

	// Noddy equivalent of a stack...
	c_hull = 3;
	hull = osmalloc(ossizeof(station) * c_stns);
	hull[0] = stns[0];
	hull[1] = stns[1];
	hull[2] = stns[2];
	OSSIZE_T i = 3;
	while(i < c_stns)
	{
		double sa;
		sa = (hull[c_hull - 1].pt.x - stns[i].pt.x)*(hull[c_hull - 2].pt.y - stns[i].pt.y)
				- (hull[c_hull - 2].pt.x - stns[i].pt.x)*(hull[c_hull - 1].pt.y - stns[i].pt.y);
		if(sa > 0)
		{
			hull[c_hull++] = stns[i++];
		}
		else
		{
			c_hull--;
		}	
	}
	{
		OSSIZE_T j;
		station s1, s2;
		double d, e, r;
		s1 = s2 = hull[0];
		r = 0;
		j = 0;
		d = 0;
		for(i = 0; i < c_hull; i++)
		{
				d = sqrt((hull[j % c_hull].pt.x - hull[i].pt.x)*(hull[j % c_hull].pt.x - hull[i].pt.x) + (hull[j % c_hull].pt.y - hull[i].pt.y)*(hull[j % c_hull].pt.y - hull[i].pt.y));
				while((e = sqrt((hull[(j+1) % c_hull].pt.x - hull[i].pt.x)*(hull[(j+1) % c_hull].pt.x - hull[i].pt.x) + (hull[(j+1) % c_hull].pt.y - hull[i].pt.y)*(hull[(j+1) % c_hull].pt.y - hull[i].pt.y))) >= d)
				{
						j++; d = e;
				}
				if(d > r)
				{
						r = d;
						s1 = hull[i];
						s2 = hull[j % c_hull];
				}
		}
		if (!compact_output) printf("Horizontal extent = %.2fm (from %s to %s)\n", r, s1.name, s2.name);
		else printf("%.2f\t%.2f\t%.2f\n", totlen, umost.pt.z - lmost.pt.z, r);
	}
	return EXIT_SUCCESS;
}