/***********************************************************************
* pc_stats.c
*
*  Pointclound patch statistics generation.
*
*  Copyright (c) 2013 OpenGeo
*
***********************************************************************/

#include "pc_api_internal.h"
#include <float.h>

/*
* Instantiate a new PCDOUBLESTATS for calculation, and set up
* initial values for min/max/sum
*/
static PCDOUBLESTATS *
pc_dstats_new(int ndims)
{
	int i;
	PCDOUBLESTATS *stats = pcalloc(sizeof(PCDOUBLESTATS));
	stats->dims = pcalloc(sizeof(PCDOUBLESTAT)*ndims);
	for ( i = 0; i < ndims; i++ )
	{
		stats->dims[i].min = DBL_MAX;
		stats->dims[i].max = -1 * DBL_MAX;
		stats->dims[i].sum = 0;
	}
	stats->npoints = 0;
	return stats;
}

static void
pc_dstats_free(PCDOUBLESTATS *stats)
{
	if ( ! stats) return;
	if ( stats->dims ) pcfree(stats->dims);
	pcfree(stats);
	return;
}

/**
* Free the standard stats object for in memory patches
*/
void
pc_stats_free(PCSTATS *stats)
{
	if ( stats->min.readonly != PC_TRUE )
		pcfree(stats->min.data);

	if ( stats->max.readonly != PC_TRUE )
		pcfree(stats->max.data);

	if ( stats->avg.readonly != PC_TRUE )
		pcfree(stats->avg.data);

	pcfree(stats);
	return;
}

/**
* Build a standard stats object on top of a serialization, allocate just the
* point shells and set the pointers to look into the data area of the
* serialization.
*/
PCSTATS *
pc_stats_new_from_data(const PCSCHEMA *schema, const uint8_t *mindata, const uint8_t *maxdata, const uint8_t *avgdata)
{
	/*size_t sz = schema->size;*/
	PCSTATS *stats = pcalloc(sizeof(PCSTATS));
	/* All share the schema with the patch */
	stats->min.schema = schema;
	stats->max.schema = schema;
	stats->avg.schema = schema;
	/* Data points into serialization */
	stats->min.data = (uint8_t*)mindata;
	stats->max.data = (uint8_t*)maxdata;
	stats->avg.data = (uint8_t*)avgdata;
	/* Can't modify external data */
	stats->min.readonly = PC_TRUE;
	stats->max.readonly = PC_TRUE;
	stats->avg.readonly = PC_TRUE;
	/* Done */
	return stats;
}

/**
* Build a standard stats object with read/write memory, allocate the
* point shells and the data areas underneath. Used for initial calcution
* of patch stats, when objects first created.
*/
PCSTATS *
pc_stats_new(const PCSCHEMA *schema)
{
	/*size_t sz = schema->size;*/
	PCSTATS *stats = pcalloc(sizeof(PCSTATS));
	stats->min.schema = schema;
	stats->max.schema = schema;
	stats->avg.schema = schema;
	stats->min.readonly = PC_FALSE;
	stats->max.readonly = PC_FALSE;
	stats->avg.readonly = PC_FALSE;
	stats->min.data = pcalloc(schema->size);
	stats->max.data = pcalloc(schema->size);
	stats->avg.data = pcalloc(schema->size);
	return stats;
}

/**
* Allocate and populate a new PCSTATS from the raw data in
* a PCDOUBLESTATS
*/
static PCSTATS *
pc_stats_new_from_dstats(const PCSCHEMA *schema, const PCDOUBLESTATS *dstats)
{
	int i;
	PCSTATS *stats = pc_stats_new(schema);

	for ( i = 0; i < schema->ndims; i++ )
	{
		pc_point_set_double(&(stats->min), schema->dims[i], dstats->dims[i].min);
		pc_point_set_double(&(stats->max), schema->dims[i], dstats->dims[i].max);
		pc_point_set_double(&(stats->avg), schema->dims[i], dstats->dims[i].sum / dstats->npoints);
	}
	return stats;
}

PCSTATS *
pc_stats_clone(const PCSTATS *stats)
{
	PCSTATS *s;
	if ( ! stats ) return NULL;
	s = pcalloc(sizeof(PCSTATS));
	s->min.readonly = s->max.readonly = s->avg.readonly = PC_FALSE;
	s->min.schema = stats->min.schema;
	s->max.schema = stats->max.schema;
	s->avg.schema = stats->avg.schema;
	s->min.data = pcalloc(stats->min.schema->size);
	s->max.data = pcalloc(stats->max.schema->size);
	s->avg.data = pcalloc(stats->avg.schema->size);
	memcpy(s->min.data, stats->min.data, stats->min.schema->size);
	memcpy(s->max.data, stats->max.data, stats->max.schema->size);
	memcpy(s->avg.data, stats->avg.data, stats->avg.schema->size);
	return s;
}

int
pc_patch_uncompressed_compute_stats(PCPATCH_UNCOMPRESSED *pa)
{
	int i, j;
	const PCSCHEMA *schema = pa->schema;
	double val;
	PCDOUBLESTATS *dstats = pc_dstats_new(pa->schema->ndims);

	if ( pa->stats )
		pc_stats_free(pa->stats);

	/* Point on stack for fast access to values in patch */
	PCPOINT pt;
	pt.readonly = PC_TRUE;
	pt.schema = schema;
	pt.data = pa->data;

	/* We know npoints right away */
	dstats->npoints = pa->npoints;

	for ( i = 0; i < pa->npoints; i++ )
	{
		for ( j = 0; j < schema->ndims; j++ )
		{
			pc_point_get_double(&pt, schema->dims[j], &val);
			/* Check minimum */
			if ( val < dstats->dims[j].min )
				dstats->dims[j].min = val;
			/* Check maximum */
			if ( val > dstats->dims[j].max )
				dstats->dims[j].max = val;
			/* Add to sum */
			dstats->dims[j].sum += val;
		}
		/* Advance to next point */
		pt.data += schema->size;
	}

	pa->stats = pc_stats_new_from_dstats(pa->schema, dstats);
	pc_dstats_free(dstats);
	return PC_SUCCESS;
}

size_t
pc_stats_size(const PCSCHEMA *schema)
{
	return 3*schema->size;
}



