#!/usr/bin/perl

# (c) 1999, 2000 Randolph Chung <tausq@debian.org>

use strict;
use Debian::xviddetect::UI;

# State machine
# States, handler subroutine, previous and next states
my %state = (
  initial	=> {handler => \&SIntro, prevs=> 'initial', nexts => 'detect'},
  detect	=> {handler => \&SDetect, prevs=> 'initial', nexts => 'fonts'},
  fonts		=> {handler => \&SFonts, prevs=> 'detect', nexts => 'term'},
  term		=> {handler => \&STermEmulators, prevs=> 'fonts', nexts => 'winmgr'},
  winmgr	=> {handler => \&SWindowManagers, prevs=> 'term', nexts => 'xdm'},
  xdm		=> {handler => \&SXDM, prevs=> 'winmgr', nexts => 'xconfig'},
  xconfig	=> {handler => \&SXConfig, prevs=> 'xdm', nexts => 'mouse'},
  mouse		=> {handler => \&SMouse, prevs=> 'xconfig', nexts => 'keymap'},
  keymap	=> {handler => \&SKeymap, prevs=> 'mouse', nexts => 'hsync'},
  hsync		=> {handler => \&SHSync, prevs=> 'keymap', nexts => 'vsync'},
  vsync		=> {handler => \&SVSync, prevs=> 'hsync', nexts => 'monitorid'},
  monitorid	=> {handler => \&SMonitorID, prevs=> 'vsync', nexts => 'videomem'},
  videomem	=> {handler => \&SVideoMem, prevs=> 'monitorid', nexts => 'videoid'},
  videoid	=> {handler => \&SVideoID, prevs=> 'videomem', nexts => 'clockchip'},
  clockchip	=> {handler => \&SClockChip, prevs=> 'videoid', nexts => 'defaultdepth'},
  defaultdepth	=> {handler => \&SDefaultDepth, prevs=> 'clockchip', nexts => 'defaultres'},
  defaultres	=> {handler => \&SDefaultRes, prevs=> 'defaultdepth', nexts => 'otherres'},
  otherres	=> {handler => \&SOtherRes, prevs=> 'defaultres', nexts => 'writeconfig'},
  writeconfig	=> {handler => \&SWriteConfig, prevs=> 'otherres', nexts => 'exit'}
);
my $curstate = 'initial';

# Constants
my $GONEXT = -1;
my $GOBACK = -2;
my $ERROR = -3;
my $XF86CONFIGTEMPLATE = '/usr/share/xviddetect/XF86Config.template';
my $ANXIOUSTEMPLATE = '/usr/share/xviddetect/anXious.templates';
#my $XF86CONFIGTEMPLATE = './XF86Config.template';
my $XF86CONFIGOUT = '/etc/X11/XF86Config';

# Global variables
my $dodebug = $ENV{ANXIOUS_DEBUG} || 0;
my $skipsetup = 0;
my $skipconfig = 0;
my $forcesetup = 0;
my $forceconfig = 0;
my $quiet = 0;
my %info;
my $ui = new Debian::xviddetect::UI;

# Utility functions
sub FindProvides {
  my $providepkg = shift;
  
  my $packages=`apt-cache showpkg $providepkg | sed -e '1,/Reverse Provides:/d' | cut -d' ' -f1 | sort | uniq`;

  $packages =~ s/\n/, /msg;
  $packages =~ s/, $//;
  return $packages;
}

sub ShowHelp {
  print STDERR <<EOF;
anXious - a X Configuration tool (c) 2000 Randolph Chung; GPLv2

anXious [--skipsetup] [--skipconfig] [--forcesetup] [--forceconfig] [--dodebug]
        [--quiet]
anXious --help
  --skipsetup: skip the setup phase; do not try to install a server
  --skipconfig: skip the config phase; do not try to write a config file
  --forcesetup: override automatic detection of phases; force setup step
  --forceconfig: override automatic detection of phases; force config  step
  --quiet: runs anXious more quietly
  --dodebug: Turns on debugging output
  --help: shows this message

  anXious has a setup stage, where it helps you determine what packages to
  install, and a configuration stage, where it writes out an XF86Config file.
  This program will automatically guess which step is needed based on
  what is available on your system. You can force anXious to start from
  scratch by using the --forcesetup and --forceconfig options.

EOF
  exit 0;
}

