/*
 * Copyright 2011-2019, European Organisation for the Exploitation of Meteorological Satellites (EUMETSAT)
 *
 * 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.
 */

#ifndef CImage_included
#define CImage_included

/*******************************************************************************

TYPE:
Concrete class

PURPOSE: 
Provide support for image-based data manipulation.

FUNCTION:	
An image is a data buffer of a non trivial (i.e. architecture dependent) datatype 
(in this case short) together with size information. The functions supported are
requantization, point-transform and packing, together with more basic access functions.


INTERFACES:	
See 'INTERFACES' in the module declaration below 
      
RESOURCES:	
Heap Memory (>2K).
      
REFERENCE:	
None.

PROCESSING:	
The constructor takes either a CDataFieldUncompressed buffer or a size information
as input. The necessary memory (image buffer) is then allocated. In the case a 
CDataFieldUncompressed buffer was passed, the imagebuffer is filled with the data
present in the CDataField, eventually unpacked.
The functions put_block() and get_block() respectively insert/extract a 8x8 pixel
block from the image at the specified location. Ihe private function pad_block()
is used in order to support put/get operations across the edge of the image (in case
the dimension of the image wouldn't be a multiple of 8).
The member functions Forward_point_transform() and Inverse_point_transform() perform 
the "point-transform" (shifting of the image data value).
The member function Pack() returns the current image, packed according to the specified
parameter in a CDataFieldUncompressedImage;
The member function Requantize() performs data requantization as specified in the MSI. The
m_NB data member is modified accordingly.
Various miscelaneous functions (operator()(), GetNext(), SetNext()) allow access
to individual pixel values. ResetState() performs a reset of the internal
position counters used by {Get|Set}Next().

DATA:
None			
      
LOGIC:
      
*******************************************************************************/

#include <vector>

#include "CDataField.h"			// DISE
#include "CBlock.h"				// COMP
#include "RMAErrorHandling.h"	// COMP
#include "Compress.h"			// COMP


namespace COMP
{


class CImage
{

private:

//DATA:

	//	Object variables
	std::vector<unsigned short>  m_data;	//	Pointer to the raw image data buffer.
	std::vector<unsigned short*> m_dataptr;	// array of pointers to each line of the block
											// m_dataptr[0] = m_data
											// m_dataptr[1] = m_data + m_NC
											// m_dataptr[2] = m_data + 2 * m_NC
											// ...
											// m_dataptr[n] = m_data + n * m_NC
											// ...
											// m_dataptr[m_NL-1] = m_data + (m_NL - 1) * m_NC
	unsigned short                  m_NL;   //  Number of lines in the image
	unsigned short                  m_NC;   //  Number of columns in the image
	unsigned short                  m_NB;   //  Max number of valid bits of the current data
	unsigned long					m_size; //  Number of pixels in the image.
	//	State variables: used to store object state information
	unsigned long	m_index;				//	Linear position in the buffer
	unsigned short	m_cur_L;				//	Current line position
	unsigned short	m_cur_C;				//	Current column position in the buffer
	unsigned short  m_cur_RI;				//	Current Restart Interval index
	unsigned short	m_RI;					//  Current Restart interval length (in pixels)

//INTERFACE:

	// Description:	Performs the block padding for an incomplete block.
	// Returns:		Nothing.
	void pad_block
	(
		CBlock<unsigned short>&	t_blk,		// block to be padded
		const unsigned short&	i_goodCol,	// # of valid columns
		const unsigned short&	i_goodLine	// # of valid lines
	)
	const
	{
		COMP_TRYTHIS
		// Pads missing pixels by copying last column, last line
		// 1° Copying columns
		unsigned int tmp = i_goodCol - 1;
		for (unsigned int j=i_goodCol ; j<8 ; j++)
			for (unsigned int i=0 ; i<i_goodLine ; i++) t_blk.Cset (i, j, t_blk.Cget (i, tmp));
		
		// 2° Copying lines
		tmp = i_goodLine - 1;
		for (unsigned int i=i_goodLine ; i<8 ; i++)
			for (unsigned int j=0 ; j<8 ; j++) t_blk.Cset (i, j, t_blk.Cget (tmp, j));
		COMP_CATCHTHIS
	}

public:

