#!/usr/bin/perl -w

use strict;
use File::Copy;
use File::Path;
use File::Spec::Functions;
use Getopt::Long;
use MIME::Parser;
use Config::General;
use Ddtc::Common;
use Ddtc::Package;
use Ddtc::Menu;
use Algorithm::Diff qw(diff sdiff);
use Text::Iconv;
use Term::ReadLine;
use Term::ANSIColor;

#
# Constants setting
#

use constant VERSION	=> "0.17";			# script version number
use constant CONFFILE	=> ".ddtcrc";			# config file

my %DDTS_CHARSETS = (
	de	=> "ISO-8859-1",
	da	=> "ISO-8859-1",
	fr	=> "ISO-8859-1",
	it	=> "ISO-8859-1",
	nl	=> "ISO-8859-1",
	pl	=> "ISO-8859-2",
	hu	=> "ISO-8859-2",
	pt_BR	=> "ISO-8859-1",
	pt_PT	=> "ISO-8859-1",
	sk	=> "ISO-8859-2",
	sv_SE	=> "ISO-8859-1",
	ja	=> "euc-jp",
	uk	=> "koi8-u",
	ru	=> "koi8-r",
	cs	=> "ISO-8859-2",
	fi	=> "ISO-8859-1",
	es	=> "ISO-8859-1",
	eo	=> "UTF-8"
	);

my $Me = $0;						# get command name with path
   $Me =~ s/.*\/([^\/]*)$/$1/;				# keep script name
#
# Configuration
#


my $Base_dir;			# ddts base directory
my $Tr_dir;			# directory for translations
my $Rev_dir;			# directory for reviews
my $Bug_dir;			# directory for bugs

my $Todo_e;			# file extension for files to review/translate
my $Tr_e;			# file extension for translated files
my $Rev_e;			# file extension for reviewed files
my $Lok_e;			# file extension for locally accepted files
my $Bug_e;			# file extension for bug report files
my $Fix_e;			# file extension for bugs fixed files
my $Sent_e;			# file extension for sent files
my $Old_e;			# file extension for old files
my $Ok_e;			# file extension for descriptions which passed the review process

my $Mail_out;			# outgoing mail address
my $Mail_in;			# incoming mail address
my $Mail_server;		# server mail address
my $Mail_self;			# send mails to myself also if set to `yes'
my $Mail_enc;			# mail encoding for mime
my $Mail_charset;		# mail charset for mime

my $Comment;			# comment string a space will be added
my $Clean_re;			# regexp for clean command
my $Language;			# language postfix
my $Ddtc_bug;			# create non standard ddts bug reports
my $Editor;			# editor

my @Menu_colors;		# menu colors
my $Menu_title_color;		# menu title color
my $Menu_letter_color;		# menu letter color
my $Menu_text_color;		# menu text color

my $Term;			# terminal


# load_conf
#
# set global variables
#
# input:
#   config hash
sub load_conf(%) {
	my %conf = @_;

	$Base_dir		= $conf{'base_dir'};		# ddts base directory
	$Comment		= $conf{'comment'};		# comment string a space will be added
	$Clean_re		= $conf{'clean_regex'};		# regexp for clean command
	$Editor			= $conf{'editor'};		# editor
	$Ddtc_bug		= $conf{'ddtc_bug'};		# create non standard ddts bug reports
	$Language		= $conf{'language'};		# language postfix
	
	$Mail_out		= $conf{'mail_out'};		# outgoing mail address
	$Mail_in		= $conf{'mail_in'};		# incoming mail address
	$Mail_server		= $conf{'mail_server'};		# server mail address
	$Mail_enc		= $conf{'mail_encoding'};	# mail encoding for mime
	$Mail_charset		= $conf{'mail_charset'};	# mail charset for mime
	$Mail_self		= $conf{'mail_self'};		# send mails to myself also if set to `yes'
	
	$Menu_title_color	= $conf{'menu_title_color'};	# menu title color
	$Menu_letter_color	= $conf{'menu_letter_color'};	# menu letter color
	$Menu_text_color	= $conf{'menu_text_color'};	# menu text color
	@Menu_colors		=($Menu_letter_color, $Menu_text_color, $Menu_title_color);
	
	# set directories
	$Base_dir = "$ENV{HOME}/$Base_dir" unless $Base_dir =~ /^\//;
	  $Tr_dir = "$Base_dir/tr";
	 $Rev_dir = "$Base_dir/rev";
	 $Bug_dir = "$Base_dir/bug";
	
	# set extentions
	$Todo_e	 = "todo";
	$Tr_e	 = "tr";
	$Rev_e	 = "rev";
	$Lok_e	 = "lok";
	$Bug_e	 = "bug";
	$Fix_e	 = "fix";
	$Sent_e	 = "sent";
	$Old_e	 = "old";
	$Ok_e	 = "ok";

	# set incomming email address if not defined
	$Mail_in = $Mail_out unless $Mail_in;
	
	# set debug level
	set_debug ($conf{'debug'});
}

my %conf = (
	base_dir		=> 'ddts',
	comment			=> '>>',
	clean_regex		=> '~$|\.old$|\.bak$',
	editor			=> 'sensible-editor',
	ddtc_bug		=> 0,
	language		=> '',
	debug			=> 1,

	mail_out		=> $ENV{EMAIL} || $ENV{DEBEMAIL} || '',
	mail_in			=> '',
	mail_server		=> 'pdesc@ddtp.debian.org',
	mail_encoding		=> '8bit',
	mail_charset		=> 'iso-8859-1',
	mail_self		=> 1,

	menu_title_color	=> 'bold cyan',
	menu_letter_color	=> 'bold cyan',
	menu_text_color		=> 'cyan',
	);

if (-e catfile($ENV{HOME}, CONFFILE)) {
	my $config = new Config::General (
		-ConfigFile		=> catfile($ENV{HOME}, CONFFILE),
		-AllowMultiOptions	=> 'no',
		-LowerCaseNames		=> 'yes',
		-UseApacheInclude	=> 'no',
		-MergeDuplicateOptions	=> 'yes',
		-AutoTrue		=> 'yes',
		-DefaultConfig		=> \%conf
		);

	%conf = $config -> getall;
}

load_conf %conf;

