/*******************************************************************************
 * Copyright (c) 2015-2016 ONERA.
 * Ce logiciel est la propriété de l’ONERA (the French Aerospace Lab).
 * Tous droits réservés.
 *
 * Ce programme et les éléments qui l'accompagnent sont mis à disposition
 * aux conditions définies par le contrat de licence logiciel CeCILL-C soumise
 * au droit français et respectant les principes de diffusion des logiciels libres.
 * Vous pouvez utiliser, modifier et/ou redistribuer ce programme
 * sous les conditions de la licence CeCILL-C (http://www.cecill.info).

 * This software is property of ONERA (the French Aerospace Lab).
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under
 * the terms of the CeCILL-C license under French law and
 * abiding by the rules of distribution of free software.
 * You can  use, modify and/ or redistribute the software under the terms of
 * the CeCILL-C license (http://www.cecill.info).
 *
 * Contributeurs/contributors:
 *      Florent Teichteil-Königsbuch (ONERA - Centre de Toulouse) - initial API and implementation
 *
 *******************************************************************************/

#ifndef SCALAR_BACKEND_HH
#define	SCALAR_BACKEND_HH

#include <boost/numeric/ublas/traits.hpp>

namespace epoch {
	namespace algebra {
		class ScalarBackend {
		public :
			
			struct NoTransformation {
				template <typename T>
				inline const T& operator()(const T& x) const {
					return x;
				}
			};
			
			struct ConjugateTransformation {
				template <typename T>
				inline T operator()(const T& x) const {
					return conj(x);
				}
			};
			
			struct MakeRealTransformation {
				template <typename T>
				inline typename T::value_type operator()(const T& x) const {
					return 2.0 * real(x);
				}
			};

			/**
			 * Scalar
			 * @tparam T Type of scalar
			 */
			template <typename T>
			struct Scalar {
				/** Actual implementation */
				typedef T Implementation;

				/**
				 * Generates an identity scalar
				 * @param dim USELESS
				 * @return identity scalar
				 */
				inline static std::auto_ptr<Implementation> identity(const std::size_t& dim) {
					return std::auto_ptr<T>(new T(1.0));
				}

				/**
				 * Generates a zero scalar
				 * @param dim1 USELESS
				 * @param dim2 USELESS
				 * @return zero scalar
				 */
				inline static std::auto_ptr<Implementation> zero(const std::size_t& dim1, const std::size_t& dim2) {
					return std::auto_ptr<T>(new T(0.0));
				}

				/**
				 * Computes the conjugate of a given scalar
				 * @param s Scalar
				 * @return Conjugate of the given scalar
				 */
				inline static std::auto_ptr<Implementation> conj(const Implementation& s) {
					return std::auto_ptr<T>(new T(boost::numeric::ublas::type_traits<T>::conj(s)));
				}

				/**
				 * Computes the real part of a given scalar
				 * @param s Scalar
				 * @return Real part of the given scalar
				 */
				inline static std::auto_ptr<typename boost::numeric::ublas::type_traits<T>::real_type> real(const Implementation& s) {
					return std::auto_ptr<typename boost::numeric::ublas::type_traits<T>::real_type>(
							new typename boost::numeric::ublas::type_traits<T>::real_type(boost::numeric::ublas::type_traits<T>::real(s)));
				}

				/**
				 * Computes the imaginary part of a given scalar
				 * @param s Scalar
				 * @return Imaginary part of the given scalar
				 */
				inline static std::auto_ptr<typename boost::numeric::ublas::type_traits<T>::real_type> imag(const Implementation& s) {
					return std::auto_ptr<typename boost::numeric::ublas::type_traits<T>::real_type>(
							new typename boost::numeric::ublas::type_traits<T>::real_type(boost::numeric::ublas::type_traits<T>::imag(s)));
				}

				/**
				 * Prints a scalar in a given output stream
				 * @param s Sclar
				 * @param o Output stream
				 */
				inline static void print(const Implementation& s, std::ostream& o) {
					o << s;
				}

