/* -*- 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 <climits>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>

#include <mia/3d/image.hh>
#include <mia/core/filter.hh>
#include <mia/core/msgstream.hh>
#include <mia/core/boundary_conditions.hh>



NS_MIA_USE
using namespace std;

class CCopyFilter: public TFilter<std::shared_ptr<C3DImage>  > {
public:

	template <typename T>
	result_type operator () (const T3DImage<T>& image) const  {
		return result_type(new T3DImage<T>(image));
	}
};


template <typename T>
static void creat_and_check(EPixelType type)
{
	const T init_data[8] = {0,  1,  0,  1, 0,  1,  0,  1 };

	T3DImage<T> *timg = new T3DImage<T>(C3DBounds(2,2, 2), init_data);
	std::shared_ptr<C3DImage > image(timg);
	BOOST_CHECK(image->get_pixel_type() == type);

	CCopyFilter copy_filter;
	std::shared_ptr<C3DImage > result = filter(copy_filter, *image);

	BOOST_CHECK(result->get_pixel_type() == type);

	C3DFVector voxel(1.2f,2.3f,4.5f);
	image->set_voxel_size(voxel);
	BOOST_CHECK(voxel == image->get_voxel_size());

	image->set_orientation(ior_saggital);
	BOOST_CHECK_EQUAL(image->get_orientation(), ior_saggital);

	image->set_orientation(ior_axial);
	BOOST_CHECK_EQUAL(image->get_orientation(), ior_axial);


	T3DImage<T> image2(*timg);
	BOOST_CHECK(image2.get_size()  == C3DBounds(2,2, 2));

	BOOST_CHECK_EQUAL(image2.get_orientation(), ior_axial);

	BOOST_CHECK(voxel == image2.get_voxel_size());
	C3DFVector voxel2(3.2f,4.3f,1.5f);
	image2.set_voxel_size(voxel2);

	*timg = image2;
	BOOST_CHECK(voxel2 == timg->get_voxel_size());
}

typedef boost::mpl::vector<bool,
		     int8_t,
		     uint8_t,
		     int16_t,
		     uint16_t,
		     int32_t,
		     uint32_t,
		     int64_t,
		     uint64_t,
		     float,
		     double
		     > test_types;

BOOST_AUTO_TEST_CASE_TEMPLATE( check_data_types , T, test_types)
{
	creat_and_check<T>(pixel_type<T>::value);
}



BOOST_AUTO_TEST_CASE(check_gradient )
{
	const float init_data[64] = {
		0, 1, 2, 3, /**/ 4, 6, 7, 9, /**/ 4, 3, 2, 1, /**/ 0, 7, 8, 9,
		2, 3, 2, 2, /**/ 3, 3, 1, 2, /**/ 1, 8, 6, 5, /**/ 2, 3, 2, 6,
		4, 3, 2, 1, /**/ 2, 3, 7, 4, /**/ 3, 4, 6, 8, /**/ 3, 4, 5, 2,
		2, 5, 6, 3, /**/ 4, 1, 2, 3, /**/ 6, 4, 4, 1, /**/ 1, 5, 3, 3,
	};

	const C3DFVector vf[64] = {

		C3DFVector(0.0f, 0.0f, 0.0f), C3DFVector( 1.0f, 0.0f, 0.0f), C3DFVector( 1.0f, 0.0f, 0.0f), C3DFVector(0.0f, 0.0f, 0.0f),
		C3DFVector(0.0f, 2.0f, 0.0f), C3DFVector( 1.5f, 1.0f, 0.0f), C3DFVector( 1.5f, 0.0f, 0.0f), C3DFVector(0.0f,-1.0f, 0.0f),
		C3DFVector(0.0f,-2.0f, 0.0f), C3DFVector(-1.0f, 0.5f, 0.0f), C3DFVector(-1.0f, 0.5f, 0.0f), C3DFVector(0.0f, 0.0f, 0.0f),
		C3DFVector(0.0f, 0.0f, 0.0f), C3DFVector( 4.0f, 0.0f, 0.0f), C3DFVector( 1.0f, 0.0f, 0.0f), C3DFVector(0.0f, 0.0f, 0.0f),

		C3DFVector(0.0f, 0.0f, 2.0f), C3DFVector( 0.0f, 0.0f, 1.0f), C3DFVector(-0.5f, 0.0f, 0.0f), C3DFVector(0.0f, 0.0f,-1.0f),
		C3DFVector(0.0f,-0.5f,-1.0f), C3DFVector(-1.0f, 2.5f,-1.5f), C3DFVector(-0.5f, 2.0f, 0.0f), C3DFVector(0.0f, 1.5f,-2.5f),
		C3DFVector(0.0f,-0.5f,-0.5f), C3DFVector( 2.5f, 0.0f, 0.5f), C3DFVector(-1.5f, 0.5f, 2.0f), C3DFVector(0.0f, 2.0f, 3.5f),
		C3DFVector(0.0f, 0.0f, 1.5f), C3DFVector( 0.0f, 0.0f,-1.5f), C3DFVector( 1.5f, 0.0f,-1.5f), C3DFVector(0.0f, 0.0f,-3.5f),
		
		C3DFVector(0.0f, 0.0f, 0.0f), C3DFVector(-1.0f, 0.0f, 1.0f), C3DFVector(-1.0f, 0.0f, 2.0f), C3DFVector(0.0f, 0.0f, 0.5f),
		C3DFVector(0.0f,-0.5f, 0.5f), C3DFVector( 2.5f, 0.5f,-1.0f), C3DFVector( 0.5f, 2.0f, 0.5f), C3DFVector(0.0f, 3.5f, 0.5f),
		C3DFVector(0.0f, 0.5f, 2.5f), C3DFVector( 1.5f, 0.5f,-2.0f), C3DFVector( 2.0f,-1.0f,-1.0f), C3DFVector(0.0f,-1.0f,-2.0f),
		C3DFVector(0.0f, 0.0f,-0.5f), C3DFVector( 1.0f, 0.0f, 1.0f), C3DFVector(-1.0f, 0.0f, 0.5f), C3DFVector(0.0f, 0.0f,-1.5f),

		C3DFVector(0.0f, 0.0f, 0.0f), C3DFVector( 2.0f, 0.0f, 0.0f), C3DFVector(-1.0f, 0.0f, 0.0f), C3DFVector(0.0f, 0.0f, 0.0f),
		C3DFVector(0.0f, 2.0f, 0.0f), C3DFVector(-1.0f,-0.5f, 0.0f), C3DFVector( 1.0f,-1.0f, 0.0f), C3DFVector(0.0f,-1.0f, 0.0f),
		C3DFVector(0.0f,-1.5f, 0.0f), C3DFVector(-1.0f, 2.0f, 0.0f), C3DFVector(-1.5f, 0.5f, 0.0f), C3DFVector(0.0f, 0.0f, 0.0f),
		C3DFVector(0.0f, 0.0f, 0.0f), C3DFVector( 1.0f, 0.0f, 0.0f), C3DFVector(-1.0f, 0.0f, 0.0f), C3DFVector(0.0f, 0.0f, 0.0f),

	};

	C3DBounds size(4,4,4);
	C3DFImage image(size, init_data);

	C3DFVectorfield gradient = get_gradient(image);

	auto ig = gradient.begin_range(C3DBounds::_0, image.get_size()); 
	auto eg = gradient.end_range(C3DBounds::_0, image.get_size()); 
	const C3DFVector * v = vf; 
	while (ig != eg) {
		BOOST_CHECK_EQUAL(*ig, *v);
		cvdebug() << ig.pos() << ": got " << *ig << " expect:" << *v << "\n"; 
		++ig; 
		++v; 
	}
	
	C3DFVector g1= image.data().get_gradient<float>(C3DFVector(1.5, 1.5, 1.5));
	float gx1 =
		((vf[21].x * 0.5 + vf[22].x * 0.5) * 0.5 +
		 (vf[25].x * 0.5 + vf[26].x * 0.5) * 0.5) * 0.5 +
		((vf[37].x * 0.5 + vf[38].x * 0.5) * 0.5 +
		 (vf[41].x * 0.5 + vf[42].x * 0.5) * 0.5) * 0.5;
	cvdebug() << gx1  << " vs. " <<  g1 << "\n";

	BOOST_CHECK_CLOSE(gx1, g1.x, 0.0001);

	C3DFVector g = image.data().get_gradient<float>(C3DFVector(1.2, 1.7, 1.4));

	float gx =
		((vf[21].x * 0.8f + vf[22].x * 0.2f) * 0.3f +
		 (vf[25].x * 0.8f + vf[26].x * 0.2f) * 0.7f) * 0.6f +
		((vf[37].x * 0.8f + vf[38].x * 0.2f) * 0.3f +
		 (vf[41].x * 0.8f + vf[42].x * 0.2f) * 0.7f) * 0.4f;
	cvdebug() << gx  << " vs. " <<  g << "\n";
	BOOST_CHECK_CLOSE(gx, g.x, 0.0001);

}

