#!/usr/bin/perl
#
# This module parses NT .INF files containing printer driver information.
# The information is turned into a magical set of data strucutures
# allowing the driver programs to do something useful with the printer
# driver.

package inf_nt;

use strict;
use vars qw($VERSION @ISA @EXPORT_OK);

use Carp qw(carp croak);
use INF qw(inf_get_key inf_get_keys inf_has_key);

require Exporter;

$VERSION = "0.01";
@ISA = qw(Exporter);
@EXPORT_OK = qw(parse_inf_nt get_models);


# Fill in the info hash with information on what the destination directories
# for the different file sections will be. The default destination directory
# is also determined here.

sub fill_destination_dirs
{
    croak("fill_destination_dirs(INF,INFO)") if @_ != 2;
    my($inf, $info) = @_;

    # The DestDir member will hold the destination for any file section.
    $info->{"DestDir"} = {};

    # If a DestinationDirs section isn't present, bail out.
    if (not inf_has_key($inf,"DestinationDirs")) {
	$info->{"DefaultDestDir"} = "66000";
	return;
    }

    # Find and handle a DefaultDestDir if it is present.
    if (exists $inf->{"DestinationDirs"}{"DefaultDestDir"}) {
	my($num, $subdir) = split(/,/, $inf->{"DestinationDirs"}{"DefaultDestDir"});
	$info->{"DefaultDestDir"} = $num;
	$info->{"DefaultDestDir"} .= "\\$subdir" if $subdir ne '';
    }

    # Loop for each file section and add it to the DestDir hash.
    foreach my $key (keys %{$inf->{"DestinationDirs"}}) {
	next if $key eq 'DefaultDestDir';

	my $str = $inf->{"DestinationDirs"}{$key};
	my($num, $subdir) = split(/,/, $str);

	my $value = $num;
	$value .= "\\$subdir" if $subdir ne '';

	$info->{"DestDir"}{$key} = $value;
    }
}

# The files to be copied for a particular driver are stored as hash refs
# inside an array ref. Each hash ref corresponds to a single file. The
# array ref stores all the files that will be copied.

sub handle_file
{
    croak("handle_file(INFO,SRC_FILENAME,DST_FILENAME,DESTDIR)") if @_ != 4;
    my($info, $src_filename, $dst_filename, $destdir) = @_;

    # Create the CopyFiles node if it doesn't exists yet.
    if (not exists $info->{"CopyFiles"}) {
	$info->{"CopyFiles"} = [];
    }

    my $filehash = {};
    $filehash->{"SrcFilename"} = $src_filename;
    $filehash->{"DstFilename"} = $dst_filename;
    $filehash->{"DestDir"} = $destdir;

    push(@{$info->{"CopyFiles"}}, $filehash);
}

sub handle_CopyFiles
{
    croak("handle_CopyFiles(INFO,INF,FILELIST)") if @_ != 3;
    my($info, $inf, $filelist) = @_;

    foreach my $specifier (split(/\s*,\s*/, $filelist)) {
	if ($specifier =~ /^@/) {
	    # A single file was specified.

	    my $fname = substr($specifier, 1);
	    handle_file($info, $fname, $fname, $info->{DefaultDestDir});
	} else {
	    # An entire section containing files to copy was specified.
	    if (not exists $inf->{$specifier}) {
		print "CopyFiles wants section $specifier not present.\n";
		exit(1);
	    }
	    foreach my $key (keys %{$inf->{$specifier}}) {
		my($dst_filename, $src_filename) = split(/\s*,\s*/, $key);
		my $destdir;

		if (exists $info->{"DestDir"}{$specifier}) {
		    $destdir = $info->{"DestDir"}{$specifier};
		} else {
		    $destdir = $info->{DefaultDestDir};
		}

		$dst_filename = $src_filename if
		    $dst_filename eq '' and $src_filename ne '';
		$src_filename = $dst_filename if $src_filename eq '';
		  
		handle_file($info, $src_filename, $dst_filename, $destdir);
	    }
	}
    }
}

sub handle_AddReg
{
    croak("handle_AddReg(INFO, SECTION_HASH)") if @_ != 2;
    my($info, $section) = @_;

    foreach my $entry (keys %{$section}) {
	my @data = split(/\s*,\s*/, $entry);
	if (not exists $info->{"AddReg"}) {
	    $info->{"AddReg"} = [];
	}
	push(@{$info->{"AddReg"}}, [ @data ]);
    }
}

sub handle_DelReg
{
    croak("handle_DelReg(INFO, SECTION_HASH)") if @_ != 2;
    my($info, $section) = @_;

    foreach my $entry (keys %{$section}) {
	my @data = split(/\s*,\s*/, $entry);
	if (not exists $info->{"DelReg"}) {
	    $info->{"DelReg"} = [];
	}
	push(@{$info->{"DelReg"}}, [ @data ]);
    }
}

