/*
 * \brief  Fader for a nitpicker client
 * \author Norman Feske
 * \date   2014-09-08
 */

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

/* Genode includes */
#include <base/component.h>
#include <nitpicker_session/connection.h>
#include <base/attached_rom_dataspace.h>
#include <base/attached_ram_dataspace.h>
#include <os/texture.h>
#include <os/surface.h>
#include <os/pixel_rgb565.h>
#include <os/pixel_alpha8.h>
#include <os/static_root.h>
#include <util/reconstructible.h>
#include <nitpicker_gfx/texture_painter.h>
#include <util/lazy_value.h>
#include <timer_session/connection.h>

/* local includes */
#include <alpha_dither_painter.h>

namespace Nit_fader {

	class Main;
	class Src_buffer;
	class Dst_buffer;
	class Framebuffer_session_component;
	class Nitpicker_session_component;

	typedef Genode::Surface_base::Area  Area;
	typedef Genode::Surface_base::Point Point;
	typedef Genode::Surface_base::Rect  Rect;

	using Genode::size_t;
	using Genode::Xml_node;
	using Genode::Dataspace_capability;
	using Genode::Attached_ram_dataspace;
	using Genode::Texture;
	using Genode::Surface;
	using Genode::Reconstructible;
	using Genode::Constructible;

	typedef Genode::Pixel_rgb565 Pixel_rgb565;
	typedef Genode::Pixel_alpha8 Pixel_alpha8;
}


/**
 * Buffer handed out to our client as virtual framebuffer
 */
class Nit_fader::Src_buffer
{
	private:

		typedef Pixel_rgb565 Pixel;

		bool             const _use_alpha;
		Attached_ram_dataspace _ds;
		Texture<Pixel>         _texture;

		static size_t _needed_bytes(Area size)
		{
			/* account for alpha channel, input mask, and pixels */
			return size.count() * (1 + 1 + sizeof(Pixel));
		}

	public:

		/**
		 * Constructor
		 */
		Src_buffer(Genode::Env &env, Area size, bool use_alpha)
		:
			_use_alpha(use_alpha),
			_ds(env.ram(), env.rm(), _needed_bytes(size)),
			_texture(_ds.local_addr<Pixel>(),
			         _ds.local_addr<unsigned char>() + size.count()*sizeof(Pixel),
			         size)
		{ }

		Dataspace_capability dataspace() { return _ds.cap(); }

		Texture<Pixel> const &texture() const { return _texture; }

		bool use_alpha() const { return _use_alpha; }
};


class Nit_fader::Dst_buffer
{
	private:

		Genode::Attached_dataspace _ds;
		Area                       _size;

		Surface<Pixel_rgb565> _pixel_surface { _ds.local_addr<Pixel_rgb565>(), _size };

		Surface<Pixel_alpha8> _alpha_surface
		{
			_ds.local_addr<Pixel_alpha8>() + _size.count()*sizeof(Pixel_rgb565),
			_size
		};

	public:

		Dst_buffer(Genode::Env &env, Dataspace_capability ds_cap, Area size)
		:
			_ds(env.rm(), ds_cap), _size(size)
		{
			/* initialize input-mask buffer */
			unsigned char *input_mask_buffer = _ds.local_addr<unsigned char>()
			                                 + _size.count()*(1 + sizeof(Pixel_rgb565));

			Genode::memset(input_mask_buffer, 0xff, _size.count());
		}

		Surface<Pixel_rgb565> &pixel_surface() { return _pixel_surface; }
		Surface<Pixel_alpha8> &alpha_surface() { return _alpha_surface; }
};


