/*
 * Copyright (C) 2014 the LinBox group
 *
 * Written by:
 *            Brice Boyer (briceboyer) <boyer.brice@gmail.com>
 *
 * ========LICENCE========
 * This file is part of the library LinBox.
 *
 * LinBox is free software: you can redistribute it and/or modify
 * it under the terms of the  GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ========LICENCE========
 *.
 */

/** @file linbox/matrix/matrixdomain/apply-domain.h
 * @brief NO DOC
 */

#ifndef __LINBOX_matrixdomain_apply_domain_H
#define __LINBOX_matrixdomain_apply_domain_H

#include "linbox/integer.h"
#include "linbox/linbox-tags.h"

namespace LinBox {

	struct applyMethod {
		struct Sequential {} ;
		struct OpenMP {} ;
		struct OpenCL {} ;
		struct Cuda {};
	} ;

	template<class _Matrix/*, class Method = applyMethod::Sequential */>
	class applyDomain {
	public:
		typedef typename     _Matrix::Field Field;
		typedef typename Field::Element Element;
	private :
		const Field & _field ;
		MatrixDomain<Field> _MD ;
	private :
		// y = a A x + b y
		template<class _In, class _Out, class Matrix>
		_Out& _apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     , typename ContainerCategories::Vector
			     , typename ContainerCategories::Vector
			    ) const
		{
			if (_field.isZero(a)) {
				_MD.vectorDomain().mulin(y,b);
				return y ;
			}

			if (t == Tag::Transpose::Trans ) {
#if 0
				typename Matrix::ConstColIterator i = A.colBegin ();
				typename _Out::iterator j = y.begin ();
				for (; j != y.end (); ++j, ++i)
					A._VD.dot (*j, x, *i);
#else
				TransposeMatrix<const Matrix> At(A);
				if (_field.isZero(b)) { /* y = a A x */
					_MD.vectorMul(y,At,x); /* y = Ax */

					if (_field.isOne(a)) {
						// return y ;
					}
					else if (_field.isMOne(a) ) { /* y = -y */
						_MD.vectorDomain().negin(y);
					}
					else {/* y = a y */
						_MD.vectorDomain().mulin(y,a);
					}
				}
				else if (_field.isOne(b)) { /* y = a A x + y */
					throw LinBoxError("not implemented yet");
				}
				else if (_field.isMOne(b)) { /* y = a A x - y */
					throw LinBoxError("not implemented yet");
				}
				else { /* y = a A x + b y */
					_MD.vectorDomain().mulin(y,b);
					_In xx(x);
					_MD.vectorDomain().mulin(xx,a);
					_MD.vectorAxpyin(y,At,xx) ;
				}
#endif
			}
			else { /* not Transpose */
				linbox_check( t == Tag::Transpose::NoTrans ) ;
				if (_field.isZero(b)) {/* y = a A x */
					_MD.vectorMul(y,A,x);
					if (_field.isOne(a)) {

					}
					else if (_field.isMOne(a) )
						_MD.vectorDomain().negin(y);
					else  {
						_MD.vectorDomain().mulin(y,a);
					}
				}
				else if (_field.isOne(b)) {/* y = a A x + y */
					throw LinBoxError("not implemented yet");
				}
				else if (_field.isMOne(b)) {/* y = a A x - y */
					throw LinBoxError("not implemented yet");
				}
				else {/* y = a A x + b y */
					_MD.vectorDomain().mulin(y,b);
					_In xx(x);
					_MD.vectorDomain().mulin(xx,a);
					_MD.vectorAxpyin(y,A,xx);
				}

			}
			return y ;
		}

