/* -*- 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/>.
 *
 */

// since vista meshes are not defined elsewhere we only need to test that
// a loading-saving roundtrip gives the same result

#define VSTREAM_DOMAIN "test_vistavmesh"

#include <mia/internal/autotest.hh>
#include <mia/mesh/triangularMesh.hh>
#include <mia/2d/vfio.hh>
#include <mia/3d/vfio.hh>

#include <set>
#include <ostream>


using namespace mia;
using namespace std;


template <typename V>
struct vector3d_less {
	bool operator()( const V& lhs, const V& rhs ) const {
		return (lhs.x < rhs.x) || (lhs.x == rhs.x && (lhs.y < rhs.y || (lhs.y == rhs.y && lhs.z < rhs.z))); 
	}
}; 

set<C3DFVector, vector3d_less<C3DFVector> > test_vertices = {
        C3DFVector(2,0,0), C3DFVector(-2,0,0), 
        C3DFVector(0,2,0), C3DFVector(0,-2,0), 
        C3DFVector(0,0,1), C3DFVector(0,0,-1)
};

set<C3DFVector, vector3d_less<C3DFVector> > test_normals = {
        C3DFVector(1,0,0), C3DFVector(-1,0,0), 
        C3DFVector(0,1,0), C3DFVector(0,-1,0), 
        C3DFVector(0,0,1), C3DFVector(0,0,-1)
};

set<C3DFVector, vector3d_less<C3DFVector> > test_colors = {
        C3DFVector(0.1, 0.5, 0.5), C3DFVector(0.2, 0.6, 0.7), 
        C3DFVector(0.3, 0.6, 0.7), C3DFVector(0.4, 0.6, 0.7), 
        C3DFVector(0.5, 0.6, 0.7), C3DFVector(0.6, 0.6, 1)
};

set<float> test_scale = {
	0.1, 2.2, 4.3, 3.4, 0.5, 0.6
};

typedef CTriangleMesh::triangle_type Triangle; 

set<Triangle, vector3d_less<Triangle>>  test_triangles = 
        {Triangle(4, 0, 2), Triangle(4, 2, 1), 
         Triangle(4, 1, 3), Triangle(4, 3, 0), 
         Triangle(5, 2, 0), Triangle(5, 1, 2), 
         Triangle(5, 3, 1), Triangle(5, 0, 3)};


template <typename T>
ostream& operator << (ostream& os, const set<T, vector3d_less<T>>& s)
{
	os << "set:[";
	for (auto x: s)
		os << "<" << x << "> ";
	os << "]\n";
	return os; 
}


template <typename IT, typename T> 
void test_set_equal(const IT begin, IT end,
		    const set<T, vector3d_less<T>>& expect)
{
        set<T, vector3d_less<T>> loaded(begin, end);
        BOOST_CHECK_EQUAL(loaded.size(), expect.size());

	BOOST_CHECK(loaded == expect);
	cvdebug() << loaded << "\n";
	cvdebug() << expect << "\n"; 
        
}

template <typename IT, typename T> 
void test_set_equal(const IT begin, IT end,
		    const set<T>& expect)
{
        set<T> loaded(begin, end);
        BOOST_CHECK_EQUAL(loaded.size(), expect.size());

	BOOST_CHECK(loaded == expect);
}

struct CVistaMeshtestFixtureV {

        CVistaMeshtestFixtureV();

        virtual void test_loaded( const CTriangleMesh& loaded);  

        void run(const char *testfilename); 
        
        PTriangleMesh mesh;
}; 

CVistaMeshtestFixtureV::CVistaMeshtestFixtureV()
{
        auto vertices = CTriangleMesh::PVertexfield(
                          new CTriangleMesh::CVertexfield(test_vertices.begin(), test_vertices.end()));

        auto triangles = CTriangleMesh::PTrianglefield(
                          new CTriangleMesh::CTrianglefield(test_triangles.begin(), test_triangles.end()));
        mesh.reset(new CTriangleMesh(triangles, vertices)); 
}

void CVistaMeshtestFixtureV::test_loaded( const CTriangleMesh& loaded)
{
        BOOST_CHECK_EQUAL(loaded.get_available_data(), mesh->get_available_data()); 
        
        test_set_equal(loaded.vertices_begin(), loaded.vertices_end(), test_vertices);
        test_set_equal(loaded.triangles_begin(), loaded.triangles_end(), test_triangles); 
}

void CVistaMeshtestFixtureV::run(const char *testfilename)
{
        BOOST_REQUIRE(CMeshIOPluginHandler::instance().save(testfilename, *mesh));
        auto loaded_mesh = CMeshIOPluginHandler::instance().load(testfilename);
        BOOST_REQUIRE(loaded_mesh);
        test_loaded(*loaded_mesh); 
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_v, CVistaMeshtestFixtureV)
{
        run("mesh-v.vmesh"); 
}


struct CVistaMeshtestFixtureVN : public CVistaMeshtestFixtureV{
        CVistaMeshtestFixtureVN();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVN::CVistaMeshtestFixtureVN()
{
        copy(test_normals.begin(), test_normals.end(), mesh->normals_begin()); 
}

void CVistaMeshtestFixtureVN::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureV::test_loaded(loaded);

        test_set_equal(loaded.normals_begin(), loaded.normals_end(), test_normals);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vn, CVistaMeshtestFixtureVN)
{
        run("mesh-vn.vmesh"); 
}




struct CVistaMeshtestFixtureVNC : public CVistaMeshtestFixtureVN{
        CVistaMeshtestFixtureVNC();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVNC::CVistaMeshtestFixtureVNC()
{
        copy(test_colors.begin(), test_colors.end(), mesh->color_begin()); 
}

void CVistaMeshtestFixtureVNC::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureVN::test_loaded(loaded);

