/* rbSFML - Copyright (c) 2010 Henrik Valter Vogelius Hansson - groogy@groogy.se
 * This software is provided 'as-is', without any express or
 * implied warranty. In no event will the authors be held
 * liable for any damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute
 * it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented;
 *    you must not claim that you wrote the original software.
 *    If you use this software in a product, an acknowledgment
 *    in the product documentation would be appreciated but
 *    is not required.
 *
 * 2. Altered source versions must be plainly marked as such,
 *    and must not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any
 *    source distribution.
 */
 
#include "RenderImage.hpp"
#include "main.hpp"
#include <SFML/Graphics/RenderImage.hpp>

VALUE globalRenderImageClass;

/* External classes */
extern VALUE globalRenderTargetModule;
extern VALUE globalImageClass;
extern VALUE globalDrawableModule;
extern VALUE globalShaderClass;
extern VALUE globalViewClass;

static void RenderImage_Free( sf::RenderImage *anObject )
{
	delete anObject;
}

static void View_Free( sf::View *anObject )
{
	delete anObject;
}

/* call-seq:
 *   render_image.create( width, height, depthBuffer = false )	-> true or false
 *
 * Create the render-image.
 *
 * Before calling this function, the render-image is in an invalid state, thus it is mandatory to call it before 
 * doing anything with the render-image. The last parameter, depthBuffer, is useful if you want to use the render-image
 * for 3D OpenGL rendering that requires a depth-buffer. Otherwise it is unnecessary, and you should leave this 
 * parameter to false (which is its default value).
 */
static VALUE RenderImage_Create( int argc, VALUE *args, VALUE self )
{
	unsigned int width = 0;
	unsigned int height = 0;
	bool depthBuffer = false;
	switch( argc )
	{
		case 3:
			if( args[2] == Qtrue )
			{
				depthBuffer = true;
			}
			else if( args[2] == Qfalse )
			{
				depthBuffer = false;
			}
			else
			{
				VALIDATE_CLASS( args[2], rb_cTrueClass, "depthBuffer" );
			}
		case 2:
			width  = FIX2UINT( args[0] );
			height = FIX2UINT( args[1] );
			break;
		default:
			rb_raise( rb_eArgError, "Expected 2 or 3 arguments but was given %d", argc );
	}
	
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	if( object->Create( width, height, depthBuffer ) == true )
	{
		return Qtrue;
	}
	else
	{
		return Qfalse;
	}
}

static VALUE RenderImage_Draw( int argc, VALUE *args, VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	switch( argc )
	{
		case 2:
		{
			VALIDATE_CLASS( args[0], globalDrawableModule, "object" );
			VALIDATE_CLASS( args[1], globalShaderClass, "shader" );
			sf::Drawable *drawable = NULL;
			Data_Get_Struct( args[0], sf::Drawable, drawable );
			sf::Shader *shader = NULL;
			Data_Get_Struct( args[1], sf::Shader, shader );
			object->Draw( *drawable, *shader );
			break;
		}
		case 1:
		{
			VALIDATE_CLASS( args[0], globalDrawableModule, "object" );
			sf::Drawable *drawable = NULL;
			Data_Get_Struct( args[0], sf::Drawable, drawable );
			object->Draw( *drawable );
			break;
		}
		default:
			rb_raise( rb_eArgError, "Expected 1 or 2 arguments but was given %d", argc );
	}
	return Qnil;
}

/* call-seq:
 *   render_image.display()
 *
 * Update the contents of the target image.
 *
 * This function updates the target image with what has been drawn so far. Like for windows, calling this function is
 * mandatory at the end of rendering. Not calling it may leave the image in an undefined state. 
 */
static VALUE RenderImage_Display( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	object->Display();
	return Qnil;
}

/* call-seq:
 *   render_image.getImage()	-> image
 *
 * Get a read-only reference to the target image.
 *
 * After drawing to the render-image and calling display, you can retrieve the updated image using this function, and 
 * draw it using a sprite (for example). The internal SFML::Image of a render-image is always the same instance, so that
 * it is possible to call this function once and keep a reference to the image even after is it modified.
 */
static VALUE RenderImage_GetImage( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	const sf::Image &image = object->GetImage();
	VALUE rbData = Data_Wrap_Struct( globalImageClass, 0, 0, const_cast< sf::Image * >( &image ) );
	rb_obj_call_init( rbData, 0, 0 );
	rb_iv_set( rbData, "@__owner_ref", self );
	return rbData;
}

/* call-seq:
 *   render_image.isSmooth()	-> true or false
 *
 * Tell whether the smooth filtering is enabled or not. 
 */
static VALUE RenderImage_IsSmooth( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	return ( object->IsSmooth() == true ? Qtrue : Qfalse );
}

