/* -*- 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/mesh/filter/scale.hh>

NS_BEGIN(mia_meshfilter_scale) 
using namespace mia; 
using namespace std; 


static const TDictMap<EMeshScaleStrategy>::Table table[] = {
	{ "iso-linear", mss_iso_linear, "Scale and shirt the mesh by scaling it "
	  "isotropically as given by parameter 'isoscale' and "
	  "apply a shift as given by parameter 'shift'"},
	{ "aniso-linear", mss_aniso_linear, "Scale and shirt the mesh by scaling "
	  "it anisotropically as given by parameter 'anisoscale' and "
	  "apply a shift as given by parameter 'shift'"},
	{ "iso-fitbox", mss_iso_fitbox, "Fit the mesh into the given box by shifting"
	  " it and apply isotropic scaling. "
	  "The box is given by the parameters 'box-start' for upper, left, frontal "
	  "corner, and 'box-end' for the lower, right back corner"}, 
	{ "aniso-fitbox", mss_stretch_fitbox, "Fit the mesh into the given box by "
	  "shifting it and apply anisotropic scaling. "
	  "The box is given by the parameters 'box-start' for upper, left, frontal "
	  "corner, and 'box-end' for the lower, right back corner"},
	{NULL, mss_undefined, NULL}
}; 

static const TDictMap<EMeshScaleStrategy> s_meshscale_dict(table); 


CScaleMeshFilter::CScaleMeshFilter(EMeshScaleStrategyInternal strategy, const C3DFVector& param1, const C3DFVector& param2):
	m_strategy(strategy), m_param1(param1), m_param2(param2) 
{
}

PTriangleMesh CScaleMeshFilter::scale(const CTriangleMesh& mesh, const C3DFVector& scale, const C3DFVector& shift) const
{
	cvdebug() << "scale by " << scale << " and shift by " << shift << "\n"; 
	PTriangleMesh result(mesh.clone()); 
	for_each(result->vertices_begin(), result->vertices_end(), [scale, shift](C3DFVector& v)->void {
			v *= scale; 
			v += shift; 
		}); 
	return result; 
}

pair<C3DFVector,  C3DFVector> CScaleMeshFilter::get_iso_scale_and_shift(const CTriangleMesh& mesh, const C3DFVector& bstart, const C3DFVector& bend)const
{
	C3DFVector bsize = bend - bstart; 
	auto iv = mesh.vertices_begin(); 

	C3DFVector max_v = *iv; 
	C3DFVector min_v = *iv; 

	for_each(iv + 1, mesh.vertices_end(), [&min_v, &max_v](const C3DFVector& v)->void {
			if (min_v.x > v.x) min_v.x = v.x; 
			if (min_v.y > v.y) min_v.y = v.y; 
			if (min_v.z > v.z) min_v.z = v.z; 

			if (max_v.x < v.x) max_v.x = v.x; 
			if (max_v.y < v.y) max_v.y = v.y; 
			if (max_v.z < v.z) max_v.z = v.z; 
		}); 

	pair<C3DFVector,  C3DFVector> result; 

	result.first = bsize/(max_v - min_v); 

	if (result.first.x < result.first.y) 
		result.first.y = result.first.x; 
	else 
		result.first.x = result.first.y;
	if (result.first.x < result.first.z) 
		result.first.z = result.first.x; 
	else {
		result.first.x = result.first.z; 
		result.first.y = result.first.z; 
	}
	
	result.second = (bsize - (max_v - min_v)* result.first) / 2.0;

	cvdebug() << __func__ << " scale=" << result.first << ", shift=" << result.second << "\n"; 
	return result; 
}

pair<C3DFVector,  C3DFVector> CScaleMeshFilter::get_aniso_scale_and_shift(const CTriangleMesh& mesh, const C3DFVector& bstart, const C3DFVector& bend) const
{
	C3DFVector bsize = bend - bstart; 
	auto iv = mesh.vertices_begin(); 

	C3DFVector max_v = *iv; 
	C3DFVector min_v = *iv; 

	for_each(iv + 1, mesh.vertices_end(), [&min_v, &max_v](const C3DFVector& v)->void {
			if (min_v.x > v.x) min_v.x = v.x; 
			if (min_v.y > v.y) min_v.y = v.y; 
			if (min_v.z > v.z) min_v.z = v.z; 

			if (max_v.x < v.x) max_v.x = v.x; 
			if (max_v.y < v.y) max_v.y = v.y; 
			if (max_v.z < v.z) max_v.z = v.z; 
		}); 

	pair<C3DFVector,  C3DFVector> result; 

	result.first = bsize/(max_v - min_v); 
	result.second = (bsize - (max_v - min_v)* result.first) / 2.0;
	return result; 
}


PTriangleMesh CScaleMeshFilter::do_filter(const CTriangleMesh& mesh) const
{
	if (mesh.vertices_size() == 0) 
		return PTriangleMesh(mesh.clone()); 
	
	switch (m_strategy)  {
	case mss_direct: return scale(mesh, m_param1, m_param2); 
	case mss_fix_iso: {
		auto help = get_iso_scale_and_shift(mesh, m_param1, m_param2); 
		return scale(mesh, help.first, help.second);
	}
	case mss_fix_aniso: {
			auto help = get_aniso_scale_and_shift(mesh, m_param1, m_param2); 
			return scale(mesh, help.first, help.second);
			
		}
	default: 
		assert(0 && "Internal programming error"); 
	}
}

CScaleMeshFilterPlugin::CScaleMeshFilterPlugin():
	CMeshFilterPlugin("scale"), 
	m_scale_strategy(mss_iso_linear), 
	m_scale(1.0f), 
	m_scale_aniso(1.0, 1.0, 1.0), 
	m_shift(0,0,0), 
	m_box_start(0,0,0), 
	m_box_end(256, 256, 256)
{
	typedef CDictParameter<EMeshScaleStrategy> CStrategyParameter; 
	add_parameter("strategy", new CStrategyParameter(m_scale_strategy, s_meshscale_dict, "Isotropic scaling parameter.", false));
	add_parameter("isoscale", make_param(m_scale, false, "Isotropic scaling parameter."));
	add_parameter("anisoscale", make_param(m_scale_aniso, false, "Ansiotropic scaling parameter."));
	add_parameter("shift", make_param(m_shift, false, "Shift mesh after scaling."));
	add_parameter("box-start", make_param(m_box_start, false, "Box start for fitbox scaling."));
	add_parameter("box-end", make_param(m_box_end, false, "Box end for fitbox scaling."));
}

					 

CMeshFilter *CScaleMeshFilterPlugin::do_create()const
{
	switch (m_scale_strategy) {
	case mss_iso_linear:
		return new CScaleMeshFilter(mss_direct, C3DFVector(m_scale, m_scale, m_scale), m_shift); 
	case mss_aniso_linear:
		return new CScaleMeshFilter(mss_direct, m_scale_aniso, m_shift); 
	case mss_iso_fitbox:
		return new CScaleMeshFilter(mss_fix_iso, m_box_start, m_box_end); 
	case mss_stretch_fitbox: 
		return new CScaleMeshFilter(mss_fix_aniso, m_box_start, m_box_end); 
	default:
		throw invalid_argument("CScaleMeshFilterPlugin:Unknown scaling strategy requested"); 
	}
}

const std::string CScaleMeshFilterPlugin::do_get_descr()const
{
	return "This plug-in provides a filter to scale triangulat meshes by a given strategy"; 
}

extern "C" EXPORT CPluginBase *get_plugin_interface()
{
	return new CScaleMeshFilterPlugin();
}


NS_END