class Nit_fader::Framebuffer_session_component
:
	public Genode::Rpc_object<Framebuffer::Session>
{
	private:

		Genode::Env &_env;

		Nitpicker::Connection &_nitpicker;
		Src_buffer            &_src_buffer;

		Constructible<Dst_buffer> _dst_buffer { };


		Lazy_value<int> _fade { };

	public:

		/**
		 * Constructor
		 */
		Framebuffer_session_component(Genode::Env &env,
		                              Nitpicker::Connection &nitpicker,
		                              Src_buffer            &src_buffer)
		:
			_env(env), _nitpicker(nitpicker), _src_buffer(src_buffer)
		{ }

		void dst_buffer(Dataspace_capability ds_cap, Area size)
		{
			_dst_buffer.construct(_env, ds_cap, size);
		}

		void transfer_src_to_dst_pixel(Rect const rect)
		{
			if (!_dst_buffer.constructed())
				return;

			_dst_buffer->pixel_surface().clip(rect);

			Texture_painter::paint(_dst_buffer->pixel_surface(),
			                       _src_buffer.texture(),
			                       Genode::Color(0, 0, 0),
			                       Point(0, 0),
			                       Texture_painter::SOLID,
			                       false);
		}

		void transfer_src_to_dst_alpha(Rect const rect)
		{
			if (!_dst_buffer.constructed())
				return;

			_dst_buffer->alpha_surface().clip(rect);

			if (_src_buffer.use_alpha()) {
				Alpha_dither_painter::paint(_dst_buffer->alpha_surface(), rect, _fade,
				                            _src_buffer.texture());
			} else {
				Alpha_dither_painter::paint(_dst_buffer->alpha_surface(), rect, _fade);
			}
		}

		Area size()
		{
			return _dst_buffer.constructed() ? _dst_buffer->pixel_surface().size()
			                                 : Area();
		}

		bool animate(unsigned num_frames)
		{
			for (unsigned i = 0; i < num_frames; i++)
				_fade.animate();

			Rect const rect(Point(0, 0), size());

			transfer_src_to_dst_alpha(rect);

			_nitpicker.framebuffer()->refresh(rect.x1(), rect.y1(), rect.w(), rect.h());

			/* keep animating as long as the destination value is not reached */
			return _fade != _fade.dst();
		}

		void fade(int fade_value, int steps) { _fade.dst(fade_value, steps); }

		bool visible() const { return _fade != 0; }



		/************************************
		 ** Framebuffer::Session interface **
		 ************************************/

		Dataspace_capability dataspace() override
		{
			return _src_buffer.dataspace();
		}

		Framebuffer::Mode mode() const override
		{
			return _nitpicker.framebuffer()->mode();
		}

		void mode_sigh(Genode::Signal_context_capability sigh) override
		{
			_nitpicker.framebuffer()->mode_sigh(sigh);
		}

		void refresh(int x, int y, int w, int h) override
		{
			transfer_src_to_dst_pixel(Rect(Point(x, y), Area(w, h)));
			transfer_src_to_dst_alpha(Rect(Point(x, y), Area(w, h)));

			_nitpicker.framebuffer()->refresh(x, y, w, h);
		}

		void sync_sigh(Genode::Signal_context_capability sigh) override
		{
			_nitpicker.framebuffer()->sync_sigh(sigh);
		}
};