/* call-seq:
 *   render_image.setActive( active )
 *
 * Activate of deactivate the render-image for rendering.
 *
 * This function makes the render-image's context current for future OpenGL rendering operations (so you shouldn't 
 * care about it if you're not doing direct OpenGL stuff). Only one context can be current on a thread, so if you want
 * to draw OpenGL geometry to another render target (like a RenderWindow) don't forget to activate it again.
 */
static VALUE RenderImage_SetActive( int argc, VALUE *args, VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	bool flag = true;
	switch( argc )
	{
		case 1:
			if( args[0] == Qtrue )
			{
				flag = true;
			}
			else if( args[0] == Qfalse )
			{
				flag = false;
			}
			else
			{
				VALIDATE_CLASS( args[0], rb_cTrueClass, "active" );
			}
		case 0:
			break;
		default:
			rb_raise( rb_eArgError, "Expected 0 or 1 arguments but was given %d", argc );	
	}
	object->SetActive( flag );
	return Qnil;
}

/* call-seq:
 *   render_image.setSmooth( smooth )
 *
 * Enable or disable image smoothing.
 *
 * This function is similar to SFML::Image#setSmooth. This parameter is enabled by default.
 */
static VALUE RenderImage_SetSmooth( VALUE self, VALUE aSmoothFlag )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	if( aSmoothFlag == Qtrue )
	{
		object->SetSmooth( true );
	}
	else if( aSmoothFlag == Qfalse )
	{
		object->SetSmooth( false );
	}
	else
	{
		VALIDATE_CLASS( aSmoothFlag, rb_cTrueClass, "smooth" );
	}
	return Qnil;
}

static VALUE RenderImage_SetView( VALUE self, VALUE aView )
{
	VALIDATE_CLASS( aView, globalViewClass, "view" );
	sf::View *view = NULL;
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	Data_Get_Struct( aView, sf::View, view );
	object->SetView( *view );
	return Qnil;
}

static VALUE RenderImage_GetView( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	const sf::View &original = object->GetView();
	sf::View * view = new sf::View( original );
	VALUE rbData = Data_Wrap_Struct( globalViewClass, 0, View_Free, view );
	rb_obj_call_init( rbData, 0, 0 );
	return rbData;
}

static VALUE RenderImage_GetDefaultView( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	const sf::View &original = object->GetDefaultView();
	sf::View * view = new sf::View( original );
	VALUE rbData = Data_Wrap_Struct( globalViewClass, 0, View_Free, view );
	rb_obj_call_init( rbData, 0, 0 );
	return rbData;
}

static VALUE RenderImage_GetWidth( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	return INT2FIX( object->GetWidth() );
}

static VALUE RenderImage_GetHeight( VALUE self )
{
	sf::RenderImage *object = NULL;
	Data_Get_Struct( self, sf::RenderImage, object );
	return INT2FIX( object->GetHeight() );
}

/* call-seq:
 *   RenderImage.new()										-> render_image
 *   RenderImage.new( width, height, depthBuffer = false )	-> render_image
 *
 * Will create a new render image instance.
 * 
 * If any arguments are specified then a call to the #create method will be made passing the arguments to it.
 */
static VALUE RenderImage_Initialize( int argc, VALUE *args, VALUE self )
{
	if( argc > 0 )
	{
		rb_funcall2( self, rb_intern( "create" ), argc, args );
	}
	return self;
}

/* call-seq:
 *   RenderImage.new()	-> render_image
 *
 * Constructs an empty, invalid render-image. You must call create() to have a valid render-image.
 */
static VALUE RenderImage_New( int argc, VALUE *args, VALUE aKlass )
{
	sf::RenderImage *object = new sf::RenderImage();
	VALUE rbData = Data_Wrap_Struct( aKlass, 0, RenderImage_Free, object );
	rb_obj_call_init( rbData, argc, args );
	return rbData;
}

/* call-seq:
 *   RenderImage.isAvailable()	-> true or false
 *
 * Check whether the system supports render images or not.
 *
 * It is very important to always call this function before trying to use the RenderImage class, as the feature may 
 * not be supported on all platforms (especially very old ones). If this function returns false, then you won't be 
 * able to use the class at all.
 */
static VALUE RenderImage_IsAvailable( VALUE aKlass )
{
	return ( sf::RenderImage::IsAvailable() == true ? Qtrue : Qfalse );
}

