#!/usr/bin/perl

use strict;
use diagnostics;
use lib qw(/usr/lib/libDrakX);
use standalone;
use common;

use ugtk2 qw(:create :helpers :wrappers);
use interactive;
use harddrake::data;  #- needs to stay after use-ugtk2 as long as this module defines globals containing some N()


# { field => [ short_translation, full_description] }
my %fields = 
    (
     "alternative_drivers" => [ N("Alternative drivers"),
                                N("the list of alternative drivers for this sound card") ],
     "bus" => 
     [ N("Bus"), 
       N("this is the physical bus on which the device is plugged (eg: PCI, USB, ...)") ],
     "channel" => [ N("Channel"), N("EIDE/SCSI channel") ],
     "bogomips" => [ N("Bogomips"), N("The GNU/Linux kernel needs to run a calculation loop at boot time to initialize a timer counter.  Its result is stored as bogomips as a way to \"benchmark\" the cpu.") ],
     "bus_id" => 
     [ N("Bus identification"), 
       N("- PCI and USB devices: this lists the vendor, device, subvendor and subdevice PCI/USB ids") ],
     "bus_location" => 
     [ N("Location on the bus"), 
       N("- pci devices: this gives the PCI slot, device and function of this card
- eide devices: the device is either a slave or a master device
- scsi devices: the scsi bus and the scsi device ids") ],
     "cache size" => [ N("Cache size"), N("Size of the (second level) cpu cache") ],
     "capacity" => [ N("Drive capacity"), N("special capacities of the driver (burning ability and or DVD support)") ],
     "coma_bug" => [ N("Coma bug"), N("Whether this cpu has the Cyrix 6x86 Coma bug") ],
     "cpu family" => [ N("Cpuid family"), N("Family of the cpu (eg: 6 for i686 class)") ],
     "cpuid level" => [ N("Cpuid level"), N("Information level that can be obtained through the cpuid instruction") ],
     "cpu MHz" => [ N("Frequency (MHz)"), N("The CPU frequency in MHz (Megahertz which in first approximation may be coarsely assimilated to number of instructions the cpu is able to execute per second)") ],
     "description" => [ N("Description"), N("This field describes the device") ],
     "device" => [ N("Old device file"),
                   N("old static device name used in dev package") ],
     "devfs_device" => [ N("New devfs device"),  
                         N("new dynamic device name generated by core kernel devfs") ],
     "driver" => [ N("Module"), N("the module of the GNU/Linux kernel that handles the device") ],
     "flags" => [ N("Flags"), N("CPU flags reported by the kernel") ],
     "fdiv_bug" => [ N("Fdiv bug"), 
                     N("Early Intel Pentium chips manufactured have a bug in their floating point processor which did not achieve the required precision when performing a Floating point DIVision (FDIV)") ],


     "fpu" => [ N("Is FPU present"), N("yes means the processor has an arithmetic coprocessor") ],
     "fpu_exception" => [ N("Whether the FPU has an irq vector"), N("yes means the arithmetic coprocessor has an exception vector attached") ],
     "f00f_bug" => [N("F00f bug"), N("Early pentiums were buggy and freezed when decoding the F00F bytecode")],
     "hlt_bug" => [ N("Halt bug"), 
                    N("Some of the early i486DX-100 chips cannot reliably return to operating mode after the \"halt\" instruction is used") ],

     "info" => [N("Floppy format"), N("Format of floppies supported by the drive")],
     "level" => [N("Level"), N("Sub generation of the cpu")],
     "media_type" => [ N("Media class"), N("class of hardware device") ],
     "Model" => [N("Model"), N("hard disk model")],
     "model" => [N("Model"), N("Generation of the cpu (eg: 8 for PentiumIII, ...)")],
     "model name" => [N("Model name"), N("Official vendor name of the cpu")],
     "nbuttons" => [ N("Number of buttons"), N("the number of buttons the mouse has") ],
     "name" => [ N("Name"), N("the name of the CPU") ],
     "port" => [N("Port"), N("network printer port")],
     "processor" => [ N("Processor ID"), N("the number of the processor") ],
     "stepping" => [ N("Model stepping"), N("Stepping of the cpu (sub model (generation) number)") ],
     "type" => [ N("Type"), N("The type of bus on which the mouse is connected") ],
     "Vendor" => [ N("Vendor"), N("the vendor name of the device") ],
     "vendor_id" => [ N("Vendor"), N("the vendor name of the processor") ]
     );


my ($in, %IDs, $pid, $w);

my (%options, %check_boxes);
my $conffile = "/etc/sysconfig/harddrake2/ui.conf";

my ($modem_check_box, $printer_check_box, $current_device, $current_configurator);


#-PO Translators, please keep all "/" charaters !!!
my %menus = (
             'options' => N("/_Options"),
             'help' => N("/_Help")
             );

my %menu_options = (
                    'PRINTERS_DETECTION' => [ $menus{options}, N("/Autodetect _printers") ],
                    'MODEMS_DETECTION' => [ $menus{options}, N("/Autodetect _modems") ],
                    'JAZZ_DETECTION' => [ $menus{options}, N("/Autodetect _jazz drives") ],
                    );


my @menu_items = 
    (
     {   path => N("/_File"), type => '<Branch>' },
     {   path => N("/_File").N("/_Quit"), accelerator => N("<control>Q"), callback => \&quit_global    },
     {   path => join('', @{$menu_options{PRINTERS_DETECTION}}), type => '<CheckItem>',
         callback => sub { $options{PRINTERS_DETECTION} = $check_boxes{PRINTERS_DETECTION}->active } },
     {   path => join('', @{$menu_options{MODEMS_DETECTION}}), type => '<CheckItem>',
         callback => sub { $options{MODEMS_DETECTION} = $check_boxes{MODEMS_DETECTION}->active } },
     {   path => join('', @{$menu_options{JAZZ_DETECTION}}), type => '<CheckItem>',
         callback => sub { $options{JAZZ_DETECTION} = $check_boxes{JAZZ_DETECTION}->active } },
     {   path => $menus{help}, type => '<Branch>' },
     {   path => $menus{help}.N("/_Help"), callback => sub { unless (fork()) { exec("drakhelp Drakxtools-Guide.html/harddrake.html") } } },
     {
         path => $menus{help}.N("/_Fields description"), 
         callback => sub {
             if ($current_device) {
                 $in->ask_warn(N("Harddrake help"), 
                               N("Description of the fields:\n\n")
                               . join("\n\n", map { if_($fields{$_}[0], "$fields{$_}[0]: $fields{$_}[1]") } sort keys %$current_device))
                 } else {
                     $in->ask_warn(N("Select a device !"), N("Once you've selected a device, you'll be able to see the device information in fields displayed on the right frame (\"Information\")"))
                 }
             }
     },
     {   path => $menus{help}.N("/_Report Bug"),
         callback => sub { unless (fork()) { exec("drakbug --report harddrake2 &") } } },
     {   path => $menus{help}.N("/_About..."), 
         callback => sub {
             $in->ask_warn(N("About Harddrake"), 
                           join("", N("This is HardDrake, a Mandrake hardware configuration tool.\nVersion:"), " $harddrake::data::version\n", 
                                 N("Author:"), " Thierry Vignaud <tvignaud\@mandrakesoft.com> \n\n",
                                 formatAlaTeX($::license)));
         }
     }
     );

$in = 'interactive'->vnew('su');

my $wait = $in->wait_message(N("Please wait"), N("Detection in progress"));
gtkflush();

%options = getVarsFromSh($conffile);

# Build the gui
add_icon_path('/usr/share/pixmaps/harddrake2/');
$::noBorder = 1;
$w = ugtk2->new(N("Harddrake2 version ") . $harddrake::data::version);
local $::main_window;   # fake diagnostics pragma
my ($menubar, $factory, $opt_menu, $help_menu);
if ($::isEmbedded) {
    ($menubar, $factory) = create_factory_popup_menu($::Plug, @menu_items);
    $opt_menu = $factory->get_widget("<main>" . strip_first_underscore($menus{options}));
    $help_menu = $factory->get_widget("<main>" . strip_first_underscore($menus{help}));
} else {
    $::main_window = $w->{rwindow};
    ($menubar, $factory) = create_factory_menu($w->{rwindow}, @menu_items);
    $w->{window}->set_size_request(805, 550);
}

my $tree_model = Gtk2::TreeStore->new(Gtk2::GType->OBJECT, Gtk2::GType->STRING);
my ($statusbar, $sig_id);
$w->{window}->add(gtkpack_(0, Gtk2::VBox->new(0, 0),
                           if_(!$::isEmbedded, 0, $menubar),
                           1, create_hpaned(gtkadd(Gtk2::Frame->new(N("Detected hardware")), 
                                                   create_scrolled_window(gtkset_size_request(my $tree = Gtk2::TreeView->new_with_model($tree_model), 350, -1))),
                                            gtkpack_(0, Gtk2::VBox->new(0, 0),
                                                     1, gtkadd(my $frame = Gtk2::Frame->new(N("Information")),
                                                               create_scrolled_window(my $text = Gtk2::TextView->new)),
                                                     0, my $module_cfg_button = gtksignal_connect(Gtk2::Button->new(N("Configure module")),
                                                                                                  clicked => sub {
                                                                                                      require modules::interactive;
                                                                                                      modules::interactive::config_window($in, $current_device);
                                                                                                      gtkset_mousecursor_normal();
                                                                                                  }),
                                                     0, my $config_button = gtksignal_connect(Gtk2::Button->new(N("Run config tool")),
                                                                                              # we've a configurator, let's add a button for it and show it
                                                                                              clicked => sub {
                                                                                                  return 1 if defined $pid;
                                                                                                  if ($pid = fork()) {
                                                                                                      $sig_id = $statusbar->push($statusbar->get_context_id("id"),
                                                                                                                                 N("Running \"%s\" ...", $current_configurator));
                                                                                                  } else {
                                                                                                      exec($current_configurator) or die "$current_configurator missing\n";
                                                                                                  }
                                                                                              })
                                                     ),
                                            ( 'resize1' => 1)
                                            ),
                           0, $statusbar = Gtk2::Statusbar->new,
                           if_($::isEmbedded, 0, gtkpack(create_hbox(),
                                                         gtksignal_connect(Gtk2::Button->new(N("Options")), 'event' => popup_menu($opt_menu), $menubar),
                                                         gtksignal_connect(Gtk2::Button->new(N("Help")), 'event' => popup_menu($help_menu), $menubar),
                                                         gtksignal_connect(Gtk2::Button->new(N("Quit")),
                                                                           'clicked' => \&quit_global),
                                                         ),
                               )
                           )
                  );

$frame->set_size_request(300, 450) unless $::isEmbedded;
#    $tree->set_column_auto_resize(0, 1);
my (%data, %configurators);
$tree->append_column(my $pixcolumn  = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererPixbuf->new, 'pixbuf' => 0));
$tree->append_column(my $textcolumn = Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 1));
$tree->set_headers_visible(0);
$tree->get_selection()->signal_connect('changed' => sub {
    my ($select) = @_;
    my ($model, $iter) = $select->get_selected();
    if ($model) {
        my $id = $model->get($iter, 1);
        $iter->free;
        $current_device = $data{$id};
        
        if ($current_device) {
            gtktext_insert($text, [ map  {

                # The U+200E character is to force LTR display, as what what follows the colon is always in LTR (device names, paths, etc),
                # this ensures proper displaying of names like /dev/fd0 (otherwise it gets 'dev/fd0/').
                # it must come *after* the space, as the space must follow the colon following the direction of writting.
                if_($fields{$_}[0], [ $fields{$_}[0] . ": \x{200e}", { 'foreground' => 'royalblue3', 'weight' => Gtk2::Pango->WEIGHT_BOLD } ],
                    [ ($_ && $current_device->{$_} =~ /^(unknown)/ ? N("unknown") :
                       $_ && $current_device->{$_} =~ /^(Unknown)/ ? N("Unknown") : 
                       $_ && $current_device->{$_} eq 'yes' ? N("Yes") : 
                       $_ && $current_device->{$_} eq 'no'  ? N("No") : 
                       $current_device->{$_}) . "\n\n", { 'foreground' => ($_ eq 'driver' && $current_device->{$_} =~ /^unknown|^Bad:/ ? 'indian red' : 'black') } ])
                } sort keys %$current_device ]);

            foreach (keys %$current_device) {
                print "Warning: skip \"$_\" field => \"$current_device->{$_}\"\n\n" unless $fields{$_}[0];
            };
            
            # if we've valid driver, let's offer to configure it, else hide buttons 
            show_hide(defined($current_device->{driver}) && $current_device->{driver} !~ /^unknown|^Bad|^Card|^Hsf|^Removable:|\|/, $module_cfg_button);
            
            $current_configurator = $configurators{$id};
            show_hide(-x first(split /\s+/, $current_configurator), $config_button);  # strip arguments for -x test
            return 1;
        }
    }
    $text->get_buffer->set_text(N_("Click on a device in the left tree in order to display its information here."), -1);
    $config_button->hide;
    $module_cfg_button->hide;
});

