#ifndef CC_WAVEFORM_HEADER
#define CC_WAVEFORM_HEADER

//Local
#include "qCC_db.h"
#include "ccGLMatrix.h"
#include "ccSerializableObject.h"

//CCCoreLib
#include <CCGeom.h>

//system
#include <cstdint>
#include <cstdlib>

//! Waveform descriptor
class QCC_DB_LIB_API WaveformDescriptor : public ccSerializableObject
{
public:
	
	//! Default constructor
	WaveformDescriptor();

	//! Difference operator
	bool operator != (const WaveformDescriptor& d) const;

	//! Equivalence operator
	bool operator == (const WaveformDescriptor& d) const { return	!(*this != d); }

	//inherited from ccSerializableObject
	bool isSerializable() const override { return true; }
	bool toFile(QFile& out, short dataVersion) const override;
	bool fromFile(QFile& in, short dataVersion, int flags, LoadedIDMap& oldToNewIDMap) override;
	short minimumFileVersion() const override;

	uint32_t numberOfSamples;	//!< Number of samples
	uint32_t samplingRate_ps;	//!< Sampling rate in pico seconds
	double digitizerGain;		//!< Digitizer gain (VOLTS = OFFSET + GAIN * Raw_Waveform_Amplitude)
	double digitizerOffset;		//!< Digitizer offset (VOLTS = OFFSET + GAIN * Raw_Waveform_Amplitude)
	uint8_t bitsPerSample;		//!< Number of bits per sample
};

//! Waveform
/** \warning Waveforms do not own their data!
**/
class QCC_DB_LIB_API ccWaveform : public ccSerializableObject
{
public:

	//! Default constructor
	ccWaveform(uint8_t descriptorID = 0);
	
	//! Destructor
	~ccWaveform() override = default;

	//! Returns the associated descriptor (ID)
	/** \warning A value of zero indicates that there is no associated waveform data.
	**/
	inline uint8_t descriptorID() const { return m_descriptorID; }

	//! Sets the associated descriptor (ID)
	inline void setDescriptorID(uint8_t id) { m_descriptorID = id; }

	//! Describes the waveform data
	void setDataDescription(uint64_t dataOffset, uint32_t byteCount);

	//! Returns the (raw) value of a given sample
	uint32_t getRawSample(uint32_t i, const WaveformDescriptor& descriptor, const uint8_t* dataStorage) const;

	//! Returns the (real) value of a given sample (in volts)
	double getSample(uint32_t i, const WaveformDescriptor& descriptor, const uint8_t* dataStorage) const;

	//! Returns the range of (real) samples
	double getRange(double& minVal, double& maxVal, const WaveformDescriptor& descriptor, const uint8_t* dataStorage) const;

	//! Decodes the samples and store them in a vector
	bool decodeSamples(std::vector<double>& values, const WaveformDescriptor& descriptor, const uint8_t* dataStorage) const;

	//! Exports (real) samples to an ASCII file
	bool toASCII(const QString& filename, const WaveformDescriptor& descriptor, const uint8_t* dataStorage) const;

	//! Helper: exports a series of values as an ASCII file
	static bool ToASCII(const QString& filename, std::vector<double>& values, uint32_t samplingRate_ps);

	//! Returns the sample position in 3D
	CCVector3 getSamplePos(float i, const CCVector3& P0, const WaveformDescriptor& descriptor) const;

	//! Returns the number of allocated bytes
	inline uint32_t byteCount() const { return m_byteCount; }

	//! Returns the byte offset to waveform data
	inline uint64_t dataOffset() const { return m_dataOffset; }

	//! Sets the byte offset to waveform data
	inline void setDataOffset(uint64_t offset) { m_dataOffset = offset; }

	//! Gives access to the internal data
	inline const uint8_t* data(const uint8_t* dataStorage) const { return dataStorage + m_dataOffset; }

	//! Sets the beam direction
	inline void setBeamDir(const CCVector3f& dir) { m_beamDir = dir; }

	//! Returns the beam direction
	inline const CCVector3f& beamDir() const { return m_beamDir; }

	//! Set the echo time (in picoseconds)
	inline void setEchoTime_ps(float time_ps) { m_echoTime_ps = time_ps; }

	//! Returns the echo time (in picoseconds)
	inline float echoTime_ps() const { return m_echoTime_ps; }

