/*
 * Copyright (c) 1999-2019 Hartmut Seichter
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#ifndef PW_CORE_MATRIX_TRANSFORM_HPP
#define PW_CORE_MATRIX_TRANSFORM_HPP

#include <pw/core/vector.hpp>

namespace pw {

template <typename T>
struct matrix_transform {

    inline static
    matrix_<4,4,T> scale_matrix(const vector3_<T>& s)
    {
        matrix_<4,4,T> scale; scale.zero();
        scale(0,0) = s[0]; scale(1,1) = s[1]; scale(2,2) = s[2]; scale(3,3) = T(1);
        return scale;
    }

	inline static
	matrix_<4,4,T> perspective_frustum(const T &left,const T &right,
									   const T &bottom,const T &top,
									   const T &z_near,const T &z_far)
	{
		matrix_<4,4,T> frustum; frustum.zero();

		frustum(0,0) =  T(2) * z_near / (right - left);
		frustum(1,1) =  T(2) * z_near / (top - bottom);

		frustum(0,2) =  (right+left)/(right-left);	//A
		frustum(1,2) =  (top+bottom)/(top-bottom);	//B
        frustum(2,2) = -(z_far+z_near)/(z_far-z_near);	//C
        frustum(2,3) = -T(2) * z_far*z_near/(z_far-z_near);   //D

		frustum(3,2) = -T(1);

		return frustum;
	}

	inline static
	matrix_<4,4,T> perspective_projection(const T &field_of_view,
										  const T &aspect_ratio,
										  const T &z_near,const T &z_far)
	{
		const auto tan_half = tan(field_of_view / T(2));
		const auto right = tan_half * z_near / aspect_ratio;
		const auto left = -right;
		const auto top = right / aspect_ratio;
		const auto bottom = -top;

		return perspective_frustum(left,right,
								   bottom,top,
								   z_near,z_far);
	}


    inline static
	matrix_<4,4,T>	orthographic_frustum(T left, T right,
                                         T bottom,T top,
                                         T z_near, T z_far)
    {

		matrix_<4,4,T> ortho;  ortho.fill(0);

		ortho(0,0) =  static_cast<T>(2) / (right-left);
		ortho(1,1) =  static_cast<T>(2) / (top-bottom);
		ortho(2,2) = -static_cast<T>(2) / (z_far-z_near);

		ortho(3,0) = -(right+left)/(right-left);
		ortho(3,1) = -(top+bottom)/(top-bottom);
		ortho(3,2) = -(z_far+z_near)/(z_far-z_near);

        ortho(3,3) = 1;

        return ortho;
    }

	inline static
	matrix_<4,4,T>	orthographic_projection(T width,T height,T z_near, T z_far)
	{
		return orthographic_frustum(-width / 2, width / 2,
									-height / 2, height / 2,
									 z_near,z_far);
	}


	inline static
	matrix_<4,4,T> look_at(const vector3_<T> &position,
						   const vector3_<T> &target,
						   const vector3_<T> &up)
	{
        matrix_<4,4,T> view_matrix; view_matrix.set_identity();

		const vector3_<T> los = (target - position).normalized();  // line of sight
		const vector3_<T> sid = los.cross(up).normalized();
		const vector3_<T> upd = sid.cross(los).normalized();

        // set base vectors
		view_matrix.set_slice(sid, 0, 0);
		view_matrix.set_slice(upd, 0, 1);
		view_matrix.set_slice(los * T(-1), 0, 2);
		view_matrix.set_slice(position, 0, 3);

		return view_matrix;
    }
};

}

#endif
