// -*- C++ -*-
//
// This file is part of YODA -- Yet more Objects for Data Analysis
// Copyright (C) 2008-2024 The YODA collaboration (see AUTHORS for details)
//
#ifndef YODA_TRAITS_H
#define YODA_TRAITS_H

#include "YODA/AnalysisObject.h"
#include <type_traits>

namespace YODA {

  /// SFINAE definition of dereferenceability trait, cf. Boost has_dereference
  template <typename T, typename=void>
  struct Derefable : std::false_type {};
  //
  template <typename T>
  struct Derefable<T, std::void_t< decltype(*std::declval<T>())> > : std::true_type {};


  /// SFINAE struct to check for dereferencing to an AnalysisObject (reference) at compile time
  template <typename T, typename=AnalysisObject>
  struct DerefableToAO : std::false_type {};
  //
  template <typename T>
  struct DerefableToAO<T, typename std::conditional<std::is_base_of<AnalysisObject,
                                                    typename std::decay< decltype(*std::declval<T>()) >::type>::value,
                                                    AnalysisObject, void>::type> : std::true_type {};
  // The following would not be enough since it doesn't work with e.g. Histo1D*:
  // struct DerefableToAO<T, typename std::decay< decltype(*std::declval<T>()) >::type > : std::true_type {};


  // SFINAE struct to check for iterator concept at compile time
  // Also works for C-style arrays.
  template<typename T, typename = void>
  struct Iterable : std::false_type { };
  //
  template<typename T>
  struct Iterable<T, std::void_t<std::decay_t<decltype(std::begin(std::declval<const T&>()))>,
                                 std::decay_t<decltype(std::end(std::declval<const T&>()))>>>  : std::true_type { };

  template<typename... T>
  inline constexpr bool isIterable = std::conjunction<Iterable<T>...>::value;

  // SFINAE struct to check for const_iterator concept at compile time
  template<typename T, typename = void>
  struct CIterable : std::false_type { };

  template<typename T>
  struct CIterable<T, std::void_t<decltype(std::declval<typename T::const_iterator>())>> : std::true_type { };

  template<typename... T>
  inline constexpr bool isCIterable = std::conjunction<CIterable<T>...>::value;

  // SFINAE struct to check for pushing concept at compile time
  template<typename T, typename VAL, typename = void>
  struct Pushable : std::false_type { };
  //
  template<typename T, typename VAL>
  struct Pushable<T, VAL, std::void_t<decltype(std::declval<T>().push_back( std::declval<VAL>() ))>>
                  : std::true_type { };

  // SFINAE struct to check for concept of fill dimension at compile time
  template<typename T, typename = void>
  struct hasFillDim : std::false_type { };
  //
  template<typename T>
  struct hasFillDim<T, std::void_t<decltype(typename T::FillDim{})>> : std::true_type { };

}

#endif