################################################################################
#
# standardise directories and file extentions
#
{
if ((exists $conf{'tr_dir'  })
 || (exists $conf{'rev_dir' })
 || (exists $conf{'bug_dir' })
 || (exists $conf{'todo_ext'})
 || (exists $conf{'tr_ext'  })
 || (exists $conf{'rev_ext' })
 || (exists $conf{'lok_ext' })
 || (exists $conf{'bug_ext' })
 || (exists $conf{'fix_ext' })
 || (exists $conf{'sent_ext'})
 || (exists $conf{'old_ext' })
 || (exists $conf{'ok_ext'  })) {
	warning "Standardising directories and file extentions.\n";
	sleep 3;

	$Tr_dir	 = $conf{'tr_dir'}   || "tr";
	$Rev_dir = $conf{'rev_dir'}  || "rev";
	$Bug_dir = $conf{'bug_dir'}  || "bug";
	$Todo_e	 = $conf{'todo_ext'} || "todo";
	$Tr_e	 = $conf{'tr_ext'}   || "tr";
	$Rev_e	 = $conf{'rev_ext'}  || "rev";
	$Lok_e	 = $conf{'lok_ext'}  || "lok";
	$Bug_e	 = $conf{'bug_ext'}  || "bug";
	$Fix_e	 = $conf{'fix_ext'}  || "fix";
	$Sent_e	 = $conf{'sent_ext'} || "sent";
	$Old_e	 = $conf{'old_ext'}  || "old";
	$Ok_e	 = $conf{'ok_ext'}   || "ext";
	  $Tr_dir = "$Base_dir/$Tr_dir"    unless   $Tr_dir =~ /^\//;
	 $Rev_dir = "$Base_dir/$Rev_dir"   unless  $Rev_dir =~ /^\//;
	 $Bug_dir = "$Base_dir/$Bug_dir"   unless  $Bug_dir =~ /^\//;

if (( $Tr_dir ne "$Base_dir/tr" )
 || ($Rev_dir ne "$Base_dir/rev")
 || ($Bug_dir ne "$Base_dir/bug")
 || ( $Fix_e ne "fix" )
 || ( $Bug_e ne "bug" )
 || (  $Ok_e ne "ok"  )
 || ($Todo_e ne "todo")
 || (  $Tr_e ne "tr"  )
 || ( $Rev_e ne "rev" )
 || ( $Lok_e ne "lok" )
 || ($Sent_e ne "sent")
 || ( $Old_e ne "old" )) {
	warning "Moving directories...\n";
	sleep 2;
	if ((-d  $Tr_dir) && ( $Tr_dir ne "$Base_dir/tr" )) {
		warning "`$Tr_dir' to `$Base_dir/tr'\n";
		suicide "`$Base_dir/tr already exists, please remove it and fix config file if requested\n" if -e "$Base_dir/tr";
		move( $Tr_dir, "$Base_dir/tr" ) or suicide "move failed: $!";
	}
	if ((-d $Rev_dir) && ($Rev_dir ne "$Base_dir/rev")) {
		warning "`$Rev_dir' to `$Base_dir/rev'\n";
		suicide "`$Base_dir/rev already exists, please remove it and fix config file if requested\n" if -e "$Base_dir/rev";
		move($Rev_dir, "$Base_dir/rev") or suicide "move failed: $!";
	}
	if ((-d $Bug_dir) && ($Bug_dir ne "$Base_dir/bug")) {
		warning "`$Bug_dir' to `$Base_dir/bug'\n";
		suicide "`$Base_dir/bug already exists, please remove it and fix config file if requested\n" if -e "$Base_dir/bug";
		move($Bug_dir, "$Base_dir/bug") or suicide "move failed: $!";
	}

	warning "Moving extentions...\n";
	warning "Entering `$Base_dir/tr'.\n";
	sleep 2;
	opendir (DIR, "$Base_dir/tr")	or suicide __("Cannot read the contents of `%s': %s\n"), "$Base_dir/tr", $!;
	if ( $Old_e ne "old" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Old_e$//;
			warning "moving `$_.$Old_e' to `$_.old'\n";
			move("$Base_dir/tr/$_.$Old_e", "$Base_dir/tr/$_.old") or suicide "move failed: $!";
		}
	}
	if (  $Ok_e ne "ok"  ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Ok_e$//;
			warning "moving `$_.$Ok_e' to `$_.ok'\n";
			move("$Base_dir/tr/$_.$Ok_e", "$Base_dir/tr/$_.ok") or suicide "move failed: $!";
		}
	}
	if ($Sent_e ne "sent") {
		foreach (readdir(DIR)) {
			next unless s/\.$Sent_e$//;
			warning "moving `$_.$Sent_e' to `$_.sent'\n";
			move("$Base_dir/tr/$_.$Sent_e", "$Base_dir/tr/$_.sent") or suicide "move failed: $!";
		}
	}
	if (  $Tr_e ne "tr" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Tr_e$//;
			warning "moving `$_.$Tr_e' to `$_.tr'\n";
			move("$Base_dir/tr/$_.$Tr_e", "$Base_dir/tr/$_.tr") or suicide "move failed: $!";
		}
	}
	if ($Todo_e ne "todo") {
		foreach (readdir(DIR)) {
			next unless s/\.$Todo_e$//;
			warning "moving `$_.$Todo_e' to `$_.todo'\n";
			move("$Base_dir/tr/$_.$Todo_e", "$Base_dir/tr/$_.todo") or suicide "move failed: $!";
		}
	}
	closedir DIR;

	warning "Entering `$Base_dir/rev'.\n";
	sleep 2;
	opendir (DIR, "$Base_dir/rev")	or suicide __("Cannot read the contents of `%s': %s\n"), "$Base_dir/rev", $!;
	if ( $Old_e ne "old" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Old_e$//;
			warning "moving `$_.$Old_e' to `$_.old'\n";
			move("$Base_dir/rev/$_.$Old_e", "$Base_dir/rev/$_.old") or suicide "move failed: $!";
		}
	}
	if (  $Ok_e ne "ok"  ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Ok_e$//;
			warning "moving `$_.$Ok_e' to `$_.ok'\n";
			move("$Base_dir/rev/$_.$Ok_e", "$Base_dir/rev/$_.ok") or suicide "move failed: $!";
		}
	}
	if ( $Lok_e ne "lok" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Lok_e$//;
			warning "moving `$_.$Lok_e' to `$_.lok'\n";
			move("$Base_dir/rev/$_.$Lok_e", "$Base_dir/rev/$_.lok") or suicide "move failed: $!";
		}
	}
	if ($Sent_e ne "sent") {
		foreach (readdir(DIR)) {
			next unless s/\.$Sent_e$//;
			warning "moving `$_.$Sent_e' to `$_.sent'\n";
			move("$Base_dir/rev/$_.$Sent_e", "$Base_dir/rev/$_.sent") or suicide "move failed: $!";
		}
	}
	if ( $Rev_e ne "rev" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Rev_e$//;
			warning "moving `$_.$Rev_e' to `$_.rev'\n";
			move("$Base_dir/rev/$_.$Rev_e", "$Base_dir/rev/$_.rev") or suicide "move failed: $!";
		}
	}
	if ($Todo_e ne "todo") {
		foreach (readdir(DIR)) {
			next unless s/\.$Todo_e$//;
			warning "moving `$_.$Todo_e' to `$_.todo'\n";
			move("$Base_dir/rev/$_.$Todo_e", "$Base_dir/rev/$_.todo") or suicide "move failed: $!";
		}
	}
	closedir DIR;

	warning "Entering `$Base_dir/bug'.\n";
	sleep 2;
	opendir (DIR, "$Base_dir/bug")	or suicide __("Cannot read the contents of `%s': %s\n"), "$Base_dir/bug", $!;
	if ( $Old_e ne "old" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Old_e$//;
			warning "moving `$_.$Old_e' to `$_.old'\n";
			move("$Base_dir/bug/$_.$Old_e", "$Base_dir/bug/$_.old") or suicide "move failed: $!";
		}
	}
	if ( $Fix_e ne "fix" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Fix_e$//;
			warning "moving `$_.$Fix_e' to `$_.fix'\n";
			move("$Base_dir/bug/$_.$Fix_e", "$Base_dir/bug/$_.fix") or suicide "move failed: $!";
		}
	}
	if ( $Bug_e ne "bug" ) {
		foreach (readdir(DIR)) {
			next unless s/\.$Bug_e$//;
			warning "moving `$_.$Bug_e' to `$_.bug'\n";
			move("$Base_dir/bug/$_.$Bug_e", "$Base_dir/bug/$_.bug") or suicide "move failed: $!";
		}
	}
	closedir DIR;

	warning "File operations completed.\n";
}

	warning "Preparing new config file...\n";

	$conf{'base_dir'} =~ s/$ENV{HOME}\///	if $conf{'base_dir'};

	delete $conf{'base_dir'}		if $Base_dir		eq "$ENV{HOME}/ddts";
	delete $conf{'comment'}			if $Comment		eq ">>";
	delete $conf{'clean_regex'}		if $Clean_re		eq "~\$|\\.old\$|\\.bak\$";
	delete $conf{'editor'}			if $Editor		eq "sensible-editor";
	delete $conf{'ddtc_bug'}		unless $Ddtc_bug;
	delete $conf{'language'}		unless $conf{'language'};
	delete $conf{'debug'}			if $conf{'debug'}	== 1;

	delete $conf{'mail_out'}		if $Mail_out		eq ($ENV{EMAIL} || $ENV{DEBEMAIL} || '');
	delete $conf{'mail_in'}			if $Mail_in		eq $Mail_out;
	delete $conf{'mail_self'}		if $Mail_self;
	delete $conf{'mail_encoding'}		if $Mail_enc		eq "8bit";
	delete $conf{'mail_charset'}		if $Mail_charset	eq "iso-8859-1";

	delete $conf{'menu_title_color'}	if $Menu_title_color	eq "bold cyan";
	delete $conf{'menu_letter_color'}	if $Menu_letter_color	eq "bold cyan";
	delete $conf{'menu_text_color'}		if $Menu_text_color	eq "cyan";

	foreach (keys %conf) {
		next if $_ eq 'base_dir';
		next if $_ eq 'comment';
		next if $_ eq 'clean_regex';
		next if $_ eq 'editor';
		next if $_ eq 'ddtc_bug';
		next if $_ eq 'language';
		next if $_ eq 'debug';

		next if $_ eq 'mail_out';
		next if $_ eq 'mail_in';
		next if $_ eq 'mail_self';
		next if $_ eq 'mail_encoding';
		next if $_ eq 'mail_charset';

		next if $_ eq 'menu_title_color';
		next if $_ eq 'menu_letter_color';
		next if $_ eq 'menu_text_color';

		delete $conf{$_};
	}

	# save new config file
	my $config = new Config::General ((-ConfigHash		=> \%conf));
	   $config -> save_file(catfile($ENV{HOME}, CONFFILE));
	
	warning "New config file saved.\n";
	warning "Please restart ddtc.\n";
	exit -1;
}
}
#
# standardise directories and file extentions
#
################################################################################


#
# Tests
#


# test_mail
#
# Stop if mail address is not defined
#

sub test_mail() {
	suicide __("Email address not defined! Please define your environment EMAIL variable or use `mail_out' and/or `mail_in' in your .ddtcrc configuration file, see ddtcrc(5).\n") unless ($Mail_out and $Mail_in);
}


# test_language
# 
# Stop if language is not defined
#

sub test_language() {
	suicide __("No language provided, please either set the `language' variable in your .ddtcrc configuration file, or use the --lang or -l option.\n") unless $Language && ($Language ne "");
}


# test_directories
#
# Stop if directories cannot be created
#

sub test_directories() {
	foreach ($Tr_dir, $Bug_dir, $Rev_dir) {
		next if (-d $_);
		mkpath ($_, 0, 0755) or suicide __("Please check ddtc configuration.\nCannot create `%s': %s\n"), $_, $!;
	}
}


# get_charset
#
# get charset
#
# output:
#   charset if different from ddts standard
#   empty string otherwise

sub get_charset() {
	test_language;

	suicide	__("No charset defined, please set the `mail_charset' variable in your .ddtcrc configuration file, see ddtcrc(5).\n") unless $DDTS_CHARSETS{$Language} || $Mail_charset;

	return $Mail_charset =~ /^$DDTS_CHARSETS{$Language}$/i ? "" : ".$Mail_charset";
}



#
# File management routines
#

# Move file
sub move_file ($$) {
	my $old = shift;
	my $new = shift;

	debug 3;
	debug 4, "old filename: $old\n".
		 "new filename: $new\n";

	if (-e $old) {
		move ($old, $new) or suicide __("Cannot move `%s' -> `%s': %s\n"), $old, $new, $!;
		debug 4, "moved\n";
	}
}

# Remove file
sub remove_file ($) {
	my $file = shift;

	debug 3;
	debug 4, "filename:     $file\n";

	if (-e $file) {
		unlink $file	or suicide __("Cannot remove `%s': %s\n"), $file, $!;
		debug 2, "$file " if get_debug == 2;
		debug 4, "removed\n";
	}
}

# Touch file
sub touch_file ($) {
	my $file = shift;

	debug 3;
	debug 4, "filename:     $file\n";

	open  FILE, ">$file"	or suicide __("Cannot create `%s': %s\n"), $file, $!;
	close FILE		or suicide __("Cannot write `%s': %s\n"), $file, $!;
}

# Write file
sub write_file ($$) {
	my $file = shift;
	my $data = shift;

	debug 3;
	debug 4, "filename:     $file\n";

	open  FILE, ">$file"	or suicide __("Cannot create `%s': %s\n"), $file, $!;
	print FILE $data;
	close FILE		or suicide __("Cannot write `%s': %s\n"), $file, $!;
}

# List directory contents
sub ls_dir ($;$) {
	my $dir   = shift;
	my $regex = shift || '$';

	my @files;

	debug 3;
	debug 4, "directory:    $dir\n";
	debug 4, "regex:        $regex\n";

	opendir (PKGLIST, $dir)	or suicide __("Cannot read the contents of `%s': %s\n"), $dir, $!;
	@files = sort grep(/$regex/, readdir(PKGLIST));
	closedir PKGLIST	or suicide __("Cannot read the contents of `%s': %s\n"), $dir, $!;

	return (@files);
}


# uncomment
# 
# remove comments
#
# input:
#   commented string
#   regex
# output:
#   uncommented string

sub uncomment ($$) {
	my @commented = split("\n", shift);
	my $comment   = shift;

	my @data;

	debug 3;
	debug 4, "comment str:  `$comment'\n";
	debug 5, join("\n", @commented)."\n";

	foreach (@commented) {
		next if /^($comment)/;
		push @data, $_;
	}

	debug 5, join("\n", @data)."\n";

	wantarray ? @data :
		    join("\n", @data)."\n";
}


# ndiff
#
# Diff two descriptions
#
# input:
#   old description
#   new description
# output:
#   diff

