/*
This work is part of the Core Imaging Library developed by
Visual Analytics and Imaging System Group of the Science Technology
Facilities Council, STFC

Copyright 2003 Hamish Carr
Copyright 2017 Srikanth Nagella, Edoardo Pasca

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//	IsoSurface II
//	Hamish Carr, 1999

//	Array3D.h:	template class for 3D arrays

#ifndef ARRAY_3D_H
#define ARRAY_3D_H 1

//	boolean controls for array
#define RANGE_CHECK 1																//!	turn range-checking on
#define ZERO_ARRAY 1																//!	initialize all arrays to 0's
#define MAX_ARRAY_SIZE 100000000

#define ERROR_UNINITIALIZED_ARRAY -1												//!	error uninitialized array
#define ERROR_OUT_OF_BOUNDS -2														//! error addressed pixel is out of bounds
#define OPERATION_SUCCESS 12345												  		//! operation was successful

#ifdef DEBUG_MALLOC
#include "Malloc.h"
#endif

#include <stdlib.h>
#include <stdio.h>

template <class ElementType>
class Array3D
	{ // class Array3D
	private:
	ElementType *block;																//	the block of elements
	long xDim, yDim, zDim;															//	dimensions in each direction
	long nElements;																//	total number of elements
	long rowSize, sliceSize;															//	size of a row (and slice)
	ElementType dummy;
	
	public:	
		ElementType * getScalarPointer() {
			return block;
		}
	Array3D<ElementType>()															//	default constructor
		{ // Array3D constructor
		nElements = xDim = yDim = zDim = rowSize = sliceSize = 0;							//	make things predictable to start with
		block = NULL;																//	ditto
		} // Array3D constructor

	~Array3D<ElementType>()															//	destructor
		{ // Array3D destructor
		free(block);																//	release the block		
		} // Array3D destructor

	void Construct(long XDim, long YDim, long ZDim, ElementType *newBlock = NULL)				//	actual constructor - allows us to delay construction
		{ // Construct()
#ifdef RANGE_CHECK
		if ((XDim < 0) || (YDim < 0) || (ZDim < 0))										//	if any dimension is 0 or negative
			{ printf("Array size %ld x %ld x %ld is illegal\n", XDim, YDim, ZDim); return; }
		if ((XDim * YDim * ZDim < XDim * YDim)	|| (XDim * YDim < XDim))						//	i.e. computational rollover
			{ printf("Array size %ld x %ld x %ld caused computation of size to rollover.\n", XDim, YDim, ZDim);  return;}
		if (XDim * YDim * ZDim > MAX_ARRAY_SIZE)										//	more than we are prepared for right now	
			{ printf("Requested array size %ld x %ld x %ld has %ld elements: more than the %ld elements allowed.\n", XDim, YDim, ZDim, 
				XDim * YDim * ZDim, MAX_ARRAY_SIZE); return; }
#endif
		xDim = XDim; 
		yDim = YDim; 
		zDim = ZDim;											//!	copy the parameters
		rowSize = zDim; sliceSize = zDim * yDim;										//!	compute secondary parameters
		nElements = sliceSize * xDim;													//!	compute total size
		if (block != NULL) free(block);													//!	free up any previous memory
		if (newBlock == NULL)															//!	if we weren't passed the data
			if (xDim * yDim * zDim <= 0) 												//!	paranoia:  make sure we don't call malloc() improperly
				block = NULL;															//!	empty array has a null pointer
			else
#ifdef ZERO_ARRAY
				block = (ElementType *) calloc(nElements, sizeof (ElementType));		//!	use calloc() to allocate, and zero the elements
#else
				block = (ElementType *) malloc(nElements * sizeof (ElementType));		//!	use malloc() to allocate without zeroing
#endif
		else // i.e. we were passed the data
			block = newBlock;															//!	save a pointer to it
	}
	// Construct()

	/**
	 * sets the value of the pixel to the passed value.
	 * @params: x, y, z , coordinates of the pixel to set
	 * value: the value of the pixel
	 *
	 */
	int setScalarValueAt(long x, long y, long z, ElementType value){
		#ifdef RANGE_CHECK																		//!	if range-checking is turned on
			if (block == NULL) {    														//!	i.e. not constructed yet
					printf("Attempt to access element of uninitialized array.\n");
					return ERROR_UNINITIALIZED_ARRAY;
				}
				if ((x < 0) || (x >= xDim) || (y < 0) || (y >= yDim) || (z < 0) || (z >= zDim))	{//!	if any of the indices is out of range
					printf("Array reference (%ld,%ld,%ld) out of valid bounds: (0-%ld, 0-%ld, 0-%ld)\n", x, y, z, xDim-1, yDim-1, zDim-1);
					return ERROR_OUT_OF_BOUNDS;
				}
		#endif
		block[z + rowSize * y + sliceSize * x] = value;
		return OPERATION_SUCCESS;
	}
	//! setScalarValueAt

	/**
	* sets the value of the pixel to the passed value.
	* @params: x, y, z , coordinates of the pixel to set
	* value: the value of the pixel
	*
	*/
	int setScalarValueAtIndex(long index, ElementType value) {
#ifdef RANGE_CHECK																		//!	if range-checking is turned on
		if (block == NULL) {    														//!	i.e. not constructed yet
			printf("Attempt to access element of uninitialized array.\n");
			return ERROR_UNINITIALIZED_ARRAY;
		}
		if (index > (ZDim() * YDim() * XDim())) {//!	if any of the indices is out of range
			printf("Array reference (%ld) out of valid bounds: (0-%ld)\n", (ZDim() * YDim() * XDim()));
			return ERROR_OUT_OF_BOUNDS;
		}
#endif
		block[index] = value;
		return OPERATION_SUCCESS;
	}
	//! setScalarValueAt

	ElementType & operator() (long x, long y, long z)									//!	use function call to access array elements
		{
#ifdef RANGE_CHECK																		//!	if range-checking is turned on
		if (block == NULL) {    														//!	i.e. not constructed yet
			printf("Attempt to access element of uninitialized array.\n");
			return dummy;
		}
		if ((x < 0) || (x >= xDim) || (y < 0) || (y >= yDim) || (z < 0) || (z >= zDim))	{//!	if any of the indices is out of range
			printf("Array reference (%ld,%ld,%ld) out of valid bounds: (0-%ld, 0-%ld, 0-%ld)\n", x, y, z, xDim-1, yDim-1, zDim-1);
			return *block;
		}
#endif
		return block[z + rowSize * y + sliceSize * x];									//	compute address & return
		} // end of operator ()

	/*!
	returns a pointer to the element whose indices are x,y,z
	it calls in turn the operator(). I find the call to the operator() very cumbersome and confusing.
	*/
	ElementType & getElementAt(long x, long y, long z) {
		return operator()(x, y, z);
	}

	ElementType & getElementAtIndex(long index) {
		//if (index > nElements)
		//	return ERROR_OUT_OF_BOUNDS; should throw an error
		return block[index];
	}

	long XDim() { return xDim; }														//	accessor to find out size
	long YDim() { return yDim; }														//	accessor to find out size
	long ZDim() { return zDim; }														//	accessor to find out size
	long RowSize() { return rowSize; }													//	accessor to find out rowsize
	long SliceSize() { return sliceSize; }												//	accessor to find out slicesize
	long NElements() { return nElements; }												//	accessor for nElements
	
	void ComputeIndex(ElementType *element, long &X, long &Y, long &Z)					//	computes position & returns it in X, Y, Z
		{ // ComputeIndex()	
		long position = element - block;												//	find the offset from the base of the array
#ifdef RANGE_CHECK
		if ((position < 0) || (position >= nElements))									//	check to see if the pointer is in the right range
			{ printf("Attempt to compute index of pointer (%X) outside valid bounds (%X-%X)\n", (long) element, (long) block, (long) block+nElements-1); return; }
#endif
		Z = position % zDim;														//	compute the z coordinate
		position = (position - Z) / zDim;												//	compute the x-y plane
		Y = position % yDim;														//	compute the y coordinate
		X = (position - Y) / yDim;													//	compute the column
		} // ComputeIndex()	
	}; // class Array3D

#endif	
