/************************************************************************/
/*                                                                      */
/*    vspline - a set of generic tools for creation and evaluation      */
/*              of uniform b-splines                                    */
/*                                                                      */
/*            Copyright 2017 - 2023 by Kay F. Jahnke                    */
/*                                                                      */
/*    Permission is hereby granted, free of charge, to any person       */
/*    obtaining a copy of this software and associated documentation    */
/*    files (the "Software"), to deal in the Software without           */
/*    restriction, including without limitation the rights to use,      */
/*    copy, modify, merge, publish, distribute, sublicense, and/or      */
/*    sell copies of the Software, and to permit persons to whom the    */
/*    Software is furnished to do so, subject to the following          */
/*    conditions:                                                       */
/*                                                                      */
/*    The above copyright notice and this permission notice shall be    */
/*    included in all copies or substantial portions of the             */
/*    Software.                                                         */
/*                                                                      */
/*    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND    */
/*    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES   */
/*    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND          */
/*    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT       */
/*    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,      */
/*    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      */
/*    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR     */
/*    OTHER DEALINGS IN THE SOFTWARE.                                   */
/*                                                                      */
/************************************************************************/

/// \file grok.cc
///
/// \brief demonstrates use of vspline::grok_type
///
/// 'grokking' functors is an extremely useful concept. It's applying
/// 'type erasure' to functors, so that the resulting functor - the
/// 'grokked' functor - behaves just like the 'grokkee' but hides the
/// 'grokkee's' type. So what's the point? In functional composition,
/// you build functors by combining several sub-functors. With rising
/// complexity of the costruct, the type becomes quite unmamageably
/// verbose, and every composite functor is unique, so it has to be
/// handled as a template argument. On the other hand, all you want
/// from the combined functor is to perform it's function on it's
/// argument(s), no matter what the internal workings are, and you
/// want to be able to 'slot in' different functors. With type
/// erasure, this becomes much more straightforward: after grokking,
/// the resulting functor is of vspline::grok_type, hiding the inner
/// workings, and all grok_type objects with the same argument
/// signature have *the same type*, so 'slotting in' becomes easy
/// and the types are manageable. Nevertheless, the optimizer can
/// 'look into' a grok_type and optimize the construct as if you'd
/// not grokked it. std::function does just that: the template
/// argument you pass (or which is inferred by ATD) just
/// specifies return type and argument types, and the resulting
/// object hides the inner workings. vspline::grok_type does not
/// just 'grok' a 'normal' function, but instead it 'groks' a
/// vspline::unary_functor, an object with an 'eval' function
/// with overloads for scalar and SIMD arguments. It does that
/// by using two std::functions internally, but most of the time
/// you can remain unaware of how it's done - you simply use the
/// factory function vspline::grok() and store the result in an
/// 'auto' variable.

#include <iostream>
#include <vspline/vspline.h>

/// grokkee_type is a vspline::unary_functor returning twice it's input

template < size_t vsize >
struct grokkee_type
: public vspline::unary_functor < double , double , vsize >
{
  template < typename IN , typename OUT >
  void eval ( const IN & in , OUT & out ) const
  {
    std::cout << "call to grokkee_type::eval, in = " << in ;
    out = in + in ;
    std::cout << ", out = " << out << std::endl ;
  }
} ;

/// 'regular' function template doing the same

template < typename T >
void twice ( const T & in , T & out )
{
  std::cout << "call to twice, in = " << in ;
  out = in + in ;
  std::cout << ", out = " << out << std::endl ;
}

/// test 'groks' a grokkee_type object 'grokkee' as a vspline::grok_type
/// and calls the resulting object, which behaves just as a grokkee_type.

template < size_t vsize >
void test()
{
  std::cout << "testing grok_type with vsize " << vsize << std::endl ;

  typedef vspline::grok_type < double , double , vsize > grok_t ;
  
  grokkee_type<vsize> grokkee ; 
  
  grok_t gk ( grokkee ) ;  

  double x = 1.0 ;
  
  std::cout << x << std::endl << " -> " << gk ( x ) << std::endl ;

  typedef typename grok_t::in_v argtype ;
  
  argtype xx = 1.0 ;
  
  std::cout << xx << std::endl << " -> " << gk ( xx ) << std::endl ;
}

int main ( int argc , char * argv[] )
{
  test<1>() ;
  test<4>() ;
  test<8>() ;
  
  // with vsize == 1, we can construct a grok_type with a single
  // std::function:

  typedef vspline::grok_type < double , double , 1 > gk1_t ;
  
  typedef gk1_t::eval_type ev_t ;
  ev_t ev = & twice<double> ;
  
  vspline::grok_type < double , double , 1 > gk1 ( ev ) ;
  std::cout << "calling grok_type with vsize 1" << std::endl ;
  gk1 ( 1.23 ) ;
  
  // with vsize greater than 1, we may pass another std::function
  // for vectorized evaluation. If we omit it, it will be generated
  // automatically, but then it won't use vector code.
  
  typedef vspline::grok_type < double , double , 8 > gk8_t ;
  
  typedef gk8_t::v_eval_type v_ev_t ;
  typedef gk8_t::in_v v8_t ;
  
  v_ev_t vev = & twice<v8_t> ;
  
  vspline::grok_type < double , double , 8 > gk8a ( ev , vev ) ;
  vspline::grok_type < double , double , 8 > gk8b ( ev ) ;

  typedef typename vspline::grok_type < double , double , 8 > :: in_v v_t ;
  std::cout << "calling grok_type using vector code" << std::endl ;
  gk8a ( v_t ( 1.23 ) ) ;
  std::cout << "calling grok_type using broadcasting" << std::endl ;
  gk8b ( v_t ( 1.23 ) ) ;
}
