//  (C) Copyright Antony Polukhin, 2012-2019.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org/libs/config for most recent version.

//
// Testing lexical_cast<> performance
//

#define BOOST_ERROR_CODE_HEADER_ONLY
#define BOOST_CHRONO_HEADER_ONLY

#include <boost/lexical_cast.hpp>

#include <boost/chrono.hpp>
#include <fstream>
#include <cstring>
#include <boost/container/string.hpp>

// File to output data
std::fstream fout;

namespace boost {
inline std::istream& operator>> (std::istream& in, boost::array<char,50>& res) {
    in >> res.begin();
    return in;
}
}

template <class OutT, class InT>
static inline void test_lexical(const InT& in_val) {
    OutT out_val = boost::lexical_cast<OutT>(in_val);
    (void)out_val;
}

template <class OutT, class InT>
static inline void test_ss_constr(const InT& in_val) {
    OutT out_val;
    std::stringstream ss;
    ss << in_val;
    if (ss.fail()) throw std::logic_error("descr");
    ss >> out_val;
    if (ss.fail()) throw std::logic_error("descr");
}

template <class OutT, class CharT, std::size_t N>
static inline void test_ss_constr(const boost::array<CharT, N>& in_val) {
    OutT out_val;
    std::stringstream ss;
    ss << in_val.begin();
    if (ss.fail()) throw std::logic_error("descr");
    ss >> out_val;
    if (ss.fail()) throw std::logic_error("descr");
}

template <class OutT, class StringStreamT, class CharT, std::size_t N>
static inline void test_ss_noconstr(StringStreamT& ss, const boost::array<CharT, N>& in_val) {
    OutT out_val;
    ss << in_val.begin(); // ss is an instance of std::stringstream
    if (ss.fail()) throw std::logic_error("descr");
    ss >> out_val;
    if (ss.fail()) throw std::logic_error("descr");
    /* reseting std::stringstream to use it again */
    ss.str(std::string());
    ss.clear();
}

template <class OutT, class StringStreamT, class InT>
static inline void test_ss_noconstr(StringStreamT& ss, const InT& in_val) {
    OutT out_val;
    ss << in_val; // ss is an instance of std::stringstream
    if (ss.fail()) throw std::logic_error("descr");
    ss >> out_val;
    if (ss.fail()) throw std::logic_error("descr");
    /* reseting std::stringstream to use it again */
    ss.str(std::string());
    ss.clear();
}

struct structure_sprintf {
    template <class OutT, class BufferT, class InT>
    static inline void test(BufferT* buffer, const InT& in_val, const char* const conv) {
        sprintf(buffer, conv, in_val);
        OutT out_val(buffer);
    }

    template <class OutT, class BufferT>
    static inline void test(BufferT* buffer, const std::string& in_val, const char* const conv) {
        sprintf(buffer, conv, in_val.c_str());
        OutT out_val(buffer);
    }
};

struct structure_sscanf {
    template <class OutT, class BufferT, class CharT, std::size_t N>
    static inline void test(BufferT* /*buffer*/, const boost::array<CharT, N>& in_val, const char* const conv) {
        OutT out_val;
        sscanf(in_val.cbegin(), conv, &out_val);
    }

    template <class OutT, class BufferT, class InT>
    static inline void test(BufferT* /*buffer*/, const InT& in_val, const char* const conv) {
        OutT out_val;
        sscanf(reinterpret_cast<const char*>(in_val), conv, &out_val);
    }

    template <class OutT, class BufferT>
    static inline void test(BufferT* /*buffer*/, const std::string& in_val, const char* const conv) {
        OutT out_val;
        sscanf(in_val.c_str(), conv, &out_val);
    }

    template <class OutT, class BufferT>
    static inline void test(BufferT* /*buffer*/, const boost::iterator_range<const char*>& in_val, const char* const conv) {
        OutT out_val;
        sscanf(in_val.begin(), conv, &out_val);
    }
};

struct structure_fake {
    template <class OutT, class BufferT, class InT>
    static inline void test(BufferT* /*buffer*/, const InT& /*in_val*/, const char* const /*conv*/) {}
};

static const int fake_test_value = 9999;