sub DoDetect {
  my $driver=`xviddetect -q`;
  chomp($driver);
  return $driver;
}

sub WriteConfig {
  my $outfile = shift;

  # Check to see if we have all the info we need. if this is a --skipsetup
  # session, the driver and font info is not yet available, so we need to
  # fetch it from the DebConf db. TODO: figure out what to do if the values
  # are not there
  if (!$info{DRIVER}) {
    $info{DRIVER} = DoDetect;
  }

  if (($info{DRIVER} ne 'svga') &&
      ($info{DRIVER} ne 'mono') &&
      ($info{DRIVER} ne 'vga16') &&
      ($info{DRIVER} ne 'fbdev')) {
    $info{DRIVER} = 'accel';
  }
  
  # Build up the font path
  $info{FONTPATH} = "";
  foreach (split(/,\s*/, $info{XFONTS})) {
    s/xfonts-//;
    $info{FONTPATH} .= "\tFontPath \"/usr/X11R6/lib/X11/fonts/$_/\"\n";
  }

  # Build the screen definition info
  my (@res, $depth, %saw);
  push(@res, $info{DEFAULTRES});
  push(@res, split(/\s+/, $info{OTHERRES}));
  # remove duplicates (from perl faq)
  undef %saw;
  @saw{@res} = ();
  @res = keys %saw;
  foreach $depth (8, 16, 24, 32) {
    $info{DISPLAYDEFN} .= "\n\tSubsection \"Display\"\n";
    $info{DISPLAYDEFN} .= "\t    Depth\t$depth\n";
    $info{DISPLAYDEFN} .= "\t    Modes ".join(" ", @res)."\n";
    $info{DISPLAYDEFN} .= "\tEndSubsection\n";
  }
  
  mkdir "/etc/X11", 0755 if (! -d "/etc/X11");
  
  open (T, "<$XF86CONFIGTEMPLATE") || die ":$!";
  open (O, ">$outfile") || die "$outfile: $!";
  while (<T>) {
    if (/^###ANXIOUS:(.+)/) {
      $_ = $1;
      s/~(.+?)~/$info{$1}/g;
      $_ .= "\n";
    }
    print O;
  }
  close O;
  close T;
}

sub QueueInstall {
  my $package = shift;
  if ($dodebug) {
    print STDERR "Queueing $package for installation\n";
  } else {
    system("echo $package install | dpkg --set-selections");
  }
}

sub Cleanup {
  my $ret = shift;

  exit $ret;
}

# State handling
sub SIntro {
  my ($ret, @ret);
  if ($skipsetup && !$forcesetup) {
    @ret = $ui->input('xviddetect/redoconfig');
    return 'xconfig' if ($ret[1] ne 'true');
  }
  @ret = $ui->input('xviddetect/configure-xwin');
  
  return $GOBACK if ($ret[0] == 30);
  if ($ret[1] ne "true") {
    Cleanup(0);
  }
  return $GONEXT;
}

sub SDetect {
  my ($ret, @ret);
  my $driver=&DoDetect;
  if ($driver eq "unknown") {
    @ret = $ui->input('xviddetect/undetected-videocard');
    if ($ret[0] == 30) {
      return $GOBACK;
    } else {
      &Cleanup(0);
    }
  }

  $ui->subst('xviddetect/detected-videocard', 'server', $driver);
  $ui->input('xviddetect/detected-videocard');
  if ($ret[0] == 30) {
    $info{DRIVER}=undef;
    return $GOBACK;
  } else {
    $info{DRIVER}=$driver;
    QueueInstall("xserver-$driver");
    return $GONEXT;
  }
}

sub SFonts {
  my @ret;
  @ret = $ui->input('xviddetect/x-fonts');
  if ($ret[0] == 30) {
    $info{XFONTS}=undef;
    return $GOBACK;
  } else {
    $ret[1] = "$ret[1], xfonts-base" if ($ret[1]);
    $info{XFONTS} = $ret[1];
    return $GONEXT;
  }
}