	template<class _In, class _Out, class Matrix>
		_Out& _apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     , typename ContainerCategories::Any
			     , typename ContainerCategories::Any
			     ) const
		{
			// linbox_check(_field.isZero(a) && _field.isOne(a));
			if (t == Tag::Transpose::Trans ) {
				typename Matrix::ConstColIterator i = A.colBegin ();
				typename _Out::iterator j = y.begin ();
				for (; j != y.end (); ++j, ++i)
					A._VD.dot (*j, x, *i);
			}
			else {
				_MD.vectorMul(y,A,x);
			}
			return y;
		}

	public:
		applyDomain(const Field & F) :
			_field(F)
			, _MD(F)
		{}
		/*! Operation \f$y gets a A x + b y\f$ or \f$y gets a A^t x + b y\f$ .
		 * General axpy with x and y vectors or matrices
		 */
		template<class _In, class _Out, class Matrix>
		_Out& apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     // , Method & m
			   ) const
		{
			// std::cout << "called generic" << std::endl;
			return _apply(t,y,a,A,b,x
				      ,typename ContainerTraits<_In> ::ContainerCategory()
				      ,typename ContainerTraits<_Out>::ContainerCategory()
				     );
		}

#if 0
		/*! Operation \f$y gets a A x + b y\f$ or \f$y gets a A^t x + b y\f$ .
		 *(can overwrite A, uses less memory that \c apply)
		 */
		template<class _In, class _Out>
		_Out& applyIn( Tag::Transpose t
			       , _Out& y
			       , const Element & a
			       , Matrix & A
			       , const Element & b
			       , const _In & x
			       // , Method & m
			     );
#endif

	};

	template<class T, class R>
	class applyDomain<const BlasMatrix<Givaro::Modular<T>, R > >{
	public:
		typedef Givaro::Modular<T>                 Field;
		typedef typename Field::Element  Element;
	private:
		const Field & _field ;

		// y = a A x + b y
		template<class _In, class _Out, class Matrix>
		_Out& _apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     , typename ContainerCategories::Vector
			     , typename ContainerCategories::Vector ) const
		{
			// std::cout << "here V" << std::endl;
			// A.write(std::cout,Tag::FileFormat::Maple) << std::endl;
			// std::cout << x << std::endl;


			FFLAS::fgemv(_field
				     , (FFLAS::FFLAS_TRANSPOSE)t,
				     A.rowdim(), A.coldim(),
				     _field.one,
				     A.getPointer(), A.getStride(),
				     x.getPointer(), x.getStride(),
				     _field.zero,
				     y.getWritePointer(),y.getStride());

			// FFLAS::fgemm(_field
				     // , (FFLAS::FFLAS_TRANSPOSE)t
				     // , FFLAS::FflasNoTrans
				     // , A.rowdim(), 1, A.coldim()
				     // , _field.one,
				     // A.getPointer(), A.getStride(),
				     // x.getPointer(),x.getStride(),
				     // _field.zero,
				     // y.getWritePointer(),y.getStride());

			// std::cout << y << std::endl;

			return y ;
		}


		// y = a A x + b y
		template<class Matrix>
		std::vector<Element>& _apply(Tag::Transpose t
		             , std::vector<Element> & y
		             , const Element & a
		             , const Matrix & A
		             , const Element & b
		             , const std::vector<Element> & x
			     , typename ContainerCategories::Vector
			     , typename ContainerCategories::Vector ) const
		{

			FFLAS::fgemv(_field
				     , (FFLAS::FFLAS_TRANSPOSE)t,
				     A.rowdim(), A.coldim(),
				     _field.one,
				     A.getPointer(), A.getStride(),
			             &x[0], 1,
				     _field.zero,
			             &y[0],1);

			return y ;
		}

		// y = a A x + b y
		template<class _In, class _Out, class Matrix>
		_Out& _apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     , typename ContainerCategories::Matrix
			     , typename ContainerCategories::Matrix
			     ) const
		{
			// needs to be debuged (trans?). Never Used.
			// std::cout << "here M" << std::endl;
			FFLAS::fgemm(_field
				     , (FFLAS::FFLAS_TRANSPOSE)t
				     , FFLAS::FflasNoTrans
				     , A.rowdim(), x.coldim(), A.coldim()
				     , _field.one,
				     A.getPointer(), A.getStride(),
				     x.getPointer(),x.getStride(),
				     _field.zero,
				     y.getWritePointer(),y.getStride());

			return y ;
		}

	public:
		applyDomain(const Field & F) :
			_field(F)
		{}

		/*! Operation \f$y gets a A x + b y\f$ or \f$y gets a A^t x + b y\f$ .
		 * General axpy with x and y vectors or matrices
		 */
		template<class _In, class _Out, class Matrix>
		_Out& apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     // , Method & m
			   ) const
		{
			// std::cout << "called Givaro::Modular<T>" << std::endl;
			return _apply(t,y,a,A,b,x
				      ,typename ContainerTraits<_In> ::ContainerCategory()
				      ,typename ContainerTraits<_Out>::ContainerCategory() );
		}

	};

	template<class T, class R>
	class applyDomain<const BlasMatrix<Givaro::ModularBalanced<T>, R > >{
	public:
		typedef Givaro::ModularBalanced<T>             Field;
		typedef typename Field::Element      Element;
	private:
		const Field & _field ;

		// y = a A x + b y
		template<class _In, class _Out, class Matrix>
		_Out& _apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     , typename ContainerCategories::Vector
			     , typename ContainerCategories::Vector
			     ) const
		{
			// std::cout << "here V" << std::endl;
			FFLAS::fgemv(_field, (FFLAS::FFLAS_TRANSPOSE)t,
				     A.rowdim(), A.coldim(),
				     _field.one,
				     A.getPointer(), A.getStride(),
				     x.getPointer(),x.getStride(),
				     // &x[0],ldx,
				     _field.zero,
				     y.getWritePointer(),y.getStride());

			return y ;
		}

		// y = a A x + b y
		template<class _In, class _Out, class Matrix>
		_Out& _apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     , typename ContainerCategories::Matrix
			     , typename ContainerCategories::Matrix
			     ) const
		{
			// needs to be debuged (trans?). Never Used.
			// std::cout << "here M" << std::endl;
			FFLAS::fgemm(_field
				     , (FFLAS::FFLAS_TRANSPOSE)t
				     , FFLAS::FflasNoTrans
				     , A.rowdim(), x.coldim(), A.coldim()
				     , _field.one,
				     A.getPointer(), A.getStride(),
				     x.getPointer(),x.getStride(),
				     _field.zero,
				     y.getWritePointer(),y.getStride());

			return y ;
		}

	public:
		applyDomain(const Field & F) :
			_field(F)
		{}

		/*! Operation \f$y gets a A x + b y\f$ or \f$y gets a A^t x + b y\f$ .
		 * General axpy with x and y vectors or matrices
		 */
		template<class _In, class _Out, class Matrix>
		_Out& apply( Tag::Transpose t
			     , _Out& y
			     , const Element & a
			     , const Matrix & A
			     , const Element & b
			     , const _In & x
			     // , Method & m
			   ) const
		{
			// std::cout << "called Givaro::ModularBalanced<T>" << std::endl;
			return _apply(t,y,a,A,b,x
				      ,typename ContainerTraits<_In >::ContainerCategory()
				      ,typename ContainerTraits<_Out>::ContainerCategory() );
		}

	};

