package
    XS::Install::Util;
use strict;
use warnings;
use XS::Install::Payload;
use Fcntl qw(:flock);   # import LOCK_* constants

sub linearize_dependent {
    my $modules = shift;
    my %modules = map { $_ => 1 } @$modules;
    # make list of all dependent
    my %dependent;
    for my $module (@$modules) {
        my $info = XS::Install::Payload::binary_module_info($module) or next;
        my $dependent = $info->{BIN_DEPENDENT} || [];
        for my $d_module (@$dependent) {
            next unless $modules{$d_module};
            push @{ $dependent{$module} }, $d_module;
        }
    }

    my $get_score; $get_score = sub {
        my $module = shift;
        my $score = 1; # initial value for myself
        my $dependent = $dependent{$module} || [];
        for my $d_module (@$dependent) {
            $score += $get_score->($d_module);
        }
        return $score;
    };
    my %scores = map { $_ => $get_score->($_) } @$modules;
    my @ordered_modules = sort {
           $scores{$a} <=> $scores{$b}
        ||          $a cmp $b
    } @$modules;

    return \@ordered_modules;
}

sub cmd_sync_bin_deps {
    my $myself = shift @ARGV;
    my @modules = @ARGV;
    foreach my $module (sort @modules) {
        my $file = XS::Install::Payload::binary_module_info_file($module);
        my $lock_file = "$file.lock";
        my $fh_lock;
        open $fh_lock, '>', $lock_file or warn "Cannot open $lock_file for writing: $!\n";
        if ($fh_lock) {
            my $ok = eval { flock($fh_lock, LOCK_EX); 1 };
            warn "Cannot lock $lock_file: $! ($@)\n" unless $ok;
        }

        my $info = XS::Install::Payload::binary_module_info($module) or next;
        my $dependent = $info->{BIN_DEPENDENT} || [];
        my %tmp = map {$_ => 1} grep {$_ ne $module} @$dependent;
        $tmp{$myself} = 1;
        $info->{BIN_DEPENDENT} = linearize_dependent([keys %tmp]);
        delete $info->{BIN_DEPENDENT} unless @{$info->{BIN_DEPENDENT}};
        my $ok  = eval { module_info_write($file, $info); 1 };
        unless ($ok) {
            warn("Reverse dependency write failed: $@");
        }
        if ($fh_lock) {
            # possible errors are ignored, as we can do nothing
            flock($fh_lock, LOCK_UN) && unlink($lock_file);
        }
    }
}

sub cmd_check_dependencies {
    require XS::Install::Deps;

    my $objext = shift @ARGV;

    my (@inc, @cfiles, @xsfiles);
    my $curlist = \@cfiles;
    foreach my $arg (@ARGV) {
        if ($arg =~ s/^-I//) {
            push @inc, $arg;
        }
        elsif ($arg eq '-xs') {
            $curlist = \@xsfiles;
        }
        else {
            push @$curlist, $arg;
        }
    }

    my @touch_list = (
        _check_mtimes(
            XS::Install::Deps::find_header_deps({
                files   => \@cfiles,
                headers => ['./'],
                inc     => \@inc,
            }),
            sub {
                my $ofile = shift;
                $ofile =~ s/\.[^.]+$//;
                $ofile .= $objext;
                return $ofile;
            },
        ),
        _check_mtimes(XS::Install::Deps::find_xsi_deps(\@xsfiles))
    );

    if (@touch_list) {
        my $now = time();
        utime($now, $now, @touch_list);
    }
}

sub _check_mtimes {
    my ($deps, $reference_file_sub) = @_;
    my %mtimes;
    my @touch_list;
    foreach my $file (keys %$deps) {
        my $list = $deps->{$file} or next;
        my $reference_file = $reference_file_sub ? $reference_file_sub->($file) : $file;
        my $reference_time = (stat($reference_file))[9] or next;
        foreach my $depfile (@$list) {
            my $mtime = $mtimes{$depfile} ||= (stat($depfile))[9];
            next if $mtime <= $reference_time;
            #warn "for file $file dependency $depfile changed";
            push @touch_list, $file;
            last;
        }
    }

    return @touch_list;
}

sub module_info_write {
    my ($file, $info) = @_;
    require Data::Dumper;
    local $Data::Dumper::Terse = 1;
    local $Data::Dumper::Indent = 0;
    my $content = Data::Dumper::Dumper($info);
    my $restore_mode;
    if (-e $file) { # make sure we have permissions to write, because perl installs files with 444 perms
        my $mode = (stat $file)[2];
        unless ($mode & 0200) { # if not, temporary enable write permissions
            $restore_mode = $mode;
            $mode |= 0200;
            chmod $mode, $file;
        }
    }

    my $temp_file = "$file.$$";
    open my $fh, '>', $temp_file or die "Cannot open $temp_file for writing: $!, binary data could not be written\n";
    print $fh $content;
    close $fh;
    rename $temp_file, $file || die("Cannot rename $temp_file to $file\n");

    chmod $restore_mode, $file if $restore_mode; # restore old perms if we changed it
}

1;