/*
 *  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 <mialm/miavector3d.h>

#include <limits.h>
#include <string.h>
#include <math.h>


/**
 * SECTION:miavector3d
 * @Short_description: A 3D vector in Euclidian space. 
 * @Title: 3D Vector 
 * 
 * A #MiaVector3d represents a 3D vector in Euclidian space. 
 */


#ifndef NDEBUG
static int n_vectors = 0; 
void print_vectors_left(void)
{
	if (n_vectors)
		g_warning("left %d vectors behind", n_vectors);
}
#endif

#define MAXFLOAT FLT_MAX

static GObject *__mia_vector3d_constructor (GType type,
					    guint n_construct_properties,
					    GObjectConstructParam *
					    construct_properties);
static void
__mia_vector3d_class_init (gpointer g_class, gpointer g_class_data);

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


GType
mia_vector3d_get_type (void)
{

	static volatile gsize g_define_type_id__volatile = 0;
	if (g_once_init_enter (&g_define_type_id__volatile)) {
		GType g_define_type_id = 
			g_type_register_static_simple(G_TYPE_OBJECT, 
						      g_intern_static_string ("MiaVector3dType"),
						      sizeof (MiaVector3dClass),
						      __mia_vector3d_class_init,
						      sizeof (MiaVector3d),
						      __mia_vector3d_instance_init, 
						      0); 
		g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
	}
	return g_define_type_id__volatile;
}


/**
 * mia_vector3d_new:
 * @x: component of new vector 
 * @y: component of new vector 
 * @z: component of new vector 
 * 
 * Created a new MiaVector3d initialized with the given components
 * 
 * Returns: the new MiaVector3d instance
 * 
 */
MiaVector3d *
mia_vector3d_new (gfloat x, gfloat y, gfloat z)
{
	MiaVector3d *obj =
		(MiaVector3d *) g_object_new (MIA_TYPE_VECTOR3D, NULL);
	/* add your code here */
	obj->x = x;
	obj->y = y;
	obj->z = z;
	return obj;
}

/**
 * mia_vector3d_dup:
 * @orig: a Mia3DVector 
 *
 * Creating a new instance of @orig by duplicating it.
 *
 * Returns: a new MiaVector3d 
 * 
 */
MiaVector3d *
mia_vector3d_dup (const MiaVector3d * orig)
{
	MiaVector3d *obj =
		(MiaVector3d *) g_object_new (MIA_TYPE_VECTOR3D, NULL);
	memcpy (&obj->x, &orig->x, 3 * sizeof (gfloat));
	return obj;
}

/**
 * mia_vector3d_dot:
 * @a: a MiaVector3d
 * @b: a MiaVector3d
 * 
 * Evaluate the dot product of @a and @b
 * 
 * Returns: the dot product 
 * 
 */
gfloat
mia_vector3d_dot (const MiaVector3d * a, const MiaVector3d * b)
{
	g_assert (MIA_IS_VECTOR3D (a));
	g_assert (MIA_IS_VECTOR3D (b));
	return a->x * b->x + a->y * b->y + a->z * b->z;

}

static inline gfloat
sqr (gfloat x)
{
	return x * x;
}

/**
 * mia_vector3d_get_norm:
 * @self: a 3D vector 
 *
 * Evaluate the Euclidian norm of @self. 
 * 
 * Returns: the norm
 * 
 */
gfloat
mia_vector3d_get_norm (MiaVector3d * self)
{
	g_assert (MIA_IS_VECTOR3D (self));
	return sqrt (sqr (self->x) + sqr (self->y) + sqr (self->z));
}

/**
 * mia_vector3d_scale:
 * @self: 3D vector to be scaled
 * @f: scaling factor 
 *
 * Scales @self by factor @f. 
 *
 * Returns: (scaled) @self for convinience
 *
 */
MiaVector3d *
mia_vector3d_scale (MiaVector3d * self, gfloat f)
{
	g_assert (MIA_IS_VECTOR3D (self));
	self->x *= f;
	self->y *= f;
	self->z *= f;
	return self;
}

/**
 * mia_vector3d_normalize:
 * @self: a MiaVector3d 
 * 
 * Normalize @self to have a norm of 1 (if the vector is not zero) 
 *
 * Returns: @self (for convinience)
 */
MiaVector3d *
mia_vector3d_normalize (MiaVector3d * self)
{
	gfloat norm = mia_vector3d_get_norm (self);
	if (norm > 0)
		mia_vector3d_scale (self, 1.0f / norm);
	return self;
}

