#!/usr/bin/perl

# drakroam: wireless network roaming GUI
# Austin Acton, 2004 <austin@mandriva.org>
# Olivier Blin, 2005-2006 <oblin@mandriva.com>
# Licensed under the GPL


use lib qw(/usr/lib/libDrakX);

use standalone;
use common;
use run_program;
use detect_devices;
use interactive;
use mygtk2;
use ugtk2 qw(:create :helpers :wrappers);
use Gtk2::SimpleList;
use network::monitor;
use network::signal_strength;
use network::network;
use network::tools;
use network::connection;
use network::connection::wireless;
use network::connection::cellular;
use modules;

my $title = N("Wireless connection");
my $icon = '/usr/share/mcc/themes/default/drakroam-mdk.png';

$ugtk2::wm_icon = $icon;
my $w = ugtk2->new($title);
#- so that transient_for is defined, for wait messages and popups to be centered
$::main_window = $w->{real_window};
my $in = 'interactive'->vnew('su');

my %args = map { if_(/^--(\w+)=(.*)$/ && member($1, qw(ap interface)), $1 => $2) } @ARGV;

my @connection_types = qw(network::connection::wireless network::connection::cellular);
my @connections;
my $connection;

my %buttons;

my $model = Gtk2::ListStore->new('Gtk2::Gdk::Pixbuf', 'Glib::String');
my $connections_combo = Gtk2::ComboBox->new($model);
$connections_combo = Gtk2::ComboBox->new($model);
my $pix_r = Gtk2::CellRendererPixbuf->new;
$connections_combo->pack_start($pix_r, 0,);
$connections_combo->add_attribute($pix_r, pixbuf => 0);
my $text_r = Gtk2::CellRendererText->new;
$connections_combo->pack_start($text_r, 1);
$connections_combo->add_attribute($text_r, text => 1);

my $pixbuf_size = 32;
my $empty_pixbuf = Gtk2::Gdk::Pixbuf->new('rgb', 1, 8, $pixbuf_size, $pixbuf_size);
$empty_pixbuf->fill(0);

sub update_connections_list() {
    $model->set($model->append, 0, $empty_pixbuf , 1, N("No device found")) unless @connections;
    $model->set($model->append,
                0, gtknew('Pixbuf', file => $_->get_type_icon)->scale_simple($pixbuf_size, $pixbuf_size, 'hyper'),
                1, $_->get_description) foreach @connections;
    my $index = $connection && eval { find_index { $_ == $connection } @connections };
    $connections_combo->set_active($index) if defined $index;
}

sub get_connection() {
    @connections or return;
    my $index = $connections_combo->get_active;
    defined $index && $connections[$index];
}

sub prepare_connection() {
    my @packages = $connection->can('get_packages') ? $connection->get_packages : ();
    if (@packages && !$in->do_pkgs->install(@packages)) {
        $in->ask_warn(N("Error"), N("Could not install the packages (%s)!", join(', ', @packages)));
        return;
    }
    $connection->prepare_device;
    $connection->setup_thirdparty($in) or return;
    if ($connection->can("check_device") && !$connection->check_device) {
        $in->ask_warn(N("Error"), $connection->{device}{error});
        return;
    }
    if ($connection->can('get_hardware_settings')) {
        $connection->guess_hardware_settings if $connection->can('guess_hardware_settings');
        $in->ask_from_({
            title => "Network settings",
            messages => N("Please enter settings for network")
        }, $connection->get_hardware_settings) or return;
    }
    if ($connection->can('check_hardware')) {
        my $_w = $in->wait_message('', N("Configuring device..."));
        if (!$connection->check_hardware) {
            $in->ask_warn(N("Error"), $connection->{hardware}{error}) if $connection->{hardware}{error};
            return;
        }
    }
}

sub select_connection() {
    $connection = get_connection();
    prepare_connection() if $connection;
    update_on_connection_change();
}

sub update_on_connection_change() {
    $buttons{refresh}->set_sensitive(to_bool($connection));
    update_networks();
}

my $net = {};
network::network::read_net_conf($net);

my $networks_list = Gtk2::SimpleList->new(
    "AP" => "hidden",
    '' => "pixbuf",
    N("SSID") => "text",
    N("Signal strength") => "pixbuf",
    '' => "pixbuf",
    N("Encryption") => "text",
    N("Operating Mode") => "text",
);
$networks_list->get_selection->set_mode('single');

