#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;
FindBin::again();
use Path::Class 'dir';

BEGIN {
  # stuff useful locations into @INC
  unshift @INC,
    dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
    dir($FindBin::RealBin, 'lib')->stringify;

  unshift @INC,
    split m/:/, ($ENV{NETDISCO_INC} || '');
}

use App::Netdisco;
use Dancer qw/:moose :script/;

BEGIN {
  warning sprintf "App::Netdisco %s backend", ($App::Netdisco::VERSION || 'HEAD');

  # this can take a few seconds - only do it once
  use Net::Domain 'hostfqdn';
  info 'resolving backend hostname...';
  setting('workers')->{'BACKEND'} ||= ($ENV{NETDISCO_BACKEND_NAME} || hostfqdn || 'fqdn-undefined');
}

use App::Netdisco::Util::MCE; # set $0 and parse maxworkers
use App::Netdisco::Backend::Queues 'worker_queues';
use NetAddr::IP::Lite ':lower'; # to quench AF_INET6 symbol errors
use Role::Tiny::With;

use MCE::Signal '-setpgrp';
use MCE::Flow Sereal => 1;
use MCE::Queue porder => $MCE::Queue::HIGHEST,
               type   => $MCE::Queue::FIFO;

# set temporary MCE files' location in home directory
my $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
my $tmp_dir = ($ENV{NETDISCO_TEMP} || dir($home, 'tmp'));
mkdir $tmp_dir if ! -d $tmp_dir;

# process-table text
prctl 'nd2: master';

# a local job queue for each pool of workers (the general one, then any
# reserved for particular actions - see workers.queues)
my @queues = worker_queues();
$_->{queue} = MCE::Queue->new for @queues;

my $max_workers = 0;
$max_workers += $_->{tasks} for @queues;

# support a scheduler-only node
setting('workers')->{'no_manager'} = 1
  if $max_workers == 0;

# a queue without workers (the general pool, on a node of reserved queues only)
# has no task, unless there are no workers at all and so this is the only one
my @pollers = grep { $_->{tasks} } @queues;
@pollers = ($queues[0]) if not @pollers;

my @task_names   = ( 'scheduler', 'manager', map { "poller_$_->{name}" } @pollers );
my @task_workers = ( 1, 1, map { $_->{tasks} } @pollers );
my @task_roles   = ( _mk_wkr('Scheduler'), _mk_wkr('Manager'),
                     map { _mk_wkr('Poller', $_->{queue}) } @pollers );

if (!defined setting('schedule')) {
  shift @task_names; shift @task_workers; shift @task_roles;
}

warning sprintf "mode: %s%s | host: %s",
  (!defined setting('schedule') ? 'worker-only'
   : $max_workers == 0          ? 'scheduler'
   :                              'scheduler+worker'),
  ($max_workers ? " | workers: $max_workers" : ''),
  (setting('workers')->{'BACKEND'} || 'unknown');

warning sprintf "queue: %s | workers: %s | actions: %s",
  $_->{name}, $_->{tasks}, join(', ', @{ $_->{actions} })
  for grep { $_->{only} } @queues;

mce_flow {
  task_name    => \@task_names,
  max_workers  => \@task_workers,
  tmp_dir      => $tmp_dir,
  on_post_exit => sub { MCE->restart_worker },
}, @task_roles;

sub _mk_wkr {
  my ($role, $queue) = @_;
  return sub {
    my $self = shift;
    $self->{queue}  = $queue;
    $self->{queues} = \@queues;

    prctl sprintf 'nd2: #%s %s: init', MCE->wid, lc($role);
    info sprintf 'applying role %s to worker %s', $role, MCE->wid;

    # post-fork, become manager, scheduler, poller, etc
    Role::Tiny->apply_roles_to_object(
      $self => "App::Netdisco::Backend::Role::$role");

    $self->worker_begin if $self->can('worker_begin');
    $self->worker_body;
  };
}

=head1 NAME

netdisco-backend-fg - Job Control for Netdisco

=head1 SEE ALSO

=over 4

=item *

L<App::Netdisco>

=back

=cut