# Fill the graphic devices tree with a "tree branch" widget per device category
foreach (@harddrake::data::tree) {
    my ($Ident, $title, $icon, $configurator, $detector) = @$_;
    next if ref($detector) ne "CODE"; #skip class witouth detector
    next if $Ident =~ /(MODEM|PRINTER)/ && $::testing;
    next if $Ident =~ /JAZZ/ && !$options{JAZZ_DETECTION};
    next if $Ident =~ /MODEM/ && !$options{MODEMS_DETECTION};
    next if $Ident =~ /PRINTER/ && !$options{PRINTERS_DETECTION};

    my @devices = &$detector;
    next unless @devices; # Skip empty class (no devices)

    my $parent_iter = $tree_model->append_set(undef, [ 0 => gtkcreate_pixbuf($icon), 1 => $title ]);

    # Fill the graphic tree with a "tree leaf" widget per device
    foreach (@devices) {
        # we really should test for $title there:
        if ($_->{bus} && $_->{bus} eq "PCI") {
            my $i = $_;
            $_->{bus_id} = join ':', map { if_($i->{$_} ne "65535",  sprintf("%lx", $i->{$_})) } qw(vendor id subvendor subid);
            $_->{bus_location} = join ':', map { sprintf("%lx", $i->{$_}) } qw(pci_bus pci_device pci_function);
        }
        # split description into manufacturer/description
        ($_->{Vendor}, $_->{description}) = split(/\|/, $_->{description}) if $_->{description};

        if ($_->{val}) { # Scanner ?
            my $val = $_->{val};
            ($_->{Vendor}, $_->{description}) = split(/\|/, $val->{DESCRIPTION});
        }
        # EIDE detection incoherency:
        if ($_->{bus} && $_->{bus} eq 'ide') {
            $_->{channel} = $_->{channel} ? N("secondary") : N("primary");
            delete $_->{info};
        } elsif ($_->{bus} && $_->{bus} !~ /USB|PCI/) {
            # SCSI detection incoherency:
            my $i = $_;
            $_->{bus_location} = join ':', map { sprintf("%lx", $i->{$_}) } qw(channel id lun);
        }
        harddrake::data::set_removable_configurator($Ident, $_, \$configurator);
        if ($Ident eq "AUDIO") {
            require harddrake::sound;
            my $alter = harddrake::sound::get_alternative($_->{driver});
            my $alternative_drivers = join(':', @$alter) if $alter->[0] ne 'unknown';
            $_->{alternative_drivers} = $alternative_drivers if $alternative_drivers;
        }
        rename_field($_, 'usb_description', 'description');
        rename_field($_, 'vendor_name', 'Vendor');
        rename_field($_, 'usb_driver', 'driver');
        rename_field($_, 'usb_media_type', 'media_type');
        foreach my $i (qw(MOUSETYPE XMOUSETYPE auxmouse devfs_prefix id pci_bus pci_device pci_function subid subvendor unsafe usb_bus usb_pci_bus usb_pci_device usb_vendor val vendor wacom)) { delete $_->{$i} };

        my $custom_id = harddrake::data::custom_id($_, $title);
        $custom_id .= ' ' while $data{$custom_id}; # get a unique id for eg bt8xx audio/video funtions
        foreach my $field (qw(devfs_device device)) {
            $_->{$field} = '/dev/'.$_->{$field} if $_->{$field};
        }
        $tree_model->append_set($parent_iter, [ 1 => $custom_id ])->free;
        $data{$custom_id} = $_;
        $configurators{$custom_id} = $configurator;
    }
    $tree->expand_row($tree_model->get_path($parent_iter), 1) unless $title eq N_("Unknown/Others");
    $parent_iter->free;
}

