The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Tak::Script;

use Getopt::Long qw(GetOptionsFromArray :config posix_defaults bundling);
use Config::Settings;
use IO::Handle;
use Tak::Client::Router;
use Tak::Client::RemoteRouter;
use Tak::Router;
use Log::Contextual qw(:log);
use Log::Contextual::SimpleLogger;
use Moo;

with 'Tak::Role::ScriptActions';

has options => (is => 'ro', required => 1);
has env => (is => 'ro', required => 1);

has log_level => (is => 'rw');

has stdin => (is => 'lazy');
has stdout => (is => 'lazy');
has stderr => (is => 'lazy');

sub _build_stdin { shift->env->{stdin} }
sub _build_stdout { shift->env->{stdout} }
sub _build_stderr { shift->env->{stderr} }

has config => (is => 'lazy');

sub _build_config {
  my ($self) = @_;
  my $file = $self->options->{config} || '.tak/default.conf';
  if (-e $file) {
    Config::Settings->new->parse_file($file);
  } else {
    {};
  }
}

has local_client => (is => 'lazy');

sub _build_local_client {
  my ($self) = @_;
  Tak::Client::Router->new(service => Tak::Router->new);
}

sub BUILD {
  shift->setup_logger;
}

sub setup_logger {
  my ($self) = @_;
  my @level_names = qw(fatal error warn info debug trace);
  my $options = $self->options;
  my $level = 2 + ($options->{verbose}||0) - ($options->{quiet}||0);
  my $upto = $level_names[$level];
  $self->log_level($upto);
  Log::Contextual::set_logger(
    Log::Contextual::SimpleLogger->new({
      levels_upto => $upto,
      coderef => sub { print STDERR '<local> ', @_ },
    })
  );
}

sub _parse_options {
  my ($self, $string, $argv) = @_;
  my @spec = split ';', $string;
  my %opt;
  GetOptionsFromArray($argv, \%opt, @spec);
  return \%opt;
}

sub run {
  my ($self) = @_;
  my @argv = @{$self->env->{argv}};
  unless (@argv && $argv[0]) {
    return $self->local_help;
  }
  my $cmd = shift(@argv);
  $cmd =~ s/-/_/g;
  if (my $code = $self->can("local_$cmd")) {
    return $self->_run($cmd, $code, @argv);
  } elsif ($code = $self->can("each_$cmd")) {
    return $self->_run_each($cmd, $code, @argv);
  } elsif ($code = $self->can("every_$cmd")) {
    return $self->_run_every($cmd, $code, @argv);
  }
  $self->stderr->print("No such command: ${cmd}\n");
  return $self->local_help;
}

sub _load_file {
  my ($self, $file) = @_;
  $self->_load_file_in_my_script($file);
}

sub local_help {
  my ($self) = @_;
  $self->stderr->print("Help unimplemented\n");
}

sub _maybe_parse_options {
  my ($self, $code, $argv) = @_;
  if (my $proto = prototype($code)) {
    $self->_parse_options($proto, $argv);
  } else {
    {};
  }
}

sub _run_local {
  my ($self, $cmd, $code, @argv) = @_;
  my $opt = $self->_maybe_parse_options($code, \@argv);
  $self->$code($opt, @argv);
}

sub _run_each {
  my ($self, $cmd, $code, @argv) = @_;
  my @targets = $self->_host_list_for($cmd);
  unless (@targets) {
    $self->stderr->print("No targets for ${cmd}\n");
    return;
  }
  my $opt = $self->_maybe_parse_options($code, \@argv);
  $self->local_client->ensure(connector => 'Tak::ConnectorService');
  foreach my $target (@targets) {
    my $remote = $self->_connection_to($target);
    $self->$code($remote, $opt, @argv);
  }
}

sub _run_every {
  my ($self, $cmd, $code, @argv) = @_;
  my @targets = $self->_host_list_for($cmd);
  unless (@targets) {
    $self->stderr->print("No targets for ${cmd}\n");
    return;
  }
  my $opt = $self->_maybe_parse_options($code, \@argv);
  $self->local_client->ensure(connector => 'Tak::ConnectorService');
  my @remotes = map $self->_connection_to($_), @targets;
  $self->$code(\@remotes, $opt, @argv);
}

sub _host_list_for {
  my ($self, $command) = @_;
  my @host_spec = map split(' ', $_), @{$self->options->{host}};
  unshift(@host_spec, '-') if $self->options->{local};
  return @host_spec;
}

sub _connection_to {
  my ($self, $target) = @_;
  log_debug { "Connecting to ${target}" };
  my @path = $self->local_client->do(
    connector => create => $target, log_level => $self->log_level
  );
  my ($local, $remote) =
    map $self->local_client->curry(connector => connection => @path => $_),
      qw(local remote);
  $local->ensure(module_sender => 'Tak::ModuleSender');
  $remote->ensure(
    module_loader => 'Tak::ModuleLoader',
    expose => { module_sender => [ 'remote', 'module_sender' ] }
  );
  $remote->do(module_loader => 'enable');
  log_debug { "Setup connection to ${target}" };
  Tak::Client::RemoteRouter->new(
    %$remote, host => $target
  );
}

1;