#!/usr/bin/perl
# -*- perl -*-

# Foomatic PPD file generator for manual PPD generation (via "-p",
# "-d", "-A", and "-P" command line options) or for automatic
# on-the-fly PPD generation by CUPS 1.2 or newer (via "cat" and "list"
# command line options).

use Foomatic::Defaults;
use Foomatic::DB;
use Getopt::Std;
use Data::Dumper;
#use strict;

my $debug = 0;

# Use program name as the first part of the PPD URI for CUPS (should be 
# "foomatic").
$0 =~ m!/([^/]+)\s*$!;
my $progname = ($1 || $0);

# Default settings for listing PPDs by cups-driverd

# List only the PPD file with the reconmmended driver for each printer
# and do not show "(recommended)" in the CUPS web interface. This mode
# makes CUPS setup with the web interface very easy for beginners.
# This can be set by "OnlyRecommended Yes" or "OnlyRecommended No" in
# /etc/cups/foomatic.conf
my $onlyrecommended = 0;
# The Foomatic database does not only generate PPD files from Foomatic
# XML data but also of ready-made PPDs mainly from printer manufacturers
# for their PostScript printers. As these PPDs are often also linked to
# the directories in which CUPS links directly to PPD files, duplicate
# listing of these PPDs by CUPS would result. Therefore we suppress
# listing the ready-made PPDs. This behaviour can be changed by using
# "ListReadyMadePPDs Yes" or "ListReadyMadePPDs No" in
# /etc/cups/foomatic.conf
my $listreadymadeppds = 0;
help() if !@ARGV;
#my ($opt_h, $opt_d, $opt_p, $opt_A, $opt_P, $opt_w);
getopts("AP:d:p:hwt:");
help() if $opt_h;
my $drv = $opt_d;
my $poid   = $opt_p;
my $showall   = $opt_A;
my $showmatch   = $opt_P;
help() if ($#ARGV > 1) && !($poid);

if ($ARGV[0] =~ /^list$/i) {
    # List all available PPD files (format for cups-driverd)
    cupslistppds();
} elsif ($ARGV[0] =~ /^cat$/i) {
    # Generate and return the selected PPD file (cups-driverd command line)
    generateppd($ARGV[1]);
} elsif ($showall or $showmatch) {
    # List all PPD files or files matching regexp (manual operation)
    foomaticlistppds($showmatch);
} elsif ($poid) {
    # Generate and return the selected PPD file (manual operation)
    generateppd($poid, $drv);
} else {
    help();
}

exit(0);

sub cupslistppds {

    # Read configuration in /etc/cups/foomatic.conf
    my $conffilename;
    if (my $cupsserverroot = $ENV{CUPS_SERVERROOT}) {
        $conffilename = "$cupsserverroot/foomatic.conf";
    } else {
        $conffilename = "/etc/cups/foomatic.conf";
    }
    if (-r $conffilename and
    open CONF, "< $conffilename") {
    while (my $line = <CONF>) {
        chomp $line;
        if ($line =~
        /^\s*OnlyRecommended\s+(Yes|On|True|1)\s*$/i) {
        $onlyrecommended = 1;
        } elsif ($line =~
             /^\s*OnlyRecommended\s+(No|Off|False|0)\s*$/i) {
        $onlyrecommended = 0;
        }
        if ($line =~
        /^\s*ListReadyMadePPDs\s+(Yes|On|True|1)\s*$/i) {
        $listreadymadeppds = 1;
        } elsif ($line =~
             /^\s*ListReadyMadePPDs\s+(No|Off|False|0)\s*$/i) {
        $listreadymadeppds = 0;
        }
    }
    close CONF;
    }

    my $db = Foomatic::DB->new();
    $db->get_overview(1, 1 + listreadymadeppds);

    for my $printer (@{$db->{'overview'}}) {
    my $poid = $printer->{'id'};
    my $make = $printer->{'make'};
    my $model = $printer->{'model'};
    my $recdriver = $printer->{'driver'};
    my @drivers = @{$printer->{'drivers'}};
    my $id = $printer->{'ieee'};

    # No drivers => No PPDs
    next if $#drivers < 0;

    # Put the reconmmended driver to the beginning of list, as CUPS
    # probably will take the first PPD which matches the printer model
    my @sorteddrivers;
    if (Foomatic::DB::member($recdriver, @drivers)) {
        # Valid entry for the recommended driver
        push(@sorteddrivers, $recdriver);
        if (!$onlyrecommended) {
        foreach my $driver (@drivers) {
            push(@sorteddrivers, $driver) if $driver ne $recdriver;
        }
        }
    } else {
        # Invalid entry for the recommended driver
        next if $onlyrecommended;
        undef $recdriver;
        @sorteddrivers = @drivers;
    }

    # Go through all the drivers and list the PPD entries
    foreach my $driver (@sorteddrivers) {
        # Get PPD header data from the PPD file generator
        my ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
        $drivername,$nickname,$modelname) =
            Foomatic::DB::getppdheaderdata($printer, $driver, 
                           ($onlyrecommended ? '' :
                            $printer->{'driver'}));
        print "\"$progname:$longname\" en \"$make\" \"$nickname\" \"$ieee1284\"\n";
    }
    }
}

