/************************************************************************/
/*                                                                      */
/*    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 domain.h

    \brief code to perform combined scaling and translation on coordinates
    
    This is what might be called collateral code, class domain is not
    currently used in vspline.

    A common requirement is to map coordinates in one range to another
    range, effectively performing a combined scaling and translation.
    Given incoming coordinates in a range [ in_low , in_high ] and a
    desired range for outgoing coordinates of [ out_low , out_high ],
    and an incoming coordinate c, a vspline::domain performs this
    operation:
    
    c' = ( c - in_low ) * scale + out_low
    
    where
    
    scale = ( out_high - out_low ) / ( in_high - in_low )
    
    The code can handle arbitrary dimensions, float and double coordinate
    elementary types, and, optionally, it can perform vectorized operations
    on vectorized coordinates.

    vspline::domain is derived from vspline::unary_functor and can be
    used like any other vspline::unary_functor. A common use case would
    be to access a vspline::evaluator with a different coordinate range
    than the spline's 'natural' coordinates (assuming a 1D spline of floats):
    
    auto _ev = vspline::make_safe_evaluator ( bspl ) ;
    auto ev = vspline::domain ( bspl , 0 , 100 ) + _ev ;
    
    ev.eval ( coordinate , result ) ;
    
    Here, the domain is built over the spline with an incoming range
    of [ 0 , 100 ], so evaluating at 100 will be equivalent to evaluating
    _ev at bspl.upper_limit().
*/

#ifndef VSPLINE_DOMAIN_H
#define VSPLINE_DOMAIN_H

#include "unary_functor.h"
#include <assert.h>

