/* vi:set ts=8 sts=4 sw=4 noet:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

/*
 * tabpanel.c:
 */

#include "vim.h"

#if defined(FEAT_TABPANEL) || defined(PROTO)

static void do_by_tplmode(int tplmode, int col_start, int col_end,
	int *pcurtab_row, int *ptabpagenr);

// set pcurtab_row. don't redraw tabpanel.
#define TPLMODE_GET_CURTAB_ROW	0
// set ptabpagenr. don't redraw tabpanel.
#define TPLMODE_GET_TABPAGENR	1
// redraw tabpanel.
#define TPLMODE_REDRAW		2

#define TPL_FILLCHAR		' '

#define VERT_LEN		1

// tpl_align's values
#define ALIGN_LEFT		0
#define ALIGN_RIGHT		1

static char_u *opt_name = (char_u *)"tabpanel";
static int opt_scope = OPT_LOCAL;
static int tpl_align = ALIGN_LEFT;
static int tpl_columns = 20;
static int tpl_is_vert = FALSE;

typedef struct {
    win_T   *wp;
    win_T   *cwp;
    char_u  *user_defined;
    int	maxrow;
    int	offsetrow;
    int	*prow;
    int	*pcol;
    int	attr;
    int	col_start;
    int	col_end;
} tabpanel_T;

    int
tabpanelopt_changed(void)
{
    char_u	*p;
    int		new_align = ALIGN_LEFT;
    int		new_columns = 20;
    int		new_is_vert = FALSE;
    int		do_equal = 0;

    p = p_tplo;
    while (*p != NUL)
    {
	if (STRNCMP(p, "align:", 6) == 0)
	{
	    p += 6;
	    if (STRNCMP(p, "left", 4) == 0)
	    {
		p += 4;
		new_align = ALIGN_LEFT;
	    }
	    else if (STRNCMP(p, "right", 5) == 0)
	    {
		p += 5;
		new_align = ALIGN_RIGHT;
	    }
	    else
		return FAIL;
	}
	else if (STRNCMP(p, "columns:", 8) == 0 && VIM_ISDIGIT(p[8]))
	{
	    p += 8;
	    new_columns = getdigits(&p);
	}
	else if (STRNCMP(p, "vert", 4) == 0)
	{
	    p += 4;
	    new_is_vert = TRUE;
	}

	if (*p != ',' && *p != NUL)
	    return FAIL;
	if (*p == ',')
	    ++p;
    }

    // Whether all the windows are automatically made the same size
    // when tabpanel size is changed.
    do_equal = p_ea && tpl_columns != new_columns;

    tpl_align = new_align;
    tpl_columns = new_columns;
    tpl_is_vert = new_is_vert;

    shell_new_columns();
    redraw_tabpanel = TRUE;

    if (do_equal)
	win_equal(curwin, FALSE, 0);

    return OK;
}

/*
 * Return the width of tabpanel.
 */
    int
tabpanel_width(void)
{
    if (msg_scrolled != 0)
	return 0;

    switch (p_stpl)
    {
	case 0:
	    return 0;
	case 1:
	    if (first_tabpage->tp_next == NULL)
		return 0;
    }
    if (Columns < tpl_columns)
	return 0;
    else
	return tpl_columns;
}

/*
 * Return the offset of a window considering the width of tabpanel.
 */
    int
tabpanel_leftcol(win_T *wp)
{
    if (cmdline_pum_active() || (wp != NULL && WIN_IS_POPUP(wp)))
	return 0;
    else
	return tpl_align == ALIGN_RIGHT ? 0 : tabpanel_width();
}

/*
 * draw the tabpanel.
 */
    void
