#pragma once

#include <psemek/util/enum.hpp>

#include <cmath>
#include <algorithm>
#include <exception>
#include <cstdint>

namespace psemek::ml
{

	// All activation functions are chosen in a way so that the derivative
	//   can be expressed as a function of the activation function's value, i.e.
	//   f'(x) = G(f(x)) for some G: R -> R
	psemek_declare_enum(activation_type, std::uint8_t,
		(id)
		(sigmoid)
		(tanh)
		(relu)
	)

	template <typename T>
	T activation(T x, activation_type type)
	{
		switch (type) {
		case activation_type::id:
			return x;
		case activation_type::sigmoid:
			return 1.f / (1.f + std::exp(-x));
		case activation_type::tanh:
			return 2.f / (1.f + std::exp(- 2.f * x)) - 1.f;
		case activation_type::relu:
			return std::max(T{0}, x);
		default:
			throw util::unknown_enum_value_exception{type};
		}
	}

	template <typename T>
	T activation_derivative(T value, activation_type type)
	{
		switch (type) {
		case activation_type::id:
			return T{1};
		case activation_type::sigmoid:
			return value * (T{1} - value);
		case activation_type::tanh:
			return T{1} - value * value;
		case activation_type::relu:
			return value == T{0} ? T{0} : T{1};
		default:
			throw util::unknown_enum_value_exception{type};
		}
	}

}
