/*
 *  Copyright (c) 2004-2013 Gert Wollny <gw.fossdev@gmail.com>
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  As an exception to this license, "NEC C&C Research Labs" may use
 *  this software under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 *
 */
 
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <limits.h>

#include <mialm/miacamera.h>
#include <mialm/miavector3d.h>
#include <mialm/miaquaternion.h>


/**
 * SECTION:miacamera
 * @Short_description: A camera description for 3D views
 * @Title: Camera 
 * 
 * #MiaCamera descibes a point of view within a 3D volume, i.e. 
 * camera location, view direction, and zoom are stored within this class and 
 * manipulated by the corresponding functions. 
 *
 */


/**
 * MiaCamera:
 *
 * This defines the camera used to view a 3D data set.
 *
 */

struct _MiaCamera {
	/*< private >*/
	GObject parent;
	gboolean dispose_has_run;
	/*< public >*/
	gfloat zoom;
	MiaVector3d *location;
	MiaQuaternion *rotation;
};


#ifndef NDEBUG
static int n_camera = 0; 
void print_camera_left(void)
{
	if (n_camera)
		g_warning("left %d cameras behind", n_camera);
}
#endif



static GObject *
__mia_camera_constructor (GType                  type,
		    guint                  n_construct_properties,
		    GObjectConstructParam *construct_properties);


static void 
__mia_camera_class_init (gpointer g_class,
                   gpointer g_class_data);

static void 
__mia_camera_instance_init (GTypeInstance   *instance,
                      gpointer         g_class);

GType mia_camera_get_type(void)
{
	static GType type = 0;
        if (type == 0) {
                static const GTypeInfo info = {
                        sizeof (MiaCameraClass),
                        NULL,   /* base_init */
                        NULL,   /* base_finalize */
                        __mia_camera_class_init,   /* class_init */
                        NULL,   /* class_finalize */
                        NULL,   /* class_data */
                        sizeof (MiaCamera),
                        0,      /* n_preallocs */
                        __mia_camera_instance_init, 	/* instance_init */
			NULL /* value_table */
                };
                type = g_type_register_static (G_TYPE_OBJECT,
                                               "MiaCameraType",
                                               &info, 0);
        }
        return type;	
}


/**
 * mia_camera_new:
 * @loc: The camera location, z is ignored  
 * @rot: the rotation angle 
 * @zoom: camera zoom into the field of view 
 * 
 *  Create a camera defined by its position, heading, distance and   
 *  zoom. The created object must be destroyed by calling g_object_unref()
 * 
 *  For some reason I can not remember, the location z value is ignored and 
 *  the distance value is given separately. 
 * 
 * Returns: a newly created quaternion
 */
MiaCamera *mia_camera_new(MiaVector3d *loc,
			  MiaQuaternion *rot,
			  gfloat zoom)
{
	MiaCamera *self = (MiaCamera *)g_object_new(MIA_TYPE_CAMERA,NULL);
	/* add your code here */
	self->rotation = rot; 
	self->location = loc;
	self->zoom = zoom; 
	return self; 
}

/**
 * mia_camera_get_zoom:
 * @self: the camera 
 * 
 * Get the current zoom value of the camera
 * 
 * Returns: zoom 
 */
gfloat mia_camera_get_zoom(const MiaCamera *self)
{
	g_assert(MIA_IS_CAMERA(self));
	return self->zoom;
}

/**
 * mia_camera_set_zoom:
 * @self: the camera 
 * @zoom: the new zoom value for the camera 
 * 
 * sets the new zoom of the camera (i.e. change field of view) 
 */
void mia_camera_set_zoom(MiaCamera *self, gfloat zoom)
{
	g_assert(MIA_IS_CAMERA(self));
	self->zoom = zoom; 
}

/**
 * mia_camera_get_location:
 * @self: the camera 
 *
 * get current location of the camera (note that the z value is equal to the distance)  
 * Returns: location 
 */
MiaVector3d *mia_camera_get_location(const MiaCamera *self)
{
	g_assert(MIA_IS_CAMERA(self));
	return self->location; 
}

/**
 * mia_camera_set_location:
 * @self: the camera 
 * @loc: new location of the camera 
 * 
 * Set the new location of the camera 
 */
void mia_camera_set_location(MiaCamera *self, MiaVector3d *loc)
{
	g_assert(MIA_IS_CAMERA(self));
	if (G_IS_OBJECT(self->location))
		g_object_unref(G_OBJECT(self->location));
	self->location = loc; 
}

/**
 * mia_camera_get_rotation:
 * @self: the camera 
 *
 * get the current view directopn of the camera as a rotation described by a MiaQuaternion 
 *
 * Returns: Quaternion describing the camera rotation 
 */
MiaQuaternion *mia_camera_get_rotation(const MiaCamera *self)
{
	g_assert(MIA_IS_CAMERA(self));
	return self->rotation;
}

