/*
** Copyright 2017-2020,2025 Thomas E. Dickey
** Copyright 1997-2010,2017 Free Software Foundation, Inc.
**
** This file is part of TACK.
**
** TACK is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, version 2.
**
** TACK is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with TACK; see the file COPYING.  If not, write to
** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
** Boston, MA 02110-1301, USA
*/

#include <tack.h>

MODULE_ID("$Id: menu.c,v 1.14 2025/04/26 23:31:56 tom Exp $")

/*
   Menu control
 */

static void test_byname(TestMenu *, int *, int *);

TestList *augment_test;
char prompt_string[80];		/* menu prompt storage */

/*
**	menu_prompt()
**
**	Print the menu prompt string.
*/
void
menu_prompt(void)
{
    ptext(&prompt_string[1]);
}

/*
**	menu_test_loop(test-structure, state, control-character)
**
**	This function implements the repeat test function.
*/
static void
menu_test_loop(
		  TestList * test,
		  int *state,
		  int *ch)
{
    int nch;

    if ((test->flags & MENU_REP_MASK) && (augment_test != test)) {
	int p;

	/* set the augment variable (first time only) */
	p = (test->flags >> 8) & 15;
	if ((test->flags & MENU_REP_MASK) == MENU_LM1) {
	    augment = lines - 1;
	} else if ((test->flags & MENU_ONE_MASK) == MENU_ONE) {
	    augment = 1;
	} else if ((test->flags & MENU_LC_MASK) == MENU_lines) {
	    augment = lines * p / 10;
	} else if ((test->flags & MENU_LC_MASK) == MENU_columns) {
	    augment = columns * p / 10;
	} else {
	    augment = 1;
	}
	augment_test = test;
	set_augment_txt();
    }
    do {
	if ((test->flags | *state) & MENU_CLEAR) {
	    put_clear();
	} else if (line_count + test->lines_needed >= lines) {
	    put_clear();
	}
	nch = 0;
	if (test->test_procedure) {
	    /* The procedure takes precedence so I can pass
	       the menu entry as an argument.
	     */
	    can_test(test->caps_done, FLAG_TESTED);
	    can_test(test->caps_tested, FLAG_TESTED);
	    test->test_procedure(test, state, &nch);
	} else if (test->sub_menu) {
	    /* nested menu's */
	    menu_display(test->sub_menu, &nch);
	    *state = 0;
	    if (nch == 'q' || nch == 's') {
		/* Quit and skip are killed here */
		nch = '?';
	    }
	} else {
	    break;		/* cya */
	}
	if (nch == '\r' || nch == '\n' || nch == 'n') {
	    nch = 0;
	    break;
	}
    } while (nch == 'r');
    *ch = nch;
}

/*
**	menu_display(menu-structure, flags)
**
**	This function implements menu control.
*/
void
menu_display(
		TestMenu * menu,
		int *last_ch)
{
    int test_state = 0, run_standard_tests;
    int hot_topic, ch = 0, nch = 0;
    TestList *mt;
    TestList *repeat_tests = NULL;
    int repeat_state = 0;
    int prompt_length;

    prompt_length = (int) strlen(prompt_string);
    if (menu->ident) {
	sprintf(&prompt_string[prompt_length], "/%s", menu->ident);
    }
    hot_topic = menu->default_action;
    run_standard_tests = menu->standard_tests ?
	menu->standard_tests[0] : -1;
    if (!last_ch) {
	last_ch = &ch;
    }
    while (1) {
	if (ch == 0) {
	    /* Display the menu */
	    put_crlf();
	    if (menu->menu_function) {
		/*
		   this function may be used to restrict menu
		   entries.  If used it must print the title.
		 */
		menu->menu_function(menu);
	    } else if (menu->menu_title) {
		ptextln(menu->menu_title);
	    }
	    for (mt = menu->tests; (mt->flags & MENU_LAST) == 0; mt++) {
		if (mt->menu_entry) {
		    ptext(" ");
		    ptextln(mt->menu_entry);
		}
	    }
	    if (menu->standard_tests) {
		ptext(" ");
		ptextln(menu->standard_tests);
		ptextln(" r) repeat test");
		ptextln(" s) skip to next test");
	    }
	    ptextln(" q) quit");
	    ptextln(" ?) help");
	}
	if (ch == 0 || ch == REQUEST_PROMPT) {
	    put_crlf();
	    ptext(&prompt_string[1]);
	    if (hot_topic) {
		ptext(" [");
		putchp(hot_topic);
		ptext("]");
	    }
	    ptext(" > ");
	    /* read a character */
	    ch = wait_here();
	}
	if (ch == '\r' || ch == '\n') {
	    ch = hot_topic;
	}
	if (ch == 'q') {
	    break;
	}
	if (ch == '?') {
	    ch = 0;
	    continue;
	}
	nch = ch;
	ch = 0;
	/* Run one of the standard tests (by request) */
	for (mt = menu->tests; (mt->flags & MENU_LAST) == 0; mt++) {
	    if (mt->menu_entry && (nch == mt->menu_entry[0])) {
		if (mt->flags & MENU_MENU) {
		    test_byname(menu, &test_state, &nch);
		} else {
		    menu_test_loop(mt, &test_state, &nch);
		}
		ch = nch;
		if ((mt->flags & MENU_COMPLETE) && ch == 0) {
		    /* top level */
		    hot_topic = 'q';
		    ch = '?';
		}
	    }
	}
	if (menu->standard_tests && nch == 'r') {
	    menu->resume_tests = repeat_tests;
	    test_state = repeat_state;
	    nch = run_standard_tests;
	}
	if (nch == run_standard_tests) {
	    if (!(mt = menu->resume_tests)) {
		mt = menu->tests;
	    }
	    if (mt->flags & MENU_LAST) {
		mt = menu->tests;
	    }
	    /* Run the standard test suite */
	    for (; (mt->flags & MENU_LAST) == 0;) {
		if ((mt->flags & MENU_NEXT) == MENU_NEXT) {
		    repeat_tests = mt;
		    repeat_state = test_state;
		    nch = run_standard_tests;
		    menu_test_loop(mt, &test_state, &nch);
		    if (nch != 0 && nch != 'n') {
			ch = nch;
			break;
		    }
		    if (test_state & MENU_STOP) {
			break;
		    }
		}
		mt++;
	    }
	    if (ch == 0) {
		ch = hot_topic;
	    }
	    menu->resume_tests = mt;
	    menu->resume_state = test_state;
	    menu->resume_char = ch;

	    if (ch == run_standard_tests) {
		/* pop up a level */
		break;
	    }
	}
    }
    *last_ch = ch;
    prompt_string[prompt_length] = '\0';
}

