#ifndef MODULE_BITMAP_BITMAP_ELEMENT_H
#define MODULE_BITMAP_BITMAP_ELEMENT_H

// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program 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 2 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/** \file
          \author Anders Dahnielson (anders@dahnielson.com)
*/

#include <k3dsdk/algebra.h> 
#include <k3dsdk/bitmap.h>

#include "bitmap_functions.h"

namespace libk3dbitmap
{

/*
   The relationship between 'pixel space' and (2d) 'world space'

        | ------ w ----- |
      - +----------------+
      | | +-> u          | p s
        | |              | i p
      h | v      +       | x a
        |  v    c \      | e c
      | |          \     | l e
      - +-----------\----+
                     \
                      \            max
                 +-----------------+
                 |      \ ^ y      | w s
                 |       \|        | o p
                 |        +--> x   | r a
                 |       o         | l c
                 |                 | d e
                 +-----------------+
               min
*/

/// Class to treat bitmap as element in (2d) world space
class bitmap_element
{
public:
	/// Extremes in world space
	int min_x, max_x, min_y, max_y;

	bitmap_element(k3d::bitmap* Bitmap) :
		m_position_x(0),
		m_position_y(0),
		m_bitmap(new k3d::bitmap(*Bitmap))
	{
		update_boundary();	
	}

	/// Return pixel value by world space coord
	const k3d::bitmap::pixel_type get_pixel(const int x, const int y)
	{
		// Convert world space to pixel space
		unsigned int u = (m_bitmap->width()/2) + (x - m_position_x); 
		unsigned int v = (m_bitmap->height()/2) - (y - m_position_y);
		
		// We must at least be inside the data
		if (u < 0 || v < 0 || u > m_bitmap->width() || v > m_bitmap->height())
			return k3d::pixel(0,0,0,k3d::pixel::sample_traits::transparent());
		
		// Return the pixel
		return *(m_bitmap->begin() + (u + (m_bitmap->width() * v)));
	}

	/// Return bitmap
	k3d::bitmap* get_bitmap()
	{
		return m_bitmap;
	}

	/// Scale element
	void scale(const k3d::vector2 scaling)
	{
		double scaling_x = scaling[k3d::VX];
		double scaling_y = scaling[k3d::VY];

		// Horizontal scaling
		if (scaling_x < 0)
		{
			flip();
			scaling_x = std::abs(scaling_x);
		}
		if (scaling_x != 1)
		{
			// Create mapping ...
			std::vector<double> mapping_x;
			for (unsigned int u = 0; u < m_bitmap->width(); ++u)
				mapping_x.push_back(u * scaling[k3d::VX]);
			
			// Create a bitmap to hold output ...
			const int width = static_cast<int>(m_bitmap->width() * scaling[k3d::VX]);
			k3d::bitmap* const output = new k3d::bitmap(width, m_bitmap->height());
			
			// Create scanline adapters ...
			scanline<k3d::bitmap::pixel_type> in(m_bitmap->data(), m_bitmap->width());
			scanline<k3d::bitmap::pixel_type> out(output->data(), output->width());
			
			// Resample each scanline ...
			for (unsigned int v = 0; v < m_bitmap->height(); ++v)
			{
				walg_forward(mapping_x, in, out, in.size(), out.size());
				++in; ++out;
			}
			
			// Replace bitmap ...
			m_bitmap = output;
		}

		// Vertical scaling
		if (scaling_y < 0)
		{
			flop();
			scaling_y = std::abs(scaling_y);
		}
		if (scaling_y != 1)
		{
			// Rotate bitmap 90CW
			rotate90CW();

			// Create mapping ...
			std::vector<double> mapping_y;
			for (unsigned int v = 0; v < m_bitmap->width(); ++v)
				mapping_y.push_back(v * scaling[k3d::VY]);

			// Create a bitmap to hold output ...
			const int width = static_cast<int>(m_bitmap->width() * scaling[k3d::VY]);
			k3d::bitmap* const output = new k3d::bitmap(width, m_bitmap->height());

			// Create scanline adapters ...
			scanline<k3d::bitmap::pixel_type> in(m_bitmap->data(), m_bitmap->width());
			scanline<k3d::bitmap::pixel_type> out(output->data(), output->width());

			// Resample each scanline ...
			for (unsigned int u = 0; u < m_bitmap->height(); ++u)
			{
				walg_forward(mapping_y, in, out, in.size(), out.size());
				++in; ++out;
			}

			// Replace bitmap ...
			m_bitmap = output;

			// Rotate bitmap 90CCW
			rotate90CCW();
		}

		update_boundary();
	}