sub handle_SourceDisksNames
{
    croak("handle_SourceDisksNames(INFO,SECTION_HASH)") if @_ != 2;
    my($info, $section) = @_;

    foreach my $ordinal (keys %{$section}) {
	my($descr, $tagfile, undef, $path) =
	    split(/\s*,\s*/, $section->{$ordinal});

	if (not exists $info->{"SourceDisksNames"}) {
	    $info->{"SourceDisksNames"} = {};
	}

	# If the ordinal already exists, skip over to allow overrides by
	# the platform-specific sections.
	next if exists $info->{"SourceDisksNames"}->{$ordinal};

	# Insert an entry for this source disk.
	$info->{"SourceDisksNames"}->{$ordinal} = {
	    'description' => $descr,
	    'tagfile' => $tagfile,
	    'path' => $path
	};
    }
}

sub handle_SourceDisksFiles
{
    croak("handle_SourceDisksFiles(INFO,SECTION_HASH)") if @_ != 2;
    my($info, $section) = @_;

    foreach my $fname (keys %{$section}) {
	my($ordinal, $subdir, $size) = split(/\s*,\s*/, $section->{$fname});
	$fname =~ tr/a-z/A-Z/;

	if (not exists $info->{"SourceDisksFiles"}) {
	    $info->{"SourceDisksFiles"} = {};
	}

	# If the ordinal already exists, skip over to allow overrides by
	# the platform-specific sections.
	next if exists $info->{"SourceDisksFiles"}->{$fname};

	# Insert an entry for this source file.
	$info->{"SourceDisksFiles"}->{$fname} = {
	    'ordinal' => $ordinal
	};
    }
}

sub get_models
{
    croak("get_manufacturers(INF, MANUFACTURER)") if @_ != 2;
    my($inf, $manufacturer) = @_;
    my($device_section_name);

    if (inf_has_key(inf_get_key($inf, "Manufacturer"), $manufacturer)) {
	if (inf_get_keys($inf, "Manufacturer", $manufacturer) eq '') {
	    $device_section_name = $manufacturer;
	} else {
	    $device_section_name = inf_get_keys($inf, "Manufacturer", $manufacturer);
	}
    } else {
	return undef;
    }

    if (inf_has_key($inf, $device_section_name)) {
	return keys %{inf_get_key($inf, $device_section_name)};
    } else {
	return undef;
    }
}

