#pragma once
#include "Vector.h"
#include <vector>
#include <optional>
#include <type_traits>
#include <iterator>
#include <boost/stl_interfaces/iterator_interface.hpp>
#include "fmt/format.h"

namespace Aftr
{
   //Author Scott Nykl
   // 
   // BLUF: Use the make_iter_* methods declared at the *bottom* of this file -- a user will rarely have to use
   // any of the classes/types defined here directly. Just expect iterator semantics and things should work.
   // 
   // Example:
   // 
   //      //NOTE: use the non-const make_*_iter functions to mutate the vertices if each triangle.
   // 
   //      auto idx = getIndexData();  //returns a std::vector<GLubyte> 
   //      auto vts = getVertData(); //returns a std::vector<Vector>
   //      auto it  = make_begin_const_iter( vts, idx ); //iterator pointing to beginning of triangles
   //      auto end = make_end_const_iter(   vts, idx ); //end iterator
   //      fmt::print( ss, "Number of Triangles {:d}\n", it.numTriangles() );
   //      fmt::print( ss, "Number of Indices   {:d}\n", it.numIndices() );
   //      fmt::print( ss, "Number of Vertices  {:d}\n", it.numVertices() );
   //      fmt::print( ss, "Number of Triangles in [it,end) {:d}\n", std::distance( it, end ) );
   //      for( auto it = make_begin_const_iter( vts, idx ); it != end; it++ )
   //      {
   //         fmt::print( ss, "Visiting Triangle {:s}", it->toString() );
   //      }
   // 
   //      ----- Iterate over a WO, like the F16 model loaded from disk
   //       auto begin = Aftr::make_begin_iter( f16a->getModel()->getCompositeVertexList(), f16a->getModel()->getCompositeIndexList() );
   //       auto end = Aftr::make_end_iter( f16a->getModel()->getCompositeVertexList(), f16a->getModel()->getCompositeIndexList() );
   //       for( int i = 0; begin != end; begin++, i++ )
   //       {
   //          fmt::print( "Triangle {:d} is {:s}\n", i, begin->toString() );
   //       }
   //
   // If the user wants to create an iterator around RAW pointers, ie Vector* and GLshort*, for example,
   // one must wrap the raw pointer with a lightweight ptr_iterVert_wrapper or ptr_iterIdx_wrapper. The
   // simplest way to do this is to call the make_*_iter_raw_ptr(...) functions defined at the bottom of
   // this file.
   // 
   // This files provides a generic set of functions to iterate across indexed geometry. 
   // Indexed Geometry is a set of Vertices that are indexed via a list of indices.
   // It is assumed the Vertices are in one random access list and the indices
   // are in another random access list. This could be two std::vectors of various types or just raw pointers.
   // See the helper make_iter_* at the end of this file.
   // See engine/src/aftr/gtest/AftrUtil_iter_test.cpp for simple examples on how to walk across
   // triangles that are fed to OpenGL.


   //This type_trait is used to determine the constness of the in passed std container iterators.
   // 
   //USAGE -- Computes if an STL iterator is const_iterator or iterator:
   // Sample Usage:
   //   using VI = std::vector<Vector>::iterator;
   //   using CVI = std::vector<Vector>::const_iterator;
   //   static_assert(!is_const_iterator_v<VI>, ""); // VI is NOT const
   //   static_assert(is_const_iterator_v<CVI>, ""); // CVI *is* const
   // 
   // 
   //https://stackoverflow.com/questions/16498674/check-modify-iterator-constness
   template<typename T, typename = void>
   struct is_const_iterator : std::false_type { };
 
   template<typename T>
   struct is_const_iterator<T,
      typename std::enable_if<
         std::is_const<
               typename std::remove_pointer<
                  typename std::iterator_traits<T>::pointer
                  >::type
               >::value
         >::type> : std::true_type { };
 
   template<typename T>
   inline constexpr bool is_const_iterator_v = is_const_iterator<T>::value;

   //A simple copy of a Tri_ref or const_Tri_ref. Since this is a copy, the user
   //owns the data and it no longer references into the Mesh. This is a simple
   //helper class that may be useful to a user.
   template< typename VEC3 >
   struct Tri
   {
      VEC3 va;
      VEC3 vb;
      VEC3 vc;