	/// Rotate element
	void rotate(double angle)
	{
		// Calculate smallest arbitary rotation necessary
		const double _45 =  k3d::radians(45.0);
		const double _90 =  k3d::radians(90.0);

		const int num_of_45s = static_cast<int>(angle / _45);
		const double rest_angle = std::fmod(angle, _45);

		if (num_of_45s % 2) // uneven
		{
			rotateOrtho(static_cast<int>(((num_of_45s * _45) / _90) + 1));
			angle = (_45 + rest_angle) - _90;
		}
		else if(num_of_45s) // even
		{
			rotateOrtho(num_of_45s / 2);
			angle = rest_angle;
		}

		double center_x = m_bitmap->width()/2;
		double center_y = m_bitmap->height()/2;

		double top_left_x, top_right_x, bottom_left_x, bottom_right_x;

		// Horizontal pass
		if (angle != 0)
		{
			// Calculate new bitmap size
			const double top_left = ((0-center_x) * cos(angle) - (0-center_y) * sin(angle)) + center_x;
			const double top_right = ((m_bitmap->width()-1-center_x) * cos(angle) - (0-center_y) * sin(angle)) + center_x;
			const double bottom_left = ((0-center_x) * cos(angle) - (m_bitmap->height()-1-center_y) * sin(angle)) + center_x;
			const double bottom_right = ((m_bitmap->width()-1-center_x) * cos(angle) - (m_bitmap->height()-1-center_y) * sin(angle)) + center_x;

			const double min = std::min(std::min(top_left, bottom_left), std::min(top_right, bottom_right));
			const double max = std::max(std::max(top_left, bottom_left), std::max(top_right, bottom_right));

			const int width = static_cast<int>(max - min);
			const double offset_u = std::abs(min);

			// Values to be pluged into next pass
			top_left_x = top_left + offset_u;
			top_right_x = top_right + offset_u;
			bottom_left_x = bottom_left + offset_u;
			bottom_right_x = bottom_right + offset_u;

			// Create a bitmap to hold output ...
			k3d::bitmap* const output = new k3d::bitmap(width, m_bitmap->height());
			
			// Create scanline adapters ...
			scanline<k3d::bitmap::pixel_type> in(m_bitmap->data(), m_bitmap->width());
			scanline<k3d::bitmap::pixel_type> out(output->data(), output->width());
			
			// Resample each scanline ...
			for (unsigned int v = 0; v < m_bitmap->height(); ++v)
			{
				std::vector<double> mapping_x;
				for (unsigned int u = 0; u < m_bitmap->width(); ++u)
					mapping_x.push_back(((u-center_x) * cos(angle) - (v-center_y) * sin(angle)) + center_x + offset_u);
				
				walg_forward(mapping_x, in, out, in.size(), out.size());
				++in; ++out;
			}
			
			// Replace bitmap ...
			m_bitmap = output;
		}

		// Vertical pass
		if (angle != 0)
		{
			// Rotate bitmap 90CW
			rotate90CW();

			// Calculate necessary bitmap size
 			const double top_left = (((top_left_x-center_x) * sin(-angle) + (m_bitmap->width()-1-center_y)) / cos(-angle)) + center_y;
 			const double top_right = (((top_right_x-center_x) * sin(-angle) + (m_bitmap->width()-1-center_y)) / cos(-angle)) + center_y;
  			const double bottom_left = (((bottom_left_x-center_x) * sin(-angle) + (0-center_y)) / cos(-angle)) + center_y;
 			const double bottom_right = (((bottom_right_x-center_x) * sin(-angle) + (0-center_y)) / cos(-angle)) + center_y;

			const double min = std::min(std::min(top_left, bottom_left), std::min(top_right, bottom_right));
			const double max = std::max(std::max(top_left, bottom_left), std::max(top_right, bottom_right));

			const int width = static_cast<int>(max - min);
			const double offset_v = std::abs(min);

			// Create a bitmap to hold output ...
			k3d::bitmap* const output = new k3d::bitmap(width, m_bitmap->height());
			
			// Create scanline adapters ...
			scanline<k3d::bitmap::pixel_type> in(m_bitmap->data(), m_bitmap->width());
			scanline<k3d::bitmap::pixel_type> out(output->data(), output->width());
			
			// Resample each scanline ...
			for (unsigned int x = 0; x < m_bitmap->height(); ++x)
			{
				std::vector<double> mapping_y;
				for (unsigned int v = 0; v < m_bitmap->width(); ++v)
					mapping_y.push_back((((x-center_x) * sin(-angle) + (v-center_y)) / cos(-angle)) + center_y + offset_v);

				walg_forward(mapping_y, in, out, in.size(), out.size());
				++in; ++out;
			}
			
			// Replace bitmap ...
			m_bitmap = output;
			
			// Rotate bitmap 90CCW
			rotate90CCW();
		}

		update_boundary();
	}

