/*
 * \brief  Window-stack handling for decorator
 * \author Norman Feske
 * \date   2014-01-09
 */

/*
 * 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.
 */

#ifndef _INCLUDE__DECORATOR__WINDOW_STACK_H_
#define _INCLUDE__DECORATOR__WINDOW_STACK_H_

/* Genode includes */
#include <nitpicker_session/nitpicker_session.h>
#include <base/log.h>

/* local includes */
#include <decorator/types.h>
#include <decorator/window.h>
#include <decorator/window_factory.h>
#include <decorator/xml_utils.h>

namespace Decorator { class Window_stack; }


class Decorator::Window_stack : public Window_base::Draw_behind_fn
{
	private:

		List_model<Window_base> _windows { };
		Window_factory_base    &_window_factory;
		Dirty_rect mutable      _dirty_rect { };

		unsigned long _front_most_id = ~0UL;

		inline void _draw_rec(Canvas_base &canvas, Window_base const *win,
		                      Rect rect) const;

		static inline
		Xml_node _xml_node_by_window_id(Genode::Xml_node node, unsigned id)
		{
			for (node = node.sub_node("window"); ; node = node.next()) {

				if (node.has_type("window") && node.attribute_value("id", 0UL) == id)
					return node;

				if (node.last()) break;
			}

			throw Xml_node::Nonexistent_sub_node();
		}

		/**
		 * Generate window list in reverse order
		 */
		Reversed_windows _reversed_window_list()
		{
			Reversed_windows reversed { };
			_windows.for_each([&] (Window_base &window) {
				window.prepend_to_reverse_list(reversed); });
			return reversed;
		}

	public:

		Window_stack(Window_factory_base &window_factory)
		:
			_window_factory(window_factory)
		{ }

		void mark_as_dirty(Rect rect) { _dirty_rect.mark_as_dirty(rect); }

		Dirty_rect draw(Canvas_base &canvas) const
		{
			Dirty_rect result = _dirty_rect;

			_dirty_rect.flush([&] (Rect const &rect) {
				_windows.apply_first([&] (Window_base const &first) {
					_draw_rec(canvas, &first, rect); }); });

			return result;
		}

		template <typename FN>
		inline void update_model(Xml_node root_node, FN const &flush);

		bool schedule_animated_windows()
		{
			bool redraw_needed = false;

			_windows.for_each([&] (Window_base const &win) {

				if (win.animated()) {
					_dirty_rect.mark_as_dirty(win.outer_geometry());
					redraw_needed = true;
				}
			});
			return redraw_needed;
		}

		/**
		 * Apply functor to each window
		 *
		 * The functor is called with 'Window_base &' as argument.
		 */
		template <typename FUNC>
		void for_each_window(FUNC const &func) { _windows.for_each(func); }

		void update_nitpicker_views()
		{
			/*
			 * Update nitpicker views in reverse order (back-most first). The
			 * reverse order is important because the stacking position of a
			 * view is propagated by referring to the neighbor the view is in
			 * front of. By starting with the back-most view, we make sure that
			 * each view is always at its final stacking position when
			 * specified as neighbor of another view.
			 */
			Reversed_windows reversed = _reversed_window_list();

			while (Genode::List_element<Window_base> *win = reversed.first()) {
				win->object()->update_nitpicker_views();
				reversed.remove(win);
			}
		}

		Window_base::Hover hover(Point pos) const
		{
			Window_base::Hover result { };

			_windows.for_each([&] (Window_base const &win) {

				if (!result.window_id && win.outer_geometry().contains(pos)) {

					Window_base::Hover const hover = win.hover(pos);
					if (hover.window_id != 0)
						result = hover;
				}
			});

			return result;
		}


		/**************************************
		 ** Window::Draw_behind_fn interface **
		 **************************************/

		void draw_behind(Canvas_base &canvas, Window_base const &window, Rect clip) const override
		{
			_draw_rec(canvas, window.next(), clip);
		}
};


void Decorator::Window_stack::_draw_rec(Decorator::Canvas_base       &canvas,
                                        Decorator::Window_base const *win,
                                        Decorator::Rect               rect) const
{
	Rect clipped;

	/* find next window that intersects with the rectangle */
	for ( ; win && !(clipped = Rect::intersect(win->outer_geometry(), rect)).valid(); )
		win = win->next();

	/* check if we hit the bottom of the window stack */
	if (!win) return;

	/* draw areas around the current window */
	if (Window_base const * const next = win->next()) {
		Rect top, left, right, bottom;
		rect.cut(clipped, &top, &left, &right, &bottom);

		if (top.valid())    _draw_rec(canvas, next, top);
		if (left.valid())   _draw_rec(canvas, next, left);
		if (right.valid())  _draw_rec(canvas, next, right);
		if (bottom.valid()) _draw_rec(canvas, next, bottom);
	}

	/* draw current window */
	win->draw(canvas, clipped, *this);
}


