/**
 * File:
 *   routines.ycp
 *
 * Module:
 *   Sound
 *
 * Summary:
 *   Routines for sound card configuration
 *
 * Authors:
 *   Dan Veselư <dan@suse.cz>
 *   Dan Meszaros <dmeszar@suse.cz>
 *
 * $Id: routines.ycp 22766 2005-03-23 15:43:06Z lslezak $
 *
 */

{
    import "Directory";
    import "Mode";
    import "Popup";
    import "Sound";

    textdomain "sound";


    // list of all card models (generated only once and cached)
    list<string> all_card_names = [];

    // list of all sound modules (table items)
    list all_module_items = [];

    /**
     * returns cards manufactured by given vendor (ALSA only) or driver
     * @param key vendor or driver; if "all" returns all models
     * @param keys in which set is the key: "vendors" or "modules"
     * @return list with sound card models
     */
    define list<string> get_card_names (string key, string keys) ``{

	Sound::LoadDatabase(true);

        if (key == "all" || key == "")
        {
	    if (all_card_names == [] || all_card_names == nil)
	    {
		// use card names from db_cards (they contain the vendor name)
		list<list> all_card_names_list = maplist (integer k, list v,
		    (map <integer,list>) Sound::db_cards, ``(v));
		all_card_names = sort ((list<string>) flatten (all_card_names_list));
	    }
	    return all_card_names;
        }

	if (keys == "vendors")
	{
	    // names from db_vendors (vendor name not included - not necessary)
	    return sort ((list<string>) (Sound::db_vendors[key]:[]));
	}
	else
	{
	    if (!Sound::use_alsa)
		return [sformat("Sound card (%1)", key)];

	    map mods = mapmap (integer k, string v,
		(map <integer,string>) Sound::db_module_index, ``($[v : k]));
	    integer index	= mods[key]:0;
	    list<string> cards	= sort (
		(list<string>) (Sound::db_cards[index]:[]));

	    if (cards == [])
	    {
		cards = [ Sound::db_modules[key, "description"]:"" ];
	    }
	    return cards;
	}
    }

    /**
     *  Returns list of already running cards
     *	(oss is nasty hacked, because it's not trivial to check this)
     *  @return list
     */
    define list get_running_cards () ``{

	// return empty list in autoyast config mode
	if (Mode::config())
	{
	    return [];
	}

	if (!Sound::use_alsa)
	    return [1,2,3,4,5];

	path proot = .audio.alsa.cards;
	list<string> cards = (list<string>) SCR::Dir (proot);
	return maplist(string c, cards, ``($[
	    "number"	: tointeger (c),
	    "name"	: SCR::Read (add(add(proot, c), "name"))
	]));
    }

    /**
     * returns the 'params' section from sndcards.ycp of the given module
     * @param modname module name
     * @return map with params and their descriptions
     */
    define map get_module_params(string modname) ``{

	if (!Sound::use_alsa)
	{
	    map<string,string> params = (map<string,string>) SCR::Read (
		topath(sformat(".modinfo.kernel.drivers.sound.%1", modname)));
	    params = filter(string k, string v, params, ``(
		!contains(["module_author","module_description","module_filename"],k)
	    ));
	    return mapmap (string name, string desc, params, ``(
		$[ name: $[
			"name"	: name,
			"descr"	: [desc],
			"type"	: "string" ]
		]
	    ));
	}

	Sound::LoadDatabase(true);
	return Sound::db_modules[modname, "params"]:$[];
    }

    /**
     * adds alias to save_info entry
     * @param entry card config
     * @param card_id card id
     * @return map modified card entry with filled alias value
     */
    define map add_alias(map entry, integer card_id) ``{

	if (!Sound::use_alsa)
	    return add(entry, "alias", sformat("sound-slot-%1", card_id));
	else
	    return add(entry, "alias", sformat("snd-card-%1", card_id));
    }

    /**
     *  adds common options for module. for alsa it is 'snd_index=${card_id}'
     *  @param entry card config
     *  @param card_id card id
     *  @return map modified save_entry
     */
    define map add_common_options(map entry, integer card_id) ``{

	map opts = entry["options"]:$[];

	if (!Sound::use_alsa)
	{

	    map<string,any> parms = (map<string,any>) get_module_params(entry["module"]:"");
	    if (size(parms) != 0)
	    {
		map enab = filter (string name, any e, parms,
		    ``(name == "snd_enable"));
		if (size(enab) != 0)
		    opts = add(opts, "snd_enable", "1");
	    }
	    entry = add(entry, "options", opts);
	    return entry;
	}
	// check existence of 'common' parameters: 'enable' and 'index'
	string modname = entry["module"]:"";
	foreach (string key, string value, (map<string,string>) SCR::Read (
	    topath (sformat(".modinfo.kernel.drivers.sound.%1", modname))), ``{

	    if (key == "enable")
	    {
		opts [key] = "1";
	    }
	    if (key == "index")
	    {
		opts [key] = sformat ("%1",card_id);
	    }
	});
	entry["options"]	= opts;
	return entry;
    }

    /**
     * Simply returns list with ALSA OSS/Free emulation modules
     * @param number number of sound cards
     * @return list of oss-alsa aliases
     */
    define list alsa_oss (integer number) ``{

	if (!Sound::use_alsa)
	    return [];

        list modules = [];
        integer i = 0;

        while (i < number)
        {
            modules = add (modules, $[
		    "alias"	: sformat ("sound-slot-%1", i),
		    "module"	: sformat ("snd-card-%1", i) ]
	    );
            i = i + 1;
        }
        return modules;
    }

    /**
     * returns list of all available sound modules (OSS only)
     * @return list as above
     */
    define list get_module_names() ``{

	if (all_module_items == [] || all_module_items == nil)
	{
	    if (Sound::use_alsa)
	    {
		Sound::LoadDatabase(true);
		list<term> mods = maplist (string k, map v, (map<string,map<string,any> >)Sound::db_modules, ``{
		    return `item(`id(k), v["description"]:k);
		});

		mods = sort(term a, term b, mods, ``{
		    return a[1]:"" < b[1]:"";
		});

		// item for all sound card models in sound card driver list
		mods = prepend(mods, `item(`id("all"), _("All")));
		all_module_items = mods;
	    }
	    else
	    {
		list<string> ret = (list<string>) SCR::Dir(.modinfo.kernel.sound.oss);
		ret = filter(string mod, ret, ``(mod != "sound"));
		ret = add(ret, "emu10k1");
		ret = add(ret, "cs4281");
		list<term> ret_t = maplist(string e, ret,``{
		    map modinfo = (map)SCR::Read(add(.modinfo.kernel.sound.oss, e));
		    string descr = modinfo["module_description"]:"";
		    return
		    `item(`id(e), (descr != "" && descr != "<none>")? descr: e);
		});
		ret_t = sort(term a, term b, ret_t,
		    ``(a[1]:"" < b[1]:""));
		all_module_items = ret_t;
	    }
	}
	return all_module_items;
    }

    /**
     * get_vol_settings
     * creates a list of stored values of volume and mute for each channel
     * of each card.
     * example: [
		    [ ["PCM", 32, false], ["Master", 100, true]],
		    [ [....], .....                            ]
		]
     * @return list as above
     */
    define list get_vol_settings() ``{

	if (!Sound::use_alsa)
	    return [];

	Sound::LoadDatabase(true);
	integer bound	= size(SCR::Dir(.audio.alsa.cards));
	integer i	= 0;
	list retlist	= [];
	while (i < bound)
	{
	    list sublist = [];
	    string modname = Sound::modules_conf[i,"module"]:"snd-dummy";
	    list<string> chans = Sound::db_modules[modname, "mixer_elements"]:[];

	    if (chans == [])
	    {
		if (contains(SCR::Dir(.audio.alsa.cards), sformat("%1", i)))
		{
		    chans = (list<string>) SCR::Dir(topath(
			sformat(".audio.alsa.cards.%1.channels", i)));
		}
		else
		{
		    chans = [];
		}
	    }

	    foreach (string e, chans, ``{
		path pth1 = topath(sformat(".audio.alsa.cards.%1.channels.%2.volume", i, e));
		path pth2 = topath(sformat(".audio.alsa.cards.%1.channels.%2.mute", i, e));
		sublist = add(sublist, [e, SCR::Read(pth1), SCR::Read(pth2)]);
	    });
	    retlist = add (retlist, sublist);
	    i = i + 1;
	}
	return retlist;
    }

    /**
     * stores values generated by get_vol_settings
     * @param vol_settings volume settings
     * @return boolean success/failure
     */
    define boolean set_vol_settings(list vol_settings) ``{

	if (!Sound::use_alsa)
	    return true;

	// during autoinstallation, vol_settings list looks different... :-(
	if (Mode::autoinst())
	{
	    vol_settings = maplist (any it, vol_settings, ``{
		if (it == nil)
		    return $[];
		else return it;
	    });
	}
	any item = vol_settings [0]:nil;
	if (is (item, map))
	{
	    vol_settings = maplist (map onecard, (list<map>) vol_settings, ``{
		return maplist (any ch, any vol, onecard, ``([ch, vol]));
	    });
	}
	integer i = 0;

	foreach(list<list<any> > channels, (list<list<list<any> > >)vol_settings, ``{
	    foreach(list<any> channel, channels, ``{
		string name = channel[0]:"Master";
		path pth1 = topath (
		    sformat(".audio.alsa.cards.%1.channels.%2.volume", i,name));
		path pth2 = topath (
		    sformat(".audio.alsa.cards.%1.channels.%2.mute", i, name));
		SCR::Write (pth1, channel[1]:0);
		SCR::Write (pth2, channel[2]:false);
	    });
	    i = i + 1;
	});
	SCR::Execute(.audio.alsa.store, "");
	return true;
    }


    /**
     * common function to extract 'name' of hardware
     * @param hardware_entry map
     * @return string readable name of the card
     */
    define string hardware_name (map hardware_entry) ``{

	y2debug("hardware_entry: %1", hardware_entry);

        string sub_vendor	= hardware_entry["sub_vendor"]:"";
	string sub_device	= hardware_entry["sub_device"]:"";
	string vendor		= hardware_entry["vendor"]:"";
	string device		= hardware_entry["device"]:"";

        if ((sub_vendor != "") && (sub_device != ""))
            return (sub_vendor + "\n" + sub_device);
        else if ((vendor == "") && (device == ""))
	{
	    string model	= hardware_entry["model"]:"";
	    string module_desc	= hardware_entry["module", "description"]:"";

	    return (size(model) > 0 && size(module_desc) > 0) ?
		model + " - " + module_desc : model + module_desc;
	}
	else return vendor + ((vendor != "") ? "\n" : "") + device;
    };


    /**
     * filters out already configured cards
     * @param save_info info from modules.conf
     * @param cards autodetected cards
     * @return list of entries of not configured cards
     */
    define list<map> filter_configured(list<map> save_info, list<map> cards) ``{

        return filter(map det_card, cards, ``{
            string uniq     = det_card["unique_key"]:"";
            boolean retval  = true;
            foreach(map saved_card, save_info, ``{
                if (saved_card["unique_key"]:"" == uniq)
                {
                    retval = false;
                }
            });
            return retval;
        });
    }

    /**
     * for a given card detected by libhd this function creates a label
     * @param card map entry from .probe.audio
     * @return string card label
     */
    define string get_card_label(map card) ``{

        list lab = splitstring(hardware_name(card),"\n");

	y2debug("Card label: %1", lab);

	if (size(lab) == 0 )
	{
	    return "";
	}
        if (size(lab) == 1 || lab[1]:"" == "")
        {
            return lab[0]:"";
        }
        return lab[1]:"";
    }

    /**
     * returns true if given string is valid sound alias
     * (snd-card-1 .. snd-card-16)
     * @param alias string
     * @return boolean is/is not
     */
    define boolean is_snd_alias(string alias) ``{
	if (Sound::use_alsa)
	{
	    return regexpmatch(alias, "^snd-card-[0-9]*$");
	}
	else
	{
	    return regexpmatch(alias, "^sound-slot-[0-9]*$");
	}
    }

    /**
     * unique key for non-pci/pnp cards or virtual cards
     * @return string key for legacy isa cards
     */
    define string isa_uniq() ``{
	return "uniq.virtual";
    }

    /**
     * reads variables listed in 'vars' from rc.config
     * @return map optname: value
     */
    define map read_rc_vars () ``{

	return $["LOAD_ALSA_SEQ" : SCR::Read(.sysconfig.sound.LOAD_SEQUENCER)];
    }

    /**
     * saves uniq keys to .probe.status
     * @param configured list of strings of configured cards uniq keys
     * @param unconfigured list of string of unconfigured cards uniq keys
     * @return boolean true
     */
    define boolean SaveUniqueKeys(list configured, list unconfigured) ``{
        maplist (string uk, (list<string>) configured, ``{
            SCR::Write(.probe.status.configured, uk, `yes);
        });

        maplist(string uk, (list<string>) unconfigured, ``{
            SCR::Write(.probe.status.configured, uk, `no);
        });

        return true;
    }



    /**
     * Used for searching. returns index of the card in the database
     * identified by name (more exactly: returns index of first name matching
     * the given substring)
     * @param name card name (or its substring)
     * @return integer position of the card
     */
    define integer search_card_id (string name) ``{
        list all_cards = get_card_names ("all", "vendors");
        integer pos = 0;
        integer len = size (name);
	name = tolower(name);
	integer bound = size (all_cards);
        while (pos < bound)
        {
            if (substring (tolower(all_cards[pos]:""), 0, len) == name)
            {
                return pos;
            }
            pos = pos + 1;
        };
        return -1;
    }

    /**
     * Itemize list for selection box
     * @param l list with values
     * @param default value to select as default
     * @return list items to be shown in list widget
     */
    define list itemize_list (list l, integer default) ``{
        integer i = 0;
        list itemized_list = [];
        integer s = size (l);
        while (i < s)
        {
            itemized_list = add (itemized_list,
		`item (`id(i), l[i]:"", (i == default)));
            i = i + 1;
        }
        return itemized_list;
    };


    /**
     *	show a warning popup for nm256 snd cards if needed
     *  @param modname string module name
     *  @return boolean continue/abort
     */

    define boolean nm256hack (string modname) ``{
        if (modname == "snd-nm256")
        {
            // special warning message for in a special case
            string warn_text = Sound::STRINGS["nm256hackWarning"]:"";
	    return Popup::YesNo(warn_text);
        }
        return true;
    }

    /**
     * Looks up in the database for the module
     * @param card map read from .probe.sound
     * @return map relevant card info found in db
     */
    define map get_module (map card) ``{

	Sound::LoadDatabase (true);

	string bus        = card["bus"]:"";
	integer offset    = (tolower (bus) == "isa") ? 0x20000 : 0x10000;
	integer vendor_id = card["vendor_id"]:0 - offset;
	integer device_id = card["device_id"]:0 - offset;

	map m1 = Sound::db_indices[vendor_id]:$[];
	if (m1 == $[])
	{
	    y2debug("vendor_id not found...");
	    return $[];
	}

	integer m2 = m1[device_id]:-1;
	if (m2 == -1)
	{
	    y2debug("device_id not found");
	    return $[];
	}

	string modname = Sound::db_module_index [m2]:"";

	map ret = Sound::db_modules [modname]:$[];

	if (ret != $[] && !haskey(ret, "name"))
	{
	    // we must supply the 'name' key with the module name
	    ret = add(ret, "name", modname);
	}

	return ret;
    }

    /**
     * umnute channel 'devide' of the 'card_id'-th sound card. alsa only
     * @param devices list of channels to be unmuted
     * @param card_id of the card
     * @return void
     */
    define void unmute(list devices, integer card_id) ``{
	if (Mode::config())
	{
	    return;
	}

	list avail = SCR::Dir(topath(sformat(".audio.alsa.cards.%1.channels", card_id)));
	if (avail == nil || size(avail)==0)
	{
	    avail = [];
	}
	foreach(string dev, (list<string>) devices,
	``{
	    if (contains(avail, dev))
	    {
		SCR::Write(topath(sformat(".audio.alsa.cards.%1.channels.%2.mute", card_id, dev)), false);
	    }
	});
    }


    /**
     * Checks whether the module has been successfully loaded
     *
     * @param save_entry card config map
     * @param card_id card id
     * @return string empty on success/ error message on failure
     */
    define string check_module(map save_entry, integer card_id) ``{
	map pm		= (map) SCR::Read(.proc.modules);
	string modname	= save_entry["module"]:"off";
	list<string> l	= splitstring (modname, "-");
	string mod_name = mergestring (l, "_");

        if ((!haskey (pm, modname) && !haskey (pm, mod_name)) ||
	    size(get_running_cards()) <= card_id)
        {
	    // add debug info to the y2log
	    y2milestone("modules: %1", pm);
	    y2milestone("modname: %1", modname);
	    y2milestone("mod_name: %1", mod_name);
	    y2milestone("get_running_cards: %1", get_running_cards());
	    y2milestone("card_id: %1", card_id);

            // label to error popup, %1 is module name
            return sformat (_("The kernel module %1 for sound support
could not be loaded. This can be caused by incorrect
module parameters, including invalid IO or IRQ parameters."), modname);
        }
        return "";

    }

    /**
     * inserts values to already set options
     * @param params list with available options for module
     * @param values values that have been already set
     * @return map with refreshed options
     *
     */
    define map restore_mod_params(map params, map values) ``{
	return mapmap(string parname, map parmap, (map<string,map<string,any> >)params, ``{
	    parmap = add (parmap, "value", values[parname]:"");
	    return $[parname : parmap];
	});
    }

    /**
     *  checks whether sound fonts have already been installed
     *	@return boolean already installed/not installed
     *
     */

    define boolean FontsInstalled() ``{
	return SCR::Read(.target.size, "/usr/share/sfbank/creative") >= 0;
    }

    /**
     * return true if the sound card supports sound fonts
     * @param save_entry save entry
     * @return boolean	card supports fonts/it doesn't
     */

    define boolean HasFonts(map save_entry) ``{
	if (Mode::config())
	{
	    // don't install sound fonts during autoinstallation config
	    return false;
	}

	if (contains(["snd-emu10k1", "snd-sbawe", "emu10k1"], save_entry["module"]:""))
	{
	    return true;
	}
	return false;
    }

    /**
     *  this small wizard will install sound fonts for soundblaster live/awe
     *  @param symlink is path to default.sf2 that is to be created
     *  @param dontask if true, skip the first messagebox
     *  @return void
     */

    define void InstallFonts(string symlink, boolean dontask) ``{
	boolean answer = false;

	// step 1: want install?
	if (dontask)
	{
	    answer = true;
	}
	else
	{
	    answer = Popup::YesNoHeadline(Sound::STRINGS["soundFontTitle"]:"",
		Sound::STRINGS["soundFontQuestion"]:"");
	}

	if (!answer)
	{
	    return;
	}

	while (true)
	{
	    // step 2: insert CD
	    if (!Popup::ContinueCancelHeadline (
		    Sound::STRINGS["soundFontTitle"]:"",
		    Sound::STRINGS["soundFontAppeal"]:""))
	    {
		return;
	    }

	    // step3: do something
	    map res = (map) SCR::Execute (.target.bash_output,
		Directory::ybindir + "/copyfonts", $[] );

	    y2debug ("copyfonts output: %1", res);
	    integer cnt = tointeger(res["stdout"]:"0");

	    // restart ALSA after sound font copy
	    y2milestone("Restarting ALSA: %1",
		SCR::Execute(.target.bash, "/etc/init.d/alsasound restart"));

	    // step4:
	    if (cnt > 0)
	    {
		Popup::Message (sformat(Sound::STRINGS["soundFontFinal"]:"",
		    cnt, "/usr/share/sfbank/creative"));
		return;
	    }
	    else
	    {
		if (!Popup::YesNoHeadline (
			Sound::STRINGS["soundFontNotFound"]:"",
			Sound::STRINGS["soundFontRetry"]:""))
		{
		    return;
		}
	    }
	}
    }

    /**
     * does this machine need a nm256/opl3sa warning?
     * @param sound_cards sound cards
     * @return boolean see as above
     */
    define boolean need_nm256_opl3sa2_warn(list sound_cards) ``{
	list mods = maplist (map card, (list<map<string,any> >)sound_cards, ``(card["module"]:""));
	if (contains(mods, "snd-nm256") && contains(mods, "snd-opl3sa2"))
	{
	    return true;
	}
	return false;
    }

    /**
     * shows warning message when both nm265 and opl3sa2 cards are present
     * @param sound_cards list of sound cards
     * @return void
     */
    define void nm256_opl3sa2_warn(list sound_cards) ``{

	list s1 = filter(map e, (list<map<string,any> >) sound_cards, ``(e["module"]:"" == "snd-nm256"));
	list s2 = filter(map e, (list<map<string,any> >)sound_cards, ``(e["module"]:""=="snd-opl3sa2"));

	if (size(s1) > 0 && size(s2) > 0)
	{
	    string name1 = s1[0, "model"]:"";
	    string name2 = s2[0, "model"]:"";
	    Popup::LongText("",
		`RichText ("<p>" +
		    sformat(Sound::STRINGS["opl3sa_nm256_warn"]:"", name1,name2)
		    +"</p>"),
		50, 12);
	}
    }

    /**
     * Hack for Thinkpad 600E notebook - it need cs4236 module instead of cs4610
     * @param card_id card number
     */
    define void Thinkpad600E_cs4236_hack (integer card_id) ``{

	map card	= Sound::save_entry;
	string modname	= "snd-cs4236";

	foreach (map c, Sound::detected_cards, ``{
	    if (c["unique_key"]:"" == card["unique_key"]:"") {

		// 1. is it right card? -> check subsystem + subvendor id
		if (c["sub_device_id"]:0 == 69648 &&
		    c["sub_vendor_id"]:0 == 69652 &&
		    card["module"]:"" != modname)
		{
		    // popup question: different module has to be choosed
		    if (!Popup::YesNo (_("It seems that you have a Thinkpad 600E laptop.
On this laptop, the CS4236 driver should be used
although the CS46xx chip is detected.
Attempt to probe the configuration of CS4236 driver?

Warning: The probe procedure can take some time and
could make your system unstable.
")))
			return;

		    string ret = Sound::ProbeOldChip ("cs4236");
		    if (ret != "")
		    {
			list returned = splitstring (ret, "\n");
			ret = returned[0]:"";

			map options = $[];
			foreach (string o, splitstring (ret, " "), ``{
			    list op = splitstring (o, "=");
			    if (size (op) == 2)
			    {
				options = add (options, op[0]:"", op[1]:"");
			    }
			});
			card["options"]	= options;
			card["module"]	= modname;
			Sound::save_entry   = (map) eval (card);
			// TODO save the card to unconfigured cards???
		    }
		}
	    }
	});
    }


    /**
     * removes entries from save_info listed by indices in id_list
     * @param save_info list
     * @return list new save_info
     */
    define list<map> recalc_save_entries(list<map> save_info) ``{
	integer pos = 0;
	return maplist(map card, save_info, ``{
	    map opts	= card["options"]:$[];
	    card	= add_alias (card, pos);
	    card	= add_common_options (card, pos);
	    pos		= pos + 1;
	    return card;
	});
    }

}//EOF
