/* This file is part of the Tomographer project, which is distributed under the
 * terms of the MIT license.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 ETH Zurich, Institute for Theoretical Physics, Philippe Faist
 * Copyright (c) 2017 Caltech, Institute for Quantum Information and Matter, Philippe Faist
 *
 * 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.
 */

#ifndef TOMOGRAPHER_TOOLS_NEEDOWNOPERATORNEW_H
#define TOMOGRAPHER_TOOLS_NEEDOWNOPERATORNEW_H

/** \file needownoperatornew.h
 *
 * \brief Managing the need for specific overrides to <code>operator new()</code> for some
 *        types (especially Eigen types)
 *
 * See \ref Tomographer::Tools::NeedOwnOperatorNew.
 */

#include <type_traits>

#include <Eigen/Eigen> // EIGEN_MAKE_ALIGNED_OPERATOR_NEW

#include <tomographer/tools/cxxutil.h> // tomo_internal::sfinae_yes, tomo_internal::sfinae_no


namespace Tomographer {
namespace Tools {


/** \brief Provider for regular types which don't need any special operator-new
 *         implementation (see \ref NeedOwnOperatorNew)
 */
struct TOMOGRAPHER_EXPORT NoSpecialOperatorNewProvider {
  template<typename T>
  struct OperatorNewAllocatorType { typedef std::allocator<T> Type; };
};

//! Helper for NoSpecialOperatorNewProvider
struct TOMOGRAPHER_EXPORT NoSpecialOperatorNew {
  typedef NoSpecialOperatorNewProvider ProviderType;

  // prevent inadvertently inheriting this class instead of
  // NoSpecialOperatorNewProvider:
  NoSpecialOperatorNew() = delete;
};


namespace tomo_internal {

// see http://stackoverflow.com/a/9154394/1694896
template<typename T> static auto test_has_ownopnewprovidertype_member(int)
  -> typename sfinae_yes<typename T::OperatorNewProviderType>::yes&;
template<typename T> static auto test_has_ownopnewprovidertype_member(long)
  -> typename sfinae_no<>::no&;
template<typename T>
struct has_ownopnewprovidertype_member {
  static constexpr bool value = (sizeof(tomo_internal::test_has_ownopnewprovidertype_member<T>(0))
				 == sizeof(typename tomo_internal::sfinae_yes<>::yes));
};
template<bool has_own_provider, typename T>
struct need_own_op_new_prov_helper {
  typedef NoSpecialOperatorNewProvider ProviderType;
};
template<typename T>
struct need_own_op_new_prov_helper<true, T> {
  typedef typename T::OperatorNewProviderType ProviderType;
};
} // namespace tomo_internal


/** \brief Provide appropriate <code>operator new()</code> definitions for a structure
 *         which has a member of the given stored types
 *
 * Some types require a specific implementation of <code>operator new()</code>, such as
 * Eigen (see <a
 * href="https://eigen.tuxfamily.org/dox/group__TopicStructHavingEigenMembers.html"
 * target="_blank">this page</a>).  Hence, if a class has an Eigen member, it needs to
 * make sure to have the appropriate implementations of <code>operator new()</code>.
 *
 * Because in our generic classes, we may be requried to store types (as members), such as
 * a \a PointType in a random walk, and we can't assume it will be an Eigen object.  The
 * solution is that those objects inherit
 * <em>NeedOwnOperatorNew<PointType>::ProviderType</em>, such that proper operator-new
 * definitions can be generated by appropriate specializations of \ref NeedOwnOperatorNew.
 *
 * The template arguments are a list of types, which could potentially require having
 * appropriate <code>operator new()</code> implementations if such a type is a class
 * member.  Just inherit <code>NeedOwnOperatorNew<Type1, Type2, ...>::ProviderType</code>
 * and (provided the types don't have conflicting requirements for special operator-new
 * implementations) the correct implementation of operator-new (if any is required) will
 * be used automatically.
 *
 *
 * <h3>Defining special operator-new requirments for a specific type</h3>
 *
 * If a specific type \a T needs a particular operator-new requirement (such as Eigen
 * types), the first thing to do is define the requested <em>operator new</em> definitions
 * inside a "provider type" class (see, for example, \ref
 * EigenAlignedOperatorNewProvider).  This class should expose the correct <em>operator
 * new</em> definition.
 *
 * \todo It should also define the appropriate allocator type.... TODO maybe clean up API?
 *       and add documentation
 * 
 * Then, you should specialize the template class NeedOwnOperatorNew for your type, and
 * expose a \a ProviderType typedef to your operator-new provider class.  Make sure you
 * delete the default constructor as a safe-guard, to avoid unadvertently inheriting
 * <code>NeedOwnOperatorNew<T></code> instead of
 * <code>NeedOwnOperatorNew<T>::ProviderType</code>.  For instance:
 * \code
 *   template<>
 *   struct NeedOwnOperatorNew<MyType>  {
 *     typedef MyTypeOperatorNewProvider ProviderType;
 *
 *     // prevent inadvertently inheriting this class instead of
 *     // EigenAlignedOperatorNewProvider:
 *     NeedOwnOperatorNew() = delete;
 *   };
 * \endcode
 * The template specialization may of course also have further template parameters, i.e.,
 * partial specialization (see for example: \ref NeedOwnOperatorNew_for_eigen_Matrix_type
 * "the specialization for Eigen Matrix types").
 */
template<typename... Types>
struct NeedOwnOperatorNew {
  // will be specialized appropriately.
};


/** \brief Specialization of \ref NeedOwnOperatorNew for a single type
 *
 */
template<typename T>
struct NeedOwnOperatorNew<T> {
  typedef typename tomo_internal::need_own_op_new_prov_helper<
    tomo_internal::has_ownopnewprovidertype_member<T>::value,
    T
    >::ProviderType
  ProviderType;

