/*
 * 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_TRANSFORM_TOOLS_HPP
#define PW_CORE_TRANSFORM_TOOLS_HPP

#include <pw/core/vector.hpp>

namespace pw {

template <typename T>
struct transform_tools {

    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> projection_from_frustum(T left,T right,T bottom,T top,T z_near,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(2,0) =     (right+left)/(right-left);	//A
        frustum(2,1) =     (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> orthogonal_projection(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) =  2 / (right-left);
        ortho(1,1) =  2 / (top-bottom);
        ortho(2,2) = -2 / (z_far-z_near);

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

        ortho(3,3) = 1;

        return ortho;
    }

    inline static
    matrix_<4,4,T> perspective_projection(T fov_y, T aspect_ratio, T z_near, T z_far)
    {
        const T height = z_near * tan(fov_y/T(360) * pi<T>());  // half height of near plane
        const T width = height * aspect_ratio;             // half width of near plane

        return projection_from_frustum(-width, width, -height, height, z_near, z_far );
    }

    inline static
    matrix_<4,4,T> look_at(
            const vector3_<T> &eye,
            const vector3_<T> &target,
            const vector3_<T> &up)
    {
        matrix_<4,4,T> view_matrix; view_matrix.set_identity();
        const vector3_<T> L = (target - eye).normalized();  // line of sight

        const vector3_<T>  S = L.cross(up).normalized();
        const vector3_<T> Ud = S.cross(L).normalized();

        // set base vectors
        view_matrix.set_slice(S,        0,0);
        view_matrix.set_slice(Ud,       0,1);
        view_matrix.set_slice(L*T(-1),  0,2);
        view_matrix.set_slice(eye,      0,3);

        return view_matrix;


        //        matrix<3,1,T> S = matrix31<T>::Cross(L,up);
        //        S.normalize();
        //        matrix<3,1,T> Ud = matrix31<T>::Cross(S,L);
        //        Ud.normalize();

        //        lookat(0,0) = S.at(0);
        //        lookat(0,1) = S.at(1);
        //        lookat(0,2) = S.at(2);
        //        lookat(0,3) = T(0);

        //        lookat(1,0) = Ud.at(0);
        //        lookat(1,1) = Ud.at(1);
        //        lookat(1,2) = Ud.at(2);
        //        lookat(1,3) = T(0);

        //        lookat(2,0) = -L.at(0);
        //        lookat(2,1) = -L.at(1);
        //        lookat(2,2) = -L.at(2);
        //        lookat(3,2) = T(0);

        //        lookat(3,0) = eye.at(0);
        //        lookat(3,1) = eye.at(1);
        //        lookat(3,2) = eye.at(2);
        //        lookat(3,3) = 1;

        //        return lookat;

    }

};

}

#endif
