#include "pch.h"
#include "Cpp17.h"

#include <string>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <any>
#include <iostream>
#include <filesystem>
#include <variant>
#include <exception>
#include <stdexcept>
#include <cmath>

//#define NDEBUG		// disable assert() for release

namespace MODERNCPP::CPP17
{
	long Cpp17::Preprocessor(unsigned i = 0b00)
	{
#if __cpp_binary_literals                    // Check for the presence of a feature
		unsigned mask1 = 0b11000000;
		unsigned mask2 = 0b00000111;
#else
		unsigned mask1 = 0xC0;
		unsigned mask2 = 0x07;
#endif
		if (i & mask1)
			return 1;
		if (i & mask2)
			return 2;
		return 0;
	}

	void Cpp17::StaticAssert()
	{
		assert(true && "Could not be found in database");		// C++98

		//static_assert(sizeof(long) == 8, "long must be 8 bytes");
		static_assert(sizeof(int) == 4, "int must be 4 bytes");		// C++11
		static_assert(sizeof(int) == 4);							// C++17, no need message
	}

	void Cpp17::StdAny()
	{
		std::cout << std::boolalpha;

		// any type
		std::any a = 1;
		std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
		a = 3.14;
		std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
		a = true;
		std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';

		// bad cast
		try
		{
			a = 1;
			std::cout << std::any_cast<float>(a) << '\n';
		}
		catch (const std::bad_any_cast& e)
		{
			std::cout << e.what() << '\n';
		}

		// has value
		a = 1;
		if (a.has_value())
		{
			std::cout << a.type().name() << '\n';
		}

		// reset
		a.reset();
		if (!a.has_value())
		{
			std::cout << "no value\n";
		}

		// pointer to contained data
		a = 1;
		int* i = std::any_cast<int>(&a);
		std::cout << *i << "\n";
	}

	void Cpp17::StdByte()
	{
		int n = 7;
		std::byte b = (std::byte) n;

		int x = std::to_integer<int>(b);
		std::cout << x << endl;
	}

	void Cpp17::StdFileSystem()
	{
		namespace fs = std::filesystem;	
		fs::path p = "C:/a/b/c/d/e/f";

		if (!fs::exists(p))
		{
			fs::create_directories(p);
			fs::remove_all("C:/a/");
		}
		std::cout << fs::current_path();
	}

	void Cpp17::StdOptional()
	{
		std::cout << "create1(false) returned "
			<< create1(false).value_or("empty") << '\n';

		// optional-returning factory functions are usable as conditions of while and if
		if (auto str = create2(true)) {
			std::cout << "create2(true) returned " << *str << '\n';
		}

		if (auto str = create_ref(true)) {
			// using get() to access the reference_wrapper's value
			std::cout << "create_ref(true) returned " << str->get() << '\n';
			str->get() = "Mothra";
			std::cout << "modifying it changed it to " << str->get() << '\n';
		}
	}

	void Cpp17::StdVariant()
	{
		std::variant<int, float> v, w;
		v = 12; // v contains int
		int i = std::get<int>(v);
		w = std::get<int>(v);
		w = std::get<0>(v);		// same effect as the previous line
		w = v;					// same effect as the previous line

	//  std::get<double>(v);	// error: no double in [int, float]
	//  std::get<3>(v);			// error: valid index values are 0 and 1

		try {
			std::get<float>(w); // w contains int, not float: will throw
		}
		catch (const std::bad_variant_access&) {}

		std::variant<std::string> x("abc"); // converting constructors work when unambiguous
		x = "def"; // converting assignment also works when unambiguous

		std::variant<std::string, bool> y("abc"); // casts to bool when passed a char const *
		assert(std::holds_alternative<bool>(y)); // succeeds
		y = "xyz"s;
		assert(std::holds_alternative<std::string>(y)); //succeeds
	}

	void Cpp17::StdConjunction()
	{
		func(1, 2, 3);
		func(1, 2, "hello!");
	}

	void Cpp17::StdDisjunction()
	{
		using result0 =
			std::disjunction<std::bool_constant<false>, std::bool_constant<false>,
			std::bool_constant<false>>;
		using result1 =
			std::disjunction<std::bool_constant<true>, std::bool_constant<false>,
			std::bool_constant<false>>;

		std::cout << std::boolalpha;
		std::cout << result0::value << '\n';
		std::cout << result1::value << '\n';
	}

	void Cpp17::StdNegation()
	{
		static_assert(
			std::is_same<
			std::bool_constant<false>,
			typename std::negation<std::bool_constant<true>>::type>::value,
			"");
		static_assert(
			std::is_same<
			std::bool_constant<true>,
			typename std::negation<std::bool_constant<false>>::type>::value,
			"");

		std::cout << std::boolalpha;
		std::cout << std::negation<std::bool_constant<true>>::value << '\n';
		std::cout << std::negation<std::bool_constant<false>>::value << '\n';
	}

	void Cpp17::StdUncaughtExceptions()
	{
		struct Foo {
			int count = std::uncaught_exceptions();
			~Foo() {
				std::cout << (count == std::uncaught_exceptions()
					? "~Foo() called normally\n"
					: "~Foo() called during stack unwinding\n");
			}
		};

		Foo f;
		try {
			Foo f;
			std::cout << "Exception thrown\n";
			throw std::runtime_error("test exception");
		}
		catch (const std::exception& e) {
			std::cout << "Exception caught: " << e.what() << '\n';
		}
	}