$SIG{CHLD} = sub { undef $pid; $statusbar->pop($sig_id) };
$w->{rwindow}->signal_connect(delete_event => \&quit_global);
$w->{rwindow}->set_position('center') unless $::isEmbedded;

foreach (keys %menu_options) {
    my $title = strip_first_underscore(@{$menu_options{$_}});
    $options{$_} = 0 unless defined($options{$_}); # force detection by default
    $check_boxes{$_} = $factory->get_widget("<main>" . $title);
    $check_boxes{$_}->set_active($options{$_});    # restore saved values
}

$textcolumn->set_min_width(350);
#$textcolumn->set_minmax_width(400);
$textcolumn->set_sizing('GTK_TREE_VIEW_COLUMN_AUTOSIZE');#GROW_ONLY
#$tree->columns_autosize();
$w->{rwindow}->show_all;
undef $wait;
gtkset_mousecursor_normal();
$_->hide foreach $module_cfg_button, $config_button; # hide buttons while no device
$SIG{CHLD} = 'IGNORE';
$w->main;


sub quit_global {
    kill(15, $pid) if $pid;
    setVarsInSh($conffile, \%options);
    ugtk2->exit(0);
}

sub show_hide {
    my ($bool, $button) = @_;
    if ($bool) { $button->show } else { $button->hide }
}


sub strip_first_underscore {
    join '', map { s/([^_]*)_(.*)/$1$2/; $_ } @_;
}

sub rename_field {
    my ($dev, $field, $new_field) = @_;
    if ($dev->{$field}) {
        if ($dev->{$new_field}) {
            $dev->{$new_field} .= " ($dev->{$field})";
        } else {
            $dev->{$new_field} = $dev->{$field};
        }
        delete $dev->{$field};
    }
}

sub popup_menu {
    my ($menu) = @_;
    sub { my (undef, $event) = @_;
          if ($event->type eq 'button-press') {
              $menu->popup(undef, undef, undef, undef, $event->button, $event->time);
              # Tell calling code that we have handled this event; the buck stops here.
              return 1;
          }
          # Tell calling code that we have not handled this event; pass it on.
          return 0;
      }
}