#pragma once

#include <cassert>


namespace lgr
{


//! OpenCVの画像データのラッパ
class CvImage
{
public:
	CvImage(void)
		: m_Image( NULL )
	{}

	CvImage( const CvImage& i_img )
	{
		if( i_img.GetImage() != NULL )
		{
			const IplImage* ipl_img = i_img.GetImage();
			m_Image = cvCreateImage( cvSize( ipl_img->width , ipl_img->height ) , ipl_img->depth , ipl_img->nChannels );
			cvCopyImage( ipl_img , m_Image );
		}
		else
		{
			m_Image = NULL;
		}
	}


	virtual ~CvImage(void)
	{
		Clear();
	}


	virtual const CvImage& operator=( const CvImage& i_img )
	{
		if( i_img.GetImage() != NULL )
			CopyImage( i_img.GetImage() );
		else
			Clear();

		return *this;
	}

	//! 作業対象バッファの指定. 指定されたバッファの所有権を受け取る
	void AssignImage( IplImage* i_img );

	//! i_imgをコピーする
	void CopyImage( const IplImage* i_img );

	void Clear(void);

	void CreateImage( int width , int height , int depth = IPL_DEPTH_8U , int channels = 3 );
	void CreateImage( int width , int height , const CvScalar& fill_color , int depth = IPL_DEPTH_8U , int channels = 3 );

	bool LoadImageFile( const char* filename , bool gen_alpha = false );
	bool SaveImageFile( const char* filename ) const;

	bool SetAlphaMask( const IplImage* i_img );
	bool SetAlphaMask( const char* filename );

	//! 画像オブジェクトが設定されているか確かめる.
	bool HasImage(void) const { return ( m_Image != NULL ); }

	// IplImage型の画像を取得する
	IplImage*       GetImage(void)       { return m_Image; }
	const IplImage* GetImage(void) const { return m_Image; }

	// ピクセルの値を設定, 取得する
	void SetColor( int x , int y , const CvScalar& col );
	CvScalar GetColor( int x , int y ) const;

	// サイズ取得
	int GetHeight(void) const;
	int GetWidth(void) const;

	int GetNumChannels(void) const;

	float* GetPixelf(int x, int y);
	const float* GetPixelf(int x, int y) const;