/**
 * mia_camera_set_rotation:
 * @self: the camera 
 * @rot: new rotation of the camera position around the center of the universe
 *
 * sets the current view direction of the camera as a rotation described by a MiaQuaternion 
 *
 */
void mia_camera_set_rotation(MiaCamera *self, MiaQuaternion *rot)
{
	g_assert(MIA_IS_CAMERA(self));
	if (G_IS_OBJECT(self->rotation))
		g_object_unref(self->rotation);
	self->rotation = rot; 
}

/**
 * mia_camera_get_dist:
 * @self: the camera 
 *
 * Evaluate the distance of the camera from the center of the universe (0,0,0)
 *
 * Returns: the distance 
 */
gfloat mia_camera_get_dist(const MiaCamera *self)
{
	g_assert(MIA_IS_CAMERA(self));
	return self->location->z; 

}

/**
 * mia_camera_set_dist:
 * @self: the camera 
 * @dist: new distance to the center of the universe 
 *
 * Sets the new distance to the center (0,0,0), can also be achieved by setting the full location
 *
 */
void mia_camera_set_dist(MiaCamera *self, gfloat dist)
{
	g_assert(MIA_IS_CAMERA(self));
	self->location->z = dist; 
}

/**
 * mia_camera_dup:
 * @orig: camera to be copied 
 *
 * Create a newly created duplicate of a MiaCamera. The returned camera 
 * must be freed by calling g_object_unref()
 *
 * Returns: the copy of the camera
 */
MiaCamera *mia_camera_dup(MiaCamera *orig)
{
	return mia_camera_new(mia_vector3d_dup(orig->location),
			      mia_quaternion_dup(orig->rotation),
			      orig->zoom);
}

/**
 * mia_camera_copy:
 * @dest: destination object 
 * @orig: original object to be copied 
 *
 * Copy a MiaCamera to an already created instance,   
 * Returns: the copy of the camera
 *
 */
MiaCamera *mia_camera_copy(MiaCamera *dest, const MiaCamera *orig)
{
	g_assert(MIA_IS_CAMERA(dest));
	g_assert(MIA_IS_CAMERA(orig));
	if (dest->location)
		mia_vector3d_copy(dest->location, orig->location);
	else 
		dest->location = mia_vector3d_dup(orig->location);
	
	if (dest->rotation) 
		mia_quaternion_copy(dest->rotation, orig->rotation);
	else 
		dest->rotation = mia_quaternion_dup(orig->rotation);
	
	dest->zoom = orig->zoom;
	return dest; 
}

enum {
	cprop_location = 1,
	cprop_rotation, 
	cprop_zoom, 
	cprop_dist
};

static void
__mia_camera_set_property (GObject      *object,
                     guint         property_id,
                     const GValue *value,
                     GParamSpec   *pspec)
{
        MiaCamera *self = (MiaCamera *) object;
        switch (property_id) {
	case cprop_location:
		mia_camera_set_location(self, MIA_VECTOR3D(g_value_get_pointer(value)));
		break;
	case cprop_rotation:
		mia_camera_set_rotation(self, MIA_QUATERNION(g_value_get_pointer(value)));
		break;
	case cprop_zoom: 
		self->zoom = g_value_get_float(value);
		break;
	case cprop_dist: 
		mia_camera_set_dist(self, g_value_get_float(value));
		break;
	default:
                /* We don't have any other property... */
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        }
}

static void
__mia_camera_get_property (GObject      *object,
                     guint         property_id,
                     GValue       *value,
                     GParamSpec   *pspec)
{
        MiaCamera *self = (MiaCamera *) object;

        switch (property_id) {
	case cprop_location:
		g_value_set_pointer(value,(gpointer)self->location);
		break;
	case cprop_rotation:
		g_value_set_pointer(value,(gpointer)self->rotation);
		break;
	case cprop_zoom: 
		g_value_set_float(value, self->zoom);
		break;
	case cprop_dist: 
		g_value_set_float(value, self->location->z);
		break;
	default:
                /* We don't have any other property... */
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
	}
}

static void
__mia_camera_instance_init (GTypeInstance   *instance,
			    gpointer         UNUSED(g_class))
{
        MiaCamera *self = (MiaCamera *)instance;
        self->dispose_has_run = FALSE;
#ifndef NDEBUG
	n_camera++;
#endif	
}

static GObject *
__mia_camera_constructor (GType                  type,
                    guint                  n_construct_properties,
                    GObjectConstructParam *construct_properties)
{
        GObject *obj;
        {
                /* Invoke parent constructor. */
                MiaCameraClass *klass;
                GObjectClass *parent_class;  
                klass = MIA_CAMERA_CLASS (g_type_class_peek (MIA_TYPE_CAMERA));
                parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
                obj = parent_class->constructor (type,
                                                 n_construct_properties,
                                                 construct_properties);
        }
        
        /* add your code here */
	
        return obj;
}