void Init_RenderImage( void )
{
/* SFML namespace which contains the classes of this module. */
	VALUE sfml = rb_define_module( "SFML" );
/* Target for off-screen 2D rendering into an image.
 *
 * sf::RenderImage is the little brother of sf::RenderWindow.
 *
 * It implements the same 2D drawing and OpenGL-related functions (see their base class sf::RenderTarget for 
 * more details), the difference is that the result is stored in an off-screen image rather than being show in a window.
 *
 * Rendering to an image can be useful in a variety of situations:
 *
 *   - precomputing a complex static image (like a level's background from multiple tiles)
 *   - applying post-effects to the whole scene with shaders
 *   - creating a sprite from a 3D object rendered with OpenGL
 *   - etc.
 *
 * Usage example:
 *
 *   # First of all: make sure that rendering to image is supported
 *   if SFML::RenderImage.available? == false
 *     # Handle error
 *   end
 *
 *   # Create a new render-window
 *   window = SFML::RenderWindow.new( SFML::VideoMode.new(800, 600), "SFML window" )
 *
 *   # Create a new render-image
 *   image = SFML::RenderImage.new
 *   if image.create( 500, 500 ) == false
 *     # Handle error
 *   end
 *
 *   # The main loop
 *   while window.open?
 *     # Event processing
 *     # ...
 *
 *     # Clear the whole image with red color
 *     image.clear( SFML::Color::Red )
 *
 *     # Draw stuff to the image
 *     image.draw( sprite )  # sprite is a SFML::Sprite
 *     image.draw( shape )   # shape is a SFML::Shape
 *     image.draw( text )    # text is a SFML::Text
 *
 *     # We're done drawing to the image
 *     image.display()
 *
 *     # Now we start rendering to the window, clear it first
 *     window.clear()
 *
 *     # Draw the image
 *     sprite = SFML::Sprite.new( image.getImage() )
 *     window.draw( sprite )
 *
 *     # End the current frame and display its contents on screen
 *     window.display()
 *   end
 *
 * Like SFML::RenderWindow, SFML::RenderImage is still able to render direct OpenGL stuff. It is even possible to mix
 * together OpenGL calls and regular SFML drawing commands. If you need a depth buffer for 3D rendering, don't 
 * forget to request it when calling SFML::RenderImage#create.
 */
 	globalRenderImageClass	= rb_define_class_under( sfml, "RenderImage", rb_cObject );
	rb_include_module( globalRenderImageClass, globalRenderTargetModule );
	
	// Class methods
	rb_define_singleton_method( globalRenderImageClass, "new", RenderImage_New, 0 );
	rb_define_singleton_method( globalRenderImageClass, "isAvailable", RenderImage_IsAvailable, 0 );
	
	// Instance methods
	rb_define_method( globalRenderImageClass, "initialize", RenderImage_Initialize, -1 );
	rb_define_method( globalRenderImageClass, "draw", RenderImage_Create, -1 );
	rb_define_method( globalRenderImageClass, "create", RenderImage_Create, -1 );
	rb_define_method( globalRenderImageClass, "display", RenderImage_Display, 0 );
	rb_define_method( globalRenderImageClass, "getImage", RenderImage_GetImage, 0 );
	rb_define_method( globalRenderImageClass, "isSmooth", RenderImage_IsSmooth, 0 );
	rb_define_method( globalRenderImageClass, "setActive", RenderImage_SetActive, -1 );
	rb_define_method( globalRenderImageClass, "setSmooth", RenderImage_SetSmooth, 1 );
	rb_define_method( globalRenderImageClass, "getView", RenderImage_GetView, 0 );
	rb_define_method( globalRenderImageClass, "setView", RenderImage_SetView, 1 );
	rb_define_method( globalRenderImageClass, "getDefaultView", RenderImage_GetDefaultView, 0 );
	rb_define_method( globalRenderImageClass, "getWidth", RenderImage_GetWidth, 0 );
	rb_define_method( globalRenderImageClass, "getHeight", RenderImage_GetHeight, 0 );
	
	// Class Aliases
	rb_define_alias( CLASS_OF( globalRenderImageClass ), "is_available", "isAvailable" );
	rb_define_alias( CLASS_OF( globalRenderImageClass ), "available?", "isAvailable" );
	
	// Instance Aliases
	rb_define_alias( globalRenderImageClass, "image", "getImage" );
	
	rb_define_alias( globalRenderImageClass, "is_smooth", "isSmooth" );
	rb_define_alias( globalRenderImageClass, "smooth?", "isSmooth" );
	
	rb_define_alias( globalRenderImageClass, "set_active", "setActive" );
	rb_define_alias( globalRenderImageClass, "activate", "setActive" );
	rb_define_alias( globalRenderImageClass, "active=", "setActive" );
	
	rb_define_alias( globalRenderImageClass, "smooth=", "setSmooth" );
	
	rb_define_alias( globalRenderImageClass, "view=", "setView" );
	rb_define_alias( globalRenderImageClass, "view", "getView" );
	
	rb_define_alias( globalRenderImageClass, "defaultView", "getDefaultView" );
	rb_define_alias( globalRenderImageClass, "default_view", "getDefaultView" );
	
	rb_define_alias( globalRenderImageClass, "width", "getWidth" );
	rb_define_alias( globalRenderImageClass, "height", "getHeight" );
}