/*---------------------------------------------------------------------------*\

    geophase: A C++ template library for geometrical operations on non-convex
    polyhedra with non-planar faces
     
    Copyright (C) 2019 Tomislav Maric, TU Darmstadt 

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Description

    IsInside algorithms:

       is_inside(g1, g2) tests if g2 is within g1

Authors
    Tomislav Maric maric@mma.tu-darmstadt.de

\*---------------------------------------------------------------------------*/

#ifndef IsInside_hpp
#define IsInside_hpp

#include "tag.hpp"
#include "Distance.hpp"
#include "tolerance.hpp"

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

namespace geophase {

    enum class position {INSIDE=-1, COINCIDENT, OUTSIDE, UNDETERMINED}; 

    template<typename Vector> 
    enable_if_type_tagged_t <Vector, vector_tag, position>
    orient2D(Vector const& p, Vector const& q, Vector const& r)
    {
        return static_cast<position>(
            sign((q[0]-p[0])*(r[1]-p[1]) - (q[1] - p[1])*(r[0] - p[0]))
        );
    }

    template<typename Vector, typename Halfspace> 
    enable_if_types_tagged_t<position, Vector, vector_tag, Halfspace, halfspace_tag>
    is_inside_float(Vector const& vec, Halfspace const& hspace)
    {
        return static_cast<position>(sign(signed_distance(vec,hspace))); 
    }

    template<typename Vector, typename Halfspace> 
    enable_if_types_tagged_t<position, Vector, vector_tag, Halfspace, halfspace_tag>
    is_inside_epsilon(
        Vector const& vec, 
        Halfspace const& hspace, 
        typename Vector::value_type epsilon
    )
    {
        return static_cast<position>(sign(signed_distance(vec,hspace), epsilon)); 
    }

    template<typename Vector, typename Halfspace> 
    enable_if_types_tagged_t<position, Vector, vector_tag, Halfspace, halfspace_tag>
    is_inside_ulp(
        Vector const& vec, 
        Halfspace const& hspace, 
        std::size_t NULP 
    )
    {
        return static_cast<position>(sign(
            signed_distance(vec, hspace), 
            NULP * (ulp(vec[0]) + ulp(vec[1]) + ulp(vec[0]))
            )
        ); 
    }

    position is_inside_error(const distanceErrorPair& dist_error)
    {
        if (dist_error.distance > 0)
        {
            if ((dist_error.distance - dist_error.abserror) > 0)
                return position::INSIDE; 
        }
        else if ((dist_error.distance + dist_error.abserror) < 0)
                return position::OUTSIDE;

        return position::COINCIDENT; 
    }

    bool is_inside_error(
        const distanceErrorPair& dist_error_a, 
        const distanceErrorPair& dist_error_b 
    )
    {
        const auto position_a = is_inside_error(dist_error_a);
        const auto position_b = is_inside_error(dist_error_b);

        return (position_a == position::INSIDE) && 
               (position_b == position::INSIDE); 
    }

    bool is_intersected_error(
        const distanceErrorPair& dist_error_a, 
        const distanceErrorPair& dist_error_b 
    )
    {
        const auto position_a = is_inside_error(dist_error_a);
        const auto position_b = is_inside_error(dist_error_b);

        return (((position_a == position::INSIDE) ||
                (position_a == position::COINCIDENT)) &&
                (position_b == position::OUTSIDE)) ||
               ((position_a == position::OUTSIDE) &&
                ((position_b == position::INSIDE) ||
                 (position_b == position::COINCIDENT))); 
    }

    template<typename Vector, typename Halfspace> 
    enable_if_types_tagged_t<position, Vector, vector_tag, Halfspace, halfspace_tag>
    is_inside_error(
        Vector const& vec, 
        Halfspace const& hspace
    )
    {
        return is_inside_error(signed_distance_error(vec,hspace)); 
    }

    template<typename Polyhedron, typename Halfspace> 
    std::enable_if_t
    <
        tag_base_of<Polyhedron, sequence_polyhedron_tag>() && 
        type_tagged<Halfspace, halfspace_tag>(), 
        position
    >
    is_inside_error(
        Polyhedron const& polyhedron, 
        Halfspace const& hspace
    )
    {
        for (const auto& polygon : polyhedron)
            for (const auto& point : polygon)
                if (is_inside_error(point, hspace) == position::OUTSIDE)
                    return position::UNDETERMINED; 

        return position::INSIDE; 
    }

    //template
    //<
        //typename Halfspace,  
        //typename Point 
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<Halfspace, halfspace_tag>::value &&
        //Traits::tag_enabled<Point, point_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //Halfspace const & halfspace, 
        //Point const & point, 
        //const double tolerance = Traits::tolerance<Halfspace>::value()
    //)
    //{
        //return distance(point, halfspace) > -1*tolerance;
    //}

    //template
    //<
        //typename AABB,  
        //typename Point 
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<AABB, aabbox_tag>::value &&
        //Traits::tag_enabled<Point, point_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //AABB const & aabb, 
        //Point const & point, 
        //const double tolerance = Traits::tolerance<Point>::value()
    //)
    //{
        //return is_inside(point,aabb);
    //}