sub foomaticlistppds {

    my ($match) = @_;

    my $db = Foomatic::DB->new();
    $db->get_overview();
    my @drivers = $db->get_driverlist();

    for my $printer (@{$db->{'overview'}}) {
    my $pr = $printer->{'make'};
    my $model = $printer->{'model'};
    my $name = "$pr $model";
    my $driver = ($printer->{'driver'} || "No Default Driver");
    my $dlist = "";
    my $dcount = 0;
    if( $printer->{'drivers'} ){
        $dcount = @{$printer->{'drivers'}};
        for my $d (@{$printer->{'drivers'}}) {
        $dlist .= "$d ";
        }
    }
    if (not $match or "$name" =~ m{$match}o) {
        print "$name Id='$printer->{'id'}' Driver='$driver'";
        if ($dcount > 1){
        print " CompatibleDrivers='$dlist'";
        }
        print "\n";
    }
    }
}

sub generateppd {

    my ($ppduri, $driver) = @_;
    my $poid;

    my $db = Foomatic::DB->new();
    $db->get_overview();
    my $printer;
    my @drivers = $db->get_driverlist();

    if ($ppduri =~ /^$progname:(.*)\.ppd$/) {
    # cups-driverd operation
    # We try to split between printer name and driver name at all
    # dashes in the PPD file name, as some drivers (ex. Gutenprint)
    # have dashes in their names.
    my $ppdname = $1;
    my @poidcomponents = split(/-/, $ppdname);
    my @drivercomponents = ();
    while ($#poidcomponents > 1) {
        unshift(@drivercomponents, pop(@poidcomponents));
        $driver = join('-', @drivercomponents);
        next if !Foomatic::DB::member($driver, @drivers);
        $poid = join('-', @poidcomponents);
        last;
    }
    die "ERROR: Could not determine driver name for $ppdname.ppd!\n"
        if( !$poid );
    } else {
    # manual operation
    $poid = $ppduri;
    }

    # If the user supplies an old numerical printer ID, translate it to
    # a new clear-text ID
    $poid = Foomatic::DB::translate_printer_id($poid);

    my $lcname = lc( $poid );
    my $pentry;
    for $printer (@{$db->{'overview'}}) {
    my $name = lc( $printer->{'id'} );
    print STDERR "DEBUG2: $progname: compare '$lcname' to '$name'\n" if $debug;
    if( $name eq $lcname ){
        $pentry = $printer;
        last;
    }
    }

    die "ERROR: Printer '$poid' not found!\n" if( not defined $pentry );
    print STDERR "DEBUG: $progname: Found $pentry->{id}\nDEBUG2: $progname: " .
    join ("\nDEBUG2: $progname: ", split(/\n/, Dumper($pentry))) .
    "\n" if $debug;
    
    if (!$driver || ($driver =~ /(default|recommended)/i)) {
    $driver = $pentry->{'driver'};
    if( not defined( $driver ) ){
        die "ERROR: $progname: Printer '$poid' does not have default driver!\n";
    }
    }
    
    my $found = 0;
    for my $d (@{$pentry->{'drivers'}}) {
    last if ($found = ($driver eq $d));
    }
    if ( not $found ) {
    warn "ERROR: $progname: Printer '$poid' and driver '$driver' are not compatible\n";
    }
    $found = 0;
    for my $d (@drivers) {
    last if ($found = ($driver eq $d));
    }
    if ( not $found ) {
    die "ERROR: $progname: Driver '$driver' not in database!\n";
    }

    # Get all the data about this driver/printer pair
    my $possible = $db->getdat($driver, $poid);
    # Stop if the printer is not supported by the given driver
    die "ERROR: $progname: That printer and driver combination is not possible.\n"
    if (!$possible);
    # Stop if the driver entry has an empty command line prototype or if there 
    # is no custom PPD file
    die "ERROR: $progname: There is neither a custom PPD file nor the driver database entry contains sufficient data to build a PPD file.\n"
    if (!$db->{'dat'}{'cmd'}) && (!$db->{'dat'}{'ppdfile'});
    
    my @data;

    @data = $db->getppd($opt_w);
    die "ERROR: $progname: No PPD file for printer '$poid' and driver '$driver'!\n"
    if not @data;

    print @data;

}

sub help {
    print <<HELP;

$progname -A
$progname -P <regexpr>
$progname -p <printerid> [-d <driver>] [-w]
$progname list
$progname cat <CUPS PPD URI> [-w]
$progname -h

 -A             : show all Printer ID's and compatible drivers
 -P <regexpr>   : show all Printer ID's whose names and model
                  matched by the RE.  For example:
                   -P HP will match all names with HP in them
 -p <printerid> : Printer ID
 -d <driver>    : Driver name
                  If the driver is not specified then the default driver 
                  for the <printerid> is used.
 list           : List all possible PPDs in the format needed by the
                  cups-driverd
 cat <CUPS PPD URI> : Generate PPD file appropriate to the <CUPS PPD URI>.
                  Available CUPS PPD URIs are listed by 
                  "$progname list".
 -w             : Generate PPD which is compatible with the CUPS PostScript
                  driver for Windows (GUI strings are limited to 39 
                  characters).
 -h             : show help information


HELP
    exit 1;

}