/*********************************************************\
         __      __           __  __
        / /     / / _____    / / / / ______   __     __
       / /     / / /___  \  / / / / / ___  | / /    / /
      / /_____/ / _____| / / / / / / /__/ / / /    / /
     / ______  / / ___  / / / / / / _____/ / /    / /
    / /     / / / /__/ / / / / / / |____  / |____/ /
   /_/     /_/ |______/ /_/ /_/  \_____/  \_____  /
                                         ______/ /
            High Level Game Framework   /_______/

  ------------------------------------------------------

  Copyright (c) 2007 - Rodrigo Braz Monteiro.
  This file is subject to the terms of license.txt.

\*********************************************************/


#pragma once

#include <cmath>
#include "utils.h"
#include "vector2d.h"

namespace Halley {
	//////////////////////////////
	// Vector3D class declaration
	template <typename T=float>
	class Vector3D {
	private:
		template <typename T>
		T Mod(T a, T b) const { return a % b; }

		template <>
		float Mod(float a, float b) const { return fmod(a,b); }

		template <>
		double Mod(double a, double b) const { return fmod(a,b); }

	public:
		T x,y,z;

	private:
		T pad;

	public:
		// Constructors
		Vector3D () { x = y = z = 0; }
		Vector3D (const T _x, const T _y, const T _z) { x = _x; y = _y; z = _z; }
		explicit Vector3D (const Vector2D<T> &vec, const T _z=0) { x = vec.x; y = vec.y; z = _z; }
		Vector3D (const Vector3D<T> &vec) { x = vec.x; y = vec.y; z = vec.z; }
		template <typename V>
		explicit Vector3D (const Vector3D<V> &vec) { x = (T)vec.x; y = (T)vec.y; z = (T)vec.z; }

		// Getter
		inline T& operator[](int n) { return n==0? x: (n==1 ? y : z); }
		inline const T& operator[](int n) const { return n==0? x: (n==1 ? y : z); }

		// Assignment and comparison
		inline void operator = (const Vector3D &p) { x = p.x; y = p.y; z = p.z; }
		inline void operator = (const T p) { x = p; y = p; z = p; }
		inline bool operator == (const Vector3D &p) const { return x == p.x && y == p.y && z == p.z; }
		inline bool operator != (const Vector3D &p) const { return x != p.x || y != p.y || z != p.z; }

		// Basic algebra
		inline Vector3D operator + (const Vector3D &p) const { return Vector3D(x + p.x,y + p.y,z + p.z); }
		inline Vector3D operator - (const Vector3D &p) const { return Vector3D(x - p.x,y - p.y,z - p.z); }
		inline Vector3D operator * (const Vector3D &p) const { return Vector3D(x * p.x,y * p.y,z * p.z); }
		inline Vector3D operator / (const Vector3D &p) const { return Vector3D(x / p.x,y / p.y,z / p.z); }
		inline Vector3D operator % (const Vector3D &p) const { return Vector3D(Mod<T>(x, p.x), Mod<T>(y, p.y), Mod<T>(z, p.z)); }

		inline Vector3D modulo(const Vector3D &p) const { return Vector3D(Halley::modulo<T>(x, p.x), Halley::modulo<T>(y, p.y), Halley::modulo<T>(z, p.z)); }
		inline Vector3D floorDiv(const Vector3D &p) const { return Vector3D(Halley::floorDiv(x, p.x), Halley::floorDiv(y, p.y), Halley::floorDiv(z, p.z)); }

		inline Vector3D operator - () const { return Vector3D(-x,-y,-z); }

		inline Vector3D operator * (const T p) const { return Vector3D(x * p,y * p, z * p); }
		inline Vector3D operator / (const T p) const { return Vector3D(x / p,y / p, z / p); }

		// In-place operations
		inline Vector3D operator += (const Vector3D &p) { x += p.x; y += p.y; z += p.z; return *this; }
		inline Vector3D operator -= (const Vector3D &p) { x -= p.x; y -= p.y; z -= p.z; return *this; }
		inline Vector3D operator *= (const T p) { x *= p; y *= p; z *= p.z; return *this; }
		inline Vector3D operator /= (const T p) { x /= p; y /= p; z /= p.z; return *this; }

		// Get the normalized vector (unit vector)
		inline Vector3D GetUnit () const
		{
			float len = Length();
			if (len != 0) {
				return (*this) / len;
			}
			else return Vector3D(0,0);
		}

		// Cross product (the Z component of it)
		// TODO: lazy atm
		//inline Vector3D Cross (const Vector3D &p) const { return x * p.y - y * p.x; }

		// Dot product
		inline T Dot (const Vector3D &p) const { return (x * p.x) + (y * p.y) + (z * p.z); }

		// Length
		inline T Length () const { return sqrt(SquaredLength()); }
		inline T Len () const { return Length(); }

		// Squared length, often useful and much faster
		inline T SquaredLength () const { return x*x+y*y+z*z; }

		// Floor
		inline Vector3D Floor () const { return Vector3D(floor(x), floor(y), floor(z)); }

		// Projection on another vector
		inline Vector3D Projection (const Vector3D &p) const { Vector3D unit = p.GetUnit(); return Dot(unit) * unit; }
		inline T ProjectionLength (const Vector3D &p) const { Vector3D unit = p.GetUnit(); return Dot(unit); }
	};


	////////////////////
	// Global operators
	template <typename T,typename V> inline Vector3D<T> operator * (V f,const Vector3D<T> &v) { return v * f; }

	////////////
	// Typedefs
	typedef Vector3D<double> Vector3d;
	typedef Vector3D<float> Vector3f;
	typedef Vector3D<int> Vector3i;
	typedef Vector3D<short> Vector3s;
	typedef Vector3D<char> Vector3c;
}
