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

    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
    Tag system used for tag dispatching. 

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

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

#ifndef tag_hpp
#define tag_hpp

#include "tags.hpp"
#include <utility>
#include <type_traits>

// STL specializations for the 'tag' struct.
// Can these be forward-declared? 
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <array>
#include <map> 
#include <set>
#include <cstddef>

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

namespace geophase { 

    template<typename Type, typename Enabled = void>
    struct tag
    {
        using type = void;
    };

    template<typename Type> 
    using tag_t = typename tag<Type>::type; 

    template<typename Type, typename Tag>  
    constexpr bool type_tagged()
    {
        return std::is_same<Tag, tag_t<Type>>();     
    }

    template<typename Type, typename Tag>  
    constexpr bool tag_base_of()
    {
        return std::is_base_of<Tag, tag_t<Type>>();     
    }

    template<typename Type, typename Tag, typename Enabled> 
    struct enable_if_type_tagged
    {
        using type = std::enable_if_t<type_tagged<Type, Tag>(), Enabled>; 
    };
    template<typename Type, typename Tag, typename Enabled=void>
    using enable_if_type_tagged_t = 
        typename enable_if_type_tagged<Type, Tag, Enabled>::type; 


    template<typename Type, typename Tag, typename Enabled=void> 
    struct enable_if_type_tag_is_base_of
    {
        using type = std::enable_if_t<std::is_base_of_v<Tag, tag_t<Type>>, Enabled>; 
    };

    template<typename Type, typename Tag, typename Enabled=void>
    using enable_if_type_tag_is_base_of_t = 
        typename enable_if_type_tagged<Type, Tag, Enabled>::type; 

    template<typename Type, typename Tag, typename Enabled=void>
    using base_tag_t = 
        typename std::enable_if_t<std::is_base_of_v<Tag, tag_t<Type>>, Type>;

    template<typename Type, typename Enabled=void> 
    struct enable_if_type_arithmetic
    {
        using type = typename std::enable_if_t<std::is_arithmetic_v<Type>, Enabled>; 
    };
    template<typename Type, typename Enabled=void>
    using enable_if_type_arithmetic_t = typename enable_if_type_arithmetic<Type, Enabled>::type; 


    template<typename Type, typename Tag, typename ... TypeTagPairs> 
    constexpr bool types_tagged()
    {
        constexpr std::size_t N = sizeof ...(TypeTagPairs); 

        // Break if the number of types is odd.
        static_assert(N % 2 == 0, "Odd number of (Type, Tag) pairs.");

        constexpr bool notTagged = ! type_tagged<Type, Tag>(); 
        if constexpr (notTagged)
            return false; 
        else if constexpr (N >= 2) return types_tagged<TypeTagPairs...>(); 

        return true;
    }

    template<typename Enable, typename ... TagTypePairs> 
    using enable_if_types_tagged_t = 
        std::enable_if_t<types_tagged<TagTypePairs...>(), Enable>;

    template<typename Tag, typename Type, typename ... Types> 
    constexpr bool types_tagged_with_tag()
    {
        if constexpr (! type_tagged<Type, Tag>())
            return false; 
        else if constexpr (sizeof...(Types) >= 1) 
            types_tagged_with_tag<Tag, Types...>(); 

        return true;
    }

    template<typename Enable, typename Tag, typename ... Types> 
    using enable_if_types_tagged_with_tag_t = 
        std::enable_if_t<types_tagged_with_tag<Tag, Types...>(), Enable>;


    // Type is tagged if tag<Type>::type != void, which is the case for all
    // types that are not tagged. TM.
    
    template<typename Type>  
    constexpr bool type_is_tagged()
    {
        return ! std::is_same_v<typename tag<Type>::type, void>; 
    }

    template<typename Type> 
    using type_is_tagged_t = 
        std::enable_if_t<type_is_tagged<Type>(), void>;

    // Partial specialization of for STL containers. 
    template<typename Type, template <typename> typename Allocator>
    struct tag<std::vector<Type, Allocator<Type>>> 
    {
        using type = sequence_tag; 
    };

    template <typename Type, template <typename> typename Allocator>
    struct tag<std::list<Type, Allocator<Type>>>
    {
        using type = sequence_tag; 
    };

    template <typename Type, template <typename> typename Allocator>
    struct tag<std::forward_list<Type, Allocator<Type>>>
    {
        using type = sequence_tag; 
    };

    template <typename Type, template <typename> typename Allocator>
    struct tag<std::deque<Type, Allocator<Type>>>
    {
        using type = sequence_tag;
    };

    template <typename Type, std::size_t N>
    struct tag<std::array<Type, N>>
    {
        using type = sequence_tag; 
    };

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

} // End namespace geophase 

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

#endif

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