#include <iostream>
#include <type_traits>
#include <functional>
#include <cstdint>
#include <typeinfo>
#include <string>

// Just gonna do this for the sake of our example here.
using FilePath = std::string;

// Parameter match checking.
namespace ParameterCheck {
    template<typename T, typename... Ts> struct parameter_match : public std::false_type {};

    // Declare (GetColor, int16_t*) valid.
    template<> struct parameter_match<int (*)(int16_t*), int16_t*> : public std::true_type {};

    // Declare (GetFile, FilePath&) valid.
    // template<> struct parameter_match<int (*)(FilePath&), FilePath&> : public std::true_type {}; // You'd think this would work, but...
    template<> struct parameter_match<int (*)(FilePath&), FilePath> : public std::true_type {}; // Nope!
    // For some reason, reference-ness isn't part of the templated type.  It acts as if it was "template<typename T> void func(T& arg)" instead.
    
    // Declare (WriteDocument, const FilePath&, const char*, bool) valid.
    // template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), const FilePath, const char*, bool> : public std::true_type {};
    // template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), const FilePath&, const char*, bool> : public std::true_type {};
    template<> struct parameter_match<int (*)(const FilePath&, const char*, bool), FilePath, const char*, bool> : public std::true_type {};
    // More reference-as-template-parameter wonkiness: Out of these three, only the last works.
    
    // Declare everything without a parameter list valid.
    template<typename T> struct parameter_match<T (*)()> : public std::true_type { };
} // namespace ParameterCheck

// Discount return type deduction:
namespace ReturnTypeCapture {
    // Credit goes to Angew ( http://stackoverflow.com/a/18695701/5386374 )
    template<typename T> struct ret_type;
    
    template<typename RT, typename... Ts> struct ret_type<RT (*)(Ts...)> {
        using type = RT;
    };
} // namespace ReturnTypeCapture

// Alias declarations:
template<typename F, typename... Ts> using PChecker = ParameterCheck::parameter_match<F, Ts...>;
template <typename F> using RChecker = typename ReturnTypeCapture::ret_type<F>::type;


// ---------------

// Quick implementations of your example functions.

int GetColor(int16_t* color) {
    std::cout << "GetColor(int16_t*) with parameter: " << *color << std::endl;
    return 3;
}

int GetFile(FilePath& file) {
    std::cout << "GetFile(FilePath&)." << std::endl;
    std::cout << "..." << file << "?  Get it yourself!" << std::endl;
    return -6;
}

int WriteDocument(const FilePath& file, const char* fileFormatName, bool askForParams) {
    std::cout << "WriteDocument(const FilePath&, const char*, bool) with: "
              << file << ", " << fileFormatName << ", and " << std::boolalpha
              << askForParams << "." << std::noboolalpha << std::endl;
    std::cout << "Eh, I'll write it tomorrow." << std::endl;
    return 8;
}

// ---------------

// The actual calling function, C++11 style.
template<typename Func, typename... Ts> auto caller2(std::true_type x, Func f, Ts... args) -> RChecker<Func> {
    std::cout << "Now calling... ";
    return f(args...);
}

// Parameter mismatch overload.
template<typename Func, typename... Ts> auto caller2(std::false_type x, Func f, Ts... args) -> RChecker<Func> {
    std::cout << "Parameter list mismatch." << std::endl;
    return static_cast<RChecker<Func> >(0); // Just to make sure we don't break stuff.
}

// Wrapper to check for parameter mismatch, C++14 style.
template<typename Func, typename... Ts> auto caller(Func f, Ts... args) /* -> RChecker<Func> */ {
    // return caller2(ParameterCheck::parameter_match<Func, Ts...>{}, f, args...);
    return caller2(PChecker<Func, Ts...>{}, f, args...);
}

// ---------------

// To show what std::true_type and std::false_type act as.

template<bool N> void fnarg2(std::integral_constant<bool, N> x) {
    std::cout << "Integral constant<bool, N> value: " << x.value << std::endl;
}

template<typename F, typename... Ts> void fnarg(F f, Ts... ts) {
    fnarg2(ParameterCheck::parameter_match<F, Ts...>{});
}

// ---------------

// Something I used to get things working, I left it here to show something odd:

// Which one is true and which is false flips depending on whether the parameter is T or T&.
template<typename T> void sameness(T& t) {
    std::cout << std::boolalpha;
    std::cout << "cfp is const: " << std::is_same<T, const FilePath>::value << std::endl;
    std::cout << "cfp ain't const: " << std::is_same<T, FilePath>::value << std::endl;
    std::cout << std::noboolalpha;
}

// ---------------

// Return type sorcery:

std::string f1() { return std::string("Nyahaha."); }

int f2() { return -42; }

char f3() { return '&'; }

// template<typename R, typename F> auto rtCaller2(R r, F f) -> typename R::type {
// template<typename F> auto rtCaller2(F f) -> typename ReturnTypeCapture::ret_type<F>::type {
template<typename F> auto rtCaller2(F f) -> RChecker<F> {
    return f();
}

template<typename F> void rtCaller(F f) {
    // auto a = rtCaller2(ReturnTypeCapture::ret_type<F>{}, f);
    auto a = rtCaller2(f);
    std::cout << a << " (type: " << typeid(a).name() << ")" << std::endl;
}

// ---------------

int main()  {
    int16_t i = 3333;
    FilePath fp = "C:\\Windows\\win.ini";
    const FilePath cfp = (const FilePath) fp; // In case you didn't know, it's a const FilePath. ^_^
    const char* cc = "Fred Format\n";
    bool b = true;

    // Checking int(*)(int16_t*):
    auto gc = caller(GetColor, &i);                                   // Valid call.
    caller(GetColor, &i, i);                                // Invalid call.
    std::cout << std::endl;
    
    // Checking int(*)(FilePath&):
    auto gf = caller(GetFile, fp);                                    // Valid call.
    caller(GetFile, main);                                  // Invalid call.
    std::cout << std::endl;
    
    //Checking int(*)(const FilePath&, const char*, bool):
    auto wd = caller(WriteDocument, cfp, cc, b);                      // Valid call.
    caller(WriteDocument, cfp, const_cast<char*>(cc), b);   // Invalid call.
    std::cout << std::endl;
    
    auto f_1 = caller(f1);
    auto f_2 = caller(f2);
    auto f_3 = caller(f3);
    std::cout << std::endl;
    
    std::cout << "Return values: " << gc << ", " << gf << ", " << wd << ", "
              << f_1 << ", " << f_2 << ", and " << f_3 <<  std::endl;

//    std::cout << std::endl;
//    sameness(cfp);
    
    // Three std::true_types, and one std::false_type.
    std::cout << std::boolalpha << std::endl;
    fnarg(GetColor, &i);
    fnarg(GetFile, fp);
    fnarg(WriteDocument, cfp, cc, b);
    fnarg(i);
    std::cout << std::noboolalpha << std::endl;
    
    // Return type stuff:
    std::cout << "Behold, return type sorcery!" << std::endl;
    rtCaller(f1);
    rtCaller(f2);
    rtCaller(f3);

}