      constexpr std::string toString() const
      {
         return fmt::format( "[ {:s}->{:s}->{:s} ]", va.toString( 3 ), vb.toString( 3 ), vc.toString( 3 ) );
      }
   };

   //A Single Triangle (mutable) within a Mesh -- stores mutable references to the
   //three Vertices comprising the triangle's corners. Includes helper methods to
   //print, etc. VEC3 is a generic type but is most commonly expected to be a
   //VectorT<T>& such as a Vector& or VectorD&.
   template<typename VEC3 >
   struct Tri_ref
   {
      using value_type = VEC3&;
      using pointer = VEC3*; //used inside Face_Iter operator*()
      value_type va;
      value_type vb;
      value_type vc;
      
      constexpr std::string toString() const
      {
         return fmt::format( "[ {:s}->{:s}->{:s} ]", va.toString( 3 ), vb.toString( 3 ), vc.toString( 3 ) );
      }

      constexpr Tri<VEC3> toTri() const noexcept { return Tri<VEC3>{ va,vb,vc }; }
   };

   //A Single Triangle (non-mutable / const) within a Mesh -- stores const references to the
   //three Vertices comprising the triangle's corners. Includes helper methods to
   //print, etc. VEC3 is a generic type but is most commonly expected to be a
   //VectorT<T>& such as a Vector& or VectorD&.
   template<typename VEC3 >
   struct const_Tri_ref
   {
      using value_type = VEC3 const&;
      using pointer = VEC3 const*; //used inside Face_Iter operator*()
      value_type va;
      value_type vb;
      value_type vc;

      constexpr std::string toString() const
      {
         return fmt::format( "[ {:s}->{:s}->{:s} ]", va.toString( 3 ), vb.toString( 3 ), vc.toString( 3 ) );
      }

      constexpr Tri<VEC3> toTri() const noexcept { return Tri<VEC3>{ va, vb, vc }; }
   };

   //Simple tag to unambiguously differentiate between creating a begin iterator 
   //and an end iterator.
   namespace Face_of_tris_iterator_detail { enum class END_ITER_FLAG { end }; }

   //When the user iterators over indexed geometry using RAW POINTERS, this is a lightweight
   //proxy class that wraps the raw pointer to the vertices to provide the internally 
   //desired operator[] semantics for uniform access to the vertices. A user may
   //only use these when manually creating an iterator. See make_begin_iter_raw() or
   //make_end_iter_raw below for a usage example.
   template< typename ptr_to_array >
   struct ptr_iterVert_wrapper : public boost::stl_interfaces::iterator_interface
      <
         ptr_iterVert_wrapper<ptr_to_array>, //CRTP, static compile-time inheritance
         std::random_access_iterator_tag, //IteratorConcept used for this iterator
         std::remove_pointer_t<ptr_to_array> //this is the value_type
      >
   {
      private:
         ptr_to_array ptr;
      public:
         using pointer = ptr_to_array;
         using value_type = std::remove_pointer_t<ptr_to_array>;
         using reference = value_type&;

         constexpr ptr_iterVert_wrapper() noexcept : ptr( nullptr ) {}
         constexpr ptr_iterVert_wrapper( ptr_to_array ptr ) noexcept : ptr( ptr ) {}

         constexpr ptr_to_array const& operator*() const noexcept  { return ptr; }
         constexpr ptr_to_array& operator*() noexcept { return ptr; }

         constexpr reference const& operator[]( size_t idx ) const
         {
            pointer va = this->ptr + idx;
            return *va;
         }

         constexpr reference operator[]( size_t idx )
         {
            pointer va = this->ptr + idx; //auto will be some type of VectorT<T>
            return *va;
         }

         constexpr ptr_iterVert_wrapper& operator+=( std::ptrdiff_t i ) noexcept
         {
            ptr += i;
            return *this;
         }
         
         constexpr auto operator-( ptr_iterVert_wrapper other ) const noexcept
         {
            return ptr - other.ptr;
         }

         using base_type = boost::stl_interfaces::iterator_interface
            <
            ptr_iterVert_wrapper<ptr_to_array>, //make base_type visible in derived subclass
            std::random_access_iterator_tag,
            std::remove_pointer_t<ptr_to_array>
            >;
         using base_type::operator++;
   };

