/*!
 * \file TmetaDetection.cpp
 *
 * Copyright (C) 2018 Christos Choutouridis
 *
 * This program is free software: you can 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 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include <utl/meta/meta.h>
#include <gtest/gtest.h>
#include <type_traits>

namespace TmetaDetection {
   using namespace utl;
   using namespace meta;

   /*
    * Types to behave like Fixtures
    */
   struct Foo {};
   struct Bar {};

   template <typename T> struct A {
      using type = T;                           // nested type
      A(int i, double d) : i_(i), d_(d) {}      // declare a non-trivial constructor
      A& operator++() { ++i_; return *this; }   // declare an operator

      // A sfinae function
      template <typename TT = T, typename = when<std::is_integral<TT>::type::value>>
      TT sfun () { return TT{}; }
   private:
         int i_; double d_;
   };

   // A binary metafunction
   template <typename T1, typename T2>
   struct mFun {
      using type = std::is_same <T1, T2>;
   };

   // detectors
   template <typename T> using try_type = typename T::type;
   template <typename T> using try_none = typename T::none;
   template <typename T> using try_ctor1= decltype (T(std::declval<int>(), std::declval<double>()));
   template <typename T> using try_ctor2= decltype (T(std::declval<int>()));
   template <typename T> using try_ppT  = decltype (++(std::declval<T>()));
   template <typename T> using try_Tpp  = decltype (std::declval<T>()++);
   template <typename T> using try_sfun = decltype (std::declval<T>().sfun());

   /*
    * void_t
    */
   TEST(TmetaDetection, VoidType) {
      EXPECT_EQ(true, (std::is_same<void, void_t<int, long, void*, void, Foo, Bar>>()));
      EXPECT_EQ(true, (std::is_same<void, void_t<>>()));
   }

   /*
    * not a type
    */
   TEST(TmetaDetection, NotAType) {
      EXPECT_EQ(false, (std::is_default_constructible<nat_>()));
      EXPECT_EQ(false, (std::is_destructible<nat_>()));
      EXPECT_EQ(false, (std::is_copy_constructible<nat_>()));
      EXPECT_EQ(false, (std::is_copy_assignable<nat_>()));
   }

   /*
    * Idiom
    */
   TEST(TmetaDetection, IsDetected) {

      EXPECT_EQ (true, (std::is_same< true_,  is_detected<try_type, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< false_, is_detected<try_none, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< true_,  is_detected<try_ctor1, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< false_, is_detected<try_ctor2, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< true_,  is_detected<try_ppT, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< false_, is_detected<try_Tpp, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< true_,  is_detected<try_sfun, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< false_, is_detected<try_sfun, A<double>> >()));

      EXPECT_EQ (true, (std::is_same< true_,  is_detected<mFun, int, void> >()));
      EXPECT_EQ (true, (std::is_same< false_, is_detected<mFun, char> >()));
      EXPECT_EQ (true, (std::is_same< false_, is_detected<mFun, char, void, void> >()));

      EXPECT_EQ (true, (is_detected_v<try_type, A<int>>));
      EXPECT_EQ (false,(is_detected_v<try_none, A<int>>));
      EXPECT_EQ (true, (is_detected_v<mFun, int, void>));

   }

   /*
    * Idiom
    */
   TEST(TmetaDetection, Toolkit) {

      // detected_t
      EXPECT_EQ (true, (std::is_same< int,    detected_t<try_type, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< nat_,   detected_t< try_none, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< A<int>, detected_t< try_ctor1, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< nat_,   detected_t< try_ctor2, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< A<int>&,detected_t< try_ppT, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< nat_,   detected_t< try_Tpp, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< int,    detected_t< try_sfun, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< nat_,   detected_t< try_sfun, A<double>> >()));

      EXPECT_EQ (true, (std::is_same< nat_,   detected_t<mFun, void> >()));
      EXPECT_EQ (true, (std::is_same< mFun<int, int>,
                                              detected_t<mFun, int, int> >()));

      // detected_or_t
      EXPECT_EQ (true, (std::is_same< int,    detected_or_t<Foo,  try_type, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< Foo,    detected_or_t<Foo,  try_none, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< A<int>, detected_or_t<void, try_ctor1, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< void,   detected_or_t<void, try_ctor2, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< A<int>&,detected_or_t<nil_, try_ppT, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< nil_,   detected_or_t<nil_, try_Tpp, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< int,    detected_or_t<void*, try_sfun, A<int>> >()));
      EXPECT_EQ (true, (std::is_same< void*,  detected_or_t<void*, try_sfun, A<double>> >()));

      EXPECT_EQ (true, (std::is_same< nil_,  detected_or_t<nil_, mFun, int> >()));
      EXPECT_EQ (true, (std::is_same< mFun<char, int>,
                                             detected_or_t<void, mFun, char, int> >()));

      // is_detected_exact
      EXPECT_EQ (true, (std::is_same<true_,  is_detected_exact< int,    try_type,  A<int>> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_exact< int,    try_none,  A<int>> >()));
      EXPECT_EQ (true, (std::is_same<true_,  is_detected_exact< A<int>, try_ctor1, A<int>> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_exact< A<int>, try_ctor2, A<int>> >()));
      EXPECT_EQ (true, (std::is_same<true_,  is_detected_exact< A<int>&,try_ppT,   A<int>> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_exact< A<int>&,try_Tpp,   A<int>> >()));
      EXPECT_EQ (true, (std::is_same<true_,  is_detected_exact< int,    try_sfun,  A<int>> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_exact< int,    try_sfun,  A<double>> >()));

      EXPECT_EQ (true, (std::is_same<true_,  is_detected_exact<mFun<char, int>, mFun, char, int> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_exact<mFun<int, int>, mFun, int> >()));   // it would be better to check against mFun<int>

      EXPECT_EQ (true, (is_detected_exact_v< int, try_type, A<int>> ));
      EXPECT_EQ (false,(is_detected_exact_v< int, try_none, A<int>> ));

      // is_detected_convertible
      EXPECT_EQ (true, (std::is_same<true_,  is_detected_convertible< int, try_type, A<char>> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_convertible< int, try_none, A<int>> >()));
      EXPECT_EQ (true, (std::is_same<false_, is_detected_convertible< mFun<int, int>, mFun, char, int> >()));

      EXPECT_EQ (true, (is_detected_convertible_v< int, try_type, A<char>> ));
      EXPECT_EQ (false,(is_detected_convertible_v< int, try_none, A<int>> ));

   }
}