/**
 * mia_vector3d_copy:
 * @dest: Existing MiaVector3d instance 
 * @src: Existing MiaVector3d instance 
 *
 * Copies the contens of @src to @dest 
 *
 * Returns: @dest (for convinience)
 */
MiaVector3d *
mia_vector3d_copy (MiaVector3d * dest, const MiaVector3d * src)
{
	g_assert (MIA_IS_VECTOR3D (dest));
	g_assert (MIA_IS_VECTOR3D (src));
	memcpy (&dest->x, &src->x, 3 * sizeof (gfloat));
	return dest;
}

/**
 * mia_vector3d_addup:
 * @self: This Mia3DVector will be changed
 * @other: second 3D vector 
 *
 * Adds @other to @self componentwise (equivalent to @self += @other
 * 
 * Returns: @self (for convinience)
 */
MiaVector3d *
mia_vector3d_addup (MiaVector3d * self, const MiaVector3d * other)
{
	g_assert (MIA_IS_VECTOR3D (self));
	g_assert (MIA_IS_VECTOR3D (other));
	self->x += other->x;
	self->y += other->y;
	self->z += other->z;
	return self;
}

/**
 * mia_vector3d_set:
 * @self: This Mia3DVector will be changed
 * @x: new x component
 * @y: new y component
 * @z: new z component
 *
 * Sets the components of @self to the given values
 *
 * Returns: @self (with the new values set)
 */
MiaVector3d *
mia_vector3d_set (MiaVector3d * self, gfloat x, gfloat y, gfloat z)
{
	MIA_IS_VECTOR3D (self);
	self->x = x;
	self->y = y;
	self->z = z;
	return self;
}


/**
 * mia_vector3d_add:
 * @self:   input Mia3DVector 
 * @other:  input Mia3DVector  
 * @result: output Mia3DVector 
 *
 * Adds @self and @other component wise and stores the result in @result. 
 * if result = NULL, then a new Mia3DVector instance is created and returned. 
 *
 * Returns: if @result != NULL then @result, else a newly created Mia3DVector
 */

MiaVector3d *
mia_vector3d_add (const MiaVector3d * self, const MiaVector3d * other,
		  MiaVector3d * result)
{
	g_return_val_if_fail (MIA_IS_VECTOR3D (self), NULL);
	g_return_val_if_fail (MIA_IS_VECTOR3D (other), NULL);

	if (!result)
		result = mia_vector3d_dup (self);
	else
	{
		g_assert (MIA_IS_VECTOR3D (result));
		mia_vector3d_copy (result, self);
	}

	mia_vector3d_addup (result, other);
	return result;
}

/**
 * mia_vector3d_equal:
 * @a:  input Mia3DVector 
 * @b:  input Mia3DVector  
 *
 * Componentwise comparison of @a and @b. 
 * 
 * Returns: %TRUE if @a and @b are equal,  %FALSE otherwise
 */
gboolean
mia_vector3d_equal (const MiaVector3d * a, const MiaVector3d * b)
{
	g_return_val_if_fail (MIA_IS_VECTOR3D (a), FALSE);
	g_return_val_if_fail (MIA_IS_VECTOR3D (b), FALSE);
	return a->x == b->x && a->y == b->y && a->z == b->z;
}

typedef struct __MiaVector3dPropertyInit _MiaVector3dPropertyInit;
struct __MiaVector3dPropertyInit
{
	gchar *name;
	gchar *nick;
	gchar *descr;
	gfloat min, max;
	gfloat def;
	GParamFlags flags;
	gint id;
};


static _MiaVector3dPropertyInit  __vector3d_properties[mia_vector3d_prop_counter] = {
	{"x", "x", "value_x", -MAXFLOAT, MAXFLOAT, 0.0f,
	 G_PARAM_READWRITE, mia_vector3d_prop_x},
	{"y", "y", "value y", -MAXFLOAT, MAXFLOAT, 0.0f,
	 G_PARAM_READWRITE, mia_vector3d_prop_y},
	{"z", "z", "value z", -MAXFLOAT, MAXFLOAT, 0.0f,
	 G_PARAM_READWRITE, mia_vector3d_prop_z}
};


/**
 * mia_vector3d_xmlio_read:
 * @state: holst the current state of the XML SAX parser
 * @property:  the name of the property, where the MiaVector3d should be stored
 *
 * Callback to read the vector from a XML file 
 */