   //When the user iterators over indexed geometry using RAW POINTERS, this is a lightweight
   //proxy class that wraps the raw pointer to the indices to provide the internally 
   //desired operator[] semantics for uniform access to the indices. A user may
   //only use these when manually creating an iterator. See make_begin_iter_raw() or
   //make_end_iter_raw below for a usage example.
   template< typename ptr_to_array >
   struct ptr_iterIdx_wrapper : public boost::stl_interfaces::iterator_interface
      <
      ptr_iterIdx_wrapper<ptr_to_array>, //CRTP, static compile-time inheritance
      std::random_access_iterator_tag, //IteratorConcept used for this iterator
      std::remove_pointer_t<ptr_to_array>
      >
   {
   private:
      ptr_to_array ptr;
   public:
      using pointer = ptr_to_array;
      using value_type = std::remove_pointer_t<ptr_to_array>;

      constexpr ptr_iterIdx_wrapper() noexcept : ptr( nullptr ) {}
      constexpr ptr_iterIdx_wrapper( ptr_to_array ptr ) noexcept : ptr( ptr ) {}

      constexpr ptr_to_array const& operator*() const noexcept { return ptr; }
      constexpr ptr_to_array& operator*() noexcept { return ptr; }

      //This particular iter wraps a pointer. This dereference operator returns
      //the idx'th element inside the array pointed to by the wrapped pointer.
      constexpr auto operator[]( size_t idx ) const
      {
         return this->ptr[idx];
      }

      constexpr auto operator[]( size_t idx )
      {
         return this->ptr[idx];
      }

      constexpr ptr_iterIdx_wrapper& operator+=( std::ptrdiff_t i ) noexcept
      {
         ptr += i;
         return *this;
      }
      constexpr auto operator-( ptr_iterIdx_wrapper other ) const noexcept
      {
         return ptr - other.ptr;
      }

      using base_type = boost::stl_interfaces::iterator_interface
         <
         ptr_iterIdx_wrapper<ptr_to_array>, //make base_type visible in derived subclass
         std::random_access_iterator_tag,
         std::remove_pointer_t<ptr_to_array>
         >;
      using base_type::operator++;
   };

   //This is the PRIMARY CLASS within this file. To use this class, see the make_*_iter method defined
   //at the bottom of this file.
   //This iterator walks each triangle of a set of vertices in the order specified by the index list. This
   //iterators over a canonical list of OpenGL-esque indexed geometry.
   //  Assumes a set of parallel arrays (contiguous memory w/ size) for verts, colors, normals, tangents, etc.
   //  Assumes a integral index list that indexes into the parallel arrays to describe all
   //  properties of Vertex N at indexLst[N]
   // This uses boot's stl_interface wrapper to write stl compliant iterators
   //https://github.com/boostorg/stl_interfaces/blob/develop/example/node_iterator.cpp and
   //https://www.boost.org/doc/libs/1_80_0/doc/html/boost_stlinterfaces/tutorial___iterator_interface_.html
   template< typename Vert_Iter, typename Idx_Iter >
   struct Face_of_tris_iterator : public boost::stl_interfaces::iterator_interface
      <
         Face_of_tris_iterator<Vert_Iter, Idx_Iter>, //CRTP, static compile-time inheritance
         std::random_access_iterator_tag, //IteratorConcept used for this iterator

