package network::ndiswrapper;

use strict;
use common;
use modules;
use detect_devices;

#- using bsd_glob() since glob("/DONT_EXIST") return "/DONT_EXIST" instead of () (and we don't want this)
use File::Glob ':glob';

my $ndiswrapper_root = "/etc/ndiswrapper";

sub installed_drivers() {
    grep { -d $::prefix . "$ndiswrapper_root/$_" } all($::prefix . $ndiswrapper_root);
}

sub present_devices {
    my ($driver) = @_;
    my @supported_devices;
    foreach (all($::prefix . "$ndiswrapper_root/$driver")) {
        my ($ids) = /^([0-9A-F]{4}:[0-9A-F]{4})\.[0-9A-F]\.conf$/;
        $ids and push @supported_devices, $ids;
    }
    grep { member(uc(sprintf("%04x:%04x", $_->{vendor}, $_->{id})), @supported_devices) } detect_devices::probeall();
}

sub get_devices {
    my ($in, $driver) = @_;
    my @devices = present_devices($driver);
    @devices or $in->ask_warn(N("Error"), N("No device supporting the %s ndiswrapper driver is present!", $driver));
    @devices;
}

sub ask_driver {
    my ($in) = @_;
    if (my $inf_file = $in->ask_file(N("Please select the Windows driver (.inf file)"), "/media")) {
        my $driver = basename(lc($inf_file));
        $driver =~ s/\.inf$//;

        #- first uninstall the driver if present, may solve issues if it is corrupted
        require run_program;
        -d $::prefix . "$ndiswrapper_root/$driver" and run_program::rooted($::prefix, 'ndiswrapper', '-e', $driver);

        unless (run_program::rooted($::prefix, 'ndiswrapper', '-i', $inf_file)) {
            $in->ask_warn(N("Error"), N("Unable to install the %s ndiswrapper driver!", $driver));
            return undef;
        }

        return $driver;
    }
    undef;
}

sub find_matching_devices {
    my ($device) = @_;
    my $net_path = '/sys/class/net';
    my @devices;

    my $is_driver_listed = sub { my ($driver) = @_; any { member($driver, @{$_->{drivers}}) } @devices };

    require network::connection::ethernet;
    foreach my $interface (all($net_path)) {
        if (network::connection::ethernet::device_matches_interface($device, $interface)) {
            my $driver = network::connection::ethernet::interface_to_driver($interface);
            push @devices, { interface => $interface, drivers => [ $driver ] } if $driver;
        }
    }

    #- find drivers with no net interface
    my $sysfs_driver = $device->{sysfs_device} && basename(readlink($device->{sysfs_device} . "/driver/module"));
    if ($sysfs_driver) {
	my @sysfs_drivers = $sysfs_driver;
	if ($sysfs_drivers[0] eq 'ssb') {
	    push @sysfs_drivers, map { basename(readlink($_)) } bsd_glob($device->{sysfs_device} . "/ssb*/driver/module");
	}
	@sysfs_drivers = grep { !$is_driver_listed->($_) } @sysfs_drivers;
	push @devices, { interface => undef, drivers => \@sysfs_drivers } if @sysfs_drivers;
    }

    #- add original driver
    push @devices, { interface => undef, drivers => [ $device->{driver} ] }
        if !$is_driver_listed->($device->{driver}) && any { $_ eq $device->{driver} } modules::loaded_modules();

    @devices;
}

sub find_conflicting_devices {
    my ($device) = @_;
    grep { !member("ndiswrapper", @{$_->{drivers}}) } find_matching_devices($device);
}

sub find_interface {
    my ($device) = @_;
    my $dev = find { member("ndiswrapper", @{$_->{drivers}}) } find_matching_devices($device);
    $dev->{interface};
}

sub setup_device {
    my ($in, $device) = @_;

    my @conflicts = find_conflicting_devices($device);
    if (@conflicts) {
        $in->ask_yesorno(N("Warning"), N("The selected device has already been configured with the %s driver.
Do you really want to use a ndiswrapper driver?", $conflicts[0]->{drivers}[0])) or return;
        #- stop old interfaces
        network::tools::stop_interface($_->{interface}, 0) foreach grep { defined $_->{interface} } @conflicts;
        #- unload old modules before trying to load ndiswrapper
        #- (sorted according to /proc/modules to handle deps nicely)
        my @drivers = intersection([ modules::loaded_modules() ], [ map { @{$_->{drivers}} } @conflicts ]);
        eval { modules::unload($_) } foreach @drivers;
    }

    #- unload ndiswrapper first so that the newly installed .inf files will be read
    eval { modules::unload("ndiswrapper") };
    eval { modules::load("ndiswrapper") };

    if ($@) {
        $in->ask_warn(N("Error"), N("Unable to load the ndiswrapper module!"));
        return;
    }

    my $interface = find_interface($device);
    unless ($interface) {
        $in->ask_warn(N("Error"), N("Unable to find the ndiswrapper interface!"));
        return;
    }

    $interface;
}

sub select_device {
    my ($in) = @_;
    my $driver;
    my @drivers = installed_drivers();
    if (@drivers) {
        $driver ||= first(@drivers);
        $in->ask_from('', N("Choose an ndiswrapper driver"), [
            { type => "list", val => \$driver, allow_empty_list => 1,
              list => [ undef, @drivers ],
              format => sub { defined $_[0] ? N("Use the ndiswrapper driver %s", $_[0]) : N("Install a new driver") } }
        ]) or return;
    }
    $driver ||= ask_driver($in) or return;

    my @devices = get_devices($in, $driver) or return;
    my $device;
    if (@devices == 1) {
        #- only one device matches installed driver
        $device = $devices[0];
    } else {
        $in->ask_from('', N("Select a device:"), [
            { type => "list", val => \$device, allow_empty_list => 1,
              list => [ present_devices($driver) ],
              format => sub { $_[0]{description} } }
        ]) or return;
    }
    return $device;
}

1;