template <class T>
static inline void min_fancy_output(T v1, T v2, T v3, T v4) {
    const char beg_mark[] = "!!! *";
    const char end_mark[] = "* !!!";
    const char no_mark[] = "";

    unsigned int res = 4;
    if (v1 < v2 && v1 < v3 && v1 < v4) res = 1;
    if (v2 < v1 && v2 < v3 && v2 < v4) res = 2;
    if (v3 < v1 && v3 < v2 && v3 < v4) res = 3;

    fout << "[ "
         << (res == 1 ? beg_mark : no_mark)
         ;

    if (v1) fout << v1;
    else fout << "<1";

    fout << (res == 1 ? end_mark : no_mark)
         << " ][ "
         << (res == 2 ? beg_mark : no_mark)
         ;

       if (v2) fout << v2;
       else fout << "<1";

       fout << (res == 2 ? end_mark : no_mark)
         << " ][ "
         << (res == 3 ? beg_mark : no_mark)
         ;

       if (v3) fout << v3;
       else fout << "<1";

       fout << (res == 3 ? end_mark : no_mark)
         << " ][ "
         << (res == 4 ? beg_mark : no_mark)
         ;

       if (!v4) fout << "<1";
       else if (v4 == fake_test_value) fout << "---";
       else fout << v4;

       fout
         << (res == 4 ? end_mark : no_mark)
         << " ]";
}

template <unsigned int IetartionsCountV, class ToT, class SprintfT, class FromT>
static inline void perf_test_impl(const FromT& in_val, const char* const conv) {

    typedef boost::chrono::steady_clock test_clock;
    test_clock::time_point start;
    typedef boost::chrono::milliseconds duration_t;
    duration_t lexical_cast_time, ss_constr_time, ss_noconstr_time, printf_time;

    start = test_clock::now();
    for (unsigned int i = 0; i < IetartionsCountV; ++i) {
        test_lexical<ToT>(in_val);
        test_lexical<ToT>(in_val);
        test_lexical<ToT>(in_val);
        test_lexical<ToT>(in_val);
    }
    lexical_cast_time = boost::chrono::duration_cast<duration_t>(test_clock::now() - start);


    start = test_clock::now();
    for (unsigned int i = 0; i < IetartionsCountV; ++i) {
        test_ss_constr<ToT>(in_val);
        test_ss_constr<ToT>(in_val);
        test_ss_constr<ToT>(in_val);
        test_ss_constr<ToT>(in_val);
    }
    ss_constr_time = boost::chrono::duration_cast<duration_t>(test_clock::now() - start);

    std::stringstream ss;
    start = test_clock::now();
    for (unsigned int i = 0; i < IetartionsCountV; ++i) {
        test_ss_noconstr<ToT>(ss, in_val);
        test_ss_noconstr<ToT>(ss, in_val);
        test_ss_noconstr<ToT>(ss, in_val);
        test_ss_noconstr<ToT>(ss, in_val);
    }
    ss_noconstr_time = boost::chrono::duration_cast<duration_t>(test_clock::now() - start);


    char buffer[128];
    start = test_clock::now();
    for (unsigned int i = 0; i < IetartionsCountV; ++i) {
        SprintfT::template test<ToT>(buffer, in_val, conv);
        SprintfT::template test<ToT>(buffer, in_val, conv);
        SprintfT::template test<ToT>(buffer, in_val, conv);
        SprintfT::template test<ToT>(buffer, in_val, conv);
    }
    printf_time = boost::chrono::duration_cast<duration_t>(test_clock::now() - start);

    min_fancy_output(
                lexical_cast_time.count(),
                ss_constr_time.count(),
                ss_noconstr_time.count(),
                boost::is_same<SprintfT, structure_fake>::value ? fake_test_value : printf_time.count()
    );
}

template <class ToT, class SprintfT, class FromT>
static inline void perf_test(const std::string& test_name, const FromT& in_val, const char* const conv) {
    const unsigned int ITERATIONSCOUNT = 100000;
    fout << "  [[ " << test_name << " ]";

    perf_test_impl<ITERATIONSCOUNT/4, ToT, SprintfT>(in_val, conv);

    fout << "]\n";
}


