#!/usr/bin/perl -w
#
# $Id$
# Author: Martin Vidner <mvidner@suse.cz>
#
# An agent for parsing and writing /etc/yp.conf.

# $comment: all comments, excluding the hash signs, separated by newlines.
#  they will be written to the beginning of file
# @servers: a list of ypservers (for the local domain)
# %domainservers:  keys are domains, values are lists of servers
# %broadcast: keys are domains, values boolean refs
#  if true, broadcast will be used as fallback.
#  The maps must have the same set of keys!
# $defaultbroadcast: boolean
#
# The interface is not very pretty, but it works
# and Nis::Read beautifies it for YaST2 anyway.

use ycp;
use strict;
use Errno qw(ENOENT);

my $comment;
my @servers;
my %domainservers;
my %broadcast;
my %slp;
my $defaultbroadcast;

my $filename;
my $modified;

# br stands for boolean reference
# convert a perl boolean to a value that ycp::Return(foo, 1) will recognize
sub perl_to_br ($)
{
    return $_[0]? \ "true" : \ "false";
}

sub br_to_perl ($)
{
    my $br = shift;
    return (ref ($br) eq "SCALAR" && $$br eq "true");
}

# fill in default values for missing entries
sub complete_maps ()
{
    my $domain;
    foreach $domain (keys %domainservers)
    {
	if (!defined $broadcast{$domain})
	{
	    $broadcast{$domain} = perl_to_br (0);
	}
    }
    foreach $domain (keys %broadcast)
    {
	if (!defined $domainservers{$domain})
	{
	    $domainservers{$domain} = [];
	}
    }
    foreach $domain (keys %slp)
    {
	if (!defined $domainservers{$domain})
	{
	    $domainservers{$domain} = [];
	}
    }
}

