#include <memory>
#include <vector>
#include "AftrOpenGLIncludes.h"

namespace Aftr
{

   //Scott Nykl -- See IndexedGeometry_TE.h for an example of how to use this efficient, elegant,
   //safe IndexedGeometry (over the earlier memory-error prone IndexedGeometry*).

class IndexedGeometry_TEIFace //type erased methods any drawable must implement
{
public:
   /// PRIMARY INTERFACE supported by this IndexedGeometry_TEIFace... No virtual methods specified here
   /// at all.
   void render() const noexcept { if( ptr ) return ptr->render(); }
   void renderGL32() const noexcept { if( ptr ) return ptr->renderGL32(); }
   void renderCompatibility() const noexcept { if( ptr ) return ptr->renderCompatibility(); }
   void renderSelection( GLubyte red, GLubyte green, GLubyte blue ) const noexcept { if( ptr ) return ptr->renderSelection( red,green,blue); }



   //----Implementation details that give "intelligent" copy and move behaviors

   /// MyModelTE const& m = typeErased.get_erased_type<MyModelTE>();
   template<typename T>
   const T& get_erased_type() const
   {
      const T& ref = static_cast<ModelTE<T>&>(*this->ptr).obj;
      return ref;
   }

   /// MyModelTE const& m = typeErased.get_erased_type<MyModelTE>();
   template<typename T>
   T& get_erased_type()
   {
      T& ref = static_cast<ModelTE<T>&>(*this->ptr).obj;
      return ref;
   }

   operator bool() const noexcept { return this->ptr != nullptr; }
   bool empty() const noexcept { return (*this) ? true : false; }

   IndexedGeometry_TEIFace() : ptr( nullptr )
   {
      //std::cout << LOC << ": ctor\n";
   }

   ~IndexedGeometry_TEIFace()
   {
      //std::cout << LOC << ": ~IndexedGeometry_TEIFace\n";
   }

   template <typename T, typename U>
   struct decay_equiv : std::is_same<typename std::decay<T>::type, U>::type {};
   template< typename T, typename U >
   inline static constexpr bool decay_equiv_v = decay_equiv< T, U >::value;

   template<typename T>
   IndexedGeometry_TEIFace( T&& moveT ) noexcept requires( !decay_equiv_v<T, IndexedGeometry_TEIFace> ) //Case where IndexedGeometry_TEIFace( T&& ) is called with a T such that T is NOT another IndexedGeometry_TEIFace
   {
      //std::cout << LOC << ": tmpl move ctor\n";
      ptr = std::unique_ptr<Concept>( new ModelTE<T>( std::forward<T>( moveT ) ) );
   }

   template< typename T> //supports use case for IndexedGeometry_TEIFace p = std::move(myCircle);
   IndexedGeometry_TEIFace& operator=( T&& toMoveT ) requires( !decay_equiv_v<T, IndexedGeometry_TEIFace> ) //const& where T is NOT a IndexedGeometry_TEIFace
   {
      //std::cout << LOC << ": tmpl operator= && move assign\n";
      ptr = std::unique_ptr<Concept>( new ModelTE<T>( std::forward<T>( toMoveT ) ) );
      return *this;
   }

   template< typename T> //supports use case for IndexedGeometry_TEIFace p{ a_const_Square } }; //Square is a T
   IndexedGeometry_TEIFace( T const& copyT ) requires(!decay_equiv_v<T, IndexedGeometry_TEIFace>) //const& where T is NOT a IndexedGeometry_TEIFace
   {
      //std::cout << LOC << ": tmpl copy ctor\n";
      ptr = std::unique_ptr<Concept>( new ModelTE<T>( copyT ) );
   }

   template< typename T> //supports use case for IndexedGeometry_TEIFace p{ a_non_const_Square } }; //Square is a T
   IndexedGeometry_TEIFace( T& copyT ) requires(!decay_equiv_v<T, IndexedGeometry_TEIFace>) //const& where T is NOT a IndexedGeometry_TEIFace
   {
      //std::cout << LOC << ": tmpl copy ctor\n";
      ptr = std::unique_ptr<Concept>( new ModelTE<T>( copyT ) );
   }