draw_tabpanel(void)
{
    int		saved_KeyTyped = KeyTyped;
    int		saved_got_int = got_int;
    int		maxwidth = tabpanel_width();
    int		vs_attr = HL_ATTR(HLF_C);
    int		curtab_row = 0;
#ifndef MSWIN
    int		row = 0;
    int		off = 0;
#endif
int		vsrow = 0;
    int		is_right = tpl_align == ALIGN_RIGHT;

    if (maxwidth == 0)
	return;

#ifndef MSWIN
    // We need this section only for the Vim running on WSL.
    for (row = 0; row < cmdline_row; row++)
    {
	if (is_right)
	    off = LineOffset[row] + Columns - maxwidth;
	else
	    off = LineOffset[row];

	vim_memset(ScreenLines + off, ' ', (size_t)maxwidth * sizeof(schar_T));
	if (enc_utf8)
	    vim_memset(ScreenLinesUC + off, -1,
		(size_t)maxwidth * sizeof(u8char_T));
    }
#endif

    // Reset got_int to avoid build_stl_str_hl() isn't evaluted.
    got_int = FALSE;

    if (tpl_is_vert)
    {
	if (is_right)
	{
	    // draw main contents in tabpanel
	    do_by_tplmode(TPLMODE_GET_CURTAB_ROW, VERT_LEN,
		    maxwidth - VERT_LEN, &curtab_row, NULL);
	    do_by_tplmode(TPLMODE_REDRAW, VERT_LEN, maxwidth, &curtab_row,
		    NULL);
	    // clear for multi-byte vert separater
	    screen_fill(0, cmdline_row, COLUMNS_WITHOUT_TPL(),
		    COLUMNS_WITHOUT_TPL() + VERT_LEN,
		    TPL_FILLCHAR, TPL_FILLCHAR, vs_attr);
	    // draw vert separater in tabpanel
	    for (vsrow = 0; vsrow < cmdline_row; vsrow++)
		screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
			COLUMNS_WITHOUT_TPL(), vs_attr);
	}
	else
	{
	    // draw main contents in tabpanel
	    do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth - VERT_LEN,
		    &curtab_row, NULL);
	    do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth - VERT_LEN,
		    &curtab_row, NULL);
	    // clear for multi-byte vert separater
	    screen_fill(0, cmdline_row, maxwidth - VERT_LEN,
		    maxwidth, TPL_FILLCHAR, TPL_FILLCHAR, vs_attr);
	    // draw vert separater in tabpanel
	    for (vsrow = 0; vsrow < cmdline_row; vsrow++)
		screen_putchar(curwin->w_fill_chars.tpl_vert, vsrow,
			maxwidth - VERT_LEN, vs_attr);
	}
    }
    else
    {
	do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
	do_by_tplmode(TPLMODE_REDRAW, 0, maxwidth, &curtab_row, NULL);
    }

    got_int |= saved_got_int;

    // A user function may reset KeyTyped, restore it.
    KeyTyped = saved_KeyTyped;

    redraw_tabpanel = FALSE;
}

/*
 * Return tabpagenr when clicking and dragging in tabpanel.
 */
    int
get_tabpagenr_on_tabpanel(void)
{
    int		maxwidth = tabpanel_width();
    int		curtab_row = 0;
    int		tabpagenr = 0;

    if (maxwidth == 0)
	return -1;

    do_by_tplmode(TPLMODE_GET_CURTAB_ROW, 0, maxwidth, &curtab_row, NULL);
    do_by_tplmode(TPLMODE_GET_TABPAGENR, 0, maxwidth, &curtab_row,
	    &tabpagenr);

    return tabpagenr;
}

/*
 * Fill tailing area between {start_row} and {end_row - 1}.
 */
    static void
screen_fill_tailing_area(
	int	tplmode,
	int	row_start,
	int	row_end,
	int	col_start,
	int	col_end,
	int	attr)
{
    int is_right = tpl_align == ALIGN_RIGHT;
    if (tplmode == TPLMODE_REDRAW)
	screen_fill(row_start, row_end,
		(is_right ? COLUMNS_WITHOUT_TPL() : 0) + col_start,
		(is_right ? COLUMNS_WITHOUT_TPL() : 0) + col_end,
		TPL_FILLCHAR, TPL_FILLCHAR, attr);
}

/*
 * screen_puts_len() for tabpanel.
 */
    static void