#if 0
	template<class _Field, class _Rep>
	class applyDomain<BlasMatrix<_Field,_Rep> > {
		// dispatch according to field.
		// if modular and p small -> Givaro::ModularBalanced<float>
		// if modular and p medium -> Givaro::ModularBalanced<float>
		// default to triple loop in MatrixDomain
	};

	template<class _Field, class _Rep>
	class applyDomain<BlasMatrix<Givaro::Extension<_Field>,_Rep> > {
		// if large enough: Toom Cook
	};

	template<class _Rep>
	class applyDomain<BlasMatrix<Givaro::ZRing<Integer>,_Rep> > {
		// if large : flint
	};

	template<class T, class _Rep>
	class applyDomain<BlasMatrix<Unparametric<T>,_Rep> > {
		// if T == float/double -> BLAS

	};

	template<class _Rep>
	class applyDomain<BlasMatrix<Givaro::Modular<double>,_Rep> > {
		// Blas
	};

	template<class _Rep>
	class applyDomain<BlasMatrix<Givaro::Modular<float>,_Rep> > {
		// Blas
	};
#endif


} // LinBox

#endif

// Local Variables:
// mode: C++
// tab-width: 4
// indent-tabs-mode: nil
// c-basic-offset: 4
// End:
// vim:sts=4:sw=4:ts=4:et:sr:cino=>s,f0,{0,g0,(0,\:0,t0,+0,=s
