/*
 * \brief  Nitpicker pointer with support for VirtualBox-defined shapes
 * \author Norman Feske
 * \author Christian Helmuth
 * \author Christian Prochaska
 * \date   2014-07-02
 */

/*
 * 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 <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <os/pixel_alpha8.h>
#include <os/pixel_rgb565.h>
#include <os/pixel_rgb888.h>
#include <os/surface.h>
#include <os/texture_rgb888.h>
#include <nitpicker_session/connection.h>
#include <report_rom/report_service.h>
#include <pointer/dither_painter.h>
#include <pointer/shape_report.h>

/* local includes */
#include "rom_registry.h"
#include "big_mouse.h"

namespace Pointer { class Main; }


template <typename PT>
void convert_default_pointer_data_to_pixels(PT *pixel, Nitpicker::Area size)
{
	unsigned char *alpha = (unsigned char *)(pixel + size.count());

	for (unsigned y = 0; y < size.h(); y++) {
		for (unsigned x = 0; x < size.w(); x++) {

			/* the source is known to be in RGB565 format */
			Genode::Pixel_rgb565 src =
				*(Genode::Pixel_rgb565 *)(&big_mouse.pixels[y][x]);

			unsigned const i = y*size.w() + x;
			pixel[i] = PT(src.r(), src.g(), src.b());
			alpha[i] = src.r() ? 255 : 0;
		}
	}
}


class Pointer::Main : public Rom::Reader
{
	private:

		typedef Genode::String<128> String;

		Genode::Env &_env;

		Genode::Attached_rom_dataspace _config { _env, "config" };

		bool _verbose = _config.xml().attribute_value("verbose", false);

		Nitpicker::Connection _nitpicker { _env };

		Nitpicker::Session::View_handle _view = _nitpicker.create_view();

		bool _default_pointer_visible = false;

		Nitpicker::Area _current_pointer_size { };

		Genode::Dataspace_capability _pointer_ds { };

		void _resize_nitpicker_buffer_if_needed(Nitpicker::Area pointer_size);
		void _show_default_pointer();
		void _update_pointer();

		/* custom shape support */

		bool _shapes_enabled = _config.xml().attribute_value("shapes", false);

		bool _xray = false;

		Genode::Constructible<Genode::Attached_rom_dataspace> _hover_ds { };
		Genode::Constructible<Genode::Attached_rom_dataspace> _xray_ds  { };

		Genode::Signal_handler<Main> _hover_signal_handler {
			_env.ep(), *this, &Main::_handle_hover };
		Genode::Signal_handler<Main> _xray_signal_handler {
			_env.ep(), *this, &Main::_handle_xray };

		Genode::Sliced_heap _sliced_heap { _env.ram(), _env.rm() };

		Rom::Registry _rom_registry { _sliced_heap, _env.ram(), _env.rm(), *this };

		Report::Root _report_root { _env, _sliced_heap, _rom_registry, _verbose };

		Genode::Session_label _hovered_label { };

		Genode::Attached_ram_dataspace _texture_pixel_ds { _env.ram(), _env.rm(),
		                                                   Pointer::MAX_WIDTH  *
		                                                   Pointer::MAX_HEIGHT *
		                                                   sizeof(Genode::Pixel_rgb888) };

		Genode::Attached_ram_dataspace _texture_alpha_ds { _env.ram(), _env.rm(),
		                                                   Pointer::MAX_WIDTH  *
		                                                   Pointer::MAX_HEIGHT };

		void _show_shape_pointer(Shape_report &shape_report);
		void _handle_hover();
		void _handle_xray();

	public:

		/**
		 * Reader interface
		 */
		void notify_module_changed() override
		{
			_update_pointer();
		}

		void notify_module_invalidated() override
		{
			_update_pointer();
		}

		Main(Genode::Env &);
};


void Pointer::Main::_resize_nitpicker_buffer_if_needed(Nitpicker::Area pointer_size)
{
	if (pointer_size == _current_pointer_size)
		return;

	Framebuffer::Mode const mode { (int)pointer_size.w(),
	                               (int)pointer_size.h(),
	                               Framebuffer::Mode::RGB565 };

	_nitpicker.buffer(mode, true /* use alpha */);

	_pointer_ds = _nitpicker.framebuffer()->dataspace();

	_current_pointer_size = pointer_size;
}


void Pointer::Main::_show_default_pointer()
{
	/* only draw default pointer if not already drawn */
	if (_default_pointer_visible)
		return;

	Nitpicker::Area const pointer_size { big_mouse.w, big_mouse.h };

	try {
		_resize_nitpicker_buffer_if_needed(pointer_size);
	} catch (...) {
		Genode::error(__func__, ": could not resize the pointer buffer "
		              "for ", pointer_size.w(), "x", pointer_size.h(), " pixels");
		return;
	}

	Genode::Attached_dataspace ds { _env.rm(), _pointer_ds };

	convert_default_pointer_data_to_pixels(ds.local_addr<Genode::Pixel_rgb565>(),
	                                       pointer_size);
	_nitpicker.framebuffer()->refresh(0, 0, pointer_size.w(), pointer_size.h());

	Nitpicker::Rect geometry(Nitpicker::Point(0, 0), pointer_size);
	_nitpicker.enqueue<Nitpicker::Session::Command::Geometry>(_view, geometry);
	_nitpicker.execute();

	_default_pointer_visible = true;
}