sub STermEmulators {
  my @ret;
  $ui->subst ('xviddetect/x-terminal-emulator', 'x-terminal-emulators', &FindProvides("x-terminal-emulator"));
  @ret = $ui->input('xviddetect/x-terminal-emulator');
  if ($ret[0] == 30) {
    $info{INSTALL_XTERMS}=undef;
    return $GOBACK;
  } else {
    $info{INSTALL_XTERMS} = $ret[1];
    return $GONEXT;
  }
}

sub SWindowManagers {
  my @ret;
  $ui->subst ('xviddetect/x-window-manager', 'x-window-managers', &FindProvides("x-window-manager"));
  @ret = $ui->input('xviddetect/x-window-manager');
  if ($ret[0] == 30) {
    $info{INSTALL_WMS}=undef;
    return $GOBACK;
  } else {
    $info{INSTALL_WMS} = $ret[1];
    return $GONEXT;
  }
}

sub SXDM {
  my @ret;
  @ret = $ui->input('xviddetect/xdm');
  if ($ret[0] == 30) {
    $info{INSTALL_XDM}=undef;
    return $GOBACK;
  } else {
    if ($ret[1] eq 'false') {
      $info{INSTALL_XDM}=undef;
    } else {
      $info{INSTALL_XDM}="xdm";
    }
    return $GONEXT;
  }
}

sub SXConfig {
  my @ret;
  if ($skipconfig && !$forceconfig) {
    @ret = $ui->input('xviddetect/redoconfig');
    return 'exit' if ($ret[1] ne 'true');
  }
  @ret = $ui->input('medium', 'xviddetect/doxconfig');
  return $GOBACK if ($ret[0] == 30);
  return 'exit' if ($ret[1] eq 'false');
  $ui->input('medium', 'xviddetect/xcfginfoneeded');
  return $GONEXT;
}

sub SMouse {
  my ($ret, @ret);
  my %mapping = (
    '1: PS/2' => 'ps/2',
    '2: Microsoft' => 'microsoft',
    '3: MouseSystems' => 'mousesystems',
    '4: Logitech Intellimouse or GPM Repeater' => 'intellimouse',
    'Other' => '<<EDITME: OTHER>>'
  );
  $ui->subst('xviddetect/xcfgmouse', 'choices', join(',', sort keys(%mapping)));
  @ret = $ui->input('xviddetect/xcfgmouse');
  return $GOBACK if ($ret[0] == 30);
  
  $info{MOUSEPROTOCOL} = $mapping{$ret[1]};


  @ret = $ui->input('xviddetect/xcfgemulate3buttons');
  return 'mouse' if ($ret[0] == 30);
  if ($ret[1] eq 'true') {
    $info{MOUSEEMULATE} = "Emulate3Buttons";
  } else {
    $info{MOUSEEMULATE} = "#Emulate3Buttons";
  }

  @ret = $ui->input('xviddetect/xcfgmousedevice');
  return 'mouse' if ($ret[0] == 30);
  $info{MOUSEDEVICE} = $ret[1] || "/dev/mouse";
  
  return $GONEXT;
}

sub SKeymap {
  my @ret;
  # TODO: add more keymaps!
  my %mapping = (
    '01. US/Standard' => 'xfree86(en_US)',
    '02. US/MS_Natural' => 'xfree86(us_microsoft)',
    '03. US/Keytronic' => 'xfree86(us_flexpro)',
    '04. ISO9995-3' => '',
    '05. German' => 'xfree86(de)',
    '06. French' => 'xfree86(fr)',
    '07. Thai' => 'xfree86(th)',
    '08. Swiss/German' => 'xfree86(de_CH)',
    '09. Swiss/French' => 'xfree86(fr_CH)',
    '10. US/International' => 'xfree86(us)',
    '11. Brazilian' => 'xfree86(br)',
    'disable' => undef,
    'none' => undef
  );

  $ui->subst('xviddetect/xcfgkeymap', 'keymaps', join(',', sort keys(%mapping)));
  @ret = $ui->input('xviddetect/xcfgkeymap');
  return $GOBACK if ($ret[0] == 30);
  if ($mapping{$ret[1]}) {
    $info{XKBDISABLE} = undef;
    $info{KEYMAP} = "XkbKeymap \"$mapping{$ret[1]}\"";
  } else {
    if ($ret[1] eq "disable") {
      $info{XKBDISABLE} = "XkbDisable";
      $info{KEYMAP} = undef;
    } else {
      $info{KEYMAP} = "XkbKeymap none";
    }
  }
  return $GONEXT;
}

