/**
 * variant.hpp
 *
 * This file is part of Algorithms library toolkit.
 * Copyright (C) 2017 Jan Travnicek (jan.travnicek@fit.cvut.cz)

 * Algorithms library toolkit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Algorithms library toolkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Algorithms library toolkit.  If not, see <http://www.gnu.org/licenses/>.
 *
 * https://gist.github.com/tibordp/6909880
 *
 * Created on: Feb 28, 2014
 * Author: Tibor Djurica Potpara
 * Modified: Jan Travnicek
 */

#ifndef __VATIANT_HPP_
#define __VATIANT_HPP_

#include <iostream>
#include <variant>

#include <extensions/compare.hpp>
#include <extensions/typeindex.h>
#include <extensions/type_traits.hpp>

#include "string.hpp"

namespace ext {

template < class Visitor, class... Variants >
constexpr auto visit ( Visitor && vis, Variants && ... vars ) {
	auto extToStd = [] ( auto && variant ) {
		using __std__variant = typename std::decay_t < decltype ( variant ) >::__base;

		if constexpr ( std::is_lvalue_reference_v < decltype ( variant ) > && std::is_const_v < std::remove_reference_t < decltype ( variant ) > > ) {
			return static_cast < const __std__variant & > ( variant );
		} else if constexpr ( std::is_lvalue_reference_v < decltype ( variant ) > ) {
			return static_cast < __std__variant & > ( variant );
		} else if constexpr ( std::is_rvalue_reference_v < decltype ( variant ) > ) {
			return static_cast < __std__variant && > ( variant );
		} else {
			static_assert ( "Unhandled option" );
		}
	};

	return std::visit ( std::forward < Visitor > ( vis ), extToStd ( std::forward < Variants > ( vars ) ) ... );
}

/**
 * \brief
 * Class to help building of the variant type or, in case variant is requested to be constructed from single type or more types but all the same, that concrete type.
 *
 * \tparam Res the resulting contstructed variant
 * \tparam Ts ... pack of types to construct the variant from
 */
template < class Res, class ... Ts >
struct variant_builder_impl {
};

/**
 * \brief
 * Recursion ending specialisation of the helper class to build the variant type. The last type T is added as last type to the variant parameters.
 *
 * \tparam ResTs ... pack of types already accepted as types of the variant
 * \tparam T last type to construct the variant from
 */
template < class ... ResTs >
struct variant_builder_impl < std::variant < ResTs ... > > : public std::variant < ResTs ... > {
	typedef std::variant < ResTs ... > __std__variant;
};

/**
 * \brief
 * Recursive step of class to build the variant type.
 *
 * \tparam ResTs ... pack of types already accepted as types of the variant
 * \tparam T next type to construct the variant from
 * \tparam Ts ... remaining types to add to variant template parameters
 */
template < class ... ResTs, class T, class ... Ts >
struct variant_builder_impl < std::variant < ResTs ... >, T, Ts ... > : public variant_builder_impl < typename std::conditional < is_in < T, ResTs ... >::value, std::variant < ResTs ... >, std::variant < ResTs ..., T > >::type, Ts ... > {
};

template < class T, class ... Ts >
struct variant_builder_start : public variant_builder_impl < std::variant < T >, Ts ... > {
};

template < class ... Ts >
using __std__variant = typename variant_builder_start < Ts ... >::__std__variant;

/**
 * \brief
 * Implementation of the variant class allowing to store any type of those listed in the template parameters. Void type is allowed, multiply specified same type is irrelevant.
 *
 * \tparam Ts ... pack of allowed types.
 */
template < typename ... Ts >
class variant : public __std__variant < Ts ... > {
	/**
	 * Internal variant to string callback implementation.
	 */
	class VariantToString {
	public:
		/**
		 * The callback for types different from void
		 *
		 * \tparam T the accepted tye
		 *
		 * \param value the accepted value
		 *
		 * \return string representation of the value
		 */
		template < class T >
		std::string operator ( ) ( const T & value ) const {
			return ext::to_string ( value );
		}
	};

	/**
	 * Internal variant to ostream callback implementation.
	 */
	class VariantToStream {
		/**
		 * The stream to print to.
		 */
		std::ostream & m_out;
	public:
		/**
		 * Constructor of the variant to stream callback.
		 *
		 * \param out the output stream to use in function call operators.
		 */
		explicit VariantToStream ( std::ostream & out ) : m_out ( out ) {
		}

