#pragma once

#include "mat.h"
#include "diag_mat.h"
#include "functions.h"

#include <limits>
#include <algorithm>

namespace cgv {
namespace math {

//helper funcition for svd
template <typename T>
T pythag(const T a, const T b) {
		T absa=std::abs(a), absb=std::abs(b);
		return T((absa > absb ? absa*sqrt(1.0+(absb/absa)*(absb/absa)) :
			(absb == 0.0 ? 0.0 : absb*sqrt(1.0+(absa/absb)*(absa/absb)))));
	}

///computes the singular value decomposition of an MxN matrix a = u* w * v^t where u is an MxN matrix, w is a diagonal
///NxN matrix and v is a NxN square matrix. If the algorithm can't achieve a convergence after 20 iteration false is returned other wise true.
///The resulting matrices are stored in the parameters u,w,v. Attention: v is returned not v^T ! So to compute the original matrix a from the 
///decomposed matrices you have to multiply u*w*transpose(v). 
///It is possible to store u directly into a to save memory, just put the same reference into a and u.
///If ordering is true the singular values are sorted in descending order.
///To ensure that u*w*v^t remains equal to the matrix a the algorithm also exchanges the columns of u and v. 
template <typename T>
bool svd(const mat<T> &a, mat<T> &u, diag_mat<T> &w,  mat<T> &v,bool ordering=true, int maxiter=30)
{
	int m = a.nrows();
	int n = a.ncols();
	
	u=a;
	v.resize(n,n);
	w.resize(n);
	T eps=std::numeric_limits<T>::epsilon();
	//decompose
	bool flag;
		int i,its,j,jj,k,l,nm;
		T anorm,c,f,g,h,s,scale,x,y,z;
		vec<T> rv1(n);
		g = scale = anorm = 0.0;
		for (i=0;i<n;i++) {
			l=i+2;
			rv1[i]=scale*g;
			g=s=scale=0.0;
			if (i < m) {
				for (k=i;k<m;k++) scale += std::abs(u(k,i));
				if (scale != 0.0) {
					for (k=i;k<m;k++) {
						u(k,i) /= scale;
						s += u(k,i)*u(k,i);
					}
					f=u(i,i);
					g = -sign(std::sqrt(s),f);
					h=f*g-s;
					u(i,i)=f-g;
					for (j=l-1;j<n;j++) {
						for (s=0.0,k=i;k<m;k++) s += u(k,i)*u(k,j);
						f=s/h;
						for (k=i;k<m;k++) u(k,j) += f*u(k,i);
					}
					for (k=i;k<m;k++) u(k,i) *= scale;
				}
			}
			w[i]=scale *g;
			g=s=scale=0.0;
			if (i+1 <= m && i+1 != n) {
				for (k=l-1;k<n;k++) scale += std::abs(u(i,k));
				if (scale != 0.0) {
					for (k=l-1;k<n;k++) {
						u(i,k) /= scale;
						s += u(i,k)*u(i,k);
					}
					f=u(i,l-1);
					g = -sign(std::sqrt(s),f);
					h=f*g-s;
					u(i,l-1)=f-g;
					for (k=l-1;k<n;k++) rv1[k]=u(i,k)/h;
					for (j=l-1;j<m;j++) {
						for (s=0.0,k=l-1;k<n;k++) s += u(j,k)*u(i,k);
						for (k=l-1;k<n;k++) u(j,k) += s*rv1[k];
					}
					for (k=l-1;k<n;k++) u(i,k) *= scale;
				}
			}
			anorm=(std::max)(anorm,(std::abs(w[i])+std::abs(rv1[i])));
		}
		for (i=n-1;i>=0;i--) {
			if (i < n-1) {
				if (g != 0.0) {
					for (j=l;j<n;j++)
						v(j,i)=(u(i,j)/u(i,l))/g;
					for (j=l;j<n;j++) {
						for (s=0.0,k=l;k<n;k++) s += u(i,k)*v(k,j);
						for (k=l;k<n;k++) v(k,j) += s*v(k,i);
					}
				}
				for (j=l;j<n;j++) v(i,j)=v(j,i)=0.0;
			}
			v(i,i)=1.0;
			g=rv1[i];
			l=i;
		}
		for (i=(std::min)(m,n)-1;i>=0;i--) {
			l=i+1;
			g=w[i];
			for (j=l;j<n;j++) u(i,j)=0.0;
			if (g != 0.0) {
				g=(T)1.0/g;
				for (j=l;j<n;j++) {
					for (s=0.0,k=l;k<m;k++) s += u(k,i)*u(k,j);
					f=(s/u(i,i))*g;
					for (k=i;k<m;k++) u(k,j) += f*u(k,i);
				}
				for (j=i;j<m;j++) u(j,i) *= g;
			} else for (j=i;j<m;j++) u(j,i)=0.0;
			++u(i,i);
		}
		for (k=n-1;k>=0;k--) {
			for (its=0;its<maxiter;its++) {
				flag=true;
				for (l=k;l>=0;l--) {
					nm=l-1;
					if (l == 0 || std::abs(rv1[l]) <= eps*anorm) {
						flag=false;
						break;
					}
					if (std::abs(w[nm]) <= eps*anorm) break;
				}
				if (flag) {
					c=0.0;
					s=1.0;
					for (i=l;i<k+1;i++) {
						f=s*rv1[i];
						rv1[i]=c*rv1[i];
						if (std::abs(f) <= eps*anorm) break;
						g=w[i];
						h=pythag(f,g);
						w[i]=h;
						h=(T)1.0/h;
						c=g*h;
						s = -f*h;
						for (j=0;j<m;j++) {
							y=u(j,nm);
							z=u(j,i);
							u(j,nm)=y*c+z*s;
							u(j,i)=z*c-y*s;
						}
					}
				}
				z=w[k];
				if (l == k) {
					if (z < 0.0) {
						w[k] = -z;
						for (j=0;j<n;j++) v(j,k) = -v(j,k);
					}
					break;
				}
				if (its == maxiter-1)
					return false;
				x=w[l];
				nm=k-1;
				y=w[nm];
				g=rv1[nm];
				h=rv1[k];
				f=((y-z)*(y+z)+(g-h)*(g+h))/((T)2.0*h*y);
				g=pythag(f,(T)1.0);
				f=((x-z)*(x+z)+h*((y/(f+sign(g,f)))-h))/x;
				c=s=1.0;
				for (j=l;j<=nm;j++) {
					i=j+1;
					g=rv1[i];
					y=w[i];
					h=s*g;
					g=c*g;
					z=pythag(f,h);
					rv1[j]=z;
					c=f/z;
					s=h/z;
					f=x*c+g*s;
					g=g*c-x*s;
					h=y*s;
					y *= c;
					for (jj=0;jj<n;jj++) {
						x=v(jj,j);
						z=v(jj,i);
						v(jj,j)=x*c+z*s;
						v(jj,i)=z*c-x*s;
					}
					z=pythag(f,h);
					w[j]=z;
					if (z) {
						z=(T)1.0/z;
						c=f*z;
						s=h*z;
					}
					f=c*g+s*y;
					x=c*y-s*g;
					for (jj=0;jj<m;jj++) {
						y=u(jj,j);
						z=u(jj,i);
						u(jj,j)=y*c+z*s;
						u(jj,i)=z*c-y*s;
					}
				}
				rv1[l]=0.0;
				rv1[k]=f;
				w[k]=x;
			}
		}
		if(ordering)
		{
			int i,j,k,s,inc=1;
		T sw;
		vec<T> su(m), sv(n);
		do { inc *= 3; inc++; } while (inc <= n);
		do {
			inc /= 3;
			for (i=inc;i<n;i++) {
				sw = w[i];
				for (k=0;k<m;k++) su[k] = u(k,i);
				for (k=0;k<n;k++) sv[k] = v(k,i);
				j = i;
				while (w[j-inc] < sw) {
					w[j] = w[j-inc];
					for (k=0;k<m;k++) u(k,j) = u(k,j-inc);
					for (k=0;k<n;k++) v(k,j) = v(k,j-inc);
					j -= inc;
					if (j < inc) break;
				}
				w[j] = sw;
				for (k=0;k<m;k++) u(k,j) = su[k];
				for (k=0;k<n;k++) v(k,j) = sv[k];

			}
		} while (inc > 1);
		for (k=0;k<n;k++) {
			s=0;
			for (i=0;i<m;i++) if (u(i,k) < 0.) s++;
			for (j=0;j<n;j++) if (v(j,k) < 0.) s++;
			if (s > (m+n)/2) {
				for (i=0;i<m;i++) u(i,k) = -u(i,k);
				for (j=0;j<n;j++) v(j,k) = -v(j,k);
			}
		}
	}

	T tsh = (T)(0.5*sqrt(m+n+1.)*w[0]*eps);
	return true;
	


	}

///compute the null space of a matrix
template <typename T>
mat<T> null(const mat<T>& a)
{
	T eps =std::numeric_limits<T>::epsilon();
	mat<T> u,v;
	diag_mat<T> s;
	svd(a,u,s,v);
	
	unsigned r = 0;
	T tol;
	if(a.nrows() > a.ncols())
		tol = (T)a.nrows()*eps;
	else
		tol = (T)a.ncols()*eps;
	
	for(unsigned i = 0; i < s.ncols(); i++)
	{
		if(s(i) > tol)
			r++;
	}
	if (r < a.ncols())
	{
      
		mat<T> N = v.sub_mat(0, r,v.nrows(),a.ncols()-r);
		for(unsigned i = 0; i < N.nrows();i++)
			for(unsigned j = 0; j < N.ncols();j++)
				if(std::abs(N(i,j)) < eps) N(i,j)=(T)0;
		
		return N;
	}
    else
      return zeros<T> (a.ncols(), 0);

}

template <typename T>
mat<T> pseudo_inv(const mat<T>& a, T eps = std::numeric_limits<T>::epsilon())
	{
		mat<T> u,v;
		diag_mat<T> s;
		svd(a,u,s,v);
		for(unsigned i = 0; i < s.ncols(); i++)
		{
			if(s(i) > eps)
				s(i)=1/s(i);
			else
				s(i) = 0;
		}
		return v*s*transpose(u);
		
	}


///compute the effective null space of a matrix using user defined tolerance
template <typename T>
mat<T> null(const mat<T>& a, T tol)
{
	T eps =std::numeric_limits<T>::epsilon();
	mat<T> u,v;
	diag_mat<T> s;
	svd(a,u,s,v);
	unsigned r = 0;
	
	
	for(unsigned i = 0; i < s.ncols(); i++)
	{
		if(s(i) > tol)
			r++;
	}
	if (r < a.ncols())
	{
      
		mat<T> N = v.submat(0, r,v.nrows(),a.ncols()-r);
		for(unsigned i = 0; i < N.nrows();i++)
			for(unsigned j = 0; j < N.ncols();j++)
				if(std::abs(N(i,j)) < eps) N(i,j)=(T)0;
		
		return N;
	}
    else
      return zeros<T> (a.ncols(), 0);

}


///computes the rank of a matrix using svd
template<typename T>
unsigned rank(const mat<T>& a)
{
	mat<T> u,v;
	diag_mat<T> s;
	svd(a,u,s,v);
	unsigned r = 0;
	T tol;
	if(a.nrows() > a.ncols())
		tol = (T)a.nrows()*std::numeric_limits<T>::epsilon();
	else
		tol = (T)a.ncols()*std::numeric_limits<T>::epsilon();
	
	for(unsigned i = 0; i < s.ncols(); i++)
	{
		if(s(i) > tol)
			r++;
	}
	return r;
}

///computes the effective rank of a matrix using svd and the given tolerance tol
template<typename T>
unsigned rank(const mat<T>& a, T tol)
{
	mat<T> u,v;
	diag_mat<T> s;
	svd(a,u,s,v);
	unsigned rank = 0;
		
	for(unsigned i = 0; i < s.ncols(); i++)
	{
		if(s(i) > tol)
			rank++;
	}
	return rank;
}


///return the rank of m x n matrix A  with m < n
/// the solution of the system Ax=b is given as x= p + N *(l_1,...,l_{n-r})^T, r is the rank of A, are the free parameters and N is the nullspace 
template <typename T>
unsigned solve_underdetermined_system(const cgv::math::mat<T>& A, const cgv::math::vec<T>& b, 
									  cgv::math::vec<T>& p, cgv::math::mat<T>& N)
{
	T eps =std::numeric_limits<T>::epsilon();
	mat<T> u,v;
	diag_mat<T> s;
	svd(A,u,s,v);
	//compute rank
	unsigned r = 0;
	T tol;
	if(A.nrows() > A.ncols())
		tol = (T)A.nrows()*eps;
	else
		tol = (T)A.ncols()*eps;

	for(unsigned i = 0; i < s.ncols(); i++)
	{
		if(s(i) > tol)
			r++;
		else
			s(i)=0;
	}
	p=v*s*transpose(u)*b;
	if (r < A.ncols())
	{
      
		N = v.sub_mat(0, r,v.nrows(),A.ncols()-r);
		for(unsigned i = 0; i < N.nrows();i++)
			for(unsigned j = 0; j < N.ncols();j++)
				if(std::abs(N(i,j)) < eps) N(i,j)=(T)0;
		
		return r;
	}
	else
	{
		N = zeros<T> (A.ncols(), 0);
		return r;
	}
}








}

}
