#!perl -w

#
# (c) Jan Gehring <jan.gehring@gmail.com>
# 
# vim: set ts=3 sw=3 tw=0:
# vim: set expandtab:
   
use strict;
use warnings;

$|++;

use LWP::UserAgent;
use YAML;
use Data::Dumper;
use Rex::Config;
use Rex::Logger;
use Rex::Commands::Fs;
use Rex::Commands::File;
use JSON::XS;
use Cwd qw(getcwd);

$Rex::Logger::silent = 1;

my $opts = {}; 

######
# default server
my $SEARCH_SERVER = "http://modules.rexify.org/api/0.41/get/recipes";
my $RECIPE_SERVER = "http://modules.rexify.org/api/0.41/get/mod/%s";
my $DEPEND_SERVER = "http://modules.rexify.org/api/0.41/get/dep/%s";
my $PERL_DEPEND_SERVER = "http://modules.rexify.org/api/0.41/get/perldep/%s";

my $AUTH_USER;
my $AUTH_PASSWORD;
my $AUTH_REALM;

Rex::Config->register_config_handler("module_server", sub {
   my ($param) = @_;

   if(exists $param->{search}) {
      $SEARCH_SERVER = $param->{search};
   }

   if(exists $param->{recipes}) {
      $RECIPE_SERVER = $param->{recipes};
   }

   if(exists $param->{dependencies}) {
      $DEPEND_SERVER = $param->{dependencies};
   }

   if(exists $param->{perl_dependencies}) {
      $PERL_DEPEND_SERVER = $param->{perl_dependencies};
   }

   if(exists $param->{username}) {
      $AUTH_USER = $param->{username};
   }

   if(exists $param->{password}) {
      $AUTH_PASSWORD = $param->{password};
   }

   if(exists $param->{realm}) {
      $AUTH_REALM = $param->{realm};
   }

});

for(my $i=0; $i<@ARGV; $i++) {

   if($ARGV[$i] =~ m/^\-\-([a-z0-9\-_]+)=/) {
      my $key = $1;
      my ($c_key, $val) = split(/=/, $ARGV[$i]);

      if(exists $opts->{$key}) {
         $opts->{$key} = [ $opts->{$key} ] if (! ref $opts->{$key});
         push(@{$opts->{$key}}, $val);
      }
      else {
         $opts->{$key} = $val || 0;
      }
   }
   elsif($ARGV[$i] =~ m/^\-\-([a-z0-9\-_]+)/) {
      my $key = $1; 
      if(! $ARGV[$i+1] || $ARGV[$i+1] =~ m/^\-\-/) {
         $opts->{$key} = 1;                                                                                      
      }   
      else {
         if(exists $opts->{$key}) {
            $opts->{$key} = [ $opts->{$key} ] if (! ref $opts->{$key});

            push(@{$opts->{$key}}, $ARGV[++$i]);
         }   
         else {
            $opts->{$key} = $ARGV[++$i];
         }   
      }   
   }   

}

if( ! $ARGV[0] || join("", @ARGV) =~ m/\-h|\-\-help/) {
   print STDERR "Usage: rexify <project-name> [<directory>] [<options>]\n";
   print STDERR "\n";
   print STDERR "Options:";
   print STDERR "\n";
   print STDERR "\t--search=value\t\t\tWill search community recipes\n";
   print STDERR "\t--use=recipe\t\t\tWill download community recipe\n";
   print STDERR "\t--template=template\t\tUse a custom template to create the Rexfile skeleton\n";
   print STDERR "\t--create-module\t\t\tCreate a module skeleton.\n";
   print STDERR "\t--create-module=Mod::Name\tCreate a module skeleton inside a Rex project.\n";
   print STDERR "\t--sudo\t\t\t\tTo use sudo for Perl Module installation.\n";
   print STDERR "\t--no-install-perl-deps\t\tUse this if you don't want that rexify tries to install Perl Modules.\n";

   print STDERR "\n";
   print STDERR "Custom Templates:\n";
   print STDERR "   box - Template to use for Rex::Commands::Box projects.\n";
   exit 1;
}


sub print_found {
   my ($name, $data) = @_;

   $name =~ s/\//::/g;
   $name =~ s/\.pm$//;
   print "* $name\n";
   print "    Author     : " . $data->{Author} . "\n";
   print "    Requires   : " . join(", ", @{ $data->{Requires} }) . "\n" if($data->{Requires});
   print "    License    : " . $data->{License} . "\n" if($data->{License});
   print "    Description: " . $data->{Description} . "\n";
}