         //The template expression below evaluates to the value_type for our iterator.
         //If the Vert_Iter is const, then the value_type of a dereference will be a const_Tri_ref.
         //If the Vert_Iter is non-const, then the value_type of a dereference will be a Tri_ref.
         std::conditional_t< is_const_iterator_v<Vert_Iter>,
                        const_Tri_ref< typename Vert_Iter::value_type >,
                              Tri_ref< typename Vert_Iter::value_type > >
      >
   {

      using Tri_refVEC3 = std::conditional_t< is_const_iterator_v<Vert_Iter>,
                        const_Tri_ref< typename Vert_Iter::value_type >,
                              Tri_ref< typename Vert_Iter::value_type > >;

      using reference = Tri_refVEC3;

      constexpr Face_of_tris_iterator(
         Vert_Iter vert_iter_begin, Vert_Iter vert_iter_end, Idx_Iter idx_iter_begin, Idx_Iter idx_iter_end ) noexcept :
         vert_it( vert_iter_begin ), vert_it_end( vert_iter_end ), 
         idx_it( idx_iter_begin ), idx_it_end( idx_iter_end )
      {
         //creating a begin iterator with nullptr containers -- this is an invalid iterator, but shouldn't crash
         //if the user passes in any nullptr containers, we set the begin iterator's idx to -1 (which is also
         //used by the end iterator) -- this way, a loop will not crash or perform UB, it will simply never
         //iterate
         if( vert_it == vert_it_end )
            this->idx = -1;
      }

      constexpr Face_of_tris_iterator( Face_of_tris_iterator_detail::END_ITER_FLAG,
         Vert_Iter vert_iter_begin, Vert_Iter vert_iter_end, Idx_Iter idx_iter_begin, Idx_Iter idx_iter_end ) noexcept :
         vert_it( vert_iter_begin ), vert_it_end( vert_iter_end ),
         idx_it( idx_iter_begin ), idx_it_end( idx_iter_end )
      {
         if( vert_it == vert_it_end )
            this->idx = -1;
         else
            this->idx = std::distance( idx_it, idx_it_end ); //one idx larger than valid index
      }


      // Implicit conversion from an existing random_access_iterator with a
      // possibly different value type.  The enable_if logic here just enforces
      // that this constructor only participates in overload resolution when the
      // expression it_ = other.it_ is well-formed.
      //template<
      //   typename Vert_Iter, typename Idx_Iter,
      //   typename E = std::enable_if_t<
      //   std::is_convertible<Vert_Iter*, Vert_Iter*>::value> >
      ////copy constructor
      constexpr Face_of_tris_iterator( Face_of_tris_iterator const& toCopy ) noexcept :
                        vert_it( toCopy.vert_it ), vert_it_end( toCopy.vert_it_end ),
                        idx_it( toCopy.idx_it ), idx_it_end( toCopy.idx_it_end ),
                        idx( toCopy.idx )
      {         
      }

      constexpr Face_of_tris_iterator& operator=( Face_of_tris_iterator const& toAssign )
      {
         if( this != &toAssign ) [[likely]]
         {
            vert_it = toAssign.vert_it;
            vert_it_end = toAssign.vert_it_end;
            idx_it = toAssign.idx_it;
            idx_it_end = toAssign.idx_it_end;
            idx = toAssign.idx;
         }
         return *this;
      }
      
      //When the iterator is dereferenced: A non-const iterator returns a Tri_ref. A const iterator
      // returns a const_Tri_ref. Internally, a [const_]Tri_ref contains three references to 
      // VectorT<T> [const]&.
      constexpr Tri_refVEC3& operator*() const noexcept
      {
         auto ia = (this->idx_it)[idx + 0];
         auto ib = (this->idx_it)[idx + 1];
         auto ic = (this->idx_it)[idx + 2];

         this->curTri.emplace( (this->vert_it)[ia],
                               (this->vert_it)[ib],
                               (this->vert_it)[ic] );
         return *curTri;
      }
      
      //This iterator operators in terms of Triangles. So incrementing by ++ will move to the
      //next triangle -- which is three indices higher. So internally, idx always grows by
      //three which is the next adjacent face in the index list.
      constexpr Face_of_tris_iterator& operator++() noexcept
      {
         this->idx += 3;
         return *this;
      }

      //Moves the iterator forward by i Triangles.
      constexpr Face_of_tris_iterator& operator+=( std::ptrdiff_t i ) noexcept
      {
         //random access "move forward by i triangles". Which maps to i * 3 indices in the index list
         this->idx += i * 3;
         if( this->idx_it + idx > this->idx_it_end ) [[unlikely]]
         {
            fmt::print( "Walked beyond end of index list -- ilist[{:d}]\n", this->idx );
            std::abort();
         }
         return *this;
      }

      //Returns the number of triangles (distance) between this iterator and other.
      constexpr auto operator-( Face_of_tris_iterator other ) const noexcept
      {
         //returns the distance between this and other
         std::ptrdiff_t num_triangles_between = (this->idx - other.idx) / 3;
         if( other.idx_it != this->idx_it ) [[unlikely]]
         {
            fmt::print( "ERROR:  Two iterators are from different spans.\n" );
            std::abort();
         }

         return num_triangles_between;
      }
      //Always returns the number of triangles between the initially passed begin and end iterators.
      //For any given instance of this Face iterator, this value will always be the same.
      constexpr auto numTriangles() const noexcept { return std::distance( idx_it, idx_it_end ) / 3; }

      //Always returns the number of indices contained in the initially passed begin and end iterators.
      //For any given instance of this Face iterator, this value will always be the same.
      constexpr auto numIndices() const noexcept { return std::distance( idx_it, idx_it_end ); }
      
      //Always returns the number of Vertices (x,y,z triplets) contained in the initially
      //passed begin and end iterators.
      //For any given instance of this Face iterator, this value will always be the same.
      constexpr auto numVertices() const noexcept { return std::distance( vert_it, vert_it_end ); }

      //template<
      //   typename Vert_Iter, typename Idx_Iter,
      //   typename E = std::enable_if_t<
      //   std::is_convertible<Vert_Iter*, Vert_Iter*>::value> >
      friend constexpr bool operator==( Face_of_tris_iterator lhs, Face_of_tris_iterator rhs ) noexcept
      {
         return lhs.idx == rhs.idx;
      }

      // node_iterator_using_declaration
      using base_type = boost::stl_interfaces::iterator_interface
         < 
            Face_of_tris_iterator<Vert_Iter, Idx_Iter>, //make base_type visible in derived subclass
            std::random_access_iterator_tag,
            Tri_refVEC3
         >;
      using base_type::operator++;

   private:

      Vert_Iter vert_it;
      Vert_Iter vert_it_end;
      Idx_Iter idx_it;
      Idx_Iter idx_it_end;
      mutable std::optional< Tri_refVEC3 > curTri; //evaluates to either Tri_ref<VectorT<T> or const_Tri_ref<VectorT<T>>
      std::int64_t idx = 0;

      // This friendship is necessary to enable the implicit conversion
      // constructor above to work.
      template< typename Const_Vert_Iter, typename Const_Idx_Iter>
      friend struct Face_of_tris_iterator;
   };