	/// Set element position
	void position(const k3d::vector2 position)
	{
		m_position_x = static_cast<int>(std::floor(position[k3d::VX])); // integral translation
		m_position_y = static_cast<int>(std::floor(position[k3d::VY])); // integral translation

		const double translate_x = position[k3d::VX] - m_position_x; // fractional translation
		const double translate_y = position[k3d::VY] - m_position_y; // fractional translation

//		std::cerr << info << "Position (" << m_position_x << ", " << m_position_y << ")" << std::endl;

		// Horizontal translation
		if (translate_x != 0)
		{
//			std::cerr << info << "Horizontal translation by " << translate_x << " pixels" << std::endl;

			// Create mapping ...
			std::vector<double> mapping_x;
			for (unsigned int u = 0; u < m_bitmap->width(); ++u)
				mapping_x.push_back(u + translate_x);

			// Create a bitmap to hold output ...
			k3d::bitmap* const output = new k3d::bitmap(m_bitmap->width(), m_bitmap->height());

			// Create scanline adapters ...
			scanline<k3d::bitmap::pixel_type> in(m_bitmap->data(), m_bitmap->width());
			scanline<k3d::bitmap::pixel_type> out(output->data(), output->width());

			// Resample each scanline ...
			for (unsigned int v = 0; v < m_bitmap->height(); ++v)
			{
				walg_forward(mapping_x, in, out, in.size(), out.size());
				++in; ++out;
			}

			// Replace bitmap ...
			m_bitmap = output;
		}

		// Vertical translation
		if (translate_y != 0)
		{
//			std::cerr << info << "Vertical translation by " << translate_y << " pixels" << std::endl;

			// Rotate bitmap 90CW
			rotate90CW();

			// Create mapping ...
			std::vector<double> mapping_y;
			for (unsigned int v = 0; v < m_bitmap->width(); ++v)
				mapping_y.push_back(v + translate_y);

			// Create a bitmap to hold output ...
			k3d::bitmap* const output = new k3d::bitmap(m_bitmap->width(), m_bitmap->height());

			// Create scanline adapters ...
			scanline<k3d::bitmap::pixel_type> in(m_bitmap->data(), m_bitmap->width());
			scanline<k3d::bitmap::pixel_type> out(output->data(), output->width());

			// Resample each scanline ...
			for (unsigned int u = 0; u < m_bitmap->height(); ++u)
			{
				walg_forward(mapping_y, in, out, in.size(), out.size());
				++in; ++out;
			}

			// Replace bitmap ...
			m_bitmap = output;

			// Rotate bitmap 90CCW
			rotate90CCW();
		}

		update_boundary();
	}

	/// Color transform element
	void color(k3d::matrix4 const color_matrix, bool premultiplied)
	{
		if (color_matrix != k3d::identity3D())
		{
			for (k3d::bitmap::iterator i = m_bitmap->begin(); i != m_bitmap->end(); ++i)
			{
				double I_red = k3d::color_traits<double>::convert((*i).red);
				double I_green = k3d::color_traits<double>::convert((*i).green);
				double I_blue = k3d::color_traits<double>::convert((*i).blue);
				double I_alpha = k3d::color_traits<double>::convert((*i).alpha);

				// Matte divide
				if (premultiplied && I_alpha != 0)
				{
					I_red   = I_red   / I_alpha;
					I_green = I_green / I_alpha;
					I_blue  = I_blue  / I_alpha;
				}
				
				// Color transform
				k3d::vector3 rgb_vector(I_red, I_green, I_blue);
				rgb_vector = rgb_vector * color_matrix;
				
				// Matte multiply (always) ...
				(*i).red = k3d::bitmap::pixel_type::sample_traits::convert(rgb_vector[k3d::VX] * I_alpha);
				(*i).green = k3d::bitmap::pixel_type::sample_traits::convert(rgb_vector[k3d::VY] * I_alpha);
				(*i).blue = k3d::bitmap::pixel_type::sample_traits::convert(rgb_vector[k3d::VZ] * I_alpha);
			}
		}
	}