    //template
    //<
        //typename AABB,  
        //typename Point 
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<AABB, aabbox_tag>::value &&
        //Traits::tag_enabled<Point, point_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //Point const & point, 
        //AABB const &  aabb, 
        //const double tolerance = Traits::tolerance<Point>::value()
    //)
    //{
        //return true;
    //}

    //template
    //<
        //typename Halfspace,  
        //typename Triangle
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<Halfspace, halfspace_tag>::value &&
        //Traits::tag_enabled<Triangle, triangle_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //Halfspace const & halfspace, 
        //Triangle const & triangle, 
        //const double tolerance = Traits::tolerance<Halfspace>::value()
    //)
    //{
        //if (! is_inside(halfspace, triangle[0], tolerance))
        //{
            //return false;
        //}
        //if (! is_inside(halfspace, triangle[1], tolerance))
        //{
            //return false;
        //}
        //if (! is_inside(halfspace, triangle[2], tolerance))
        //{
            //return false;
        //}

        //return true;
    //}

    //template
    //<
        //typename PointCollection,
        //typename Point, 
        //typename Vector
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<PointCollection, point_collection_tag>::value &&
        //Traits::tag_enabled<Point, point_tag>::value &&
        //Traits::tag_enabled<Vector, vector_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //PointCollection const & points, 
        //Point const& P, 
        //Vector const& N,
        //const double tolerance = Traits::tolerance<Point>::value()
    //)
    //{
        //auto P0 = points.begin();  
        //auto P1 = std::next(P0); 

        //for (; P1 != points.end(); ++P0, ++P1)
        //{
           //if ((((*P1 - *P0) ^ (P - *P0)) & N) < -tolerance)
               //return false;
        //}

        //P1 = points.begin(); 

        //if ((((*P1 - *P0) ^ (P - *P0)) & N) < -tolerance)
            //return false;

        //return true;
    //}

    //template
    //<
        //typename PointCollection,
        //typename Point
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<PointCollection, point_collection_tag>::value &&
        //Traits::tag_enabled<Point, point_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //PointCollection const & points, 
        //Point const& P, 
        //const double tolerance = Traits::tolerance<Point>::value()
    //)
    //{
        //auto pointsNormal = normal_area_vec(points); 

        //return is_inside(points, P, pointsNormal, tolerance); 
    //}


    //template
    //<
        //typename Halfspace,  
        //typename PointCollection
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<Halfspace, halfspace_tag>::value &&
        //Traits::tag_enabled<PointCollection, point_collection_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //Halfspace const & halfspace, 
        //PointCollection const & points,
        //const double tolerance = Traits::tolerance<Halfspace>::value()
    //)
    //{
        //for(const auto& point : points)
        //{
            //if (! is_inside(halfspace, point, tolerance))
            //{
                //return false;
            //}
        //}
        //return true;
    //}

    //template
    //<
        //typename Halfspace,  
        //typename PolygonCollection
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<Halfspace, halfspace_tag>::value &&
        //Traits::tag_enabled<PolygonCollection, polygon_collection_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //Halfspace const & halfspace, 
        //PolygonCollection const & polygons,
        //const double tolerance = Traits::tolerance<Halfspace>::value()
    //)
    //{
        //for(const auto& polygon: polygons)
        //{
            //if (! is_inside(halfspace, polygon, tolerance))
            //{
                //return false;
            //}
        //}
        //return true;
    //}
    
    //template
    //<
        //typename Halfspace,  
        //typename Tetrahedron
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<Halfspace, halfspace_tag>::value &&
        //Traits::tag_enabled<Tetrahedron, triangle_collection_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //Halfspace const & halfspace, 
        //Tetrahedron const & tetrahedron,
        //const double tolerance = Traits::tolerance<Halfspace>::value()
    //)
    //{
        //for (const auto& triangle : tetrahedron)
        //{
            //if (! is_inside(halfspace, triangle, tolerance))
            //{
                //return false;
            //}
        //}

        //return true;
    //}

    //template
    //<
        //typename PolygonSequence, 
        //typename Triangulation 
    //>
    //typename std::enable_if
    //<
        //Traits::tag_enabled<PolygonSequence, polygon_sequence_tag>::value &&
        //Traits::tag_enabled<Triangulation, tetrahedron_collection_tag>::value,
        //bool 
    //>::type
    //is_inside(
        //PolygonSequence const & polyhedron, 
        //Triangulation const & triangulation,
        //const double tolerance = Traits::tolerance<PolygonSequence>::value()
    //)
    //{
        //for (const auto& polygon : polyhedron)
        //{
            //auto halfspace = build<Halfspace<point,point>>(polygon); 

            //halfspace.flip(); 

            //for (const auto& tetrahedron : triangulation)
            //{
                //if (! is_inside(halfspace, tetrahedron, tolerance))
                //{
                    //return false;
                //}
            //}
        //}

        //return true;
    //}

} // End namespace geophase 

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

#endif

// ************************************************************************* //
