#!/usr/bin/perl

use Term::Clui;
use Term::ShellUI;
use Term::ANSIColor qw(:constants);
use Getopt::Long;
use JMX::Jmx4Perl::Config;
use strict;
use English;

use Data::Dumper;

use OSGi::Osgish;

my %opts = ();
my $result = GetOptions(\%opts,
                        "server|s=s",
                        "user|u=s","password|p=s",
                        "proxy=s",
                        "proxy-user=s","proxy-password=s",
                        "target|t=s","target-user=s","target-password=s",
                        "config=s",
                        "version!",
                        "help|h!" => sub { &Getopt::Long::HelpMessage() }
                       );

if ($opts{version}) {
    print "osgish ",$OSGi::Osgish::VERSION,"\n";
    exit(0);
}

my $use_color = 1;
my $no_color_prompt = 0;        # Don't color for certain readline imp.
my $config = &get_config($opts{config});
my $jmx_config = new JMX::Jmx4Perl::Config($config);
my $args = &get_args(\%opts);

&init;

# Current connection to OSGi agent
my $osgi;

# Current server
my $server;
my ($server_list,$server_map) = &init_server_list(\%opts,$jmx_config);

# Context for subcommands
my @contexts = ();

my $osgi = &get_osgi($server);

my $top_commands = &top_commands;
my $term = new Term::ShellUI(
                             commands => $top_commands,
                             history_file => "~/.osgish_history",
                             prompt => \&prompt
                            );
#$term->{debug_complete}=5;

# Special readline customization:
if ($term->{term}->ReadLine eq "Term::ReadLine::Perl") {
    $term->{term}->ornaments(0);
    $no_color_prompt = 1;
}
$term->run;

# ====================================================================
# Commands used

# Top-Level commands
sub top_commands {
    return 
        { 
         &bundle_commands,
         &service_commands,
         &global_commands
#         "help" =>   { 
#                      desc => "Print helpful information",
#                      args => sub { shift->help_args($helpcats, @_); },
#                      method => sub { shift->help_call($helpcats, @_); } 
#                     },
        };
}

# Commands in context "bundle"
sub bundle_commands {
    my $cmds = { 
                "ls" => { 
                         desc => "List all bundles",
                         proc => \&cmd_list_bundles,
                        },
                "start" => { 
                            desc => "Start a bundle",
                            proc => \&cmd_start_bundle,
                            args => sub { &complete_bundles(@_) }
                           },
                "stop" => { 
                           desc => "Stop a bundle",
                           proc => \&cmd_stop_bundle,
                           args => sub { &complete_bundles(@_) }
                          }
               };
    
    return $server ? (
                      "bundle" => { 
                                   desc => "Bundles related operations",
                                   proc => sub { 
                                       &cmd_set_context("bundle",$cmds,&top_commands) 
                                   },
                                   cmds => $cmds                       
                                  },
                      "b" => { alias => "bundle", exclude_from_completion=>1},
                     ) : ();
}

# Commands im context "service"
sub service_commands {
    my $cmds = { 
                "ls" => { 
                         desc => "List all service",
                         proc => \&cmd_list_services,
                        },
               };
    
    return $server ? (
                      "service" => { 
                                   desc => "Service related operations",
                                   proc => sub { 
                                       &cmd_set_context("service",$cmds,&top_commands) 
                                   },
                                   cmds => $cmds                       
                                  },
                      "s" => { alias => "service", exclude_from_completion=>1},
                      "serv" => { alias => "service", exclude_from_completion=>1},
                     ) : ();
}

# Commands always available
sub global_commands {
    return 
      ( 
       "connect" => { 
                     desc => "Connect to a server by its URL or symbolic name",
                     minargs => 1, maxargs => 2,
                     args => sub { &complete_servers(@_) },
                     proc => \&cmd_connect
                    },
       "servers" => { 
                     desc => "Show all configured servers",
                     proc => \&cmd_show_servers
                    },
       "error" => {
                   desc => "Show last error (if any)",
                   proc => \&cmd_last_error
                  },
       $server ? ("shutdown" => {
                                 desc => "Shutdown server",
                                 proc => \&cmd_shutdown
                                }) : ()
      );
}

# ==================

# Update the context and available commands
sub cmd_set_context {
    # The new context
    my $context = shift;
    # Sub-commands within the context
    my $sub_cmds = shift;
    # Parent commands of this sub context
    my $parent_cmds = shift;
    
    push @contexts,$context;
    
    # Set sub-commands
    $term->commands
      ({
        %$sub_cmds,
        &global_commands,
        $parent_cmds ? 
        (".." => {
                 desc => "Go up one level",
                 proc => 
                  sub { 
                      pop @contexts;
                      $term->commands($parent_cmds);
                  }
                 },
         "/" => { 
                 desc => "Go to the top level",
                 proc => 
                 sub { 
                     &reset_context();
                 }
                }) : ()
       });    
}

