using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

#if ALGLIB
using alglib;
#endif

namespace miew.Matrix
{
	using Math = System.Math;
	using String = System.String;

	[DebuggerDisplay("{_DebugString()}")]
	public class Matrix
	{
		private double[,] _values;
		private int c_rows;
		private int c_cols;

		private String _DebugString() { return String.Format("{0} rows by {1} columns", c_rows, c_cols); }

		public Matrix(int rowCount, int columnCount)
		{
			c_rows = rowCount;
			c_cols = columnCount;
			_values = new double[c_rows, c_cols];
		}

		public Matrix(double[] v)
			: this(v.Length, v.Length)
		{
			for (int i = 0; i < v.Length; i++)
				_values[i, i] = v[i];
		}

		public Matrix(double[] v, int c_take)
			: this(c_take, c_take)
		{
			c_take = Math.Min(c_take, v.Length);
			for (int i = 0; i < c_take; i++)
				_values[i, i] = v[i];
		}

		public Matrix(double[,] m)
			: this(m.GetLength(0), m.GetLength(1))
		{
			for (int i = 0; i < c_rows; i++)
				for (int j = 0; j < c_cols; j++)
					_values[i, j] = m[i, j];
		}

		public double this[int row, int column]
		{
			get { return _values[row, column]; }
			set { _values[row, column] = value; }
		}

		public int RowCount { get { return c_rows; } }
		public int ColumnCount { get { return c_cols; } }

		public double[] Row(int i)
		{
			double[] v = new double[ColumnCount];
			for (int j = 0; j < ColumnCount; j++)
				v[j] = _values[i, j];
			return v;
		}

		public double[] Column(int j)
		{
			double[] v = new double[RowCount];
			for (int i = 0; i < RowCount; i++)
				v[i] = _values[i, j];
			return v;
		}

		public IEnumerable<double[]> Rows
		{
			get
			{
				//int x = _values.Cast<Double>().Count();	// can access all in [,] this way
				for (int i = 0; i < RowCount; i++)
					yield return Row(i);
			}
		}

		public IEnumerable<double[]> Columns
		{
			get
			{
				for (int i = 0; i < ColumnCount; i++)
					yield return Column(i);
			}
		}

		public void CleaveVertical(int left_cols, out Matrix left, out Matrix right)
		{
			if (left_cols == ColumnCount)
			{
				left = new Matrix(this);
				right = new Matrix(RowCount, 0);
			}
			else if (left_cols == 0)
			{
				left = new Matrix(RowCount, 0);
				right = new Matrix(this);
			}
			else if (left_cols > ColumnCount)
				throw new Exception();
			else
			{
				left = new double[RowCount, left_cols];
				right = new double[RowCount, ColumnCount - left_cols];
				for (int i = 0; i < RowCount; i++)
					for (int j = 0; j < ColumnCount; j++)
						if (j < left_cols)
							left[i, j] = _values[i, j];
						else
							right[i, j - left_cols] = _values[i, j];
			}
		}

		public void CleaveHorizontal(int top_rows, out Matrix top, out Matrix bottom)
		{
			if (top_rows == RowCount)
			{
				top = new Matrix(this);
				bottom = new Matrix(0, ColumnCount);
			}
			else if (top_rows == 0)
			{
				top = new Matrix(0, ColumnCount);
				bottom = new Matrix(this);
			}
			else if (top_rows > RowCount)
				throw new Exception();
			else
			{
				top = new double[top_rows, ColumnCount];
				bottom = new double[RowCount - top_rows, ColumnCount];
				for (int i = 0; i < RowCount; i++)
					for (int j = 0; j < ColumnCount; j++)
						if (i < top_rows)
							top[i, j] = _values[i, j];
						else
							bottom[i - top_rows, j] = _values[i, j];
			}
		}

		public void SetColumnValues(int j, double[] v)
		{
			if (v.Length != RowCount)
				throw new Exception();
			for (int i = 0; i < RowCount; i++)
				_values[i, j] = v[i];
		}

		public static Matrix Identity(int size)
		{
			Matrix m = new Matrix(size, size);
			for (int i = 0; i < size; i++)
				for (int j = 0; j < size; j++)
					m[i, j] = (i == j) ? 1.0 : 0.0;
			return m;
		}

		public Matrix Transpose()
		{
			Matrix m = new Matrix(c_cols, c_rows);
			for (int i = 0; i < c_rows; i++)
				for (int j = 0; j < c_cols; j++)
					m[j, i] = this[i, j];
			return m;
		}

		public static Matrix Add(Matrix m_l, Matrix m_r)
		{
			Debug.Assert(m_l.ColumnCount == m_r.ColumnCount);
			Debug.Assert(m_l.RowCount == m_r.RowCount);

			Matrix m = new Matrix(m_l.RowCount, m_r.ColumnCount);
			for (int i = 0; i < m_l.RowCount; i++)
				for (int j = 0; j < m_l.ColumnCount; j++)
					m[i, j] = m_l[i, j] + m_r[i, j];
			return m;
		}