screen_puts_len_for_tabpanel(
	int	    tplmode,
	char_u	    *p,
	int	    len,
	int	    attr,
	tabpanel_T  *pargs)
{
    int		j, k;
    int		chlen;
    int		chcells;
    char_u	buf[IOSIZE];
    char_u	*temp;

    for (j = 0; j < len;)
    {
	if (tplmode != TPLMODE_GET_CURTAB_ROW
		&& pargs->maxrow <= *pargs->prow - pargs->offsetrow)
	    break;

	if (p[j] == '\n' || p[j] == '\r')
	{
	    // fill the tailing area of current row.
	    if (*pargs->prow - pargs->offsetrow >= 0
		    && *pargs->prow - pargs->offsetrow < pargs->maxrow)
		screen_fill_tailing_area(tplmode,
			*pargs->prow - pargs->offsetrow,
			*pargs->prow - pargs->offsetrow + 1,
			*pargs->pcol, pargs->col_end, attr);
	    (*pargs->prow)++;
	    *pargs->pcol = pargs->col_start;
	    j++;
	}
	else
	{
	    if (has_mbyte)
		chlen = (*mb_ptr2len)(p + j);
	    else
		chlen = (int)STRLEN(p + j);

	    for (k = 0; k < chlen; k++)
		buf[k] = p[j + k];
	    buf[chlen] = NUL;
	    j += chlen;

	    // Make all characters printable.
	    temp = transstr(buf);
	    if (temp != NULL)
	    {
		vim_strncpy(buf, temp, sizeof(buf) - 1);
		vim_free(temp);
	    }

	    if (has_mbyte)
		chcells = (*mb_ptr2cells)(buf);
	    else
		chcells = 1;

	    if (pargs->col_end < (*pargs->pcol) + chcells)
	    {
		// fill the tailing area of current row.
		if (*pargs->prow - pargs->offsetrow >= 0
			&& *pargs->prow - pargs->offsetrow < pargs->maxrow)
		    screen_fill_tailing_area(tplmode,
			    *pargs->prow - pargs->offsetrow,
			    *pargs->prow - pargs->offsetrow + 1,
			    *pargs->pcol, pargs->col_end, attr);
		*pargs->pcol = pargs->col_end;

		if (pargs->col_end < chcells)
		    break;
	    }

	    if (*pargs->pcol + chcells <= pargs->col_end)
	    {
		int off = (tpl_align == ALIGN_RIGHT)
			? COLUMNS_WITHOUT_TPL()
			: 0;
		if (TPLMODE_REDRAW == tplmode
			&& (*pargs->prow - pargs->offsetrow >= 0
			&& *pargs->prow - pargs->offsetrow < pargs->maxrow))
		    screen_puts(buf, *pargs->prow - pargs->offsetrow,
			    *pargs->pcol + off, attr);
		*pargs->pcol += chcells;
	    }
	}
    }
}

/*
 * default tabpanel drawing behavior if 'tabpanel' option is empty.
 */
    static void
draw_tabpanel_default(int tplmode, tabpanel_T *pargs)
{
    int		modified;
    int		wincount;
    int		len = 0;
    char_u	buf[2] = { NUL, NUL };

    modified = FALSE;
    for (wincount = 0; pargs->wp != NULL;
	    pargs->wp = pargs->wp->w_next, ++wincount)
	if (bufIsChanged(pargs->wp->w_buffer))
	    modified = TRUE;

    if (modified || wincount > 1)
    {
	if (wincount > 1)
	{
	    vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount);
	    len = (int)STRLEN(NameBuff);
	    screen_puts_len_for_tabpanel(tplmode, NameBuff, len,
#if defined(FEAT_SYN_HL)
		    hl_combine_attr(pargs->attr, HL_ATTR(HLF_T)),
#else
		    pargs->attr,
#endif
		    pargs);
	}
	if (modified)
	{
	    buf[0] = '+';
	    screen_puts_len_for_tabpanel(tplmode, buf, 1, pargs->attr, pargs);
	}

	buf[0] = TPL_FILLCHAR;
	screen_puts_len_for_tabpanel(tplmode, buf, 1, pargs->attr, pargs);
    }

    get_trans_bufname(pargs->cwp->w_buffer);
    shorten_dir(NameBuff);
    len = (int)STRLEN(NameBuff);
    screen_puts_len_for_tabpanel(tplmode, NameBuff, len, pargs->attr, pargs);

    // fill the tailing area of current row.
    if (*pargs->prow - pargs->offsetrow >= 0
	    && *pargs->prow - pargs->offsetrow < pargs->maxrow)
	screen_fill_tailing_area(tplmode, *pargs->prow - pargs->offsetrow,
		*pargs->prow - pargs->offsetrow + 1,
		*pargs->pcol, pargs->col_end, pargs->attr);
    *pargs->pcol = pargs->col_end;
}

