/* -*- 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/2d/multicost.hh>
#include <mia/2d/cost.hh>
#include <mia/2d/fullcost.hh>
#include <mia/2d/transformfactory.hh>
#include <mia/2d/transformmock.hh>
#include <mia/2d/imageio.hh>

#include <mia/internal/autotest.hh>

NS_MIA_USE
namespace bfs=::boost::filesystem; 

class C2DFullCostMock: public C2DFullCost {
public: 
	C2DFullCostMock(double weight, double cost, double gx, double gy); 
private:
	double do_evaluate(const C2DTransformation& t, CDoubleVector& gradient) const;
	double do_value(const C2DTransformation& t) const;
	double do_value() const; 
	void do_set_size(); 
	void do_reinit(); 
	double m_cost; 
	double m_gx; 
	double m_gy; 
}; 

C2DFullCostMock::C2DFullCostMock(double weight, double cost, double gx, double gy):
	C2DFullCost(weight),
	m_cost(cost), 
	m_gx(gx), 
	m_gy(gy)

{
}

double C2DFullCostMock::do_evaluate(const C2DTransformation&, CDoubleVector& gradient) const 
{
	for(auto g = gradient.begin(); g != gradient.end(); g += 2) {
		g[0] = m_gx; 
		g[1] = m_gy; 
	}
	return m_cost; 
}

double C2DFullCostMock::do_value(const C2DTransformation& /*t*/) const
{
	return m_cost; 
}

double C2DFullCostMock::do_value() const
{
	return m_cost; 
}
void C2DFullCostMock::do_reinit()
{
}

void C2DFullCostMock::do_set_size()
{
}

BOOST_AUTO_TEST_CASE( test_fullcost ) 
{
	const double mcost = 10.0; 
	const double mgx   = 2.0; 
	const double mgy   = 3.0; 

	C2DFullCostMock c(0.5, mcost, mgx, mgy); 
	C2DTransformMock t(C2DBounds(2,1)); 
	CDoubleVector gradient(t.degrees_of_freedom()); 
	c.set_size(t.get_size()); 
	
	BOOST_CHECK_CLOSE(c.evaluate(t,gradient), 0.5 * mcost, 0.01);

	BOOST_CHECK_CLOSE(gradient[0], 0.5 * mgx, 0.01);
	BOOST_CHECK_CLOSE(gradient[1], 0.5 * mgy, 0.01);
}

BOOST_AUTO_TEST_CASE( test_multicost ) 
{
	const double mcost1 = 10.0; 
	const double mgx1   = 2.0; 
	const double mgy1   = 3.0; 

	const double mcost2 = 2.0; 
	const double mgx2   = 4.0; 
	const double mgy2   = 9.0; 

	P2DFullCost c1(new C2DFullCostMock(0.5, mcost1, mgx1, mgy1)); 
	P2DFullCost c2(new C2DFullCostMock(0.2, mcost2, mgx2, mgy2)); 
	
	C2DFullCostList costs; 
	costs.push(c1); 
	costs.push(c2); 
	
	C2DTransformMock t(C2DBounds(2,1)); 
	CDoubleVector gradient(t.degrees_of_freedom(), true); 
	costs.set_size(t.get_size()); 
	
	BOOST_CHECK_CLOSE(costs.evaluate(t,gradient), 0.5 * mcost1 + 0.2 * mcost2, 0.1);

	BOOST_CHECK_CLOSE(gradient[0], 0.5 * mgx1 + 0.2 * mgx2, 0.1);
	BOOST_CHECK_CLOSE(gradient[1], 0.5 * mgy1 + 0.2 * mgy2, 0.1);
}

BOOST_AUTO_TEST_CASE( test_multicost2 ) 
{
	const double mcost1 = 10.0; 
	const double mgx1   = 2.0; 
	const double mgy1   = 3.0; 

	const double mcost2 = 2.0; 
	const double mgx2   = 4.0; 
	const double mgy2   = 9.0; 

	P2DFullCost c1(new C2DFullCostMock(0.5, mcost1, mgx1, mgy1)); 
	P2DFullCost c2(new C2DFullCostMock(0.2, mcost2, mgx2, mgy2)); 
	
	C2DFullCostList costs; 
	costs.push(c2); 
	costs.push(c1); 

	
	C2DTransformMock t(C2DBounds(2,1)); 
	CDoubleVector gradient(t.degrees_of_freedom()); 
	costs.set_size(t.get_size()); 
	
	BOOST_CHECK_CLOSE(costs.evaluate(t,gradient), 0.5 * mcost1 + 0.2 * mcost2, 0.1);

	BOOST_CHECK_CLOSE(gradient[0], 0.5 * mgx1 + 0.2 * mgx2, 0.1);
	BOOST_CHECK_CLOSE(gradient[1], 0.5 * mgy1 + 0.2 * mgy2, 0.1);
}


