/////////////////////////////////////////////////////////////////////////////
// File:        mod_ipl_sdl.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Copyright:   (C) 2012 Cesar Mauri Loba - CREA Software Systems
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////
#include "mod_camera/iplimagetype.h"
#include "mod_sdl/sdlsurfacetype.h"
#include "spcore/libimpexp.h"
#include <SDL_rotozoom.h>

#include "creavision/crvimage.h"

using namespace spcore;
using namespace mod_sdl;
using namespace mod_camera;

namespace mod_ipl_sdl {

static int Ipl2SDL_zoomSurface (const CTypeIplImage &src, SmartPtr<CTypeSDLSurface>& dst)
{
	const SDL_VideoInfo* vi= SDL_GetVideoInfo();
	if (!vi) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "SDL_GetVideoInfo call failed", "mod_collage");
		return -1;
	}

	if (src.getImage()->depth!= IPL_DEPTH_8U) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "IPL image format non-supported (depth).", "mod_collage");
		return -1;
	}

	Uint32 rmask, gmask, bmask, amask= 0;
	int nChannels= src.getImage()->nChannels;
	const char* channelSeq= src.getImage()->channelSeq;

	if (nChannels== 2) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "IPL image format non-supported (# channels).", "mod_collage");
		return -1;
	}

	if (nChannels== 1) { rmask= gmask= bmask= 0xff; }				
	else {
		if (channelSeq[0]== 'R' && channelSeq[1]== 'G' && channelSeq[2]== 'B') {
			// RGB channel sequence
			if (nChannels== 4) {
				#if SDL_BYTEORDER == SDL_BIG_ENDIAN
					rmask = 0xff000000;
					gmask = 0x00ff0000;
					bmask = 0x0000ff00;
					amask = 0x000000ff;
				#else
					rmask = 0x000000ff;
					gmask = 0x0000ff00;
					bmask = 0x00ff0000;
					amask = 0xff000000;
				#endif
			}
			else {
				#if SDL_BYTEORDER == SDL_BIG_ENDIAN
					rmask = 0xff0000;
					gmask = 0x00ff00;
					bmask = 0x0000ff;
				#else
					rmask = 0x0000ff;
					gmask = 0x00ff00;
					bmask = 0xff0000;
				#endif
			}
		}
		else {
			// BGR channel sequence
			if (nChannels== 4) {
			#if SDL_BYTEORDER == SDL_BIG_ENDIAN
				rmask = 0x000000ff;
				gmask = 0x0000ff00;
				bmask = 0x00ff0000;
				amask = 0xff000000;
			#else
				rmask = 0xff000000;
				gmask = 0x00ff0000;
				bmask = 0x0000ff00;
				amask = 0x000000ff;
			#endif
			}
			else {
			#if SDL_BYTEORDER == SDL_BIG_ENDIAN
				rmask = 0x0000ff;
				gmask = 0x00ff00;
				bmask = 0xff0000;
			#else
				rmask = 0xff0000;
				gmask = 0x00ff00;
				bmask = 0x0000ff;							
			#endif
			}
		}
	}			

	int depth= nChannels * 8;

	// Create surface from ipl image pixels
	SDL_Surface *orig_surface= 
		SDL_CreateRGBSurfaceFrom(
			src.getImage()->imageData, 
			src.getImage()->width, 
			src.getImage()->height,
			depth, 
			src.getImage()->widthStep,
			rmask, gmask, bmask, amask);

	// Compute zoom ratio
	double zoomx= (double) vi->current_w / (double) src.getImage()->width;
	double zoomy= (double) vi->current_h / (double) src.getImage()->height;
	
	SDL_Surface* result_surface= zoomSurface (orig_surface, zoomx, zoomy, SMOOTHING_OFF);
	
	dst->setSurface(result_surface);
	
	// Free orig_surface
	SDL_FreeSurface(orig_surface);

	return 0;
}

