#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;

my $VERSION="1.0d";
my $date="2004/06/04";

my $units;

sub get_real_cap {
    $_=shift;

    die "Invalid capacity format '$_'" if(!/^(\d+(?:\.\d*)?)([kmgKMG]?)/);
    my $cap=$1;
    $units=uc $2;
    return int $cap*1024 if $units eq 'K';
    return int $cap*1024*1024 if $units eq 'M';
    return int $cap*1024*1024*1024 if $units eq 'G';
}

sub get_human_size {
    $_=shift;

    $_=sprintf("%.2f",$_/1024) if $units eq 'K';
    $_=sprintf("%.2f",$_/(1024*1024)) if $units eq 'M';
    $_=sprintf("%.2f",$_/(1024*1024*1024)) if $units eq 'G';

    return $_.$units;
}

my $my_cap="4485M";
my ($start_time,$end_time);

my @ftable;
my $nr_files=0;
my $nr_combinations;
my $nr_large=0;     # Objects that are larger than media
my $total_size=0;
my $fl_all=0;

sub usage {
    my $ec=shift;
    print<<EOH;
lorry $VERSION, written by Ondra Havel, $date
-help           give this help
-size <sz>      optimize for given size (default $my_cap)
                K,M,G suffixes are recognized
-all            process all files, do multiple volumes
EOH

    exit defined $ec ? $ec : 0;
}

$start_time=time;

GetOptions("help"=>\&usage,"size=s"=>\$my_cap,"all"=>\$fl_all);

my $real_cap=get_real_cap $my_cap;
print STDERR "Optimizing for capacity $my_cap ($real_cap bytes)\n";

for (@ARGV) {
    next if /^\.\.?$/;
        next if ! -e $_;
        s/`/\\`/g;
    my $s=`du -sb "$_"`;
        chomp $s;
        $s=~s/^(\d+).*/$1/;
    if($s>$real_cap) {
        $nr_large++;
        next;
    }
    push(@ftable,{name=>$_, size=>$s});
    $total_size+=$s;
    $nr_files++;
}

print STDERR "Warning $nr_large objects were rejected because of large size.\n" if $nr_large;
if(!$nr_files) {
    print STDERR "Nothing to do\n";
    exit 0;
}
print STDERR "Processing $nr_files objects, total size @{[ get_human_size $total_size
]}.\n";

if($total_size<=$real_cap) {
    print STDERR "Nothing to do media capacity not exceeded.\n";
    print "${$_}{'name'}\n" for (@ftable);
    exit 0;
}

sub do_combine {
    my($my_idx,$my_limit,$my_res)=@_;

    return 0 if $my_idx==scalar @ftable;

    $nr_combinations++;

    my $ret0=[];
    my $s0=do_combine($my_idx+1,$my_limit,$ret0);
    my $my_size=$ftable[$my_idx]{'size'};

    if($my_size<=$my_limit) {
        my $s1;
        my $ret1=[];
        $s1=$my_size+do_combine($my_idx+1,$my_limit-$my_size,$ret1);
        if($s1>$s0) {
            push @{$my_res},$my_idx;
            push @{$my_res},@{$ret1};
            return $s1;
        }
    }

    push @{$my_res},@{$ret0};
    return $s0;
}

while(scalar @ftable) {
    my $myres=[];
    $nr_combinations=0;
    my $gsize=do_combine 0,$real_cap,$myres;
    for(@{$myres}) {
        print "$ftable[$_]{'name'}\n";
        $ftable[$_]='';
    }
    my $eff=sprintf("%.2f",100*$gsize/$real_cap);
    print STDERR "$nr_combinations combinations considered, $gsize/$real_cap ($eff%, ".($real_cap-$gsize)." bytes left)\n";
    last if !$fl_all;
    print "\n";
    @ftable=grep(!/^$/,@ftable);
}

$end_time=time;

$end_time-=$start_time;
if($end_time) {
    my $days=int($end_time/86400);
    $end_time%=86400;
    my $hours=int($end_time/3600);
    $end_time%=3600;
    my $mins=int($end_time/60);
    my $secs=$end_time % 60;

    print STDERR "Elapsed time: ";
    print STDERR "$days days  " if $days;
    print STDERR "$hours hours  " if $hours or $days;
    print STDERR "$mins minutes  " if $mins or $hours or $days;
    print STDERR "$secs seconds\n";
}