my $status_bar = Gtk2::Statusbar->new;
my $status_bar_cid = $status_bar->get_context_id("Network event");

sub get_network_event_message {
    my ($member, @args) = @_;
    #- FIXME: the hostname.d script and s2u use a different D-Bus interface
    if ($member eq 'hostname') {
        my ($hostname) = @args;
        N("Hostname changed to \"%s\"", $hostname);
    } elsif ($member eq 'status') {
        my ($status, $interface) = @args;
        my $event_connection = find { $_->get_interface eq $interface } @connections;
        $event_connection && $event_connection->get_status_message($status);
    }
}

my $dbus;
eval { $dbus = dbus_object::system_bus() };
if ($dbus) {
    eval { $net->{monitor} = network::monitor->new($dbus) };
    $dbus->{connection}->add_filter(sub {
                                        my ($_con, $msg) = @_;
                                        my $member = $msg->get_member;
                                        my $message = get_network_event_message($member, $msg->get_args_list) or return;
                                        my $m_id = $status_bar->push($status_bar_cid, $message);
                                        Glib::Timeout->add(20000, sub { $status_bar->remove($status_bar_cid, $m_id); 0 });
                                        update_networks() if $member eq 'status';
    });
    $dbus->{connection}->add_match("type='signal',interface='com.mandriva.network'");
    dbus_object::set_gtk2_watch_helper($dbus);
}

my %pixbufs =
  (
      state => { map { $_ => gtkcreate_pixbuf($_) } qw(connected disconnected refresh) },
      link_level => { map {
          $_ => gtkcreate_pixbuf('wifi-' . sprintf('%03d', $_) . '.png')->scale_simple(24, 24, 'hyper');
      } qw(20 40 60 80 100) },
      encryption => { map {
          $_ => gtkcreate_pixbuf("encryption-$_-24.png");
      } qw(open weak strong) },
  );

sub update_networks() {
    @{$networks_list->{data}} = ();

    if ($connection) {
        my $wait = $connection->network_scan_is_slow && $in->wait_message('', N("Scanning for networks..."));
        $connection->{networks} = $connection->get_networks;
        undef $wait;

        $connection->{network} ||= find { $connection->{networks}{$_}{current} } keys %{$connection->{networks}};

        my $routes = network::tools::get_routes();
        my $interface = $connection->get_interface;
        my $connected = exists $routes->{$interface}{network};

        while (my ($ap, $network) = each(%{$connection->{networks}})) {
            push @{$networks_list->{data}}, [
                $ap || $network->{name},
                $network->{current} ? $connected ? $pixbufs{state}{connected} : $pixbufs{state}{refresh} : undef,
                !$network->{essid} && exists $net->{wireless}{$ap} && $net->{wireless}{$ap}{WIRELESS_ESSID} || $network->{name},
                network::signal_strength::get_strength_icon($network),
                $pixbufs{encryption}{$network->{flags} =~ /WPA/i ? 'strong' : $network->{flags} =~ /WEP/i ? 'weak' : 'open'},
                $network->{flags},
                $network->{mode},
            ];
        }

        if ($connection->{network}) {
            my $index = eval { find_index { $_->[0] eq $connection->{network} } @{$networks_list->{data}} };
            $networks_list->select($index) if defined $index;
        }
    }

    update_on_network_change();
}

sub load_settings() {
    $connection->load_interface_settings;
    $connection->guess_network_access_settings if $connection->can('guess_network_access_settings');
    $connection->guess_protocol($net) if $connection->can('guess_protocol');
    $connection->guess_access_settings if $connection->can('guess_access_settings');
    $connection->guess_address_settings if $connection->can('guess_address_settings');
    $connection->guess_hostname_settings if $connection->can('guess_hostname_settings');
    $connection->guess_network_control_settings if $connection->can('guess_network_control_settings');
    $connection->guess_control_settings;
}