/*
**	generic_done_message(test_list)
**
**	Print the Done message and request input.
*/
void
generic_done_message(
			TestList * test,
			int *state,
			int *ch)
{
    if (test->caps_done) {
	char done_message[128];

	sprintf(done_message, "(%s) Done ", test->caps_done);
	ptext(done_message);
    } else {
	ptext("Done ");
    }
    *ch = wait_here();
    if (*ch == '\r' || *ch == '\n' || *ch == 'n') {
	*ch = 0;
    }
    if (*ch == 's') {
	*state |= MENU_STOP;
	*ch = 0;
    }
}

/*
**	menu_clear_screen(test, state, ch)
**
**	Just clear the screen.
*/
void
menu_clear_screen(
		     TestList * test GCC_UNUSED,
		     int *state GCC_UNUSED,
		     int *ch GCC_UNUSED)
{
    put_clear();
}

/*
**	menu_reset_init(test, state, ch)
**
**	Send the reset and init strings.
*/
void
menu_reset_init(
		   TestList * test GCC_UNUSED,
		   int *state GCC_UNUSED,
		   int *ch GCC_UNUSED)
{
    reset_init();
    put_crlf();
}

/*
**	subtest_menu(test, state, ch)
**
**	Scan the menu looking for something to execute
**	Return TRUE if we found anything.
*/
int
subtest_menu(
		TestList * test,
		int *state,
		int *ch)
{
    if (*ch) {
	TestList *mt;

	for (mt = test; (mt->flags & MENU_LAST) == 0; mt++) {
	    if (mt->menu_entry && (*ch == mt->menu_entry[0])) {
		*ch = 0;
		menu_test_loop(mt, state, ch);
		return TRUE;
	    }
	}
    }
    return FALSE;
}

/*
**	menu_can_scan(menu-structure)
**
**	Recursively scan the menu tree and find which cap names can be tested.
*/
void
menu_can_scan(
		 const TestMenu * menu)
{
    TestList *mt;

    for (mt = menu->tests; (mt->flags & MENU_LAST) == 0; mt++) {
	can_test(mt->caps_done, FLAG_CAN_TEST);
	can_test(mt->caps_tested, FLAG_CAN_TEST);
	if (!(mt->test_procedure)) {
	    if (mt->sub_menu) {
		menu_can_scan(mt->sub_menu);
	    }
	}
    }
}

/*
**	menu_search(menu-structure, cap)
**
**	Recursively search the menu tree and execute any tests that use cap.
*/
static void
menu_search(
	       TestMenu * menu,
	       int *state,
	       int *ch,
	       char *cap)
{
    TestList *mt;
    int nch;

    for (mt = menu->tests; (mt->flags & MENU_LAST) == 0; mt++) {
	nch = 0;
	if (cap_match(mt->caps_done, cap)
	    || cap_match(mt->caps_tested, cap)) {
	    menu_test_loop(mt, state, &nch);
	}
	if (!(mt->test_procedure)) {
	    if (mt->sub_menu) {
		menu_search(mt->sub_menu, state, &nch, cap);
	    }
	}
	if (*state & MENU_STOP) {
	    break;
	}
	if (nch != 0 && nch != 'n') {
	    *ch = nch;
	    break;
	}
    }
}

/*
**	test_byname(menu, state, ch)
**
**	Get a cap name then run all tests that use that cap.
*/
static void
test_byname(
	       TestMenu * menu,
	       int *state GCC_UNUSED,
	       int *ch)
{
    int test_state = 0;
    char cap[32];

    if (tty_can_sync == SYNC_NOT_TESTED) {
	verify_time();
    }
    ptext("enter name: ");
    read_string(cap, sizeof(cap));
    if (cap[0]) {
	menu_search(menu, &test_state, ch, cap);
    }
    *ch = '?';
}