// -2 not-supported error, -1 fatal error
static int Ipl2SDL_opencv (const CTypeIplImage &src, SmartPtr<CTypeSDLSurface>& dst)
{
	const SDL_VideoInfo* vi= SDL_GetVideoInfo();
	if (!vi) {
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, "SDL_GetVideoInfo call failed", "mod_collage");
		return -1;
	}

	//
	// Temporal IPL image to match colorspace
	//
	
	// frame buffer channel sequence
	if (vi->vfmt->BytesPerPixel== 2) return -2; // Not supported.
	int fbOrder;	// false RGB, true BGR
	int fbHasAlpha= 0;
	if (vi->vfmt->BytesPerPixel== 4) fbHasAlpha= 1;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	if (vi->vfmt->Rmask> vi->vfmt->Bmask) fbOrder= 0;
	else fbOrder= 1;
#else		
	if (vi->vfmt->Rmask> vi->vfmt->Bmask) fbOrder= 1;
	else fbOrder= 0;
#endif

	// src image channel order
	int srcOrder;	// false RGB, true BGR
	int srcHasAlpha= 0;
	if (src.getImage()->nChannels== 2) return -2;	// Non-supported
	if (src.getImage()->nChannels== 4) srcHasAlpha= 1;

	if (src.getImage()->channelSeq[0]== 'R' || src.getImage()->channelSeq[0]== 'r') srcOrder= 0;
	else srcOrder= 1;

	// conversion code
	int code= -1;
	if (src.getImage()->nChannels== 1) {
		// source is GRAY
		if (fbOrder) {
			if (fbHasAlpha) code= CV_GRAY2BGRA;
			else code= CV_GRAY2BGR;
		}
		else {
			if (fbHasAlpha) code= CV_GRAY2RGBA;
			else code= CV_GRAY2RGB;
		}
	}
	else {
		// source has colour
		static const int codeArray[]=	// srcOrder, srcHasAlpha, fbOrder, fbHasAlpha
		{
			-1,				// b0000
			CV_RGB2RGBA,	// b0001
			CV_RGB2BGR,		// b0010
			CV_RGB2BGRA,	// b0011
			CV_RGBA2RGB,	// b0100
			-1,				// b0101
			CV_RGBA2BGR,	// b0110
			CV_RGBA2BGRA,	// b0111
			CV_BGR2RGB,		// b1000
			CV_BGR2RGBA,	// b1001
			-1,				// b1010
			CV_BGR2BGRA,	// b1011
			CV_BGRA2RGB,	// b1100
			CV_BGRA2RGBA,	// b1101
			CV_BGRA2BGR,	// b1110
			-1				// b1111
		};
		
		code= codeArray[(srcOrder << 3) | (srcHasAlpha << 2) | (fbOrder << 1) | fbHasAlpha];
	}

	// Create tmp img
	CIplImage* srcImg_tmp= NULL;
	if (code!= -1) {
		const char* fbChannelSeq= NULL;
		if (fbOrder) {
			if (fbHasAlpha) fbChannelSeq= "BGRA";
			else fbChannelSeq= "BGR";
		}
		else {
			if (fbHasAlpha) fbChannelSeq= "RGBA";
			else fbChannelSeq= "RGB";
		}
		srcImg_tmp= new CIplImage (src.getImage()->width, src.getImage()->height, IPL_DEPTH_8U, fbChannelSeq);
		cvCvtColor(src.getImage(), srcImg_tmp->ptr(), code );
	}

	//
	// Temporal IPL image using the SDL pixels (on hardware)
	//

	// Create surface
	SDL_Surface* surface_result= SDL_CreateRGBSurface(
		SDL_HWSURFACE, 
		vi->current_w, vi->current_h, 
		vi->vfmt->BitsPerPixel, 
		vi->vfmt->Rmask, vi->vfmt->Gmask, vi->vfmt->Bmask, vi->vfmt->Amask);

	// Create IPL image with the same properties as the resulting sdl format
	IplImage* img_result= cvCreateImageHeader( cvSize(vi->current_w, vi->current_h), IPL_DEPTH_8U, vi->vfmt->BytesPerPixel );

	// Adjust fields to meet fb format
	for (int i= 0; i< 4; ++i) img_result->channelSeq[i]= src.getImage()->channelSeq[i];
	img_result->widthStep= surface_result->pitch;
	img_result->imageData= static_cast<char*>(surface_result->pixels);	// Point to SDL pixels
	img_result->imageSize= surface_result->pitch * surface_result->h;

	// Resize
	if (SDL_MUSTLOCK(surface_result)) SDL_LockSurface(surface_result);
	//cvConvertScale (src.getImage(), img_result, scale, 0);
	if (code!= -1)
		cvResize(srcImg_tmp->ptr(), img_result, CV_INTER_LINEAR);
	else
		cvResize(src.getImage(), img_result, CV_INTER_LINEAR);
	if (SDL_MUSTLOCK(surface_result)) SDL_UnlockSurface(surface_result);

	// Release resources
	cvReleaseImageHeader(&img_result);
	delete srcImg_tmp;

	// Store result
	dst->setSurface(surface_result);

	return 0;
}




