The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl
## simplistic cobalt2 Bot::Cobalt::Core frontend

use 5.10.1;
use strict;
use warnings;

use File::Spec;

## Default rcfile location.
my $rcfile = $ENV{HOME} ?
  File::Spec->catfile( $ENV{HOME}, ".cobalt2rc" )
  : ".cobalt2rc" ;

my $opt_debug  = 0;
my $opt_detach = 1;
my $loglevel   = 'info';
my ($etcdir, $vardir, $basedir);

# FIXME switch to Daemon::Control? drops Proc::PID::File
# and all the custom double-forkness
use Proc::PID::File;

use Bot::Cobalt::Frontend::RC qw/rc_read/;

use Getopt::Long;
GetOptions(
  ## Path to .cobalt2rc
  'rcfile=s' => \$rcfile,
  'config=s' => \$rcfile,

  ## Override .cobalt2rc
  'rundir=s' => \$basedir,
  'base=s'   => \$basedir,

  ## Invocation opts
  'debug:+' => \$opt_debug,   ## Overrides loglevel=
  'detach!' => \$opt_detach,
  'daemon!' => \$opt_detach,
  'loglevel=s' => \$loglevel,

  ## Informational
  version => sub {
    require Bot::Cobalt;
    my $vers = $Bot::Cobalt::VERSION // 'vcs';
    print "cobalt $vers\n";
    exit 0
  },

  help => \&show_help,
);

sub show_help {
  print(
    "cobalt2 invocation help \n",
    "   --version \n",
    "     Display current Bot::Cobalt::Core version \n",

    "\n",
    "  Execution:\n",
    "   --nodetach / --nodaemon \n",
    "     Run in the foreground (do not daemonize) \n",
    "   --loglevel=LOGLEVEL \n",
    "     Specify log verbosity. Defaults to 'info' \n",
    "     Valid levels, most verbose to least: \n",
    "       debug info notice warn err crit alert emerg \n",
    "   --debug / --nodebug / --debug=LEVEL\n",
    "     Enable debug output. Overrides loglevel. \n",
    "     Higher levels offer more verbosity. \n",

    ## FIXME; note on POCOIRC_DEBUG and POE debug opts ?

    "\n",
    "  Paths:\n",
    "   --rcfile=/path/to/rcfile \n",
    "     Specify a rcfile. Defaults to \$HOME/.cobalt2rc \n",
    "   --base=/path/to/basedir \n",
    "     Specify base path for 'etc/' and 'var/' for this instance\n",
    "     Overrides rcfile. \n",
  );

  exit 0
}

sub _rc_check {
  unless (-e $rcfile) {
    say ">! rcfile $rcfile not found.";
    say ">! You can specify one via --rcfile=";
    say ">! If this is your first time running cobalt2, try `cobalt2-installer`";
    die "rcfile not found"
  } else {
    return 1
  }
}

sub _check_dirs {
  my %paths = (
    etcdir => $etcdir,
    vardir => $vardir,
  );
  for my $thisname (keys %paths) {
    my $thispath = $paths{$thisname};
    unless (-e $thispath) {
      say ">! $thisname ($thispath) doesn't appear to exist.";
      say ">! Your rcfile ($rcfile) may be broken.";
      say ">! Perhaps try `cobalt2-installer`";
      die "$thisname not a directory"
    }
  }
}

sub _check_cfs {
  ## Check if required confs exist in etcdir
  ## Otherwise suggest cobalt2-installer

  my @required = qw/ cobalt.conf channels.conf plugins.conf /;

  for my $file (@required) {
    unless (-e File::Spec->catfile($etcdir, $file) ) {
      say ">! Missing core conf: $file";
      say ">! (etcdir: $etcdir)";
      say ">! You may want to try `cobalt2-installer`";
      die "missing core conf: $file"
    }
  }
}


sub _start_cobalt {
  my $pid = Proc::PID::File->new(
    dir => $vardir,
    name => 'cobalt',
  );
  die "cobalt appears to be already running\n"
    if $pid->alive;

  ## POSIX fork dance
  use POSIX ();
  if ($opt_detach)
  {
    say "Starting cobalt in background";
    my $fork = fork;
    exit 1 if not defined $fork;
    exit 0 if $fork;
    POSIX::setsid();
    $fork = fork;
    exit 1 if not defined $fork;
    exit 0 if $fork;
    chdir('/');
    open(STDIN, '<', '/dev/null');
    open(STDOUT, '>>', '/dev/null');
    open(STDERR, '>>', '/dev/null');
    umask(022);
  }
  $pid->touch();

  require Bot::Cobalt::Conf;
  require Bot::Cobalt::Core;

  ## FIXME could take paths to specific confs now
  my $cfg = Bot::Cobalt::Conf->new(
    etc   => $etcdir,
    debug => $opt_debug,
  );

  Bot::Cobalt::Core->instance(
    cfg => $cfg,
    var => $vardir,
    loglevel => $loglevel,
    debug    => $opt_debug,
    detached => $opt_detach,
  )->init;

  POE::Kernel->run;
}


say "-> debug ON, overrides loglevel" if $opt_debug;
## Bot::Cobalt::Core does this anyway, but just for the validator:
$loglevel = $opt_debug ? 'debug' : lc $loglevel ;
## Check specified loglevel
my @loglevels = qw/debug info notice warn warning
                   err error crit critical alert
                   emerg emergency/;

unless ($loglevel && grep { $_ eq $loglevel } @loglevels) {
  say("Invalid loglevel ($loglevel)");
  say("Possible loglevels, most verbose to least: ".join(' ',@loglevels));
  say("Setting loglevel to INFO");

  $loglevel = 'info';
}


if ($basedir) {
  say ">! Using --basedir=${basedir}";
  ## A basedir was specified, disregard cobalt2rc
  unless (-e $basedir) {
    die "basedir $basedir specified but nonexistant";
  }

  $etcdir = File::Spec->catdir($basedir, "etc");
  $vardir = File::Spec->catdir($basedir, "var");

} else {
  ## no basedir specified, try rcfile
  _rc_check();
  ($basedir, $etcdir, $vardir) = rc_read($rcfile);
}


_check_dirs();
_check_cfs();
_start_cobalt();

__END__
=pod

=head1 NAME

cobalt2 - Bot::Cobalt IRC bot frontend

=head1 SYNOPSIS

  # Start cobalt2 in the background
  # Grab etc/var paths from ~/.cobalt2rc
  cobalt2

  # Start but do not detach
  # '--nodaemon' is also valid
  cobalt2 --nodetach

  # Start in the foreground in debug mode
  cobalt2 --debug --nodetach

  # Higher debug verbosity modes:
  cobalt2 --debug=2

  # Start cobalt2 using a specified rcfile
  cobalt2 --rcfile=/path/to/cobalt2rc

  # Start cobalt2, only log warnings and above
  cobalt2 --loglevel=warn

=head1 DESCRIPTION

L<Bot::Cobalt> is a pluggable IRC bot framework coupled with a core set
of plugins vaguely replicating classic B<darkbot> and B<cobalt1>
behavior (and a great deal more).

This is the core frontend; it uses C<.cobalt2rc> files to find the 
relevant configuration & data directories to start a particular 
Cobalt instance.

See C<cobalt2-installer> for more on getting started. The installer can 
set up directories and example configuration files for a fresh instance.

=head1 SEE ALSO

L<Bot::Cobalt>

L<Bot::Cobalt::Manual::Plugins>

L<Bot::Cobalt::Core>

L<Bot::Cobalt::IRC>

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

L<http://www.cobaltirc.org>

=cut