sub SHSync {
  my @ret;
  my %mapping = (
    '01: Standard VGA 640x480 60Hz' => '31.5',
    '02: Super VGA 800x600 56Hz' => '31.5-35.1',
    '03: 8514 compatible 1024x768 87Hz interlaced no 800x600' => '31.5, 35.5',
    '04: Super VGA 1024x768 87Hz interlaced 800x600 56Hz' => '31.5, 35.15, 35.5',
    '05: 800x600 60Hz 640x480 72Hz' => '31.5-37.9',
    '06: 1024x760 60Hz 800x600 72Hz' => '31.5-48.5',
    '07: 1024x768 70Hz' => '31.5-57.0',
    '08: 1280x1024 60Hz' => '31.5-64.3',
    '09: 1280x1024 74Hz' => '31.5-79.0',
    '10: 1280x1024 76Hz' => '31.5-82.0',
    'custom' => undef
  );
  $ui->subst('xviddetect/xcfghsync', 'choices', join(',', sort keys(%mapping)));
  @ret = $ui->input('xviddetect/xcfghsync');
  return $GOBACK if ($ret[0] == 30);
  my $ret = $mapping{$ret[1]};
  if (!$ret) {
    $ret = $ui->input('xviddetect/xcfghsyncaux');
  }
  $info{HSYNC} = $ret;
  return $GONEXT;
}

sub SVSync {
  my ($ret, @ret);
  my %mapping = (
    '1: 50-70' => '50-70',
    '2: 50-90' => '50-90',
    '3: 50-100' => '50-100',
    '4: 40-150' => '40-150',
    'custom' => undef
  );
  $ui->subst('xviddetect/xcfgvsync', 'choices', join(',', sort keys(%mapping)));
  @ret = $ui->input('xviddetect/xcfgvsync');
  return $GOBACK if ($ret[0] == 30);
  $ret = $mapping{$ret[1]};
  while (!$ret) {
    $ret = $ui->input('xviddetect/xcfgvsyncaux');
  }
  $info{VSYNC} = $ret;
  return $GONEXT;
}

sub SMonitorID {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgmonitorid');
  return $GOBACK if ($ret[0] == 30);
  $info{MONITORID} = $ret[1];
  return $GONEXT;
}

sub SVideoMem {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgvideomem');
  return $GOBACK if ($ret[0] == 30);
  if ($ret[1] eq "other") {
    $ret[1] = undef;
    while (!$ret[1]) {
      @ret = input('xviddetect/xcfgvideomemaux');
    }
  }
  $ret[1] =~ s/[^0-9]//g;
  
  $info{VIDEORAM} = $ret[1];
  return $GONEXT;
}

sub SVideoID {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgvideoid');
  return $GOBACK if ($ret[0] == 30);
  $info{VIDEOID} = $ret[1];
  return $GONEXT;
}

sub SClockChip {
  my ($ret, @ret);
  my %mapping = (
    '00. none' => undef,
    '01. Chrontel 8391' => 'ch8391',
    '02. ICD2061A' => 'icd2061a',
    '03. ICS2595' => 'ics2595',
    '04. ICS5342' => 'ics5342',
    '05. ICS5341' => 'ics5341',
    '06. S3 GenDac 86C708 or ICS5300' => 's3gendac',
    '07. S3 SDAC 86C716' => 's3_sdac',
    '08. STD 1703' => 'stg1703',
    '09. Sierra SC11412' => 'sc11412',
    '10. TI 3025' => 'ti3025',
    '11. TI 3026' => 'ti3026',
    '12. IBM RGB 51x/52x' => 'ibm_rgb5xx'
  );

  $ui->subst('xviddetect/xcfgclockchip', 'choices', join(',', sort keys(%mapping)));
  @ret = $ui->input('xviddetect/xcfgclockchip');
  return $GOBACK if ($ret[0] == 30);
  $ret = $mapping{$ret[1]};
  $info{CLOCKCHIP} = "ClockChip \"$ret\"" if ($ret);

  if (!$ret) {
    @ret = $ui->input('xviddetect/xcfgclocks');
    return 'clockchip' if ($ret[0] == 30);
    if ($ret[1] eq "true") {
      my $clocks = `X -probeonly 2>&1 | grep Clock`;
      if ($clocks) {
        # TODO: verify this is right, I'm just guessing here
        $clocks =~ s/.*(Clock*)/$1/;
	$info{CLOCKS} = $clocks;
      } else {
        $ui->input('xviddetect/xcfgclocksnone');
      }
    }
  }
  
  return $GONEXT;
}

