/* -*- 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/3d/orientation.hh>
#include <mia/core/utils.hh>
#include <mia/core/attributes.cxx>

NS_MIA_BEGIN
using namespace std; 


C3DOrientationAndPosition::C3DOrientationAndPosition():
	m_axisorder(ior_default), 
	m_origin(0,0,0), 
	m_scale(1,1,1), 
	m_rotation(1,0,0,0)
{
}

C3DOrientationAndPosition::C3DOrientationAndPosition(E3DImageOrientation axis):
	m_axisorder(axis), 
	m_origin(0,0,0), 
	m_scale(1,1,1), 
	m_rotation(1,0,0,0)
{
}

C3DOrientationAndPosition::C3DOrientationAndPosition(E3DImageOrientation axis, 
						     const C3DFVector& origin, 
						     const C3DFVector& scale, 
						     const Quaternion& rot):
	m_axisorder(axis), 
	m_origin(origin), 
	m_scale(scale), 
	m_rotation(rot)
{
}

void C3DOrientationAndPosition::get_transform_parameters(CDoubleVector& params) const
{
	assert(params.size() == 16); 

	const C3DFMatrix& axis_switch = get_axis_switch_matrix(); 
	const C3DFMatrix rot = m_rotation.get_rotation_matrix(); 
	const C3DFMatrix scale(C3DFVector(m_scale.x, 0, 0), C3DFVector(0, m_scale.y, 0), C3DFVector(0,0, m_scale.z)); 

	C3DFMatrix rs = axis_switch * (rot * scale);
	
	params[0] = rs.x.x; 
	params[1] = rs.x.y; 
	params[2] = rs.x.z; 
	params[3] = m_origin.x; 

	params[4] = rs.y.x; 
	params[5] = rs.y.y; 
	params[6] = rs.y.z; 
	params[7] = m_origin.y; 

	params[8]  = rs.z.x; 
	params[9]  = rs.z.y; 
	params[10] = rs.z.z; 
	params[11] = m_origin.z; 

	params[12] = 0; 
	params[13] = 0; 
	params[14] = 0; 
	params[15] = 1; 


}

void C3DOrientationAndPosition::get_inverse_transform_parameters(CDoubleVector& params) const
{
	assert(params.size() == 16);
	const C3DFMatrix& axis_switch = get_axis_switch_matrix(); 
	const C3DFMatrix rot = m_rotation.get_rotation_matrix().transposed(); 
	const C3DFMatrix scale(C3DFVector(1.0/m_scale.x, 0, 0), 
			       C3DFVector(0, 1.0/m_scale.y, 0), 
			       C3DFVector(0,0, 1.0/m_scale.z)); 

	const C3DFMatrix sr = scale * rot;
	const C3DFVector mt = C3DFVector::_0 - m_origin; 
	const C3DFVector it = sr * mt; 

	const C3DFMatrix stf = sr * axis_switch;
	const C3DFVector ft = it * axis_switch;
	
	
	params[0] = stf.x.x; 
	params[1] = stf.x.y; 
	params[2] = stf.x.z; 
	params[3] = ft.x; 

	params[4] = stf.y.x; 
	params[5] = stf.y.y; 
	params[6] = stf.y.z; 
	params[7] = ft.y; 

	params[8]  = stf.z.x; 
	params[9]  = stf.z.y; 
	params[10] = stf.z.z; 
	params[11] = ft.z;

	params[12] = 0; 
	params[13] = 0; 
	params[14] = 0; 
	params[15] = 1; 

}

const C3DFMatrix& C3DOrientationAndPosition::get_axis_switch_matrix() const
{
	switch (m_axisorder) {
	case ior_xyz:         return ms_order_XYZ; 
	case ior_xyz_flipped: return ms_order_XYZ_F; 
	case ior_xzy:         return ms_order_XZY; 
	case ior_xzy_flipped: return ms_order_XZY_F; 
	case ior_yxz:         return ms_order_YXZ; 
	case ior_yxz_flipped: return ms_order_YXZ_F; 
	case ior_yzx:         return ms_order_YZX; 
	case ior_yzx_flipped: return ms_order_YZX_F; 
	case ior_zxy:         return ms_order_ZXY; 
	case ior_zxy_flipped: return ms_order_ZXY_F; 
	case ior_zyx:         return ms_order_ZYX; 
	case ior_zyx_flipped: return ms_order_ZYX_F; 
	default: 
		cvwarn() << "C3DOrientationAndPosition: axis ordering not specified, assuming default XYZ\n"; 
		return ms_order_XYZ; 
	}
}

C3DOrientationAndPosition& C3DOrientationAndPosition::operator +=(const C3DOrientationAndPosition& MIA_PARAM_UNUSED(other))
{
	assert(0 && "to be implemented"); 
}

bool C3DOrientationAndPosition::operator == (const C3DOrientationAndPosition& other) const
{
	return 	m_axisorder == other.m_axisorder && 
		m_origin == other.m_origin && 
		m_scale == other.m_scale && 
		m_rotation == other.m_rotation; 

}

bool C3DOrientationAndPosition::operator < (const C3DOrientationAndPosition& MIA_PARAM_UNUSED(other)) const
{
	assert(0 && "to be implemented"); 
}

E3DImageOrientation C3DOrientationAndPosition::get_axis_orientation() const
{
	return m_axisorder; 
}



const mia::C3DFVector& C3DOrientationAndPosition::get_scale() const
{
	return m_scale; 
}


const mia::C3DFVector& C3DOrientationAndPosition::get_origin() const
{
	return m_origin; 
}

const mia::Quaternion& C3DOrientationAndPosition::get_rotation() const
{
	return m_rotation; 
}


EXPORT_3D  std::ostream& operator << (std::ostream& os, E3DImageOrientation orient)
{
	os << g_image_orientation_map.get_name(orient); 
	return os;
}

EXPORT_3D std::istream& operator >> (std::istream& is, E3DImageOrientation& orient)
{
	string temp;
	is >> temp;
	orient = g_image_orientation_map.get_value(temp.c_str()); 
	return is;
}

const C3DFMatrix C3DOrientationAndPosition::ms_order_XYZ(C3DFVector(1,0,0), C3DFVector(0,1,0), C3DFVector(0,0,1)); 
const C3DFMatrix C3DOrientationAndPosition::ms_order_YXZ(C3DFVector(0,1,0), C3DFVector(1,0,0), C3DFVector(0,0,1));
const C3DFMatrix C3DOrientationAndPosition::ms_order_XZY(C3DFVector(1,0,0), C3DFVector(0,0,1), C3DFVector(0,1,0));
const C3DFMatrix C3DOrientationAndPosition::ms_order_ZXY(C3DFVector(0,0,1), C3DFVector(1,0,0), C3DFVector(0,1,0));
const C3DFMatrix C3DOrientationAndPosition::ms_order_ZYX(C3DFVector(0,0,1), C3DFVector(0,1,0), C3DFVector(1,0,0));
const C3DFMatrix C3DOrientationAndPosition::ms_order_YZX(C3DFVector(0,1,0), C3DFVector(0,0,1), C3DFVector(1,0,0));

const C3DFMatrix C3DOrientationAndPosition::ms_order_XYZ_F(C3DFVector(1,0,0), C3DFVector(0,1,0), C3DFVector(0,0,-1)); 
const C3DFMatrix C3DOrientationAndPosition::ms_order_YXZ_F(C3DFVector(0,1,0), C3DFVector(1,0,0), C3DFVector(0,0,-1));
const C3DFMatrix C3DOrientationAndPosition::ms_order_XZY_F(C3DFVector(1,0,0), C3DFVector(0,0,1), C3DFVector(0,-1,0));
const C3DFMatrix C3DOrientationAndPosition::ms_order_ZXY_F(C3DFVector(0,0,1), C3DFVector(1,0,0), C3DFVector(0,-1,0));
const C3DFMatrix C3DOrientationAndPosition::ms_order_ZYX_F(C3DFVector(0,0,1), C3DFVector(0,1,0), C3DFVector(-1,0,0));
const C3DFMatrix C3DOrientationAndPosition::ms_order_YZX_F(C3DFVector(0,1,0), C3DFVector(0,0,1), C3DFVector(-1,0,0));


static const TDictMap<E3DImageOrientation>::Table image_orientation_map[] = {
	{ "(undefined)", ior_undefined, "image orientation not defined"}, 
	{ "axial", ior_xyz, "transversal/axial"}, 
	{ "axial-flipped", ior_xyz_flipped, "transversal/axial z-order reverse"}, 
	{ "axial-transposed",  ior_yxz, "transversal/axial, transposed"}, 
	{ "axial-transposed-flipped", ior_yxz_flipped, "transversal/axial, z-order reverse, transposed"}, 

	{ "coronal", ior_xzy, "coronal,  face facing front"}, 
	{ "coronal-flipped", ior_xzy_flipped, "coronal, back facing front"}, 
	{ "coronal-transposed", ior_zxy, "coronal, face facing front, transposed"}, 
	{ "coronal-transposed-flipped", ior_zxy_flipped, "coronal, back facing front,  transposed"},

	{ "saggital", ior_yzx, "saggital,  left facing front"}, 
	{ "saggital-flipped", ior_yzx_flipped, "saggital, right facing front"}, 
	{ "saggital-transposed", ior_zyx, "saggital, left facing front, transposed"}, 
	{ "saggital-transposed-flipped", ior_zyx_flipped, "saggital, right facing front,  transposed"},
	
	{ nullptr, ior_undefined,  nullptr}
}; 

const TDictMap<E3DImageOrientation> g_image_orientation_map(image_orientation_map, true); 

static const TDictMap<E3DPatientPositioning>::Table patient_position_map_table[] = {
	{"(undefined)", ipp_undefined, "undefined patient position"}, 
	{"HFS", ipp_hfs, " head first supine "},  
	{"HFP", ipp_hfp, " head first prone "},  
	{"HFDR", ipp_hfdr, " head first Decubitus Right "},
	{"HFDL", ipp_hfdl, " head first Decubitus Left "},
	{"FFP", ipp_ffp, " feet first prone "},  
	{"FFS", ipp_ffs, " feet first supine "},  
	{"FFDR", ipp_ffdr, " feet first Decubitus Right "},
	{"FFDL", ipp_ffdl, " feet first Decubitus Left "}, 
	{0, ipp_undefined, 0}
}; 


const TDictMap<E3DPatientPositioning> g_patient_position_map(patient_position_map_table, true); 

EXPORT_3D  ostream& operator << (ostream& os, E3DPatientPositioning pp)
{
	os << g_patient_position_map.get_name(pp); 
	return os; 
}

EXPORT_3D  istream& operator >> (istream& is, E3DPatientPositioning& pp)
{
	string s; 
	is >> s; 
	pp = g_patient_position_map.get_value(s.c_str()); 
	return is; 
}

void C3DOrientationAndPosition::print(std::ostream& os)const
{
	os << "[" << m_axisorder << " [" << m_origin << "] [" 
	   << m_scale << "] [" << m_rotation << "]]"; 
}

EXPORT_3D  std::istream& operator >> (std::istream& is, C3DOrientationAndPosition& orient)
{
	const char *msg = "unable to read C3DOrientationAndPosition from stream"; 
	E3DImageOrientation axisorder; 
	C3DFVector origin; 
	C3DFVector scale; 
	Quaternion rotation; 

	eat_char(is, '[', msg); 
	
	is >> axisorder; 

	eat_char(is, '[', msg); 

	is >> origin; 

	eat_char(is, ']', msg); 
	eat_char(is, '[', msg); 
	
	is >> scale; 
	
	eat_char(is, ']', msg); 
	eat_char(is, '[', msg); 
	
	
	is >> rotation; 
	
	eat_char(is, ']', msg); 
	eat_char(is, ']', msg); 

	orient = C3DOrientationAndPosition(axisorder, origin, scale, rotation);
	
	return is; 
}

#ifdef __GNUC__
#pragma GCC diagnostic push
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wattributes"
#endif
#endif

template class EXPORT_3D TAttribute<E3DImageOrientation>;
template class EXPORT_3D TAttribute<E3DPatientPositioning>;
template class EXPORT_3D TAttribute<C3DOrientationAndPosition>; 

template class  EXPORT_3D TTranslator<E3DImageOrientation>;
template class  EXPORT_3D TTranslator<E3DPatientPositioning>;
template class  EXPORT_3D TTranslator<C3DOrientationAndPosition>; 

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif 


NS_MIA_END