sub parse_inf_nt
{
    croak("parse_inf_nt(INF,MANUFACTURER,MODEL,ARCH)") if @_ != 4;
    my($inf, $manufacturer, $model, $arch) = @_;
    my(%info, $device_section_name);

    # Make sure that the [Manufacturer] section exists.
    if (not inf_has_key($inf, "Manufacturer")) {
	print STDERR "[Manufacturer] section not present.\n";
	return undef;
    }

    # Make sure that the manufacturer is actually listed.
    if (inf_has_key(inf_get_key($inf, "Manufacturer"), $manufacturer)) {
	if (inf_get_key(inf_get_key($inf, "Manufacturer"), $manufacturer) eq '') {
	    $device_section_name = $manufacturer;
	} else {
	    $device_section_name = inf_get_key(inf_get_key($inf, "Manufacturer"), $manufacturer);
	}
    } else {
	print STDERR "ERROR: Manufacturer $manufacturer not listed.\n";
	return undef;
    }

    # We now know the name of the device section. Try to find the device that
    # the caller gave us in this section.
    if (not inf_has_key($inf, $device_section_name)) {
	print STDERR "Device section `$device_section_name' does not exist.\n";
	return undef;
    }
    if (not inf_has_key(inf_get_key($inf, $device_section_name), $model)) {
	print STDERR "Specified model ($model) not present in .INF file.\n";
	return undef;
    }

    # This model exists so fill in the device name.
    $info{pName} = $model;

    # Figure out the name of the install section. The install section is the
    # heart of the .INF file and determines all actions to be taken.
    my($install_section_name, @DEVICE_IDS) = split(/\s*,\s*/, inf_get_key(inf_get_key($inf, $device_section_name), $model));
    if ($install_section_name eq '') {
	print STDERR "Install section name is blank.\n";
	return undef;
    }

    # Determine if this INF is for NT.
    my $is_NT = 0;
    $is_NT = 1 if $arch =~ /^W32X86$/i;
    $is_NT = 1 if $arch =~ /^W32alpha$/i;
    $is_NT = 1 if $arch =~ /^W32mips$/i;
    $is_NT = 1 if $arch =~ /^W32ppc$/i;

    # Make sure the install section exists.
    if (not inf_has_key($inf, $install_section_name)
	and not inf_has_key($inf, $install_section_name . ".nt")
	and not inf_has_key($inf, $install_section_name . ".ntx86")) {
	print STDERR "Install section ($install_section_name) not present.\n";
	return undef;
    }

    my $install_section;
    if ($is_NT and inf_has_key($inf, $install_section_name . ".ntx86")) {
	$install_section = inf_get_key($inf, $install_section_name . ".ntx86");
    } elsif ($is_NT and inf_has_key($inf, $install_section_name . ".nt")) {
	$install_section = inf_get_key($inf, $install_section_name . ".nt");
    } else {
	$install_section = inf_get_key($inf, $install_section_name);
    }

    # See if there is an optional data section.
    my $data_section = {};
    if (inf_has_key($install_section, "DataSection")) {
	if (not inf_has_key($inf, inf_get_key($install_section, "DataSection"))) {
	    print STDERR "DataSection directive given but section missing.\n";
	    return undef;
	}
	$data_section = inf_get_key($inf, inf_get_key($install_section, "DataSection"));
    }

    # Handle the LanguageMonitor directive.
    if (inf_has_key($data_section, "LanguageMonitor")) {
	$info{"LanguageMonitor"} = inf_get_key($data_section, "LanguageMonitor");
    } elsif (inf_has_key($install_section, "LanguageMonitor")) {
	$info{"LanguageMonitor"} = inf_get_key($install_section, "LanguageMonitor");
    }

    # Handle the DriverFile directive.
    if (inf_has_key($data_section, "DriverFile")) {
	$info{"DriverFile"} = inf_get_key($data_section, "DriverFile");
    } elsif (inf_has_key($install_section, "DriverFile")) {
	$info{"DriverFile"} = inf_get_key($install_section, "DriverFile");
    }
    if ($info{"DriverFile"} eq '') {
	$info{"DriverFile"} = $install_section_name;
    }

    # Handle the DataFile directive.
    if (inf_has_key($data_section, "DataFile")) {
	$info{"DataFile"} = inf_get_key($data_section, "DataFile");
    } elsif (inf_has_key($install_section, "DataFile")) {
	$info{"DataFile"} = inf_get_key($install_section, "DataFile");
    }
    if ($info{"DataFile"} eq '') {
	$info{"DataFile"} = $install_section_name;
    }

    # Handle the ConfigFile directive.
    if (inf_has_key($data_section, "ConfigFile")) {
	$info{"ConfigFile"} = inf_get_key($data_section, "ConfigFile");
    } elsif (inf_has_key($install_section, "ConfigFile")) {
	$info{"ConfigFile"} = inf_get_key($install_section, "ConfigFile");
    }
    ## Windows NT always splits the driver and config file up
    ## Windows 9x places them in the same file
    if ($info{"ConfigFile"} eq '') {
	# $info{"ConfigFile"} = $install_section_name;
	$info{"ConfigFile"} = $info{"DriverFile"};
    }

    # Handle the HelpFile directive.
    if (inf_has_key($data_section, "HelpFile")) {
	$info{"HelpFile"} = inf_get_key($data_section, "HelpFile");
    } elsif (inf_has_key($install_section, "HelpFile")) {
	$info{"HelpFile"} = inf_get_key($install_section, "HelpFile");
    }

    # Parse the destination directory information.
    fill_destination_dirs($inf, \%info);

    # Handle any files that need to be copied.
    if (inf_has_key($data_section, "CopyFiles")) {
	handle_CopyFiles(\%info, $inf, inf_get_key($data_section, "CopyFiles"));
    }
    if (inf_has_key($install_section, "CopyFiles")) {
	handle_CopyFiles(\%info, $inf, inf_get_key($install_section, "CopyFiles"));
    }

    # Handle any AddReg sections.
    if (inf_has_key($install_section, "AddReg")) {
	foreach my $addreg_name (split(/\s*,\s*/,
				 inf_get_key($install_section, "AddReg"))) {
	    if (not inf_has_key($inf, $addreg_name)) {
		print STDERR "ERROR: Section given in AddReg doesn't exist.\n";
		return undef;
	    }
	    handle_AddReg(\%info, inf_get_key($inf, $addreg_name));
	}
    }

    # Handle any DelReg sections.
    if (inf_has_key($install_section, "DelReg")) {
	foreach my $delreg_name (split(/\s*,\s*/,
				 inf_get_key($install_section, "DelReg"))) {
	    if (not inf_has_key($inf, $delreg_name)) {
		print STDERR "ERROR: Section given in DelReg doesn't exist.\n";
		return undef;
	    }
	    handle_DelReg(\%info, inf_get_key($inf, $delreg_name));
	}
    }

    # Handle any SourceDisksNames sections.
    if (inf_has_key($inf, "SourceDisksNames.x86")) {
	handle_SourceDisksNames(\%info, inf_get_key($inf, "SourceDisksNames.x86"));
    }
    if (inf_has_key($inf, "SourceDisksNames")) {
	handle_SourceDisksNames(\%info, inf_get_key($inf, "SourceDisksNames"));
    }

    # Handle any SourceDisksFiles section.
    if (inf_has_key($inf, "SourceDisksFiles.x86")) {
	handle_SourceDisksFiles(\%info, inf_get_key($inf, "SourceDisksFiles.x86"));
    }
    if (inf_has_key($inf, "SourceDisksFiles")) {
	handle_SourceDisksFiles(\%info, inf_get_key($inf, "SourceDisksFiles"));
    }

    return %info;
}

1;
