/* -*- 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/core/filter.hh>
#include <mia/3d/image.hh>


NS_MIA_BEGIN

const char *C3DImage::data_descr = "3dimage";

C3DImage::C3DImage(EPixelType pixel_type):
	m_pixel_type(pixel_type)
{
}

C3DImage::C3DImage(const CAttributedData& data, EPixelType type):
	CAttributedData(data),
	m_pixel_type(type)
{
}


C3DImage::~C3DImage()
{
}

EPixelType C3DImage::get_pixel_type() const
{
	return m_pixel_type;
}

E3DImageOrientation C3DImage::get_orientation() const
{
	E3DImageOrientation axis_orientation = ior_default; 
	const PAttribute attr = get_attribute("orientation");
	if (attr) {
		auto op = dynamic_cast<const TAttribute<E3DImageOrientation> *>(attr.get());
		if (!op) {
			cvwarn() << "C3DImage::get_orientation: Bogus orientation attribute, return default\n"; 
		}else {
			axis_orientation = *op; 
		}
	}
	return axis_orientation; 
}

void C3DImage::set_orientation(E3DImageOrientation orient)
{
	set_attribute("orientation", PAttribute(new TAttribute<E3DImageOrientation>(orient)));
}

void C3DImage::set_voxel_size(const C3DFVector& voxel)
{
	set_attribute("voxel", PAttribute(new CVoxelAttribute(voxel)));
}

C3DFVector C3DImage::get_voxel_size() const
{
	const PAttribute attr = get_attribute("voxel");
	if (!attr) {
		cvinfo() << "T3DImage<T>::get_voxel_size(): voxel size not defined, default to <1,1,1>\n";
		return C3DFVector::_1;
	}

	const CVoxelAttribute * vs = dynamic_cast<const CVoxelAttribute *>(attr.get());
	if (!vs){
		cvinfo() << "T3DImage<T>::get_voxel_size(): voxel size wrong type, default to <1,1,1>\n";
		return C3DFVector::_1;
	}

	return *vs;
}


C3DFVector C3DImage::get_origin() const 
{
	const PAttribute attr = get_attribute("origin3d");
	if (!attr) {
		cvinfo() << "T3DImage<T>::get_origin(): Origin size not defined, default to <0,0,0>\n";
		return C3DFVector::_0;
	}

	const CVoxelAttribute * vs = dynamic_cast<const CVoxelAttribute *>(attr.get());
	if (!vs){
		cvinfo() << "T3DImage<T>::get_origin(): origin attribute of wrong type, default to <0,0,0>\n";
		return C3DFVector::_0;
	}

	return *vs;
}

void C3DImage::set_origin(const C3DFVector& voxel) 
{
	set_attribute("origin3d", PAttribute(new CVoxelAttribute(voxel)));
}


C3DRotation C3DImage::get_rotation() const
{
	const PAttribute attr = get_attribute("rotation3d");
	if (!attr) {
		cvinfo() << "T3DImage<T>::get_rotation(): Rotation size not defined, default to no rotation\n";
		return C3DRotation::_1;
	}
	
	const C3DRotationAttribute * vs = dynamic_cast<const C3DRotationAttribute *>(attr.get());
	if (!vs){
		cvinfo() << "T3DImage<T>::get_rotation(): Rotation attribute is of wrong type, default to no rotation\n";
		return C3DRotation::_1;
	}
	return *vs;
}

void C3DImage::set_rotation(const C3DRotation& rot)
{
	set_attribute("rotation3d", PAttribute(new C3DRotationAttribute(rot)));
}



template <typename T>
T3DImage<T>::T3DImage(const C3DBounds& size, const T* init_data):
	C3DImage((EPixelType)pixel_type<T>::value),
	m_image(size, init_data)
{
}

template <typename T>
T3DImage<T>::T3DImage(const C3DBounds& size, const data_array& init_data):
	C3DImage((EPixelType)pixel_type<T>::value),
	m_image(size, init_data)
{
}


template <typename T>
T3DImage<T>::T3DImage(const C3DBounds& size, const CAttributedData& attr):
	C3DImage(attr, (EPixelType)pixel_type<T>::value),
	m_image(size)
{
}

template <typename T>
T3DImage<T>::T3DImage(const C3DBounds& size):
	C3DImage((EPixelType)pixel_type<T>::value),
	m_image(size)
{
}

template <typename T>
T3DImage<T>::T3DImage(const T3DImage<T>& orig):
	C3DImage(orig),
	m_image(orig.m_image)
{
}

template <typename T>
T3DImage<T>::T3DImage(T3DImage<T>&& orig):
	C3DImage(orig),
	m_image(orig.m_image)
{
}	

template <typename T>
T3DImage<T>& T3DImage<T>::operator = (const T3DImage<T>& orig)
{
	if (this != &orig) {
		C3DImage::operator =(orig);
		m_image = orig.m_image;
	}
	return *this; 
}


template <typename T>
T3DImage<T>& T3DImage<T>::operator = (T3DImage<T>&& orig)
{
	if (this != &orig) {
		C3DImage::operator =(std::move(orig));
		m_image = std::move(orig.m_image);
	}
	return *this;
}


template <typename T>
T3DImage<T>::T3DImage(const T3DDatafield<T>& orig):
	C3DImage((EPixelType)pixel_type<T>::value),
	m_image(orig)

{
}


template <typename T>
C3DImage::Pointer T3DImage<T>::clone() const
{
	return P3DImage(new T3DImage<T>(*this));
}

template <typename T>
void T3DImage<T>::get_data_line_x(int y, int z, std::vector<T>& buffer)const
{
	m_image.get_data_line_x(y, z, buffer);
}

template <typename T>
void T3DImage<T>::get_data_line_y(int x, int z, std::vector<T>& buffer)const
{
	m_image.get_data_line_y(x, z, buffer);
}


template <typename T>
void T3DImage<T>::get_data_line_z(int x, int y, std::vector<T>& buffer)const
{
	m_image.get_data_line_z(x, y, buffer);
}


template <typename T>
void T3DImage<T>::put_data_line_x(int y, int z, const std::vector<T> &buffer)
{
	m_image.put_data_line_x(y, z, buffer);
}


template <typename T>
void T3DImage<T>::put_data_line_y(int x, int z, const std::vector<T> &buffer)
{
	m_image.put_data_line_y(x, z, buffer);
}


template <typename T>
void T3DImage<T>::put_data_line_z(int x, int y, const std::vector<T> &buffer)
{
	m_image.put_data_line_z(x, y, buffer);
}

template <typename T>
T2DImage<T> T3DImage<T>::get_data_plane_xy(size_t  z)const
{
	return  T2DImage<T>(m_image.get_data_plane_xy(z), *this);
}

template <typename T>
T2DImage<T> T3DImage<T>::get_data_plane_yz(size_t  x)const
{
	return  T2DImage<T>(m_image.get_data_plane_yz(x), *this);
}

template <typename T>
T2DImage<T> T3DImage<T>::get_data_plane_xz(size_t  y)const
{
	return  T2DImage<T>(m_image.get_data_plane_xz(y), *this);
}

template <typename T>
void T3DImage<T>::put_data_plane_xy(size_t  z, const T2DImage<T>& p)
{
	m_image.put_data_plane_xy(z, p.data());
}

template <typename T>
void T3DImage<T>::put_data_plane_yz(size_t  x, const T2DImage<T>& p)
{
	m_image.put_data_plane_yz(x, p.data());
}

template <typename T>
void T3DImage<T>::put_data_plane_xz(size_t  y, const T2DImage<T>& p)
{
	m_image.put_data_plane_xz(y, p.data());
}


template <typename T>
T3DImage<T>::T3DImage():
	C3DImage((EPixelType)pixel_type<T>::value),
	m_image(C3DBounds(0,0,0))
{
}

template <typename T>
size_t T3DImage<T>::size() const
{
	return m_image.size();
}

template <typename T>
const T3DDatafield<T>& T3DImage<T>::data() const
{
	return m_image;
}

//template <typename T>
//T3DDatafield<T>& T3DImage<T>::data()
//{
//	return m_image;
//}

template <typename T>
const C3DBounds& T3DImage<T>::get_size() const
{
	return m_image.get_size();
}

template <class T>
std::pair<double, double> T3DImage<T>::get_minmax_intensity() const
{
	auto mm = std::minmax_element( m_image.begin(), m_image.end());
	return std::pair<double, double>(*mm.first, *mm.second);
}


struct FGetGradient3D: public TFilter< C3DFVectorfield> {
	template <typename T>
	C3DFVectorfield operator () ( const T3DImage<T>& image) const {

		C3DFVectorfield result(image.get_size());

		size_t slice_size = image.get_size().x * image.get_size().y; 
		size_t row_size = image.get_size().x; 
		
		auto v = result.begin(); 
		auto i = image.begin(); 

		++i; 
		*v++ = C3DFVector(); 

		for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i)
			*v = C3DFVector( 0.5 * (i[1] - i[-1]), 0.0f, 0.0f);
		
		++i; 
		*v++ = C3DFVector(); 

		for (size_t y = 1; y < image.get_size().y - 1; ++y ) {
			*v = C3DFVector( 0.0f, 0.5 * (i[row_size] - i[-row_size]), 0.0f);
			++i; ++v; 
			for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i) {
				*v = C3DFVector( 0.5 * (i[1] - i[-1]), 
						 0.5 * (i[row_size] - i[-row_size]), 0.0f); 
			}
			*v = C3DFVector( 0.0f, 0.5 * (i[row_size] - i[-row_size]), 0.0f);
			++i; ++v; 
		}

		++i; 
		*v++ = C3DFVector(); 

		for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i)
			*v = C3DFVector( 0.5 * (i[1] - i[-1]), 0.0f, 0.0f);
		
		++i; 
		*v++ = C3DFVector(); 
		
		for (size_t z = 1; z < image.get_size().z - 1; ++z) {
			*v = C3DFVector(0.0, 0.0, 0.5 * (i[slice_size] - i[-slice_size]));
			++i; ++v;
			
			for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i)
				*v = C3DFVector( 0.5 * (i[1] - i[-1]), 0.0f, 0.5 * (i[slice_size] - i[-slice_size]));
			
			*v = C3DFVector(0.0, 0.0, 0.5 * (i[slice_size] - i[-slice_size]));
			++i; ++v;

			for (size_t y = 1; y < image.get_size().y - 1; ++y ) {
				*v = C3DFVector( 0.0f, 0.5 * (i[row_size] - i[-row_size]), 
						 0.5 * (i[slice_size] - i[-slice_size]));
				++i; ++v; 
				for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i) {
					*v = C3DFVector( 0.5 * (i[1] - i[-1]), 
							 0.5 * (i[row_size] - i[-row_size]), 
							 0.5 * (i[slice_size] - i[-slice_size])); 
				}
				*v = C3DFVector( 0.0f, 0.5 * (i[row_size] - i[-row_size]), 
						 0.5 * (i[slice_size] - i[-slice_size]));
				++i; ++v; 
			}
			*v = C3DFVector(0.0, 0.0, 0.5 * (i[slice_size] - i[-slice_size]));
			++i; ++v;
			
			for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i)
				*v = C3DFVector( 0.5 * (i[1] - i[-1]), 0.0f, 0.5 * (i[slice_size] - i[-slice_size]));
			
			*v = C3DFVector(0.0, 0.0, 0.5 * (i[slice_size] - i[-slice_size]));
			++i; ++v;
		}
		
		++i; 
		*v++ = C3DFVector(); 

		for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i)
			*v = C3DFVector( 0.5 * (i[1] - i[-1]), 0.0f, 0.0f);
		
		++i; 
		*v++ = C3DFVector(); 

		for (size_t y = 1; y < image.get_size().y - 1; ++y ) {
			*v = C3DFVector( 0.0f, 0.5 * (i[row_size] - i[-row_size]), 0.0f);
			++i; ++v; 
			for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i) {
				*v = C3DFVector( 0.5 * (i[1] - i[-1]), 
						 0.5 * (i[row_size] - i[-row_size]), 0.0f); 
			}
			*v = C3DFVector( 0.0f, 0.5 * (i[row_size] - i[-row_size]), 0.0f);
			++i; ++v; 
		}
		
		++i; 
		*v++ = C3DFVector(); 

		for (size_t x = 1; x < image.get_size().x - 1; ++x, ++v, ++i)
			*v = C3DFVector( 0.5 * (i[1] - i[-1]), 0.0f, 0.0f);
		
		++i; 
		*v++ = C3DFVector(); 

		
		return result;
	}
};

/**
   Evaluate the gradient of an image by using symmetric finite differences. 
   @param image input image to calculate the gradient from 
   @returns the 3D vector field representing the gradient, boundaries are set to zero. 
*/

