#ifndef EL__BFU_DIALOG_H
#define EL__BFU_DIALOG_H

#include "bfu/style.h"
#include "bfu/widget.h"
#include "main/timer.h" /* timer_id_T */
#include "terminal/terminal.h"
#include "terminal/window.h" /* dialog_data->win->term is so common that... */
#include "util/memlist.h"

#ifdef __cplusplus
extern "C" {
#endif

struct dialog_data;
struct term_event;

struct dialog_layout {
	/* Whether to adjust the dialog width to the maximal width. If not set
	 * only use required width. */
	unsigned int maximize_width:1;
	/* Whether to leave one blank line at the top of the dialog. */
	unsigned int padding_top:1;
	/* Whether to adjust width to fit datalen of _first_ widget. */
	unsigned int fit_datalen:1;
	/* Whether to float grouped widgets on one line. */
	unsigned int float_groups:1;
	/* Whether to draw all the fancy frames and backgrounds. */
	unsigned int only_widgets:1;
};

enum dlg_refresh_code {
	/* The dialog should be redrawn and refreshed again */
	REFRESH_DIALOG,
	/* The dialog should be canceled */
	REFRESH_CANCEL,
	/* The dialog should not be redrawn but refreshed again */
	REFRESH_NONE,
	/* The dialog should not be redrawn or refreshed again */
	REFRESH_STOP,
};

typedef enum dlg_refresh_code (*dialog_refresh_handler_T)(struct dialog_data *, void *);

struct dialog_refresh {
	dialog_refresh_handler_T handler;
	void *data;
	timer_id_T timer;
};

struct dialog {
	char *title;
	void *udata;
	void *udata2;
	struct dialog_refresh *refresh;

	void (*layouter)(struct dialog_data *);
	widget_handler_status_T (*handle_event)(struct dialog_data *);
	void (*abort)(struct dialog_data *);

	struct dialog_layout layout;

	/** How many widgets have been added to this dialog.
	 * The #widgets array may be larger than this if some
	 * of the intended widgets have not yet been added.  */
	int number_of_widgets;

	struct widget widgets[1]; /* must be at end of struct */

	/* There may be additional data after widgets[].  The size and
	 * format of the additional data is specific to each dialog.
	 * Dialogs typically place the buffer of each WIDGET_FIELD
	 * widget somewhere in this additional-data area so that the
	 * buffer need not be allocated and freed separately.
	 *
	 * When constructing a dialog, give the size of the additional
	 * data as a parameter to sizeof_dialog() and calloc_dialog().
	 * Then, use get_dialog_offset() to get the address of the
	 * additional data.  */
};

/** Gets the amount of memory needed for a dialog.
 *
 * @param n
 *   How many widgets there will be in the dialog.
 * @param add_size
 *   The size of the additional data, in bytes.
 *
 * @return
 *   The total amount of memory needed for struct dialog, the widgets,
 *   and the additional data.  This however does not include struct
 *   dialog_data.
 *
 * struct dialog already reserves memory for one widget.  */
#define sizeof_dialog(n, add_size) \
	(sizeof(struct dialog) + ((n) - 1) * sizeof(struct widget) + (add_size))

/** Allocates and clears memory for a dialog.
 *
 * @param n
 *   How many widgets there will be in the dialog.
 * @param add_size
 *   The size of the additional data, in bytes.
 *
 * @return
 *   struct dialog *; or NULL if out of memory.
 *
 * This macro sets dialog.number_of_widgets = 0.
 * The caller can then add widgets to the dialog
 * until dialog.number_of_widgets reaches @a n.  */
#define calloc_dialog(n, add_size) ((struct dialog *) mem_calloc(1, sizeof_dialog(n, add_size)))

/** Gets the address of the additional data of a dialog.
 *
 * @param dlg
 *  struct dialog *dlg; the dialog that carries the additional data. 
 * @param n
 *  For how many widgets the dialog was allocated; i.e. the
 *  @c n parameter of sizeof_dialog() and calloc_dialog().
 *  This macro does not read dialog.number_of_widgets because
 *  that is typically still zero when this macro is used.
 *
 * @return
 *  The address of the additional data.  */
#define get_dialog_offset(dlg, n) \
	(((char *) dlg) + sizeof_dialog(n, 0))

#define dialog_has_refresh(dlg_data) \
	((dlg_data)->dlg->refresh && (dlg_data)->dlg->refresh->timer != TIMER_ID_UNDEF)

static inline int
dialog_max_width(struct terminal *term)
{
	int width = term->width * DIALOG_WIDTH_RATIO - 2 * DIALOG_LB;

	int_bounds(&width, 1, int_max(term->width - 2 * DIALOG_LB, 1));

	return width;
}

static inline int
dialog_max_height(struct terminal *term)
{
	int height = term->height * DIALOG_HEIGHT_RATIO - 2 * DIALOG_TB;

	int_bounds(&height, 1, int_max(term->height - 2 * DIALOG_TB, 1));

	return height;
}

struct dialog_data {
	struct window *win;
	struct dialog *dlg;
	struct memory_list *ml;

	/** Size and location of the dialog box, excluding the drop shadow.
	 * This includes the outer border and the frame.
	 * The coordinates are relative to the terminal.  */
	struct el_box box;

	/** Size and location of the widget area and the inner border.
	 * This is the area in which widgets can be drawn.
	 * The frame of the dialog box is drawn around this area,
	 * and the outer border is around the frame.
	 * The coordinates are relative to the terminal.  */
	struct el_box real_box;

	/** Vertical scrolling of the widget area of the dialog box.
	 * Widget Y screen coordinate = widget_data.box.y - dialog_data.y.
	 * Initially, this is 0 and the coordinate system of the widget area
	 * matches the coordinate system of the terminal.
	 * Horizontal scrolling of dialog boxes has not been implemented.  */
	int y;

	int number_of_widgets;
	int selected_widget_id;
	struct term_event *term_event;

	struct widget_data widgets_data[1]; /* must be at end of struct */
};


struct dialog_data *do_dialog(struct terminal *, struct dialog *,
			      struct memory_list *);

/* Draws the dialog background and shadow */
void draw_dialog(struct dialog_data *dlg_data, int width, int height);

/* Draws the dialog borders and layouter if @layout is non zerro. Finally all
 * dialog items are redisplayed. */
void redraw_dialog(struct dialog_data *dlg_data, int layout);

widget_handler_status_T ok_dialog(struct dialog_data *, struct widget_data *);
widget_handler_status_T cancel_dialog(struct dialog_data *, struct widget_data *);
widget_handler_status_T clear_dialog(struct dialog_data *, struct widget_data *);
int check_dialog(struct dialog_data *);
int update_dialog_data(struct dialog_data *);
void generic_dialog_layouter(struct dialog_data *dlg_data);
void refresh_dialog(struct dialog_data *, dialog_refresh_handler_T handler, void *data);

#define selected_widget(dlg_data) (&(dlg_data)->widgets_data[(dlg_data)->selected_widget_id])

void select_widget(struct dialog_data *dlg_data, struct widget_data *widget_data);
struct widget_data *select_widget_by_id(struct dialog_data *dlg_data, int i);

#define before_widgets(dlg_data) (&(dlg_data)->widgets_data[-1])
#define end_of_widgets(dlg_data) (&(dlg_data)->widgets_data[(dlg_data)->number_of_widgets])
#define first_widget(dlg_data) (&(dlg_data)->widgets_data[0])
#define last_widget(dlg_data) (&(dlg_data)->widgets_data[(dlg_data)->number_of_widgets - 1])

#define foreach_widget(dlg_data, widget_data) \
	for ((widget_data) = first_widget(dlg_data); \
	     (widget_data) != end_of_widgets(dlg_data); \
	     (widget_data)++)

#define foreach_widget_back(dlg_data, widget_data) \
	for ((widget_data) = last_widget(dlg_data); \
	     (widget_data) != before_widgets(dlg_data); \
	     (widget_data)--)

#define is_selected_widget(dlg_data, widget_data) ((widget_data) == selected_widget(dlg_data))

#define add_dlg_end(dlg, n)						\
	do {								\
		if (0 && (n));						\
		assert(n == (dlg)->number_of_widgets);			\
	} while (0)

#ifdef __cplusplus
}
#endif

#endif
