/************************************************************************/
/*                                                                      */
/*    vspline - a set of generic tools for creation and evaluation      */
/*              of uniform b-splines                                    */
/*                                                                      */
/*            Copyright 2015 - 2023 by Kay F. Jahnke                    */
/*                                                                      */
/*    The git repository for this software is at                        */
/*                                                                      */
/*    https://bitbucket.org/kfj/vspline                                 */
/*                                                                      */
/*    Please direct questions, bug reports, and contributions to        */
/*                                                                      */
/*    kfjahnke+vspline@gmail.com                                        */
/*                                                                      */
/*    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 xel_of_vector.h

    \brief overloads for arithmetic operators involving 'xel's
           of a VECTOR_TYPE like vspline::simd_type or Vc::SimdArray

    We have two different notions of aggregation here: first, there is
    the notion of an SIMD vector, expressed in the class template
    which is passed in as 'VECTOR_TYPE'. Then we have the notion of an
    aggregate of 'several items of equal type', like the three channels
    of a pixel. This is expressed by the class template in 'XEL_TYPE',
    in short a 'xel'. vspline uses vigra::TinyVector as XEL_TYPE.

    For efficient horizontal vectorization, a xel's vectorized form is
    a xel of vectors - so, for example, if we have vigra::TinyVector
    as XEL_TYPE and vspline::simd_type as VECTOR_TYPE, the vectorized
    form is vigra::TinyVector < vspline::simd_type < T , S > , N >
  
    vspline uses two SIMD types: the first one, vspline::simd_type,
    is vspline's 'own' implementation of an SIMD type. The second one is
    Vc::SimdArray - which is only used if the code is compiled with
    USE_VC defined. Since the required definitions to provide 'xels' of
    both vector types are the same, we use this common header,
    which is included once with VECTOR_TYPE defined to vspline::simd_type
    and, if USE_VC is defined, once to Vc::SimdArray. I don't really like
    this type of construct - I'd much rather introduce the vector type as
    a class template - but Vc::SimdArray has additional template arguments
    on top of the obvious typename and size_t which make it's template
    argument list incompatible with vspline::simd_type's, and when it
    comes to 'XEL', vigra's TinyVector uses int where most containers
    use std::size_t for the number of T in the aggregate. Building up
    the definitions as we do here, the differences in template argument
    pattern don't matter, because we never refer to the class template
    itself but only instantiate it, and the instantiation works if the
    template arguments are compatible and defaulted template arguments
    may be omitted.

    To reiterate: if we use a template like

    template < template class xel_t < typename T , std::size_t S > , ... >
    
    we can't instantiate it with, for example, vigra::TinyVector as xel_t,
    because vigra::TinyVector takes int and not std::size_t for it's size.
    It's possible to work around this issue by using constructs like

    template < typename sz_t , 
               template class xel_t < typename T , sz_t S > , ... >

    but this only covers the differently-typed template argument and the
    issue of additional template arguments still remains to be fixed,
    requiring even more template artistry. All in all, such finessing
    becomes very verbose and hard to comprehend, so I stick with this
    simple way of avoiding it.
    
    This implementation uses simple overloads for the intended operations,
    which is less powerful and less comprehensive than using promote traits,
    but it suffices up to the level of vectorized 'xel' data and is
    completely explicit about what is defined and what is not. And
    the code needed to set up the desired functionality is straightforward
    and easy to comprehend - and maintain.

    Note that the code is very strict about the value_types involved: the
    overloads require all operands to share the same value_type, so there
    is no interoperability, e.g, between float- and double-based objects
    or differently sized integers. To move values to differently-typed
    objects, you'll have to loop over the xel's elements - the vector
    data provide assignments across type boundaries.

    The header defines augmented assignments and unary and binary operators
    between:
    
    - XEL_TYPE < VECTOR_TYPE < T , S > , N >

    and

    - XEL_TYPE < VECTOR_TYPE < T , S > , N >
    - XEL_TYPE < T , N >
    - VECTOR_TYPE < T , S >
    - T

    This header is optional and won't be automatically pulled in when
    including other vspline headers.
**/