  typedef typename ProviderType::template OperatorNewAllocatorType<T>::Type AllocatorType;
};


//
// Dark magic for allowing a struct to have several members of possibly different struct
// types (such as a ValueCalculator and a HistogramTypes), each of which might possibly
// require their own operator-new-providers; those super-structures should inherit from
// NeedOwnOperatorNew<Type1,Type2,Type3,...>::ProviderType , which resolves to the
// provider-type of all these types if they are the same (or if they don't require a
// provider-type).
//

template<typename Type1, typename Type2, typename... OtherTypes>
struct NeedOwnOperatorNew<Type1, Type2, OtherTypes...>;


namespace tomo_internal {

template<bool same_provider_type, bool t1_trivial_provider, bool t2_trivial_provider,
         typename Type1, typename Type2, typename... OtherTypes>
struct need_own_operator_new_combination_helper {
  // missing ProviderType member, will generate runtime error.
  static_assert(!same_provider_type && !t1_trivial_provider && !t2_trivial_provider,
                "ERROR: you have a type which requested a NeedOwnOperatorNew<T1,T2> "
                "with incompatible needs.") ;
};
// case same_provider_type == true
template<bool t1_trivial_provider, bool t2_trivial_provider,
         typename Type1, typename Type2, typename... OtherTypes>
struct need_own_operator_new_combination_helper<true, t1_trivial_provider, t2_trivial_provider,
                                                Type1, Type2, OtherTypes...> {
  // Type1 and Type2 have same provider, all ok, so continue and recurse on the other types:
  typedef typename NeedOwnOperatorNew<Type1, OtherTypes...>::ProviderType ProviderType;
};
// case t1_trivial_provider == true
template<bool t2_trivial_provider,
         typename Type1, typename Type2, typename... OtherTypes>
struct need_own_operator_new_combination_helper<false, true, t2_trivial_provider,
                                                Type1, Type2, OtherTypes...> {
  // all ok, use Type2's provider, and recurse on the other types
  typedef typename NeedOwnOperatorNew<Type2, OtherTypes...>::ProviderType ProviderType;
};
// case t2_trivial_provider == true (and t1_trivial_provider == false, otherwise see case above)
template<typename Type1, typename Type2, typename... OtherTypes>
struct need_own_operator_new_combination_helper<false, false, true,
                                                Type1, Type2, OtherTypes...> {
  // all ok, use Type2's provider, and recurse on the other types
  typedef typename NeedOwnOperatorNew<Type1, OtherTypes...>::ProviderType ProviderType;
};


} // namespace tomo_internal


template<typename Type1, typename Type2, typename... OtherTypes>
struct NeedOwnOperatorNew<Type1, Type2, OtherTypes...>
{
  typedef tomo_internal::need_own_operator_new_combination_helper<
    std::is_same<typename NeedOwnOperatorNew<Type1>::ProviderType,
                 typename NeedOwnOperatorNew<Type2>::ProviderType >::value,
    std::is_same<typename NeedOwnOperatorNew<Type1>::ProviderType,
                 NoSpecialOperatorNewProvider >::value,
    std::is_same<typename NeedOwnOperatorNew<Type2>::ProviderType,
                 NoSpecialOperatorNewProvider >::value,
    Type1, Type2, OtherTypes...
    > ProviderType;
};








//
// for Eigen types
//



/** \brief Provides correct operator-new implementation for Eigen types via the \ref
 *         NeedOwnOperatorNew mechanism
 */
struct TOMOGRAPHER_EXPORT EigenAlignedOperatorNewProvider {
public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW ;
  typedef EigenAlignedOperatorNewProvider OperatorNewProviderType;

