/**
 * File:	modules/LdapPopup.ycp
 * Package:	Configuration of LDAP
 * Summary:	Additional user interface functions: special edit popups
 * Authors:	Jiri Suchomel <jsuchome@suse.cz>
 *
 * $Id: LdapPopup.ycp 20548 2004-12-14 13:28:33Z jsuchome $
 *
 * Popups for editing the values of LDAP configuration tables.
 */

{

module "LdapPopup";
textdomain "ldap-client";

import "Ldap";
import "Label";
import "Popup";
import "Wizard";

    /**
     * Popup for browsing LDAP tree and selecting the DN
     * WARNING we expect that LDAP connection is already correctly initialized !
     * @param root_dn the starting point (root of tree); if empty string is
     * given, the search for available bases will be done automatically
     * @return DN of selected item, empty string when canceled
     */
    global define string BrowseTree (string root_dn) {

	// get display mode
	map display_info = UI::GetDisplayInfo();
	boolean textmode = display_info["TextMode"]:true;

	// map of already read subtrees
	map<string,boolean> dns	= $[];
	// selected DN (return value)
	string current_dn	= "";

	term contents = `HBox (`VSpacing (20), `VBox (`HSpacing(70),
	    `VSpacing (0.2),
	    `HBox (
		`HSpacing (),
		`ReplacePoint (`id (`reptree), `Tree (`id(`tree), root_dn, [])),
		`ReplacePoint (`id (`repbuttons), `Empty ()),
		`HSpacing ()
	    ),
	    `HBox (
		`HSpacing (1.5),
	        `PushButton (`id (`ok), `opt (`key_F10), Label::OKButton()),
		`PushButton(`id (`cancel),`opt (`key_F9),Label::CancelButton()),
		// pushbutton label
		textmode ?
		  // button label
		  `Right (`PushButton (`id (`open), `opt (`key_F6),_("&Open"))):
		  `Empty (),
		`HSpacing (1.5)
	    ),
	    `VSpacing (0.2)
	));

	UI::OpenDialog (`opt (`decorated), contents);

	list<term> items	= [];
	list<string> search	= (list<string>) SCR::Read (.ldap.search, $[
		"base_dn"	: root_dn,
		"scope"		: 1,
		"dn_only"	: true,
		"not_found_ok"	: true ]
	);
	if (size (search) > 0)
	{
	    items = maplist (string dn, search, {
		dns [dn]	= false;
		return `item (dn, false, []);
	    });
	}

	if (size (items) > 0)
	{
	    UI::ReplaceWidget (`id (`reptree), textmode ?
		`Tree (`id(`tree), root_dn, items) :
		`Tree (`id(`tree), `opt(`notify), root_dn, items));
	}
	else if (root_dn == "")
	{
	    list bases = (list) SCR::Read (.ldap.search, $[
		"base_dn"	: "",
	        "scope"		: 0,
		"attrs"		: [ "namingcontexts" ]
	    ]);
	    if (size (bases) > 0)
		items = maplist (string dn, bases[0,"namingcontexts"]:[],
		    ``(`item(dn, false, [])));
	    if (size (items) > 0)
		UI::ReplaceWidget (`id (`reptree), textmode ?
		    `Tree (`id(`tree), root_dn, items) :
		    `Tree (`id(`tree), `opt(`notify), root_dn, items));
	}

	if (textmode)
	    UI::SetFocus (`id(`tree));

	list<string> subdns	= [];
	map open_items		= $[];

	define list<term> update_items (list<term> its) {

	    return maplist (term it, its, {
		string dn	= it[0]:"";
		if (dn == current_dn)
		{
		    return `item (dn, true, maplist (string k,
			subdns, ``(`item (k, false, []))));
		}
		integer last = size (it) - 1;
		if (size (it[last]:[]) == 0)
		    return it;
		// `OpenItems doesn't work in ncurses...
		boolean open = haskey (open_items, dn) && !textmode;
		return `item (dn, open, update_items (it[last]:[]));
	    });
	}

	string retval = root_dn;
	while (true)
	{
	    any ret = UI::UserInput ();
	    if (ret == `tree || ret == `open)
	    {
		current_dn = (string) UI::QueryWidget (`id(`tree),`CurrentItem);
		if (! dns[current_dn]:false)
		{
		    subdns = (list<string>) SCR::Read (.ldap.search, $[
			"base_dn"	: current_dn,
			"scope"		: 1,
			"dn_only"	: true,
			"not_found_ok"	: true,
		    ]);
		    dns [current_dn]	= true;
		    if (size (subdns) > 0)
		    {
			open_items = (map) UI::QueryWidget (`tree, `OpenItems);
			items	= update_items (items);
			UI::ReplaceWidget (`id (`reptree), textmode ?
			    `Tree (`id(`tree), root_dn, items) :
			    `Tree (`id(`tree), `opt(`notify), root_dn, items));
			UI::ChangeWidget (`id(`tree), `CurrentItem, current_dn);
			open_items = $[];
		    }
		}
		if (textmode)
		    UI::SetFocus (`id(`tree));
	    }
	    if (ret == `cancel)
	    {
		retval = "";
		break;
	    }
	    if (ret == `ok)
	    {
		string dn = (string)UI::QueryWidget (`id(`tree), `CurrentItem);
		if (dn != nil)
		    retval = dn;
		else retval = current_dn;
		break;
	    }
	}
	UI::CloseDialog ();
	return retval;
    }

    /**
     * Open the LDAP Browse popup and initialize initialize LDAP connection
     * before.
     * @param connection init map with information passed to ldap agent
     * (see ldap agent '.ldap' Execute call documentation)
     * @param root_dn the starting point (root of tree); if empty string is
     * given, the search for available bases will be done automatically
     * @return DN of selected item, empty string when canceled
     */
    global define string InitAndBrowseTree (string root_dn, map connection) {

	map args = size (connection) > 0 ? connection : $[
	    "hostname"	: Ldap::GetFirstServer (Ldap::server),
	    "port"	: Ldap::GetFirstPort (Ldap::server),
	    "version"	: Ldap::ldap_v2 ? 2 : 3,
	    "use_tls"	: Ldap::ldap_tls ? "yes" : "no"
	];
	boolean init = (boolean) SCR::Execute (.ldap, args);
	if (init == nil || !init)
	{
	    y2error ("init failed");
	    return root_dn;
	}
	return BrowseTree (root_dn);
    }


/**
 * Generic popup for editing attribute's value
 * @param map with settings, it could have these keys:
 *  "attr"	attribute name
 *  "value"	current attribute values
 *  "conflicts" list of forbidden values (e.g. existing 'cn' values)
 *  "single"	if attribute can have only one value
 *  "offer"	list of possible values for current attribute (e.g. existing
 *		groups for "secondaryGroup" attribute)
 *  "browse"	if Browse LDAP Tree widget should be provided for choosing DN
 * @return list of atrtibute values (edited or unchanged)
 */
global define list<string> EditAttribute (map settings)
{

    string attr		= settings["attr"]:"";
    list<string> value	= settings["value"]:[];
    list conflicts	= settings["conflicts"]:[];
    list offer		= settings["offer"]:[];
    boolean single	= settings["single"]:false;
    boolean browse	= settings["browse"]:false;

    // help text 1/3
    string help_text = _("<p>Set the new value for the current attribute.</p>") +

    // help text 2/3
    _("<p>If the attribute can have more values, add new entries
with <b>Add Value</b>. Sometimes the button contains the list of
possible values to use for the current attribute.
If the value of the edited attribute should be a distinguished name (DN),
it is possible to choose it from LDAP tree using <b>Browse</b>.
</p>
");

    string desc = Ldap::AttributeDescription (attr);

    if (desc != "")
	// help text 3/3, %1 is attribute name, description follows.
	// The description will be not translated: maybe add a note
	// "available only in english" to the sentence for other languages?
	// Example:
	// "<p>The description of attribute \"%1\"<br>(available only in english):</p>"
	// or:
	// "<p>The description (only in english) of attribute \"%1\":<br></p>"
	help_text = help_text + sformat (_("<p>The description of attribute \"%1\":<br></p>"), attr) +

	sformat ("<p><i>%1</i></p>", desc);

    list<string> org_value = value;
    integer value_size = size (value);

    // horizontal size of popup for
    integer hsize = size (value[0]:"") + 10;

    /**
     * Helper for creating items for EditAttribute Popup
     */
    define term generate_value_list () ``{
	term ret = `VBox();
	if (single)
	{
	    ret = add (ret, `TextEntry (`id (0),
		// textentry label
		sformat(_("&Value of \"%1\" Attribute"), attr), value[0]:""));
	}
	else
	{
	    ret = add (ret, `TextEntry (`id (0),
		// textentry label
		sformat(_("&Values of \"%1\" Attribute"), attr), value[0]:""));
	    integer i = 1;
	    while (i < value_size)
	    {
		ret = add (ret, `TextEntry (`id (i), "", value[i]:""));
		if (size (value[i]:"" + 10) > hsize)
		    hsize = size (value[i]:"") + 10;
		i = i + 1;
	    }
	}
	return ret;
    }

    term values = generate_value_list ();
    // button label
    term add_button = `PushButton(`id(`new), `opt (`key_F3), _("&Add Value"));

    if (size (offer) > 0 || browse)
    {
	// menubutton item (default value)
	list mb = [ `item(`id(`new), _("&Empty Entry")) ];
	if (browse)
	    mb	= add (mb, `item(`id(`browse), _("Bro&wse")));
	foreach (string it, (list<string>) offer, ``{
	    mb = add (mb, `item(`id(it), it));
	});
	// button label
	add_button = `MenuButton(`id(`mb), `opt (`key_F3), _("&Add Value"), mb);
    }

    UI::OpenDialog( `opt(`decorated), `HBox(
	`HSpacing(1),
	`VBox(
	    `HSpacing (hsize > 50 ? hsize : 50),
	    `ReplacePoint (`id(`rp), values),
	    `HBox(
		`PushButton(`id(`ok),`opt(`default,`key_F10),Label::OKButton()),
		`PushButton(`id(`cancel),`opt (`key_F9), Label::CancelButton()),
		`PushButton(`id(`help),`opt (`key_F2), Label::HelpButton()),
		(single)? `Empty() :
		add_button
	    )
	),
	`HSpacing(1)
    ));
    any result = nil;
    list<string> new_value = [];

    if (value_size == 0)
	value_size = 1;
    UI::SetFocus (`id(value_size-1));
    while (true)
    {
	result = UI::UserInput ();
	if (result == `cancel)
	{
	    new_value = org_value;
	    break;
	}
	if ( result == `help )
	{
	    Wizard::ShowHelp( help_text );
	}
	if (result == `new || contains (offer, result) || result == `browse)
	{
	    integer j = 0;
	    value = [];
	    while (j < value_size)
	    {
		value = add (value, (string) UI::QueryWidget (`id(j), `Value) );
		j = j + 1;
	    }
	    if (contains (offer, result) && value[value_size-1]:"" == "")
	    {
		// relace last empty entry
		value [value_size-1] = (string) result;
	    }
	    else if (result == `browse)
	    {
		value [value_size-1] = BrowseTree ("");
	    }
	    else
	    {
		// add new entry
		value = add (value, (result == `new)? "" : (string) result);
		value_size = value_size + 1;
	    }
	    UI::ReplaceWidget (`id(`rp), generate_value_list ());
	    UI::SetFocus (`id(value_size-1));
	}
	if (result == `ok)
	{
	    integer j = 0;
	    boolean duplicate = false;
	    new_value = [];
	    while (j < value_size && !duplicate)
	    {
		string v = (string) UI::QueryWidget (`id(j), `Value);
		if (!contains (org_value, v) && contains (conflicts, v))
		{
		    //error popup
		    Popup::Error (sformat (_("The value \'%1\' already exists.
Please select another one."), v));
		    duplicate = true;
		}
		if (v != "") new_value = add (new_value, v);
		j = j + 1;
	    }
	    if (duplicate) continue;
	    break;
	}
    }
    UI::CloseDialog();
    return new_value;
}

/**
 * Popup for adding new configuration module
 * @param conflicts list of forbidden names ('cn' values)
 * @param available list of possible object classes for new module
 * @return map of new module (contains its name and object class)
 */
global define map NewModule (list available, list conflicts) ``{

    map descriptions = $[
	// description of configuration object
	"suseuserconfiguration": _("Configuration of user management tools"),
	// description of configuration object
	"susegroupconfiguration": _("Configuration of group management tools"),
    ];
    // label
    term r_buttons = `VBox( `Left(`Label (_("Object Class of New Module"))));
    foreach (string class, (list<string>) available, ``{
	string desc = class;
	if (descriptions [class]:"" != "")
	    desc = sformat ("%1 (%2)", class, descriptions [class]:"");
	r_buttons = add (r_buttons,`Left(`RadioButton (`id(class), desc,true)));
    });
    UI::OpenDialog( `opt(`decorated), `HBox(
	`HSpacing(1),
	`VBox(
	    `HSpacing (50),
	    `RadioButtonGroup (`id(`rb), r_buttons),
	    // textentry label, do not translate "cn"
	    `TextEntry (`id (`cn), _("&Name of New Module (\"cn\" value)")),
	    `HBox(
		`PushButton(`id(`ok),`opt(`default,`key_F10),Label::OKButton()),
		`PushButton(`id(`cancel),`opt (`key_F9), Label::CancelButton())
	    )
	),
	`HSpacing(1)
    ));
    any result = nil;
    string new_value = "";
    string class = "";

    UI::SetFocus (`id(`cn));
    while (true)
    {
	result = UI::UserInput ();
	if (result == `cancel)
	{
	    new_value = "";
	    break;
	}
	if (result == `ok)
	{
	    new_value = (string) UI::QueryWidget (`id(`cn), `Value);
	    if (contains (conflicts, new_value))
	    {
		//error popup
		Popup::Error (_("The entered value already exists.
Select another one.
"));
		continue;
	    }
	    if (new_value == "")
	    {
		//error popup
		Popup::Error (_("Enter the module name."));
		continue;
	    }
	    class = (string) UI::QueryWidget (`id(`rb), `CurrentButton);
	    break;
	}
    }
    UI::CloseDialog();
    return $[ "class": class, "cn": new_value ];
}

/**
 * Popup for adding new default value (default value is template's attribute)
 * @param conflicts list of attributes already set
 * @param available list of possible attributes
 * @return map of new "default value" (contains attribute name and value)
 */
global define map AddDefaultValue (list available, list conflicts) ``{

    // help text 1/3
    string help_text = _("<p>Here, set the values of attributes belonging
to an object using the current template. Such values are used as defaults when
the new object is created.</p>
") +

    /*
    // help text 2/3 do not translate "defaultObjectClass"
    _("<p>The list of attributes provided in <b>Attribute Name</b> is the
list of allowed attributes for objects described in the \"defaultObjectClass\"
entry of the current template.</p>
") +
    */

    // help text 3/3 do not translate "homedirectory"
    _("<p>You can use special syntax to create attribute
values from existing ones. The expression <i>%attr_name</i> will be replaced
with the value of attribute \"attr_name\" (for example, use \"/home/%uid\"
as a value of \"homeDirectory\").</p>
");

    available = filter (string attr, (list<string>) available, ``(!contains (conflicts,attr)));

    UI::OpenDialog( `opt(`decorated), `HBox(
	`HSpacing(1),
	`VBox(
	    `HSpacing (50),
	    `VSpacing(0.5),
	    // combobox label
	    `Left(`ComboBox (`id (`attr),`opt(`editable), _("Attribute &Name"),
		available)),
	    `VSpacing(0.5),
	    // textentry label
	    `TextEntry (`id (`val), _("Attribute &Value")),
	    `VSpacing(0.5),
	    `HBox(
		`PushButton(`id(`ok),`opt(`default,`key_F10),Label::OKButton()),
		`PushButton(`id(`cancel), `opt(`key_F9), Label::CancelButton()),
		`PushButton(`id(`help),`opt (`key_F2), Label::HelpButton())
	    ),
	    `VSpacing(0.5)),
	`HSpacing(1)
    ));
    any result = nil;
    string new_value = "";
    string attr = "";

    UI::SetFocus (`id(`attr));
    while (true)
    {
	result = UI::UserInput ();
	if (result == `cancel)
	{
	    new_value = "";
	    break;
	}
	if ( result == `help )
	{
	    Wizard::ShowHelp( help_text );
	}
	if (result == `ok)
	{
	    attr	= (string) UI::QueryWidget (`id(`attr), `Value);
	    new_value	= (string) UI::QueryWidget (`id(`val), `Value);
	    break;
	}
    }
    UI::CloseDialog();
    return $[ "attr": attr, "value": new_value ];
}

}//EOF