namespace vigra
{
// abbreviated notation

#define VECTOR VECTOR_TYPE < T , S >

#define XEL_OF_T XEL_TYPE < T , N >

#define XEL_OF_VECTOR XEL_TYPE < VECTOR , N >

// test whether a type holds integral or boolan value_type

#ifndef XEL_OF_V_COMMON // shield these definitions from repeated include

template < typename T >
struct holds_integral
: public std::is_integral < T >
{ } ;

template < typename T >
struct holds_bool
: public std::false_type
{ } ;

template<>
struct holds_bool < bool >
: public std::true_type
{ } ;

#define XEL_OF_V_COMMON
#endif

template < typename T , std::size_t S >
struct holds_integral < VECTOR >
: public holds_integral < T >
{ } ;

template < typename T , std::size_t S >
struct holds_bool < VECTOR >
: public holds_bool < T >
{ } ;

// We start out with constraints for integral and boolean types

#define INTEGRAL_ONLY \
  static_assert ( holds_integral < T > :: value , \
                  "this operation is only allowed for integral types" ) ;

#define BOOL_ONLY \
  static_assert ( holds_bool < T > :: value , \
                  "this operation is only allowed for booleans" ) ;

#define TPL_ARGS template < class T , std::size_t S , int N >

// next we go through the motions of explicitly coding templates for
// all operator functions we intend to provide. This is done with
// macros to avoid copy-and-paste errors.

// VECTOR_EQOPFUNC applies an augmented assignment to a xel
// of VECTOR_TYPE. rhs can be the same xel, the contained vector,
// the vector's value_type. or a xel of N value_type

#define VECTOR_EQOPFUNC(OPEQ,OPNAME,CONSTRAINT) \
TPL_ARGS inline XEL_OF_VECTOR & \
OPNAME (       XEL_OF_VECTOR & lhs , \
         const XEL_OF_VECTOR & rhs ) \
{ \
  CONSTRAINT \
  for ( int i = 0 ; i < N ; i++ ) \
    lhs [ i ] OPEQ rhs [ i ] ; \
  return lhs ; \
} \
TPL_ARGS inline XEL_OF_VECTOR & \
OPNAME (       XEL_OF_VECTOR & lhs , \
         const VECTOR & rhs ) \
{ \
  CONSTRAINT \
  for ( int i = 0 ; i < N ; i++ ) \
    lhs [ i ] OPEQ rhs ; \
  return lhs ; \
} \
TPL_ARGS inline XEL_OF_VECTOR & \
OPNAME (       XEL_OF_VECTOR & lhs , \
         const T & rhs ) \
{ \
  CONSTRAINT \
  for ( int i = 0 ; i < N ; i++ ) \
    lhs [ i ] OPEQ rhs ; \
  return lhs ; \
} \
TPL_ARGS inline XEL_OF_VECTOR & \
OPNAME (       XEL_OF_VECTOR & lhs , \
         const XEL_OF_T & rhs ) \
{ \
  CONSTRAINT \
  for ( int i = 0 ; i < N ; i++ ) \
    lhs [ i ] OPEQ rhs [ i ] ; \
  return lhs ; \
}

// VECTOR_OPFUNC defines binary operations involving a xel of
// VECTOR_TYPE on the rhs or lhs, combined with the same, a VECTOR,
// the VECTOR's value_type or a xel of N value_type. The operation
// uses the augmented assignments defined above.

#define VECTOR_OPFUNC(OPEQ,OPNAME,OPEQNAME,CONSTRAINT) \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const XEL_OF_VECTOR & lhs , \
         const XEL_OF_VECTOR & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
} \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const XEL_OF_VECTOR & lhs , \
         const VECTOR  & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
} \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const XEL_OF_VECTOR & lhs , \
         const T & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
} \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const XEL_OF_VECTOR & lhs , \
         const XEL_OF_T & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
} \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const VECTOR  & lhs , \
         const XEL_OF_VECTOR & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
} \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const T & lhs , \
         const XEL_OF_VECTOR & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
} \
TPL_ARGS inline XEL_OF_VECTOR \
OPNAME ( const XEL_OF_T & lhs , \
         const XEL_OF_VECTOR & rhs ) \
{ \
  CONSTRAINT \
  XEL_OF_VECTOR result ( lhs ) ; \
  result OPEQ rhs ; \
  return result ; \
}

#define VECTOR_OP(OPEQ,OPNAME,OPEQNAME,CONSTRAINT) \
VECTOR_EQOPFUNC ( OPEQ , OPEQNAME , CONSTRAINT ) \
VECTOR_OPFUNC ( OPEQ , OPNAME , OPEQNAME , CONSTRAINT )

// showtime: roll the macros out for arithmetic operators.
// +, -, * and / are defined for every value_type

VECTOR_OP ( += , operator+ , operator+= , )
VECTOR_OP ( -= , operator- , operator-= , )
VECTOR_OP ( *= , operator* , operator*= , )
VECTOR_OP ( /= , operator/ , operator/= , )

// %, &, |, ^, <<, and >> are only defined for integral operands.
// the limitation is enforced with a static_assert, which should produce
// a useful error message

VECTOR_OP ( %= , operator% , operator%= , INTEGRAL_ONLY )
VECTOR_OP ( &= , operator& , operator&= , INTEGRAL_ONLY )
VECTOR_OP ( |= , operator| , operator|= , INTEGRAL_ONLY )
VECTOR_OP ( ^= , operator^ , operator^= , INTEGRAL_ONLY )
VECTOR_OP ( <<= , operator<< , operator<<= , INTEGRAL_ONLY )
VECTOR_OP ( >>= , operator>> , operator>>= , INTEGRAL_ONLY )

// finally the unary operators:
// VECTOR_UNOP applies a unary operator to a xel of vectors

#define VECTOR_UNOP(OPER,OPNAME,CONSTRAINT) \
TPL_ARGS inline XEL_OF_VECTOR & \
OPNAME ( XEL_OF_VECTOR & tv ) \
{ \
  CONSTRAINT \
  for ( int i = 0 ; i < N ; i++ ) \
    OPER ( tv [ i ] ) ; \
  return tv ; \
} ;

VECTOR_UNOP ( - , operator- , )
VECTOR_UNOP ( ~ , operator~ , INTEGRAL_ONLY )
VECTOR_UNOP ( ! , operator! , BOOL_ONLY ) // TODO may omit this one

// undef the macros

#undef VECTOR_UNOP
#undef VECTOR_OP
#undef VECTOR_EQOPFUNC
#undef VECTOR_OPFUNC
#undef INTEGRAL_ONLY
#undef BOOL_ONLY
#undef VECTOR
#undef XEL_OF_VECTOR

} ;