sub ndiff ($$) {
	my @orig = split("\n", shift);
	my @dest = split("\n", shift);

	my @diff;

	debug 3;

	my @diffs = diff(\@orig, \@dest);
	my $o = 0;				# index in @orig
	my $d = 0;				# index in @dest

	foreach my $hunk (@diffs) {
		foreach (@{ $hunk }) {
			if ($_->[0] eq  '-') {
				while ($o < $_->[1]) {
					push @diff, " $orig[$o++]";
					$d++;
				}
				push @diff, "-$orig[$o++]";
			} else {
				while ($d < $_->[1]) {
					push @diff, " $dest[$d++]";
					$o++;
				}
				push @diff, "+$dest[$d++]";
			}
		}
	}
	push @diff, " $dest[$d++]" while ($d < @dest);

	debug 5, join("\n", @diff)."\n";

	wantarray ? @diff :
		    join("\n", @diff)."\n";
}


# superdiff
#
# Diff two descriptions, keeping short description before long one
#
# input:
#   old description
#   new description
# output:
#   diff

sub superdiff ($$) {
	my @orig = split("\n", shift);
	my @dest = split("\n", shift);

	my @sdiff;

	debug 3;

	my @sdiffs = sdiff(\@orig, \@dest);
	foreach (@sdiffs) {
		if	($_->[0] eq  'u') {
			push @sdiff, " ".$_->[1];
		} elsif ($_->[0] eq  '-') {
			push @sdiff, "-".$_->[1];
		} elsif ($_->[0] eq  '+') {
			push @sdiff, "+".$_->[2];
		} elsif ($_->[0] eq  'c') {
			push @sdiff, "-".$_->[1];
			push @sdiff, "+".$_->[2];
		} else {
			suicide __("Unknown sdiff instruction: %s.\nThis is a BUG, please report it to ddtc package maintainer\n"), $_->[0];
		}
	}

	debug 5, join("\n", @sdiff)."\n";

	wantarray ? @sdiff :
		    join("\n", @sdiff)."\n";
}


# get_apt_name
# 
# Get the package name through apt-cache
#
# input:
#   short description
# output:
#   package name

sub get_apt_name ($) {
	my $short = shift;
	   $short =~ s/^Description: //;

	my $name;

	debug 3;
	debug 4, "short desc.:  $short\n";

	if (open  APT, "apt-cache search '$short' |") {
		my @names;
		my $s = $short;
		$s =~ s/^\W+//;
		$s =~ s/\W+$//;
		$s =~ s/(\W)/\\$1/g;				# escape non word characters
		while (<APT>) {
			chomp $_;
			next unless m/$short/;
			push @names, $_;
		}
		if	((close APT ) && @names) {
			($name, my $short) = split / - /, shift @names;
			while (@names) {
				(my $n, my $s) = split / - /, shift @names;
				next unless length($s) < length($short);
				$name  = $n;
				$short = $s;
			}
		} elsif (not @names) {
			$name = undef;
		} else {
			$name = undef;
			warning __("Unable to run apt-cache: maybe you are not on a Debian system!\n");
		}
	} else {
		$name = undef;
		warning __("Unable to run apt-cache: maybe you are not on a Debian system!\n");
	}
	if (defined $name) {
		debug 1, __("I guess `%s' referes to `%s'\n"), $short, $name;
	} else {
		$name = "noname-$short";
		$name =~ s/(\s)/_/g;
		$name =~ s/\//_/g;

		warning __("I can't guess `%s', output file named `%s'\n"), $short, $name;
	}

	return ($name);
}


# parse_translation
#
# Parse descriptions to translate
#
# input:
#   data structure

sub parse_translation ($) {
	my $pkg = shift;

	debug 3;

	debug 4, "package:      ".($pkg->name	    || "")."\n".
		 "language:     ".($pkg->language   || "")."\n".
		 "message id:   ".($pkg->message_id || "")."\n";
	
	unless ($pkg->description || $pkg->translation) {
		warning	__("%s, skipped, malformed data:\n%s%s"),
			$pkg->name	      || __("no name"),
			$pkg->description ? "" : __("Missing description\n"),
			$pkg->translation ? "" : __("Missing translation\n");
		return;
	}

	my $package = $pkg->name;
	move_file("$Tr_dir/$package.$Tr_e",   "$Tr_dir/$package.$Old_e" );
	move_file("$Tr_dir/$package.$Ok_e",   "$Tr_dir/$package.$Sent_e");
	move_file("$Tr_dir/$package.$Sent_e", "$Tr_dir/$package.$Old_e" );

	my $new = new Ddtc::Package::;
	my $old = new Ddtc::Package::;

	$new->name	 ($pkg->name);
	$new->language	 ($pkg->language);
	$new->message_id ($pkg->message_id);

	unless ($old->get_file("$Tr_dir/$package.$Old_e")) {
		$old = bless {};
		$old = undef;
	}

	my @desc;
	@desc =	$old			? superdiff(uncomment($old->description, "#"),	$pkg->description) :
		$pkg->old_description	? superdiff($pkg->old_description,		$pkg->description) :
					  superdiff("",                   		$pkg->description) ;
	@desc = map { $_ = /^\ (.*)/?    $1 :$_ } @desc;	# remove head character unchanged line
	@desc = map { $_ = /^\-(.*)/?"# -$1":$_ } @desc;	# comment removed line
	@desc = map { $_ = /^\+(.*)/?    $1 :$_ } @desc;	# remove head character added line
	$new->description (@desc);

	my @tran;
	@tran = $old			? superdiff(uncomment($old->translation, "#"),	$pkg->translation) :
		$pkg->old_translation	? superdiff($pkg->old_translation,		$pkg->translation) :
					  superdiff("",                   		$pkg->translation) ;
	@tran = map { $_ = /^\ (.*)/?    $1 :$_ } @tran;	# remove head character unchanged line
	@tran = map { $_ = /^\-(.*)/?"# -$1":$_ } @tran;	# comment removed line
	@tran = map { $_ = /^\+(.*)/?    $1 :$_ } @tran;	# remove head character added line
	$new->translation (@tran);

	write_file("$Tr_dir/$package.$Todo_e",  $new->header
					       .$new->description
					       .$new->translation);
}


# parse_review
#
# Parse descriptions to review
#
# input:
#   Ddtc object

sub parse_review ($) {
	my $pkg = shift;

	debug 3;

	debug 4, "package:      ".$pkg->name."\n".
		 "language:     ".$pkg->language."\n".
		 "translator:   ".$pkg->translator."\n".
		 "message id:   ".$pkg->message_id."\n";
	
	unless ($pkg->description || $pkg->translation) {
		warning	__("%s, skipped, malformed data:\n%s%s"),
			$pkg->name	      || __("no name"),
			$pkg->description ? "" : __("Missing description\n"),
			$pkg->translation ? "" : __("Missing translation\n");
		return;
	}

	my $package = $pkg->name;
	move_file("$Rev_dir/$package.$Ok_e",   "$Rev_dir/$package.$Sent_e");
	move_file("$Rev_dir/$package.$Lok_e",  "$Rev_dir/$package.$Sent_e");
	move_file("$Rev_dir/$package.$Rev_e",  "$Rev_dir/$package.$Sent_e");
	move_file("$Rev_dir/$package.$Sent_e", "$Rev_dir/$package.$Old_e" );

	my $new = new Ddtc::Package::;
	my $old = new Ddtc::Package::;

	$new->name        ($pkg->name	    );
	$new->language    ($pkg->language   );
	$new->message_id  ($pkg->message_id );
	$new->translator  ($pkg->translator );
	$new->description ($pkg->description);
	$new->translation ($pkg->translation);

	unless ($old->get_file("$Rev_dir/$package.$Old_e")) {
		$old = bless {};
		$old = undef;
	}

	write_file("$Rev_dir/$package",  $new->header
					.$new->description
					.$new->translation);

	my @desc;
	@desc = $old			? superdiff(uncomment($old->description, "#"),	$pkg->description) :
		$pkg->old_description	? superdiff($pkg->old_description,		$pkg->description) :
					  superdiff("",                   		$pkg->description) ;
	@desc = map { $_ = /^\ (.*)/?    $1 :$_ } @desc;		# remove head character unchanged line
	@desc = map { $_ = /^\-(.*)/?"# -$1":$_ } @desc;		# comment removed line
	@desc = map { $_ = /^\+(.*)/?    $1 :$_ } @desc;		# remove head character added line
	$new->description (@desc);

	my @tran;

	if ($old) {
		my $c	 = substr($Comment, 1);					# remove first character of `$Comment' for selection
		my @data = uncomment($old->translation, $Comment);
		   @data = map { $_ = /^\+$c(--|\+\+)(.*)/?$2:$_ } @data ;	# remove header from selected lines
		my $data = join ("\n", @data);
		@tran = superdiff($data,		 $pkg->translation);
	} elsif ($pkg->old_translation) {
		@tran = superdiff($pkg->old_translation, $pkg->translation);
	} else {
		@tran = superdiff("",			 $pkg->translation) ;
	}

	@tran = map { $_ = /^\ (.*)/?           $1 :$_ } @tran;		# remove head character unchanged line
	@tran = map { $_ = /^\-(.*)/?"$Comment--$1":$_ } @tran;		# comment removed line
	@tran = map { $_ = /^\+(.*)/?"$Comment++$1":$_ } @tran;		# comment added line
	@tran = map { $_ = /^$Comment\+\+(.*)/? $1 :$_ } @tran unless grep (!/^$Comment\+\+/, @tran);	# uncomment if new review
	$new->translation (@tran);

	write_file("$Rev_dir/$package.$Todo_e",  $new->header
						.$new->description
						.$new->translation);

	if ($old				      &&
	    not(grep (/^$Comment/, $new->translation))  ) {
		move_file("$Rev_dir/$package.$Todo_e",  "$Rev_dir/$package.$Rev_e");

		debug 1, __("ok:           %s\n"), $package;
	}
}


# parse_bug
#
# Parse bug report
#
# input:
#   Ddtc object

