/**
 *
 * File:
 *   options.ycp
 *
 * Module:
 *   Sound
 *
 * Summary:
 *   Module for setting options passed to the module
 *
 * String corrections by Christian Steinruecken <cstein@suse.de>, 2001/08/01
 *
 *
 * Authors:
 *   Dan Vesely <dan@suse.cz>
 *   Dan Meszaros <dmeszar@suse.cz>
 *
 * $Id: options.ycp 20376 2004-11-22 17:55:48Z lslezak $
 *
 * parameters: 1st: parameter list
 *
 */

{

    textdomain "sound";

    import "Sound";
    import "Wizard";
    import "Label";
    import "Popup";

    include "sound/ui.ycp"; // ReallyAbort
    include "sound/routines.ycp"; // get_module_params, restore_mod_params

    /**
     * checks whether param #1 is if type of param #2
     * @param value value
     * @param type expected type. one of {int, string}
     * @param poss if poss is nonempty, checks if value is one of them
     * returns error message, empty string if ok
     * @return string error message
     */
    define string check_value (string value, string type, list<string> poss) ``{
        if (type == "int")
        {
            if (tolower(substring (value, 0, 2)) == "0x")	// hex number
            {
                string rest = tolower(substring (value, 2));
                if (filterchars(rest, "0123456789abcdef") != rest)
                {
                      // To translators: popup message, wrong value
                      return sformat (_("The value: %1 must be a number"), value);
                }
            }
            else                // decimal number
            {
                string rest = value;
                if (substring (value, 0, 1) == "-")      // negative number
                {
                    rest = substring (value, 1);
                }
                if (filterchars(rest, "0123456789") != rest)
                {
                    // To translators: popup message, wrong value
                    return sformat (_("The value: %1 must be a number"), value);
                }
            }
        }
        else if (type == "string")
        {
            if (filterchars (tolower (value), "abcdefghijklmnopqrstuvwxyz_0123456789-") != value)
            {

                integer wrong_pos = findfirstnotof (tolower (value), "abcdefghijklmnopqrstuvwxyz_0123456789-");
                string wrong_char = substring (value, wrong_pos, 1);
                if (wrong_char == " ")
                {
                    // To translators: "space" means blank character
                    wrong_char = wrong_char + " " + _("(space)");
                }
                // To translators: popup message, wrong value
                return  sformat (_("String cannot contain: %1"), wrong_char);
            }
        }

        // now check for poss list
        if (size (poss) > 0)
        {
            list poss_list = [];

            if (type != "int")
            {
                poss_list = maplist (string v, poss, ``(value == v));
            }

            if (!contains (poss_list, true))
            {
                // popup message: wrong value; %1 is list of right values
                return sformat (_("The value must be one of
%1"), mergestring (poss,","));
            }
        }

        return "";  // ok :-)
    }

    /**
     *  creates itemized table entries,
     *  @param lm list of maps to take keys from
     *  @param lk list of keys to look for in 1st param
     *  @return list of items
     */
    define list<term> create_table (list lm, list lk) ``{
	list<term> res = [];
	integer pos = 0;
	foreach (map<string,any> m, (list<map<string,any> >) lm, ``{
	    res = add(res, `item(`id(pos),
		m[lk[0]:""]:"",
		m[lk[1]:""]:"",
		m[lk[2]:""]:""
	    ));
	    pos = pos + 1;
	});
	return res;
    }

    /**
     * parses string (eg. '{{0,2},{0,100,20}}') to a list
     * (in this case [0,1,2,0,20,40,60,80,100])
     * @param input input string
     * @return list of possible values
     */
    define list parse_bracket(string input) ``{

        string inner	= input; // substring(input, 1, size(input)-2);
        integer siz	= size(inner);
        integer pos	= 0;
        integer oldpos	= 0;
        list output	= [];

        while (pos < siz)
        {
            if (substring(inner, pos, 1) == "{")
            {
                oldpos = pos;
                pos = pos + 1;
                integer cnter = 1;
                // let's find the pair bracket for '{'
                while ((cnter > 0) && (pos < siz))
                {
                    if (substring(inner, pos, 1) == "}")
                    {
                        cnter = cnter - 1;
                    }
                    else if (substring(inner, pos, 1) == "{")
                    {
                        cnter = cnter + 1;
                    }
                    pos = pos + 1;
                }
                output = add (output,
		    parse_bracket(substring(inner, oldpos+1, pos - oldpos-2)));
                pos = pos + 1;
            }
            else
            {
                oldpos = pos;
                while ((substring(inner, pos, 1) != ",") && pos < siz)
                {
                    pos = pos + 1;
                }
                output = add(output, substring(inner, oldpos, pos - oldpos));
                pos = pos + 1;
            }
        }
        return output;
    }

    /**
     * gets an 'modules.generic_string' like options description string
     * and returns a list of possible values
     * @param input string
     * @return list of values
     */
    define list<string> string2vallist(string input) ``{
	boolean parse_error	= false;
	list l			= flatten ((list<list>)parse_bracket(input));

	l = maplist(list<any> e, (list<list<any> >) l, ``{
	    if (size(e) == 0)
	    {
		return [];
	    }
	    if (size(e) == 1)
	    {
		return e;
	    }

	    integer step = 1;
	    if (size(e) == 3)
	    {
		step = tointeger (e[2]:"1");
	    }
	    if (step < 1)
	    {
		parse_error = true;
		return [];
	    }

	    boolean hex = false;
	    if (regexpmatch (e[1]:"", "^-[0-9]*$") ||
		regexpmatch (e[1]:"", "^[0-9]*$"))
	    {
		hex = false;
	    }
	    else if (regexpmatch (e[1]:"", "^-0x[0-9a-fA-F]*") ||
		     regexpmatch (e[1]:"", "^0x[0-9a-fA-F]*"))
	    {
		hex = true;
	    }
	    else
	    {
		if (contains(["Disabled", "Enabled"], e[1]:""))
		{
		    return [ e[0]:"" ];
		}
	    }

	    integer from = tointeger (e[0]:"");
	    integer to = tointeger (e[1]:"");

	    if (((to - from) / step) > 50)
	    {
		// too many values :(
		parse_error = true;
		return [];
	    }

	    list<string> outlist = [];

	    while (from <= to)
	    {
		if (hex)
		{
		    outlist = add(outlist, tohexstring(from));
		}
		else
		{
		    outlist = add(outlist, sformat("%1", from));
		}
		from = from + step;
	    }
	    return outlist;
	});

	l = flatten((list<list>) l);

	if (parse_error)
	{
	    y2debug("parse error: %1", input);
	}

	return (list<string>) l;
    }

    /**
     * default widget when there are no known values
     * @return term widget
     */
    define term defWidget() ``{
	// label text
	return `Label(_("Possible value:\nnot known"));
    }

    /**
     * widget for choosing one value from list
     * @param vals string with values eg. "12,3,4,6"
     * @param default string default value (preselected in combo)
     * @return term combobox widget
     */
    define term gen_list(list<string> vals, string default) ``{
	list<term> vls = maplist (string e, vals, ``(`item(`id(e), e, default == e)));
	// combo box label
	return `ComboBox(`id(`poss),`opt(`notify), _("&Possible value:"), vls);
    }

    /**
     * when the selected option in table is changed, we need to update
     * combo with values
     * @param values list of values
     * @param default item
     * @return void
     */
    define void updatePossibleValues(string values, string default) ``{
	list l = string2vallist(values);
	if (l == [])
	{
	    UI::ReplaceWidget(`id(`reppoint), defWidget());
	    return;
	}
	term widg = gen_list((list<string>)l, default);

	UI::ReplaceWidget(`id(`reppoint), widg);
    }

    /**
     * Returns description of card module option
     * @param arg type of arg can be string or list
     * @return string description
     */
    define string getDescr(any arg) ``{
	if (is(arg, string))
	{
	    return (string) arg;
	}
	if (is(arg, list))
	{
	    list larg = (list) arg;
	    return sformat(larg[0]:"%1", larg[1]:"");
	}
	return _("No description available");
    }

    /**
     *	UI controls for options setting dialog
     *
     *  @param cardlabel card model string
     *  @param itemized_descriptions option list (preformated using
     *		'create_table' with tripples: description, name, value)
     *  @param current_option_name initially selected item name
     *  @param current_option_value value of current option
     *  @return term options dialog contents
     *  @see options#OptionsDialog
     */
    define term OptionsCon (string cardlabel, list itemized_descriptions,
		    string current_option_name, string current_option_value) ``{

	return `HBox (
	    `HSpacing (3),
	    `VBox (
		`VSquash(`Top(`Label (`opt(`outputField), cardlabel))),
                `VSpacing (),
		`Table (`id(`table), `opt (`notify, `immediate), `header(
		    // Table header -- option description
		    _("Description"),
		    // Table header -- option name
		    _("Option"),
		    // Table header -- value of an option
		    `Right(_("Value"))), itemized_descriptions),
                `VSpacing (0.5),
		`HBox (
		    `TextEntry (`id (`des), `opt(`notify), current_option_name,
			current_option_value),
		    `VBox(
			`Label(""),
			`HBox(
			    // push button label - set option value
			    `PushButton (`id (`set), _("&Set")),
			    // restore original option values
			    `PushButton (`id (`reset), _("R&eset all"))
			)
		    )
		),
		`Left(`ReplacePoint(`id(`reppoint), `Label(""))),
                `VSpacing (0.5)
            ),
	    `HSpacing (3)
        );
    }

    /**
     * displays dialog with card options
     *
     * @param cardlabel string label for the card
     * @param opts list. list where each item is map
     *        with keys: name, value, type, default, description.
     *	      values in map are strings
     * @return map result
     */
    define map OptionsDialog (string cardlabel, map opts) ``{

	string help_text = Sound::STRINGS["OptionsDialog"]:"";

	map origOptions = opts;
	list options = maplist (string name, map<string,any> o,
	    (map <string, map <string, any> >) opts, ``($[
	    // label: description of option is not available
	    "description" : getDescr(o["descr"]:nil),
	    "name"	  : name,
	    "value"	  : o["value"]:"",
	    "type"        : o["type"]:"string",
	    "default"     : o["default"]:""]
	));

	list<term> itemized_descriptions = create_table (options,
	    ["description", "name", "value"]);

	integer current_option = 0;
	string current_option_name = options[current_option, "name"]:"";

	term con = `VBox (
	    // To translators: label message
	    `HVCenter (`Label (_("There are no options for this module")))
	);

	if (size (itemized_descriptions) > 0)
	{
	    con = OptionsCon (cardlabel, itemized_descriptions,
		    current_option_name, options[current_option, "value"]:"");
	}

	// dialog title
	Wizard::SetContents (_("Sound card advanced options"), con, help_text,
	    true, true);

	if (size (itemized_descriptions) > 0)
	{
	    UI::ChangeWidget(`id(`table), `CurrentItem, 0);
	    UI::SetFocus (`id(`table));
	}

	symbol ui = `table;

	if (size (itemized_descriptions) == 0)
	    ui = `no;

	repeat
	{
	    if (ui == `table)
	    {
		current_option =
		    (integer) UI::QueryWidget (`id(`table), `CurrentItem);
		term curr_opt = (term) UI::QueryWidget(`id(`table), `Item(current_option));
		current_option_name = curr_opt[2]:"";
		UI::ChangeWidget(`id(`des), `Label,
		    // name of currently selected option: eg. 'Option: snd_asdf'
		    sformat ("%1 %2", _("&Option: "),current_option_name));

		UI::ChangeWidget(`id(`des), `Value, curr_opt[3]:"");

		// update possible values
		map optmap = origOptions[current_option_name]:$[];
		string vals = "";
		if (size(optmap) == 0)
		{
		    vals = "";
		}
		else
		{
		    vals = optmap["allows"]:"";
		}

		updatePossibleValues (vals, curr_opt[3]:"");
	    }
	    if (ui == `poss)
	    {
		current_option =
		    (integer) UI::QueryWidget (`id(`table), `CurrentItem);
		term curr_opt = (term) UI::QueryWidget(`id(`table), `Item(current_option));
                current_option_name = curr_opt[2]:"";

		string value = sformat("%1",UI::QueryWidget(`id(`poss),`Value));

		UI::ChangeWidget(`id(`des), `Value, value);
	    }
	    if (ui == `set)	// set button pressed
	    {
		string value = (string) UI::QueryWidget(`id(`des), `Value);

		if (size (value) == 0)  // we don't care  about empty values
		{
		    UI::ChangeWidget(`id(`table), `Item(current_option, 2), "");
		    ui = `skip_event;
		    continue;
		}

		// Should the value be checked, if it is in 'possible values'?
		list<string> possible_values = string2vallist(
		    origOptions [current_option_name, "allows"]:"");

		string err = check_value (value,
		    options [current_option, "type"]:"string", possible_values);

		if (size (err) > 0)	// error - wrong value
		{
		    Popup::Message (err);
		    UI::ChangeWidget(`id(`des), `Value, "");
		}
		else			// value is correct
		{
		    UI::ChangeWidget(`id(`table),`Item(current_option,2),value);

		    list<term> tmp = [];
		    maplist (term e, itemized_descriptions, ``{
			if (current_option == e[0,0]:0)
			{
			    tmp = add (tmp, `item(`id (current_option),
				e[1]:"", e[2]:"", value));
			}
			else
			    tmp = add (tmp, e);
		    });
		    itemized_descriptions = tmp;
		}
	    }
	    else if (ui == `reset &&
		    // popup question
		    Popup::YesNo(_("Do you really want to reset all values?")))
	    {
		UI::ChangeWidget(`id(`des), `Value, "");

		integer i = 0;
		while (i < size (itemized_descriptions))
		{
		    UI::ChangeWidget (`id(`table), `Item(i, 2),
			options[i,"default"]:"");
		    // reset the values in items list
		    term e = itemized_descriptions [i]:`item(`id(i),"","","");
		    itemized_descriptions [i] =
			`item (e[0]:`id(i), e[1]:"",
			       e[2]:"", options[i,"default"]:"");
		    i = i + 1;
		}
	    }
	    else if (ui == `abort || ui == `cancel)
	    {
		if (ReallyAbort())
		{
		    ui = `abort;
		    break;
		}
	    }
	    ui = (symbol) UI::UserInput();
	} until (ui == `back || ui == `next || ui == `cancel);

	return $["ui" : ui, "return" : itemized_descriptions];
    }

    /**
     * just calls options dialog
     * @param save_entry map with card configuration
     * @return map result
     */
    define map sound_options(map save_entry) ``{

	string modname	= save_entry["module"]:"";
	string label	= save_entry["model"]:"";
	map params	= get_module_params (modname);

	map p2		= restore_mod_params (params,save_entry["options"]:$[]);
	params		= restore_mod_params (params,save_entry["options"]:$[]);

	Wizard::RestoreNextButton();

	// now show the dialog
	map result	= OptionsDialog (label, params);
	list<term> vals	= result["return"]:[];

	// convert from table entries (items) back to card
	map opts = $[];
	foreach (term it, vals, ``{
	    if (it[3]:"" != "")
	    {
		opts = add(opts, it[2]:"", it[3]:"");
	    }
	});

	save_entry = add(save_entry, "options", opts);

	return $["ui" : result["ui"]:`cancel, "return" : save_entry];
    }

}//EOF