/* ******************************************************************************
	ipl2sdl		Convert IPL image into SDL surfaces

	Input pins:
		in (iplimage): input image

	Output pins:
		out (sdl_surface): output sdl surface

	Command line flags:		
*/

class Ipl2Sdl : public CComponentAdapter
{
public:
	static const char * getTypeName() { return "ipl2sdl"; }
	virtual const char * GetTypeName() const { return Ipl2Sdl::getTypeName(); }

	Ipl2Sdl(const char * name, int argc, const char * argv[])
	: CComponentAdapter(name, argc, argv)
	{
		m_oPinResult= CTypeSDLSurface::CreateOutputPin("out");
		if (m_oPinResult.get()== NULL)
			// Output pin creation failed. Shouldn't happen but...
			throw std::runtime_error("Ipl2Sdl: output pin creation failed.");
		RegisterOutputPin (*m_oPinResult);

		RegisterInputPin (*SmartPtr<InputPinIPL>(new InputPinIPL(*this), false));
		
		m_SDLSurface= CTypeSDLSurface::CreateInstance();
		if (m_SDLSurface.get()== NULL)
			throw std::runtime_error("Ipl2Sdl: cannot create internal instance.");
	}

private:
	SmartPtr<CTypeSDLSurface> m_SDLSurface;
	SmartPtr<spcore::IOutputPin> m_oPinResult;

	// Write pin to send the SDL surface
	class InputPinIPL : public CInputPinWriteOnly<CTypeIplImage, Ipl2Sdl> {
	public:
		InputPinIPL (Ipl2Sdl & component) : CInputPinWriteOnly<CTypeIplImage, Ipl2Sdl>("in", component) {}
		virtual int DoSend(const CTypeIplImage &src) {
			assert (getSpCoreRuntime()->IsMainThread());
			if (!getSpCoreRuntime()->IsMainThread()) {
				getSpCoreRuntime()->LogMessage (ICoreRuntime::LOG_ERROR, "message from non-main thread. ignoring", "ipl2sdl");
				return -1;
			}
			else {
				// First try the fastest function
				int retval= Ipl2SDL_opencv(src, m_component->m_SDLSurface);
				if (retval== -2) 
					// If the previous function failed by a non-fatal error try the slow function
					retval= Ipl2SDL_zoomSurface (src, m_component->m_SDLSurface);
				if (retval) return retval;
				
				return m_component->m_oPinResult->Send (m_component->m_SDLSurface);
			}
		}
	};
};

// camera_viewer component factory
typedef ComponentFactory<Ipl2Sdl> Ipl2SdlFactory;

/* ******************************************************************************
	sdl_base module
*/
class Ipl2SdlModule : public spcore::CModuleAdapter {
public:
	Ipl2SdlModule() {
		RegisterComponentFactory(SmartPtr<spcore::IComponentFactory>(new Ipl2SdlFactory(), false));
	}
	virtual const char * GetName() const { return "mod_ipl_sdl"; }
};

static spcore::IModule* g_module= NULL;
SPEXPORT_FUNCTION spcore::IModule* module_create_instance()
{
	if (g_module== NULL) g_module= new Ipl2SdlModule();
	return g_module;
}

};
