#!/usr/bin/env perl
use Mojo::Base -strict;

use if -e 'lib/Mojo/Run3.pm', qw(lib lib);
use IO::Handle;
use Mojo::IOLoop::Stream;
use Mojo::Run3;
use Mojo::Run3::Util qw(stty_set);
use Mojo::Util       qw(getopt);
use Term::ReadKey    ();

my ($password, @winsize);
die "Usage: $0 -e -- ssh example.com\n" unless @ARGV;
getopt('f=s' => \&read_password_from_file, 'e' => \&read_password_from_env);
abort(q(Can't start without valid -e or -f <file>)) unless $password;
main();

sub abort { warn "sshpass: $_[0]\n"; exit($! || 1) }

sub main {
  @winsize = -t STDIN ? Term::ReadKey::GetTerminalSize(\*STDIN) : (80, 24, 0, 0);
  @winsize = map { $winsize[$_] } (1, 0, 2, 3);                                     # row, col, xpixel, ypixel

  my $stdin = -t STDIN ? 'pty' : 'pipe';
  my $run3  = Mojo::Run3->new(driver => {close_slave => 0, pipe => 1, pty => 'pty', stdin => $stdin});
  $run3->once(spawn  => \&setup_stdin);
  $run3->once(stdout => \&logged_in);
  $run3->on(error  => sub { abort($_[1]) });
  $run3->on(pty    => \&maybe_write_ssh_password);
  $run3->on(stderr => sub { STDERR->binmode; STDERR->syswrite($_[1]) });
  $run3->on(stdout => sub { STDOUT->binmode; STDOUT->syswrite($_[1]) });
  $run3->run_p(\&start_child)->catch(sub { abort(shift) })->wait;
  exit $run3->exit_status;
}

sub logged_in {
  my ($run3) = @_;
  $run3->unsubscribe(pty => \&maybe_write_ssh_password);
  $run3->close('slave');
  Term::ReadKey::ReadMode(4) if -t STDIN;
}

sub maybe_write_ssh_password {
  my ($run3, $chunk) = @_;

  if ($chunk =~ /password\s*:/i) {
    state $seen = 0;
    abort("Can't retry same password") if $seen++;
    $run3->write("$password\n", "pty", sub { shift->close('slave') });
  }
  elsif ($chunk =~ /Enter passphrase for key/i) {
    state $seen = 0;
    abort("Can't retry same passphrase") if $seen++;
    $run3->write("$password\n", "pty", sub { shift->close('slave') });
  }
}

sub read_password_from_env {
  return if $password = delete $ENV{SSHPASS};
  abort('-e option given but SSHPASS environment variable not set');
}

sub read_password_from_file {
  my ($name, $file) = @_;
  abort("Can't read $file: $!") unless open my $PW, '<', $file;
  $password = readline $PW;
  chomp $password;
  abort("Can't read password from $file") unless $password;
}

sub setup_stdin {
  my $run3  = shift;
  my $STDIN = \*STDIN;
  my $stdin = Mojo::IOLoop::Stream->new($STDIN);

  $stdin->timeout(0);
  $stdin->on(error => sub { abort($_[1]) });
  $stdin->on(read  => sub { $run3->write($_[1]) });
  $stdin->on(close => sub { $run3->close('stdin') });
  $run3->ioloop->stream($stdin);
  $STDIN->binmode;
}

sub start_child {
  my ($run3) = @_;

  if (my $fh = $run3->handle('pty')) {
    stty_set $fh, qw(TCSANOW -ECHO);
    $fh->set_winsize(@winsize) if $fh;
  }

  exec @ARGV;
}

END { Term::ReadKey::ReadMode(0) }
