//- ****************************************************************************
//- 
//- Copyright 2009 Sandia Corporation. Under the terms of Contract
//- DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
//- retains certain rights in this software.
//- 
//- BSD Open Source License.
//- All rights reserved.
//- 
//- Redistribution and use in source and binary forms, with or without
//- modification, are permitted provided that the following conditions are met:
//- 
//-    * Redistributions of source code must retain the above copyright notice,
//-      this list of conditions and the following disclaimer.
//-    * Redistributions in binary form must reproduce the above copyright
//-      notice, this list of conditions and the following disclaimer in the
//-      documentation and/or other materials provided with the distribution.
//-    * Neither the name of Sandia National Laboratories nor the names of its
//-      contributors may be used to endorse or promote products derived from
//-      this software without specific prior written permission.
//- 
//- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
//- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//- POSSIBILITY OF SUCH DAMAGE.
//-
//- ****************************************************************************

#ifndef PROFILETHIN_OBJECT_H
#define PROFILETHIN_OBJECT_H

// **** _SYSTEM INCLUDES_ ******************************************************

#include <iostream>
#include <fstream>
#include <string>

// use standard library objects
using namespace std;

// **** _LOCAL INCLUDES_ *******************************************************

#include "Data.h"
#include "Profile.h"
#include "ProfileType.h"
#include "IFStreamAscii.h"
#include "IFStreamBinary.h"

// **** _BEGIN GEOTESS NAMESPACE_ **********************************************

namespace geotess {

// **** _FORWARD REFERENCES_ ***************************************************

class GeoTessMetaData;

// **** _CLASS DEFINITION_ *****************************************************

/**
 * \brief A Profile object consiting of a single radius value and a single
 * Data object.  It represents a profile through a layer that has no thickness.
 *
 * A Profile object that consists of a single radius value and a single
 * Data object. It represents a profile through a layer that has zero thickness.
 */
class GEOTESS_EXP_IMP ProfileThin : virtual public Profile
{
private:

	/**
	 * Thin layer radius.
	 */
	float									radius;

	/**
	 * Attached Data object.
	 */
	Data*									data;

	/**
	 * Grid point index of this data
	 */
	int											pointIndex;

	/**
	 * Default constructor.
	 */
	ProfileThin() : Profile(), radius(0.0), data(NULL),
			pointIndex(-1) {};

public:

	/**
	 * Default constructor.
	 */
	ProfileThin(float rad, Data* dat) :
		Profile(), radius(rad), data(dat),
		pointIndex(-1) {};

	/**
	 * Returns the class name.
	 */
	static  string				class_name() { return "ProfileThin"; };

	/**
	 * Returns the class size.
	 */
	virtual int						class_size() const
	{ return (int) sizeof(ProfileThin); };

	/**
	 * Returns ProfileType (THIN).
	 *
	 * @return ProfileType (THIN).
	 */
	virtual	const ProfileType&	getType() const
	{ return ProfileType::THIN; };

	/**
	 * Return true if the input Profile object (p) equals this Profile object.
	 */
	virtual bool operator == (const Profile& p) const
	{ return (Profile::operator==(p) && (radius == p.getRadius(0)) && (*data == p.getData(0)));	}

	/**
	 * Retrieve the value of the specified attribute from this
	 * profile at the specified node index
	 *
	 * @param attributeIndex
	 * @param nodeIndex
	 * @return double
	 */
	virtual double getValue(int attributeIndex, int nodeIndex) const
	{ return nodeIndex == 0 ? data->getDouble(attributeIndex) : NaN_DOUBLE; }

	/**
	 * Retrieve the value of the specified attribute interpolated at the
	 * specified radius.
	 * @param rInterpType either Int
	 * @param attributeIndex
	 * @param r radius in km
	 * @param allowRadiusOutOfRange
	 * @return interpolated attribute value
	 */
	virtual double	getValue(const InterpolatorType& rInterpType,
			int attributeIndex, double r, bool allowRadiusOutOfRange) const
	{
		if (!allowRadiusOutOfRange &&
				((r < getRadiusBottom()) || (r > getRadiusTop())))
			return NaN_DOUBLE;

		// default behavior is to simply return the data value for the first
		// data object. This works for all the Profile classes that only support
		// a single Data object like ProfileConstant, ProfileSurface, and
		// ProfileThin. Profile classes for which the number of supported
		// Data objects is not equal to 1 must override this method.

		return getValue(attributeIndex, 0);
	};

	/**
	 * Retrieve the value of the specified attribute at the top of the layer.
	 *
	 * @param attributeIndex
	 * @return double
	 */
	virtual double getValueTop(int attributeIndex) const
	{	return data->getDouble(attributeIndex); }

	/**
	 * Return true if the specified Data value is NaN.
	 * For doubles and floats, this means not NaN.
	 * For bytes, shorts, ints and longs, always returns false
	 * since there is no value that is NaN.
	 *
	 * @param nodeIndex
	 * @param attributeIndex
	 * @return true if the specified Data value is NaN.
	 */
	virtual bool isNaN(int nodeIndex, int attributeIndex)
	{
		return nodeIndex != 0 || data->isNaN(attributeIndex);
	}

	/**
	 * Get the i'th radius value in this profile in km. Radii are in order of
	 * increasing radius.
	 */
	virtual float					getRadius(int i) const { return radius; };

