///-------------------------------------------------------------------------------------------------
/// 
/// \file Median.hpp
/// \brief All Median functions for array or matrix.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 29/07/2020.
/// \copyright Copyright (C) 2022 Inria
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as published
/// by the Free Software Foundation, either version 3 of the License, or
/// (at your option) any later version.
///
/// This program 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 Affero General Public License for more details.
///
/// You should have received a copy of the GNU Affero General Public License
/// along with this program.  If not, see <https://www.gnu.org/licenses/>.
/// \remarks This algortihms is inspired by the plugin clean_rawdata in <a href="https://sccn.ucsd.edu/eeglab/index.php">EEGLAB</a> (<a href="https://github.com/sccn/clean_rawdata/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------

#pragma once

#include <Eigen/Dense>
#include <vector>
#include "geometry/Metrics.hpp"

namespace Geometry {

//---------------------------------------------------------------------------
//------------------------------ Matrix Median ------------------------------
//---------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
/// <summary> Find the median of stl vector. </summary>
/// <typeparam name="T"> The type of the values (only arithmetic type). </typeparam>
/// <param name="v"> the vector of values. </param>
/// <returns> The median of vector. </returns>
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
T Median(const std::vector<T>& v)
{
	std::vector<T> tmp = v;
	const size_t n     = tmp.size() / 2;								// Where is the middle (if odd number of value the decimal part is floor by cast)
	std::stable_sort(tmp.begin(), tmp.end());							// We sort all because nth_element doesn't have same behaviour in Windows and Unix
	return (tmp.size() % 2 == 0) ? (tmp[n] + tmp[n - 1]) / 2 : tmp[n];	// For Even number of value we take the mean of the two middle value
}
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
/// <summary> Find the median of values of the Eigen Matrix. </summary>
/// <param name="m"> the matrix. </param>
/// <returns> The median of matrix. </returns>
double Median(const Eigen::MatrixXd& m);
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
/// <summary>	Compute the median of vector of matrix with the Weiszfeld's algorithm. <br/> 
/// To compute this median, we start by computing the initial median of the dataset by taking each element of the matrices independently.
/// That is to say that for the element at position i, j(a_i, j) of the matrices, we computes the median of the elements a_i, j of all the matrices of the dataset.
/// We thus have an initial median for our dataset. <br/> 
/// Then, we refine our median by the iterative algorithm of Weiszfeld:  
/// - We remove the median in our dataset.
/// - For each new matrices, we compute the norm.
/// - We sum the the matrices in initial dataset (divided by their own norm) and we normalize the result by the sum of inverse norms.
/// - We iterate this previous step until we have a difference between the old and new median is under an epsilon or that the number of iterations is above the limit.
/// </summary>
/// <param name="matrices">	Vector of Matrix. </param>
/// <param name="median">	The computed median. </param>
/// <param name="epsilon">	(Optional) The epsilon value to stop algorithm. </param>
/// <param name="maxIter">	(Optional) The maximum iteration allowed to find best Median. </param>
/// <returns>	<c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks>  it's an iterative algorithm, so we have a limit of iterations and an epsilon value to consider the calculation as satisfactory. </remarks>
bool MedianEuclidian(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon = 0.0001, const size_t maxIter = 50);
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
/// <summary>	Compute the median of vector of matrix with the Riemman Barycentre. <br/>
/// - Initialize the median with the euclidian mean of matrices.
/// - Iterate until the stop criterion (<c>iteration</c> over <c>maxIter</c> or \f$\text{gain}\f$ under <c>epsilon</c>).
///   - Compute the tangent space projection of each matrices with median as reference.
///   - Compute the sum (\f$\mathcal{S}\f$) of euclidian distance of each tangent space projection. <br/>
///   \f[ \delta_E=\sqrt{\sum_{i \in N}{x_i^2}} \quad \text{with } x_i \text{ the feature } i \text{ of the tangent space projection}\f]
///   - Compare with previous sum and stop if \f$\text{gain} < \varepsilon\f$. <br/>
///   \f[ \text{gain} = \left|\frac{\mathcal{S} - \mathcal{S}_\text{prev}}{\mathcal{S}_\text{prev}}\right| \f]
///   - Compute Median of each feature \f$i\f$ of tangent space projection.
///   - Transform this tangent space projection median to riemann space with previous median as reference and update the median by this new matrix.
/// </summary>
/// <param name="matrices">	Vector of Matrix. </param>
/// <param name="median">	The computed median. </param>
/// <param name="epsilon">	(Optional) The epsilon value to stop algorithm. </param>
/// <param name="maxIter">	(Optional) The maximum iteration allowed to find best Median. </param>
/// <returns>	<c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MedianRiemann(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon = 0.0001, const size_t maxIter = 50);
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
/// <summary> Give the identity matrix has median. </summary>
/// <param name="matrices">	Vector of Matrix. </param>
/// <param name="median">	The computed median. </param>
/// <returns>	<c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MedianIdentity(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median);
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
/// <summary>	Compute the median of vector of matrix with the Weiszfeld's algorithm for Euclidian Metric and Riemman Barycentre. </summary>
/// <param name="matrices">	Vector of Matrix. </param>
/// <param name="median">	The computed median. </param>
/// <param name="epsilon">	(Optional) The epsilon value to stop algorithm. </param>
/// <param name="maxIter">	(Optional) The maximum iteration allowed to find best Median. </param>
/// <param name="metric">	(Optional) THe metric to use. </param>
/// <returns>	<c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks>  it's an iterative algorithm, so we have a limit of iterations and an epsilon value to consider the calculation as satisfactory. </remarks>
bool Median(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median,
			const double epsilon = 0.0001, const size_t maxIter = 50, const EMetric& metric = EMetric::Euclidian);
//-------------------------------------------------------------------------------------------------

}  // namespace Geometry