		public static Matrix Subtract(Matrix m_l, Matrix m_r)
		{
			Debug.Assert(m_l.ColumnCount == m_r.ColumnCount);
			Debug.Assert(m_l.RowCount == m_r.RowCount);
			Matrix m = new Matrix(m_l.RowCount, m_r.ColumnCount);
			for (int i = 0; i < m_l.RowCount; i++)
				for (int j = 0; j < m_l.ColumnCount; j++)
					m[i, j] = m_l[i, j] - m_r[i, j];
			return m;
		}

		public static Matrix Multiply(Matrix m_l, Matrix m_r)
		{
			if (m_l.ColumnCount != m_r.RowCount)
				throw new Exception();
			Matrix m = new Matrix(m_l.RowCount, m_r.ColumnCount);
			for (int i = 0; i < m.ColumnCount; i++)
			{
				for (int j = 0; j < m_l.RowCount; j++)
				{
					double value = 0.0;
					for (int k = 0; k < m_r.RowCount; k++)
						value += m_l[j, k] * m_r[k, i];
					m[j, i] = value;
				}
			}
			return m;
		}

		public static Matrix Multiply(double left, Matrix m_r)
		{
			Matrix m = new Matrix(m_r.RowCount, m_r.ColumnCount);
			for (int i = 0; i < m.RowCount; i++)
				for (int j = 0; j < m_r.ColumnCount; j++)
					m[i, j] = left * m_r[i, j];
			return m;
		}

		public static Matrix Multiply(Matrix m_l, double right)
		{
			Matrix m = new Matrix(m_l.RowCount, m_l.ColumnCount);
			for (int i = 0; i < m_l.RowCount; i++)
				for (int j = 0; j < m_l.ColumnCount; j++)
					m[i, j] = m_l[i, j] * right;
			return m;
		}

		public static Matrix Divide(Matrix m_l, double right)
		{
			Matrix m = new Matrix(m_l.RowCount, m_l.ColumnCount);
			for (int i = 0; i < m_l.RowCount; i++)
				for (int j = 0; j < m_l.ColumnCount; j++)
					m[i, j] = m_l[i, j] / right;
			return m;
		}

		public static double[] DiagonalVector(Matrix m)
		{
			Debug.Assert(m.RowCount == m.ColumnCount);

			double[] v = new double[m.RowCount];
			for (int i = 0; i < m.RowCount; i++)
				v[i] = m[i, i];
			return v;
		}

		public static double[,] ToDoubleArray(Matrix m)
		{
			double[,] result = new double[m.RowCount, m.ColumnCount];
			for (int i = 0; i < m.RowCount; i++)
				for (int j = 0; j < m.ColumnCount; j++)
					result[i, j] = m[i, j];
			return result;
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < RowCount; i++)
			{
				for (int j = 0; j < ColumnCount; j++)
					sb.Append(_values[i, j].ToString("N4").PadLeft(8) + " ");
				sb.AppendLine();
			}
			return sb.ToString();
		}

		public Matrix Trim(int r0, int c0)
		{
			if (RowCount <= r0 && ColumnCount <= c0)
				return new Matrix(this);
			double[,] _new = new double[r0, c0];
			for (int i = 0; i < r0; i++)
				for (int j = 0; j < c0; j++)
					_new[i, j] = _values[i, j];
			return new Matrix(_new);
		}

		public Matrix TakeKRows(int k) { return Trim(k, ColumnCount); }
		public Matrix TakeKColumns(int k) { return Trim(RowCount, k); }
		public Matrix TakeKRowsAndColumns(int k) { return Trim(k, k); }

		public void NormalizeColumns()
		{
			double[] lengths = new double[ColumnCount];
			for (int i = 0; i < RowCount; i++)
				for (int j = 0; j < ColumnCount; j++)
					lengths[j] += Math.Pow(_values[i, j], 2);
			for (int j = 0; j < ColumnCount; j++)
				lengths[j] = Math.Sqrt(lengths[j]);

			for (int i = 0; i < RowCount; i++)
				for (int j = 0; j < ColumnCount; j++)
					_values[i, j] /= lengths[j];
		}

		public static Matrix operator +(Matrix m_l, Matrix m_r) { return Matrix.Add(m_l, m_r); }
		public static Matrix operator -(Matrix m_l, Matrix m_r) { return Matrix.Subtract(m_l, m_r); }
		public static Matrix operator *(Matrix m_l, Matrix m_r) { return Matrix.Multiply(m_l, m_r); }
		public static Matrix operator *(double left, Matrix m_r) { return Matrix.Multiply(left, m_r); }
		public static Matrix operator *(Matrix m_l, double right) { return Matrix.Multiply(m_l, right); }
		public static Matrix operator /(Matrix m_l, double right) { return Matrix.Divide(m_l, right); }
		public static implicit operator Matrix(double[,] m) { return new Matrix(m); }
		public static implicit operator double[,](Matrix m) { return ToDoubleArray(m); }

#if ALGLIB
		/// <summary>
		/// Make alglib's SVD nicer by using the above type
		/// </summary>
		public static bool SingularValueDecomposition(Matrix A, out double[] w, out Matrix V)
		{
			w = null;
			V = null;
			double[,] u = null;
			double[,] v = null;
			if (!svd.rmatrixsvd(A, A.RowCount, A.ColumnCount, 0, 1, 2, ref w, ref u, ref v))
				return false;
			V = new Matrix(v);
			return true;
		}
#endif
	};
}