/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis 
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA is free software; you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mia/internal/autotest.hh>
#include <mia/3d/cost/ngf.hh>


using namespace std;
using namespace ngf_3dimage_cost; 
using namespace mia; 

struct C3DFVectorfieldFixture {
	
	C3DFVectorfieldFixture(); 
	
	C3DBounds size;
	C3DFVectorfield field; 
}; 


BOOST_FIXTURE_TEST_CASE( test_ngf_evaluator_dot, C3DFVectorfieldFixture )
{
	
	C3DFVectorfield input(C3DBounds(3,3,3)); 
	
	FScalar scalar; 

	BOOST_CHECK_CLOSE( scalar.cost(field(0,0,0), field(1,1,1)), - 26.0f*26.0f, 0.01f); 
	
	double cost = 0.0; 
	FEvaluator::field_range_iterator ifield = field.begin_range(C3DBounds(1,1,1), C3DBounds(3,3,3)).with_boundary_flag(); 
	auto grad = scalar.grad(3, 9, ifield,  field(0,0,0), cost); 

	BOOST_CHECK_CLOSE( cost, - 26.0f* 26.0f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.x, 13.0f * 19.0f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.y, -13.0f * 15.0f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.z, -13.0f * 31.0f, 0.01f);
	
}

BOOST_FIXTURE_TEST_CASE( test_ngf_evaluator_cross, C3DFVectorfieldFixture )
{
	
	C3DFVectorfield input(C3DBounds(3,3,3)); 
	
	FCross costfunct; 

	BOOST_CHECK_CLOSE( costfunct.cost(field(0,0,0), field(1,1,1)),10.0f, 0.01f); 
	
	double cost = 0.0; 
	FEvaluator::field_range_iterator ifield = field.begin_range(C3DBounds(1,1,1), C3DBounds(3,3,3)).with_boundary_flag(); 
	auto grad = costfunct.grad(3, 9, ifield, field(0,0,0), cost); 

	BOOST_CHECK_CLOSE( cost, 10.0f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.x, 9.0f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.y, 1.0f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.z, -11.0f, 0.01f); 
	
}

BOOST_FIXTURE_TEST_CASE( test_ngf_evaluator_delta_scalar, C3DFVectorfieldFixture )
{
	
	C3DFVectorfield input(C3DBounds(3,3,3)); 
	
	FDeltaScalar costfunct; 

	BOOST_CHECK_CLOSE( costfunct.cost(field(1,1,1),field(0,0,0)), 2*5.3330572096f, 0.01f); 
	
	double cost = 0.0; 
	FEvaluator::field_range_iterator ifield = field.begin_range(C3DBounds(1,1,1), C3DBounds(3,3,3)).with_boundary_flag(); 
	cvdebug() << "ifield:boundary=" << ifield.get_boundary_flags() << "\n"; 
	auto grad = costfunct.grad(3, 9, ifield, field(0,0,0), cost); 

	BOOST_CHECK_CLOSE( cost, 2*5.3330572096f, 0.01f); 

	BOOST_CHECK_CLOSE( grad.x, -7.7363f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.y, 6.4125f, 0.01f); 
	BOOST_CHECK_CLOSE( grad.z, 12.761, 0.01f); 
	
}




C3DFVectorfieldFixture::C3DFVectorfieldFixture():
	size(3,3,3), 
	field(size)
{
	field(0,0,0) = C3DFVector(1,2,3); 
	field(1,0,0) = C3DFVector(2,3,5); 
	field(2,0,0) = C3DFVector(4,5,1); 

	field(0,1,0) = C3DFVector(2,5,5); 
	field(1,1,0) = C3DFVector(3,2,4); 
	field(2,1,0) = C3DFVector(5,1,2); 

	field(0,2,0) = C3DFVector(0,3,1); 
	field(1,2,0) = C3DFVector(1,2,2); 
	field(2,2,0) = C3DFVector(2,1,2); 

	field(0,0,1) = C3DFVector(2,1,2); 
	field(1,0,1) = C3DFVector(3,1,4); 
	field(2,0,1) = C3DFVector(2,4,2); 

	field(0,1,1) = C3DFVector(5,6,7); 
	field(1,1,1) = C3DFVector(2,3,6); 
	field(2,1,1) = C3DFVector(3,2,4); 

	field(0,2,1) = C3DFVector(1,2,3); 
	field(1,2,1) = C3DFVector(2,3,8); 
	field(2,2,1) = C3DFVector(8,5,3); 

	field(0,0,2) = C3DFVector(2,3,5); 
	field(1,0,2) = C3DFVector(4,5,6); 
	field(2,0,2) = C3DFVector(4,3,1); 

	field(0,1,2) = C3DFVector(1,3,2); 
	field(1,1,2) = C3DFVector(7,8,9); 
	field(2,1,2) = C3DFVector(3,5,7); 

	field(0,2,2) = C3DFVector(1,2,5); 
	field(1,2,2) = C3DFVector(3,4,5); 
	field(2,2,2) = C3DFVector(4,5,2); 

}


class FEvaluatorMock: public FEvaluator {
public:
	double cost (const C3DFVector& /*src*/, const C3DFVector& /*ref*/) const {
		return 1.0; 
	}
	
	C3DFVector grad(int /*nx*/, int /*nxy*/, field_range_iterator& irsrc,
			const C3DFVector& /*ref*/, double& cost) const {
		cost += 1; 
		C3DFVector result; 
		if (! (irsrc.get_boundary_flags() & field_range_iterator::eb_x))
			result.x = 1;
		
		if (! (irsrc.get_boundary_flags() & field_range_iterator::eb_y))
			result.y = 2;
		
		if (! (irsrc.get_boundary_flags() & field_range_iterator::eb_z))
			result.z = 3; 
		
		return result; 
	}
};




BOOST_AUTO_TEST_CASE( test_ngf_field ) 
{
	
	C3DNFGImageCost cost(PEvaluator(new FEvaluatorMock));

	C3DBounds size(3,4,5); 
	
	C3DFImage test(size); 
	C3DFImage ref(size); 
	C3DFVectorfield field(size);  
	fill(field.begin(), field.end(), C3DFVector::_0); 

	cost.set_reference(ref); 
	
	// the boundary should be ignored 
	BOOST_CHECK_EQUAL(cost.value(test), 30); 

	double value = cost.evaluate_force(test, field);
	BOOST_CHECK_EQUAL(value, 30);
	
	C3DFVector testv; 
	auto ifield = field.begin(); 
	for (unsigned z = 0; z < size.z; ++z) {
		testv.z = (z == 0 || z == size.z -1) ? 0 : 3; 
		for (unsigned y = 0; y < size.y; ++y) {
			testv.y = (y == 0 || y == size.y -1) ? 0 : 2; 
			for (unsigned x = 0; x < size.x; ++x, ++ifield) {
				testv.x = (x == 0 || x == size.x -1) ? 0 : 1;
				BOOST_CHECK_EQUAL(*ifield, testv); 
			}
		}
	}
	
}