/*
 * default tabpanel drawing behavior if 'tabpanel' option is NOT empty.
 */
    static void
draw_tabpanel_userdefined(int tplmode, tabpanel_T *pargs)
{
    char_u	*p;
    int		p_crb_save;
    char_u	buf[IOSIZE];
    stl_hlrec_T *hltab;
    stl_hlrec_T *tabtab;
    int		curattr;
    int		n;

    // Temporarily reset 'cursorbind', we don't want a side effect from moving
    // the cursor away and back.
    p_crb_save = pargs->cwp->w_p_crb;
    pargs->cwp->w_p_crb = FALSE;

    // Make a copy, because the statusline may include a function call that
    // might change the option value and free the memory.
    p = vim_strsave(pargs->user_defined);

    build_stl_str_hl(pargs->cwp, buf, sizeof(buf),
	    p, opt_name, opt_scope,
	    TPL_FILLCHAR, pargs->col_end - pargs->col_start, &hltab, &tabtab);

    vim_free(p);
    pargs->cwp->w_p_crb = p_crb_save;

    curattr = pargs->attr;
    p = buf;
    for (n = 0; hltab[n].start != NULL; n++)
    {
	screen_puts_len_for_tabpanel(tplmode, p, (int)(hltab[n].start - p),
		curattr, pargs);
	p = hltab[n].start;
	if (hltab[n].userhl == 0)
	    curattr = pargs->attr;
	else if (hltab[n].userhl < 0)
	    curattr = syn_id2attr(-hltab[n].userhl);
#ifdef FEAT_TERMINAL
	else if (pargs->wp != NULL && pargs->wp != curwin
		&& bt_terminal(pargs->wp->w_buffer)
		&& pargs->wp->w_status_height != 0)
	    curattr = highlight_stltermnc[hltab[n].userhl - 1];
	else if (pargs->wp != NULL && bt_terminal(pargs->wp->w_buffer)
		&& pargs->wp->w_status_height != 0)
	    curattr = highlight_stlterm[hltab[n].userhl - 1];
#endif
	else if (pargs->wp != NULL && pargs->wp != curwin
		&& pargs->wp->w_status_height != 0)
	    curattr = highlight_stlnc[hltab[n].userhl - 1];
	else
	    curattr = highlight_user[hltab[n].userhl - 1];
    }
    screen_puts_len_for_tabpanel(tplmode, p, (int)STRLEN(p), curattr, pargs);

    // fill the tailing area of current row.
    if (*pargs->prow - pargs->offsetrow >= 0
	    && *pargs->prow - pargs->offsetrow < pargs->maxrow)
	screen_fill_tailing_area(tplmode, *pargs->prow - pargs->offsetrow,
		*pargs->prow - pargs->offsetrow + 1, *pargs->pcol,
		pargs->col_end, curattr);
    *pargs->pcol = pargs->col_end;
}

    static char_u *
starts_with_percent_and_bang(tabpanel_T *pargs)
{
    int		len = 0;
    char_u	*usefmt = p_tpl;

    if (usefmt == NULL)
	return NULL;

    len = (int)STRLEN(usefmt);

    if (len == 0)
	return NULL;

#ifdef FEAT_EVAL
    // if "fmt" was set insecurely it needs to be evaluated in the sandbox
    int	use_sandbox = was_set_insecurely(opt_name, opt_scope);

    // When the format starts with "%!" then evaluate it as an expression and
    // use the result as the actual format string.
    if (len > 1 && usefmt[0] == '%' && usefmt[1] == '!')
    {
	typval_T	tv;
	char_u		*p = NULL;

	tv.v_type = VAR_NUMBER;
	tv.vval.v_number = pargs->cwp->w_id;
	set_var((char_u *)"g:tabpanel_winid", &tv, FALSE);

	p = eval_to_string_safe(usefmt + 2, use_sandbox, FALSE, FALSE);
	if (p != NULL)
	    usefmt = p;

	do_unlet((char_u *)"g:tabpanel_winid", TRUE);
    }
#endif

    return usefmt;
}

