#!/usr/bin/perl
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if $running_under_some_shell;
#!/usr/bin/perl

use strict;
use File::Copy;
use File::Temp qw/ tempfile tempdir /;

my %module_dirname = (
    "core"              => "",
    "dictionaries"      => "dictionaries",
    "help"              => "helpcontent2",
    "translations"      => "translations"
);

# get libreoffice-build version from the given libreoffice-build sources
sub get_config_version($)
{
    my ($lo_core_dir) = @_;
    my $version;

    open (CONFIGURE, "$lo_core_dir/configure.ac") ||
        die "can't open \"$lo_core_dir/configure.ac\" for reading: $!\n";

    while (my $line = <CONFIGURE>) {
        chomp $line;

        if ($line =~ /AC_INIT\s*\(\s*libreoffice-build\s*,\s*([\w\.]*)\)/) {
            $version="$1";
        }
    }
    close (CONFIGURE);
    return $version;
}

# increment the version for a test build:
#        + add 'a' if the version ended with a number
#       + bump the letter otherwise
sub inc_test_version($)
{
    my ($version) = @_;

    my $lastchar = chop $version;
    my $new_version;

    if ($lastchar =~ /\d/) {
        return "$version" . "$lastchar" . "a";
    } elsif ($lastchar =~ /\w/) {
        # select next letter alhabeticaly: a->b, b->c, ...
        $lastchar =~ tr/0a-zA-Z/a-zA-Z0/;
        return "$version" . "$lastchar";
    } else {
        die "Can't generate test version from \"$version$lastchar\n";
    }
}

sub get_release_version($$$$)
{
    my ($config_version, $state_config_version, $state_release_version, $inc_version) = @_;
    my $release_version;

    if (defined $state_config_version &&
        defined $state_release_version &&
        "$state_config_version" eq "$config_version") {
        $release_version = "$state_release_version";
    } else {
        $release_version = "$config_version";
    }

    if ( defined $inc_version ) {
        $release_version = inc_test_version($release_version);
    }

    return $release_version;
}

# copy files to temp dir; showing a progress; using a black list
sub copy_dir_filter_and_show_progress($$)
{
    my ($source_dir, $target_dir) = @_;

    print "Copying \"$source_dir\" -> \"$target_dir\"...";
    # copy sources from git and show progress
    system ("cd $source_dir && " .
            "git archive --format=tar HEAD | " .
            "  tar -xf - -C $target_dir --checkpoint=500 --checkpoint-action=exec=\"echo -n .\"") &&
        die "Error: copying failed: $!\n";
    print "\n";
}

# copy files to temp dir; showing a progress; using a black list
sub remove_empty_submodules($)
{
    my ($target_topdir) = @_;

    foreach my $submodule (sort values %module_dirname) {
        next unless ($submodule);
        print "Removing empty submodule: $submodule...\n";
        rmdir "$target_topdir/$submodule" || die "Error: Can't remove submodule directory: $target_topdir/$submodule";
    }
}

# copy the source directory into a tmp directory
# omit the .git subdirectories
sub copy_lo_module_to_tempdir($$$)
{
    my ($source_dir, $module, $lo_topdir_name) = @_;
    my $tempdir = tempdir( 'libreoffice-XXXXXX', DIR => File::Spec->tmpdir );

    mkdir "$tempdir/$lo_topdir_name" || die "Can't create directory \"$tempdir/$lo_topdir_name\": $!\n";
    mkdir "$tempdir/$lo_topdir_name/$module_dirname{$module}" || die "Can't create directory \"$tempdir/$lo_topdir_name/$module_dirname{$module}\": $!\n" if ($module_dirname{$module});
    
    copy_dir_filter_and_show_progress("$source_dir/$module_dirname{$module}", "$tempdir/$lo_topdir_name/$module_dirname{$module}");
    remove_empty_submodules("$tempdir/$lo_topdir_name/") if ($module eq "core");

    return $tempdir;
}

