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

/*
 * Copyright (C) 2014 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.
 */

#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:

		Window_list          _windows;
		Window_factory_base &_window_factory;
		Dirty_rect mutable   _dirty_rect;

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

		Window_base *_lookup_by_id(unsigned const id)
		{
			for (Window_base *win = _windows.first(); win; win = win->next())
				if (win->id() == id)
					return win;

			return 0;
		}

		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") && attribute(node, "id", 0UL) == id)
					return node;

				if (node.last()) break;
			}

			throw Xml_node::Nonexistent_sub_node();
		}

		void _destroy(Window_base &window)
		{
			_windows.remove(&window);
			_window_factory.destroy(&window);
		}

		/**
		 * Generate window list in reverse order
		 *
		 * After calling this method, the '_windows' list is empty.
		 */
		Window_list _reversed_window_list()
		{
			Window_list reversed;
			while (Window_base *w = _windows.first()) {
				_windows.remove(w);
				reversed.insert(w);
			}
			return reversed;
		}

	public:

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

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

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

			return result;
		}

		inline void update_model(Xml_node root_node);

		bool schedule_animated_windows()
		{
			bool redraw_needed = false;

			for (Window_base *win = _windows.first(); win; win = win->next()) {
				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)
		{
			for (Window_base *win = _windows.first(); win; win = win->next())
				func(*win);
		}

		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.
			 */
			Window_list reversed = _reversed_window_list();

			while (Window_base *win = reversed.first()) {
				win->update_nitpicker_views();
				reversed.remove(win);
				_windows.insert(win);
			}
		}

		void flush()
		{
			while (Window_base *window = _windows.first())
				_destroy(*window);
		}

		Window_base::Hover hover(Point pos) const
		{
			for (Window_base const *win = _windows.first(); win; win = win->next())
				if (win->outer_geometry().contains(pos)) {

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

			return Window_base::Hover();
		}


		/**************************************
		 ** 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);
}


void Decorator::Window_stack::update_model(Genode::Xml_node root_node)
{
	/*
	 * Step 1: Remove windows that are no longer present.
	 */
	for (Window_base *window = _windows.first(), *next = 0; window; window = next) {
		next = window->next();
		try {
			_xml_node_by_window_id(root_node, window->id());
		}
		catch (Xml_node::Nonexistent_sub_node) {
			_dirty_rect.mark_as_dirty(window->outer_geometry());
			_destroy(*window);
		};
	}

	/*
	 * Step 2: Update window properties of already present windows.
	 */
	for (Window_base *window = _windows.first(); window; window = window->next()) {

		/*
		 * After step 1, a Xml_node::Nonexistent_sub_node exception can no
		 * longer occur. All windows remaining in the window stack are present
		 * in the XML model.
		 */
		try {
			Rect const orig_geometry = window->outer_geometry();
			if (window->update(_xml_node_by_window_id(root_node, window->id()))) {
				_dirty_rect.mark_as_dirty(orig_geometry);
				_dirty_rect.mark_as_dirty(window->outer_geometry());
			}
		}
		catch (Xml_node::Nonexistent_sub_node) {
			Genode::error("could not look up window ", window->id(), " in XML model"); }
	}

	/*
	 * Step 3: Add new appearing windows to the window stack.
	 */
	for_each_sub_node(root_node, "window", [&] (Xml_node window_node) {

		unsigned long const id = attribute(window_node, "id", 0UL);

		if (!_lookup_by_id(id)) {

			Window_base *new_window = _window_factory.create(window_node);

			if (new_window) {

				new_window->update(window_node);

				/*
				 * Insert new window in front of all other windows.
				 *
				 * Immediately propagate the new stacking position of the new
				 * window to nitpicker ('update_nitpicker_views'). Otherwise,
				 */
				new_window->stack(Nitpicker::Session::View_handle());

				_windows.insert(new_window);

				_dirty_rect.mark_as_dirty(new_window->outer_geometry());
			}
		}
	});

	/*
	 * Step 4: Adjust window order.
	 */
	Window_base *previous_window = 0;
	Window_base *window          = _windows.first();

	for_each_sub_node(root_node, "window", [&] (Xml_node window_node) {

		if (!window) {
			Genode::error("unexpected end of window list during re-ordering");
			return;
		}

		unsigned long const id = attribute(window_node, "id", 0UL);

		if (window->id() != id) {
			window = _lookup_by_id(id);
			if (!window) {
				Genode::error("window lookup unexpectedly failed during re-ordering");
				return;
			}

			_windows.remove(window);
			_windows.insert(window, previous_window);

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

		previous_window = window;
		window          = window->next();
	});

	/*
	 * 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.
	 */
	Window_list reversed = _reversed_window_list();

	if (Window_base * const back_most = reversed.first()) {

		/* keep back-most window as is */
		reversed.remove(back_most);
		_windows.insert(back_most);

		/* check consistency between window list order and view stacking */
		while (Window_base *w = reversed.first()) {

			Window_base * const neighbor = _windows.first();

			reversed.remove(w);
			_windows.insert(w);

			/* propagate change stacking order to nitpicker */
			if (w->in_front_of(*neighbor))
				continue;

			w->stack(neighbor->frontmost_view());
		}
	}
}

#endif /* _INCLUDE__DECORATOR__WINDOW_STACK_H_ */