	/// True tranlation
	void true_translation()
	{
		if (m_position_x != 0 || m_position_y != 0)
		{
			k3d::pixel_size_t left = 0;
			k3d::pixel_size_t right = 0;
			k3d::pixel_size_t top = 0;
			k3d::pixel_size_t bottom = 0;

			if (m_position_x > 0) 
				left = m_position_x * 2;
			else 
				right = std::abs(m_position_x) * 2;
			
			if (m_position_y > 0) 
				bottom = m_position_y * 2;
			else 
				top = std::abs(m_position_y) * 2;
			
			pad(left, right, top, bottom);
			m_position_x = 0;
			m_position_y = 0;
			update_boundary();
		}
	}

private:
	int m_position_x, m_position_y;
	k3d::bitmap* m_bitmap;

	void update_boundary()
	{
		min_x = m_position_x - (m_bitmap->width()/2);
		max_x = m_position_x + (m_bitmap->width()/2);

		min_y = m_position_y - (m_bitmap->height()/2);
		max_y = m_position_y + (m_bitmap->height()/2);
	}

	void pad(const k3d::pixel_size_t left, const k3d::pixel_size_t right, const k3d::pixel_size_t top, const k3d::pixel_size_t bottom)
	{
		k3d::bitmap* const bitmap_padded = new k3d::bitmap(left + m_bitmap->width() + right, top + m_bitmap->height() + bottom);
		bitmap_padding(*m_bitmap, *bitmap_padded, left, right, top, bottom);
		m_bitmap = bitmap_padded;
	}

	void rotate90CW()
	{
		k3d::bitmap* const bitmap_rotated_90CW = new k3d::bitmap(m_bitmap->height(), m_bitmap->width());
		bitmap_rotate90CW(*m_bitmap, *bitmap_rotated_90CW);
		m_bitmap = bitmap_rotated_90CW;
	}

	void rotate90CCW()
	{
		k3d::bitmap* const bitmap_rotated_90CCW = new k3d::bitmap(m_bitmap->height(), m_bitmap->width());
		bitmap_rotate90CCW(*m_bitmap, *bitmap_rotated_90CCW);
		m_bitmap = bitmap_rotated_90CCW;
	}

	void rotate180()
	{
		k3d::bitmap* const bitmap_rotated_180 = new k3d::bitmap(m_bitmap->width(), m_bitmap->height());
		bitmap_rotate180(*m_bitmap, *bitmap_rotated_180);
		m_bitmap = bitmap_rotated_180;
	}

	void flip()
	{
		k3d::bitmap* const bitmap_fliped = new k3d::bitmap(m_bitmap->width(), m_bitmap->height());
		bitmap_flip(*m_bitmap, *bitmap_fliped);
		m_bitmap = bitmap_fliped;
	}

	void flop()
	{
		k3d::bitmap* const bitmap_floped = new k3d::bitmap(m_bitmap->width(), m_bitmap->height());
		bitmap_flop(*m_bitmap, *bitmap_floped);
		m_bitmap = bitmap_floped;
	}

	void rotateOrtho(const int num_of_90s)
	{
		if (!(num_of_90s % 4))
			return;

		if (!(num_of_90s % 3))
		{
			if (num_of_90s > 0)
				rotate90CCW();
			else
				rotate90CW();
			return;
		}

		if (!(num_of_90s % 2))
		{
			rotate180();
			return;
		}

		if (!(num_of_90s % 1))
		{
			if (num_of_90s > 0)
				rotate90CW();
			else
				rotate90CCW();
			return;
		}
	}
};

} // namespace libk3dbitmap

#endif // !MODULE_BITMAP_BITMAP_ELEMENT_H