	// Description:	Creates a CImage from size information.
	//				This is also the default constructor.
	// Returns:		Nothing.
	CImage
	(
		unsigned short i_NC=0,	// Number of columns of the image to create
		unsigned short i_NL=0,	// Number of lines of the image to create
		unsigned short i_NB=16	// "Depth" of the image to create (is used in the pack() member)
	);

	// Description:	Creates a CImage from the CDataFieldUncompressedImage by unpacking it.
	// Returns:		Nothing.
	CImage
	(
		const Util::CDataFieldUncompressedImage& i_cdfui	// buffer containing the image data.
															// May be packed.
	);
	
	// Description:	Destructor
	// Returns:		Nothing.
	~CImage()
	{
	}

	// Description:	Recreates a CImage from size information. 
	//				Existing information is discarted.
	// Returns:		Nothing.
	void Resize
	(
		unsigned short i_NC,	// Number of columns of the image to create
		unsigned short i_NL,	// Number of lines of the image to create
		unsigned short i_NB=16	// "Depth" of the image to create (is used in the pack() member)
	);

	// Description:	Place the given pixel 8x8 block at the specified position.
	// Returns:		Nothing.
	void put_block
	(
		CBlock<unsigned short>&	i_blk,		// pixel block to insert
		unsigned short			i_colLeft,	// position of the upper left pixel of the block
		unsigned short			i_lineUp	// "
	)
	{
		COMP_TRYTHIS
		// Puts a 8*8 pixels block into the buffer at the specified position
		unsigned int bStep, iStep;
		unsigned int stop_line = i_lineUp + 8;
		unsigned int stop_column = i_colLeft + 8;
		
#ifdef DEBUG
		Assert (i_lineUp < m_NL, Util::CParamException ());
		Assert (i_colLeft < m_NC, Util::CParamException ());
#endif

		// check for image limits
		if (stop_line > m_NL) stop_line = m_NL;
		if (stop_column > m_NC)
		{
			stop_column = m_NC;
			bStep = 8 - stop_column + i_colLeft;
			iStep = m_NC - 8 + bStep;
		}
		else
		{
			bStep = 0;
			iStep = m_NC - 8;
		}

		unsigned int maxval = (1 << m_NB) -1;
		unsigned short val = 0;

		// copy the block into the buffer
		unsigned int bIndex = 0;
		unsigned long iIndex = (unsigned long)i_lineUp * m_NC + i_colLeft;
		for (unsigned int i=i_lineUp ; i<stop_line ; i++, bIndex+=bStep, iIndex+=iStep)
			for (unsigned int j=i_colLeft ; j<stop_column ; j++)
				//m_data[iIndex++] = i_blk.Cget (bIndex++);
				m_data[iIndex++] = (( val = i_blk.Cget( bIndex++ )) > maxval )? maxval: val;
		COMP_CATCHTHIS
	}