void
mia_vector3d_xmlio_read(ParserState * state, const gchar *property)
{
	gfloat x,y,z;
	MiaVector3d *v;
	GValue value = G_VALUE_INIT;
	GObject *obj = G_OBJECT (state->pss->parent->data);
	
	g_assert (obj);
	
	g_value_init (&value, G_TYPE_POINTER);

	sscanf (state->pss->ch->str, "%g %g %g", &x, &y, &z);

	v = mia_vector3d_new (x, y, z);
	g_value_set_pointer (&value, (gpointer) v);
	g_object_set_property(obj, property, &value);
}

/**
 * mia_vector3d_xmlio_write:
 * @parent:  node to attach the vector to 
 * @ns: xml name space
 * @tag: XML tag of the node 
 * @v: a MiaVector3d to be stored 
 *
 * Add vector @v as XML node with tag @tag to the @parent node 
 *
 * Returns: %TRUE if vector was NULL or sucessfully added to the xml node %FALSE otherwise
 *
 */

gboolean
mia_vector3d_xmlio_write (xmlNodePtr parent, xmlNsPtr ns, const gchar * tag,
		      const MiaVector3d * v)
{
	gchar text[1024];
	if (!v)
		return TRUE;

	snprintf (text, 1023, "%g %g %g", v->x, v->y, v->z);
	return (xmlNewTextChild (parent, ns, (const xmlChar *)tag, (const xmlChar *)text) != NULL);
}


static void
__mia_vector3d_set_property (GObject * object,
			     guint property_id,
			     const GValue * value, GParamSpec * pspec)
{
	MiaVector3d *self = (MiaVector3d *) object;

	switch (property_id)
	{

	case mia_vector3d_prop_x:
		self->x = g_value_get_float (value);
		break;
	case mia_vector3d_prop_y:
		self->y = g_value_get_float (value);
		break;
	case mia_vector3d_prop_z:
		self->z = 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_vector3d_get_property (GObject * object,
			     guint property_id,
			     GValue * value, GParamSpec * pspec)
{
	MiaVector3d *self = (MiaVector3d *) object;

	switch (property_id)
	{
	case mia_vector3d_prop_x:
		g_value_set_float (value, self->x);
		break;
	case mia_vector3d_prop_y:
		g_value_set_float (value, self->y);
		break;
	case mia_vector3d_prop_z:
		g_value_set_float (value, self->z);
		break;
	default:
		/* We don't have any other property... */
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
						   pspec);
	}
}

static void
__mia_vector3d_instance_init (GTypeInstance * UNUSED(instance), gpointer UNUSED(g_class))
{
	//      MiaVector3d *self = (MiaVector3d *)instance;
#ifndef NDEBUG	
	n_vectors++;
#endif	
}

static GObject *
__mia_vector3d_constructor (GType type,
			    guint n_construct_properties,
			    GObjectConstructParam * construct_properties)
{
	GObject *obj;
	{
		/* Invoke parent constructor. */
		MiaVector3dClass *klass;
		GObjectClass *parent_class;
		klass = MIA_VECTOR3D_CLASS (g_type_class_peek
					    (MIA_TYPE_VECTOR3D));
		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_vector3d_dispose (GObject * UNUSED(obj))
{
	/* add your code herenothing to dispose */

}

static void
__mia_vector3d_finalize (GObject * UNUSED(obj))
{
	/* add your destruction code here */
#ifndef NDEBUG	
	n_vectors--;
#endif	
}



static void
__mia_vector3d_class_init (gpointer g_class, gpointer UNUSED(g_class_data))
{
	int i;
	_MiaVector3dPropertyInit *lp;
	GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
	/*MiaLandmarkClass *klass = MIA_LANDMARK_CLASS (g_class); */

	gobject_class->set_property = __mia_vector3d_set_property;
	gobject_class->get_property = __mia_vector3d_get_property;
	gobject_class->dispose = __mia_vector3d_dispose;
	gobject_class->finalize = __mia_vector3d_finalize;
	gobject_class->constructor = __mia_vector3d_constructor;

	lp = __vector3d_properties;
	/* add your code here (e.g. define properties) */
	for (i = 0; i < mia_vector3d_prop_counter - 1; i++, ++lp)
	{
		GParamSpec *pspec = g_param_spec_float (lp->name, lp->nick,
							lp->descr,
							lp->min, lp->max,
							lp->def,
							lp->flags);
		g_object_class_install_property (gobject_class, lp->id,
						 pspec);
	}
}