# Connect to a server
sub cmd_connect {
    my $arg = shift;
    my $name = shift;
    my $s = $server_map->{$arg};
    unless ($s) {
        unless ($arg =~ m|^\w+://[\w:]+/|) {
            print "Invalid URL $arg\n";
            return;
        }
        $name ||= &prepare_server_name($arg);
        my $entry = { name => $name, url => $arg };
        push @$server_list,$entry;
        $server_map->{$name} = $entry;
        $s = $entry;
    }
    my ($old_server,$old_osgi) = ($server,$osgi);
    eval { 
        $server = $s->{name};
        $osgi = &get_osgi($server) || die "Unknown $server (not an alias nor a proper URL).\n";;
        $osgi->init();
    };
    if ($@) {
        $server = $old_server;
        $osgi = $old_osgi;
        die $@;
    } 
    &reset_context;
    $term->commands(&top_commands);
    my ($yellow,$reset) = &get_color(YELLOW,RESET);
    print "Connected to " . $yellow . $server . $reset .  " (" . $osgi->url . ")\n";
}

# Show all servers
sub cmd_show_servers {
    for my $s (@$server_list) {
        my ($ms,$me) = &get_color(YELLOW,RESET);
        my $sep = $s->{from_config} ? "-" : "*";
        printf " " . $ms . '%30.30s' . $me . ' %s %s' . "\n",$s->{name},$sep,$s->{url};
    }
}

# Shutdown a server
sub cmd_shutdown {
    unless ($osgi) {
        print "Not connected to a server\n";
        return;
    }
    my ($yellow,$reset) = &get_color(YELLOW,RESET);
    my $answer = &choose("Really shutdown " . $yellow . $server . $reset . " ?","yes","no");
    if ($answer eq "yes") {
        $osgi->shutdown;
        $server = undef;
        $osgi = undef;
        &reset_context;
    } else {
        print "Shutdown of ". $yellow . $server . $reset . " cancelled\n";
    }
}

# =================================================================================================== 

# List bundles
sub cmd_list_bundles {

    unless ($osgi) {
        print "Not connected to a server\n";
        return;  
    }
    my $bundles = $osgi->bundles;
    my $text = sprintf("%4.4s   %-11.11s %3s %s\n","Id","State","Lev","Name");
    $text .= "-" x 87 . "\n";
    my $nr = 0;
    for my $k (sort { $bundles->{$a}->{Identifier} <=> $bundles->{$b}->{Identifier} } keys %$bundles) {
        my $b = $bundles->{$k};
        my $id = $b->{Identifier};
        my ($green,$red,$reset) = &get_color(GREEN,RED,RESET);
        my $state = lc $b->{State};
        my $color = "";
        $color = $red if $state eq "installed";
        $color = $green if $state eq "active";
        my $state = uc(substr($state,0,1)) . substr($state,1);
        my $level = $b->{StartLevel};

        my $name = $b->{Headers}->{'[Bundle-Name]'}->{Value};
        my $sym_name = $b->{SymbolicName};
        my $version = $b->{Version};
        my $location = $b->{Location};
        my $desc = $name || $sym_name || $location;
        $desc .= " ($version)" if $version && $version ne "0.0.0";
        
        $text .= sprintf "%s%4d   %-11s%s %3d %s%s%s\n",$color,$id,$state,$reset,$level,$desc; 
        $nr++;
    }
    &print_paged($text,$nr);
    #print $text;
    #print Dumper($bundles);
}

sub cmd_start_bundle {
    my $bundle = shift;
    $osgi->start_bundle($bundle);
}

sub cmd_stop_bundle {
    my $bundle = shift;
    $osgi->stop_bundle($bundle);    
}

# =========================================================================================

sub cmd_list_services { 
    unless ($osgi) {
        print "Not connected to a server\n";
        return;  
    }
    my $services = $osgi->services;
    my $text = sprintf("%4.4s   %-60.60s %5.5s | %s\n","Id","Class","Bd-Id","Using bundles");
    $text .= "-" x 74 . "+" . "-" x 24 . "\n";
    my $nr = 0;
    for my $k (sort { $services->{$a}->{Identifier} <=> $services->{$b}->{Identifier} } keys %$services) {
        my $s = $services->{$k};
        my $id = $s->{Identifier};
        my ($green,$red,$reset) = &get_color(GREEN,RED,RESET);
        my $using_bundles = $s->{UsingBundles} || [];
        my $using = $using_bundles ? join (", ",sort { $a <=> $b } @$using_bundles) : "";
        my $bundle_id = $s->{BundleIdentifier};
        my $classes = $s->{objectClass};        
        $text .= sprintf "%4d   %-60.60s  %4d | %s\n",$id,$classes->[0],$bundle_id,$using;
        for my $i (1 .. $#$classes) {
            $text .= sprintf "       %-66.66s |\n",$classes->[$i];
        }
        $nr++;
    }
    &print_paged($text,$nr);
}

# =========================================================================================
sub cmd_last_error {
    if ($osgi && $osgi->last_error) { 
        my $txt = $osgi->last_error;
        chop $txt;
        print "$txt\n";
    } else {
        print "No errors";
    }
}

# ==================

sub reset_context {
    $term->commands(&top_commands);
    @contexts = ();
}