	// Description:	Extract a pixel block at the specified position
	// Returns:		Nothing.
	void get_block
	(
		CBlock<unsigned short>&	o_blk,		// 
		unsigned short			i_colLeft,	// position of the upper left pixel of the block
		unsigned short			i_lineUp	// "
	)
	const
	{
		COMP_TRYTHIS
		// Takes a 8*8 pixels block into the buffer at the specified position,
		// and pads it if needed.

		unsigned int bStep, iStep;
		unsigned int stop_line = i_lineUp + 8;
		unsigned int stop_column = i_colLeft + 8;
		unsigned int real_lines = 8;		// number of correct lines
		unsigned int real_columns = 8;	// number of correct columns
		bool padding = false;

#ifdef DEBUG
		Assert (i_lineUp < m_NL, Util::CParamException ());
		Assert (i_colLeft < m_NC, Util::CParamException ());
#endif

		// Test for image boundaries
		if (stop_line > m_NL)
		{
			padding = true;
			stop_line = m_NL;
			real_lines = stop_line - i_lineUp;
		}
		if (stop_column > m_NC)
		{
			padding = true;
			stop_column = m_NC;
			real_columns = stop_column - i_colLeft;
			bStep = 8 - real_columns;
			iStep = m_NC - 8 + bStep;
		}
		else
		{
			bStep = 0;
			iStep = m_NC - 8;
		}
		
		// 1° Take real pixels

		unsigned int bIndex = 0;
		unsigned long iIndex = (unsigned long)i_lineUp * m_NC + i_colLeft;
		for (unsigned int i=i_lineUp ; i<stop_line ; i++, bIndex+=bStep, iIndex+=iStep)
			for (unsigned int j=i_colLeft ; j<stop_column ; j++)
				o_blk.Cset (bIndex++, m_data[iIndex++]);

		// 2° Padding, if needed
		if (padding) pad_block (o_blk, real_columns, real_lines);
		COMP_CATCHTHIS
	}

	// Description:	Performs the forward point transform on the data
	//				as specified in the JPEG norm.
	// Returns:		Nothing.
	void Forward_point_transform
	(
		unsigned short i_pt	// point transform
	);

	// Description:	Performs the inverse point transform on the data
	//				as specified in the JPEG norm
	// Returns:		Nothing.
	void Inverse_point_transform
	(
		unsigned short i_pt	// point transform
	);

	// Description: Packs an uncompressed image, from 16 bits/pixel
	//				to the specified representation.
	// Returns:		The packed uncompressed image.
	Util::CDataFieldUncompressedImage pack
	(
		unsigned short i_NR	// number of representation bits.
	);

	// Description:	Perform data requantization (12|10 bit-> 8bit) as specified in the MSI.
	//				Note that this function is supposed to also modify the m_NB to reflect
	//				the change.
	// Returns:		Nothing.
	void Requantize
	(
		COMP::ERequantizationMode i_mode	// specifies the chosen requantization mode.
	);

	// Description:	Returns a reference to the specified pixel value.
	// Returns:		The pixel value at the specified location.
	unsigned short& operator()
	(
		const unsigned short& i_i,	// line number of the pixel value to return
		const unsigned short& i_j	// column number of the pixel value to return
	)
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG
		Assert (((unsigned long)i_i * m_NC +  i_j) < m_size, Util::CParamException ());
#endif
		return m_dataptr[i_i][i_j];
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Returns the value of the specified pixel.
	// Returns:		The pixel value at the specified location.
	unsigned short Cget
	(
		const unsigned short& i_i,	// line number of the pixel value to return
		const unsigned short& i_j	// column number of the pixel value to return
	)
	const
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG
		Assert (((unsigned long)i_i * m_NC +  i_j) < m_size, Util::CParamException ());
#endif
		return m_dataptr[i_i][i_j];
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Set the value of the specified pixel.
	// Returns:		Nothing.
	void Cset
	(
		const unsigned short& i_i,	// line number of the pixel value to return
		const unsigned short& i_j,	// column number of the pixel value to return
		const unsigned short& i_val	// the pixel value
	)
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG
		Assert (((unsigned long)i_i * m_NC +  i_j) < m_size, Util::CParamException ());
#endif
		m_dataptr[i_i][i_j] = i_val;
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Returns a reference to the specified pixel value.
	// Returns:		The pixel value at the specified location.
	unsigned short& operator[]
	(
		const unsigned long& i_i	// linear pixel index to return
	)
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG
		Assert (i_i < m_size, Util::CParamException ());
#endif
		return m_data[i_i];
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Returns the value of the specified pixel.
	// Returns:		The pixel value at the specified location.
	unsigned short Cget
	(
		const unsigned long& i_i	// the linear pixel index
	)
	const
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG
		Assert (i_i < m_size, Util::CParamException ());
#endif
		return m_data[i_i];
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Set the value of the specified pixel
	// Returns:		Nothing.
	void Cset
	(
		const unsigned long& i_i,	// the linear pixel index
		const unsigned short& i_val	// the pixel value
	)
	{
		COMP_TRYTHIS_SPEED
#ifdef _DEBUG
		Assert (i_i < m_size, Util::CParamException ());
#endif
		m_data[i_i] = i_val;
		COMP_CATCHTHIS_SPEED
	}