   //Helpful user functions to traverse via stl-compliant iterators

   //----------------- Iterator-based const and non-const factory make_iter funcs
   ///Returns a BEGIN iterator to first triangle in collection. 
   ///If all in passed iterators are non-const, returned iterater is non-const.
   ///If all in passed iterators are const, returned iterator is const.
   /// If your data is stored in a std::vector<VectorT<T>> verts and std::vector<Integral_type> indices,
   /// consider using the simpler make_*_iter( std::vector<...> [const]& verts, std::vector<...> [const]& indices )
   template< typename Vert_Iter_m, typename Idx_Iter_m>
   auto make_begin_iter( Vert_Iter_m vert_begin_iter, Vert_Iter_m vert_end_iter,
                         Idx_Iter_m idx_begin_iter, Idx_Iter_m idx_end_iter )
   {
      return Face_of_tris_iterator( vert_begin_iter, vert_end_iter, idx_begin_iter, idx_end_iter );
   }
   ///Returns an END iterator beyond the last triangle in collection. 
   ///If all in passed iterators are non-const, returned iterater is non-const.
   ///If all in passed iterators are const, returned iterator is const.
   /// If your data is stored in a std::vector<VectorT<T>> verts and std::vector<Integral_type> indices,
   /// consider using the simpler make_*_iter( std::vector<...> [const]& verts, std::vector<...> [const]& indices )
   template< typename Vert_Iter_m, typename Idx_Iter_m>
   auto make_end_iter( Vert_Iter_m vert_begin_iter, Vert_Iter_m vert_end_iter,
                       Idx_Iter_m idx_begin_iter, Idx_Iter_m idx_end_iter )
   {
      return Face_of_tris_iterator( Face_of_tris_iterator_detail::END_ITER_FLAG{}, vert_begin_iter, vert_end_iter, idx_begin_iter, idx_end_iter );
   }
   //-----------------