sub generate_lo_module_changelog($$$)
{
    my ($source_dir, $lo_module_release_topdir, $module) = @_;
    
    my $log_name = "ChangeLog";
    $log_name .= "-$module_dirname{$module}" if ($module_dirname{$module});
    print "Generating changelog for $module...\n";
#    print "1:$lo_module_clone, 2:$lo_module_release_dir, 3:$module\n";
    system ("cd $source_dir/$module_dirname{$module} && " .
            "git log --date=short --pretty='format:@%cd  %an  <%ae>  [%H]%n%n%s%n%n%e%b' | " .
            "  sed -e 's|^\([^@]\)|\t\1|' -e 's|^@||' >$lo_module_release_topdir/$log_name" ) &&
        die "Error: generating failed: $!\n";
}

sub run_autogen($$)
{
    my ($dir, $module) = @_;

    print "Running autogen for $module...\n";
    system ("cd $dir && " .
            "NOCONFIGURE=1 ./autogen.sh && " .
            "rm -rf autom4te.cache && " .
            "cd - >/dev/null 2>&1") && die "Error: autogen failed: $!\n";
}

sub generate_sources_version_file($$)
{
    my ($dir, $release_version) = @_;

    open (VERFILE, ">$dir/sources.ver") || die "Can't open $dir/sources.ver: $!\n";

    print VERFILE "lo_sources_ver=$release_version\n";

    close VERFILE;
}

sub generate_tarball($$$)
{
    my ($dir, $tarball, $tar_compress_option) = @_;

    print "Creating $tarball...";
    # generate the tarball in the current directory; avoid "./" prefix in the stored paths; show progress
    system ("tar -c $tar_compress_option -f $tarball -C $dir --checkpoint=500 --checkpoint-action=exec=\"echo -n .\" --transform=\"s|^\./||\" .") && 
        die "Error: releasing failed: $!\n";
    print "\n";
}

sub generate_md5($)
{
    my ($filename) = @_;

    print "Generating MD5...\n";
    system ("md5sum $filename >$filename.md5") &&
        die "Error: releasing failed: $!\n";
}

sub default_releases_state_file($)
{
    my ($lo_core_dir) = @_;

    my $rootdir = $lo_core_dir;
    $rootdir =~ s/^(.*?)\/?[^\/]+\/?$/$1/;

    my $releases_state_file;
    if ($rootdir) {
        $releases_state_file = "$rootdir/.releases";
    } else {
        $releases_state_file = ".releases";
    }

    return "$releases_state_file";
}

sub load_releases_state($)
{
    my ($releases_state_file) = @_;

    my $state_config_version;
    my $state_release_version;

    if (open (STATE, "$releases_state_file")) {

        while (my $line = <STATE>) {
            chomp $line;

            if ($line =~ /^\s*configure_version\s*=\s*(.*)$/) {
                $state_config_version = "$1";
            } elsif ($line =~ /^\s*released_version\s*=\s*(.*)$/) {
                $state_release_version = "$1";
            }
        }
            close (STATE);
    }

    return $state_config_version, $state_release_version;
}

sub save_releases_state($$$)
{
    my ($releases_state_file, $config_version, $release_version) = @_;

    open (STATE, '>', "$releases_state_file") ||
        die "Can't open \"$releases_state_file\" for writing: $!\n";

    print STATE "configure_version = $config_version\n";
    print STATE "released_version  = $release_version\n";

    close (STATE);
}

sub remove_tempdir($)
{
    my ($tempdir) = @_;

#    print "Cleaning $tempdir...\n";
    system ("rm -rf $tempdir") && die "Error: rm failed: $!\n";
}

sub check_if_file_exists($$)
{
    my ($file, $force) = @_;

    if (-e $file) {
        if (defined $force) {
            print "Warning: $file already exists and will be replaced!\n";
        } else {
            die "Error: $file alrady exists.\n".
                "       Use --force if you want to replace it.\n";
        }
    }
}

