#include "catch.hpp"

#include <mapnik/value/types.hpp>
#include <mapnik/value.hpp>
#include <mapnik/unicode.hpp>
#include <mapnik/util/conversions.hpp>

#include <iostream>
#include <unordered_map>
#include <sstream>

#if defined(_MSC_VER) && _MSC_VER < 1900
#include <cstdio>
#endif

TEST_CASE("conversions") {

SECTION("to string") {

    #if defined(_MSC_VER) && _MSC_VER < 1900
    unsigned int old = _set_output_format(_TWO_DIGIT_EXPONENT);
    #endif

    using mapnik::util::to_string;
    using mapnik::util::string2bool;

    try
    {
        std::string out;

        // Test double
        to_string(out, double(0));
        REQUIRE( out ==  "0" );
        out.clear();

        to_string(out, double(1));
        REQUIRE( out ==  "1" );
        out.clear();

        to_string(out, double(-1));
        REQUIRE( out ==  "-1" );
        out.clear();

        to_string(out, double(0.1));
        REQUIRE( out ==  "0.1" );
        out.clear();

        to_string(out, double(-0.1));
        REQUIRE( out ==  "-0.1" );
        out.clear();

        to_string(out, double(0.123));
        REQUIRE( out ==  "0.123" );
        out.clear();

        to_string(out, double(-0.123));
        REQUIRE( out ==  "-0.123" );
        out.clear();

        to_string(out, double(1e-06));
        REQUIRE( out ==  "1e-06" );
        out.clear();

        to_string(out, double(-1e-06));
        REQUIRE( out ==  "-1e-06" );
        out.clear();

        to_string(out, double(1e-05));
        REQUIRE( out ==  "1e-05" );
        out.clear();

        to_string(out, double(-1e-05));
        REQUIRE( out ==  "-1e-05" );
        out.clear();

        to_string(out, double(0.0001));
        REQUIRE( out ==  "0.0001" );
        out.clear();

        to_string(out, double(-0.0001));
        REQUIRE( out ==  "-0.0001" );
        out.clear();

        to_string(out, double(0.0001));
        REQUIRE( out ==  "0.0001" );
        out.clear();

        to_string(out, double(0.00001));
        REQUIRE( out ==  "1e-05" );
        out.clear();

        to_string(out, double(0.000001));
        REQUIRE( out ==  "1e-06" );
        out.clear();

        to_string(out, double(0.0000001));
        REQUIRE( out ==  "1e-07" );
        out.clear();

        to_string(out, double(0.00000001));
        REQUIRE( out ==  "1e-08" );
        out.clear();

        to_string(out, double(0.000000001));
        REQUIRE( out ==  "1e-09" );
        out.clear();

        to_string(out, double(0.0000000001));
        REQUIRE( out ==  "1e-10" );
        out.clear();

        to_string(out, double(-1.234e+16));
        REQUIRE( out ==  "-1.234e+16" );
        out.clear();

        // critical failure when karam is used
        // https://github.com/mapnik/mapnik/issues/1741
        // https://github.com/mapbox/tilemill/issues/1456
        to_string(out, double(8.3));
        REQUIRE( out ==  "8.3" );
        out.clear();

        // non-critical failures if karma is used
        to_string(out, double(0.0001234567890123456));
        // TODO: https://github.com/mapnik/mapnik/issues/1676
        REQUIRE( out ==  "0.000123457" );
        out.clear();

        to_string(out, double(0.00000000001));
        REQUIRE( out ==  "1e-11" );
        out.clear();

        to_string(out, double(0.000000000001));
        REQUIRE( out ==  "1e-12" );
        out.clear();

        to_string(out, double(0.0000000000001));
        REQUIRE( out ==  "1e-13" );
        out.clear();

        to_string(out, double(0.00000000000001));
        REQUIRE( out ==  "1e-14" );
        out.clear();

        to_string(out, double(0.000000000000001));
        REQUIRE( out ==  "1e-15" );
        out.clear();

        to_string(out, double(100000));
        REQUIRE( out ==  "100000" );
        out.clear();

        to_string(out, double(1000000));
        REQUIRE( out ==  "1e+06" );
        out.clear();

        to_string(out, double(10000000));
        REQUIRE( out ==  "1e+07" );
        out.clear();

        to_string(out, double(100000000));
        REQUIRE( out ==  "1e+08" );
        out.clear();

        to_string(out, double(1000000000));
        REQUIRE( out ==  "1e+09" );
        out.clear();

        to_string(out, double(10000000000));
        REQUIRE( out ==  "1e+10" );
        out.clear();

        to_string(out, double(100000000000));
        REQUIRE( out ==  "1e+11" );
        out.clear();

        to_string(out, double(1000000000000));
        REQUIRE( out ==  "1e+12" );
        out.clear();

        to_string(out, double(10000000000000));
        REQUIRE( out ==  "1e+13" );
        out.clear();

        to_string(out, double(100000000000000));
        REQUIRE( out ==  "1e+14" );
        out.clear();

        to_string(out, double(1000000000000005));
        REQUIRE( out ==  "1e+15" );
        out.clear();

        to_string(out, double(-1000000000000000));
        REQUIRE( out ==  "-1e+15" );
        out.clear();

        to_string(out, double(100000000000000.1));
        REQUIRE( out ==  "1e+14" );
        out.clear();

        to_string(out, double(1.00001));
        REQUIRE( out ==  "1.00001" );
        out.clear();

        to_string(out, double(67.65));
        REQUIRE( out ==  "67.65" );
        out.clear();

        to_string(out, double(67.35));
        REQUIRE( out ==  "67.35" );
        out.clear();

        to_string(out, double(1234000000000000));
        REQUIRE( out ==  "1.234e+15" );
        out.clear();

        to_string(out, double(1e+16));
        REQUIRE( out ==  "1e+16" );
        out.clear();

        to_string(out, double(1.234e+16));
        REQUIRE( out ==  "1.234e+16" );
        out.clear();

        // int
        to_string(out, int(2));
        REQUIRE( out == "2" );
        out.clear();

        to_string(out, int(0));
        REQUIRE( out == "0" );
        out.clear();

        to_string(out, int(-2));
        REQUIRE( out == "-2" );
        out.clear();

        to_string(out, int(2147483647));
        REQUIRE( out == "2147483647" );
        out.clear();

        to_string(out, int(-2147483648));
        REQUIRE( out == "-2147483648" );
        out.clear();

        // unsigned
        to_string(out, unsigned(4294967295));
        REQUIRE( out == "4294967295" );
        out.clear();

#ifdef BIGINT
        // long long
        to_string(out,mapnik::value_integer(-0));
        REQUIRE( out == "0" );
        out.clear();

        to_string(out,mapnik::value_integer(-2));
        REQUIRE( out == "-2" );
        out.clear();

        to_string(out,mapnik::value_integer(9223372036854775807));
        REQUIRE( out == "9223372036854775807" );
        out.clear();
#else
  #ifdef _MSC_VER
    #pragma NOTE("BIGINT not defined so skipping large number conversion tests")
  #else
    #warning BIGINT not defined so skipping large number conversion tests
  #endif
#endif
        // bool
        to_string(out, true);
        REQUIRE( out == "true" );
        out.clear();

        to_string(out, false);
        REQUIRE( out == "false" );
        out.clear();

        bool val = false;
        REQUIRE( !string2bool("this is invalid",val) );
        REQUIRE( val == false );
        REQUIRE( string2bool("true",val) );
        REQUIRE( val == true );

        // mapnik::value hash() and operator== works for all T in value<Types...>
        mapnik::transcoder tr("utf8");
        using values_container = std::unordered_map<mapnik::value, mapnik::value>;
        values_container vc;
        mapnik::value keys[5] = {true, 123456789, 3.14159f, tr.transcode("Мапник"), mapnik::value_null()} ;
        for (auto const& k : keys)
        {
            vc.insert({k, k});
            REQUIRE( vc[k] == k );
        }

        // mapnik::value << to ostream
        std::stringstream s;
        mapnik::value_unicode_string ustr = tr.transcode("hello world!");
        mapnik::value streamable(ustr);
        s << streamable;
        CHECK( s.str() == std::string("hello world!") );

    }
    catch (std::exception const & ex)
    {
        std::clog << ex.what() << "\n";
        REQUIRE(false);
    }
}

}