package Packages::Release;

use strict;
use warnings;

use Date::Parse;

sub new {
    my $classname = shift;
    my $config = shift || {};

    my $self = {};
    bless( $self, $classname );

    $self->{config} = $config;
    if ($config->{file}) {
	$self->parse;
    }

    return $self;
}

sub parse {
    my ($self, $file, $config) = @_;

    $self->config(%$config) if $config;

    $self->{config}{file} = $file if $file;
    return unless $self->{config}{file};

    local $/ = undef;

    open(my $rf, '<', $self->{config}{file})
	or die "$self->{config}{file}: $!\n";

    my @content = <$rf>;
    die "too many paragraphs in release file $self->{config}{file})"
	if @content > 1;
    return unless @content && $content[0] !~ /^\s*$/;

    my %data = ();
    $_ = $content[0];
    chomp;
    s/\n /\377/g;
    while (/^(\S+):\s*(.*)\s*$/mg) {
	my ($key, $value) = ($1, $2);
	$value =~ s/\377/\n /g;
	$key =~ tr [A-Z] [a-z];
	$data{$key} = $value;
    }

    $data{components} = [ split(/\s+/, $data{components}||'') ];
    $data{architectures} = [ split(/\s+/, $data{architectures}||'') ];
    $data{timestamp} = str2time($data{date}) if $data{date};

    read_files_field( \%data, 'md5sum' );
    read_files_field( \%data, 'sha1' );
    read_files_field( \%data, 'sha256' );

    $self->{data} = \%data;
}

sub read_files_field {
    my ($data, $fieldname) = @_;

    return unless $data->{$fieldname};
    my @lines = split /\n/, $data->{$fieldname};

    foreach (@lines) {
	next if /^\s*$/;
	chomp;
	s/^\s+//;

#	warn "line=$_ ";
	my ($checksum, $size, $name) = split /\s+/, $_, 3;
#	warn "($checksum, $size, $name)\n";

	(my $basename = $name) =~ s/\.(gz|bz2)$//o;
	my $ext = 'uncompressed';
	if ($basename ne $name) {
	    $ext = $1;
	}

	if ($data->{files}{$basename}{$ext}{size}
	    and $data->{files}{$basename}{$ext}{size} != $size) {
	    die "conflicting sizes for $name: $data->{files}{$basename}{$ext}{size} != $size\n";
	}
	$data->{files}{$basename}{$ext}{size} = $size;
	$data->{files}{$basename}{$ext}{$fieldname} = $checksum;

    }
    delete($data->{$fieldname});
}

sub check {
    my ($self, $base, $config) = @_;

    $self->config(%$config) if $config;

    return unless $self->{config}{file};
    my $sigfile = "$self->{config}{file}.gpg";

    if ($self->{config}{keyring}) {
	$self->_v("checking signature\n");

	die "$self->{config}{keyring} not readable\n"
	    unless -r $self->{config}{keyring};

	if (system('gpg',
		   '--trust-model', 'always', '--no-default-keyring',
		   '--keyring', $self->{config}{keyring}, '--verify',
		   $sigfile, $self->{config}{file})) {
	    die "signature check failed.\n";
	}
    }

    $self->{config}{base} = $base if $base;
    return unless $self->{config}{base};
    return unless -d $self->{config}{base};
    return unless $self->{data}{files};

    foreach my $f (sort keys %{$self->{data}{files}}) {
	$self->_v("checking file $f:\n");

	$self->_check_file($f);
	$self->_check_file($f, 'gz');
	$self->_check_file($f, 'bz2');
    }
}

sub _check_file {
    my ($self, $file, $ext) = @_;

    my $f = "$self->{config}{base}/$file";
    $f .= ".$ext" if $ext;
    $ext ||= 'uncompressed';

    return unless exists $self->{data}{files}{$file}{$ext};

    unless (-f $f) {
	warn "\t$f doesn't exist or is not a file\n"
	    unless $self->{config}{ignoremissing};
	return;
    }

    my $size = -s _;
    $self->_v("\t$ext: ");
    if ($size == $self->{data}{files}{$file}{$ext}{size}) {
	$self->_v('size ok');
    } else {
	$self->_ce("$f size NOT OK: $size != $self->{data}{files}{$file}{$ext}{size}");
	$self->{errors}{$file}{$ext}{size} = $size;
	return;
    }

    my %checksums = %{ get_checksums($f) };

    foreach (qw(md5sum sha1 sha256)) {
	$self->_v(' ');
	if (!exists $self->{data}{files}{$file}{$ext}{$_}) {
	    $self->_v("$_ not available");
	} elsif ($checksums{$_} eq $self->{data}{files}{$file}{$ext}{$_}) {
	    $self->_v("$_ ok");
	} else {
	    $self->_ce("$f $_ NOT OK: $checksums{$_} ne $self->{data}{files}{$file}{$ext}{$_}");
	    $self->{errors}{$file}{$ext}{$_} = $checksums{$_};
	    return;
	}
    }
    $self->_v("\n");
}

sub get_checksums {
    my ($file) = @_;

    my %checksums;

    $checksums{md5sum} = `md5sum $file 2>/dev/null`;
    $checksums{sha1} = `sha1sum $file 2>/dev/null`;
    $checksums{sha256} = `sha256sum $file 2>/dev/null`;

    foreach (qw(md5sum sha1 sha256)) {
	chomp $checksums{$_};
	$checksums{$_} = (split(/\s+/, $checksums{$_}, 2))[0];
    }

    return \%checksums;
}

sub _v {
    my ($self, @text) = @_;

    print(STDERR @text)  if $self->{config}{verbose};
}

sub _ce {
    my ($self, @text) = @_;

    if ($self->{config}{dieoncheckerror}) {
	die(@text,"\n");
    } else {
	warn(@text,"\n");
    }
}

sub config {
    my ($self, %config) = @_;

    while (my ($k, $v) = each %config) {
	$self->{config}{$k} = $v;

    }
}

1;