class Nit_fader::Nitpicker_session_component
:
	public Genode::Rpc_object<Nitpicker::Session>
{
	private:

		typedef Nitpicker::View_capability      View_capability;
		typedef Nitpicker::Session::View_handle View_handle;

		Genode::Env &_env;

		Reconstructible<Src_buffer> _src_buffer { _env, Area(1, 1), false };

		Nitpicker::Connection _nitpicker { _env };

		Genode::Attached_ram_dataspace _command_ds {
			_env.ram(), _env.rm(), sizeof(Nitpicker::Session::Command_buffer) };

		Nitpicker::Session::Command_buffer &_commands =
			*_command_ds.local_addr<Nitpicker::Session::Command_buffer>();

		Framebuffer_session_component _fb_session { _env, _nitpicker, *_src_buffer };

		Framebuffer::Session_capability _fb_cap { _env.ep().manage(_fb_session) };

		Nitpicker::Session::View_handle _view_handle { };

		bool _view_visible = false;
		Rect _view_geometry { };

		void _update_view_visibility()
		{
			if (!_view_handle.valid() || (_view_visible == _fb_session.visible()))
				return;

			typedef Nitpicker::Session::Command Command;

			if (_fb_session.visible())
				_nitpicker.enqueue<Command::Geometry>(_view_handle, _view_geometry);
			else
				_nitpicker.enqueue<Command::Geometry>(_view_handle, Rect());

			_nitpicker.execute();

			_view_visible = _fb_session.visible();
		}

	public:

		/**
		 * Constructor
		 */
		Nitpicker_session_component(Genode::Env &env) : _env(env)
		{ }

		/**
		 * Destructor
		 */
		~Nitpicker_session_component()
		{
			_env.ep().dissolve(_fb_session);
		}

		bool animate(unsigned num_frames)
		{
			bool const keep_animating =  _fb_session.animate(num_frames);

			_update_view_visibility();

			return keep_animating;
		}

		void fade(int fade_value, int steps)
		{
			_fb_session.fade(fade_value, steps);
		}


		/**********************************
		 ** Nitpicker::Session interface **
		 **********************************/

		Framebuffer::Session_capability framebuffer_session() override
		{
			return _fb_cap;
		}

		Input::Session_capability input_session() override
		{
			return _nitpicker.input_session();
		}

		View_handle create_view(View_handle parent) override
		{
			_view_handle = _nitpicker.create_view(parent);
			_update_view_visibility();
			return _view_handle;
		}

		void destroy_view(View_handle handle) override
		{
			return _nitpicker.destroy_view(handle);
		}

		View_handle view_handle(View_capability view_cap,
		                        View_handle handle) override
		{
			return _nitpicker.view_handle(view_cap, handle);
		}

		View_capability view_capability(View_handle handle) override
		{
			return _nitpicker.view_capability(handle);
		}

		void release_view_handle(View_handle handle) override
		{
			_nitpicker.release_view_handle(handle);
		}

		Dataspace_capability command_dataspace() override
		{
			return _command_ds.cap();
		}

		void execute() override
		{
			for (unsigned i = 0; i < _commands.num(); i++) {

				Nitpicker::Session::Command command = _commands.get(i);

				bool forward_command = true;

				if (command.opcode == Nitpicker::Session::Command::OP_GEOMETRY) {

					/* remember view geometry as defined by the client */
					_view_geometry = command.geometry.rect;

					if (!_view_visible)
						forward_command = false;
				}

				if (forward_command)
					_nitpicker.enqueue(command);
			}
			_fb_session.transfer_src_to_dst_pixel(Rect(Point(0, 0), _fb_session.size()));
			_fb_session.transfer_src_to_dst_alpha(Rect(Point(0, 0), _fb_session.size()));
			return _nitpicker.execute();
		}

		Framebuffer::Mode mode() override
		{
			return _nitpicker.mode();
		}

		void mode_sigh(Genode::Signal_context_capability sigh) override
		{
			_nitpicker.mode_sigh(sigh);
		}

		void buffer(Framebuffer::Mode mode, bool use_alpha) override
		{
			Area const size(mode.width(), mode.height());

			_src_buffer.construct(_env, size, use_alpha);

			_nitpicker.buffer(mode, true);

			_fb_session.dst_buffer(_nitpicker.framebuffer()->dataspace(), size);
		}

		void focus(Genode::Capability<Session> focused) override
		{
			_nitpicker.focus(focused);
		}
};


struct Nit_fader::Main
{
	Genode::Env &env;

	Genode::Attached_rom_dataspace config { env, "config" };

	Timer::Connection timer { env };

	enum { PERIOD = 20 };

	unsigned long alpha = 0;

	Genode::uint64_t curr_frame() const { return timer.elapsed_ms() / PERIOD; }

	Genode::uint64_t last_frame = 0;

	void handle_config_update();

	Genode::Signal_handler<Main> config_handler
	{
		env.ep(), *this, &Main::handle_config_update
	};

	Nitpicker_session_component nitpicker_session { env };

	Genode::Static_root<Nitpicker::Session> nitpicker_root
	{
		env.ep().manage(nitpicker_session)
	};

	void handle_timer()
	{
		Genode::uint64_t frame = curr_frame();
		if (nitpicker_session.animate(frame - last_frame))
			timer.trigger_once(PERIOD);

		last_frame = frame;
	}

	Genode::Signal_handler<Main> timer_handler
	{
		env.ep(), *this, &Main::handle_timer
	};

	Main(Genode::Env &env) : env(env)
	{
		config.sigh(config_handler);

		timer.sigh(timer_handler);

		/* apply initial config */
		handle_config_update();

		env.parent().announce(env.ep().manage(nitpicker_root));
	}
};


void Nit_fader::Main::handle_config_update()
{
	config.update();

	Genode::Xml_node config_xml = config.xml();

	unsigned long new_alpha = alpha;
	if (config_xml.has_attribute("alpha"))
		config_xml.attribute("alpha").value(new_alpha);

	/* respond to state change */
	if (new_alpha != alpha) {

		int const steps = (new_alpha > alpha) ? 20 : 50;
		nitpicker_session.fade(280*new_alpha, steps);

		alpha = new_alpha;

		/* schedule animation */
		last_frame = curr_frame();
		timer.trigger_once(PERIOD);
	}
}


/***************
 ** Component **
 ***************/

void Component::construct(Genode::Env &env) {
		static Nit_fader::Main desktop(env); }