template <class ConverterT>
void string_like_test_set(const std::string& from) {
    typedef structure_sscanf ssc_t;
    ConverterT conv;

    perf_test<char, ssc_t>(from + "->char",               conv("c"), "%c");
    perf_test<signed char, ssc_t>(from + "->signed char", conv("c"), "%hhd");
    perf_test<unsigned char, ssc_t>(from + "->unsigned char", conv("c"), "%hhu");

    perf_test<int, ssc_t>(from + "->int",             conv("100"), "%d");
    perf_test<short, ssc_t>(from + "->short",         conv("100"), "%hd");
    perf_test<long int, ssc_t>(from + "->long int",   conv("100"), "%ld");
    perf_test<boost::long_long_type, ssc_t>(from + "->long long", conv("100"), "%lld");

    perf_test<unsigned int, ssc_t>(from + "->unsigned int",             conv("100"), "%u");
    perf_test<unsigned short, ssc_t>(from + "->unsigned short",         conv("100"), "%hu");
    perf_test<unsigned long int, ssc_t>(from + "->unsigned long int",   conv("100"), "%lu");
    perf_test<boost::ulong_long_type, ssc_t>(from + "->unsigned long long", conv("100"), "%llu");

    // perf_test<bool, ssc_t>(from + "->bool", conv("1"), "%");

    perf_test<float, ssc_t>(from + "->float",             conv("1.123"), "%f");
    perf_test<double, ssc_t>(from + "->double",           conv("1.123"), "%lf");
    perf_test<long double, ssc_t>(from + "->long double", conv("1.123"), "%Lf");
    perf_test<boost::array<char, 50>, ssc_t>(from + "->array<char, 50>", conv("1.123"), "%s");

    perf_test<std::string, structure_fake>(from + "->string", conv("string"), "%Lf");
    perf_test<boost::container::string, structure_fake>(from + "->container::string"
                                                        , conv("string"), "%Lf");

}

struct to_string_conv {
    std::string operator()(const char* const c) const {
        return c;
    }
};

struct to_char_conv {
    const char*  operator()(const char* const c) const {
        return c;
    }
};

struct to_uchar_conv {
    const unsigned char*  operator()(const char* const c) const {
        return reinterpret_cast<const unsigned char*>(c);
    }
};


struct to_schar_conv {
    const signed char*  operator()(const char* const c) const {
        return reinterpret_cast<const signed char*>(c);
    }
};

struct to_iterator_range {
    boost::iterator_range<const char*>  operator()(const char* const c) const {
        return boost::make_iterator_range(c, c + std::strlen(c));
    }
};

struct to_array_50 {
    boost::array<char, 50> operator()(const char* const c) const {
        boost::array<char, 50> ret;
        std::strcpy(ret.begin(), c);
        return ret;
    }
};

int main(int argc, char** argv) {
    BOOST_ASSERT(argc >= 2);
    std::string output_path(argv[1]);
    output_path += "/results.txt";
    fout.open(output_path.c_str(), std::fstream::in | std::fstream::out | std::fstream::app);
    BOOST_ASSERT(fout);

    fout << "[section " << BOOST_COMPILER << "]\n"
        << "[table:id Performance Table ( "<< BOOST_COMPILER << ")\n"
        << "[[From->To] [lexical_cast] [std::stringstream with construction] "
        << "[std::stringstream without construction][scanf/printf]]\n";


    // From std::string to ...
    string_like_test_set<to_string_conv>("string");

    // From ... to std::string
    perf_test<std::string, structure_sprintf>("string->char",                'c', "%c");
    perf_test<std::string, structure_sprintf>("string->signed char",     static_cast<signed char>('c'), "%hhd");
    perf_test<std::string, structure_sprintf>("string->unsigned char", static_cast<unsigned char>('c'), "%hhu");

    perf_test<std::string, structure_sprintf>("int->string",                 100, "%d");
    perf_test<std::string, structure_sprintf>("short->string",               static_cast<short>(100), "%hd");
    perf_test<std::string, structure_sprintf>("long int->string",            100l, "%ld");
    perf_test<std::string, structure_sprintf>("long long->string",           100ll, "%lld");

    perf_test<std::string, structure_sprintf>("unsigned int->string",        static_cast<unsigned short>(100u), "%u");
    perf_test<std::string, structure_sprintf>("unsigned short->string",      100u, "%hu");
    perf_test<std::string, structure_sprintf>("unsigned long int->string",   100ul, "%lu");
    perf_test<std::string, structure_sprintf>("unsigned long long->string",  static_cast<boost::ulong_long_type>(100), "%llu");

    // perf_test<bool, structure_sscanf>("bool->string", std::string("1"), "%");

    perf_test<std::string, structure_sprintf>("float->string",       1.123f, "%f");
    perf_test<std::string, structure_sprintf>("double->string",      1.123, "%lf");
    perf_test<std::string, structure_sprintf>("long double->string", 1.123L, "%Lf");


    string_like_test_set<to_char_conv>("char*");
    string_like_test_set<to_uchar_conv>("unsigned char*");
    string_like_test_set<to_schar_conv>("signed char*");
    string_like_test_set<to_iterator_range>("iterator_range<char*>");
    string_like_test_set<to_array_50>("array<char, 50>");

    perf_test<int, structure_fake>("int->int", 100, "");
    perf_test<double, structure_fake>("float->double", 100.0f, "");
    perf_test<signed char, structure_fake>("char->signed char", 'c', "");


    fout << "]\n"
        << "[endsect]\n\n";
    return 0;
}


