/* $Id: layout.c,v 1.7 2009-05-16 11:48:47 nicm Exp $ */

/*
 * Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

#include <string.h>

#include "tmux.h"

/*
 * Layout functions: second argument (int) is 1 if definitely the /only/ change
 * has been the active pane has changed. If 0 then panes, active pane or both
 * may have changed.
 */
void	layout_manual(struct window *, int);
void	layout_active_only(struct window *, int);
void	layout_even_horizontal(struct window *, int);
void	layout_even_vertical(struct window *, int);
void	layout_left_vertical(struct window *, int);

const struct {
	const char	*name;
	void		(*fn)(struct window *, int);
} layouts[] = {
	{ "manual", layout_manual },
	{ "active-only", layout_active_only },
	{ "even-horizontal", layout_even_horizontal },
	{ "even-vertical", layout_even_vertical },
	{ "left-vertical", layout_left_vertical },
};

const char *
layout_name(struct window *w)
{
	return (layouts[w->layout].name);
}

int
layout_lookup(const char *name)
{
	u_int	i;
	int	matched = -1;

	for (i = 0; i < nitems(layouts); i++) {
		if (strncmp(layouts[i].name, name, strlen(name)) == 0) {
			if (matched != -1)	/* ambiguous */
				return (-1);
			matched = i;
		}
	}

	return (matched);
}

int
layout_select(struct window *w, u_int layout)
{
	if (layout > nitems(layouts) - 1 || layout == w->layout)
		return (-1);
	w->layout = layout;

	if (w->layout == 0) {
		/* XXX Special-case manual. */
		window_fit_panes(w);
		window_update_panes(w);
	}
	layout_refresh(w, 0);
	return (0);
}

void
layout_next(struct window *w)
{
	w->layout++;
	if (w->layout > nitems(layouts) - 1) {
		w->layout = 0;
		/* XXX Special-case manual. */
		window_fit_panes(w);
		window_update_panes(w);
	}
	layout_refresh(w, 0);
}

void
layout_previous(struct window *w)
{
	if (w->layout == 0)
		w->layout = nitems(layouts) - 1;
	else
		w->layout--;
	if (w->layout == 0) {
		/* XXX Special-case manual. */
		window_fit_panes(w);
		window_update_panes(w);
	}
	layout_refresh(w, 0);
}

void
layout_refresh(struct window *w, unused int active_changed)
{
	layouts[w->layout].fn(w, active_changed);
	server_redraw_window(w);
}

void
layout_manual(unused struct window *w, unused int active_changed)
{
}

void
layout_active_only(struct window *w, unused int active_changed)
{
	struct window_pane	*wp;

	TAILQ_FOREACH(wp, &w->panes, entry) {
		if (wp == w->active) {
			wp->flags &= ~PANE_HIDDEN;
			wp->xoff = wp->yoff = 0;
			window_pane_resize(wp, w->sx, w->sy);
		} else
			wp->flags |= PANE_HIDDEN;
	}
}

void
layout_even_horizontal(struct window *w, int active_changed)
{
	struct window_pane	*wp;
	u_int			 i, n, width, xoff;

	if (active_changed)
		return;

	/* Get number of panes. */
	n = window_count_panes(w);
	if (n == 0)
		return;

	/* How many can we fit? */
	if (w->sx / n < PANE_MINIMUM) {
		width = PANE_MINIMUM;
		n = w->sx / PANE_MINIMUM;
	} else
		width = w->sx / n;

	/* Fit the panes. */
	i = xoff = 0;
	TAILQ_FOREACH(wp, &w->panes, entry) {
		if (i > n) {
			wp->flags |= PANE_HIDDEN;
			continue;
		}
		wp->flags &= ~PANE_HIDDEN;

		wp->xoff = xoff;
		wp->yoff = 0;
		if (i != n - 1)
 			window_pane_resize(wp, width - 1, w->sy);
		else
 			window_pane_resize(wp, width, w->sy);

		i++;
		xoff += width;
	}

	/* Any space left? */
	while (xoff++ < w->sx) {
		wp = TAILQ_LAST(&w->panes, window_panes);
		window_pane_resize(wp, wp->sx + 1, wp->sy);
	}
}

void
layout_even_vertical(struct window *w, int active_changed)
{
	struct window_pane	*wp;
	u_int			 i, n, height, yoff;

	if (active_changed)
		return;

	/* Get number of panes. */
	n = window_count_panes(w);
	if (n == 0)
		return;

	/* How many can we fit? */
	if (w->sy / n < PANE_MINIMUM) {
		height = PANE_MINIMUM;
		n = w->sy / PANE_MINIMUM;
	} else
		height = w->sy / n;

	/* Fit the panes. */
	i = yoff = 0;
	TAILQ_FOREACH(wp, &w->panes, entry) {
		if (i > n) {
			wp->flags |= PANE_HIDDEN;
			continue;
		}
		wp->flags &= ~PANE_HIDDEN;

		wp->xoff = 0;
		wp->yoff = yoff;
		if (i != n - 1)
 			window_pane_resize(wp, w->sx, height - 1);
		else
 			window_pane_resize(wp, w->sx, height);

		i++;
		yoff += height;
	}

	/* Any space left? */
	while (yoff++ < w->sy) {
		wp = TAILQ_LAST(&w->panes, window_panes);
		window_pane_resize(wp, wp->sx, wp->sy + 1);
	}
}

void
layout_left_vertical(struct window *w, int active_changed)
{
	struct window_pane	*wp;
	u_int			 i, n, height, yoff;

	if (active_changed)
		return;

	/* Get number of panes. */
	n = window_count_panes(w);
	if (n == 0)
		return;

	/* Need >1 pane and minimum columns; if fewer, display active only. */
	if (n == 1 || w->sx < 82 + PANE_MINIMUM) {
		layout_active_only(w, active_changed);
		return;
	}
	n--;

	/* How many can we fit, not including first? */
	if (w->sy / n < PANE_MINIMUM) {
		height = PANE_MINIMUM;
		n = w->sy / PANE_MINIMUM;
	} else
		height = w->sy / n;

	/* Fit the panes. */
	i = yoff = 0;
	TAILQ_FOREACH(wp, &w->panes, entry) {
		if (wp == TAILQ_FIRST(&w->panes)) {
			wp->xoff = 0;
			wp->yoff = 0;
			window_pane_resize(wp, 81, w->sy);
			wp->flags &= ~PANE_HIDDEN;
			continue;
		}

		if (i > n) {
			wp->flags |= PANE_HIDDEN;
			continue;
		}
		wp->flags &= ~PANE_HIDDEN;

		wp->xoff = 82;
		wp->yoff = yoff;
		if (i != n - 1)
 			window_pane_resize(wp, w->sx - 82, height - 1);
		else
 			window_pane_resize(wp, w->sx - 82, height);

		i++;
		yoff += height;
	}

	/* Any space left? */
	while (yoff++ < w->sy) {
		wp = TAILQ_LAST(&w->panes, window_panes);
		while (wp != NULL && wp == TAILQ_FIRST(&w->panes))
			wp = TAILQ_PREV(wp, window_panes, entry);
		if (wp == NULL)
			break;
		window_pane_resize(wp, wp->sx, wp->sy + 1);
	}
}