sub check_if_already_released($$$$$$)
{
    my ($p_module_tarball_name, $force, $bzip2, $xz, $pack_lo_core, $pack_lo_modules) = @_;

    foreach my $tarball_name ( sort values %{$p_module_tarball_name} ) {
        check_if_file_exists("$tarball_name.tar.bz2", $force) if (defined $bzip2);
        check_if_file_exists("$tarball_name.tar.xz", $force) if (defined $xz);
    }
}

sub prepare_module_sources($$$$)
{
    my ($source_dir, $release_version, $module, $lo_topdir_name) = @_;

    # prepare sources
    my $temp_dir = copy_lo_module_to_tempdir($source_dir, $module, $lo_topdir_name);
    generate_lo_module_changelog($source_dir, "$temp_dir/$lo_topdir_name", $module);
    run_autogen("$temp_dir/$lo_topdir_name", $module) if ($module eq 'core');
    generate_sources_version_file("$temp_dir/$lo_topdir_name", $release_version) if ($module eq 'core');

    return $temp_dir;
}

sub pack_module_sources($$$$)
{
    my ($temp_dir, $md5, $tarball, $tar_compress_option) = @_;

    generate_tarball($temp_dir, $tarball, $tar_compress_option);
    generate_md5($tarball) if (defined $md5);
}

sub generate_module_tarball($$$$$$$$)
{
    my ($source_dir, $release_version, $module, $md5, $bzip2, $xz, $lo_topdir_name, $module_tarball_name) = @_;

    my $temp_dir = prepare_module_sources($source_dir, $release_version, $module, $lo_topdir_name);
    pack_module_sources($temp_dir, $md5, "$module_tarball_name.tar.bz2", "--bzip2") if (defined $bzip2);
    pack_module_sources($temp_dir, $md5, "$module_tarball_name.tar.xz", "--xz") if (defined $xz);
    remove_tempdir($temp_dir);
}


sub generate_tarballs($$$$$$$$$)
{
    my ($source_dir, $release_version, $md5, $bzip2, $xz, $lo_topdir_name, $p_module_tarball_name, $pack_lo_core, $pack_lo_modules) = @_;

    foreach my $module (sort keys %{$p_module_tarball_name} ) {
        print "\n--- Generating $module ---\n";
        generate_module_tarball($source_dir, $release_version, $module, $md5, $bzip2, $xz, $lo_topdir_name, $p_module_tarball_name->{$module});
    }
}


sub usage()
{
    print "This tool helps to pack the libreoffice-build and module sources\n\n" .

          "Usage:\n".
          "\tlo-pack-sources [--help]\n" .
          "\t  [--force] [--md5] [--bzip2] [--xz]\n" .
          "\t [--version][--set-version=<ver>] [--inc-version]\n" .
          "\t [--no-submodule] [--module=<module>]\n" .
          "\t [dir]\n\n" .

          "Options:\n\n" .
          "\t--help: print this help\n" .
          "\t--force: replace an already existing release of the same version\n" .
          "\t--md5: generate md5 sum for the final tarball\n" .
          "\t--bzip2: generate tarballs compressed by bzip2\n" .
          "\t--xz: generate tarballs compressed by xz (default)\n" .
          "\t--version: just print version of the released package but do not\n" .
          "\t\trelease it; the version is affected by the other options, e.g.\n" .
          "\t\t--inc-version\n" .
          "\t--set-version: force another version\n" .
          "\t--inc-version: increment the latest version; there is a difference\n" .
          "\t\tbetween test release (default) and final (not yet supported)\n" .
          "\t--no-submodule: do not pack sources from git submodules\n" .
          "\t--module=<module>: pack just a single module, use \"core\"\n" .
          "\t\tfor the main git repo,\n" .
          "\tdir: path of the source directory, either libreoffice-build or module\n";
}


my $module;
my $ptf;
my $md5;
my $bzip2;
my $xz;
my $inc_version;
my $config_version;
my $set_version;
my $get_config_version;
my $release_version;
my $pack_lo_core=1;
my $pack_lo_modules=1;
my $source_dir;
my $releases_state_file;
my $state_config_version;
my $state_release_version;
my $lo_core_tempdir;
my $force;
my $verbose=1;
my $lo_topdir_name;
my %module_tarball_name;

