/*
 * \brief  Animated cube
 * \author Norman Feske
 * \date   2015-06-26
 */

/*
 * Copyright (C) 2015 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode includes */
#include <os/config.h>
#include <polygon_gfx/shaded_polygon_painter.h>
#include <polygon_gfx/interpolate_rgb565.h>
#include <polygon_gfx/textured_polygon_painter.h>
#include <nano3d/dodecahedron_shape.h>
#include <nano3d/cube_shape.h>
#include <nano3d/scene.h>
#include <nano3d/sqrt.h>


template <typename PT>
class Scene : public Nano3d::Scene<PT>
{
	public:

		enum Shape { SHAPE_DODECAHEDRON, SHAPE_CUBE };
		enum Painter { PAINTER_SHADED, PAINTER_TEXTURED };

	private:

		Nitpicker::Area const _size;

		struct Radial_texture
		{
			enum { W = 128, H = 128 };

			unsigned char              alpha[H][W];
			PT                         pixel[H][W];
			Genode::Surface_base::Area size { W, H };
			Genode::Texture<PT>        texture { &pixel[0][0], &alpha[0][0], size };

			Radial_texture()
			{
				int const r_max = W/2 + 5;

				for (unsigned y = 0; y < H; y++) {
					for (unsigned x = 0; x < W; x++) {

						int const dx = x - W/2;
						int const dy = y - H/2;

						int const radius = Nano3d::sqrt(dx*dx + dy*dy);

						alpha[y][x] = 250 - (radius*250)/r_max;

						if ((x&4) ^ (y&4))
							alpha[y][x] = 0;

						int const r = (x*200)/W;
						int const g = (y*200)/H;
						int const b = (x*128)/W + (y*128)/H;

						pixel[y][x] = PT(r, g, b);
					}
				}
			}
		};

		Radial_texture _texture;

		Shape   _shape   = SHAPE_DODECAHEDRON;
		Painter _painter = PAINTER_TEXTURED;

		void _handle_config(unsigned)
		{
			Genode::config()->reload();

			try {
				_shape = SHAPE_DODECAHEDRON;
				if (Genode::config()->xml_node().attribute("shape").has_value("cube"))
					_shape = SHAPE_CUBE;
			} catch (...) { }

			try {
				_painter = PAINTER_TEXTURED;
				if (Genode::config()->xml_node().attribute("painter").has_value("shaded"))
					_painter = PAINTER_SHADED;
			} catch (...) { }
		}

		Genode::Signal_dispatcher<Scene> _config_dispatcher;

	public:

		Scene(Genode::Signal_receiver &sig_rec, unsigned update_rate_ms,
		      Nitpicker::Point pos, Nitpicker::Area size)
		:
			Nano3d::Scene<PT>(sig_rec, update_rate_ms, pos, size), _size(size),
			_config_dispatcher(sig_rec, *this, &Scene::_handle_config)
		{
			Genode::config()->sigh(_config_dispatcher);
			_handle_config(0);
		}

	private:

		Polygon::Shaded_painter _shaded_painter {
			*Genode::env()->heap(), _size.h() };
		Polygon::Textured_painter _textured_painter {
			*Genode::env()->heap(), _size.h() };

		Nano3d::Cube_shape         const _cube         { 7000 };
		Nano3d::Dodecahedron_shape const _dodecahedron { 10000 };

		template <typename SHAPE>
		void _render_shape(Genode::Surface<PT>                   &pixel,
		                   Genode::Surface<Genode::Pixel_alpha8> &alpha,
		                   SHAPE const &shape, unsigned frame,
		                   bool backward_facing)
		{
			typedef Genode::Color Color;

			auto vertices = shape.vertex_array();

			vertices.rotate_x(frame*1);
			vertices.rotate_y(frame*2);
			vertices.rotate_z(frame*3);
			vertices.project(1600, 800);
			vertices.translate(200, 200, 0);

			if (_painter == PAINTER_TEXTURED) {

				typedef Polygon::Textured_painter::Point Textured_point;

				shape.for_each_face([&] (unsigned const vertex_indices[],
				                         unsigned num_vertices) {

					Textured_point points[num_vertices];

					int angle = -frame*4;
					for (unsigned i = 0; i < num_vertices; i++) {

						Nano3d::Vertex const vertex = vertices[vertex_indices[i]];

						Textured_point &point =
							backward_facing ? points[num_vertices - 1 - i]
							                : points[i];

						int const r = _texture.size.w()/2;

						int const u = r + (r*Nano3d::cos_frac16(angle) >> 16);
						int const v = r + (r*Nano3d::sin_frac16(angle) >> 16);

						angle += Nano3d::Sincos_frac16::STEPS / num_vertices;

						point = Textured_point(vertex.x(), vertex.y(), u, v);
					}

					_textured_painter.paint(pixel, alpha, points, num_vertices,
					                        _texture.texture);
				});
			}

			if (_painter == PAINTER_SHADED) {

				typedef Polygon::Shaded_painter::Point Shaded_point;

				shape.for_each_face([&] (unsigned const vertex_indices[],
				                         unsigned num_vertices) {

					Shaded_point points[num_vertices];

					for (unsigned i = 0; i < num_vertices; i++) {

						Nano3d::Vertex const v = vertices[vertex_indices[i]];

						Shaded_point &point =
							backward_facing ? points[num_vertices - 1 - i]
							                : points[i];

						Color const color =
							backward_facing ? Color(i*10, i*10, i*10, 230 - i*18)
							                : Color(240, 10*i, 0, 10 + i*35);

						point = Shaded_point(v.x(), v.y(), color);
					}

					_shaded_painter.paint(pixel, alpha,
					                      points, num_vertices);
				});
			}
		}

	public:

		/**
		 * Scene interface
		 */
		void render(Genode::Surface<PT>                   &pixel,
		            Genode::Surface<Genode::Pixel_alpha8> &alpha) override
		{
			unsigned const frame = (this->elapsed_ms()/10) % 1024;

			if (_shape == SHAPE_DODECAHEDRON) {

				_render_shape(pixel, alpha, _dodecahedron, frame, true);
				_render_shape(pixel, alpha, _dodecahedron, frame, false);

			} else if (_shape == SHAPE_CUBE) {

				_render_shape(pixel, alpha, _cube, frame, true);
				_render_shape(pixel, alpha, _cube, frame, false);
			}
		}
};


int main(int argc, char **argv)
{
	static Genode::Signal_receiver sig_rec;

	enum { UPDATE_RATE_MS = 20 };

	static Scene<Genode::Pixel_rgb565>
		scene(sig_rec, UPDATE_RATE_MS,
		      Nitpicker::Point(-200, -200), Nitpicker::Area(400, 400));

	scene.dispatch_signals_loop(sig_rec);

	return 0;
}