namespace vspline
{
/// class domain is a coordinate transformation functor. It provides
/// a handy way to translate an arbitrary range of incoming coordinates
/// to an arbitrary range of outgoing coordinates. This is done with a
/// linear translation function. if the source range is [s0,s1] and the
/// target range is [t0,t1], the translation function s->t is:
///
/// t = ( s - s0 ) * ( t1 - t0 ) / ( s1 - s0 ) + t0 
///
/// In words: the target coordinate's distance from the target range's
/// lower bound is proportional to the source coordinate's distance
/// from the source range's lower bound. Note that this is *not* a
/// gate function: the domain will accept any incoming value and
/// perform the shift/scale operation on it; incoming values outside
/// [ in_low , in_high ] will produce outgoing values outside
/// [ out_low , out_high ].
///
/// The first constructor takes s0, s1, t0 and t1. With this functor,
/// arbitrary mappings of the form given above can be achieved.
/// The second constructor takes a vspline::bspline object to obtain
/// t0 and t1. These are taken as the spline's 'true' range, depending
/// on it's boundary conditions: for periodic splines, this is [0...M],
/// for REFLECT Bcs it's [-0.5,M-0,5], and for 'normal' splines it's
/// [0,M-1]. s0 and s1, the start and end of the domain's coordinate range,
/// can be passed in and default to 0 and 1, which constitutes 'normalized'
/// spline coordinates, where 0 is mapped to the lower end of the 'true'
/// range and 1 to the upper.
///
/// class domain is especially useful for situations where several b-splines
/// cover the same data in different resolution, like in image pyramids.
/// If these different splines are all evaluated with a domain chained to the
/// evaluator which uses a common domain range, they can all be accessed with
/// identical coordinates, even if the spline shapes don't match isotropically.
/// Note, though, that domain objects created with b-splines with REFLECT
/// or PERIODIC boundary conditions won't match unless the splines have the
/// same size because the spline's bounds won't coincide for differently
/// sized splines. You may want to create the domain object from coordinate
/// values in this case.
///
/// The evaluation routine in class domain_type makes sure that incoming
/// values in [ in_low , in_high ] will never produce outgoing values
/// outside [ out_low , out_high ]. If this guarantee is not needed, the
/// 'raw' evaluation routine _eval can be used instead. with _eval, output
/// may overshoot out_high slightly.
///
/// I should mention libeinspline here, which has this facility as a fixed
/// feature in it's spline types. I decided to keep it separate and create
/// class domain instead for those cases where the functionality is needed.

template < typename coordinate_type ,
           size_t _vsize = vspline::vector_traits<coordinate_type>::vsize
         >
struct domain_type
: public vspline::unary_functor < coordinate_type ,
                                  coordinate_type ,
                                  _vsize > ,
  public vspline::callable
         < domain_type < coordinate_type , _vsize > ,
           coordinate_type ,
           coordinate_type ,
           _vsize
         >
{
  typedef vspline::unary_functor < coordinate_type ,
                                   coordinate_type ,
                                   _vsize >
    base_type ;
    
  using base_type::dim_in ;
  using base_type::vsize ;  
  using typename base_type::in_type ;
  using typename base_type::in_ele_type ;
  using typename base_type::out_type ;
  using typename base_type::in_v ;
  using typename base_type::in_ele_v ;
  using typename base_type::out_v ;
  
  typedef typename base_type::in_ele_type rc_type ;
  typedef typename base_type::in_nd_ele_type nd_rc_type ;
  typedef typename vigra::TinyVector < rc_type , dim_in > limit_type ;

  // internally, we work with definite TinyVectors, in order
  // to code consistently for any dimensionality:
  
  const limit_type out_low , in_low , out_high , in_high ;
  const limit_type scale ;
  
  /// constructor taking the lower and upper fix points
  /// for incoming and outgoing values. If coordinate_type
  /// is fundamental, assigning to the limit_type members
  /// produces the desired TinyVectors of one fundamental.
  
  domain_type ( const coordinate_type & _in_low ,
                const coordinate_type & _in_high ,
                const coordinate_type & _out_low ,
                const coordinate_type & _out_high )
  : in_low ( _in_low ) ,
    out_low ( _out_low ) ,
    in_high ( _in_high ) ,
    out_high ( _out_high ) ,
    scale ( ( _out_high - _out_low ) / ( _in_high - _in_low ) )
  { }

  // KFJ 2019-04-17 Modified arguments to domain_type's c'tor to
  // take a bspline object as lower reference, as upper reference
  // and for both references. The previous code taking a single
  // bspine object was confusing, because it took the spline and the
  // limits 'the other way round'. Also removed defaults for limit
  // arguments.

  /// constructor taking the fix points for outgoing values
  /// from a bspline object, and the incoming lower and upper
  /// fix points explicitly

  template < class bspl_type >
  domain_type ( const bspl_type & bspl ,
                const coordinate_type & _out_low ,
                const coordinate_type & _out_high )
  : in_low ( bspl.lower_limit() ) ,
    in_high ( bspl.upper_limit() ) ,
    out_low ( _out_low ) ,
    out_high ( _out_high ) ,
    scale (   ( _out_high - _out_low )
            / ( bspl.upper_limit() - bspl.lower_limit() ) )
  { }
  
  template < class bspl_type >
  domain_type ( const coordinate_type & _in_low ,
                const coordinate_type & _in_high ,
                const bspl_type & bspl )
  : in_low ( _in_low ) ,
    in_high ( _in_high ) ,
    out_low ( bspl.lower_limit() ) ,
    out_high ( bspl.upper_limit() ) ,
    scale (   ( bspl.upper_limit() - bspl.lower_limit() )
            / ( _in_high - _in_low ) )
  { }
  
  template < class bspl_type_1 , class bspl_type_2 >
  domain_type ( const bspl_type_1 & bspl_in ,
                const bspl_type_2 & bspl_out )
  : in_low ( bspl_in.lower_limit() ) ,
    in_high ( bspl_in.upper_limit() ) ,
    out_low ( bspl_out.lower_limit() ) ,
    out_high ( bspl_out.upper_limit() ) ,
    scale (   ( bspl_out.upper_limit() - bspl_out.lower_limit() )
            / ( bspl_in.upper_limit() - bspl_in.lower_limit() ) )
  { }
  
private:

  /// 'polish' simply checks if in is == in_high and returns
  /// out_high for that case. This looks silly, but depending
  /// on the scaling factor, out may overshoot out_high if
  /// in == in_high. Hence this routine.

  void polish ( const in_type & in ,
                out_type & out ,
                int d
              ) const
  {
    out = ( in == in_high[d] ) ? out_high[d] : out ;
  }

  /// now the vectorized version. Here we can use
  /// 'proper' vector code with masking: the equality
  /// test yields a SIMD mask.

  template < typename T , int N >
  void polish ( const vspline::simdized_type < T , N > & in ,
                vspline::simdized_type < T , N > & out ,
                int d
              ) const
  {
    out ( in == in_high[d] ) = out_high[d] ;
  }

public:
  
  // next we have variants of eval(). Depending on circumstances,
  // we have a fair number of distinct cases to deal with: we may
  // receive vectorized or unvectorized data, possibly of several
  // dimensions, and the vector data may or may not use Vc SIMD types.
  
  /// eval for unvectorized data. we dispatch on in_type, the incoming
  /// coordinate, being a 'naked' fundamental or a TinyVector
  
  void eval ( const in_type & in ,
                    out_type & out ) const
  {
    eval ( in , out , std::is_fundamental < in_type > () ) ;
  }
  
  /// eval dispatch for fundamental, 1D coordinates
  
  void eval ( const in_type & in ,
                    out_type & out ,
                    std::true_type     // in_type is a naked fundamental
            ) const
  {
    if ( in == in_high[0] )
      out = out_high[0] ;
    else
      out = ( in - in_low[0] ) * scale[0] + out_low[0] ;
  }
    
  /// eval dispatch for TinyVectors of fundamentals
  /// - unvectorized nD cordinates
  
  void eval ( const in_type & in ,
                    out_type & out ,
                    std::false_type     // in_type is a TinyVector
            ) const
  {
    for ( int d = 0 ; d < dim_in ; d++ )
    {
      if ( in[d] == in_high[d] )
        out[d] = out_high[d] ;
      else
        out[d] = ( in[d] - in_low[d] ) * scale[d] + out_low[d] ;
    }
  }
    
  /// eval for vectorized data.
  /// if in_type is a 'naked' fundamental, crd_type is a 'naked' ele_v,
  /// otherwise it's a TinyVector of ele_v. So we dispatch:

  template < class crd_type >
  void eval ( const crd_type & in ,
                    crd_type & out ) const
  {
    eval ( in , out , std::is_fundamental < in_type > () ) ;
  }
  
  /// eval dispatch for vectorized coordinates, 1D case
  
  template < typename = std::enable_if < ( vsize > 1 ) > >
  void eval ( const in_v & in ,
                    out_v & out ,
                    std::true_type    // crd_type is a 'naked' ele_v
            ) const
  {  
    out = ( in - in_low[0] ) * scale[0] + out_low[0] ;
    
    // we dispatch to one of two variants of 'polish', depending on
    // whether crd_type, which is a vspline::vector, internally uses
    // a Vc type or a TinyVector:
    
    polish < rc_type , vsize > ( in , out , 0 ) ;
  }

  /// eval dispatch for vectorized coordinates, nD case. This iterates over
  /// the dimensions and performs the 1D operation for each component.
  
  template < typename = std::enable_if < ( vsize > 1 ) > >
  void eval ( const in_v & in ,
                    out_v & out ,
                    std::false_type
            ) const
  {  
    typedef typename in_v::value_type component_type ;
    
    for ( int d = 0 ; d < dim_in ; d++ )
    {
      out[d] = ( in[d] - in_low[d] ) * scale[d] + out_low[d] ;      
      polish < rc_type , vsize > ( in[d] , out[d] , d ) ;
    }
  }

} ;

/// factory function to create a domain_type type object from the
/// desired lower and upper fix point for incoming coordinates and
/// the lower and upper fix point for outgoing coordinates.
/// the resulting functor maps incoming coordinates in the range of
/// [in_low,in_high] to coordinates in the range of [out_low,out_high]

template < class coordinate_type ,
           size_t _vsize  = vspline::vector_traits<coordinate_type>::vsize >
vspline::domain_type < coordinate_type , _vsize >
domain ( const coordinate_type & in_low ,
         const coordinate_type & in_high ,
         const coordinate_type & out_low ,
         const coordinate_type & out_high )
{
  return vspline::domain_type < coordinate_type , _vsize >
          ( in_low , in_high , out_low , out_high ) ;
}

/// factory function to create a domain_type type object
/// from the desired lower and upper reference point for incoming
/// coordinates and a vspline::bspline object providing the lower
/// and upper reference for outgoing coordinates
/// the resulting functor maps incoming coordinates in the range of
/// [ in_low , in_high ] to coordinates in the range of
/// [ bspl.lower_limit() , bspl.upper_limit() ]

template < class coordinate_type ,
           class spline_type ,
           size_t _vsize  = vspline::vector_traits<coordinate_type>::vsize >
vspline::domain_type < coordinate_type , _vsize >
domain ( const coordinate_type & in_low ,
         const coordinate_type & in_high ,
         const spline_type & bspl )
{
  return vspline::domain_type < coordinate_type , _vsize >
          ( in_low , in_high , bspl ) ;
}

/// factory function to create a domain_type type object
/// from a vspline::bspline object providing the lower
/// and upper reference for incomoing coordinates and the
/// desired lower and upper reference point for outgoing coordinates
/// the resulting functor maps incoming coordinates in the range of
/// [ bspl.lower_limit() , bspl.upper_limit() ]
/// to coordinates in the range of [ out_low , out_high ]

template < class coordinate_type ,
           class spline_type ,
           size_t _vsize  = vspline::vector_traits<coordinate_type>::vsize >
vspline::domain_type < coordinate_type , _vsize >
domain ( const spline_type & bspl ,
         const coordinate_type & out_low ,
         const coordinate_type & out_high )
{
  return vspline::domain_type < coordinate_type , _vsize >
          ( bspl , out_low , out_high ) ;
}

/// factory function to create a domain_type type object
/// from a vspline::bspline object providing the lower
/// and upper reference for incomoing coordinates and a
/// vspline::bspline object providing the lower
/// and upper reference for outgoing coordinates
/// the resulting functor maps incoming coordinates in the range of
/// [ bspl_in.lower_limit() , bspl_in.upper_limit() ]
/// to outgoing coordinates in the range of
/// [ bspl_out.lower_limit() , bspl_out.upper_limit() ]

template < class coordinate_type ,
           class spline_type ,
           size_t _vsize  = vspline::vector_traits<coordinate_type>::vsize >
vspline::domain_type < coordinate_type , _vsize >
domain ( const spline_type & bspl_in ,
         const spline_type & bspl_out )
{
  return vspline::domain_type < coordinate_type , _vsize >
          ( bspl_in , bspl_out ) ;
}

} ; // namespace vspline

#endif // #ifndef VSPLINE_DOMAIN_H