	// Description:	Put the given value at the current index position and increment
	//				the current index position. Index position are counted in a linear
	//				manner in the image buffer
	// Returns:		Nothing.
	void SetNext
	(
		const unsigned short& i_v	// new value of the pixel.
	)
	{
		COMP_TRYTHIS
#ifdef _DEBUG
		Assert (m_index < m_size , COMP::COutOfBufferException ());
#endif
		m_data[m_index++] = i_v;
		COMP_CATCHTHIS
	}

	// Description:	Returns the pixel value at the current index position and increment
	//				the current index position.
	// Returns:		The specified pixel value
	unsigned short GetNext()
	{
		COMP_TRYTHIS
#ifdef _DEBUG
		Assert (m_index < m_size, COMP::COutOfBufferException ());
#endif
		return m_data[m_index++];
		COMP_CATCHTHIS
	}

	// Description:	Reset the state information (m_index, m_cur_*, ...) to the initial
	//				buffer position.
	// Returns:		Nothing.
	void ResetState ();
	
	// Description:	Returns the image height.
	// Returns:		The number of lines in the image.
	unsigned short GetH() const { return m_NL; }

	// Description:	Returns the image width.
	// Returns:		The number of columns in the image.
	unsigned short GetW() const { return m_NC; }

	// Description:	Returns the image depth.
	// Returns:		The number of used bits per pixel.
	unsigned short GetNB() const { return m_NB; }

	// Description:	Similar to std::auto_ptr::size().
	// Returns:		The number of pixels in the image.
	unsigned long GetSize() const { return m_size; }

	// Description:	Similar to std::auto_ptr::get().
	// Returns:		A pointer to the raw data.
	unsigned short* Get() { return &m_data[0]; }

	// Description:	Gets the array of pointers to lines.
	// Returns:		A pointer to the array of line pointers.
	unsigned short** GetP() { return &m_dataptr[0]; }

	// Description:	Set the pixels of the lines from line # i_from to line  
	//				# i_to included to 0.
	//				The current index position is updated to point at the first
	//				pixel following the last pixel reset.
	// Returns:		Nothing.
	void Zero
	(
		const unsigned short& i_from,	// first line to reset
		const unsigned short& i_to		// last line to reset
	)
	{
		COMP_TRYTHIS
		const unsigned long x_from = i_from * m_NC;
		const unsigned long x_to   = i_to * m_NC;
#ifdef _DEBUG
		if (x_from != m_index) std::cerr << "!!! x_from != m_index\n";
		Assert (x_from == m_index, Util::CParamException ());
		Assert (x_to <= m_size, Util::CParamException ());
#endif
		for (unsigned long i=x_from ; i<x_to ; i++) m_data[i] = 0;
		m_index = x_to;
		COMP_CATCHTHIS
	}

	// Description:	Set the pixels between the current index and the specified index to 0.
	//				The current index position is updated.
	// Returns:		Nothing.
	void ZeroPixels
	(
		const unsigned long &i_from,	// index of the first pixel to reset.
		const unsigned long &i_to		// index of the last pixel to reset.
	)
	{
		COMP_TRYTHIS
#ifdef _DEBUG
		Assert (i_from <= i_to && i_to < m_size, Util::CParamException ());
#endif
		for (m_index=i_from ; m_index<=i_to ;) m_data[m_index++] = 0;
		COMP_CATCHTHIS
	}

};


} // end namespace


#endif