C3DFVectorfield get_gradient(const C3DImage& image)
{
	FGetGradient3D gg;
	return filter(gg, image);
}


template class T3DImage<bool>;
template class T3DImage<int8_t>;
template class T3DImage<uint8_t>;
template class T3DImage<int16_t>;
template class T3DImage<uint16_t>;
template class T3DImage<int32_t>;
template class T3DImage<uint32_t>;
template class T3DImage<int64_t>;
template class T3DImage<uint64_t>;
template class T3DImage<float>;
template class T3DImage<double>;


template <typename T>
C3DValueAttribute<T>::C3DValueAttribute(const T3DVector<T>& value):
	m_value(value)
{
}

template <typename T>
C3DValueAttribute<T>::operator T3DVector<T>()const
{
	return m_value;
}

template <typename T>
std::string C3DValueAttribute<T>::do_as_string() const
{
	std::stringstream s;
	s << m_value.x << " " << m_value.y << " " << m_value.z;
	return s.str();
}

template <typename T>
bool C3DValueAttribute<T>::do_is_equal(const CAttribute& other) const
{
	const C3DValueAttribute *va = dynamic_cast<const C3DValueAttribute*>(&other);
	if (!va)
		return false;
	return  m_value == va->m_value;
}

template <typename T>
bool C3DValueAttribute<T>::do_is_less(const CAttribute& other) const
{
	const C3DValueAttribute *va = dynamic_cast<const C3DValueAttribute*>(&other);
	if (!va)
		return strcmp(typedescr(), other.typedescr());
	return  m_value < va->m_value;
}

template <typename T>
PAttribute C3DValueAttributeTranslator<T>::do_from_string(const std::string& value) const
{
	std::istringstream inp(value);
	T3DVector<T> v;

	inp >> v.x >> v.y >> v.z;

	if (inp.good() || inp.eof())
		return PAttribute(new C3DValueAttribute<T>(v));
	throw std::invalid_argument(value + " does not represent a voxel size");
}

template <typename T>
bool C3DValueAttributeTranslator<T>::register_for(const std::string& key)
{
	C3DValueAttributeTranslator *me = new C3DValueAttributeTranslator();
	if (!me->do_register(key)) {
		delete me; 
		return false; 
	}
	return true; 
}

template class C3DValueAttribute<float>;
template class  EXPORT_3D C3DValueAttributeTranslator<float>;
template class  EXPORT_3D C3DValueAttribute<int>;
template class  EXPORT_3D C3DValueAttributeTranslator<int>;

NS_MIA_END