		/**
		 * The callback for types different from void
		 *
		 * \tparam T the accepted tye
		 *
		 * \param value the accepted value
		 */
		template < class T >
		void operator ( ) ( const T & value ) {
			m_out << value;
		}
	};

	/**
	 * Internal variant to ostream callback implementation.
	 */
	class VariantCompare {
		const ext::variant < Ts ... > & second;
	public:
		explicit VariantCompare ( const ext::variant < Ts ... > & inst ) : second ( inst ) {
		}

		/**
		 * The callback for types different from void
		 *
		 * \tparam T the accepted tye
		 *
		 * \param value the accepted value
		 */
		template < class T >
		int operator ( ) ( const T & first ) {
			return - second.compare ( first );
		}
	};

	/**
	 * Internal variant to ostream callback implementation.
	 */
	template < class R >
	class VariantCompare2 {
		const R & second;
	public:
		explicit VariantCompare2 ( const R & inst ) : second ( inst ) {
		}

		/**
		 * The callback for types different from void
		 *
		 * \tparam T the accepted tye
		 *
		 * \param value the accepted value
		 */
		template < class T >
		int operator ( ) ( const T & first ) {
			if constexpr ( ! std::is_same < T, R >::value ) {
				return ext::type_index ( typeid ( T ) ) - ext::type_index ( typeid ( R ) );
			} else {
				static ext::compare < typename std::decay < T >::type > comp;
				return comp ( first, second );
			}
		}
	};
public:
	/**
	 * Remember the base class, i.e. std::variant
	 */
	typedef __std__variant < Ts ... > __base;

	/**
	 * Inherit constructors of the standard variant
	 */
	using __std__variant < Ts ... >::__std__variant; // NOLINT(modernize-use-equals-default)

	/**
	 * Inherit operator = of the standard variant
	 */
	using __std__variant < Ts ... >::operator=;
#ifndef __clang__

	/**
	 * Default constructor needed by g++ since it is not inherited
	 */
	variant ( ) = default;

	/**
	 * Copy constructor needed by g++ since it is not inherited
	 */
	variant ( const variant & other ) = default;

	/**
	 * Move constructor needed by g++ since it is not inherited
	 */
	variant ( variant && other ) = default;

	/**
	 * Copy operator = needed by g++ since it is not inherited
	 */
	variant & operator = ( variant && other ) = default;

	/**
	 * Move operator = needed by g++ since it is not inherited
	 */
	variant & operator = ( const variant & other ) = default;
#endif
	/**
	 * \brief
	 * Implementation of the three way comparison for varant.
	 *
	 * \param other the value compared to this
	 *
	 * \return negative value if this is smaller than the other instance, positive if this is greater than the other instance, zero othervise
	 */
	int compare ( const variant < Ts ... > & other ) const {
		return ext::visit ( VariantCompare ( other ), * this );
	}

	/**
	 * \brief
	 * Implementation of the three way comparison for varant.
	 *
	 * \tparam T the type of the other compared value
	 *
	 * \param other the value compared to this
	 *
	 * \return negative value if this is smaller than the other instance, positive if this is greater than the other instance, zero othervise
	 */
	template < typename T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
	int compare ( const T & other ) const {
		return ext::visit ( VariantCompare2 < T > ( other ), * this );
	}

	/**
	 * \brief
	 * Function to test whether the variant currently contains type T.
	 *
	 * \tparam T the type to test
	 *
	 * \return true it the variant contains instance of type T
	 */
	template < typename T >
	bool is() const {
		return std::holds_alternative < T > ( * this );
	}

	/**
	 * \brief
	 * Allows to assign a value to the variant.
	 *
	 * \tparam T the type of the value to assign
	 *
	 * \param value the new value to assign to the variant
	 */
	template < typename T >
	void set ( typename std::enable_if < ext::is_in<T, Ts...>::value, T >::type && value ) {
		* this = std::move ( value );
	}

	/**
	 * \brief
	 * Allows to assign a value to the variant.
	 *
	 * \tparam T the type of the value to assign
	 *
	 * \param value the new value to assign to the variant
	 */
	template < typename T >
	void set ( const typename std::enable_if < ext::is_in<T, Ts...>::value, T >::type & value ) {
		* this = value;
	}

	/**
	 * \brief
	 * Allows to retrieve a value from the variant.
	 *
	 * \tparam T the type of the value to retrieve
	 *
	 * \return value retrieved from the variant
	 *
	 * \throws bad_cast exception if the variant stores different type than specified
	 */
	template < typename T >
	T & get ( ) & {
		return std::get < T > ( * this );
	}