	/**
	 * Retrieve a reference to all of the Data obects associated with this
	 * Profile.
	 */
	virtual Data**				getData()
	{ Data** da = new Data* [1]; da[0] = data; return da; };

	/**
	 * Retrieve a reference the i'th Data object
	 */
	virtual Data*					getData(int i) { return data; };

	/**
	 * Retrieve a reference the i'th Data object
	 */
	virtual const Data&		getData(int i) const { return *data; };

	/**
	 * Resets the data object to the new input data
	 */
	virtual void					setData(const vector<Data*>& inData)
	{ delete data; data = inData[0]; }

	/**
	 * Replace the radii currently associated with this Profile with new values.
	 */
	virtual void					setRadii(const vector<float>& newRadii)
	{ radius = newRadii[0]; }

	/**
	 * Resets the data object at index to the new input data.
	 */
	virtual void          setData(int index, Data* inData)
	{ delete data; data = inData; }

	/**
	 * Get the radius at the top of the profile, in km.
	 */
	virtual float					getRadiusTop() const { return radius; };

	/**
	 * Get the Data object at the top of the profile.
	 */
	virtual const Data&		getDataTop() const { return *data; };

	/**
	 * Get the Data object at the top of the profile.
	 */
	virtual Data*					getDataTop() { return data; };

	/**
	 * Get the radius at the bottom of the profile, in km.
	 */
	virtual float					getRadiusBottom() const { return radius; };

	/**
	 * Get the Data object at the bottom of the profile.
	 */
	virtual const Data&		getDataBottom() const { return *data; };

	/**
	 * Get the Data object at the bottom of the profile.
	 */
	virtual Data*					getDataBottom() { return data; };

	/**
	 * Get the number of radii that comprise this profile.
	 */
	virtual int						getNRadii() const { return 1; };

	/**
	 * Get the number of Data objects that comprise this profile.
	 */
	virtual int						getNData() const { return 1; };

	/**
	 * Retrieve a deeep copy of the radii values in km.
	 */
	virtual float*				getRadii()
	{ float* fa = new float [1]; fa[0] = radius; return fa; };

	/// @cond PROTECTED  Turn off doxygen documentation until 'endcond' is found

	/**
	 * Standard constructor that reads radii from the provided input file stream.
	 */
	ProfileThin(IFStreamBinary& ifs, GeoTessMetaData& gtmd) : Profile(),
			radius(-1.0), data(NULL), pointIndex(-1)
	{ radius = ifs.readFloat(); data = Data::getData(ifs, gtmd); };

	/**
	 * Standard constructor that reads radii from the provided input file stream.
	 */
	ProfileThin(IFStreamAscii& ifs, GeoTessMetaData& gtmd) : Profile(),
			radius(-1.0), data(NULL), pointIndex(-1)
	{ radius = ifs.readFloat(); data = Data::getData(ifs, gtmd); };

	/**
	 * Default destructor. Note that all Profile objects delete their data. Data was
	 * created by input functionality.
	 */
	virtual								~ProfileThin() { if (data != NULL) delete data; };

	/**
	 * Write the radii and data values to binary file.
	 */
	virtual void					write(IFStreamBinary& ofs)
	{ ofs.writeByte((byte) ProfileType::THIN.ordinal());
	ofs.writeFloat(radius); data->write(ofs); };

	/**
	 * Write the radii and data values to binary file.
	 */
	virtual void					write(IFStreamAscii& ofs)
	{ ofs.writeInt(ProfileType::THIN.ordinal());
	ofs.writeString(" ");
	ofs.writeFloat(radius);
	data->write(ofs);
	ofs.writeNL(); };

	/**
	 * Find the index of the node in this Profile that has radius closest to the
	 * supplied radius.
	 *
	 * @param radius in km
	 * @return The index of the node in this Profile that has radius closest to the
	 *         supplied radius.
	 */
	virtual int							findClosestRadiusIndex(double r) const
	{	return 0; }

	/**
	 * Set the pointIndex that corresponds to the supplied nodeIndex.
	 * <p>There is a node index for each Data object in a profile and they are indexed from 0 to
	 * the number of Data objects managed by a Profile.  There is a pointIndex for every
	 * Data object in the entire model, indexed from 0 to the number of Data objects in the
	 * model.
	 */
	virtual void						setPointIndex(int nodeIndex, int pntIndex)
	{	pointIndex = pntIndex; }

	/**
	 * Reset all the pointIndex values to -1.
	 * <p>There is a node index for each Data object in a profile and they are indexed from 0 to
	 * the number of Data objects managed by a Profile.  There is a pointIndex for every
	 * Data object in the entire model, indexed from 0 to the number of Data objects in the
	 * model.
	 */
	virtual void resetPointIndices() { pointIndex = -1; }

	/**
	 * Get the pointIndex that corresponds to the supplied nodeIndex.
	 * <p>There is a node index for each Data object in a profile and they are indexed from 0 to
	 * the number of Data objects managed by a Profile.  There is a pointIndex for every
	 * Data object in the entire model, indexed from 0 to the number of Data objects in the
	 * model.
	 */
	virtual int							getPointIndex(int nodeIndex) const
	{	return pointIndex; }

	/**
	 * Returns a deep copy of this profile.
	 */
	virtual Profile*      copy()
	{
		return new ProfileThin(radius, data->copy());
	}

	///@endcond

}; // end class ProfileThin

} // end namespace geotess

#endif  // PROFILETHIN_OBJECT_H
