/******************************************************************************
 * ************************************************************************** *
 * * ********************************************************************** * *
 * * * ****************************************************************** * * *
 * * * * ************************************************************** * * * *
 * * * * * ********************************************************** * * * * *
 * * * * * * ****************************************************** * * * * * *
 * * * * * * * ************************************************** * * * * * * *
 * * * * * * * * ********************************************** * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *       Please do not read this until        * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *       after you have made a serious        * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *        attempt at writing all the          * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *         code, including templates.         * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *       Reading the solution will not        * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *     help you understand how to program.    * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * ********************************************** * * * * * * * *
 * * * * * * * ************************************************** * * * * * * *
 * * * * * * ****************************************************** * * * * * *
 * * * * * ********************************************************** * * * * *
 * * * * ************************************************************** * * * *
 * * * ****************************************************************** * * *
 * * ********************************************************************** * *
 * ************************************************************************** *
 ******************************************************************************/







/******************************************************************************
 * ************************************************************************** *
 * * ********************************************************************** * *
 * * * ****************************************************************** * * *
 * * * * ************************************************************** * * * *
 * * * * * ********************************************************** * * * * *
 * * * * * * ****************************************************** * * * * * *
 * * * * * * * ************************************************** * * * * * * *
 * * * * * * * * ********************************************** * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * *          YOU HAVE BEEN WARNED  :-)         * * * * * * * * *
 * * * * * * * * *                                            * * * * * * * * *
 * * * * * * * * ********************************************** * * * * * * * *
 * * * * * * * ************************************************** * * * * * * *
 * * * * * * ****************************************************** * * * * * *
 * * * * * ********************************************************** * * * * *
 * * * * ************************************************************** * * * *
 * * * ****************************************************************** * * *
 * * ********************************************************************** * *
 * ************************************************************************** *
 ******************************************************************************/

#ifndef ARRAY_H
#define ARRAY_H

#include <algorithm>
#include <iostream>
#include <cmath>
#include "Exception.h"

template <typename Type>
class Array {
	 private:
		int array_capacity;
		Type *internal_array;
		int array_size;

	public:
		Array( int = 10 );
		Array( Array const & );
		~Array();

		int size() const;
		int capacity() const;
		bool empty() const;
		bool full() const;

		Type operator[]( int ) const;

		Type sum() const;
		double average() const;
		double variance() const;
		double std_dev() const;

		Array &operator=( Array<Type> );
		void swap( Array<Type> & );
		bool append( Type const & );
		void clear();

	// Friends:  this is a global function
	template <typename T>
	friend std::ostream &operator<<( std::ostream &, Array<T> const & );
};

/*
 * Constructor
 *
 * Create a new array with a capacity of at least one.
 * The default value of the argument is 10.
 *
 * The array is initially empty, and
 * there is no need to zero out the entries of the array;
 */

template <typename Type>
Array<Type>::Array( int n ):
array_capacity( std::max( n, 1 ) ),
internal_array( new Type[capacity()] ),
array_size( 0 ) {
	// does nothing
}

/*
 * Copy constructor
 *
 * Make a deep copy of the argument array.
 */

template <typename Type>
Array<Type>::Array( Array<Type> const &other ):
array_capacity( other.capacity() ),
internal_array( new Type[capacity()] ),
array_size( other.size() ) {
	for ( int i = 0; i < other.size(); ++i ) {
		internal_array[i] = other.internal_array[i];
	}
}

/*
 * Destructor
 *
 * Deallocate the memory assigned to 'internal_array'
 */

template <typename Type>
Array<Type>::~Array() {
	delete [] internal_array;
}

/*
 * Size
 *
 * Return the number of objects currently stored in this array.
 */

template <typename Type>
int Array<Type>::size() const {
	return array_size;
}

/*
 * Capacity
 *
 * Return the capacity of this array (the maximum number of objects
 * this array can store).
 */

template <typename Type>
int Array<Type>::capacity() const {
	return array_capacity;
}

/*
 * Empty
 *
 * Returns true if the array is empty (the size is 0), and false otherwise.
 */

template <typename Type>
bool Array<Type>::empty() const {
	return (size() == 0);
}

/*
 * Full
 *
 * Returns true if the number of objects stored in the array (the size)
 * equals the capacity.
 */

template <typename Type>
bool Array<Type>::full() const {
	return (size() == capacity());
}

/*
 * operator[]( int n )
 *
 * If n >= 0 and n < size(), return the object stored at index n of this array.
 * If n < 0, throw an underflow exception.
 * If n >= size(), throw an overflow exception.
 */

template <typename Type>
Type Array<Type>::operator[]( int n ) const {
	return internal_array[n];
}

/*
 * Sum
 *
 * Return the sum of the objects stored in this array.
 */

template <typename Type>
Type Array<Type>::sum() const {
	Type result = 0;

	for ( int i = 0; i < array_size; ++i ) {
		result += internal_array[i];
	}

	return result;
}