sub download_recipe {
   my ($name) = @_;

   if(! -f "Rexfile") {
      print STDERR "This is not a Rex project directory. There is no Rexfile.\n";
      exit 1;
   }

   if(! -d "lib") { mkdir "lib"; }

   print "Getting dependencies for $name...\n";
   my $deps = decode_json(get(sprintf($DEPEND_SERVER, $name)));
   if(scalar(@{ $deps }) > 0) {
      print "   Found: \n      - " . join("\n      - ", @{ $deps }) . "\n";
      for my $dep (@{ $deps }) {
         download_recipe($dep);
      }
   }
   else {
      print "   None found.\n";
   }

   if( ! exists $opts->{"no-install-perl-deps"}) {
      print "Getting perl dependencies for $name...\n";
      my $perl_deps = decode_json(get(sprintf($PERL_DEPEND_SERVER, $name)));
      if(scalar(@{ $perl_deps }) > 0) {
         print "   Found: \n      - " . join("\n      - ", @{ $perl_deps }) . "\n";
         for my $dep (@{ $perl_deps }) {
            install_perl_module($dep);
         }
      }
      else {
         print "   None found.\n";
      }
   }

   print "Downloading $name...   ";
   $name =~ s/::/\//g;
   my $content = get(sprintf($RECIPE_SERVER, $name));
   open(my $fh, ">", "tmp-mod.tar.gz") or die($!);
   binmode $fh;
   print $fh $content;
   close($fh);
   chdir("lib");
   system("tar xvzf ../tmp-mod.tar.gz >dl.log 2>&1");
   unlink("dl.log");
   chdir("..");
   unlink("tmp-mod.tar.gz");

   print "done.\n";

}

sub install_perl_module {
   my ($mod) = @_;

   print "Checking $mod: ";
   eval "use $mod";
   if($@) {
      print "[failed]\n";
   }
   else {
      print "[ok]\n";
      return;
   }

   print "Trying to install $mod... ";

   my $cmd = "cpanm";
   my $out = qx($cmd --help 2>&1);
   if($? != 0) {
      $cmd = "cpan";
      $out = qx($cmd -h 2>&1);

      if($? != 0) {
         print "[failed]\n";
         print "Can't find cpanm or cpan. Please install $mod manually.\n";
         return;
      }

   }

   my $cpanm_opts = "";
   if(exists $opts->{sudo}) {
      $cmd = "sudo $cmd";
   }

   $out = qx($cmd $cpanm_opts $mod 2>&1);
   open(my $log, ">>", "rexify-install.log") or die($!);
   print $log $out;
   close($log);
   if($? != 0) {
      print "[failed]\n";
      print "!! Please install $mod manually. See rexify-install.log for more details.\n";
   }
   else {
      print "[ok]\n";
   }
}

if(exists $opts->{search}) {
   my $search_string = $opts->{search};
   # only a search
   print "Downloading recipes.yml ... ";
   my $recipes = get($SEARCH_SERVER);
   print " done.\n";

   print "Searching...\n\n";

   my $data    = Load($recipes);

   for my $mod (keys %{ $data }) {
      if($mod =~ qr{$search_string}i) {
         print_found($mod, $data->{$mod});
         next;
      }

      if($data->{$mod}->{Description} =~ qr{$search_string}i) {
         print_found($mod, $data->{$mod});
      }
   }

   exit 0;
}

if(exists $opts->{use} && $ARGV[0] =~ /^\-\-use/) {
   if($opts->{use}) {
      if(! ref $opts->{use}) {
         $opts->{use} = [ $opts->{use} ];
      }

      for my $use_mod (@{ $opts->{use} }) {
         download_recipe($use_mod);
      }
   }

   exit 0;
}

if(exists $opts->{"create-module"}) {
   my $dir = $ARGV[0];
   my $module_name = $dir;

   if(-f "Rexfile" && $opts->{"create-module"}) {
      $dir = "lib/" . $opts->{"create-module"};
      $module_name = $opts->{"create-module"};
   }

   if($dir =~ m/\-\-create\-module/) {
      print "USAGE: $0 Module::Name --create-module\n";
      exit 1;
   }

   $dir =~ s/::/\//g;

   print "Creating module $module_name...\n";

   print "   mkdir $dir\n";
   mkdir $dir, recursive => 1;
   chdir $dir;

   print "   Creating template file: __module__.pm\n";
   file "__module__.pm",
      content => template('@module.pm.tpl', dir => $dir, module_name => $module_name);

   print "   Creating template file: meta.yml\n";
   file "meta.yml",
      content => template('@meta.yml.tpl', dir => $dir, module_name => $module_name);

   print "\n";
   print "Your module has been created in $dir.\n";
   
   exit 0;
}


my $dir = $ARGV[0];

if(defined $ARGV[1] && $ARGV[1] !~ m/^\-\-/) {
   $dir = $ARGV[1];
}

if(exists $opts->{template} && -f $opts->{template} && $opts->{template} !~ m/^\//) {
   my $cwd = getcwd;
   $opts->{template} = "$cwd/" . $opts->{template};
}
elsif(exists $opts->{template} && $opts->{template} !~ m/^\//) {
   $opts->{template} = '@' . $opts->{template};
}

unless(-d $dir) {
   print "Created $dir\n";
   mkdir($dir);
}
print "chdir to $dir\n";
chdir($dir);

unless(-d 'lib') {
   mkdir('lib');
}

