The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl
my $version = '0.15';

use 5.10.1;
use strictures 1;

use File::Path;
use File::Spec;

use Term::ReadKey qw/ReadMode/;

use Bot::Cobalt::Utils qw/ rplprintf mkpasswd /;
use Bot::Cobalt::Frontend::Utils qw/:all/;
use Bot::Cobalt::Frontend::RC qw/rc_read rc_write/;

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

use Getopt::Long;
GetOptions(

  version => sub {
    say "$0 $version";
    exit 0
   },

  help => sub {
    print(
      "$0 $version\n\n",
      "Options:\n",
      "  -c, --rcfile=PATH    Path to cobalt2 rcfile\n",
      "                       [default: $rcfile]\n",
    );
    exit 0
   },

  'rcfile=s' => \$rcfile,
  'config=s' => \$rcfile,
);

my $config_q = {

  info => [
    {
      ## For normal questions ask_question and set Var in hash
      Question => "Bot's nickname",
      Default  => "cobalt2",
      Var => "CFG_BOT_NICK",
    },
  
    {
      Question => "Bot's username",
      Default  => "cobalt",
      Var => "CFG_BOT_USERNAME",
    },
  
    {
      Question => "Bot's 'realname'",
      Default  => "Bot::Cobalt",
      Var => "CFG_BOT_REALNAME",
    },
    
    {
      Question => "Command character for this bot",
      Default  => '!',
      Var      => "CFG_CMD_CHAR",
    },
  ],
  
  server => [
    {
      Question => "Remote server address",
      Default  => "irc.cobaltirc.org",
      Var => "CFG_SERVER_ADDR",
    },
    
    {
      ## For YesNo => use ask_yesno and default to YesNo value
      Question => "Use SSL for this server? (Requires POE::Component::SSLify)",
      YesNo   => 'n',
      Var => "CFG_USE_SSL",
    },
    
    {
      Question => "Remote IRC port",
      Default  => 6667,
      Var => "CFG_SERVER_PORT",
    },
  ],
  
  channel => [
    {
      Question => "Channel name",
      Default  => '#eris',
      Var => "CHAN",
    },
  ],
  
  auth => [
    {
      Question => "Username for your superuser",
      Default  => "MyUser",
      Var => "AUTH_USER",
    },
    
    {
      Question => "Password",
      Hide  => 1,
      Crypt => 1,
      Var  => "AUTH_PASS",
    },
    
    {
      Question => "Hostmask",
      Default  => '*nobody@example.org',
      Var => "AUTH_MASK",
    },
  
  ],  

};

sub ask_from_ref {
  my ($hash_r) = @_;
  
  ## my ($var, $value) = ask_from_ref($itemref);
  
  unless ($hash_r->{Question} && $hash_r->{Var}) {
    die "Missing Question or Var"
  }
  
  my $type = $hash_r->{YesNo} ? 'yesno' : 'question' ;

  my $var = $hash_r->{Var};
  my $q   = $hash_r->{Question};
  
  my $value;
  
  if ($type eq 'yesno') {
    $value = ask_yesno(
      prompt  => $q,
      default => $hash_r->{YesNo},
    );
  } else {
    ReadMode('noecho') if $hash_r->{Hide};
    $value = ask_question(
      prompt  => $q,
      default => $hash_r->{Default},
    );
    if ($hash_r->{Hide}) {
      ReadMode(0);
      print "\n";
    }
    
    if ($hash_r->{Crypt}) {
      $value = mkpasswd($value);
    }
  } 
  
  return($var, $value)
}

sub _deserialize_etc {
  ## Pulls our /etc from our DATA handle
  my $cfs;
  { local $/ ; $cfs = <DATA>;  }

  my $ref;
  { local $@;
    $ref = eval $cfs;
    die "Could not reconstitute example confs" if $@;
  }

  return ref $ref eq 'HASH' ? $ref 
    : die "Reconstituted example confs not a HASH";
}

sub _slurp {
  my ($path) = @_;
  ## Slurp a file
  my $str;
  open(my $fh, '<', $path) or die "failed file read: $path: $!" ;
  {  local $/ ; $str = <$fh>;  }
  close($fh) or warn "failed to close $path: $!\n";
  return $str
}

## Conf_* funcs

sub Conf_write_confs {
  ## Conf_write_confs($vars_replacement_hash)
  my ($vars_h) = @_;
  my ($base, $etc, $var) = rc_read($rcfile);
  File::Path::mkpath( $var ."/db" );
  File::Path::mkpath( $var ."/tmp" );
  my $ref = _deserialize_etc();
  STDOUT->autoflush(1);
  ## use File::Path to create our dir structure
  ## write our deserialized confs back out
  ## run rplprintf against each file before writing
  for my $confpath (keys %$ref) {

    my $output_path = $etc ."/". $confpath ;
    (undef, my $dir, my $file) = File::Spec->splitpath($output_path);
    File::Path::mkpath($dir) if $dir;
    die "tried to create $dir but can't be found?" unless -e $dir;

    my $content = $ref->{$confpath};
    my $output = rplprintf( $content, $vars_h );

    print ">! Writing $output_path .. ";

    if (-e $output_path) {
      say ">! Exists already: $output_path";
      
      my $yesno = ask_yesno(
        prompt  => "Overwrite this file?",
        default => 'n',
      );
      if ($yesno) {
        say ">! Overwriting $output_path";
      } else { say "Skipped $output_path"; next }
    }

    open(my $fh, '>', $output_path) or die "open failed: $output_path: $!";
    print $fh $output;
    close($fh) or warn "close failed on $output_path: $!\n";
    chmod(0600, $output_path) if $output_path =~ /auth\.conf$/;
    print "OK\n";
  }

  say ">! Finished writing confs.";
}