        test_set_equal(loaded.color_begin(), loaded.color_end(), test_colors);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vnc, CVistaMeshtestFixtureVNC)
{
        run("mesh-vnc.vmesh"); 
}


struct CVistaMeshtestFixtureVC : public CVistaMeshtestFixtureV{
        CVistaMeshtestFixtureVC();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVC::CVistaMeshtestFixtureVC()
{
        copy(test_colors.begin(), test_colors.end(), mesh->color_begin()); 
}

void CVistaMeshtestFixtureVC::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureV::test_loaded(loaded);

        test_set_equal(loaded.color_begin(), loaded.color_end(), test_colors);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vc, CVistaMeshtestFixtureVC)
{
        run("mesh-vc.vmesh"); 
}


struct CVistaMeshtestFixtureVS : public CVistaMeshtestFixtureV{
        CVistaMeshtestFixtureVS();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVS::CVistaMeshtestFixtureVS()
{
        copy(test_scale.begin(), test_scale.end(), mesh->scale_begin()); 
}

void CVistaMeshtestFixtureVS::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureV::test_loaded(loaded);

        test_set_equal(loaded.scale_begin(), loaded.scale_end(), test_scale);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vs, CVistaMeshtestFixtureVS)
{
        run("mesh-vs.vmesh"); 
}


struct CVistaMeshtestFixtureVCS : public CVistaMeshtestFixtureVC{
        CVistaMeshtestFixtureVCS();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVCS::CVistaMeshtestFixtureVCS()
{
        copy(test_scale.begin(), test_scale.end(), mesh->scale_begin()); 
}

void CVistaMeshtestFixtureVCS::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureVC::test_loaded(loaded);

        test_set_equal(loaded.scale_begin(), loaded.scale_end(), test_scale);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vcs, CVistaMeshtestFixtureVCS)
{
        run("mesh-vcs.vmesh"); 
}

struct CVistaMeshtestFixtureVNCS : public CVistaMeshtestFixtureVNC{
        CVistaMeshtestFixtureVNCS();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVNCS::CVistaMeshtestFixtureVNCS()
{
        copy(test_scale.begin(), test_scale.end(), mesh->scale_begin()); 
}

void CVistaMeshtestFixtureVNCS::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureVNC::test_loaded(loaded);

        test_set_equal(loaded.scale_begin(), loaded.scale_end(), test_scale);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vncs, CVistaMeshtestFixtureVNCS)
{
        run("mesh-vncs.vmesh"); 
}


struct CVistaMeshtestFixtureVNS : public CVistaMeshtestFixtureVN {
        CVistaMeshtestFixtureVNS();
        virtual void test_loaded( const CTriangleMesh& loaded);  
};

CVistaMeshtestFixtureVNS::CVistaMeshtestFixtureVNS()
{
        copy(test_scale.begin(), test_scale.end(), mesh->scale_begin()); 
}

void CVistaMeshtestFixtureVNS::test_loaded( const CTriangleMesh& loaded)
{
        CVistaMeshtestFixtureVN::test_loaded(loaded);

        test_set_equal(loaded.scale_begin(), loaded.scale_end(), test_scale);
}

BOOST_FIXTURE_TEST_CASE( test_vista_meshio_vns, CVistaMeshtestFixtureVNS)
{
        run("mesh-vns.vmesh"); 
}



BOOST_AUTO_TEST_CASE ( test_3dvfio )
{
        C3DBounds size(1,2,3); 
        C3DFVectorfield vf(size);

        vector<C3DFVector> test_data = {
                C3DFVector(1, 2, 3), C3DFVector(2, 3, 4), C3DFVector(3, 4, 5),
                C3DFVector(4, 5, 6), C3DFVector(5, 6, 7), C3DFVector(7, 8, 9)
        };

        copy(test_data.begin(), test_data.end(), vf.begin()); 

        C3DIOVectorfield iovf(vf); 
        BOOST_REQUIRE(C3DVFIOPluginHandler::instance().save("3d.vf", iovf));

        auto loaded = C3DVFIOPluginHandler::instance().load("3d.vf");
        BOOST_REQUIRE(loaded);
        
        BOOST_CHECK_EQUAL(loaded->get_size(), size);
        auto i = loaded->begin();
        auto e = loaded->end();
        auto t = vf.begin();

        while (i != e) {
                BOOST_CHECK_EQUAL(*i, *t);
                ++i; ++t; 
        }
}

BOOST_AUTO_TEST_CASE ( test_2dvfio )
{
        C2DBounds size(2,3); 
        C2DFVectorfield vf(size);

        vector<C2DFVector> test_data = {
                C2DFVector(2, 3), C2DFVector(3, 4), C2DFVector(4, 5),
                C2DFVector(5, 6), C2DFVector(6, 7), C2DFVector(8, 9)
        };

        copy(test_data.begin(), test_data.end(), vf.begin()); 

        C2DIOVectorfield iovf(vf); 
        BOOST_REQUIRE(C2DVFIOPluginHandler::instance().save("2d.vf", iovf));

        auto loaded = C2DVFIOPluginHandler::instance().load("2d.vf");
        BOOST_REQUIRE(loaded);
        
        BOOST_CHECK_EQUAL(loaded->get_size(), size);
        auto i = loaded->begin();
        auto e = loaded->end();
        auto t = vf.begin();

        while (i != e) {
                BOOST_CHECK_EQUAL(*i, *t);
                ++i; ++t; 
        }
}