				/** Computes res += a * ((b * real(L)) - (c * imag(L))) */
				template <typename Tc>
				inline static void computeF1(const T& a, const T& b, const T& c, const typename Scalar<Tc>::Implementation& L, Implementation& res) {
					res += a * ((b * std::real(L)) - (c * std::imag(L)));
				}

				/** Computes res += a * L */
				inline static void computeF2(const T& a, const Implementation& L, Implementation& res) {
					res += a * L;
				}
			};
			
			template <typename T>
			inline std::auto_ptr<T> initialize() const {
				return std::auto_ptr<T>(new T());
			};
			
			// computeO1(a, M, tm, V, tv, res): res = a * tm(M) * tv(V)
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			inline void computeO1(const T1& a,
			                      const T2& M, const T3& mTransformation,
			                      const T4& V, const T5& vTransformation,
			                      T6& res) const {
				res = a * mTransformation(M) * vTransformation(V);
			}

			// computeO2p(V, tv, res): res += tv(V)
			template <typename T1, typename T2, typename T3>
			inline void computeO2p(const T1& V, const T2& vTransformation,
			                       T3& res) const {
				res += vTransformation(V);
			};

			// computeO2m(V, tv, res): res -= tv(V)
			template <typename T1, typename T2, typename T3>
			inline void computeO2m(const T1& V, const T2& vTransformation,
			                       T3& res) const {
				res -= vTransformation(V);
			};

			// computeO3(a, M, tm, V, tv, res): res = a * ((tm(M) * tv(V)) - res)
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			inline void computeO3(const T1& a,
			                      const T2& M, const T3& mTransformation,
			                      const T4& V, const T5& vTransformation,
			                      T6& res) const {
				res = a * ((mTransformation(M) * vTransformation(V)) - res);
			};

			// computeO4p(M, tM, V, tV, res, mr): res += mr(tm(M) * tv(V))
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			inline void computeO4p(const T1& M, const T2& mTransformation,
			                       const T3& V, const T4& vTransformation,
			                       T5& res, const T6& resTransformation) const {
				res += resTransformation(mTransformation(M) * vTransformation(V));
			}

			// computeO4m(M, tM, V, tV, res, mr): res -= mr(tm(M) * tv(V))
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			inline void computeO4m(const T1& M, const T2& mTransformation,
			                       const T3& V, const T4& vTransformation,
			                       T5& res, const T6& resTransformation) const {
				res -= resTransformation(mTransformation(M) * vTransformation(V));
			}

			// computeO5(a, M, tm, res): res = a * tm(M)
			template <typename T1, typename T2, typename T3, typename T4>
			inline void computeO5(const T1& a,
			                      const T2& M, const T3& mTransformation,
			                      T4& res) const {
				res = a * mTransformation(M);
			}

			// computeO6(a, M1, tm1, M2, tm2, res): res = a * (tm1(M1) - tm2(M2))
			template <typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
			inline void computeO6(const T1& a,
			                      const T2& M1, const T3& m1Transformation,
			                      const T4& M2, const T5& m2Transformation,
			                      T6& res) const {
				res = a * (m1Transformation(M1) - m2Transformation(M2));
			}

			// computeO7(M, tm, V, tv, res): res = tm(M) * tv(V)
			template <typename T1, typename T2, typename T3, typename T4, typename T5>
			inline void computeO7(const T1& M, const T2& mTransformation,
			                      const T3& V, const T4& vTransformation,
			                      T5& res) const {
				res = mTransformation(M) * vTransformation(V);
			}

			// computeO8(a, res): res *= a
			template <typename T1, typename T2>
			inline void computeO8(const T1& a, T2& res) const {
				res *= a;
			}

			// computeO9(a, M, res): res = a * (res - M)
			template <typename T1, typename T2, typename T3>
			inline void computeO9(const T1& a,
			                      const T2& M,
			                      T3& res) const {
				res = a * (res - M);
			}
		}; // class ScalarBackend
	} // namespace algebra
} // namespace epoch

#endif // SCALAR_BACKEND_HH