  template<typename T> struct OperatorNewAllocatorType {
    typedef Eigen::aligned_allocator<T> Type;
  };

#ifdef TOMOGRAPHER_TEST_TOOLS_NEEDOWNOPERATORNEW_DEBUG_MEMBERS
  // DEBUG: provide something we can test that it is indeed eigen-aligned 
  inline EigenAlignedOperatorNewProvider()
    : EigenAlignedOperatorNewIsActive(true)
  {
  }
  const bool EigenAlignedOperatorNewIsActive;
#endif
};


//! Helper to specialize NeedOwnOperatorNew for Eigen types
template<bool ReallyNeeded = true>
struct NeedEigenAlignedOperatorNew {
  typedef EigenAlignedOperatorNewProvider ProviderType;

  // prevent inadvertently inheriting this class instead of
  // EigenAlignedOperatorNewProvider:
  NeedEigenAlignedOperatorNew() = delete;
};
// but if it's not really needed, for instance for dynamic-sized Eigen matrices which
// anyway allocate the data on the heap internally using aligned allocators:
template<>
struct NeedEigenAlignedOperatorNew<false> {
  typedef NoSpecialOperatorNewProvider ProviderType;

  // prevent inadvertently inheriting this class instead of
  // EigenAlignedOperatorNewProvider:
  NeedEigenAlignedOperatorNew() = delete;
};

/** \brief Specialize \ref NeedOwnOperatorNew for Eigen types
 *
 * \anchor NeedOwnOperatorNew_for_eigen_Matrix_type
 */
template<typename Scalar, int FixedRows, int FixedCols, int Options, int MaxRows, int MaxCols>
struct NeedOwnOperatorNew<Eigen::Matrix<Scalar,FixedRows,FixedCols,Options,MaxRows,MaxCols> >
  : public NeedEigenAlignedOperatorNew<!(MaxRows == Eigen::Dynamic || MaxCols == Eigen::Dynamic)> { };


//! Specialize \ref NeedOwnOperatorNew for Eigen types
template<typename Scalar, int FixedRows, int FixedCols, int Options, int MaxRows, int MaxCols>
struct NeedOwnOperatorNew<Eigen::Array<Scalar,FixedRows,FixedCols,Options,MaxRows,MaxCols> >
  : public NeedEigenAlignedOperatorNew<!(MaxRows == Eigen::Dynamic || MaxCols == Eigen::Dynamic)> { };





} // namespace Tools
} // namespace Tomographer



#endif