	//! Applies a rigid transformation (on the beam direction)
	void applyRigidTransformation(const ccGLMatrix& trans);

	//! Returns the return index
	uint8_t returnIndex() const { return m_returnIndex; }
	//! Sets the return index
	void setReturnIndex(uint8_t index) { m_returnIndex = index; }

	//inherited from ccSerializableObject
	bool isSerializable() const override { return true; }
	bool toFile(QFile& out, short dataVersion) const override;
	bool fromFile(QFile& in, short dataVersion, int flags, LoadedIDMap& oldToNewIDMap) override;
	short minimumFileVersion() const override;

protected: //members

	//! Waveform packet size in bytes
	/** \warning Not necessarily equal to the number of samples!
	**/
	uint32_t m_byteCount;

	//! Byte offset to waveform data
	uint64_t m_dataOffset;

	//! Laser beam direction
	/** Parametric line equation for extrapolating points along the associated waveform:
		X = X0 + X(t)
		Y = Y0 + Y(t)
		Z = Z0 + Z(t)
	**/
	CCVector3f m_beamDir;

	//! Return Point location (in picoseconds)
	/** The offset in picoseconds from the first digitized value to the location
		within the waveform packet that the associated return pulse was detected.
	**/
	float m_echoTime_ps;

	//! Wave Packet descriptor index
	/** \warning A value of zero indicates that there is no associated waveform data.
	**/
	uint8_t m_descriptorID;

	//! Return index
	uint8_t m_returnIndex;
};

//! Waveform proxy
/** For easier access to the waveform data
**/
class QCC_DB_LIB_API ccWaveformProxy
{
public:

	//! Default constructor
	ccWaveformProxy(const ccWaveform& w, const WaveformDescriptor& d, const uint8_t* storage)
		: m_w(w)
		, m_d(d)
		, m_storage(storage)
	{}

	//! Returns whether the waveform (proxy) is valid or not
	inline bool isValid() const { return m_storage && m_w.descriptorID() != 0 && m_d.numberOfSamples != 0; }

	//! Returns the associated descriptor (ID)
	/** \warning A value of zero indicates that there is no associated waveform data.
	**/
	inline uint8_t descriptorID() const { return m_w.descriptorID(); }

	//! Returns the (raw) value of a given sample
	inline uint32_t getRawSample(uint32_t i) const { return m_w.getRawSample(i, m_d, m_storage); }

	//! Returns the (real) value of a given sample (in volts)
	inline double getSample(uint32_t i) const { return m_w.getSample(i, m_d, m_storage); }

	//! Returns the range of (real) samples
	inline double getRange(double& minVal, double& maxVal) const { return m_w.getRange(minVal, maxVal, m_d, m_storage); }

	//! Decodes the samples and store them in a vector
	inline bool decodeSamples(std::vector<double>& values) const { return m_w.decodeSamples(values, m_d, m_storage); }

	//! Exports (real) samples to an ASCII file
	inline bool toASCII(const QString& filename) const { return m_w.toASCII(filename, m_d, m_storage); }

	//! Returns the sample position in 3D
	inline CCVector3 getSamplePos(float i, const CCVector3& P0) const { return m_w.getSamplePos(i, P0, m_d); }

	//! Returns the number of allocated bytes
	inline uint32_t byteCount() const { return m_w.byteCount(); }

	//! Gives access to the internal data
	inline const uint8_t* data() const { return m_w.data(m_storage); }

	//! Returns the beam direction
	inline const CCVector3f& beamDir() const { return m_w.beamDir(); }

	//! Returns the echo time (in picoseconds)
	inline float echoTime_ps() const { return m_w.echoTime_ps(); }

	//! Returns the number of samples
	inline uint32_t numberOfSamples() const { return m_d.numberOfSamples; }

	//! Returns the descriptor
	inline const WaveformDescriptor& descriptor() const { return m_d; }
	//! Returns the waveform
	inline const ccWaveform& waveform() const { return m_w; }

protected: //members

	//! Associated ccWaveform instance
	const ccWaveform& m_w;
	//! Associated descriptor
	const WaveformDescriptor& m_d;
	//! Associated storage data
	const uint8_t* m_storage;
};

#endif //CC_WAVEFORM_HEADER