sub parse_bug ($) {
	my $pkg = shift;

	debug 3;

	debug 4, "package:      ".$pkg->name."\n".
		 "language:     ".$pkg->language."\n".
		 "message id:   ".$pkg->message_id."\n";
	
	unless ($pkg->description || $pkg->translation) {
		warning	__("%s, skipped, malformed data:\n%s%s"),
			$pkg->name	      || __("no name"),
			$pkg->description ? "" : __("Missing description\n"),
			$pkg->translation ? "" : __("Missing translation\n");
		return;
	}

	my $package	    =  $pkg->name;

	if (-e "$Bug_dir/$package.$Fix_e") {
		warning __("collision detected, %s exists, try to rename it into %s\n%s skipped\n"), "$package.$Fix_e", "$package.$Bug_e", $package;
		return;
	}
	if (-e "$Tr_dir/$package.$Tr_e") {
		warning __("collision detected, %s exists, try to remove it and rename %s into %s.\n%s skipped\n"), "$package.$Tr_e", "$package.$Fix_e.$Old_e", "$package.$Bug_e", $package;
		return;
	}
	move_file("$Tr_dir/$package.$Ok_e",   "$Tr_dir/$package.$Sent_e");
		
	my $i;
	my $l;
	my $old_review;
	my @diff;

	my  $new = new Ddtc::Package::;
	my  $bug = new Ddtc::Package::;
	my $sent = new Ddtc::Package::;
	unless ( $bug->get_file("$Bug_dir/$package.$Bug_e" )) {
		 $bug = bless {};
		 $bug = undef;
	}
	unless ($sent->get_file("$Tr_dir/$package.$Sent_e")) {
		$sent = bless {};
		$sent = undef;
	}

	$new->name	 ($pkg->name);
	$new->message_id ($pkg->message_id);

	if ($bug) {
		$new->bugs	(($bug->bugs,      $pkg->bugs));
		$new->reviewers (($bug->reviewers, $pkg->reviewers));
	} else {
		$new->bugs	($pkg->bugs);
		$new->reviewers ($pkg->reviewers);
	}
	$l = chr(96 + $new->bugs);

	# Check if already parsed
	if ($bug) {
		my $m = $pkg->bug(0);
		if (grep(/$m/, $bug->bugs )) {
			debug 1, __("parsed already %s\n"), $package;
			return;
		}
	} elsif ($sent) {
		my $m = $pkg->bug(0);
		if (grep(/$m/, $sent->bugs )) {
			debug 1, __("parsed already %s\n"), $package;
			return;
		}
	}

	move_file  ("$Bug_dir/$package.$Bug_e", "$Bug_dir/$package.$Bug_e.$Old_e");

	# Choose original description
	$new->old_description ("");
	$new->old_description ( $pkg->old_description)	if $pkg->old_description;
	$new->old_description ($sent->description)	if $sent;
	$new->old_description ( $bug->description)	if $bug;

	@diff = superdiff(uncomment($new->old_description, "#"), $pkg->description);
	@diff = map { $_ = /^\ (.*)/?    $1 :$_ } @diff;	# remove head character unchanged line
	@diff = map { $_ = /^\-(.*)/?"# -$1":$_ } @diff;	# comment removed line
	@diff = map { $_ = /^\+(.*)/?    $1 :$_ } @diff;	# remove head character added line
	$new->description (@diff);

	# Re-format review for superdiff
	my @review = $pkg->translation;
	   @review = map  { $_ = /^## \-#(.*)/?"$Comment\--$1":$_ } @review;
	   @review = map  { $_ = /^## \##(.*)/?"$Comment$l#$1":$_ } @review;
	   @review = map  { $_ = /^## \+#(.*)/?"$Comment$l+$1":$_ } @review;
	for ($i = 0; $i<@review; $i++) {
		$_ = $review[$i];
		if (/^$Comment--/) {
			my $j;
			for ($j = $i-1; $j>=0; $j--) {
				next if ($review[$j] =~ /^$Comment/);
				next if ($review[$j] eq "");
				$review[$j] = "";
				last;
			}
		}
	}
	@review = grep (/^.+$/, @review);

	$new->translation     (map { $_ = /^$Comment.\+(.*)/?$1:$_ } grep (!/^$Comment--/,       $new->translation     (@review)));
	$new->old_translation (map { $_ = /^$Comment\--(.*)/?$1:$_ } grep (!/^$Comment$l(#|\+)/, $new->old_translation (@review)));

	# Choose original translation
	$new->old_translation ( $pkg->old_translation)	if $pkg->old_translation;
	$new->old_translation ($sent->translation)	if $sent;

	@diff = superdiff($new->old_translation, $new->translation);
	@diff = map { $_ = /^\ (.*)/?            $1 :$_ } @diff;		# remove head character unchanged line
	@diff = map { $_ = /^\-(.*)/?"$Comment\--$1":$_ } @diff;		# comment removed line
	@diff = map { $_ = /^\+(.*)/?"$Comment$l+$1":$_ } @diff;		# comment added line
	@diff = map { $_ = /^$Comment..($Comment.*)/?$1:$_ } @diff;		# commented line

	$new->translation (@diff);

	if ($bug) {
		my @tr = $new->translation;
		my @bg = $bug->translation;

		foreach (@tr) {
			next unless m/$Comment--(.*)/;
			for (my $i = 0; $i < @bg; $i++) {
				next unless $bg[$i] eq $1;
				$bg[$i] = "$Comment--$bg[$i]";
			}
		}
		foreach (@bg) {
			next unless m/$Comment--(.*)/;
			for (my $i = 0; $i < @tr; $i++) {
				next unless $tr[$i] eq $1;
				$tr[$i] = "$Comment--$tr[$i]";
			}
		}
		@diff = ndiff(join("\n", @tr), join("\n", @bg));
		s/^.// foreach (@diff);
		$new->translation (@diff);
	}

	write_file("$Bug_dir/$package.$Bug_e",  $new->header
					       .$new->description
					       .$new->translation);
}


# parse_reviewed
# 
# set fully reviewed description ok
#
# input:
#   Ddtc object

sub parse_reviewed ($) {
	my $pkg = shift;

	debug 3;

	my $package = $pkg->name;
	debug 4, "package:      $package\n";

	move_file( "$Tr_dir/$package.$Sent_e",  "$Tr_dir/$package.$Ok_e");
	move_file("$Rev_dir/$package.$Lok_e",  "$Rev_dir/$package.$Ok_e");
}


# get_data
#
# split attached file into package descriptions
#
# input:
#   mail
#   mime part
# output:
#   list of Ddtc objects

sub get_data ($$) {
	my $mail = shift;
	my $part = shift;

	my $data;
	# data
	# +-> 1
	# +-> 2
	#     +-> Ddtc
	# +-> ...
	# +-> n
	my $message_id;

	chomp ($message_id = $mail -> head -> get ('Message-Id'));

	debug 3;
	debug 4, "message id:   ".$message_id."\n";

	my @names;
	my @lines = $part -> bodyhandle -> as_lines;
	my @copy  = @lines;
	s/\n// foreach (@lines);
	while (@lines) {
		$_ = shift (@lines);
		debug 5, $_."\n";
		
		if (/^# package\(?s?\)?: ?(\S+)?/i) {
			push @names, $1 || '';
			$data->[@names] = new Ddtc::Package::;
			$data->[@names]->name ($1 || '');
			$data->[@names]->message_id ($message_id);

			debug 1, __("package:      %s\n"), $1 || '';
			next;
		}

		if (/^# reviewer: (.*)/i) {
			my $n = MIME::Words::decode_mimewords($1);
			$data->[@names]->add_reviewer ($n);

			debug 1, __("reviewer:     %s\n"), $n;
			next;
		}

		if (/^# translator: (.*)/i) {
			my $n = MIME::Words::decode_mimewords($1);
			$data->[@names]->translator ($n);

			debug 1, __("translator:   %s\n"), $n;
			next;
		}

		if (/^# bug number: (\d+)/i) {
			$data->[@names]->add_bug ($1);

			debug 1, __("bug number:   %s\n"), $1;
			next;
		}

		if (/^Description: (.*)/) {
			my   @void;
			push @void, "Description: $1";
			while (@lines) {
				$_ = shift (@lines);
				last unless /^( .*)/;
				push @void, $1;
			}
			unshift @lines, $_ if @lines;
			$data->[@names]->description (@void);

			unless ($data->[@names]->name) {
				$data->[@names]->name (get_apt_name(($data->[@names]->description)[0]));
			}

			debug 5, "description:\n".
				 $data->[@names]->description;
			next;
		}

		if (/^# old description:/i) {
			$_ = shift (@lines);
			/^# Description: (.*)/;
			my   @void;
			push @void, "Description: $1";
			while (@lines) {
				$_ = shift (@lines);
				last unless /^# ( .*)/;
				push @void, $1;
			}
			unshift @lines, $_ if @lines;
			$data->[@names]->old_description (@void);

			debug 5, "old description\n".
				 $data->[@names]->old_description;
			next;
		}

		if (/^Description-(..(?:_..)?)(?:\.([\w-]+))?: (.*)/) {
			$data->[@names]->language ($1);
			my   $ddts_charset = $2 || $DDTS_CHARSETS{$1};
			my   @void;
			push @void, "Description-$1: $3";
			while (@lines) {
				$_ = shift (@lines);
				next if (/^#\s*$/);	# remove empty comments
				next if (/^# ppart translation, please check it! It needs your help:/);	# remove ppart comments
				last unless /^(##)?( .*)/;
				push @void, (defined $1 ? "$1$2" :
							    "$2" );
			}
			unshift @lines, $_ if @lines;

			unless ($Mail_charset =~ /^$ddts_charset$/) {
				my $converter = Text::Iconv -> new ($ddts_charset, $Mail_charset);
				my $converted = $converter->convert(join("\n", @void));
				if ($converted) {
					debug 4, "Charset converted from $ddts_charset to $Mail_charset\n";
					@void = split("\n", $converted);
				} else {
					warning __("Unable to convert charset from %s to %s\n"), $ddts_charset, $Mail_charset;
				}
			}
			$data->[@names]->translation (@void);

			debug 5, "translation\n".
				 "langage:      ".$data->[@names]->language."\n".
				 $data->[@names]->translation;
			next;
		}

		if (/^# translated description from the db:/i ||
		    /^# old translation:/i) {
			$_ = shift (@lines);
			/^# Description-(..(?:_..)?)(?:\.([\w-]+))?: (.*)/;
			$data->[@names]->language ($1) unless $data->[@names]->language;
			my   $ddts_charset = $2 || $DDTS_CHARSETS{$1};
			my   @void;
			push @void, "Description-$1: $3";
			while (@lines) {
				$_ = shift (@lines);
				last unless /^# ( .*)/;
				push @void, $1;
			}
			unshift @lines, $_ if @lines;

			unless ($Mail_charset =~ /^$ddts_charset$/) {
				my $converter = Text::Iconv -> new ($ddts_charset, $Mail_charset);
				my $converted = $converter->convert(join("\n", @void));
				if ($converted) {
					debug 4, "Charset converted from $ddts_charset to $Mail_charset\n";
					@void = split("\n", $converted);
				} else {
					warning __("Unable to convert charset from %s to %s\n"), $ddts_charset, $Mail_charset;
				}
			}
			$data->[@names]->old_translation (@void);

			debug 5, "old translation\n".
				 "langage:      ".$data->[@names]->language."\n".
				 $data->[@names]->old_translation;
			next;
		}

		# Try to parse old bugs:
		if (/^# diff old-new description:/i) {
			unless ($data->[@names]->bugs) {
				($_) = grep (/^btsclose: \d+/i, @copy);
				next unless defined $_;
				$data->[@names]->add_bug($1) if /^btsclose: (\d+)/i;
			}
		}
		if (/^# diff old-new translation:/i) {
			unless ($data->[@names]->bugs) {
				($_) = grep (/^btsclose: \d+/i, @copy);
				next unless defined $_;
				$data->[@names]->add_bug($1) if /^btsclose: (\d+)/i;
			}
		}

		next if (/^# /);				# skip comment
		next if (/^#$/);				# skip comment
		next if (/^$/);					# skip empty line
		next if (/^from: /i);				# skip ddts command
		next if (/^btsclose: /i);			# skip ddts command

		suicide __("Unknown line:\n%s\n"), $_;
	}

	shift @{ $data };
	debug 4, scalar(@{ $data })." package(s) found\n";
	return $data;
}


# parse_nop
#
# Parse no operation

sub parse_nop () {
}


# parse
#
# Parse a message
#
# input:
#   Mime stream on STDIN

sub parse () {
	my $parser;					# MIME parser
	my $mail;					# mail to parse
	my $subject;					# mail subject

	debug 3;

	test_directories;

	$parser = new MIME::Parser:: ;

	$mail    = $parser -> read(\*STDIN)	or suicide __("Cannot parse mime stream: %s\n"), $!;
	$subject = $mail -> head -> get ('subject');

	unless ($mail -> is_multipart) {
		$mail -> purge;
		suicide __("This is NOT a multipart mime message. Please make sure you've provided the whole message to STDIN including the headers and encoded mime parts.\n");
	}

	debug 1, $subject."\n";

	my $file;
	foreach my $part ($mail -> parts) {
		debug 4, ($part -> head -> mime_type)."\n";
		next unless $part -> head -> mime_type eq "application/debian-dt";

		my $file = $part -> head -> recommended_filename;
		next if $file =~ /^close-bug/;			# skip bug closed

		my $data = get_data ($mail, $part);

		if	($subject =~ /reviewed/i) {		# new reviewed descriptions
			parse_reviewed ($_)	foreach (@{ $data });
		} elsif ($file =~ /newreview/i) {		# new descriptions to review
			parse_review   ($_)	foreach (@{ $data });
		} elsif ($file =~ /^new(\..*)?/i) {		# new descriptions to translate
			parse_translation ($_)	foreach (@{ $data });
		} else {					# bug report
			parse_bug ($_)		foreach (@{ $data });
		}
	}

	$mail -> purge;
}


# fix
#
# Fix bugs
#

sub fix () {
	my $file;

	debug 3;

	test_directories;

	foreach (ls_dir($Bug_dir, "\\.$Fix_e\$")) {
		s/\.$Fix_e//;
		my $file = $_;
		my $c    = substr($Comment, 1);					# remove first character of `$Comment' for selection

		debug 1, __("fixed:        %s\n"), $file;

		my $fix = new Ddtc::Package::;
		unless ($fix->get_file("$Bug_dir/$file.$Fix_e" )) {
			$fix = bless {};
			$fix = undef;
			next;
		}
		my @data = $fix->translation;
		   @data = grep (!/^$Comment/,			       @data);	# remove not selected lines
		   @data = map  { $_ = /^\+$c(--|[a-z]\+)(.*)/?$2:$_ } @data ;	# remove header from selected lines
		$fix->translation (@data);

		move_file ("$Tr_dir/$file.$Tr_e",   "$Tr_dir/$file.$Old_e");
		move_file ("$Bug_dir/$file.$Fix_e", "$Bug_dir/$file.$Fix_e.$Old_e");
		write_file("$Tr_dir/$file.$Tr_e", $fix->header
						 .$fix->description
						 .$fix->translation);
	}

	return 1;
}


# check_format
#
# check line length
#
# input:
#   english description
#   translation
# output:
#   ok		on success
#   tab		if \t found
#   nb para	if scructures differ
#   too long	if line length > 80
#   _.+		if something follows a dot

sub check_format ($$) {
	my @description = split("\n", shift);
	my @translation = split("\n", shift);

	my $count = 0;

	debug 3;

	my $c	  = substr($Comment, 1);				# remove first character of `$Comment' for selection

	if (grep(/^ \.$/, @description) ne grep(/^(\+$c[+-]{2})? \.$/, @translation)) {
		warning	__("Number of paragraphs differs in description and translation\n");

		debug	5, "nb para\n";
		return	   "nb para";
	}

	foreach (@translation) {
		$count++;

		next if /^# /;
		next if /^$Comment/;

		s/^\+$c[+-]{2}//;					# remove header from selected lines

		if	(/\t/) {
			my $l = $_;
			s/\t.*/^/;
			s/[^^]/ /g;
			warning __("Line %s, <tab> not allowed:\n%s\n%s\n"), $count, $l, $_;

			debug	5, "tab\n";
			return	   "tab";
		} elsif (/^ \../) {
			warning	__("Line %s, dot starts non-empty line:\n%s\n  ^\n"), $count, $_;

			debug	5, "_.+\n";
			return	   "_.+";
		}

		s/^Description-[^:]+: //;
		next unless length($_) > 80;

		warning __("Line %s too long:\n%s\n%s^\n"), $count, $_, " "x80;

		debug	5, "too long\n";
		return	   "too long";
	}

	debug	5, "ok\n";
	return	   "ok";
}


# make_translation
#
# make the translation to send
#
# input:
#   package name
# output:
#   data to send

sub make_translation ($) {
	my $file = shift;

	debug 3;
	debug 4, "filename:     $file\n";

	my $new = new Ddtc::Package::;
	unless ($new->get_file("$Tr_dir/$file.$Tr_e")) {
		$new = bless {};
		$new = undef;
		next;
	}

	my @diff;
	push @diff, "btsclose: $_"		foreach ($new->bugs);
	push @diff, uncomment($new->description, "# ");
	push @diff, uncomment($new->translation, "# |$Comment");

	my $ddts_charset = $DDTS_CHARSETS {$new->language};
	if ($ddts_charset ne $Mail_charset) {
		@diff = map ($_ = /^(Description-$Language)(:.*)/?"$1.$Mail_charset$2":$_, @diff);
	}

	debug 5, join("\n", @diff)."\n";

	return (join("\n", @diff)."\n");
}


# make_review
#
# make the review to send to prevent parts from being updated
#
# input:
#   package name
# output:
#   data to send

sub make_review ($) {
	my $file = shift;

	debug 3;
	debug 4, "filename:     $file\n";

	my $pkg = new Ddtc::Package::;
	unless ($pkg->get_file("$Rev_dir/$file")) {
		$pkg = bless {};
		$pkg = undef;
		next;
	}
	my $rev = new Ddtc::Package::;
	unless ($rev->get_file("$Rev_dir/$file.$Rev_e")) {
		$rev = bless {};
		$rev = undef;
		next;
	}
	my $original = uncomment($pkg->description
				.$pkg->translation     , "# ");
	my $reviewed = uncomment($rev->description
				.$rev->translation     , "# ");
	my @diff;

	my $c	  = substr($Comment, 1);				# remove first character of `$Comment' for selection
	my @data  = split("\n", $reviewed);
	   @data  = grep ( !/^$Comment(--|\+\+)/,	    @data);	# remove not selected lines
	   @data  = map  { $_ = /^\+$c(--|\+\+)(.*)/?$2:$_ } @data ;	# remove header from selected lines
	$reviewed = join ("\n", @data);

	if ($Ddtc_bug) {
		@diff = superdiff($original, $reviewed);

		@diff = map ($_ = /^\ (.*)/	   ?"$1"	 :$_, @diff);
		@diff = map ($_ = /^\-(.*)/	   ?"$1\n## -#$1":$_, @diff);
		@diff = map ($_ = /^\+$Comment(.*)/?    "## ##$1":$_, @diff);
		@diff = map ($_ = /^\+(.*)/	   ?    "## +#$1":$_, @diff);
		@diff = split("\n", join("\n", @diff));			# split original/removed lines
	} else {
		@diff = @data;
	}

	my $ddts_charset = $DDTS_CHARSETS {$pkg->language};
	if ($ddts_charset ne $Mail_charset) {
		@diff = map ($_ = /^(Description-$Language)(:.*)/?"$1.$Mail_charset$2":$_, @diff);
	}

	debug 5, join("\n", @diff)."\n";

	return (join("\n", @diff)."\n");
}


# mail
#
# Send mails
#

sub mail () {
	my $BCount  = 0;
	my $nb_sent = 0;
	
	debug 3;

	test_mail;
	test_directories;

	my $mail = build MIME::Entity::
			Type		=> "multipart/mixed",
			From		=> $Mail_out,
			To		=> (get_debug == 9 ? $Mail_out :
							     $Mail_server),
			Cc		=> ($Mail_self ? $Mail_out :
							 undef	     ),
			'Reply-To:'	=> $Mail_in,
			'User-Agent:'	=> "ddtc ".VERSION;

	my @tr_files;
	foreach (ls_dir($Tr_dir, "\\.$Tr_e\$")) {
		s/\.$Tr_e$//;				# remove extension
		my $file = $_;				# get package name

		my $pkg = new Ddtc::Package::;
		unless ($pkg->get_file("$Tr_dir/$file.$Tr_e")) {
			$pkg = bless {};
			$pkg = undef;
			next;
		}

		my $ddts_charset = $DDTS_CHARSETS {$pkg->language};
		$mail -> head -> replace (Subject => "nothing ".$pkg->language.
				($Mail_charset =~ /^$ddts_charset$/i ?"":".$Mail_charset")." noguide")	unless $mail -> head -> get ('Subject');

		unless ("ok" eq check_format($pkg->description, $pkg->translation)) {
			warning __("translation skipped: %s\n"), $file;
			next;
		}

		attach $mail	Data		=> make_translation($file),
				Charset		=> $Mail_charset,
				Encoding	=> $Mail_enc,
				Filename	=> $file;

		debug 1, __("translation:  %s\n"), $file;

		push @tr_files, $file;
	}

	my @sent_files;
	my @lok_files;
	foreach (ls_dir($Rev_dir, "\\.$Rev_e\$")) {
		s/\.$Rev_e$//;
		my $file = $_;
		next unless (-e "$Rev_dir/$file");
		next if     (-e "$Rev_dir/$file.$Sent_e");

		my $pkg = new Ddtc::Package::;
		unless ($pkg->get_file("$Rev_dir/$file")) {
			$pkg = bless {};
			$pkg = undef;
			next;
		}
		my $rev = new Ddtc::Package::;
		unless ($rev->get_file("$Rev_dir/$file.$Rev_e")) {
			$rev = bless {};
			$rev = undef;
			next;
		}

		my $ddts_charset = $DDTS_CHARSETS {$pkg->language};
		$mail -> head -> replace (Subject => "nothing ".$pkg->language.
				($Mail_charset =~ /^$ddts_charset$/i ?"":".$Mail_charset")." noguide")	unless $mail -> head -> get ('Subject');

		unless ("ok" eq check_format($rev->description, $rev->translation)) {
			warning __("review skipped:      %s\n"), $file;
			next;
		}

		attach $mail	Data		=> make_review($file),
				Charset		=> $Mail_charset,
				Encoding	=> $Mail_enc,
				Filename	=> $file;

		my $c	  = substr($Comment, 1);				# remove first character of `$Comment' for selection
		my @rev  = $rev->translation;
		   @rev  = grep ( !/^$Comment(--|\+\+)/,	    @rev);	# remove not selected lines
		   @rev  = map  { $_ = /^\+$c(--|\+\+)(.*)/?$2:$_ } @rev ;	# remove header from selected lines
		if ($pkg->translation eq join("\n", @rev)."\n") {
			debug 1, __("review (ok):  %s\n"), $file;
			push @lok_files, $file;
		} else {
			debug 1, __("review (bug): %s\n"), $file;
			push @sent_files, $file;
		}
	}

	if (@tr_files + @lok_files + @sent_files) {
		unless ($mail -> send) {
			suicide __("Cannot send email");
		};

		foreach (@tr_files) {
			move_file ("$Tr_dir/$_.$Tr_e", "$Tr_dir/$_.$Sent_e");
		}
		foreach (@lok_files) {
			remove_file ("$Rev_dir/$_.$Rev_e");
			move_file   ("$Rev_dir/$_", "$Rev_dir/$_.$Lok_e");
		}
		foreach (@sent_files) {
			move_file ("$Rev_dir/$_.$Rev_e", "$Rev_dir/$_.$Sent_e");
		}

		debug 1, __("sent: #%s\n"), @tr_files + @lok_files + @sent_files;
	} else {
		debug 1, __("nothing to send\n");
	}

	$mail -> purge;

	return 1;
}


# clean
#
# Clean obsolete files
#

sub clean () {
	debug 3;

	test_directories;

	inc_debug if get_debug == 1;			# increase verbose level if 1

	debug 2, __("Removing files...\n");

	foreach (ls_dir($Tr_dir)) {
		next if /^\./;

		remove_file("$Tr_dir/$_") if /$Clean_re/;

		s/\.[^.]*$//;				# remove extension
		if (-e "$Tr_dir/$_.$Todo_e") {
			remove_file("$Tr_dir/$_.$Todo_e") if (-e "$Tr_dir/$_.$Tr_e");
			remove_file("$Tr_dir/$_.$Todo_e") if (-e "$Tr_dir/$_.$Sent_e");
		}
	}

	foreach (ls_dir($Bug_dir)) {
		next if /^\./;

		remove_file("$Bug_dir/$_") if /$Clean_re/;

		remove_file("$Bug_dir/$_.$Bug_e") if (-e "$Bug_dir/$_.$Fix_e");
	}

	foreach (ls_dir($Rev_dir)) {
		next if /^\./;

		remove_file("$Rev_dir/$_") if /$Clean_re/;

		s/\.[^.]*$//;				# remove extension
		if	(-e "$Rev_dir/$_.$Ok_e") {
			remove_file("$Rev_dir/$_");
			remove_file("$Rev_dir/$_.$Todo_e");
			remove_file("$Rev_dir/$_.$Rev_e");
			remove_file("$Rev_dir/$_.$Sent_e");
			remove_file("$Rev_dir/$_.$Lok_e");
		} elsif (-e "$Rev_dir/$_.$Lok_e") {
			remove_file("$Rev_dir/$_");
			remove_file("$Rev_dir/$_.$Todo_e");
			remove_file("$Rev_dir/$_.$Rev_e");
			remove_file("$Rev_dir/$_.$Sent_e");
		} elsif (-e "$Rev_dir/$_.$Sent_e") {
			remove_file("$Rev_dir/$_.$Todo_e");
			remove_file("$Rev_dir/$_.$Rev_e");
		} elsif ((-e "$Rev_dir/$_.$Rev_e") && (-e "$Rev_dir/$_.$Todo_e")) {
			remove_file("$Rev_dir/$_.$Todo_e");
		}

		if (!(-e "$Rev_dir/$_")) {
			remove_file("$Rev_dir/$_.$Todo_e");
			remove_file("$Rev_dir/$_.$Sent_e");
		}
	}

	dec_debug if get_debug == 2;			# decrease verbose level back to 1

	return 1;
}


# stats
#
# Print statistics
#

sub stats () {
	my    $totr_c = 0;
	my      $tr_c = 0;
	my  $senttr_c = 0;
	my    $oktr_c = 0;
	my     $bug_c = 0;
	my     $fix_c = 0;
	my   $torev_c = 0;
	my     $rev_c = 0;
	my $sentrev_c = 0;
	my  $lokrev_c = 0;
	my   $okrev_c = 0;
	
	debug 3;

	test_directories;

	foreach (ls_dir($Tr_dir)) {
		next         if /^\./;
		  $totr_c++  if /\.$Todo_e$/;
		    $tr_c++  if   /\.$Tr_e$/;
		$senttr_c++  if /\.$Sent_e$/;
		  $oktr_c++  if   /\.$Ok_e$/;
	}
	foreach (ls_dir($Bug_dir)) {
		next         if /^\./;
		$bug_c++     if /\.$Bug_e$/;
		$fix_c++     if /\.$Fix_e$/;
	}
	foreach (ls_dir($Rev_dir)) {
		next         if /^\./;
		  $torev_c++ if /\.$Todo_e$/;
		    $rev_c++ if  /\.$Rev_e$/;
		$sentrev_c++ if /\.$Sent_e$/;
		 $lokrev_c++ if  /\.$Lok_e$/;
		  $okrev_c++ if   /\.$Ok_e$/;
	}

	my $sumtr  =  $tr_c +  $senttr_c             +  $oktr_c;
	my $sumbug = $bug_c +     $fix_c;
	my $sumrev = $rev_c + $sentrev_c + $lokrev_c + $okrev_c;

	my $l = 0;
	   $l = $_>$l?$_:$l foreach ($sumtr, $sumbug, $sumrev);	# get max
	   $l = length $l;					# get length for separators
	if ($sumtr + $totr_c) {
		debug 0, __("===== translations\n     to do: %s\n   to send: %s\n      sent: %s\n  accepted: %s\n            %s\ntranslated: %s\n"), sprintf("%${l}d", $totr_c), sprintf("%${l}d", $tr_c), sprintf("%${l}d", $senttr_c), sprintf("%${l}d", $oktr_c), __("-")x$l, sprintf("%${l}d", $sumtr);
	}

	if ($sumbug) {
		debug 0, __("===== bug reports\n   reports: %s\n     fixed: %s\n            %s\n    opened: %s\n"), sprintf("%${l}d", $bug_c), sprintf("%${l}d", $fix_c), __("-")x$l, sprintf("%${l}d", $sumbug);
	}

	if ($sumrev + $torev_c) {
		debug 0, "\n"	if $sumbug || ($sumtr + $totr_c);
		debug 0, __("===== reviews\n     to do: %s\n   to send: %s\n    bugged: %s\n    agreed: %s\n  accepted: %s\n            %s\n  reviewed: %s\n"), sprintf("%${l}d", $torev_c), sprintf("%${l}d", $rev_c), sprintf("%${l}d", $sentrev_c), sprintf("%${l}d", $lokrev_c), sprintf("%${l}d", $okrev_c), __("-")x$l, sprintf("%${l}d", $sumrev);
	}

	return 1;
}


# ddts_command
#
# Send a command to the ddts
#
# input:
#   array of commands

sub ddts_command (@) {
	debug 3;
	debug 4, "command:      ".join(" ", @_)."\n";
	
	test_mail;

	my $command = "";

	$_ = " ".join(" ", @_)." ";


	#
	# check command syntax:
	# 
	if (m/ (?i:section) /) {
	    m/ (?i:section) (\w+) /;
		debug 4, "section ".($1 || "")."\n";

		if ($1) {
			$command .= "section $1 ";
		} else {
			warning	__("section\nmissing section name, set to none\n");
		}
	}
	if	(m/ (?i:sget) /) {
		 m/ (?i:sget) ([\w+.-]+) /;
		debug 4, "sget ".($1 || "")."\n";

		if ($1) {
			test_language;
			$command .= "sget $1 $Language".get_charset." ";
		} else {
			warning	__("sget\nmissing package name, command skipped\n");
		}
	} elsif (m/ (?i:get) /) {
		 m/ (?i:get) ((?:\d+)|(?:[\w+.-]+)) /;
		debug 4, "get ".($1 || "")."\n";

		if ($1) {
			test_language;
			$command .= "get $1 $Language".get_charset." ";
		} else {
			warning	__("get\nmissing number or package name, command skipped\n");
		}
	} elsif (m/ (?i:review) /) {
		 m/ (?i:review) ((?:\d+)|(?:[\w+.-]+)) /;
		debug 4, "review ".($1 || "")."\n";

		if ($1) {
			test_language;
			$command .= "review $1 $Language".get_charset." ";
		} else {
			warning	__("review\nmissing number or package name, command skipped\n");
		}
	} elsif (m/ (?i:getbug) /) {
		 m/ (?i:getbug) ([ \d]+) /;
		debug 4, "getbug ".($1 || "")."\n";

		if ($1) {
			$command .="getbug $1 ";
		} else {
			warning	__("getbug\nmissing bug number, command skipped\n");
		}
	} elsif (m/ (?i:btsclose) /) {
		 m/ (?i:btsclose) ([ \d]+) /;
		debug 4,"btsclose ".($1 || "")."\n";

		if ($1) {
			$command .= "btsclose $1 ";
		} else {
			warning	__("btsclose\nmissing bug number, command skipped\n");
		}
	} elsif (m/ (?i:notification) /) {
		 m/ (?i:notification) ([\w+:-]+) /;
		debug 4, "notification ".($1 || "")."\n";

		if ($1) {
			$command .= "notification $Mail_in $1 ";
		} else {
			warning	__("notification\nmissing language, command skipped\n");
		}
	}
	if (m/ (?i:listtranslatedpackages) /) {
		debug 4, "ListTranslatedPackages\n";

		test_language;
		$command .= "listtranslatedpackages $Language ";
	}
	if (m/ (?i:status) /) {
		debug 4, "status\n";

		test_language;
		$command .= "status $Language ";
	}
	if	(m/ (?i:noneveraguide) /) {
		debug 4, "NoNeverAGuide\n";

		$command .= "noneveraguide ";
	} elsif (m/ (?i:neveraguide) /) {
		debug 4, "NeverAGuide\n";

		$command .= "neveraguide ";
	}
	if	(m/ (?i:noguide) /) {
		debug 4, "NoGuide\n";

		$command .= "noguide ";
	} elsif (m/ (?i:guide) /) {
		debug 4, "found Guide\n";

		$command .= "guide ";
	} else {
		$command .= "noguide ";
	}

	return if $command eq "noguide ";

	my $mail = build MIME::Entity::
			From		=> $Mail_out,
			To		=> (get_debug == 9 ? $Mail_out : $Mail_server),
			Cc		=> ($Mail_self	   ? $Mail_out : undef),
			Subject		=> $command,
			'Reply-To:'	=> $Mail_in,
			'User-Agent:'	=> "ddtc ".VERSION,
			Data		=> "";
	unless ($mail -> send) {
		suicide __("Cannot send email");
	};
	debug 1, __("command sent to the server: %s\n"), $command;
	$mail -> purge;

	return 1;
}


# inter_config
#
# interactive configuration
#

sub inter_config() {
	debug 3;

	sub var_edit($$$) {
		my $menu   = shift;
		my $format = shift;
		my $conf   = shift;
		my $item   = @{ $menu };
	
		my $function = sub {
			$$conf = $Term -> readline("", $$conf);
			chomp $$conf;
			$menu -> text($item, sprintf($format, $$conf));
			return 1;
		};

		$menu -> add(sprintf($format, $$conf), $function);
	}
	sub var_yn($$$$) {
		my $menu   = shift;
		my $format = shift;
		my $conf   = shift;
		my $title  = shift;
		my $item   = @{ $menu };
	
		my $function = sub {
			my @subs = (sub { 1 }, sub { 0 });
			my @text = split (/\n/, sprintf(__("%s\n_yes\n_no"), $title));
			my $submenu = new Ddtc::Menu:: (shift @text, @Menu_colors);
			$submenu -> add(shift @text, shift @subs) while @text;
			$$conf = $submenu -> execute($Term);
			$menu -> text($item, sprintf($format, $$conf?__("yes"):__("no")));
			return 1;
		};

		$menu -> add(sprintf($format, $$conf?__("yes"):__("no")), $function);
	}

	my %c = %conf;

	my @subs = (sub { "general" }, sub { "mail" }, sub { "color" }, sub { "save" }, sub { 0 });
	my @text = split(/\n/, __("Configuration\n_general\n_mail\n_colors\n_save and exit\n_X_exit and discard changes"));
	my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
	$menu -> add(shift @text, shift @subs) while @text;

	while (my $res = $menu -> execute($Term)) {
		if	($res eq "general") {
			my @refs = \($c{'base_dir'}, $c{'comment'}, $c{'clean_regex'}, $c{'language'}, $c{'editor'}, $c{'ddtc_bug'});
			my @text = split(/\n/, __("General configuration\n_base directory  %s\nc_omment         %s\n_clean regex     %s\n_language        %s\n_editor          %s\nddtc _bug        %s\n_quit"));
			my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
			var_edit($menu, shift @text, shift @refs) while @text > 2;
			var_yn  ($menu, shift @text, shift @refs, __("Send bugs in special format"));
			$menu -> add(shift @text, sub { 0 });
			while ($menu -> execute($Term)) { };
		} elsif ($res eq "mail" ) {
			my @refs = \($c{'mail_out'}, $c{'mail_in'}, $c{'mail_server'}, $c{'mail_encoding'}, $c{'mail_charset'}, $c{'mail_self'});
			my @text = split(/\n/, __("Mail configuration\n_outgoing  %s\n_incoming  %s\n_server    %s\n_encoding  %s\n_charset   %s\nse_lf      %s\n_quit"));
			my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
			var_edit($menu, shift @text, shift @refs) while @text > 2;
			var_yn  ($menu, shift @text, shift @refs, __("Send mails to oneself"));
			$menu -> add(shift @text, sub { 0 });
			while ($menu -> execute($Term)) { };
		} elsif ($res eq "color") {
			my @refs = \($c{'menu_title_color'}, $c{'menu_letter_color'}, $c{'menu_text_color'});
			my @text = split(/\n/, __("Color configuration\nt_itle   %s\n_letter  %s\n_text    %s\n_quit"));
			my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
			var_edit($menu, shift @text, shift @refs) while @text > 1;
			$menu -> add(shift @text, sub { 0 });
			while ($menu -> execute($Term)) { };
		} elsif ($res eq "save") {
			my $old_base_dir = $Base_dir;

			load_conf %c;

			if      (($old_base_dir ne $Base_dir) && (-e $Base_dir)) {
				warning __("`%s' already exists, keeping `%s' as base directory.\n"), $Base_dir, $old_base_dir;
				$c{'base_dir'} = $conf{'base_dir'};
				load_conf %c;
			} elsif (($old_base_dir ne $Base_dir) && not(-e $old_base_dir)) {
			} elsif  ($old_base_dir ne $Base_dir) {
				move($old_base_dir, $Base_dir)	or suicide __("Cannot move `%s' to `%s': %s\n"), $old_base_dir, $Base_dir, $!;
			}

			# remove defaults
			$c{'base_dir'} =~ s/$ENV{HOME}\/// if $c{'base_dir'};

			delete $c{'base_dir'}		if $Base_dir		eq "$ENV{HOME}/ddts";
			delete $c{'comment'}		if $Comment		eq ">>";
			delete $c{'clean_regex'}	if $Clean_re		eq "~\$|\\.old\$|\\.bak\$";
			delete $c{'editor'}		if $Editor		eq "sensible-editor";
			delete $c{'ddtc_bug'}		unless $Ddtc_bug;
			delete $c{'language'}		unless $c{'language'};
			delete $c{'debug'}		if $c{'debug'}		== 1;

			delete $c{'mail_out'}		if $Mail_out		eq ($ENV{EMAIL} || $ENV{DEBEMAIL} || '');
			delete $c{'mail_in'}		if $Mail_in		eq $Mail_out;
			delete $c{'mail_server'}	if $Mail_server		eq "pdesc\@ddtp.debian.org";
			delete $c{'mail_self'}		if $Mail_self;
			delete $c{'mail_encoding'}	if $Mail_enc		eq "8bit";
			delete $c{'mail_charset'}	if $Mail_charset	eq "iso-8859-1";

			delete $c{'menu_title_color'}	if $Menu_title_color	eq "bold cyan";
			delete $c{'menu_letter_color'}	if $Menu_letter_color	eq "bold cyan";
			delete $c{'menu_text_color'}	if $Menu_text_color	eq "cyan";

			foreach (keys %c) {
				next if $_ eq 'base_dir';
				next if $_ eq 'comment';
				next if $_ eq 'clean_regex';
				next if $_ eq 'editor';
				next if $_ eq 'ddtc_bug';
				next if $_ eq 'language';
				next if $_ eq 'debug';
		
				next if $_ eq 'mail_out';
				next if $_ eq 'mail_in';
				next if $_ eq 'mail_server';
				next if $_ eq 'mail_self';
				next if $_ eq 'mail_encoding';
				next if $_ eq 'mail_charset';
		
				next if $_ eq 'menu_title_color';
				next if $_ eq 'menu_letter_color';
				next if $_ eq 'menu_text_color';
		
				delete $c{$_};
			}

			# save new config file
			my $config = new Config::General ((-ConfigHash		=> \%c));
			   $config -> save_file(catfile($ENV{HOME}, CONFFILE));

			$c{'base_dir'}		||= 'ddts';
			$c{'comment'}		||= '>>';
			$c{'clean_regex'}	||= '~$|\.old$|\.bak$';
			$c{'editor'}		||= 'sensible-editor';
			$c{'ddtc_bug'}		||= 0;
			$c{'language'}		||= '';
			$c{'debug'}		||= 1;

			$c{'mail_out'}		||= $ENV{EMAIL} || $ENV{DEBEMAIL} || '';
			$c{'mail_in'}		||= '';
			$c{'mail_server'}	||= 'pdesc@ddtp.debian.org';
			$c{'mail_encoding'}	||= '8bit';
			$c{'mail_charset'}	||= 'iso-8859-1';
			$c{'mail_self'}		||= 1;

			$c{'menu_title_color'}	||= 'bold cyan';
			$c{'menu_letter_color'}	||= 'bold cyan';
			$c{'menu_text_color'}	||= 'cyan';

			%conf = %c;

			last;
		}
	}
	return 1;
}


# choose_file
#
# choose file
#
# input:
#   reference of file list
sub choose_file(\@) {
	my $files = shift;

	debug 3;

	my $n = 0;
	foreach (@$files) {
		debug 0, (color($Menu_letter_color).$n.color('clear').color($Menu_text_color)." ".$$files[$n].color('clear')."\n");
		$n++;
	}

	$n--;
	my $l;
	until ((defined $l) && ($l eq "")) {
		$l = $Term -> readline("0-$n\: ");
		chomp $l;
		return $$files[$l] if ($l =~ /^\d+$/) && (0<=$l) && ($l<=$n);
	}

	return undef;
}


# inter_trans
#
# edit translation
#

sub inter_trans () {
	debug 3;

	test_directories;

	my @subs = (sub { "first" }, sub { "choose" }, sub { "random" }, sub { "last" }, sub { 0 });
	my @text = split (/\n/, __("Edit translation\n_first file\n_choose\nat _random\n_last file\n_quit"));
	my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
	$menu -> add(shift @text, shift @subs) while @text;

	my @files = ls_dir($Tr_dir, "\\.$Todo_e\$");
	s/\.$Todo_e$// foreach (@files);

	unless (@files) {
		debug 0, __("no file to edit");
		return 1;
	}

	while (my $res = $menu -> execute($Term)) {
		my $file;

		if	($res eq "first") {
			$file = $files[0];
		} elsif ($res eq "choose") {
			$file = choose_file @files;
		} elsif ($res eq "random") {
			$file = $files[(scalar(@files)*rand()) % scalar(@files)];
		} elsif ($res eq "last") {
			$file = $files[-1];
		} else {
			next;
		}

		my @subs = (sub { "edit" }, sub { "acheck" }, sub { "send" }, sub { 0 });
		my @text = split (/\n/, sprintf(__("Edit %s\n_edit\nrun _acheck\nset for _send\n_quit"), $file));
		my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
		$menu -> add(shift @text, shift @subs) while @text;
		while (my $res = $menu -> execute($Term)) {
			if	($res eq "edit") {
				system ($Editor, "$Tr_dir/$file.$Todo_e");
			} elsif ($res eq "acheck") {
				system ("acheck", "-t", "$Tr_dir/$file.$Todo_e");
			} elsif ($res eq "send") {
				move_file ("$Tr_dir/$file.$Todo_e", "$Tr_dir/$file.$Tr_e");
				@files = ls_dir($Tr_dir, "\\.$Todo_e\$");
				s/\.$Todo_e$// foreach (@files);
				last if @files;
				debug 0, __("no more file to edit");
				return 1;
			}
		}
	}

	return 1;
}


# inter_review
#
# edit review
#

sub inter_review () {
	debug 3;

	test_directories;

	my @subs = (sub { "first" }, sub { "choose" }, sub { "random" }, sub { "last" }, sub { 0 });
	my @text = split (/\n/, __("Edit review\n_first file\n_choose\nat _random\n_last file\n_quit"));
	my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
	$menu -> add(shift @text, shift @subs) while @text;

	my @files = ls_dir($Rev_dir, "\\.$Todo_e\$");
	s/\.$Todo_e$// foreach (@files);

	unless (@files) {
		debug 0, __("no file to edit");
		return 1;
	}

	while (my $res = $menu -> execute($Term)) {
		my $file;

		if	($res eq "first") {
			$file = $files[0];
		} elsif ($res eq "choose") {
			$file = choose_file @files;
		} elsif ($res eq "random") {
			$file = $files[(scalar(@files)*rand()) % scalar(@files)];
		} elsif ($res eq "last") {
			$file = $files[-1];
		} else {
			next;
		}

		my @subs = (sub { "edit" }, sub { "acheck" }, sub { "send" }, sub { 0 });
		my @text = split (/\n/, sprintf(__("Edit %s\n_edit\nrun _acheck\nset for _send\n_quit"), $file));
		my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
		$menu -> add(shift @text, shift @subs) while @text;
		while (my $res = $menu -> execute($Term)) {
			if	($res eq "edit") {
				system ($Editor, "$Rev_dir/$file.$Todo_e");
			} elsif ($res eq "acheck") {
				system ("acheck", "-r", "$Rev_dir/$file.$Todo_e");
			} elsif ($res eq "send") {
				move_file ("$Rev_dir/$file.$Todo_e", "$Rev_dir/$file.$Rev_e");
				@files = ls_dir($Rev_dir, "\\.$Todo_e\$");
				s/\.$Todo_e$// foreach (@files);
				last if @files;
				debug 0, __("no more file to edit");
				return 1;
			}
		}
	}

	return 1;
}


# inter_bug
#
# edit bug report
#

sub inter_bug () {
	debug 3;

	test_directories;

	my @subs = (sub { "first" }, sub { "choose" }, sub { "random" }, sub { "last" }, sub { 0 });
	my @text = split (/\n/, __("Edit bug report\n_first file\n_choose\nat _random\n_last file\n_quit"));
	my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
	$menu -> add(shift @text, shift @subs) while @text;

	my @files = ls_dir($Bug_dir, "\\.$Bug_e\$");
	s/\.$Bug_e$// foreach (@files);

	unless (@files) {
		debug 0, __("no file to edit");
		return 1;
	}

	while (my $res = $menu -> execute($Term)) {
		my $file;

		if	($res eq "first") {
			$file = $files[0];
		} elsif ($res eq "choose") {
			$file = choose_file @files;
		} elsif ($res eq "random") {
			$file = $files[(scalar(@files)*rand()) % scalar(@files)];
		} elsif ($res eq "last") {
			$file = $files[-1];
		} else {
			next;
		}

		my @subs = (sub { "edit" }, sub { "acheck" }, sub { "fix" }, sub { 0 });
		my @text = split (/\n/, sprintf(__("Edit %s\n_edit\nrun _acheck\nset as _fixed\n_quit"), $file));
		my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
		$menu -> add(shift @text, shift @subs) while @text;
		while (my $res = $menu -> execute($Term)) {
			if	($res eq "edit") {
				system ($Editor, "$Bug_dir/$file.$Bug_e");
			} elsif ($res eq "acheck") {
				system ("acheck", "-t", "$Bug_dir/$file.$Bug_e");
			} elsif ($res eq "fix") {
				move_file ("$Bug_dir/$file.$Bug_e", "$Bug_dir/$file.$Fix_e");
				@files = ls_dir($Bug_dir, "\\.$Bug_e\$");
				s/\.$Bug_e$// foreach (@files);
				last if @files;
				debug 0, __("no more file to edit");
				return 1;
			}
		}
	}

	return 1;
}


# inter_command
#
# send a server command in interactive mode
#

sub inter_command () {
	debug 3;

	my $section = "";
	my $package = "";
	my $bug_nb  = "";

	my @subs = (sub { "sget" }, sub { "get" }, sub { "review" }, sub { "section" }, sub { "getbug" }, sub { "btsclose" }, sub { ddts_command ("listtranslatedpackages") }, sub { ddts_command ("status") }, sub { 0 });
	my @text = split (/\n/, __("Server command\nget descriptions of a _source package\n_get descriptions\nget _reviews\nset s_ection\nget a _bug report\n_close a bugreport\n_list translated packages\nget translation s_tatus\n_quit"));
	my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
	$menu -> add(shift @text, shift @subs) while @text;

	while (my $res = $menu -> execute($Term)) {
		if	($res eq "sget") {
			$package = $Term -> readline (__("source package name: "));
			ddts_command (($section ? "section $section" : ""), "sget", $package);
		} elsif	($res eq "get") {
			$package = $Term -> readline (__("package name or number: "));
			ddts_command (($section ? "section $section" : ""), "get", $package);
		} elsif	($res eq "review") {
			$package = $Term -> readline (__("package name or number: "));
			ddts_command (($section ? "section $section" : ""), "review", $package);
		} elsif	($res eq "section") {
			$section = $Term -> readline (__("section name: "));
		} elsif	($res eq "getbug") {
			$bug_nb  = $Term -> readline (__("bug number: "));
			ddts_command ("getbug", $bug_nb);
		} elsif	($res eq "btsclose") {
			$bug_nb  = $Term -> readline (__("bug number: "));
			ddts_command ("btsclose", $bug_nb);
		}
	}

	return 1;
}


# interactive
#
# run interactive mode
#

sub interactive () {
	debug 3;

	my @subs = (\&mail, \&fix, \&clean, \&stats, \&inter_command, \&inter_config, \&inter_trans, \&inter_review, \&inter_bug, sub { 0 });
	my @text = split(/\n/, __("Main menu\n_Mail descriptions to the server\n_Fix bugged descriptions\n_Clean obsolete files\nprint _Statistics\nsend _server command\nc_onfigure\nedit _translation\nedit _review\nedit _bug report\n_quit"));
	my $menu = new Ddtc::Menu:: (shift @text, @Menu_colors);
	$menu -> add(shift @text, shift @subs) while @text;

	while ($menu -> execute($Term)) { };
}


# print_version
#
# Print script version and exit
# 

sub print_version () {
	debug 3;

	debug 0, __("%s version %s\n"), $Me, VERSION;

	exit;
}


# print_help
#
# Print help message and exit
# 

sub print_help () {
	debug 3;

	debug 0, __("Usage: %s [OPTIONS] COMMAND [ARGUMENTS]\n\ncommands:\n  parse       extract the package descriptions from a ddts mail\n  mail        send descriptions back to the ddts\n  fix         create a fixed description from bugs\n  clean       remove obsolete files\n  stats       print statistics\n  and server commands\n\noptions:\n  -q, --quiet        quiet mode\n  -v                 verbose, add more for more verbosity\n      --verbose n    set verbosity to n\n  -s, --mail-self    send mail copy to oneself\n  -n, --nomail-self  don't send mail copy to oneself\n  -l, --lang xx_XX   use xx_XX language extension\n  -V, --version      print version and exit\n  -h, --help         print this message and exit\n\n%s version %s\n"), $Me, $Me, VERSION;

	exit;
}


#
# Parse command line
#

{
	debug 3;

	my %commands = (					# command references
		'parse'		=> \&parse,
		'mail'		=> \&mail,
		'fix'		=> \&fix,
		'ask_trans'	=> \&ask_tr,
		'ask_review'	=> \&ask_rev,
		'clean'		=> \&clean,
		'stats'		=> \&stats,
		'interactive'	=> \&interactive,
		 );
	my @ddts_commands = (					# known server commands
		"section",
		"sget",
		"get",
		"review",
		"getbug",
		"btsclose",
		"notification ",
		"listtranslatedpackages",
		"status",
		"noneveraguide",
		"neveraguide",
		"noguide",
		"guide"
		);

	Getopt::Long::Configure qw(permute bundling);		# set standard gnu options (for potato perl)
	Getopt::Long::GetOptions (
		'verbose=i'	=> sub { set_debug $_[1] },		# verbose <value>
		'v+'		=> sub { inc_debug },			# incremental
		'quiet|q'	=> sub { set_debug 0 },			# quiet
		'version|V'	=> sub { print_version; exit 0 },	# version
		'help|h'	=> sub { print_help;    exit 0 },	# help
		'mail-self!'	=> \$Mail_self,				# mail-self negatable
		's'		=> sub { $Mail_self = 1 },		# short
		'n'		=> sub { $Mail_self = 0 },		# short no
		'ddtc-bug!'	=> \$Ddtc_bug,				# ddtc-bug negatable
		'lang|l=s'	=> \$Language,				# language
	) or $ARGV[0] = '';

	# set charset if not defined
	$Mail_charset = $Mail_charset ? $Mail_charset		  :
			$Language     ? $DDTS_CHARSETS{$Language} :
					"ISO-8859-1"		  ;

	debug 4, "Configuration dump:\n".
		 "base_dir      $Base_dir\n".
		 "comment       $Comment\n".
		 "ddtc_bug      ".($Ddtc_bug   ?"yes":"no")."\n".
		 "debug         ".get_debug."\n".
		 "clean_re      $Clean_re\n".
		 "mail_addr     $Mail_out\n".
		 "mail_from     $Mail_in\n".
		 "mail_server   ".$Mail_server."\n".
		 "mail_self     ".($Mail_self  ?"yes":"no")."\n".
		 "mail_enc      $Mail_enc\n".
		 "mail_charset  $Mail_charset\n".
		 "version       ".VERSION."\n".
		 "command       ".join(" ", @ARGV)."\n";

	my $command = shift @ARGV || '';			# get command
	   $command ||= "parse"	   unless -t;
	if ((-t) && (-t STDOUT)) {
		$command ||= "interactive" if    (-t) && (-t STDOUT);
		$Term = new Term::ReadLine ($Me);
	}

	if (defined $commands{$command}) {
		&{$commands{$command}};				# execute known command
	} elsif (grep (/^$command$/i, @ddts_commands)) {
		ddts_command $command, @ARGV;			# send command to the server
	} elsif ($command) {
		debug 0, __("Unknown command `%s'\n"), $command;# unknown command
		print_help;
	} else {
		print_help;
	}
}