/*
 * do something by tplmode for drawing tabpanel.
 */
    static void
do_by_tplmode(
	int	tplmode,
	int	col_start,
	int	col_end,
	int	*pcurtab_row,
	int	*ptabpagenr)
{
    int		attr_tplf = HL_ATTR(HLF_TPLF);
    int		attr_tpls = HL_ATTR(HLF_TPLS);
    int		attr_tpl = HL_ATTR(HLF_TPL);
    int		col = col_start;
    int		row = 0;
    tabpage_T	*tp = NULL;
    typval_T	v;
    tabpanel_T	args;

    args.maxrow = cmdline_row;
    args.offsetrow = 0;
    args.col_start = col_start;
    args.col_end = col_end;

    if (tplmode != TPLMODE_GET_CURTAB_ROW && args.maxrow > 0)
	while (args.offsetrow + args.maxrow <= *pcurtab_row)
	    args.offsetrow += args.maxrow;

    tp = first_tabpage;

    for (row = 0; tp != NULL; row++)
    {
	if (tplmode != TPLMODE_GET_CURTAB_ROW
		&& args.maxrow <= row - args.offsetrow)
	    break;

	col = col_start;

	v.v_type = VAR_NUMBER;
	v.vval.v_number = tabpage_index(tp);
	set_var((char_u *)"g:actual_curtabpage", &v, TRUE);

	if (tp->tp_topframe == topframe)
	{
	    args.attr = attr_tpls;
	    if (tplmode == TPLMODE_GET_CURTAB_ROW)
	    {
		*pcurtab_row = row;
		break;
	    }
	}
	else
	    args.attr = attr_tpl;

	if (tp == curtab)
	{
	    args.cwp = curwin;
	    args.wp = firstwin;
	}
	else
	{
	    args.cwp = tp->tp_curwin;
	    args.wp = tp->tp_firstwin;
	}

	char_u *usefmt = starts_with_percent_and_bang(&args);
	if (usefmt != NULL)
	{
	    char_u	buf[IOSIZE];
	    char_u	*p = usefmt;
	    size_t	i = 0;

	    while (p[i] != NUL)
	    {
		while (p[i] == '\n' || p[i] == '\r')
		{
		    // fill the tailing area of current row.
		    if (row - args.offsetrow >= 0
			    && row - args.offsetrow < args.maxrow)
			screen_fill_tailing_area(tplmode,
				row - args.offsetrow,
				row - args.offsetrow + 1,
				col, args.col_end, args.attr);
		    row++;
		    col = col_start;
		    p++;
		}

		while (p[i] != '\n' && p[i] != '\r' && (p[i] != NUL))
		{
		    if (i + 1 >= sizeof(buf))
			break;
		    buf[i] = p[i];
		    i++;
		}
		buf[i] = NUL;

		args.user_defined = buf;
		args.prow = &row;
		args.pcol = &col;
		draw_tabpanel_userdefined(tplmode, &args);

		p += i;
		i = 0;
	    }
	    if (usefmt != p_tpl)
		VIM_CLEAR(usefmt);
	}
	else
	{
	    args.user_defined = NULL;
	    args.prow = &row;
	    args.pcol = &col;
	    draw_tabpanel_default(tplmode, &args);
	}

	do_unlet((char_u *)"g:actual_curtabpage", TRUE);

	tp = tp->tp_next;

	if ((tplmode == TPLMODE_GET_TABPAGENR)
		&& (mouse_row <= (row - args.offsetrow)))
	{
	    *ptabpagenr = v.vval.v_number;
	    break;
	}
    }

    // fill the area of TabPanelFill.
    screen_fill_tailing_area(tplmode, row - args.offsetrow, args.maxrow,
	    args.col_start, args.col_end, attr_tplf);
}

#endif // FEAT_TABPANEL
