package AllProv;
use warnings;
use strict;
use integer;
use CheckVer;

# Terminology:
#
#   * vname - The name of a provided package.  In many cases this is the
#     same as the name of an actual binary; but in the model discussed
#     here, the vname is not attached to any specific actual binary.  In
#     this model, all vnames are virtual.
#
#   * pname - The name of one of the actual binaries which provides the
#     virtual package $vname.
#
# Each $vname has one or more @pname which provide it (where in Perl
# idiom @pname means $pnames, more or less; the `@' is Perl's plural
# marker).
#
# The topic of version numbers in this model can be slightly confusing.
# In this model, the version numbers apply to vnames not to pnames!  The
# virtual package is the one which has the version.  To complicate the
# matter further, a vname can have more than one version number---and ''
# is a valid number meaning [no version].  A simple example to
# illustrate the point:
#
#     Package: foo5
#     Version: 5.1.2
#     Provides: foo
#
#     Package: bar
#     Version: 0.8
#     Provides: foo5, foo
#
# Three vnames are seen: foo5, foo and bar.  Note that bar is indeed a
# vname, because every binary implicitly provides itself.  This leads to
# the AllProv entries
#
#     foo5 => {
#       [no version] => { bar },
#       5.1.2        => { foo5 },
#     },
#
#     foo  => {
#       [no version] => { foo5, bar },
#     },
#
#     bar  => {
#       0.8          => { bar },
#     },
#
# (Actually where it says "{ foo5, bar }", it technically
# means "{ foo5=>1, bar=>1 }", but this detail is omitted to unclutter
# the diagram.  Only the keys matter here; the values are dummy trues.)
# In the example, if one were looking for "foo5", one would find it
# provided by either of the actual binaries bar and foo5.  However, if
# one were looking for "foo5 (>= 5)", then only the actual binary foo5
# could provide this.  And "foo (>= 5)" is not available at all.
#
# The data pattern is
#
#     vname => {
#       version_a => { pname1, pname2, pname3 },
#       version_b => { pname4, pname4 },
#     },
#
# Debian plans to have versioned Provides at some time in the future.
# The model used here correctly handles versioned Provides.  The key to
# understanding it all is that the version number applies not to the
# pname (actual binary) but rather to the vname (virtual package), and
# that each vname can have more than one version.

# Instantiate and initialize a new AllProv object.  Also, let the new
# AllProv object and the existing AllBin object refer to one another by
# the null '' hash key.  (In a neat implementation, such a reference
# would not be kept in the same spaces as the pnames and vnames.
# However, a neat implementation would probably not be written in Perl.
# This implementation seems to fit the present need.  If the need grew
# much, the module will probably want to be rewritten in C++, anyway.)
#
# As the foregoing implies, this constructor slightly modifies the
# AllBin object passed to it.  It adds thereto a reference to the new
# AllProv object.
#
sub new {

  # Instantiate.
  my $class = shift;
  my $prov  = {};
  bless $prov, $class;

  # Build the provision hash.
  my $bin   = shift;
  for my $pname ( grep {length} keys %$bin ) {
    my $bin1  = $bin->{$pname};
    my $prov2 = $bin1->{prov2};
    for my $vname ( keys %$prov2 ) {
      my $ver = $prov2->{$vname};
      $prov->{$vname}{$_}{$pname} = 1 for keys %$ver;
    }
  }

  # Let the AllProv and AllBin hashes refer to one another.
  exists( $prov->{''} ) || exists( $bin->{''} )
    and die "$0: impossible: null vname or pname\n";
  $prov->{''} = $bin ;
  $bin ->{''} = $prov;

  return $prov;

}

# Does the distribution provide the specified vname, relationship and
# version, at the specified selection level?
sub does_provide {
  my( $prov, $vname, $rel, $ver, $sel ) = @_;
  defined $sel or $sel = $DebSrcBin::select_up_default;
  my $bin   = $prov->{''}
    or die "$0: AllProv fails to refer to AllBin\n";
  my $vhash = $prov->{$vname};
  for my $ver0 ( keys %$vhash ) {
    if ( CheckVer::check_ver $ver0, $rel, $ver ) {
      return '1' unless $bin;
      my $pnames = $vhash->{$ver0};
      $bin->{$_}{select} >= $sel and return '1' for keys %$pnames;
    }
  }
  return '';
}

# Check a single dependency alternation.
sub check_altern {
  my( $prov, $altern, $sel ) = @_;
  for my $vent ( @$altern ) {
    return '1' if $prov->does_provide(
      $vent->{vname}, $vent->{rel}, $vent->{ver}, $sel
    );
  }
  return '';
}

1;

# Return the pnames which could satisfy a given vname, relation and
# version.  Optionally accept a reference $$xpname to a pname string.
# If so,
#
#   * ban the pname from the return list; and
#
#   * if $$xpname given happens to be the only pname provided, clear the
#     string $$xpname to undef.
#
# (Some callers of this method may find the $$xpname uninteresting.
# This is fine; they can ignore it; in fact, they need not even provide
# it.  Other callers, however---particularly some indirect callers
# through &pnames_provided_str()---may need to be signalled when the
# only pname provided happens to be the same as the name of the
# depending package---or more to the point, when it happens to be the
# same as the name of the *conflicting* package.  Debian binaries which
# provide a virtual binary also typically replace and conflict with the
# same virtual binary.  When no other package selected provides the
# virtual binary, the conflict is probably uninteresting to the user of
# these DebParse modules.  The reference $$xpname provides a convenient
# way to flag interested indirect callers to the fact.)
#
# Further optionally accept a reference $$sel to a selection level.  If
# so,
#
#   * ban unselected binaries from the return list; and
#
#   * if this leaves no binaries, clear $$sel to undef.
#
sub pnames_provided {
  my( $prov, $vname, $rel, $ver, $xpname, $xsel ) = @_;
  $prov->{$vname} or return ();
  my $bin   = $prov->{''}
    or die "$0: AllProv fails to refer to AllBin\n";
  my $vhash = $prov->{$vname};
  my %pname;
  for my $ver0 ( keys %$vhash ) {
    CheckVer::check_ver $ver0, $rel, $ver or next;
    $pname{$_} = 1 for keys %{ $vhash->{$ver0} };
  }
  # Optionally ban pnames as needed from the output.
  {
    my @pname = keys %pname;
    if ( $xpname ) {
      delete $pname{$$xpname};
      $$xpname = undef if @pname == 1 && $pname[0] eq $$xpname;
    }
    if ( $xsel   ) {
      delete $pname{$_}
        for grep { $bin->{$_}{select} < $$xsel } @pname;
      $$xsel   = undef unless keys %pname;
    }
  }
  return sort keys %pname;
}

# Return a string of pnames provided.  Optionally suppress the
# string if the only pname is identical to the vname.  (Do not
# suppress anything, however, based on $$xpname or $$xsel.  Let the
# caller and &pnames_provided() handle these.  Just pass $$xpname
# and $$xsel through.)
sub pnames_provided_str {
  my( $prov, $vname, $rel, $ver, $suppress, $xpname, $xsel ) = @_;
  my @pp = $prov->pnames_provided( $vname, $rel, $ver, $xpname, $xsel );
  return ( $suppress && @pp == 1 && $pp[0] eq $vname )
    ? '' : ' [' . join( ' ', @pp ) . ']';
}

1;