unless(-f 'lib' . $ARGV[0] . '.pm') {
   open(my $fh, ">", "lib/$ARGV[0].pm") or die($!);
   print $fh template('@libfile', lib => $ARGV[0]);
   close($fh);

   print STDERR "Created lib/Rex/$ARGV[0].pm\n";

   if($opts->{template}) {
      open($fh, ">", "Rexfile") or die($!);
      print $fh template($opts->{template}, lib => $ARGV[0]);
      close($fh);
   }
   else {
      open($fh, ">", "Rexfile") or die($!);
      print $fh template('@rexfile', lib => $ARGV[0]);
      close($fh);
   }

   if($opts->{use}) {
      if(! ref $opts->{use}) {
         $opts->{use} = [ $opts->{use} ];
      }

      for my $use_mod (@{ $opts->{use} }) {
         download_recipe($use_mod);
      }
   }

   print STDERR "Created Rexfile.\n";
   print STDERR "Done.\n\nNow edit Rexfile to suite your needs.\n";
   print STDERR "You can edit $dir/lib/$ARGV[0].pm to define tasks.\n";
   print STDERR "\n\nIf you have any questions or wishes\n\n\tjust join #rex on freenode\n\nor post them here:\n\n\thttps://github.com/krimdomu/Rex/issues\n\n";
}
else {

   if($opts->{use}) {
      if(! ref $opts->{use}) {
         $opts->{use} = [ $opts->{use} ];
      }

      for my $use_mod (@{ $opts->{use} }) {
         download_recipe($use_mod);
      }
   }



   exit;
}

sub get {
   my ($url) = @_;

   my $ua = LWP::UserAgent->new;

   if($AUTH_USER && $AUTH_PASSWORD) {
      my ($netloc) = ($RECIPE_SERVER =~ m/^https?:\/\/([^\/]+)\//);
      unless($netloc =~ m/\:\d+$/)  {
         if($netloc =~ m/^https/) {
            $netloc .= ":443";
         }
         else {
            $netloc .= ":80";
         }
      }
      $ua->credentials($netloc, $AUTH_REALM, $AUTH_USER, $AUTH_PASSWORD);
   }

   my $resp = $ua->get($url);
   if($resp->is_success) {
      return $resp->decoded_content;
   }
}

__DATA__

@rexfile
# enable new Features
use Rex -feature => 0.40;

# set your username
set user => "<user>";

# set your password
set password => "<password>";

# enable password authentication
set -passauth;

# put your server in this group
set group => "servers" => "server1", "server2";


# now load every module via ,,require''
require <%= $::lib %>;

@end

@libfile
package <%= $::lib %>;

use Rex -base;

desc "Get uptime of server";
task "uptime", group => 'servers', sub {
   say run "uptime";
};

1;
@end

@box
use strict;
use warnings;

use Rex -feature => 0.40;
use Rex::Commands::Box;

set user => '<user>';
set password => '<password>';
set -passauth;

#
# CALL:
# rex init --name=<%= $::lib %> --url=http://box.rexify.org/box/ubuntu-server-12.10-amd64.ova
desc "Initialize and start the VM: rex init --name=vmname [--url=http://...]";
task "init", sub {

   my $param = shift;

   box {
      my ($box) = @_;
      $box->name($param->{name});

      # where to download the base image
      $box->url($param->{url});

      # default is nat
      #$box->network(1 => {
      #   type => "bridged",
      #   bridge => "eth0",
      #});

      # only works with network type = nat
      # if a key ssh is present, rex will use this to log into the vm
      # you need this if you run VirtualBox not on your local host.
      $box->forward_port(ssh => [2222 => 22]);

      # share a folder from the host system
      #$box->share_folder("sharename" => "/path/on/host/system");

      # define the authentication to the box
      # if you're downloading one from box.rexify.org this is the default.
      $box->auth(
         user => "root",
         password => "box",
      );

      # if you want to provision the machine, 
      # you can define the tasks to do that
      $box->setup(qw/install_webserver/);
   };

};

#
# CALL:
# rex down --name=<%= $::lib %>
desc "Stop the VM: rex down --name=vmname";
task "down", sub {
   
   my $param = shift;

   my $box = Rex::Commands::Box->new(name => $param->{name});
   $box->stop;
};


task "install_webserver", sub {

   # install packages / customize the vm
   install "apache2";

};

require <%= $::lib %>;

@end

@module.pm.tpl
package <%= $::module_name %>;

use Rex -base;

task example => sub {
   my $output = run "uptime";
   say $output;
};

1;

=pod

=head1 NAME

$::module_name - {{ SHORT DESCRIPTION }}

=head1 DESCRIPTION

{{ LONG DESCRIPTION }}

=head1 USAGE

{{ USAGE DESCRIPTION }}

 include qw/<%= $::module_name %>/;
  
 task yourtask => sub {
    <%= $::module_name %>::example();
 };

=head1 TASKS

=over 4

=item example

This is an example Task. This task just output's the uptime of the system.

=back

=cut

@end

@meta.yml.tpl
Name: <%= $::module_name %>
Description: {{ DESCRIPTION }}
Author: {{ your name <your.name@email.com> }}
License: {{ THE LICENSE }}

# Only if you have dependencies to other Rex Modules.
Require:
   - Other::Rex::Module
   - 2nd::Rex::Module
@end