sub configure_network() {
    load_settings();

    $in->ask_from_({
        title => "Network settings",
        messages => N("Please enter settings for network")
    },
                   [
                       $connection->can('get_network_access_settings') ? (
                           { label => $connection->get_network_access_settings_label, title => 1, advanced => 1 },
                           @{$connection->get_network_access_settings},
                       ) : (),
                       $connection->can('get_protocols') ? (
                         @{$connection->get_protocol_settings},
                       ) : (),
                       $connection->can('get_access_settings') ? (
                         { label => $connection->get_access_settings_label, title => 1, advanced => 1 },
                         @{$connection->get_access_settings}
                       ) : (),
                       $connection->can('get_address_settings') ? (
                         { label => $connection->get_address_settings_label, title => 1, advanced => 1 },
                         @{$connection->get_address_settings('show_all')}
                       ) : (),
                       $connection->can('get_network_control_settings') ? (
                         @{$connection->get_network_control_settings}
                       ) : (),
                   ],
               ) or return;

    $connection->install_packages($in) if $connection->can('install_packages');
    $connection->unload_connection if $connection->can('unload_connection');

    my $modules_conf = modules::any_conf->read;
    $connection->write_settings($net, $modules_conf);
    $modules_conf->write;

    1;
}

sub connect_to_network() {
    if ($connection->selected_network_is_configured || configure_network()) {
        gtkset_mousecursor_wait($w->{window}->window);
        my $_wait = $in->wait_message(N("Please wait"), N("Connecting..."));
        #- settings have to be rewritten only if they are impacted by choices from the main window
        if ($connection->can('get_networks')) {
            load_settings();
            $connection->write_settings($net);
        }
        $connection->prepare_connection if $connection->can('prepare_connection');
        $connection->disconnect;
        $connection->connect($in, $net);
        gtkset_mousecursor_normal($w->{window}->window);
    }
}

sub select_network() {
    if ($connection) {
        my ($selected) = $networks_list->get_selected_indices;
        $connection->{network} = defined $selected && $networks_list->{data}[$selected][0];
    }
    update_on_network_change();
}

sub update_on_network_change() {
    $buttons{connect}->set_label(toggle_would_disconnect() ? N("Disconnect") : N("Connect"));
    #- always allow to disconnect if connected
    $buttons{connect}->set_sensitive($connection && ($connection->get_status || $connection->{network}));
    #- allow to configure only if a network is selected
    $buttons{configure}->set_sensitive($connection && $connection->{network});
}

sub toggle_would_disconnect() {
    my $network = $connection && $connection->get_selected_network;
    $connection && $connection->get_status &&
      (!$network || keys(%{$connection->{networks}}) <= 1 || $network->{current});
}

sub toggle_connection() {
    if (toggle_would_disconnect()) {
        gtkset_mousecursor_wait($w->{window}->window);
        my $_wait = $in->wait_message(N("Please wait"), N("Disconnecting..."));
        $connection->disconnect;
        gtkset_mousecursor_normal($w->{window}->window);
    } elsif ($connection) {
        $connection->{network} or return;
        connect_to_network();
    }
    update_on_network_change();
}

gtkadd($w->{window},
       gtknew('VBox', spacing => 5, children => [
           $::isEmbedded ? () : (0, Gtk2::Banner->new($icon, $title)),
           0, gtknew('HBox', children_tight => [ gtknew('Label_Left', text => N("Device: "), alignment => [ 0.5, 0.5 ]),
                                                 gtksignal_connect($connections_combo, changed => \&select_connection) ]),
           1, gtknew('ScrolledWindow', width => 500, height => 300, child => $networks_list),
           0, gtknew('HButtonBox', layout => 'edge', children_loose => [
                   $buttons{configure} = gtknew('Button', text => N("Configure"), clicked => \&configure_network),
                   $buttons{connect} = gtknew('Button', relief => 'half', clicked => \&toggle_connection),
                   $buttons{refresh} = gtknew('Button', text => N("Refresh"), clicked => \&update_networks),
                   gtknew('Button', text => N("Quit"), clicked => sub { Gtk2->main_quit })
           ]),
           0, $status_bar,
       ]),
   );
$networks_list->get_selection->signal_connect('changed' => \&select_network);

$w->show;

@connections = map { $_->get_connections(automatic_only => 1) } @connection_types;
$connection = $args{interface} && find { $_->get_interface eq $args{interface} } @connections;
$connection ||= find { !$_->network_scan_is_slow } @connections;
update_connections_list();
update_on_connection_change();

if ($args{ap} && $connection) {
    $connection->{network} = $args{ap};
    $w->{window}->show_all;
    connect_to_network();
}

$w->main;