BOOST_AUTO_TEST_CASE( test_multicost_property ) 
{
	const double mcost1 = 10.0; 
	const double mgx1   = 2.0; 
	const double mgy1   = 3.0; 

	const double mcost2 = 2.0; 
	const double mgx2   = 4.0; 
	const double mgy2   = 9.0; 

	P2DFullCost c1(new C2DFullCostMock(0.5, mcost1, mgx1, mgy1)); 
	P2DFullCost c2(new C2DFullCostMock(0.2, mcost2, mgx2, mgy2)); 

	const char *test_prop = "test_prop"; 
	
	C2DFullCostList costs; 
	BOOST_CHECK(!costs.has(test_prop)); 
	
	costs.push(c1); 
	costs.push(c2); 
	
	BOOST_CHECK(!costs.has(test_prop)); 
	c1->add(test_prop); 
	BOOST_CHECK(!costs.has(test_prop)); 
	c2->add(test_prop); 
	BOOST_CHECK(costs.has(test_prop)); 
}

class PrepareFullcostTests {
public: 
	static const PrepareFullcostTests& instance(); 
	
	const C2DFullCostPluginHandler::Instance& fullcost_handler()const; 
	const C2DTransformCreatorHandler::Instance& transform_handler()const; 
private:
	PrepareFullcostTests(); 
}; 


BOOST_AUTO_TEST_CASE( test_load_plugins ) 
{	
	const C2DFullCostPluginHandler::Instance& handler = PrepareFullcostTests::instance().fullcost_handler(); 
	BOOST_CHECK_EQUAL(handler.size(), 3u); 
	BOOST_CHECK_EQUAL(handler.get_plugin_names(), "image labelimage maskedimage ");
}

#if 0 
BOOST_AUTO_TEST_CASE ( test_ssd_cost_spline_rate_3 ) 
{
	auto cost_ssd = PrepareFullcostTests::instance().fullcost_handler().produce("image:cost=ssd"); 
	auto transform_factory = PrepareFullcostTests::instance().transform_handler().produce("spline:rate=3"); 

	C2DBounds size(16,16); 
	const float src_init[ 16 * 16] = {
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,20,20,20,10,10,10,10,10,20,10,10,10, 0, 
		0,10,10,20,30,30,30,30,30,30,30,30,20,10, 0, 
		0, 0,20,20,20,20,20,20,20,20,20,20,20,10, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,20,20,20,10,10,10,10,10,20,10,10,10, 0, 
		0,10,10,20,30,30,30,30,30,30,30,30,20,10, 0, 
		0, 0,20,20,20,20,20,20,20,20,20,20,20,10, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,20,20,20,10,10,10,10,10,20,10,10,10, 0, 
		0,10,10,20,30,30,30,30,30,30,30,30,20,10, 0, 
		0, 0,20,20,20,20,20,20,20,20,20,20,20,10, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
		
	}; 

	const float ref_init[ 16 * 16] = {
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,30,30,40,20,20,30,30,40,40,20,30,10, 0, 
		0,10,20,10,20,20,20,30,40,50,40,40,30,10, 0, 
		0, 0,20,20,20,20,20,20,20,20,20,20,20,10, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,20,20,20,10,10,10,10,10,20,10,10,10, 0, 
		0,10,30,30,20,20,32,20,30,40,50,30,20,10, 0, 
		0, 0,30,30,30,30,30,30,20,20,50,20,20,10, 0, 
		0,10,30.10,30,30,30,30,30,20,20,20,10,10, 0, 
		0,10,30,10,30,30,40,43,20,20,30,30,10,10, 0, 
		0,10,10,20,30,30,20,20,20,20,20,30,20,10, 0, 
		0, 0,20,20,20,30,30,20,20,20,20,20,20,10, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
		
	}; 

	P2DImage src(new C2DFImage(size, src_init)); 
	save_image2d("src.@", src); 

	P2DImage ref(new C2DFImage(size, ref_init)); 
	save_image2d("ref.@", ref); 

	cost_ssd->reinit(); 
	cost_ssd->set_size(size); 
	
	P2DTransformation t = transform_factory->create(size); 

	auto params = t->get_parameters(); 
	CDoubleVector gradient(params.size());  

	double cost = cost_ssd->evaluate(*t, gradient);
//	BOOST_CHECK_CLOSE(cost, 55*55 / 1024.0, 0.1); 
	
	for (size_t i = 0; i < params.size(); ++i) {
		params[i] -= 0.0001; 
		t->set_parameters(params); 
		double costm = cost_ssd->cost_value(*t);
		params[i] += 0.0002; 
		t->set_parameters(params); 
		double costp = cost_ssd->cost_value(*t);
		params[i] -= 0.0001;
		double cgrad = (costp - costm) / 0.0002; 

		cvinfo()  << gradient[i] << " vs " << cgrad << " " 
			  << gradient[i]/ cgrad<< "\n"; 

		if (fabs(cgrad) > 0.001) 
			BOOST_CHECK_CLOSE(gradient[i], cgrad, 0.1); 
		else 
			BOOST_CHECK_CLOSE(1.0 + gradient[i], 1.0, 0.1); 
	}

}