static void
__mia_camera_dispose (GObject *obj)
{
	MiaCamera *self = (MiaCamera *)obj;

	if (self->dispose_has_run) {
               /* If dispose did already run, return. */
               return;
	}
        /* Make sure dispose does not run twice. */
	self->dispose_has_run = TRUE;
	
	/* add your code here */
	g_object_unref(G_OBJECT(self->rotation));
	self->rotation = NULL; 
	
	g_object_unref(G_OBJECT(self->location));
	self->location = NULL; 
}

static void
__mia_camera_finalize (GObject *UNUSED(obj))
{
#ifndef NDEBUG
	n_camera--;
#endif		
	
}

static const char crot_property[] = "rotation";
static const char cloc_property[] = "location";
static const char czoom_property[] = "zoom";
static const char cdist_property[] = "distance";

static void
__mia_camera_class_init (gpointer g_class,
			 gpointer UNUSED(g_class_data))
{
	GParamSpec *pspec;
        GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
        /*MiaLandmarkClass *klass = MIA_LANDMARK_CLASS (g_class);*/
		
        gobject_class->set_property = __mia_camera_set_property;
        gobject_class->get_property = __mia_camera_get_property;
        gobject_class->dispose = __mia_camera_dispose;
        gobject_class->finalize = __mia_camera_finalize;
        gobject_class->constructor = __mia_camera_constructor;
	
	/* add your code here (e.g. define properties) */
	pspec = g_param_spec_pointer (cloc_property, "loc",
				      "location of the camera", 
				      G_PARAM_READWRITE);	
	g_object_class_install_property (gobject_class, cprop_location ,pspec);
	
	pspec = g_param_spec_pointer (crot_property, "rot",
				      "rotation of the camera", 
				      G_PARAM_READWRITE);	
	g_object_class_install_property (gobject_class, cprop_rotation ,pspec);
	
	pspec = g_param_spec_float (czoom_property, "zoom",
				    "zoom of the camera", 0.1, FLT_MAX, 20.0,
				      G_PARAM_READWRITE);	
	g_object_class_install_property (gobject_class, cprop_zoom ,pspec);
	
	pspec = g_param_spec_float (cdist_property, "dist",
				    "dist of the camera", 0.1, FLT_MAX, 400.0,
				      G_PARAM_READWRITE);	
	g_object_class_install_property (gobject_class, cprop_dist ,pspec);
}

const ParserTags camera_parser_tags[] = {
	{cloc_property,   NULL, xmlio_get_string, mia_vector3d_xmlio_read, NULL},
	{crot_property,   NULL, xmlio_get_string, mia_quaternion_xmlio_read, NULL},
	{czoom_property,  NULL, xmlio_get_string, xmlio_end_float, NULL},
	{cdist_property,  NULL, xmlio_get_string, xmlio_end_float, NULL}, 
	END_PARSER_TAGS
};

/**
 * mia_camera_xmlio_write:
 * @root: XML node to attach the camera description to 
 * @ns: XML namespace 
 * @tag: XML tag to use to create the XML node.
 * @c:   the camera to store 
 *
 * Write the MiaCamera instance to an XML node
 *
 * Returns: true if the camera was sucessfully written nor c==NULL
 */


gboolean
mia_camera_xmlio_write(xmlNodePtr root, xmlNsPtr ns, const gchar * tag,
		      const MiaCamera *c)
{
	xmlNodePtr camera_ptr;
	if (!c)
		return TRUE;

	g_assert(MIA_IS_CAMERA(c));

	camera_ptr = xmlNewChild (root, ns, (const xmlChar *)tag, NULL);
	if (!camera_ptr)
		return FALSE;
	if (!mia_vector3d_xmlio_write
	    (camera_ptr, ns, cloc_property, mia_camera_get_location (c)))
		return FALSE;
	if (!xml_write_float
	    (camera_ptr, ns, czoom_property, mia_camera_get_zoom (c)))
		return FALSE;
	if (!mia_quaternion_xmlio_write
	    (camera_ptr, ns, crot_property, mia_camera_get_rotation (c)))
		return FALSE;
	if (!xml_write_float
	    (camera_ptr, ns, cdist_property, mia_camera_get_dist (c)))
		return FALSE;
	return TRUE;
}



#if 0

/** 
 * mia_camera_test: 
 *
 * Run some tests on the camera (not impmented) 
 *
 * Returns: TRUE if tests were successful
 *
 */
gboolean mia_camera_test(void)
{
	gboolean result = FALSE;
	MiaCamera *test = mia_camera_new();
	g_return_val_if_fail(test, FALSE);
	result = MIA_IS_CAMERA(test);
	
	/* add your tests here */
	
	
	g_object_unref(G_OBJECT(test));
	return result;
}

#endif