void Pointer::Main::_show_shape_pointer(Shape_report &shape_report)
{
	Nitpicker::Area  shape_size;
	Nitpicker::Point shape_hot;

	if (shape_report.visible) {

		shape_size = Nitpicker::Area(shape_report.width, shape_report.height);
		shape_hot = Nitpicker::Point((int)-shape_report.x_hot, (int)-shape_report.y_hot);

		try {
			_resize_nitpicker_buffer_if_needed(shape_size);
		} catch (...) {
			error(__func__, ": could not resize the pointer buffer "
			      "for ", shape_size, " pixels");
			throw;
		}

		using namespace Genode;

		/* import shape into texture */

		Texture<Pixel_rgb888>
			texture(_texture_pixel_ds.local_addr<Pixel_rgb888>(),
			        _texture_alpha_ds.local_addr<unsigned char>(),
			        shape_size);

		for (unsigned int y = 0; y < shape_size.h(); y++) {

			/* import the RGBA-encoded line into the texture */
			unsigned char *shape = shape_report.shape;
			unsigned char *line  = &shape[y * shape_size.w() * 4];
			texture.rgba(line, shape_size.w(), y);
		}

		/* draw texture */

		Attached_dataspace ds { _env.rm(), _pointer_ds };

		Pixel_rgb565 *pixel = ds.local_addr<Pixel_rgb565>();

		Pixel_alpha8 *alpha =
			reinterpret_cast<Pixel_alpha8 *>(pixel + shape_size.count());

		Surface<Pixel_rgb565> pixel_surface(pixel, shape_size);
		Surface<Pixel_alpha8> alpha_surface(alpha, shape_size);

		Dither_painter::paint(pixel_surface, texture);
		Dither_painter::paint(alpha_surface, texture);
	}

	_nitpicker.framebuffer()->refresh(0, 0, shape_size.w(), shape_size.h());

	Nitpicker::Rect geometry(shape_hot, shape_size);
	_nitpicker.enqueue<Nitpicker::Session::Command::Geometry>(_view, geometry);
	_nitpicker.execute();

	_default_pointer_visible = false;
}


void Pointer::Main::_update_pointer()
{
	if (!_shapes_enabled || _xray) {
		_show_default_pointer();
		return;
	}

	try {

		Rom::Readable_module &shape_module =
			_rom_registry.lookup(*this, _hovered_label);

		try {
			Shape_report shape_report { 0, 0, 0, 0, 0, 0 };

			shape_module.read_content(*this, (char*)&shape_report, sizeof(shape_report));

			/* show default pointer on invisible/empty/invalid shape report */
			if (!shape_report.visible ||
			    ((shape_report.width == 0) ||
			     (shape_report.height == 0) ||
			     (shape_report.width > MAX_WIDTH) ||
			     (shape_report.height > MAX_HEIGHT)))
			    throw Genode::Exception();

			_show_shape_pointer(shape_report);

		} catch (...) {
			_rom_registry.release(*this, shape_module);
			throw;
		}

		_rom_registry.release(*this, shape_module);

	} catch (...) {
		_show_default_pointer();
	}
}


void Pointer::Main::_handle_hover()
{
	_hover_ds->update();
	if (!_hover_ds->valid())
		return;

	/* read new hover information from nitpicker's hover report */
	try {
		Genode::Xml_node node(_hover_ds->local_addr<char>());

		Genode::Session_label hovered_label {
			node.attribute_value("label", String()) };

		hovered_label = hovered_label.prefix();

		if (_verbose)
			Genode::log("hovered_label: ", hovered_label);

		/* update pointer if hovered label changed */
		if (hovered_label != _hovered_label) {
			_hovered_label  = hovered_label;
			_update_pointer();
		}
	}
	catch (...) {
		Genode::warning("could not parse hover report");
	}
}


void Pointer::Main::_handle_xray()
{
	_xray_ds->update();
	if (!_xray_ds->valid())
		return;

	try {
		Genode::Xml_node node(_xray_ds->local_addr<char>());

		bool xray = node.attribute_value("enabled", false);

		/* update pointer if xray status changed */
		if (xray != _xray) {
			_xray = xray;
			_update_pointer();
		}
	}
	catch (...) {
		Genode::warning("could not parse xray report");
	}
}

Pointer::Main::Main(Genode::Env &env) : _env(env)
{
	/*
	 * Try to allocate the Nitpicker buffer for the maximum supported
	 * pointer size to let the user know right from the start if the
	 * RAM quota is too low.
	 */
	Framebuffer::Mode const mode { Pointer::MAX_WIDTH, Pointer::MAX_HEIGHT,
	                               Framebuffer::Mode::RGB565 };

	_nitpicker.buffer(mode, true /* use alpha */);

	if (_shapes_enabled) {
		try {
			_hover_ds.construct(_env, "hover");
			_hover_ds->sigh(_hover_signal_handler);
			_handle_hover();
		} catch (Genode::Rom_connection::Rom_connection_failed) {
			Genode::warning("Could not open ROM session for \"hover\".",
		                	" This ROM is used for custom pointer shape support.");
		}

		try {
			_xray_ds.construct(_env, "xray");
			_xray_ds->sigh(_xray_signal_handler);
			_handle_xray();
		} catch (Genode::Rom_connection::Rom_connection_failed) {
			Genode::warning("Could not open ROM session for \"xray\".",
		                	" This ROM is used for custom pointer shape support.");
		}
	}

	typedef Nitpicker::Session::View_handle View_handle;
	_nitpicker.enqueue<Nitpicker::Session::Command::To_front>(_view, View_handle());
	_nitpicker.execute();

	_update_pointer();

	if (_shapes_enabled) {
		/* announce 'Report' service */
		env.parent().announce(env.ep().manage(_report_root));
	}
}


void Component::construct(Genode::Env &env) { static Pointer::Main main(env); }
