///////////////////////////////////////////////////////////////////////////////////////////////////
// OpenGL Mathematics Copyright (c) 2005 - 2008 G-Truc Creation (www.g-truc.net)
///////////////////////////////////////////////////////////////////////////////////////////////////
// Created : 2005-12-21
// Updated : 2008-10-22
// Licence : This source is under MIT License
// File    : glm/gtx/matrix_projection.inl
///////////////////////////////////////////////////////////////////////////////////////////////////

namespace glm{
namespace gtx{
namespace matrix_projection{

template <typename T> 
inline detail::tmat4x4<T> perspective(T fovy, T aspect, T zNear, T zFar)
{
    T range = tan(radians(fovy / T(2))) * zNear;	
    T left = -range * aspect;
    T right = range * aspect;
    T bottom = -range;
    T top = range;

    detail::tmat4x4<T> Result(T(0));
    Result[0][0] = (T(2) * zNear) / (right - left);
    Result[1][1] = (T(2) * zNear) / (top - bottom);
    Result[2][2] = - (zFar + zNear) / (zFar - zNear);
    Result[2][3] = - T(1);
    Result[3][2] = - (T(2) * zFar * zNear) / (zFar - zNear);
    return Result;
}

template <typename valType>
inline detail::tmat4x4<valType> perspectiveFov
(
	valType const & fov, 
	valType const & width, 
	valType const & height, 
	valType const & zNear, 
	valType const & zFar
)
{
	valType rad = glm::radians(fov);
	valType h = glm::cos(valType(0.5) * rad) / glm::sin(valType(0.5) * rad);
	valType w = h * height / width;

	detail::tmat4x4<valType> Result(valType(0));
	Result[0][0] = w;
	Result[1][1] = h;
	Result[2][2] = (zFar + zNear) / (zFar - zNear);
	Result[2][3] = valType(1);
	Result[3][2] = -(valType(2) * zFar * zNear) / (zFar - zNear);
	return Result;
}

template <typename T> 
inline detail::tmat4x4<T> infinitePerspective(T fovy, T aspect, T zNear)
{
    T range = tan(radians(fovy / T(2))) * zNear;	
    T left = -range * aspect;
    T right = range * aspect;
    T bottom = -range;
    T top = range;

    detail::tmat4x4<T> Result(T(0));
    Result[0][0] = (T(2) * zNear) / (right - left);
    Result[1][1] = (T(2) * zNear) / (top - bottom);
    Result[2][2] = - T(1);
    Result[2][3] = - T(1);
    Result[3][2] = - T(2) * zNear;
    return Result;
}

template <typename T> 
inline detail::tmat4x4<T> tweakedInfinitePerspective(T fovy, T aspect, T zNear)
{
    T range = tan(radians(fovy / T(2))) * zNear;	
    T left = -range * aspect;
    T right = range * aspect;
    T bottom = -range;
    T top = range;

    detail::tmat4x4<T> Result(T(0));
    Result[0][0] = (T(2) * zNear) / (right - left);
    Result[1][1] = (T(2) * zNear) / (top - bottom);
    Result[2][2] = T(0.0001) - T(1);
    Result[2][3] = T(-1);
    Result[3][2] = - (T(0.0001) - T(2)) * zNear;
    return Result;
}

template <typename T> 
inline detail::tmat4x4<T> ortho2D(T left, T right, T bottom, T top)
{
    detail::tmat4x4<T> Result(1);
    Result[0][0] = T(2) / (right - left);
    Result[1][1] = T(2) / (top - bottom);
	Result[2][2] = - T(1);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);
    return Result;
}

template <typename T> 
inline detail::tmat4x4<T> ortho3D(T left, T right, T bottom, T top, T zNear, T zFar)
{
    detail::tmat4x4<T> Result(1);
    Result[0][0] = T(2) / (right - left);
    Result[1][1] = T(2) / (top - bottom);
    Result[2][2] = - T(2) / (zFar - zNear);
    Result[3][0] = - (right + left) / (right - left);
    Result[3][1] = - (top + bottom) / (top - bottom);
    Result[3][2] = - (zFar + zNear) / (zFar - zNear);
    return Result;
}

template <typename T> 
inline detail::tmat4x4<T> frustum(T left, T right, T bottom, T top, T nearVal, T farVal)
{
	detail::tmat4x4<T> Result(0);
	Result[0][0] = (T(2) * nearVal) / (right - left);
	Result[1][1] = (T(2) * nearVal) / (top - bottom);
	Result[2][0] = (right + left) / (right - left);
	Result[2][1] = (top + bottom) / (top - bottom);
	Result[2][2] = -(farVal + nearVal) / (farVal - nearVal);
	Result[2][3] = T(-1);
	Result[3][2] = -(T(2) * farVal * nearVal) / (farVal - nearVal);
	return Result;
}

//template <typename T, typename U> 
//tvec3<T> projectGTX(const tvec3<T>& obj, const tmat4x4<T>& model, const tmat4x4<T>& proj, const tvec4<U>& view)
//{
//    tvec4<T> v = proj * model * tvec4<T>(obj, 1);
//    return tvec3<T>(T(view.x) + T(view.z) * (v.x + 1) / 2, T(view.y) + T(view.w) * (v.y + 1) / 2, (v.z + 1) / 2);
//}

template <typename T, typename U>
inline detail::tvec3<T> project(const detail::tvec3<T>& obj, const detail::tmat4x4<T>& model, const detail::tmat4x4<T>& proj, const detail::tvec4<U>& viewport)
{
	detail::tvec4<T> tmp = detail::tvec4<T>(obj, T(1));
	tmp = model * tmp;
	tmp = proj * tmp;

	tmp /= tmp.w;
	tmp = tmp * T(0.5) + T(0.5);
	tmp[0] = tmp[0] * T(viewport[2]) + T(viewport[0]);
	tmp[1] = tmp[1] * T(viewport[3]) + T(viewport[1]);

	return detail::tvec3<T>(tmp);
}

//template <typename T, typename U> 
//tvec3<T> unProjectGTX(const tvec3<T>& win, const tmat4x4<T>& model, const tmat4x4<T>& proj, const tvec4<U>& view)
//{
//    tvec4<T> v(2 * (win.x - T(view.x)) / view.z - 1, 2 * (win.y - T(view.y)) / view.w - 1, 2 * win.z - 1, 1);
//    return tvec3<T>(inverseGTX(proj * model) * v);
//}

template <typename T, typename U>
inline detail::tvec3<T> unProject(const detail::tvec3<T>& win, const detail::tmat4x4<T>& model, const detail::tmat4x4<T>& proj, const detail::tvec4<U>& viewport)
{
	detail::tmat4x4<T> inverse = inverseGTX(proj * model);

	detail::tvec4<T> tmp = detail::tvec4<T>(win, T(1));
	tmp.x = (tmp.x - viewport[0]) / viewport[2];
	tmp.y = (tmp.y - viewport[1]) / viewport[3];
	tmp = tmp * T(2) - T(1);

	detail::tvec4<T> obj = inverse * tmp;
	obj /= obj.w;

	return detail::tvec3<T>(obj);
}

}//namespace matrix_projection
}//namespace gtx
}//namespace glm
