// ---------------------------------------------------------------------
//
// Copyright (C) 2005 - 2016 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, redistribute
// it, and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// The full text of the license can be found in the file LICENSE at
// the top level of the deal.II distribution.
//
// ---------------------------------------------------------------------

#ifndef dealii__signaling_nan_h
#define dealii__signaling_nan_h

#include <deal.II/base/config.h>
#include <deal.II/base/tensor.h>
#include <deal.II/base/symmetric_tensor.h>
#include <deal.II/base/derivative_form.h>

#include <limits>


DEAL_II_NAMESPACE_OPEN

namespace numbers
{

  namespace internal
  {
    /**
     * A namespace for the implementation of functions that create signaling
     * NaN objects. This is where the Utilities::signaling_nan() function
     * calls into.
     */
    namespace SignalingNaN
    {
      /**
       * A general template for classes that know how to initialize objects of
       * type @p T with signaling NaNs to denote invalid values.
       *
       * The real implementation of this class happens in (partial)
       * specializations for particular values of the template argument @p T.
       */
      template <typename T>
      struct NaNInitializer;


      /**
       * A specialization of the general NaNInitializer class that provides a
       * function that returns a @p float value equal to the invalid signaling
       * NaN.
       */
      template <>
      struct NaNInitializer<float>
      {
        static float invalid_element ()
        {
          return std::numeric_limits<float>::signaling_NaN();
        }
      };


      /**
       * A specialization of the general NaNInitializer class that provides a
       * function that returns a @p double value equal to the invalid
       * signaling NaN.
       */
      template <>
      struct NaNInitializer<double>
      {
        static double invalid_element ()
        {
          return std::numeric_limits<double>::signaling_NaN();
        }
      };


      /**
       * A specialization of the general NaNInitializer class that provides a
       * function that returns a Tensor<1,dim> value whose components are
       * invalid signaling NaN values.
       */
      template <int dim, typename T>
      struct NaNInitializer<Tensor<1,dim,T> >
      {
        static Tensor<1,dim,T> invalid_element ()
        {
          Tensor<1,dim,T> nan_tensor;

          for (unsigned int i=0; i<dim; ++i)
            nan_tensor[i] = NaNInitializer<T>::invalid_element();

          return nan_tensor;
        }
      };



      /**
       * A specialization of the general NaNInitializer class that provides a
       * function that returns a Tensor<rank,dim> value whose components are
       * invalid signaling NaN values.
       */
      template <int rank, int dim, typename T>
      struct NaNInitializer<Tensor<rank,dim,T> >
      {
        static Tensor<rank,dim,T> invalid_element ()
        {
          Tensor<rank,dim,T> nan_tensor;

          // recursively initialize sub-tensors with invalid elements
          for (unsigned int i=0; i<dim; ++i)
            nan_tensor[i] = NaNInitializer<Tensor<rank-1,dim,T> >::invalid_element();

          return nan_tensor;
        }
      };



      /**
       * A specialization of the general NaNInitializer class that provides a
       * function that returns a SymmetricTensor<rank,dim> value whose
       * components are invalid signaling NaN values.
       */
      template <int rank, int dim, typename T>
      struct NaNInitializer<SymmetricTensor<rank,dim,T> >
      {
        static SymmetricTensor<rank,dim,T> invalid_element ()
        {
          // initialize symmetric tensors via the unrolled list of elements
          T initializers[SymmetricTensor<rank,dim,T>::n_independent_components];
          for (unsigned int i=0; i<SymmetricTensor<rank,dim,T>::n_independent_components; ++i)
            initializers[i] = NaNInitializer<T>::invalid_element();

          return SymmetricTensor<rank,dim,T>(initializers);
        }
      };



      /**
       * A specialization of the general NaNInitializer class that provides a
       * function that returns a DerivativeForm<order,dim,spacedim> value
       * whose components are invalid signaling NaN values.
       */
      template <int order, int dim, int spacedim, typename T>
      struct NaNInitializer<DerivativeForm<order,dim,spacedim,T> >
      {
        static DerivativeForm<order,dim,spacedim,T> invalid_element ()
        {
          DerivativeForm<order,dim,spacedim,T> form;

          // recursively initialize sub-tensors with invalid elements
          for (unsigned int i=0; i<spacedim; ++i)
            form[i] = NaNInitializer<Tensor<order,dim,T> >::invalid_element();

          return form;
        }
      };
    }
  }




  /**
   * Provide an object of type @p T filled with a signaling NaN that will
   * cause an exception when used in a computation. The content of these
   * objects is a "signaling NaN" ("NaN" stands for "not a number", and
   * "signaling" implies that at least on platforms where this is supported,
   * any arithmetic operation using them terminates the program). The purpose
   * of such objects is to use them as markers for uninitialized objects and
   * arrays that are required to be filled in other places, and to trigger an
   * error when this later initialization does not happen before the first
   * use.
   *
   * @tparam T The type of the returned invalid object. This type can either
   * be a scalar, or of type Tensor, SymmetricTensor, or DerivativeForm. Other
   * types may be supported if there is a corresponding specialization of the
   * internal::SignalingNaN::NaNInitializer class for this type.
   *
   * @note Because the type @p T is not used as a function argument, the
   * compiler cannot deduce it from the type of arguments. Consequently, you
   * have to provide it explicitly. For example, the line
   *   @code
   *     Tensor<1,dim> tensor = Utilities::signaling_nan<Tensor<1,dim> >();
   *   @endcode
   * initializes a tensor with invalid values.
   */
  template <class T>
  T
  signaling_nan()
  {
    // dispatch to the classes in the internal namespace because there
    // we can do partial specializations, which is not possible for
    // template functions such as the current one
    return internal::SignalingNaN::NaNInitializer<T>::invalid_element ();
  }
}


DEAL_II_NAMESPACE_CLOSE

#endif