# return false if an error occurred
sub parse_file ()
{
    $comment = "";
    @servers = ();
    %domainservers = ();
    $defaultbroadcast = 0;

    if (!open (FILE, $filename))
    {
	return 1 if ($! == ENOENT); # ok if it is not there
	y2error ("$filename: $!");
	return 0;
    }

    while (<FILE>)
    {
	chomp;
	if (s/\#(.*)//)		# get comments
	{
	    $comment .= "$1\n";
	}
	s/^\s*//;		# strip leading whitespace
	next if /^$/;		# skip empty lines
	if (/^ypserver\s+(\S+)/)
	{
	    push @servers, $1;
	}
	elsif (/^domain\s+(\S+)\s+server\s+(\S+)/)
	{
	    push @{$domainservers{$1}}, $2;
	}
	elsif (/^domain\s+(\S+)\s+broadcast\b/)
	{
	    $broadcast{$1} = perl_to_br (1);
	}
	elsif (/^domain\s+(\S+)\s+slp\b/)
	{
	    $slp{$1} = perl_to_br (1);
	}
	elsif (/^broadcast\b/)
	{
	    $defaultbroadcast = 1;
	}
	else
	{
	    y2error ("Parse error: <$_>");
	    close (FILE);
	    return 0;
	}
    }
    close (FILE);
    complete_maps ();
    return 1;
}

sub write_file ()
{
    return 1 if (! $modified);

    complete_maps ();
    open (FILE, ">$filename.YaST2.new") or return y2error ("$filename: $!"), 0;

    foreach (split /\n/, $comment)
    {
	print FILE "#$_\n";
    }
    foreach (@servers)
    {
	print FILE "ypserver $_\n";
    }
    if ($defaultbroadcast)
    {
	print FILE "broadcast\n";
    }
    foreach my $domain (sort keys %domainservers)
    {
	foreach my $server (@{$domainservers{$domain}})
	{
	    print FILE "domain $domain server $server\n";
	}
	if (br_to_perl ($broadcast{$domain}))
	{
	    print FILE "domain $domain broadcast\n";
	}
	if (br_to_perl ($slp{$domain}))
	{
	    print FILE "domain $domain slp\n";
	}
    }
    close (FILE);

    if (-f $filename)
    {
	rename $filename, "$filename.YaST2.save" or return y2error ("$filename: $!"), 0;
    }
    rename "$filename.YaST2.new", $filename or return  y2error ("$filename: $!"), 0;
    $modified = 0;
    return 1;
}


#
# MAIN cycle
#

# read the agent arguments
$_ = <STDIN>;
# no input at all - simply exit
exit if ! defined $_;
# reply to the client (this actually gets eaten by the ScriptingAgent)
ycp::Return (undef);
print "\n";

my ($symbol, $fn, undef) = ycp::ParseTerm ($_);
if ($symbol ne "YpConf")
{
    y2error ("The first command must be the configuration.(Seen '$_')");
    exit;
}
else
{
    $filename = $fn || "/etc/yp.conf";
}


# if reading fails, defaulting to no servers
# TODO error state - copy from another agent
parse_file ();
$modified = 0;

while ( <STDIN> )
{
    my ($command, $path, $argument) = ycp::ParseCommand ($_);

    if ($command eq "Dir")
    {
	if ($path eq ".")
	{
	    ycp::Return ([
			  "broadcast",
			  "comment",
			  "defaultbroadcast",
			  "domainservers",
			  "servers",
			  "slp"
			 ]);
	}
	else
	{
	    ycp::Return ([]);
	}
    }

    elsif ($command eq "Write")
    {
	my $result = "true";
	if ($path eq ".servers" && ref ($argument) eq "ARRAY")
	{
	    @servers = @{$argument};
	    $modified = 1;
	}
	elsif ($path eq ".domainservers" && ref ($argument) eq "HASH")
	{
	    foreach my $v (values %{$argument})
	    {
		if (ref ($v) ne "ARRAY")
		{
		    y2error ("Domainservers value not a list: $v");
		    $result = "false";
		    last;
		}
	    }
	    if ($result eq "true")
	    {
		%domainservers = %{$argument};
		$modified = 1;
	    }
	}
	elsif ($path eq ".broadcast" && ref ($argument) eq "HASH")
	{
	    # broadcast must be maintained as boolean references
	    my %broadcast_norefs = %{$argument};
	    %broadcast = ();
	    while (my ($k, $v) = each %broadcast_norefs)
	    {
		$broadcast{$k} = perl_to_br ($v);
	    }
	    $modified = 1;
	}
	elsif ($path eq ".slp" && ref ($argument) eq "HASH")
	{
	    # broadcast must be maintained as boolean references
	    my %slp_norefs = %{$argument};
	    %slp = ();
	    while (my ($k, $v) = each %slp_norefs)
	    {
		$slp{$k} = perl_to_br ($v);
	    }
	    $modified = 1;
	}
	elsif ($path eq ".defaultbroadcast" && !ref ($argument))
	{
	    $defaultbroadcast = $argument;
	    $modified = 1;
	}
	elsif ($path eq ".comment" && !ref ($argument))
	{
	    $comment = $argument;
	    $modified = 1;
	}
	elsif ($path eq "." && !defined ($argument))
	{
	    $result = write_file () ? "true":"false";
	}
	elsif ($path eq "." && ref($argument) eq "HASH")
	{
	    # a hack to make testing simple
	    $comment = $argument->{"comment"};
	    @servers = @{$argument->{"servers"}};
	    %domainservers = %{$argument->{"domainservers"}};
	    # broadcast must be maintained as boolean references
	    my %broadcast_norefs = %{$argument->{"broadcast"}};
	    %broadcast = ();
	    while (my ($k, $v) = each %broadcast_norefs)
	    {
		$broadcast{$k} = perl_to_br ($v);
	    }
	    # slp must be maintained as boolean references
	    my %slp_norefs = %{$argument->{"slp"}};
	    %slp = ();
	    while (my ($k, $v) = each %slp_norefs)
	    {
		$slp{$k} = perl_to_br ($v);
	    }
	    $defaultbroadcast = $argument->{"defaultbroadcast"};
	    $modified = 1;
	}
	else
	{
	    y2error ("Wrong path $path or argument: ", ref ($argument));
	    $result = "false";
	}

	ycp::Return ($result);
    }

    elsif ($command eq "Read")
    {
	if ($path eq ".servers")
	{
	    ycp::Return (\@servers, 1);
	}
	elsif ($path eq ".domainservers")
	{
	    ycp::Return (\%domainservers, 1);
	}
	elsif ($path eq ".broadcast")
	{
	    ycp::Return (\%broadcast, 1);
	}
	elsif ($path eq ".slp")
	{
	    ycp::Return (\%slp, 1);
	}
	elsif ($path eq ".defaultbroadcast")
	{
	    ycp::Return (perl_to_br ($defaultbroadcast), 1);
	}
	elsif ($path eq ".comment")
	{
	    ycp::Return ($comment, 1);
	}
	elsif ($path eq ".")
	{
	    # a hack to make testing simple
	    ycp::Return ({
			  "comment" => $comment,
			  "domainservers" => \%domainservers,
			  "broadcast" => \%broadcast,
			  "defaultbroadcast" => perl_to_br ($defaultbroadcast),
			  "servers" => \@servers,
			  "slp" => \%slp,
			  }, 1);
	}
	else
	{
	    y2error ("Unrecognized path! '$path'");
	    ycp::Return (undef);
	}
    }

    elsif ($command eq "result")
    {
	last;
    }

    # Unknown command
    else
    {
	y2error ("Unknown instruction $command or argument: ", ref ($argument));
	ycp::Return (undef);
    }
    print "\n";
}

write_file ();
