#!/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";
    }
    my $onlyrecommended = 0;
    my $listreadymadeppds = 0;
    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:$printer->{'id'}-$driver.ppd\" 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();
    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. hpijs-pcl3)
	# have dashes in their names.
	my $ppdname = $1;
	my @poidcomponents = split(/-/, $ppdname);
	my @drivercomponents = ();
	my @printers = $db->get_printerlist();
	while ($#poidcomponents > 1) {
	    unshift(@drivercomponents, pop(@poidcomponents));
	    $driver = join('-', @drivercomponents);
	    $printer = join('-', @poidcomponents);
	    my @driver_printers = $db->get_printers_for_driver ($driver);
	    next if ((!Foomatic::DB::member($driver, @drivers) or
		      !Foomatic::DB::member($printer, @printers)) and
		     !Foomatic::DB::member($printer, @driver_printers));
	    $poid = $printer;
	    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 = $db->get_printer($poid);
    if (!$pentry || ($pentry->{'noxmlentry'} != 0)) {
	$pentry = undef;
	$db->get_overview();
	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;
		$poid = $pentry->{'id'};
		print STDERR "DEBUG: $progname: Using printer entry '$poid'\n" if $debug;
		last;
	    }
	}
    }

    print STDERR "DEBUG: Printer '$poid' not found!\n" if ($debug && 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 @drvlist = $db->get_drivers_for_printer($poid);
    my $found = 0;
    for my $d (@drvlist) {
	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 ) {
	print STDERR "DEBUG: $progname: Driver '$driver' not in database!\n" if $debug;
    }

    # 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;

}