	// 左右反転, 上下反転
	void Filp_X(void);
	void Filp_Y(void);
	void Filp_XY(void);


protected:
	IplImage* m_Image;
};



// ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


//! 作業対象バッファの指定. 指定されたバッファの所有権を受け取る
inline void CvImage::AssignImage( IplImage* i_img )
{
	if( m_Image == i_img )
		return;

	Clear();
	m_Image = i_img;
}

//! i_imgをコピーする
inline void CvImage::CopyImage( const IplImage* i_img )
{
	Clear();

	CreateImage( i_img->width , i_img->height , i_img->depth , i_img->nChannels );

	cvCopyImage( i_img , m_Image );
}

//! 画像を生成する.
inline void CvImage::CreateImage( int width , int height , int depth , int channels )
{
	Clear();

	m_Image = cvCreateImage( cvSize(width,height), depth , channels );
}

//! 画像を生成する.
inline void CvImage::CreateImage( int width , int height , const CvScalar& fill_color , int depth , int channels )
{
	Clear();

	m_Image = cvCreateImage( cvSize(width,height), depth , channels );
	cvSet( m_Image , fill_color );
}

//! 画像をクリアする.
inline void CvImage::Clear(void)
{
	if( m_Image != NULL )
		cvReleaseImage( &m_Image );
}


//! ファイルから画像を読み込む.
//! @param[in] filename  - ファイル名
//! @param[in] gen_alpha - falseなら( RGB x byte ) , trueなら( RGBA x byte ) のバッファの画像として読み込む.
inline bool CvImage::LoadImageFile( const char* filename , bool gen_alpha )
{
	Clear();

	if( gen_alpha )
	{
		IplImage* img_src = cvLoadImage( filename );
		if( img_src == NULL )
			return false;

		m_Image = cvCreateImage( cvSize( img_src->width , img_src->height ) , img_src->depth , 4 );
		for( int x = 0 ; x < img_src->width ; ++x )
		{
			for( int y = 0 ; y < img_src->height ; ++y )
			{
				CvScalar c = cvGet2D( img_src , y , x );
				c.val[3] = 255;
				cvSet2D( m_Image , y , x , c );
			}
		}

		cvReleaseImage( &img_src );
	}
	else
	{
		m_Image = cvLoadImage( filename );
		if( m_Image == NULL )
			return false;
	}

	return true;
}

//! 画像をファイルに保存する.
//! @param[in] filename - ファイル名
inline bool CvImage::SaveImageFile( const char* filename ) const
{
	int ret_value = cvSaveImage( filename , m_Image );
	return ret_value == 1;
}


//! 指定された画像の輝度値をアルファ成分にセットする.
//! m_Imageにアルファ成分がなければ, バッファを再生成する.
inline bool CvImage::SetAlphaMask( const IplImage* i_img )
{
	// アルファ成分があるか確認
	_ASSERTE( m_Image != NULL );
	if( m_Image == NULL )
		return false;

	// アルファチャンネルが無ければ生成する.
	if( m_Image->nChannels < 4 )
	{
		IplImage* new_image = cvCreateImage( cvSize( m_Image->width , m_Image->height ) , m_Image->depth , 4 );

		for( int x = 0 ; x < m_Image->width ; ++x )
		{
			for( int y = 0 ; y < m_Image->height ; ++y )
			{
				CvScalar c = cvGet2D( m_Image , y , x );
				c.val[3] = 255;
				cvSet2D( new_image , y , x , c );
			}
		}

		cvReleaseImage( &m_Image );
		std::swap( new_image , m_Image );
	}

	// m_Imageとサイズが一致していなければリサイズ.
	IplImage* resized_image = NULL;
	if( m_Image->width != i_img->width || m_Image->height != i_img->height )
	{
		resized_image = cvCreateImage( cvSize( m_Image->width , m_Image->height ) , i_img->depth , i_img->nChannels );
		cvResize( i_img , resized_image , CV_INTER_CUBIC );
	}

	const IplImage* src_image = i_img;
	if( resized_image != NULL ) src_image = resized_image;

	for( int x = 0 ; x < src_image->width ; ++x )
	{
		for( int y = 0 ; y < src_image->height ; ++y )
		{
			CvScalar col_src = cvGet2D( src_image , y , x );
			CvScalar col_dst = cvGet2D( m_Image , y , x );

			col_dst.val[3] = col_src.val[0];  // 先頭要素の輝度を利用する.

			cvSet2D( m_Image , y , x , col_dst );
		}
	}

	if( resized_image != NULL )
		cvReleaseImage( &resized_image );

	return false;
}


//! 指定された画像ファイルの輝度値をアルファ成分にセットする.
inline bool CvImage::SetAlphaMask( const char* filename )
{
	// アルファ成分があるか確認
	_ASSERT( m_Image != NULL );
	if( m_Image == NULL )
		return false;

	IplImage* img = cvLoadImage( filename );
	_ASSERT( img != NULL );
	if( img == NULL )
		return false;

	bool bSuccessed = SetAlphaMask( img );

	cvReleaseImage( &img );

	return bSuccessed;
}


inline void CvImage::SetColor( int x , int y , const CvScalar& col )
{
	cvSet2D( GetImage() , y , x , col );
}

inline CvScalar CvImage::GetColor( int x , int y ) const
{
	return cvGet2D( GetImage() , y , x );
}


// サイズ取得
inline int CvImage::GetHeight(void) const
{
	assert( m_Image != NULL );
	if( m_Image == NULL )
		return -1;

	return m_Image->height;
}

inline int CvImage::GetWidth(void) const
{
	assert( m_Image != NULL );
	if( m_Image == NULL )
		return -1;

	return m_Image->width;
}

inline int CvImage::GetNumChannels(void) const
{
	assert( m_Image != NULL );
	if( m_Image == NULL )
		return -1;

	return m_Image->nChannels;
}


inline float* CvImage::GetPixelf(int x, int y)
{
	assert( m_Image != NULL );

	int w = GetWidth();
	int c = GetNumChannels();
	int pidx = ( w * y + x ) * c;

	float* buf = reinterpret_cast<float*>( m_Image->imageData );

	return &buf[ pidx ];
}

inline const float* CvImage::GetPixelf(int x, int y) const
{
	assert( m_Image != NULL );

	int w = GetWidth();
	int c = GetNumChannels();
	int pidx = ( w * y + x ) * c;

	const float* buf = reinterpret_cast<const float*>( m_Image->imageData );

	return &buf[ pidx ];
}


//! 左右反転
inline void CvImage::Filp_X(void)
{
	cvFlip( m_Image , NULL , 1 );
}

//! 上下反転
inline void CvImage::Filp_Y(void)
{
	cvFlip( m_Image , NULL , 0 );
}

//! 左右反転, 上下反転
inline void CvImage::Filp_XY(void)
{
	cvFlip( m_Image , NULL , -1 );
}


}