/*
 * Average
 *
 * Calculate the arithmetic average of the entries in the array.
 * The average is not defined if the array is empty, throw an underflow exception.
 */

template <typename Type>
double Array<Type>::average() const {
	if ( empty() ) {
		throw underflow();
	}

	// The average is the sum of the entries divided by the size
	return static_cast<double>( sum() )/static_cast<double>( size() );
}

/*
 * Variance
 *
 * Calculate the sample variance of the entries in the array.
 * This value is the sum of the squares of the differences of
 * the entries divided by the size minus 1.
 *
 * The sample variance is not defined if the array does not
 * have at least two objects in it, throw an underflow exception.
 */

template <typename Type>
double Array<Type>::variance() const {
	if ( size() <= 1 ) {
		throw underflow();
	} 

	double av = average();

	// Calculate the sum of the squares of the differences
	// between the objects and the average.
	double ssdiff = 0.0;

	for ( int i = 0; i < size(); ++i ) {
		ssdiff += (internal_array[i] - av)*(internal_array[i] - av);
	}

	// Return the sum divided by the size minus 1
	return ssdiff/(size() - 1);
}

/*
 * Standard deviation
 *
 * Calculate the sample standard deviation of the entries in the array.
 * This value is the square root of the sample variance.
 *
 * The sample standard deviation is not defined if the array does not
 * have at least two objects in it, throw an underflow exception.
 */

template <typename Type>
double Array<Type>::std_dev() const {
	// variance() will throw an exception if size() < 2
	return std::sqrt( variance() );
}

/*
 * Swap
 *
 * Swap the member variables of 'this' object and those of
 * the argument 'other'.
 *
 * We are using the standard library 'swap' function.
 */

template <typename Type>
void Array<Type>::swap( Array<Type> &other ) {
	std::swap( array_capacity, other.array_capacity );
	std::swap( internal_array, other.internal_array );
	std::swap( array_size, other.array_size );
}

/*
 * Assignment operator
 *
 * Assign the argument to this array.
 */

template <typename Type>
Array<Type> &Array<Type>::operator=( Array<Type> rhs ) {
	swap( rhs );

	return *this;
}

/*
 * Append
 *
 * If the array is full, do nothing and return false.
 *
 * Otherwise, append the argument to the next available location
 * in the array, increment the array size, and return true.
 */

template <typename Type>
bool Array<Type>::append( Type const &obj ) {
	if ( full() ) {
		return false;
	}

	// currently, entries 0, ..., array_size - 1 are occupied
	internal_array[array_size] = obj;
	++array_size;
	return true;
}

/*
 * Clear
 *
 * Empty the array; that is, set the array size to 0.
 *
 * Note:  nothing else has to be done here.  We do not have
 * to delete the array, we do not have to set the entries in
 * the array to 0, nullptr or anything else.
 */

template <typename Type>
void Array<Type>::clear() {
	array_size = 0;
}

/*
 * Printing the array
 *
 * This is a globally defined function, and therefore it is not a member variable
 * of the Array class.  In order for this function to access the private member variables
 * and (though none in this example) private member functions, this global function
 * must be declared to be a 'friend' of the Array class.  If you return to the Array class
 * definition, you will see the statement.
 *
 * The format of the printing will be to create the string where:
 *   - each value is displayed, and
 *   - any entries that are available but not filled are displayed with a '-'
 *
 * For example, the array with default size after having 2, 3 and 4 appended would be
 * displayed as the string
 *   "2 3 4 - - - - - - -"
 *
 * Note to interested students:
 *   If you enter
 *        cout << my_array;
 *   the compiler treats this as a call to the global function operator<<( cout, my_array ),
 *   and the function below takes as its arguments an ostream object and an Array object.
 *
 *   You will note that this globally defined overloaded operator takes two arguments,
 *   while the overloaded assignment operator declared as a class member takes only
 *   one argument.  In essence, if @ is any operator (+, -, *, /, =, <<, etc.) and the
 *   compiler sees x @ y where x and y are instances of classes XX and YY, then:
 *
 *     If the class XX has a member function operator@( YY ), it will call that
 *     member function.
 *   
 *     If there is globally defined function operator@( XX, YY ), it will call that one.  
 *
 *   In this case, because the user is unable to add a member function to the 'ostream'
 *   class, we must go the route of declaring a global function.
 */

template <typename T>
std::ostream &operator<<( std::ostream &out, Array<T> const &param ) {
	if ( param.empty() ) {
		out << "-";
	} else {
		out << param.internal_array[0];
	}

	for ( int i = 1; i < param.size(); ++i ) {
		out << " " << param.internal_array[i];
	}

	for ( int i = param.size(); i < param.capacity(); ++i ) {
		out << " -";
	}

	return out;
}

#endif