BOOST_AUTO_TEST_CASE ( test_ssd_cost_vf ) 
{
	auto cost_ssd = PrepareFullcostTests::instance().fullcost_handler().produce("image:cost=ssd"); 
	auto transform_factory = PrepareFullcostTests::instance().transform_handler().produce("vf"); 

	C2DBounds size(16,16); 
	const float src_init[ 16 * 16] = {
		0,10,20,30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,20,30,40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,30,40,50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,40,50,60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,50,60,70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,60,70,80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,70,80,90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,80,90,00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,70,80,90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,60,70,80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,50,60,70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,40,50,60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,30,40,50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,20,30,40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,10,20,30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0,10,20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		
	}; 

	const float ref_init[ 16 * 16] = {
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,30,30,40,20,20,30,30,40,40,20,30,10, 0, 
		0,10,20,10,20,20,20,30,40,50,40,40,30,10, 0, 
		0, 0,20,20,20,20,20,20,20,20,20,20,20,10, 0, 
		0,10,10,10,10,10,10,20,20,20,20,20,10,10, 0, 
		0,10,20,20,20,10,10,10,10,10,20,10,10,10, 0, 
		0,10,30,30,20,20,32,20,30,40,50,30,20,10, 0, 
		0, 0,30,30,30,30,30,30,20,20,50,20,20,10, 0, 
		0,10,30.10,30,30,30,30,30,20,20,20,10,10, 0, 
		0,10,30,10,30,30,40,43,20,20,30,30,10,10, 0, 
		0,10,10,20,30,30,20,20,20,20,20,30,20,10, 0, 
		0, 0,20,20,20,30,30,20,20,20,20,20,20,10, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
		
	}; 

	P2DImage src(new C2DFImage(size, src_init)); 
	save_image2d("src.@", src); 

	P2DImage ref(new C2DFImage(size, ref_init)); 
	save_image2d("ref.@", ref); 

	cost_ssd->reinit(); 
	cost_ssd->set_size(size); 
	
	P2DTransformation t = transform_factory->create(size); 

	auto params = t->get_parameters(); 
	CDoubleVector gradient(params.size());  

	double cost = cost_ssd->evaluate(*t, gradient);
//	BOOST_CHECK_CLOSE(cost, 55*55 / 1024.0, 0.1); 
	
	for (size_t i = 0; i < params.size(); ++i) {
		params[i] -= 0.0001; 
		t->set_parameters(params); 
		double costm = cost_ssd->cost_value(*t);
		params[i] += 0.0002; 
		t->set_parameters(params); 
		double costp = cost_ssd->cost_value(*t);
		params[i] -= 0.0001;
		double cgrad = (costp - costm) / 0.0002; 

		cvinfo()  << gradient[i] << " vs " << cgrad << " " 
			  << gradient[i]/ cgrad<< "\n"; 

		if (fabs(cgrad) > 0.01) 
			BOOST_CHECK_CLOSE(gradient[i], cgrad, 0.1); 
		else 
			BOOST_CHECK_CLOSE(1.0 + gradient[i], 1.0, 0.1); 
	}

}
#endif

const PrepareFullcostTests& PrepareFullcostTests::instance()
{
	const static PrepareFullcostTests  me; 
	return me; 
}

const C2DFullCostPluginHandler::Instance& PrepareFullcostTests::fullcost_handler() const
{
	return C2DFullCostPluginHandler::instance(); 
}

const C2DTransformCreatorHandler::Instance& PrepareFullcostTests::transform_handler()const
{
	return C2DTransformCreatorHandler::instance(); 
}

PrepareFullcostTests::PrepareFullcostTests()
{
}

