extern crate sensei_math;
use sensei_math::math::Mat;
use std::f32;

fn dot(v1:&[f32], v2:&[f32]) -> f32 {
	assert!(v1.len() == v2.len());
	//TODO: Do some performance comparisons between this form and a
	// for loop with an accumulator.
	v1.iter()
	  .zip(v2.iter())
	  .map(|(&x,&y)| x*y)
	  .fold(0f32, |sum, x| sum + x) // sum() cannot infer the type...
}

pub fn batch(alpha:f32, features:&Mat<f32>, labels:&[f32]) -> Vec<f32> {
	assert!(features.row_count > 0, "Expected at least one sample.");
	let m = labels.len();
	let mut weights = vec! [0.0; features[0].len()];
	let feature_count = weights.len();
	let epsilon = 0.000_000_0001f32;
	let mut prev_error = f32::MAX; 
	let mut cumulative_error = 0.0f32;
	// Continue descending until we are no longer making progress
	loop {
		for j in 0..m {
			let ref x:Vec<f32> = features[j];
			let error = dot(&weights, &x) - labels[j];
			for i in 0..feature_count {
				weights[i] = weights[i] - alpha * error * x[i];
			}
			cumulative_error = cumulative_error + error*error;
		}
		if prev_error - cumulative_error < epsilon 

		prev_error = cumulative_error;			
		cumulative_error = 0.0f32;
	}
	weights
}

#[cfg(test)]
mod tests {
    use super::*;
    use sensei_math::math::Mat;
	#[test]
	fn it_works() {
	    let features = mat! [
	                    [1.0],
	                    [2.0],
	                    [3.0],
	                    [4.0]
	                    ];
	    let labels = vec![1.0,2.0,3.0,4.0];
	    let expected_gradient = 1.0;
	    let computed_gradient = batch(0.001f32, &features, &labels)[0];
	    assert!((expected_gradient - computed_gradient).abs() < 0.00001);
	}
}
{ break; }