sub Conf_interactive {
  my $cf = {};

  print(
    "Interactively configuring basic cobalt2 opts.\n",
    "This installer only covers the most basic set of options.\n",
    "You should review etc/ yourself when finished.\n\n",
    "Defaults are shown [like this]\n\n",
  );

  say ">! Configuring cobalt.conf";

  for my $itemref (@{ $config_q->{info} }) {
    my ($var, $value) = ask_from_ref( $itemref );
    $cf->{$var} = $value;
  }

  for my $itemref (@{ $config_q->{server} }) {
    my ($var, $value) = ask_from_ref($itemref);
    $cf->{$var} = $value;
  }

  print "\n";

  say ">! Done configuring cobalt.conf" ;
  say ">! There are many more opts; review the file!";

  print "\n";

  # plugins.conf is probably fine with a suitable example file
  
  say ">! Configuring channels.conf\n";
  say "You're going to want to set up an initial channel.";

  for my $itemref (@{ $config_q->{channel} }) {
    my ($var, $value) = ask_from_ref($itemref);
    $cf->{$var} = $value;
  }

  print "\n";

  say "You may want to edit channels.conf and add other channels.\n";

  say ">! Configuring auth.conf\n";

  say "You'll want at least one SuperUser to control the bot.";
  say "Authorized users have a username that may or may not be their IRC nick.";

  for my $itemref (@{ $config_q->{auth} }) {
    my ($var, $value) = ask_from_ref($itemref);
    $cf->{$var} = $value;
  }

  print "\n";

  return $cf
}


## MAIN
print(
  "This is the cobalt2 install helper.\n",
  "This script will create a cobalt2rc file specifying install paths.\n",
  "It will also help you initialize some starter cobalt2 conf files.\n\n",
  "> Press ENTER to continue, Ctrl+C to quit.",
);
<STDIN>;

say "> Default rcfile: $rcfile";

## prompt user for a rcfile path
my $enter_diff_yn = ask_yesno(
  prompt  => "Enter a different rcfile location?",
  default => 'n',
);
if ( $enter_diff_yn ) {
  $rcfile = ask_question(
    prompt => "Path to rcfile",
  );
}

say ">! using rcfile: $rcfile";

## prompt user for a relative basedir
say "> Cobalt needs a place for etc/ and var/ to live.";
say "> Normally this would be somewhere in your HOME directory.";
say "> Specify a directory relative to your HOME for cobalt to live:";
my $relative_basedir = ask_question(
  prompt  => "Base directory",
  default => "cobalt2",
);

my $real_basedir;   ## Reported by rc_write
if (-e $rcfile) {
  say ">! $rcfile seems to already exist.";
  my $overwrite_yn = ask_yesno(
    prompt => "Overwrite existing rcfile?",
    default => 'n',    
  );
  if ( $overwrite_yn ) {
    $real_basedir = rc_write($rcfile, $relative_basedir)
      or die "Failed rc_write";
  } else {
    say ">! Skipping rcfile write; attempting to use existing.";
  }
} else {
  $real_basedir = rc_write($rcfile, $relative_basedir)
    or die "Failed rc_write";
}
say ">! Wrote rcfile; base path is $real_basedir";
say ">! To change, edit $rcfile\n";

my $rplvars = Conf_interactive();

Conf_write_confs($rplvars);

say ">! Finished.";

=pod

=head1 NAME

cobalt2-installer - install a cobalt2 instance

=head1 SYNOPSIS

B<cobalt2-installer> [ --rcfile=PATH ]

=head1 DESCRIPTION

A simple walk-through installer for fresh L<Bot::Cobalt> instances.

Asks a few questions & copies initial confs and langsets to a specified 
directory.

Typically you would run this installer before starting Cobalt:

  ## Initiate a new cobalt2 instance
  ## Each instance has its own rcfile
  $ cobalt2-installer
  $ cobalt2

You can run multiple instances of Cobalt, but they each need their own 
I<etc> and I<var> directories, specified in their own rcfile:

  ## Initiate another cobalt2 instance
  $ cobalt2-installer --rcfile=${HOME}/cobalts/MyCobalt.rc
  $ cobalt2 --rcfile=${HOME}/cobalts/MyCobalt.rc

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

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

=cut
__DATA__