sub complete_servers {
    my ($term,$cmpl) = @_;
    return [] unless @$server_list;
    my $str = $cmpl->{str} || "";
    my $len = length($str);
    return [ grep { substr($_,0,$len) eq $str }  map { $_->{name} } @$server_list  ];
}

sub complete_bundles {
    my ($term,$cmpl) = @_;
    return [] unless $osgi;
    my $str = $cmpl->{str} || "";
    my $len = length($str);
    if ($str =~ /^\d+$/) { 
        # Complete on ids
        return [ sort { $a <=> $b } grep { substr($_,0,$len) eq $str } @{$osgi->ids(use_cached => 1)} ];
    } else {
        my @sym_names = sort keys %{$osgi->symbolic_names(use_cached => 1)};
        return \@sym_names unless $str;
        return [ grep { substr($_,0,$len) eq $str } @sym_names ];
    }
}

sub prompt {
    my $term = shift;
    my ($yellow,$cyan,$red,$reset) = $no_color_prompt ? ("","","","") : &get_color(YELLOW,CYAN,RED,RESET,{escape => 1});
    my $p = "[";
    $p .= $server ? $yellow . $server : $red . "osgish";
    $p .= $reset;
    $p .= ":" . $cyan if @contexts;
    for my $i (0 .. $#contexts) {
        $p .= $contexts[$i];
        $p .= $i < $#contexts ? "/" : $reset;
    }
    $p .= "] : ";
    return $p;
}

sub get_osgi {
    my $server = shift;
    return undef unless $server;
    my $sc = $server_map->{$server};
    return undef unless $sc;
    if ($sc->{from_config}) {
        return new OSGi::Osgish({ %$args, server => $server, config => $jmx_config});
    } else {
        return new OSGi::Osgish({ %$args, url => $sc->{url}});
    }
}

sub get_server_list_from_config {
    my $jmx_config = shift;
    return map { { name => $_->{name}, url => $_->{url}, from_config => 1 } } @{$jmx_config->get_servers};
}

sub prepare_server_name {
    my $url = shift;
    return $1 if $url =~ m|^\w+://([^/]+)/?|;
}

sub get_config {
    my $file = shift || $ENV{HOME} . "/.osgish";
    my $ret = {};

    # Merge if servers from ~/.j4p
    my $default =  {};
    $default = { new Config::General(-ConfigFile => $ENV{HOME} . "/.j4p",-LowerCaseNames => 1)->getall } 
      if -e $ENV{HOME} . "/.j4p";
    if ($file && -e $file) {
        $ret = { new Config::General(-ConfigFile => $file,-LowerCaseNames => 1,-DefaultConfig => $default)->getall };        
    } 
    return $ret;
}

sub get_args {
    my $o = shift;
    my $ret = { };
    
    for my $arg qw(user password) {
        if (defined($opts{$arg})) {
            $ret->{$arg} = $opts{$arg};
        }
    }
    
    if (defined($opts{proxy})) {
        my $proxy = {};
        $proxy->{url} = $opts{proxy};
        for my $k (qw(proxy-user proxy-password)) {
            $proxy->{$k} = defined($opts{$k}) if $opts{$k};
        }
        $ret->{proxy} = $proxy;
    }        
    if (defined($opts{target})) {
        $ret->{target} = {
                          url => $opts{target},
                          $opts{'target-user'} ? (user => $opts{'target-user'}) : (),
                          $opts{'target-password'} ? (password => $opts{'target-password'}) : (),
                         };
    }
    return $ret;
}

sub get_color { 
    my @colors = @_;
    my $args = ref($colors[$#colors]) eq "HASH" ? pop @colors : {};
    if ($use_color) {
        if ($args->{escape}) {
            return map { "\01" . $_ . "\02" } @colors;
        } else {
            return @colors;
        }
    } else {
        return map { "" } @colors;
    }
}

sub print_paged {
    my $text = shift;
    my $nr = shift;
    if (defined($nr) && $nr < 24) {
        print $text;
    } else {
        view("",$text);
    }
}

sub init {
    # Force pager to show colors
    if ($use_color) {
        if ($ENV{LESS}) {
            $ENV{LESS} .= " -f";
        } else {
            $ENV{LESS} = "-f";
        }
    }
}

sub init_server_list {
    my ($o,$jc) = @_;
    my @servers = &get_server_list_from_config($jc);
    if ($o->{server}) {
        my $config_s = $jc->get_server_config($o->{server});
        if ($config_s) {
            my $found = 0;
            my $i=0;
            my $entry = { name => $config_s->{name}, url => $config_s->{url}, from_config => 1 } ;
            for my $s (@servers) {
                if ($s->{name} eq $o->{server}) {
                    $servers[$i] = $entry;
                    $found = 1;                 
                    last;
                }
                $i++;
            } 
            push @servers,$entry unless $found;
            $server = $config_s->{name};
        } else {
            die "Invalid URL ",$o->{server} unless ($o->{server} =~ m|^\w+://|);
            my $name = &prepare_server_name($opts{server});
            push @servers,{ name => $name, url => $o->{server} };
            $server = $name;
        }
    }

    return (\@servers,
              { map { $_->{name} => $_ } @servers });
}

