/**
 * File:
 *   controller.ycp
 *
 * Module:
 *   Configuration of disk controller
 *
 * Summary:
 *   Main file
 *
 * Authors:
 *   THomas Fehr <fehr@suse.de>
 *
 * $Id: controller.ycp 19529 2004-09-22 11:40:57Z fehr $
 *
 */

{
textdomain "storage";

import "Wizard";
import "Misc";
/*import "Bootloader";*/

import "Label";
import "Popup";

list<string> initrd = [];
list<string> initrd_orig = [];
map<string,string> params = $[];
map<string,string> params_orig = $[];

define string HelpText()
    ``{
    // help text
    return( _("<p>This module configures the disk controllers that are accessed by a
kernel module.</p>
<p>The table on top contains the controllers to configure. If
there is more than one controller, switch between controllers
by clicking a line in the table.</p>
<p>Influence the order in which modules are loaded with the
buttons to the right of the table.</p>
<p>The next line contains the name of the kernel module for the selected 
controller. There are controllers where more than one alternative kernel
module can be used. If there is more than one module, you can switch 
between these modules.</p>
<p>The next line displays whether the module is currently loaded.</p>
<p>Next, there is a check box in which to determine if the module should be
 loaded at system boot (in initrd).</p>
<p>The text edit field may contain module parameters with which the module
is loaded. These fields can stay empty for most modules.</p>
<p>Last follows a button with which to try loading the selected module
with the given parameters. It is <b>strongly</b> recommended to try that
if you changed module parameters and plan to use your module in initrd.</p>
"));
    }

define term ModComboBox( list modules )
    ``{
    return( `ComboBox( `id(`module), `opt(`editable,`notify), 
		       // label text
                       _("&Module to use:"), modules ));
    };

define term InitCheckBox( boolean val )
    ``{
    return( `CheckBox( `id(`initrd), `opt(`notify),
		       // button text
		       _("&Load module in initrd"), val ));
    };

define void SetModule( map cont )
    ``{
    integer num = 0;
    list mod = maplist( map entry, cont["drivers"]:[],
	``{
	term it = `item( `id(num), mergestring(entry["modules",0]:[]," "), 
	                 cont["modnum"]:0==num );
	num = num+1;
	return( it );
	});
    y2milestone( "mod=%1", mod );
    UI::ReplaceWidget( `id(`rep_mod), ModComboBox( mod ));
    if( !haskey( cont, "modnum" ))
	{
	cont["modnum"] = 0;
	}
    };

define void SetLoaded( string mod )
    ``{
    SCR::UnmountAgent (.proc.modules);
    map lmod = (map)SCR::Read(.proc.modules);
    // label text
    string val = haskey(lmod, mod) ? _("Yes") : _("No");
    UI::ChangeWidget( `id(`loaded), `Value, val );
    y2milestone( "Loaded=%1", val );
    }

define void SetInitrd( string mod )
    ``{
    boolean val = contains(initrd,mod);
    UI::ReplaceWidget( `id(`rep_initrd), InitCheckBox( val ));
    y2milestone( "Initrd=%1", val );
    }

define void SetParams( string mod, string default )
    ``{
    string param = default;
    if( haskey( params, mod ))
	{
	param = params[mod]:"";
	}
    else if( haskey( params_orig, mod ))
	{
	param = params_orig[mod]:"";
	}
    else
	{
	list opts = (list)SCR::Read( .modules.options );
	y2milestone( "ppts=%1", opts ); 
	if( contains( opts, mod ))
	    {
	    map<string,string> options = (map<string,string>)SCR::Read( .modules.options, mod );
	    y2milestone( "options %1 = %2", mod, options ); 
	    foreach( string k, string v, options,
		``{
		if( size(param)>0 )
		    {
		    param = param + " ";
		    }
		if( size(v)==0 )
		    {
		    param = param + k;
		    }
		else
		    {
		    param = param + k + "=" + v;
		    }
		});
	    }
	params_orig[mod] = param;
	}
    UI::ChangeWidget( `id(`param), `Value, param );
    }

define string ModNameChanged( map cont, integer num, integer old )
    ``{
    string oldmod = cont["drivers",old,"modules",0,0]:"";
    string mod = cont["drivers",num,"modules",0,0]:"";
    y2milestone( "mod=%1 old=%2", mod, old );
    if( oldmod != mod )
	{
	params[oldmod] = (string)UI::QueryWidget( `id(`param), `Value );
        if( contains( initrd, oldmod ) )
	    {
	    initrd = maplist( string e, initrd, ``{ return( e==oldmod ? mod : e ); });
	    y2milestone( "new initrd=%1", initrd );
	    }
	}
    SetLoaded( mod );
    SetInitrd( mod );
    SetParams( mod, cont["drivers",num,"modules",1]:"" );
    return( mod );
    }

define integer TermToId( term t )
    ``{
    return( (integer)(t[0,0]:0) );
    }

define integer GetArrayPos( list l, integer num )
    ``{
    integer i = 0;
    while( i < size(l) && TermToId(l[i]:`Empty())!=num )
	{
	i = i+1;
	}
    return( i );
    }

define string SetAllData( map cont, list clist, integer cur )
    ``{
    string ret = "";
    if( size(cont)>0 )
	{
	SetModule( cont );
	integer num = (integer)UI::QueryWidget( `id(`module), `Value );
	ret = ModNameChanged( cont, num, num );
	UI::ChangeWidget( `id(`moveup), `Enabled, GetArrayPos(clist,cur)>0 );
	UI::ChangeWidget( `id(`movedown), `Enabled, 
	                  GetArrayPos(clist,cur)<size(clist)-1 );
	}
    return( ret );
    }

define boolean TestLoad( map cont )
    ``{
    y2milestone( "num %1 drivers %2", cont["modnum"]:0, cont["drivers"]:[] );
    string cmd = "";
    foreach( map e, cont["drivers"]:[],
	``{
	foreach( list m, e["modules"]:[],
	    ``{
	    if( size(m[0]:"")>0 )
		{
		cmd = "/sbin/rmmod -r " + m[0]:"";
		y2milestone( "cmd %1", cmd );
		SCR::Execute( .target.bash, cmd );
		}
	    });
	});
    map m = cont["drivers",cont["modnum"]:0]:$[];
    if( size(m)>0 && size(m["modules"]:[])>0 )
	{
	boolean ok = true;
	string param = (string) UI::QueryWidget( `id(`param), `Value );
	integer num = 0;
	foreach( list d, m["modules"]:[],
	    ``{
	    cmd = m["modprobe"]:false ? "/sbin/modprobe " : "/sbin/insmod ";
	    y2milestone( "cmd %1", cmd );
	    cmd = cmd + d[0]:"" + " ";
	    y2milestone( "cmd %1", cmd );
	    cmd = cmd + (num==0 ? param : d[1]:"");
	    y2milestone( "cmd %1", cmd );
	    map ret = (map)SCR::Execute( .target.bash_output, cmd );
	    if( ret["exit"]:1 == 1)
		{
		// error popup text
		string text = sformat( _("
Error loading module.
Executed cmd was:
    %1

Stderr was:
%2
"), cmd, ret["stderr"]:"" );
		Popup::Error( text );
		ok = false;
		}
	    });
	if( ok )
	    {
	    // popup text
	    Popup::Message( sformat(_("Module \"%1\" successfully loaded"),
	                              m["modules",0,0]:"" ));
	    }
	}
    }

    Wizard::OpenAbortApplyFinishDialog();

    list<map> dev = [];
    foreach( map entry, (list<map>)SCR::Read(.probe.storage), 
	``{
	map conf = (map)SCR::Read(.probe.status, entry["unique_key"]:"");
	y2milestone( "entry:%1", entry );
	y2milestone( "conf:%1", conf );
	if( conf["available"]:`no != `no && size(entry["drivers"]:[])>0 &&
            find( map e, dev, ``(e["bus"]:""==entry["bus"]:"" && 
				 e["slot_id"]:""==entry["slot_id"]:"" &&
				 e["bus_id"]:""==entry["bus_id"]:""))==nil )
	    {
	    entry["conf"] = conf;
	    dev = add( dev, entry );
	    }
	});

    /*
    dev = add( dev, $[ "conf" : $[ "configured" : `new ],
                       "drivers" : [ $[ "modprobe" : false,
		                        "modules" : [ [ "loop_fish2", "" ]]]],
		       "model" : "Loop driver" ] );
    */

    

    boolean first_new = true;
    integer cur_num = 0;
    integer num = 0;
    list<term> clist = maplist( map entry, dev,
	``{
	term it = `item( `id(num) );
	it = add( it, entry["model"]:"" );
	if( entry["conf","configured"]:`no==`new )
	    {
	    // label text
	    it = add( it, _("Yes"));
	    if( first_new )
		{
		first_new = false;
		cur_num = num;
		}
	    }
	else
	    {
	    // label text
	    it = add( it, _("No") );
	    }
	num = num+1;
	return( it );
	});

    string tmp = Misc::SysconfigRead( .sysconfig.kernel.INITRD_MODULES, "");
    initrd_orig = filter( string e, splitstring( tmp, " " ), ``(size(e)>0) );
    initrd = initrd_orig;
    y2milestone( "initrd %1", tmp );

    clist = sort( term a, term b, clist,
	``{
	boolean ret = true;
	integer numa = TermToId(a);
	integer numb = TermToId(b);
	list pos_mod = [];
	foreach( map e, dev[numa,"drivers"]:[],
	    ``{
	    if( size(e["modules",0,0]:"")>0 )
		pos_mod = add( pos_mod, e["modules",0,0]:"");
	    });
	integer initrda = -1;
	integer initrdb = -1;
	integer posm = 0;
	integer posi = 0;
	while( posm < size(pos_mod) && initrda==-1 )
	    {
	    posi = 0;
	    while( posi<size(initrd) && initrda==-1 )
		{
		if( initrd[posi]:"" == pos_mod[posm]:"" )
		    {
		    initrda = posi;
		    }
		posi = posi + 1;
		}
	    posm = posm + 1;
	    }
	pos_mod = [];
	foreach( map e, dev[numb,"drivers"]:[],
	    ``{
	    if( size(e["modules",0,0]:"")>0 )
		pos_mod = add( pos_mod, e["modules",0,0]:"");
	    });
	posm = 0;
	while( posm < size(pos_mod) && initrdb==-1 )
	    {
	    posi = 0;
	    while( posi<size(initrd) && initrdb==-1 )
		{
		if( initrd[posi]:"" == pos_mod[posm]:"" )
		    {
		    initrdb = posi;
		    }
		posi = posi + 1;
		}
	    posm = posm + 1;
	    }
	y2debug( "a:%1 initrda:%2 b:%3 initrdb:%4", numa, initrda, numb, 
		 initrdb );
	if( initrda!=-1 && initrdb!=-1 )
	    {
	    ret = initrda<=initrdb ;
	    }
	else if( initrda==-1 && initrdb==-1 )
	    {
	    boolean newa = dev[numa,"conf","configured"]:`no == `new;
	    boolean newb = dev[numb,"conf","configured"]:`no == `new;
	    if( newa && !newb )
		ret = false;
	    else
		ret = true;
	    }
	else
	    {
	    ret = initrda!=-1;
	    }
	ret = ret && a!=b;
	y2debug( "ret %1", ret );
	return( ret );
	});
    y2milestone( "clist=%1", clist );
    
    term dialog =
	`VBox( 
	    `VSpacing(1),
	    `Left(`HBox( `HWeight(70, `Table( `id(`controller),
	                                      `opt(`notify,`immediate,`keepSorting),
					      // table heading text
	                                      `header( _("Disk Controller"), _("New")),
					      clist )), 
			 `HWeight(30, 
			      // button text
			     `VBox( `PushButton( `id(`moveup), _("Move &Up") ),
			            `PushButton( `id(`movedown), 
						 // button text
				                 _("Move &Down")))))),
	    `VSpacing(1),
	    `Left(`HBox( `HWeight(40, `ReplacePoint(`id(`rep_mod),
	                                            ModComboBox( [] ) )),
			 `HWeight(60, `HStretch()) )),
	    `VSpacing(1),
	    // label text
	    `Left(`HBox( `Label( _("Module currently loaded:")), `HSpacing(2),
			 // label text
		         `Label( `id(`loaded), _("Yes") ))),
	    `VSpacing(1),
	    `Left(`ReplacePoint(`id(`rep_initrd), InitCheckBox( false ))),
	    `VSpacing(1),
	    // label text
	    `Left(`TextEntry( `id(`param), _("Module &Parameters"), "abdef" )),
	    `VSpacing(1),
	    // button text
	    `PushButton( `id(`test_load), _("&Test loading of module")),
	    `VSpacing(2) 
	    );

    if( size(dev)==0 )
	{
	// popup text
	Popup::Message( _("No configurable controller found") );
	UI::CloseDialog();
	return `cancel;
	}
    // heading text
    Wizard::SetContents( _("Disk controller configuration"), dialog,
			 HelpText(), true, true );
    Wizard::SetDesktopIcon("controller");

    UI::ChangeWidget( `id(`controller), `CurrentItem, cur_num );
    string modname = SetAllData( dev[cur_num]:$[], clist, cur_num );
    symbol ret = `none;
    boolean changed = false;
    y2milestone( "cur %1", dev[cur_num]:$[] );
    do
	{
	ret = (symbol)UI::UserInput();
	y2milestone( "ret = %1", ret );
	if( ret == `controller )
	    {
	    num = (integer)UI::QueryWidget( `id(`controller), `CurrentItem );
	    y2milestone( "num %1 cur_num %2", num, cur_num );
	    if( num != cur_num )
		{
		cur_num = num;
		params[modname] = (string)UI::QueryWidget( `id(`param), `Value );
		modname = SetAllData( dev[cur_num]:$[], clist, cur_num );
		y2milestone( "controller module=%1", modname );
		}
	    }
	else if( ret == `moveup )
	    {
	    changed = true;
	    integer rpos = GetArrayPos( clist, cur_num );
	    term tmp = clist[rpos]:`Empty();
	    clist[rpos] = clist[rpos-1]:`Empty();
	    clist[rpos-1] = tmp;
	    UI::ChangeWidget( `id(`controller), `Items, clist );
	    UI::ChangeWidget( `id(`controller), `CurrentItem, cur_num );
	    rpos = GetArrayPos( clist, cur_num );
	    y2milestone( "items=%1", clist );
	    y2milestone( "rpos=%1", rpos );
	    UI::ChangeWidget( `id(`moveup), `Enabled, rpos>0 );
	    UI::ChangeWidget( `id(`movedown), `Enabled, rpos<size(clist)-1 );
	    }
	else if( ret == `movedown )
	    {
	    changed = true;
	    integer rpos = GetArrayPos( clist, cur_num );
	    term tmp = clist[rpos]:`Empty();
	    clist[rpos] = clist[rpos+1]:`Empty();
	    clist[rpos+1] = tmp;
	    UI::ChangeWidget( `id(`controller), `Items, clist );
	    UI::ChangeWidget( `id(`controller), `CurrentItem, cur_num );
	    rpos = GetArrayPos( clist, cur_num );
	    y2milestone( "items=%1", clist );
	    y2milestone( "rpos=%1", rpos );
	    UI::ChangeWidget( `id(`moveup), `Enabled, rpos>0 );
	    UI::ChangeWidget( `id(`movedown), `Enabled, rpos<size(clist)-1 );
	    }
	else if( ret == `module )
	    {
	    num = (integer)UI::QueryWidget( `id(`module), `Value );
	    if( num != dev[cur_num,"modnum"]:0 )
		{
		changed = true;
		modname = ModNameChanged( dev[cur_num]:$[], num, 
		                          dev[cur_num,"modnum"]:0 );
		dev[cur_num,"modnum"] = num;
		y2milestone( "module=%1", modname );
		}
	    }
	else if( ret == `initrd )
	    {
	    changed = true;
	    if( contains( initrd, modname ))
		{
		initrd = filter( string e, initrd, ``(e!=modname));
		}
	    else
		{
		initrd = add( initrd, modname );
		}
	    y2milestone( "initrd=%1", initrd );
	    }
	else if( ret == `test_load )
	    {
	    TestLoad( dev[cur_num]:$[] );
	    SetLoaded( modname );
	    }
	else if( ret == `apply || ret == `finish )
	    {
	    boolean mkinitrd = false;
	    params[modname] = (string)UI::QueryWidget( `id(`param), `Value );
	    list<string> new_initrd = [];
	    list<string> tlist = initrd;
	    foreach( term e, clist,
		``{
		integer num = TermToId( e );
		integer modnum = dev[num,"modnum"]:0;
		if( contains( tlist, 
		              dev[num,"drivers",modnum,"modules",0,0]:"" ) )
		    {
		    foreach( list d, dev[num,"drivers",modnum,"modules"]:[],
			``{
			new_initrd = add( new_initrd, d[0]:"" );
			tlist = filter( string e, tlist, ``(e!=d[0]:""));
			if( size(params[d[0]:""]:"")==0 )
			    params[d[0]:""] = d[1]:"";
			mkinitrd = mkinitrd || size(d[1]:"")>0;
			});
		    }
		});
	    foreach( string e, initrd,
		``{
		if( size(e)>0 && !contains( new_initrd, e ) )
		    new_initrd = add( new_initrd, e );
		});
	    string newinit = mergestring( new_initrd, " " );
	    string old = Misc::SysconfigRead( .sysconfig.kernel.INITRD_MODULES,
	                                      "");
	    mkinitrd = mkinitrd || newinit != old;
	    if( newinit != old )
		{
		initrd = new_initrd;
		initrd_orig = new_initrd;
		}
	    y2milestone( "make %3 new \"%1\" old \"%2\"", newinit, old, 
	                 mkinitrd );
	    y2milestone( "params %1", params );
	    boolean writemod = false;
	    foreach( string k, string v, params, 
		``{
		y2milestone( "par orig:\"%1\" new:\"%2\"", params_orig[k]:"", 
		             v );
		if( params_orig[k]:"" != v )
		    {
		    mkinitrd = mkinitrd || contains(new_initrd,k);
		    params_orig[k] = v;
		    list<string> val = filter( string e, splitstring( v, " " ), 
					       ``(size(e)>0) );
		    y2milestone( "val %1", val );
		    map opts = $[];
		    foreach( string v, val,
			``{
			integer pos = find( v, "=" );
			y2milestone( "v %1 pos %2", v, pos );
			if( pos<0 )
			    {
			    opts[v] = "";
			    }
			else
			    {
			    opts[substring(v,0,pos)] = substring( v, pos+1 );
			    }
			});
		    writemod = true;
		    y2milestone( "k %1 opts:%2 write:%3", k, opts, writemod );
		    SCR::Write( .modules.options, opts, k );
		    }
		});
	    if( writemod )
		{
		y2milestone( "writing modules.conf" );
		SCR::Write( .modules, nil );
		y2milestone( "finished writing modules.conf" );
		}
	    y2milestone( "mkinitrd %1", mkinitrd );
	    if( mkinitrd )
		{
		SCR::Write( .sysconfig.kernel.INITRD_MODULES, newinit );
		SCR::Write( .sysconfig.kernel, nil );
		// popup text
		UI::OpenDialog( `VBox( `Label( _("Executing mkinitrd") ) ) );
		integer exit = (integer)SCR::Execute( .target.bash, "/sbin/mkinitrd" );
		UI::CloseDialog();
		y2milestone( "exit /sbin/mkinitrd %1", exit );
		if( exit != 0 )
		    {
		    // popup text
		    Popup::Error( _("Executing mkinitrd failed.
Try executing /sbin/mkinitrd manually and look at the error messages.
"));
		    }
		else
		    {
		    map boot_write = $[];
		    boot_write["insert_initrd"] = true;
		    boot_write["initrd_changed_externally"] = true;
		    boot_write["forbid_save_initrd"] = true;
		    /* FIXME 
		    Bootloader::SetWriteMode( boot_write );
		    Bootloader::Write();
		    */
		    }
		}
	    foreach( map entry, dev,
		``{
		if( size(entry["unique_key"]:"")>0 )
		    SCR::Write(.probe.status.configured, entry["unique_key"]:"",
			       `yes);
		});
	    changed = false;
	    SCR::Execute( .target.bash, "/sbin/hwscan --disk" );
	    SCR::Execute( .target.bash, "/sbin/hwscan --cdrom" );
	    }
	else if( ret == `abort || ret == `cancel )
	    {
	    if( changed )
		{
		// popup text
		if( !Popup::YesNo( _("You have unsaved changes.
These changes will be lost if you abort now.

Discard changes?
")))
		    {
		    ret = `continue;
		    }
		}
	    }
	}
    while( ret != `abort && ret != `finish );
    UI::CloseDialog();

}