BOOST_AUTO_TEST_CASE( test_rotation_attribute )
{
	C3DRotationAttributeTranslate::register_for("rotation3d");
	string value("rot-quaternion=0.5,0.1,0.5,0.7");

	PAttribute attr = CStringAttrTranslatorMap::instance().to_attr("rotation3d", value);
	
	const C3DRotationAttribute& ra = dynamic_cast<C3DRotationAttribute&>(*attr);
	C3DRotation r = ra; 
	auto q = r.as_quaternion(); 

	BOOST_CHECK_EQUAL(q.w(), 0.5);
	BOOST_CHECK_EQUAL(q.x(), 0.1);
	BOOST_CHECK_EQUAL(q.y(), 0.5);
	BOOST_CHECK_EQUAL(q.z(), 0.7);
}

BOOST_AUTO_TEST_CASE( test_voxel_attribute )
{
	string value("1 2 3");

	CVoxelAttributeTranslator::register_for("voxel");
	PAttribute attr = CStringAttrTranslatorMap::instance().to_attr("voxel", value);

	CVoxelAttribute *va = dynamic_cast<CVoxelAttribute*>(attr.get());
	BOOST_REQUIRE(va);

	C3DFVector vv = *va;
	BOOST_CHECK_EQUAL(vv.x, 1.0);
	BOOST_CHECK_EQUAL(vv.y, 2.0);
	BOOST_CHECK_EQUAL(vv.z, 3.0);

	BOOST_CHECK_EQUAL(attr->as_string(), value);
}

BOOST_AUTO_TEST_CASE( test_3dint_attribute )
{
	string value("1 2 3");

	C3DIntAttributeTranslator::register_for("3dint");
	PAttribute attr = CStringAttrTranslatorMap::instance().to_attr("3dint", value);

	C3DIntAttribute *va = dynamic_cast<C3DIntAttribute*>(attr.get());
	BOOST_REQUIRE(va);

	T3DVector<int> vv = *va;
	BOOST_CHECK_EQUAL(vv.x, 1);
	BOOST_CHECK_EQUAL(vv.y, 2);
	BOOST_CHECK_EQUAL(vv.z, 3);

	BOOST_CHECK_EQUAL(attr->as_string(), value);
}
