// -*- C++ -*-
/**
 * \file LayoutEngine.C
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Angus Leeming
 *
 * Full author contact details are available in file CREDITS.
 *
 * A generic layout engine.
 *
 * Draws heavily on the GTK+ files gtkbox.[ch], gtkhbox.[ch],
 * for both inspiration and implementation,
 * and from which this notice is taken:
 *
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */

#include <config.h>

#include "LayoutEngine.h"
#include "lyx_forms.h"

#include <boost/assert.hpp>
#include <boost/bind.hpp>

using boost::bind;
using boost::shared_ptr;

using std::for_each;

namespace lyx {
namespace frontend {


bool BoxList::empty() const
{
	return data_.empty();
}


BoxList::size_type BoxList::size() const
{
	return data_.size();
}


void BoxList::clear()
{
	data_.clear();
}


shared_ptr<Box> BoxList::push_back(Box const & box)
{
	data_.push_back(shared_ptr<Box>(new Box(box)));
	return data_.back();
}


BoxList::iterator BoxList::begin()
{
	return data_.begin();
}


BoxList::iterator BoxList::end()
{
	return data_.end();
}


BoxList::const_iterator BoxList::begin() const
{
	return data_.begin();
}


BoxList::const_iterator BoxList::end() const
{
	return data_.end();
}


BoxList::iterator BoxList::erase(iterator where)
{
	return data_.erase(where);
}


BoxList::iterator BoxList::erase(iterator begin, iterator end)
{
	return data_.erase(begin, end);
}


Box::Orientation Box::default_orientation_ = Box::Vertical;
Box::Packing Box::default_packing_ = Box::Shrink;


Box::Box(dimension_t min_w, dimension_t min_h)
	: visible_(true),
	  min_w_(min_w),
	  min_h_(min_h),
	  w_(min_w),
	  h_(min_h),
	  x_(0),
	  y_(0),
	  orientation_(default_orientation_),
	  packing_(default_packing_),
	  prefered_visibility_(Visible)
{}


void Box::setMinimumDimensions(dimension_t min_w, dimension_t min_h)
{
	min_w_ = min_w;
	min_h_ = min_h;
}


Box::Orientation Box::orientation() const
{
	return orientation_;
}


void Box::set(Orientation o)
{
	orientation_ = o;
}


Box::Orientation Box::defaultOrientation()
{
	return default_orientation_;
}


void Box::setDefault(Orientation o)
{
	default_orientation_ = o;
}


Box::Packing Box::packing() const
{
	return packing_;
}


void Box::set(Packing p)
{
	packing_ = p;
}


Box::Packing Box::defaultPacking()
{
	return default_packing_;
}


void Box::setDefault(Packing p)
{
	default_packing_ = p;
}


bool Box::expandable() const
{
	if (!visible_)
		return false;

	if (packing_ == Expand)
		return true;

	BoxList::const_iterator it = children_.begin();
	BoxList::const_iterator const end = children_.end();
	for (; it != end; ++it) {
		if ((*it)->visible() && (*it)->packing() == Expand)
			return true;
	}

	return false;
}


Box::PreferedVisibility Box::preferedVisibility() const
{
	return prefered_visibility_;
}


void Box::set(PreferedVisibility pv)
{
	prefered_visibility_ = pv;
	if (pv == Invisible && visible_)
		hide();
}


bool Box::visible() const
{
	return visible_;
}


void Box::show()
{
	if (prefered_visibility_ == Invisible)
		return;

	visible_ = true;

	for_each(children_.begin(), children_.end(),
		bind(&Box::show, _1));
}


void Box::hide()
{
	visible_ = false;

	for_each(children_.begin(), children_.end(),
		 bind(&Box::hide, _1));
}


BoxList & Box::children()
{
	return children_;
}


BoxList const & Box::children() const
{
	return children_;
}


Box::dimension_t Box::width() const
{
	return w_;
}


Box::dimension_t Box::height() const
{
	return h_;
}


Box::dimension_t Box::xorigin() const
{
	return x_;
}


Box::dimension_t Box::yorigin() const
{
	return y_;
}


void Box::updateMetrics()
{
	shrinkMetrics();
	expandMetrics(x_, y_, w_, h_);
}


void Box::shrinkMetrics()
{
	dimension_t width = 0;
	dimension_t height = 0;

	BoxList::iterator it = children_.begin();
	BoxList::iterator const end = children_.end();
	for (; it != end; ++it) {
		if (!(*it)->visible())
			continue;

		(*it)->shrinkMetrics();
		dimension_t child_width = (*it)->width();
		dimension_t child_height = (*it)->height();

		if (orientation_ == Horizontal) {
			width += child_width;
			height = std::max(height, child_height);
		} else {
			width = std::max(width, child_width);
			height += child_height;
		}
	}

	w_ = visible_ ? std::max(min_w_, width) : 0;
	h_ = visible_ ? std::max(min_h_, height) : 0;
}


void Box::expandMetrics(dimension_t x_in, dimension_t y_in,
			dimension_t w_avail, dimension_t h_avail)
{
	x_ = x_in;
	y_ = y_in;
	w_ = w_avail;
	h_ = h_avail;

	if (orientation_ == Vertical)
		expandVbox(x_in, y_in, w_avail, h_avail);
	else
		expandHbox(x_in, y_in, w_avail, h_avail);
}


void Box::expandHbox(dimension_t x_in, dimension_t y_in,
		     dimension_t w_avail, dimension_t h_avail)
{
	int nvisible_children = 0;
	int nexpanded_children = 0;
	dimension_t w_fixed = 0;

	BoxList::const_iterator cit = children_.begin();
	BoxList::const_iterator const cend = children_.end();
	for (; cit != cend; ++cit) {
		if ((*cit)->visible()) {
			nvisible_children += 1;
			if ((*cit)->expandable())
				nexpanded_children += 1;
			else
				w_fixed += (*cit)->width();
		}
	}

	if (nvisible_children == 0)
		return;

	dimension_t width = 0;
	dimension_t extra = 0;
	if (nexpanded_children > 0) {
		width = w_avail - w_fixed;
		extra = width / nexpanded_children;
	}

	dimension_t x_child = x_in;
	dimension_t y_child = y_in;
	dimension_t h_child = h_avail;

	BoxList::iterator it = children_.begin();
	BoxList::iterator const end = children_.end();
	for (; it != end; ++it) {
		if (!(*it)->visible())
			continue;

		dimension_t w_child = (*it)->width();
		if ((*it)->expandable()) {
			if (nexpanded_children == 1)
				w_child = std::max(w_child, width);
			else
				w_child = std::max(w_child, extra);

			nexpanded_children -= 1;
			width -= w_child;
		}

		(*it)->expandMetrics(x_child, y_child, w_child, h_child);
		x_child += w_child;
	}
}


void Box::expandVbox(dimension_t x_in, dimension_t y_in,
		     dimension_t w_avail, dimension_t h_avail)
{
	int nvisible_children = 0;
	int nexpanded_children = 0;
	dimension_t h_fixed = 0;

	BoxList::const_iterator cit = children_.begin();
	BoxList::const_iterator const cend = children_.end();
	for (; cit != cend; ++cit) {
		if ((*cit)->visible()) {
			nvisible_children += 1;
			if ((*cit)->expandable())
				nexpanded_children += 1;
			else
				h_fixed += (*cit)->height();
		}
	}

	if (nvisible_children == 0)
		return;

	dimension_t height = 0;
	dimension_t extra = 0;
	if (nexpanded_children > 0) {
		height = h_avail - h_fixed;
		extra = height / nexpanded_children;
	}

	dimension_t x_child = x_in;
	dimension_t y_child = y_in;
	dimension_t w_child = w_avail;

	BoxList::iterator it = children_.begin();
	BoxList::iterator const end = children_.end();
	for (; it != end; ++it) {
		if (!(*it)->visible())
			continue;

		dimension_t h_child = (*it)->height();
		if ((*it)->expandable()) {
			if (nexpanded_children == 1)
				h_child = std::max(h_child, height);
			else
				h_child = std::max(h_child, extra);
			nexpanded_children -= 1;
			height -= h_child;
		}

		(*it)->expandMetrics(x_child, y_child, w_child, h_child);
		y_child += h_child;
	}
}


shared_ptr<Box> WidgetMap::add(FL_OBJECT * ob, BoxList & container,
		     dimension_t min_w, dimension_t min_h)
{
	shared_ptr<Box> box = container.push_back(Box(min_w, min_h));
	widgets_[ob] = box;
	return box;
}


void WidgetMap::updateMetrics() const
{
	DataMap::const_iterator it = widgets_.begin();
	DataMap::const_iterator const end = widgets_.end();
	for (; it != end; ++it) {
		FL_OBJECT * ob = it->first;
		Box & box = *it->second;

		if (box.visible()) {
			fl_set_object_geometry(ob,
					       box.xorigin(), box.yorigin(),
					       box.width(), box.height());
			if (!ob->visible)
				fl_show_object(ob);
		} else {
			if (ob->visible)
				fl_hide_object(ob);
		}
	}
}


shared_ptr<Box>
embed(FL_OBJECT * ob, BoxList & container, WidgetMap & widgets, int bw)
{
	container.push_back(Box(0, bw));
	shared_ptr<Box> middle = container.push_back(Box(0, 0));
	middle->set(Box::Horizontal);
	container.push_back(Box(0, bw));

	middle->children().push_back(Box(bw, 0));
	shared_ptr<Box> center = widgets.add(ob, middle->children(), 0, 0);
	middle->children().push_back(Box(bw, 0));

	return center;
}

} // namespace frontend
} // namespace lyx