###################
# Arguments parsing
###################

for my $arg (@ARGV) {
    if ($arg eq '--help' || $arg eq '-h') {
        usage;
        exit 0;
    } elsif ($arg eq '--force') {
        $force=1;
    } elsif ($arg eq '--md5') {
        $md5=1;
    } elsif ($arg eq '--bzip2') {
        $bzip2=1;
    } elsif ($arg eq '--xz') {
        $xz=1;
    } elsif ($arg eq '--version') {
        $get_config_version=1;
        $verbose = undef;
    } elsif ($arg eq '--inc-version') {
        $inc_version=1
    } elsif ($arg =~ m/--set-version=(.*)/) {
          $set_version="$1";
    } elsif ($arg eq '--no-submodule') {
        $module = "core";
    } elsif ($arg =~ m/--module=(.*)/) {
          # process just one module and do not pack libreoffice-build
          die("Error: unknown module: $1") unless (defined $module_dirname{$1});
          $module = $1;
    } elsif ($arg =~ /^-/ ) {
        die "Error: unknown option: $arg\n";
    } else {
        if (! defined $source_dir) {
            $source_dir = $arg;
        } else {
            die "Error: Too many arguments $arg\n";
        }
    }
}

# ugly hack; we want only one module
if ($module) {
    my $name = $module_dirname{$module};
    %module_dirname = ();
    $module_dirname{$module} = $name;
}

###################
# Initial checks
###################

unless ( defined $source_dir ) {
    die "Error: undefined source directory, try --help\n";
}

unless ( -d "$source_dir" ) {
    die "Error: is not a directory: $source_dir\n";
}

# check if it is a valid libreoffice-core directory
unless (-f "$source_dir/autogen.sh" && -f "$source_dir/config_host.mk.in") {
    die "Error: \"$source_dir\" is not a valid libreoffice-core directory\n";
}

if (defined $set_version && defined $inc_version) {
    die "Error: --set-version and --inc-version options can't be used together\n";
}

# default compression
$xz = 1 unless (defined $xz || defined $bzip2);


###################
# Main logic
###################


print "Source: $source_dir\n" if ($verbose);

# detect some paths
$releases_state_file = default_releases_state_file($source_dir) unless (defined $releases_state_file);

# detect versions
$config_version = get_config_version($source_dir);
($state_config_version, $state_release_version) = load_releases_state($releases_state_file);
if (defined $set_version) {
    $release_version = "$set_version";
} else {
    $release_version = get_release_version($config_version, $state_config_version, $state_release_version, $inc_version);
}

# define tarball names
print "Detected module:\n";
foreach my $module (sort keys %module_dirname) {
    if (-e "$source_dir/$module_dirname{$module}/.git") {
        print "    $module\n";
        if ($module eq "core") {
            $module_tarball_name{$module} = "libreoffice-$release_version";
        } else {
            $module_tarball_name{$module} = "libreoffice-$module-$release_version";
        }
    } else {
        print "did not found: $source_dir/$module_dirname{$module}/.git\n";
        print "Warning: $module sources are not available -> skipping\n";
    }
}

# top directory inside the source tarballs
$lo_topdir_name = "libreoffice-$release_version";

print "Default version   : $config_version\n" if ($verbose && defined $config_version);
print "Last used version : $state_release_version\n" if ($verbose && defined $state_release_version);
print "New version       : $release_version\n" if ($verbose);

# do the real job
if ( defined $get_config_version ) {
    print "$release_version\n";
} else {
    check_if_already_released(\%module_tarball_name, $force, $bzip2, $xz, $pack_lo_core, $pack_lo_modules);

    # give a chance to stop the process
    print ("\nWaiting 3 seconds...\n");
    sleep 3;

    generate_tarballs($source_dir, $release_version, $md5, $bzip2, $xz, $lo_topdir_name, \%module_tarball_name, $pack_lo_core, $pack_lo_modules);

    if ( defined $releases_state_file ) {
        save_releases_state($releases_state_file, $config_version, $release_version);
    }
}