sub SDefaultDepth {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgdefaultdepth');
  return $GOBACK if ($ret[0] == 30);

  $ret[1] =~ s/bpp$//;
  $info{DEFAULTCOLORDEPTH} = $ret[1];
  return $GONEXT;
}

sub SDefaultRes {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgdefaultres');
  return $GOBACK if ($ret[0] == 30);
  $info{DEFAULTRES} = "\"$ret[1]\"";
  return $GONEXT;
}

sub SOtherRes {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgotherres');
  return $GOBACK if ($ret[0] == 30);
  foreach (split(/[,\s]+/, $ret[1])) {
    $info{OTHERRES} = "$info{OTHERRES} \"$_\""
  }
  return $GONEXT;
}

sub SWriteConfig {
  my @ret;
  @ret = $ui->input('xviddetect/xcfgwritecfg');
  return $GOBACK if ($ret[0] == 30);
  if (-e $ret[1]) {
    $ui->subst('xviddetect/xcfgoverwritecfg', 'cfglocation', $ret[1]);
    my @ret2 = $ui->input('xviddetect/xcfgoverwritecfg');
    if ($ret2[1] ne 'true') {
      return "writeconfig";
    }
  }
  WriteConfig($ret[1]);
  $ui->subst('xviddetect/xcfgdone', 'cfglocation', $ret[1]);
  $ui->input('xviddetect/xcfgdone');
  return $GONEXT;
}

### MAIN ###
foreach (@ARGV,split(/\s+/, $ENV{ANXIOUS_PARAMS})) {
  if ($_ eq "--debug") {
    $dodebug = 1;
  } elsif ($_ eq "--skipsetup") {
    $skipsetup = 1;
  } elsif ($_ eq "--skipconfig") {
    $skipconfig = 1;
  } elsif ($_ eq "--forcesetup") {
    $forcesetup = 1;
  } elsif ($_ eq "--forceconfig") {
    $forceconfig = 1;
  } elsif ($_ eq "--quiet") {
    $quiet = 1;
  } elsif ($_ eq "--help") {
    ShowHelp;
  }
}

$skipsetup = 1 if (defined(glob("/usr/X11R6/bin/XF86_*")));
$skipconfig = 1 if (-e "/etc/X11/XF86Config");

if ($skipsetup && $skipconfig && !($forcesetup || $forceconfig)) {
  exit 0;
}

print STDERR "Running in DEBUG mode\n" if ($dodebug);
$Debian::xviddetect::UI::debug = $dodebug;

$ui->title('anXious');
$ui->ReadTemplate($ANXIOUSTEMPLATE);

# This is our state/event loop

while ($curstate ne 'exit') {
  my $handler = $state{$curstate}{handler};
  if (!defined($handler)) {
    print STDERR "E: Reached an unknown state: [$curstate]\n";
    exit 0;
  }
  my $result = &$handler;
  print STDERR "Handler returned $result\n" if ($dodebug);
  if ($result == $ERROR) {
    print STDERR "E: state handler returned an error condition\n";
    Cleanup(1);
  } elsif ($result == $GOBACK) {
    $curstate = $state{$curstate}{prevs};
  } elsif ($result == $GONEXT) {
    $curstate = $state{$curstate}{nexts};
  } else {
    $curstate = $result;
  }
}

system("reset; clear") if ($dodebug);

foreach (split /[,\s]+/, "$info{INSTALL_XTERMS} $info{INSTALL_WMS} $info{INSTALL_XDM} $info{XFONTS}") {
  QueueInstall $_;
}

if ($dodebug) {
  print STDERR "Contents of info hash:\n";
  foreach (keys(%info)) {
    print STDERR sprintf("%-30s = %s\n", $_, $info{$_});
  }
}

if (!$quiet) {
  print STDERR <<_EOT_;
Any packages that need to be installed have been queued. You can install
them with dselect, or by running 
          apt-get dselect-upgrade
on the command line.
_EOT_
}