   //----------------- Iterator-based const and non-const factory make_iter funcs FOR RAW POINTERS
   ///Returns a BEGIN iterator to first triangle in collection. 
   ///If all in passed iterators are non-const, returned iterater is non-const.
   ///If all in passed iterators are const, returned iterator is const.
   /// If your data is stored in a std::vector<VectorT<T>> verts and std::vector<Integral_type> indices,
   /// consider using the simpler make_*_iter( std::vector<...> [const]& verts, std::vector<...> [const]& indices )
   template< typename Vert_Ptr, typename Idx_Ptr>
   auto make_begin_iter_raw( Vert_Ptr vert_begin, Vert_Ptr vert_end,
                             Idx_Ptr idx_begin, Idx_Ptr idx_end )
   {
      return Face_of_tris_iterator( ptr_iterVert_wrapper(vert_begin), ptr_iterVert_wrapper( vert_end ),
                                    ptr_iterIdx_wrapper( idx_begin ), ptr_iterIdx_wrapper( idx_end ) );
   }
   ///Returns an END iterator beyond the last triangle in collection. 
   ///If all in passed iterators are non-const, returned iterater is non-const.
   ///If all in passed iterators are const, returned iterator is const.
   /// If your data is stored in a std::vector<VectorT<T>> verts and std::vector<Integral_type> indices,
   /// consider using the simpler make_*_iter( std::vector<...> [const]& verts, std::vector<...> [const]& indices )
   template< typename Vert_Ptr, typename Idx_Ptr>
   auto make_end_iter_raw( Vert_Ptr vert_begin, Vert_Ptr vert_end,
                           Idx_Ptr idx_begin, Idx_Ptr idx_end )
   {
      return Face_of_tris_iterator( Face_of_tris_iterator_detail::END_ITER_FLAG{},
                                    ptr_iterVert_wrapper( vert_begin ), ptr_iterVert_wrapper( vert_end ),
                                    ptr_iterIdx_wrapper( idx_begin ), ptr_iterIdx_wrapper( idx_end ) );
   }
   //-----------------


   //----------------- std::vector<...>-based const and non-const factory make_iter funcs

   ///Returns a BEGIN iterator that will traverse the triangles specified
   ///in the vertex & index lists passed in. If the containers are const, the iterator
   /// **WILL be CONST**. If the containers are mutable, this method will return a
   /// mutable iterator allowing the user to mutate the triangles.
   template< typename Vert_Cont_m, typename Idx_Cont_m >
   auto make_begin_iter( Vert_Cont_m& vert_container, Idx_Cont_m& idx_container )
   {
      return Face_of_tris_iterator( vert_container.begin(), vert_container.end(),
                                    idx_container.begin(), idx_container.end() );
   }
   ///Returns an END iterator pointing beyond the last triangle specified
   ///in the vertex & index lists passed in. If the containers are const, the iterator
   /// **WILL be CONST**. If the containers are mutable, this method will return a
   /// mutable iterator allowing the user to mutate the triangles
   template< typename Vert_Cont_m, typename Idx_Cont_m >
   auto make_end_iter( Vert_Cont_m& vert_container, Idx_Cont_m& idx_container )
   {
      return Face_of_tris_iterator( Face_of_tris_iterator_detail::END_ITER_FLAG{},
                                    vert_container.begin(), vert_container.end(),
                                    idx_container.begin(), idx_container.end() );
   }
   //-----------------

   //-----------------
   ///Returns a CONST BEGIN iterator over STL container
   template< typename Vert_Cont_m, typename Idx_Cont_m >
   auto make_begin_const_iter( Vert_Cont_m& vert_container, Idx_Cont_m& idx_container )
   {
      return Face_of_tris_iterator( vert_container.cbegin(), vert_container.cend(),
                                    idx_container.cbegin(), idx_container.cend() );
   }
   ///Returns a CONST END iterator over STL container
   template< typename Vert_Cont_m, typename Idx_Cont_m >
   auto make_end_const_iter( Vert_Cont_m& vert_container, Idx_Cont_m& idx_container )
   {
      return Face_of_tris_iterator( Face_of_tris_iterator_detail::END_ITER_FLAG{},
         vert_container.cbegin(), vert_container.cend(),
         idx_container.cbegin(), idx_container.cend() );
   }
   //-----------------
}