	void Cpp17::StructuredBindingDeclaration()
	{
		// used structured binding declaration to put results of tuple in variables a and b
		auto[a, b] = returnTuple(); 

		std::cout << a << ' ' << b << '\n';
	}

	void Cpp17::IfSwitchInitializer()
	{
		auto getval = [value = 1]{ return value; };

		if (auto a = getval(); a < 10) {
			std::cout << a << endl;
		}

		switch (auto b = getval(); b) {
		case 1:
			std::cout << b << endl;
			break;
		}
	}

	void Cpp17::InlineVariable()
	{
		std::cout << inline_variable << endl;
	}

	void Cpp17::FoldExpression()
	{
		cout << sum1(1, 2, 3, 4, 5, 6, 7) << "\n";
		cout << sum2(1, 2, 3, 4, 5, 6, 7) << "\n";

		FoldPrint("hello", ", ", 10, ", ", 90.0);

		std::vector<int> v;
		FoldPushBack(v, 1, 2, 3, 4);

		for (auto &i : v)
			std::cout << i << "\n";
	}

	void Cpp17::RemovedTrigraphs()
	{
		std::cout << "??=" << " ";		// #
		std::cout << "??//" << " ";		// /
		std::cout << "??'" << " ";		// ^
		std::cout << "??)" << " ";		// ]
		std::cout << "??(" << " ";		// [
		std::cout << "??!" << " ";		// |
		std::cout << "??<" << " ";		// {
		std::cout << "??>" << " ";		// }
		std::cout << "??-" << " ";		// ~
		std::cout << std::endl;
	}

	void Cpp17::Utf8Literals()
	{
		auto h = u8"Hello World";
		std::cout << h << endl;
	}

	void Cpp17::HexFloatingPointLiterals()
	{
		double d = 0x1.2p3; // hex fraction 1.2 (decimal 1.125) scaled by 2^3, that is 9.0

		std::cout << 58. << '\n'
			<< 4e2 << '\n'
			<< 123.456e-67 << '\n'
			<< .1E4f << '\n'
			<< d << '\n'
			<< 0x10.1p0 << '\n';
	}

	void Cpp17::TemplateDeductionConstructor()
	{
		// similar to call TemplateArgumentDeduction
		auto [a, b] = std::pair(5.0, false);		// instead of std::pair<double, bool>(5.0, false)
		std::cout << a << " " << b << endl;
	}

	// suppresses compiler warnings about unused param entities.
	int Cpp17::StandardAttributes(bool b [[maybe_unused]])
	{
		int num = 0;

		switch (num) {
		case 0:
			// DO your stuff....
			// indicates that a fallthrough in a switch statement is intentional.
			[[fallthrough]];	// no need break

		default:
			return num;
		}
		return num;
	}

	void Cpp17::SpecialMathFunctions()
	{
		// EACH math function has 3 versions
		// double		func
		// float		func_f
		// long double	func_l

		auto binom = [](int n, int k) { return 1 / ((n + 1)*std::beta(n - k + 1, k + 1)); };

		// associated Laguerre polynomials 
		std::cout << std::assoc_laguerre(1, 10, 0.5) << endl;

		// associated Legendre polynomials 
		std::cout << std::assoc_legendre(1, 10, 0.5) << endl;

		// beta function
		std::cout << binom(1, 5) << endl;

		// (complete)elliptic integral of the first kind
		std::cout << std::comp_ellint_1(3.14159) << endl;

		// (complete) elliptic integral of the second kind 
		std::cout << std::comp_ellint_2(3.14159) << endl;

		// (complete) elliptic integral of the third kind 
		std::cout << std::comp_ellint_3(3.14159, 1.0) << endl;

		// regular modified cylindrical Bessel functions 
		std::cout << std::cyl_bessel_i(0, 1.2345) << endl;

		// cylindrical Bessel functions (of the first kind) 
		std::cout << std::cyl_bessel_j(0, 1.2345) << endl;

		// irregular modified cylindrical Bessel functions
		std::cout << std::cyl_bessel_k(0, 1.2345) << endl;

		// cylindrical Neumann functions 
		std::cout << std::cyl_neumann(0.5, 1.2345) << endl;

		// cylindrical Neumann functions 
		std::cout << std::ellint_1(0.5, 1.2345) << endl;

		// (incomplete)elliptic integral of the second kind
		std::cout << std::ellint_2(0.5, 1.2345) << endl;

		// exponential integral
		std::cout << std::expint(0) << endl;

		// hermite polynomials 
		std::cout << std::hermite(3, 10) << endl;

		// Legendre polynomials 
		std::cout << std::legendre(3, 10) << endl;

		// Laguerre polynomials 
		std::cout << std::laguerre(3, 10) << endl;

		// Riemann zeta function 
		std::cout << std::riemann_zeta(3) << endl;

		// spherical Bessel functions (of the first kind) 
		std::cout << std::sph_bessel(3, 6) << endl;

		// spherical associated Legendre functions 
		std::cout << std::sph_legendre(3, 6, 9.0) << endl;

		// spherical Neumann functions 
		std::cout << std::sph_neumann(3, 6.0) << endl;
	}
}