	/**
	 * \brief
	 * Allows to retrieve a value from the variant.
	 *
	 * \tparam T the type of the value to retrieve
	 *
	 * \return value retrieved from the variant
	 *
	 * \throws bad_cast exception if the variant stores different type than specified
	 */
	template<typename T >
	const T & get ( ) const & {
		return std::get < T > ( * this );
	}

	/**
	 * \brief
	 * Allows to retrieve a value from the variant.
	 *
	 * \tparam T the type of the value to retrieve
	 *
	 * \return value retrieved from the variant
	 *
	 * \throws bad_cast exception if the variant stores different type than specified
	 */
	template < typename T >
	T && get ( ) && {
		return std::move ( std::get < T > ( * this ) );
	}

	/**
	 * \brief
	 * Callback executor on current variant value.
	 *
	 * \tparam Result the uniform result type
	 * \tparam Callable type of callable able to accept any variant type.
	 *
	 * \param callable object of callable type able to accept any variant type via function call operator. The possible void type is represented by call with no parameters
	 *
	 * \return the result of the callable on the current variable type
	 */
	template < class Result, class Callable >
	Result call ( Callable callable ) const {
		return ext::visit ( callable, * this );
	}

	/**
	 * \brief
	 * Operator to print the variant to the output stream.
	 *
	 * \param out the output stream
	 * \param obj the variant to print
	 *
	 * \return the output stream from the \p out
	 */
	friend std::ostream & operator << ( std::ostream & out, const variant < Ts ... > & obj ) {
		obj.call < void > ( VariantToStream ( out ) );
		return out;
	}

	/**
	 * \brief
	 * To string cast operator.
	 *
	 * \return string representation of the variant
	 */
	explicit operator std::string() const {
		return this->call < std::string > ( VariantToString ( ) );
	}
};

/**
 * \brief
 * Specialisation of the compare structure implementing the three-way comparison.
 *
 * \tparam Ts ... types in the variant
 */
template<typename ... Ts>
struct compare < ext::variant < Ts ... > > {

	/**
	 * \brief
	 * Implementation of the three-way comparison.
	 *
	 * \param first the left operand of the comparison
	 * \param second the right operand of the comparison
	 *
	 * \return negative value of left < right, positive value if left > right, zero if left == right
	 */
	int operator()(const ext::variant < Ts ... > & first, const ext::variant < Ts ... > & second) const {
		return first.compare(second);
	}
};

/**
 * \brief
 * Overload of to_string function.
 *
 * \param value the vector to be converted to string
 *
 * \tparam Ts ... types in the variant
 *
 * \return string representation
 */
template < class ... Ts >
std::string to_string ( const ext::variant < Ts ... > & value ) {
	return ( std::string ) value;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator < ( const ext::variant < Ts ... > & first, const T & second ) {
	return first.compare ( second ) < 0;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator > ( const ext::variant < Ts ... > & first, const T & second ) {
	return first.compare ( second ) > 0;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator <= ( const ext::variant < Ts ... > & first, const T & second ) {
	return first.compare ( second ) <= 0;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator >= ( const ext::variant < Ts ... > & first, const T & second ) {
	return first.compare ( second ) >= 0;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator == ( const ext::variant < Ts ... > & first, const T & second ) {
	return first.compare ( second ) == 0;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator != ( const ext::variant < Ts ... > & first, const T & second ) {
	return first.compare ( second ) != 0;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator < ( const T & first, const ext::variant < Ts ... > & second ) {
	return second > first;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator > ( const T & first, const ext::variant < Ts ... > & second ) {
	return second < first;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator <= ( const T & first, const ext::variant < Ts ... > & second ) {
	return second >= first;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator >= ( const T & first, const ext::variant < Ts ... > & second ) {
	return second <= first;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator == ( const T & first, const ext::variant < Ts ... > & second ) {
	return second == first;
}

template < class ... Ts, class T, typename std::enable_if < ext::is_in < T, Ts ... >::value >::type * = nullptr >
bool operator != ( const T & first, const ext::variant < Ts ... > & second ) {
	return second != first;
}

} /* namespace ext */

namespace std {

template < class ... Types >
struct variant_size < ext::variant < Types ... > > : public variant_size < typename ext::variant < Types ... >::__std__variant > { };

template < size_t _Np, typename ... _Types >
struct variant_alternative < _Np, ext::variant < _Types ... > > {
	using type = variant_alternative_t < _Np, typename ext::variant < _Types ... >::__std__variant >;
};

}

#endif /* __VARIANT_HPP_ */