   template< typename T> //supports use case for IndexedGeometry_TEIFace p = Square{}; //Square is a T
   IndexedGeometry_TEIFace& operator=( T const& copyT ) requires(!decay_equiv_v<T, IndexedGeometry_TEIFace>) //const& where T is NOT a IndexedGeometry_TEIFace
   {
      //std::cout << LOC << ": tmpl operator= copy assign ctor\n";
      ptr = std::unique_ptr<Concept>( new ModelTE<T>( copyT ) );
      return *this;
   }

   IndexedGeometry_TEIFace( IndexedGeometry_TEIFace const& toCopy ) //supports use case for const IndexedGeometry_TEIFace& ref = v[0]; IndexedGeometry_TEIFace z( ref );
   {
      //std::cout << LOC << "Vanilla copy ctor IndexedGeometry_TEIFace( IndexedGeometry_TEIFace const& toCopy )...\n";
      this->ptr = toCopy.ptr ? toCopy.ptr->clone() : nullptr;
   }

   IndexedGeometry_TEIFace( IndexedGeometry_TEIFace&& toMove ) noexcept
   {
      //std::cout << LOC << ": Mov ctor...\n";
      this->ptr = std::move( toMove.ptr );
      toMove.ptr = nullptr;
   };

   IndexedGeometry_TEIFace& operator=( IndexedGeometry_TEIFace const& toCopyAssign )
   {
      std::printf( "%s: WARNING copyAssignClone issued, expensive operation if each frame...\n", AFTR_FILE_LINE_STR );
      if( this != &toCopyAssign )
         this->ptr = toCopyAssign.ptr ? toCopyAssign.ptr->clone() : nullptr;
      return *this;

   }

   IndexedGeometry_TEIFace& operator=( IndexedGeometry_TEIFace&& toMoveAssign ) noexcept
   {
      if( this != &toMoveAssign )
      {
         this->ptr = std::move( toMoveAssign.ptr );
         toMoveAssign.ptr = nullptr;
      }
      return *this;
   }

private:
   struct Concept
   {
      /// PRIMARY INTERFACE supported by this IndexedGeometry_TEIFace... all methods should be virtual
      virtual void render() const noexcept = 0;
      virtual void renderGL32() const noexcept = 0; //and duplicate the interface in this Concept
      virtual void renderCompatibility() const noexcept = 0;
      virtual void renderSelection( GLubyte red, GLubyte green, GLubyte blue ) const noexcept = 0;

      virtual std::unique_ptr<Concept> clone() const = 0;


      // Done w/ PRIMARY INTERFACE
      Concept& operator=( Concept const& toCopyAssign ) = delete;
      Concept& operator=( Concept&& toMoveAssign ) = delete;
      Concept( Concept const& toCopy ) = delete;
      Concept( Concept&& toMove ) noexcept = delete;
      

      Concept() = default;
      virtual ~Concept() = default;
   };
   std::unique_ptr<Concept> ptr;

   template< typename T >
   struct ModelTE : public Concept
   {
      ModelTE& operator=( ModelTE const& toCopyAssign ) = delete;
      ModelTE& operator=( ModelTE&& toMoveAssign ) noexcept = delete;
      ModelTE() = delete;

      ModelTE( T const& toCopy ) : obj( toCopy )
      {
         //std::cout << LOC << ": ModelTE copy ctor...\n";
      }

      ModelTE( T&& value ) noexcept : obj( std::forward<T>( value ) )
      {
         //std::cout << LOC << ": ModelTE move ctor...\n";
      }

      virtual ~ModelTE()
      {
         //std::cout << LOC << ": ~ModelTE...\n";
      }


      //PRIMARY INTERFACE-------------
      virtual void render() const noexcept override { obj.render(); };
      virtual void renderGL32() const noexcept override { obj.renderGL32(); };
      virtual void renderCompatibility() const noexcept override { obj.renderCompatibility(); }
      virtual void renderSelection( GLubyte red, GLubyte green, GLubyte blue ) const noexcept override { obj.renderSelection( red,green,blue ); }

      //Requires that T is copy-able
      virtual std::unique_ptr<Concept> clone() const override
      {
         T copy( obj );
         return std::make_unique<ModelTE>( std::forward<T>(copy) );
      }

      //PRIMARY INTERFACE-------------

      T obj;
      using type = T;
   };
};
}