/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2015-2018 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "aabb.hpp"
#include "plane.hpp"
#include "../vector.hpp"
#include "../point.hpp"
#include "../matrix.hpp"

#include <iosfwd>


///////////////////////////////////////////////////////////////////////////////
namespace cruft::geom {
    template <size_t S, typename T>
    struct ray {
        // queries
        T closest (point<S,T>) const;

        cruft::point<S,T> at (T) const;

        // data members
        point<S,T>  origin;
        vector<S,T> direction;
    };


    template <size_t S, typename T>
    ray (point<S,T>,vector<S,T>) -> ray<S,T>;


    ///////////////////////////////////////////////////////////////////////////
    typedef ray<2,float> ray2f;
    typedef ray<3,float> ray3f;


    ///////////////////////////////////////////////////////////////////////////
    template <size_t S, typename T>
    ray<S,T>
    operator* (matrix<S+1,S+1,T> lhs, ray<S,T> rhs)
    {
        return {
            lhs * rhs.origin,
            normalised (lhs * rhs.direction)
        };
    }


    ///////////////////////////////////////////////////////////////////////////
    /// returns the distance along the ray in a ray-plane intersection
    ///
    /// returns inf if parallel
    /// returns 0   if coplanar
    template <size_t S, typename T>
    constexpr T
    distance (const ray<S,T> r, const plane<S,T> p)
    {
        CHECK_SANITY (r);

        return -dot (p.coefficients, r.origin.   template redim<S+1> (1)) /
                dot (p.coefficients, r.direction.template redim<S+1> (0));
    }


    //-------------------------------------------------------------------------
    template <size_t S, typename T>
    constexpr bool
    intersects (const ray<S,T> r, const plane<S,T> p)
    {
        const auto d = distance (r, p);
        return d >= 0 && std::isfinite (d);
    }


    ///////////////////////////////////////////////////////////////////////////
    // returns the distance along a ray to an aabb
    //
    // the distance will never be negative
    //
    // the origin point of the ray does not provoke an intersection, so the
    // returned distances _should_ always be strictly positive.
    //
    // if a forward intersection cannot be found then 'infinity' is returned
    // instead.
    template <size_t S, typename T>
    constexpr T
    distance (const ray<S,T> r, const aabb<S,T> b)
    {
        CHECK_SANITY (r);

        const auto t1 = (b.lo - r.origin) / r.direction;
        const auto t2 = (b.hi - r.origin) / r.direction;

        const auto tmin = max (min (t1, t2));
        const auto tmax = min (max (t1, t2));

        // did not intersect
        if (tmin >= tmax)
            return std::numeric_limits<T>::infinity ();

        // closest is behind us
        if (tmin <= 0) {
            if (tmax <= 0) {
                return std::numeric_limits<T>::infinity ();
            } else {
                return tmax;
            }
        }

        // closest is in front of us
        return tmin;
    }


    //-------------------------------------------------------------------------
    // convenience method to test a ray intersects an aabb
    template <size_t S, typename T>
    constexpr bool
    intersects (const ray<S,T> r, const aabb<S,T> b)
    {
        return distance (r, b) >= 0;
    }


    ///////////////////////////////////////////////////////////////////////////
    template <size_t S, typename T>
    std::ostream&
    operator<< (std::ostream&, ray<S,T>);
}