template <typename FN>
void Decorator::Window_stack::update_model(Genode::Xml_node root_node,
                                           FN const &flush_window_stack_changes)
{
	Abandoned_windows _abandoned_windows { };

	struct Update_policy : List_model<Window_base>::Update_policy
	{
		Abandoned_windows   &_abandoned_windows;
		Window_factory_base &_window_factory;
		Dirty_rect          &_dirty_rect;

		Update_policy(Abandoned_windows   &abandoned_windows,
		              Window_factory_base &window_factory,
		              Dirty_rect          &dirty_rect)
		:
			_abandoned_windows(abandoned_windows),
			_window_factory(window_factory),
			_dirty_rect(dirty_rect)
		{ }

		void destroy_element(Window_base &window)
		{
			window.abandon(_abandoned_windows);
		}

		Window_base &create_element(Xml_node node)
		{
			return *_window_factory.create(node);
		}

		void update_element(Window_base &window, Xml_node node)
		{
			Rect const orig_geometry = window.outer_geometry();

			if (window.update(node)) {
				_dirty_rect.mark_as_dirty(orig_geometry);
				_dirty_rect.mark_as_dirty(window.outer_geometry());
			}
		}

		static bool element_matches_xml_node(Window_base const &elem, Xml_node node)
		{
			return elem.id() == node.attribute_value("id", ~0UL);
		}

		static bool node_is_element(Xml_node) { return true; }
	};

	Update_policy policy { _abandoned_windows, _window_factory, _dirty_rect };

	_windows.update_from_xml(policy, root_node);

	unsigned long new_front_most_id = ~0UL;
	if (root_node.has_sub_node("window"))
		new_front_most_id = root_node.sub_node("window").attribute_value("id", ~0UL);

	/*
	 * Propagate changed stacking order to nitpicker
	 *
	 * First, we reverse the window list. The 'reversed' list starts with
	 * the back-most window. We then go throuh each window back to front
	 * and check if its neighbor is consistent with its position in the
	 * window list.
	 */
	Reversed_windows reversed = _reversed_window_list();

	/* return true if window just came to front */
	auto new_front_most_window = [&] (Window_base const &win) {
		return (new_front_most_id != _front_most_id) && (win.id() == new_front_most_id); };

	auto stack_back_most_window = [&] (Window_base &window) {

		if (window.stacked())
			return;

		if (new_front_most_window(window))
			window.stack_front_most();
		else
			window.stack_back_most();

		_dirty_rect.mark_as_dirty(window.outer_geometry());
	};

	auto stack_window = [&] (Window_base &window, Window_base &neighbor) {

		if (window.stacked() && window.in_front_of(neighbor))
			return;

		if (new_front_most_window(window))
			window.stack_front_most();
		else
			window.stack(neighbor.frontmost_view());

		_dirty_rect.mark_as_dirty(window.outer_geometry());
	};

	if (Genode::List_element<Window_base> *back_most = reversed.first()) {

		/* handle back-most window */
		reversed.remove(back_most);
		Window_base &window = *back_most->object();
		stack_back_most_window(window);
		window.stacking_neighbor(Window_base::View_handle());

		Window_base *neighbor = &window;

		/* check consistency between window list order and view stacking */
		while (Genode::List_element<Window_base> *elem = reversed.first()) {

			reversed.remove(elem);

			Window_base &window = *elem->object();
			stack_window(window, *neighbor);
			window.stacking_neighbor(neighbor->frontmost_view());
			neighbor = &window;
		}
	}

	/*
	 * Apply window-creation operations before destroying windows to prevent
	 * flickering.
	 */
	flush_window_stack_changes();

	/*
	 * Destroy abandoned window objects
	 *
	 * This is done after all other operations to avoid flickering whenever one
	 * window is replaced by another one. If we first destroyed the original
	 * one, the window background would appear for a brief moment until the new
	 * window is created. By deferring the destruction of the old window to the
	 * point when the new one already exists, one of both windows is visible at
	 * all times.
	 */
	Genode::List_element<Window_base> *elem = _abandoned_windows.first(), *next = nullptr;
	for (; elem; elem = next) {
		next = elem->next();
		_dirty_rect.mark_as_dirty(elem->object()->outer_geometry());
		_window_factory.destroy(elem->object());
	}

	_front_most_id = new_front_most_id;
}

#endif /* _INCLUDE__DECORATOR__WINDOW_STACK_H_ */
