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

    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
    A multiset polygon.

    A sequence of Points compliant to an STL sequence container and tagged as 
    a polygon 

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

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

#ifndef Polygon_HPP
#define Polygon_HPP

#include "tag.hpp"
#include "Vector.hpp"

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

namespace geophase 
{

/*---------------------------------------------------------------------------*\
                         Class ContainerPolygon.H Declaration
\*---------------------------------------------------------------------------*/

template
<
    typename Point,    
    template<typename, typename> class Container,
    template <typename T> class Allocator = std::allocator, 
    typename Enable = void
>
class ContainerPolygon;

template
<
    typename Point,    
    template<typename, typename> class Container,
    template <typename T> class Allocator 
>
class ContainerPolygon
<
    Point, 
    Container, 
    Allocator,  
    enable_if_type_tagged_t<Point, vector_tag> 
> final
:
    public Container<Point, Allocator<Point>> 
{
    public:

        using container_type = Container<Point, Allocator<Point>>; 
        using container_type::container_type; 

        template<typename ...Args>
        void* operator new (size_t, Args...) = delete;
};

// WKT format output 
template<typename Polygon> 
std::ostream& 
write_wkt_polygon(std::ostream& os, Polygon const& polygon)
{
    // https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
    os << "POLYGON(("; 
    for (const auto& point : polygon)
    {
        for (const auto cmpt : point)
            os << cmpt << " "; 
        os << '\b' << ',' << " "; 
    }
    for (const auto cmpt: polygon.front())
        os << cmpt << " "; 
    os << '\b' << "))";
    return os;
}

template
<
    typename Point,    
    template<typename, typename> class Container,
    template <typename T> class Allocator = std::allocator, 
    typename Enable = void
>
std::ostream& 
operator<<(
    std::ostream& os, 
    ContainerPolygon<Point, Container, Allocator, Enable> const& polygon
)
{
    // https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
    return write_wkt_polygon(os, polygon); 
}


template<typename Point, template<typename> typename Allocator = std::allocator> 
using VectorPolygon = ContainerPolygon<Point, std::vector, Allocator>; 

template<typename Point, template <typename> typename Allocator> 
struct tag<VectorPolygon<Point, Allocator>>
{
    using type = vector_polygon_tag; 
};

template<typename Point, template<typename> typename Allocator = std::allocator> 
using ListPolygon = ContainerPolygon<Point, std::list, Allocator>; 

template<typename Point, template <typename> typename Allocator> 
struct tag<ListPolygon<Point, Allocator>>
{
    using type = list_polygon_tag; 
};

template<typename Point, template<typename> typename Allocator = std::allocator> 
using DequePolygon = ContainerPolygon<Point, std::deque, Allocator>; 

template<typename Point, template <typename> typename Allocator> 
struct tag<DequePolygon<Point, Allocator>>
{
    using type = deque_polygon_tag; 
};

template
<
    typename Point,    
    std::size_t N,  
    typename Enable = void
>
class ArrayPolygon;

template
<
    typename Point,    
    std::size_t N 
>
class ArrayPolygon
<
    Point, 
    N,
    enable_if_type_tagged_t<Point, vector_tag> 
> final 
: 
    public std::array<Point, N> 
{
    public:

        using container_type = std::array<Point, N>; 
        using container_type::container_type;

        template<typename ... Args>
        constexpr ArrayPolygon(Args&& ... args)
            : 
                container_type{std::forward<Args>(args)...}
        {}

        // Delete new operator to prevent undefined behavior for
        // std::array*= new Vector; delete array; std::array has 
        // no virtual destructors.
        template<typename ...Args>
        void* operator new (size_t, Args...) = delete;
};

template<typename Point, std::size_t N>  
struct tag<ArrayPolygon<Point, N>>
{
    using type = array_polygon_tag; 
};

// WKT format std::ostream operator<<
template <typename Point, std::size_t N >
std::ostream& 
operator<<(std::ostream& os, ArrayPolygon<Point, N> const& polygon)
{
    return write_wkt_polygon(os, polygon); 
}

// Common polygons
using vectorPolygon = VectorPolygon<vector>; 
using listPolygon   = ListPolygon<vector>; 
using dequePolygon  = DequePolygon<vector>; 
using triangle      = ArrayPolygon<vector, 3>; 
using rectangle     = ArrayPolygon<vector, 4>; 
using pentagon      = ArrayPolygon<vector, 5>; 
using hexagon       = ArrayPolygon<vector, 6>; 

}  // End namespace geophase 

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

#endif

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