#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long qw{:config posix_default no_ignore_case gnu_compat};
use IO::Socket;
use Try::Tiny;
use PlSense;
use PlSense::Logger;
use PlSense::Configure;
use PlSense::Util;
use PlSense::SocketClient;

my %opthelp_of = ("-h, --help"        => "Show this message.",
                  "-v, --version"     => "Show vertion.",
                  "-i, --interactive" => "Run interactively.",
                  "-c, --cachedir"    => "Path of directory caching information for Completion/Help.",
                  "--port1"           => "Port number for listening by main server process. Default is 33333.",
                  "--port2"           => "Port number for listening by work server process. Default is 33334.",
                  "--port3"           => "Port number for listening by resolve server process. Default is 33335.",
                  "--maxtasks"        => "Limit count of task that run on server process.",
                  "--loglevel"        => "Level of logging. Its value is for Log::Handler.",
                  "--logfile"         => "Path of log file.",
                  );

my %fnchelp_of = ("help [Command]"                => [ "Show detail help about Command." ],
                  "svstat, serverstatus"          => [ "Show server process status." ],
                  "svstart, serverstart"          => [ "Start server process." ],
                  "svstop, serverstop"            => [ "Stop server process." ],
                  "refresh"                       => [ "Refresh server process." ],
                  "o, open [File/Module]"         => [ "Open File/Module.", <<'EOF'

Build information about File/Module if not yet build it.
The included modules (ex. 'use ...;') of File/Module are built too if not yet build it.

And exec onfile for given file path or module name.
For detail, see help of location command.

The build task is run on server process, and requires a few minutes until finish.
For checking the status, use ready/ps/queue commands.

EOF
                                                       ],
                  "u, update [File/Module]"       => [ "Update File/Module.", <<'EOF'

Build information about File/Module forcely.
The included modules (ex. 'use ...;') of File/Module are built only if not yet build it.

EOF
                                                       ],
                  "remove [File/Module]"          => [ "Remove the cached information about File/Module." ],
                  "removeall"                     => [ "Remove all cached information." ],
                  "mhelp, modhelp [Module]"       => [ "Show help about Module." ],
                  "fhelp, subhelp [Sub] [Module]" => [ "Show help about Sub of Module." ],
                  "vhelp, varhelp [Var] [Module]" => [ "Show help about Var of Module." ],
                  "ahelp, assisthelp [Cand]"      => [ "Show help about Cand of last assist.", <<'EOF'

Cand is a one of the result of last assist command.
For detail, see help of assist command.

EOF
                                                       ],
                  "chelp, codehelp [Code]"        => [ "Show help about Code.", << 'EOF'

Code is a incomplete source code of Perl like the following line.

IO::File
my $io = IO::File->new
$io

EOF
                                                       ],
                  "subinfo [Code]"                => [ "Show signature of the method detected by Code.", <<'EOF'

Code is a incomplete source code of Perl like the following line.

&AnyModule::do_something
grep
my $io = IO::File->new

The result format is the following.

NAME: Method-name
ARG1: Var-name As Unknown/Module-name/Var-type
ARG2: ...
RETURN: Var-name/Literal/NoIdent As Unknown/Module-name/Var-type
FILE: File-path
LINE: Line-number
COL: Column-number

Var-type is a one of SCALAR, ARRAY, HASH, REFERENCE.
Literal means return value is literal (ex. return "hoge";).
NoIdent means return value is not variable/literal (ex. return grep ...;).
Unknown means still not clear.

EOF
                                                       ],
                  "onfile [File]"                 => [ "Set current location by File for assist.", <<'EOF'

Set File to current filepath.
And set 'main' to current module automatically.
And set '' to current function automatically.

EOF
                                                       ],
                  "onmod [Module]"                => [ "Set current location by Module for assist.", <<'EOF'

Set Module to current module.
And set '' to current function automatically.

EOF
                                                       ],
                  "onf, onsub [Sub]"              => [ "Set current location by Sub for assist.", <<'EOF'

Set Sub to current function.

EOF
                                                       ],
                  "loc, location"                 => [ "Show current location.", <<'EOF'

Current location is where you are now.
The result format is the following.

Project: Project-name
File: File-path
Module: Module-name
Sub: Function-name

Project-name is detected by the config file that is named '.plsense' and put in the root of project tree.
The file is found in following order. If not found, the project value is 'default'.

If the file of current location is /home/user1/MyProj/src/lib/MyModule.pm,
/home/user1/MyProj/src/lib/.plsense
/home/user1/MyProj/src/.plsense
...
Use the first value that the file exists.
But, '~/.plsense' is ignored because it's a config file for plsense.
For setting location, see help of open/onfile/onmod/onsub command.

.plsense format is the following.

name=Project-name
lib-path=Library-path

Project-name need to match '[a-zA-Z0-9_]+'.
Library-path is a relative path to root of library for the project.
About '~/.plsense', see help of config command.

EOF
                                                       ],
                  "c, codeadd [Code]"             => [ "Add source to current location.", <<'EOF'

Add information that is built from Code to current location.
This command is a substitute for open/update command and lighter than them.
Code is a incomplete source code of Perl like the following line.

use List::AllUtils qw{ :al
for $i ( 0.. 10 ) { print $i; }
sub hoge { my $self = shift; if ( $self ) { do_anything(); } return "hog

Abount current location, see help of location command.

EOF
                                                       ],
                  "a, assist [Code]"              => [ "Do assist about Code.", <<'EOF'

Provide the optimized completion that is the available words after Code.
Code is a incomplete source code of Perl like the following line.

use 
Use IO::F
$
$self->

This command needs where you are.
For more information, see help of location command.

EOF
                                                       ],
                  "ps"                            => [ "Show task list that is running on server process." ],
                  "queue"                         => [ "Show stack of task that is not yet running on server process." ],
                  "ready [File/Module]"           => [ "Show status of File/Module.", <<'EOF'

Kind of status is 'Yes'/'No'/'Not Found'.
'Yes' means the File/Module information have been built already.
'No' means the File/Module information is not built yet.
'Not Found' means File/Module is not opened yet.
If File/Module is not given, show the list of the 'Yes' modules.

EOF
                                                       ],
                  "debugstk"                      => [ "Show quantity of stocked on server process for debugging." ],
                  "debugmod [File/Module]"        => [ "Show detail of File/Module for debugging." ],
                  "debugrt [Regexp]"              => [ "Show routing matched by Regexp for debugging." ],
                  "debugsubst [Regexp]"           => [ "Show substitute matched by Regexp for debugging." ],
                  "debuglex [Code/File]"          => [ "Show the PPI::Lexer layout as result of parsing Code/File for debugging." ],
                  "config"                        => [ "Update configuration about plsense.", <<'EOF'

Configuration is saved into '~/.plsense'.
The format is the following.

Option-name=Option-value
...

About Option-name, exec 'plsense --help'.

The config of '~/.plsense' means giving '--Option-name=Option-value' when execut plsense.
You can override the config of '~/.plsense' by executing 'plsense --Option-name=Option-value'.

EOF
                                                       ],
                  "loglevel [Level] [Server]"     => [ "Update the log level to Level about the logging of Server.", <<'EOF'

Level is the level of logging by Log::Handler.
Server is the target server. This value is one of the following value.
  main    ... only Main server
  work    ... only Work server
  resolve ... only Resolve server
  all     ... all server
If Server is not given, The target is local process.

EOF
                                                       ],
                  "explore [Regexp]"              => [ "Show the packages matched by Regexp and their methods.", <<'EOF'

Regexp is the regular expression matches the packages to explore.
The one format of result is like the following.

Package-name Package-filepath Package-line:Package-column
  &Method-name Method-line:Method-column
  ...

Package-line/Package-column is the line/column number that the package is defined on.
Method-line/Method-column is the line/column number that the method is defined on.

EOF
                                                       ],
                  );

my %function_of = (help         => \&show_help,
                   serverstatus => \&show_server_status,
                   svstat       => \&show_server_status,
                   serverstop   => \&stop_server,
                   svstop       => \&stop_server,
                   serverstart  => \&start_server,
                   svstart      => \&start_server,
                   refresh      => \&refresh_server,
                   open         => \&open_file,
                   o            => \&open_file,
                   update       => \&update_file,
                   u            => \&update_file,
                   remove       => \&remove_file,
                   removeall    => \&remove_all,
                   modhelp      => \&help_module,
                   mhelp        => \&help_module,
                   subhelp      => \&help_method,
                   fhelp        => \&help_method,
                   varhelp      => \&help_variable,
                   vhelp        => \&help_variable,
                   assisthelp   => \&help_last_assist,
                   ahelp        => \&help_last_assist,
                   codehelp     => \&help_code,
                   chelp        => \&help_code,
                   subinfo      => \&get_method_information,
                   onfile       => \&set_currentfile,
                   onmod        => \&set_currentmodule,
                   onsub        => \&set_currentmethod,
                   onf          => \&set_currentmethod,
                   location     => \&get_current_location,
                   loc          => \&get_current_location,
                   codeadd      => \&add_source,
                   c            => \&add_source,
                   assist       => \&assist_coding,
                   a            => \&assist_coding,
                   ps           => \&get_process_list,
                   queue        => \&get_task_queue,
                   ready        => \&is_ready,
                   debugstk     => \&debug_stocked,
                   debugmod     => \&debug_module,
                   debugrt      => \&debug_routing,
                   debugsubst   => \&debug_substitute,
                   debuglex     => \&debug_lexer,
                   config       => \&update_config,
                   loglevel     => \&update_loglevel,
                   explore      => \&explore_package,
                   );

my ($cachedir, $port1, $port2, $port3, $maxtasks, $loglvl, $logfile);
my $interactive = 0;
GetOptions ('help|h'        => sub { show_usage(); exit 0; },
            'version|v'     => sub { print "PlSense version is $PlSense::VERSION\n"; exit 0; },
            'cachedir|c=s'  => \$cachedir,
            'port1=i'       => \$port1,
            'port2=i'       => \$port2,
            'port3=i'       => \$port3,
            'maxtasks=i'    => \$maxtasks,
            'loglevel=s'    => \$loglvl,
            'logfile=s'     => \$logfile,
            'interactive|i' => \$interactive, );

setup_logger($loglvl, $logfile);
if ( ! $loglvl ) { $loglvl = ''; }
if ( ! $logfile ) { $logfile = ''; }

if ( ! $cachedir ) {
    logger->info("Read option from config file.");
    setup_config("", 0, 1) or exit 1;
    $cachedir   = get_config("cachedir");
    $port1    ||= get_config("port1");
    $port2    ||= get_config("port2");
    $port3    ||= get_config("port3");
    $maxtasks ||= get_config("maxtasks");
    $loglvl   ||= get_config("loglevel") || "";
    $logfile  ||= get_config("logfile") || "";
    setup_logger($loglvl, $logfile);
}
set_primary_config( cachedir => $cachedir,
                    port1    => $port1,
                    port2    => $port2,
                    port3    => $port3,
                    maxtasks => $maxtasks,
                    loglevel => $loglvl,
                    logfile  => $logfile, );
setup_config() or exit 1;

my $scli = PlSense::SocketClient->new({ retryinterval => 0.2, maxretry => 10 });
my %pid_of;

$SIG{INT}  = sub { logger->notice("Receive SIGINT");  exit 0; };
$SIG{TERM} = sub { logger->notice("Receive SIGTERM"); exit 0; };

if ( $interactive ) {

    print "> ";
    *STDOUT->autoflush();
    CMD:
    while ( my $line = <STDIN> ) {
        chomp $line;
        my $cmdnm = $line =~ s{ ^ \s* ([a-z]+) }{}xms ? $1 : "";
        if ( $cmdnm eq "quit" ) {
            last CMD;
        }
        elsif ( $cmdnm eq "exit" ) {
            last CMD;
        }
        elsif ( exists $function_of{$cmdnm} ) {
            logger->notice("Do $cmdnm : $line");
            my $fnc = $function_of{$cmdnm};
            print &$fnc($line);
        }
        elsif ( $cmdnm ) {
            print STDERR "Unknown command : $cmdnm\n";
        }
        print "> ";
    }

}
else {

    my @cmdarg = @ARGV;
    my $cmdnm = shift @cmdarg || "";
    if ( ! $cmdnm ) {
        show_usage();
        exit 1;
    }
    elsif ( ! exists $function_of{$cmdnm} ) {
        print STDERR "Unknown command : $cmdnm\n";
        exit 1;
    }

    my $line = join(" ", @cmdarg);
    logger->notice("Do $cmdnm : $line");
    my $fnc = $function_of{$cmdnm};
    print &$fnc($line);

}

exit 0;


sub show_usage {
    my $optstr = "";
    OPTHELP:
    foreach my $key ( sort keys %opthelp_of ) {
        $optstr .= sprintf("  %-25s %s\n", $key, $opthelp_of{$key});
    }
    my $comstr = "";
    FUNCTION_HELP:
    foreach my $key ( sort keys %fnchelp_of ) {
        $comstr .= sprintf("  %-35s %s\n", $key, @{$fnchelp_of{$key}}[0]);
    }

    print <<"EOF";
PlSense is a development tool for Perl.
PlSense provides Completion/Help about Module/Function/Variable optimized for context.

Usage:
  plsense [Option] [Command] argument...
  plsense [Option] -i

Option:
$optstr
Command:
$comstr
EOF
    return;
}

sub show_help {
    my $cmdnm = shift || "";
    $cmdnm =~ s{ ^\s+ }{}xms;
    $cmdnm =~ s{ \s+$ }{}xms;
    if ( $cmdnm =~ m{ \A[a-z]+\z }xms ) {
        FHELP:
        foreach my $key ( keys %fnchelp_of ) {
            if ( $key =~ m{ \b $cmdnm \b }xms ) {
                print "Usage: ".$key."\n\n";
                print @{$fnchelp_of{$key}}[0]."\n";
                if ( $#{$fnchelp_of{$key}} == 1 ) {
                    print @{$fnchelp_of{$key}}[1];
                }
                return;
            }
        }
    }
    print "Unknown command : $cmdnm\n";
    print "Command name list at the following.\n";
    print join("\n", sort keys %function_of)."\n";
    return;
}


sub start_main_server {
    my $immediately = shift || 0;
    return start_server_sentinel("main", $immediately);
}
sub start_work_server {
    my $immediately = shift || 0;
    return start_server_sentinel("work", $immediately);
}
sub start_resolve_server {
    my $immediately = shift || 0;
    return start_server_sentinel("resolve", $immediately);
}
sub start_server_sentinel {
    my $svtype = shift || "";
    my $immediately = shift || 0;

    my $pid = $pid_of{$svtype};
    if ( $pid && kill(0, $pid) ) { return 1; }

    if ( ! $immediately ) {
        $pid = $svtype eq "main"    ? $scli->get_main_server_response("pid",    { ignore_error => 1, maxretry => 5 })
             : $svtype eq "work"    ? $scli->get_work_server_response("pid",    { ignore_error => 1, maxretry => 5 })
             : $svtype eq "resolve" ? $scli->get_resolve_server_response("pid", { ignore_error => 1, maxretry => 5 })
             :                        0;
        if ( $pid ) {
            $pid_of{$svtype} = $pid;
            return 1;
        }
    }

    my $cmdnm = "plsense-server-".$svtype;
    my $cmdstr = $cmdnm.get_common_option_string();
    if ( $svtype eq "work" ) { $cmdstr .= " --maxtasks '".( get_config("maxtasks") || "" )."'"; }
    logger->info("Start server : $cmdstr");
    system "$cmdstr &";

    $pid = $svtype eq "main"    ? $scli->get_main_server_response("pid",    { ignore_error => 1, maxretry => 10 })
         : $svtype eq "work"    ? $scli->get_work_server_response("pid",    { ignore_error => 1, maxretry => 10 })
         : $svtype eq "resolve" ? $scli->get_resolve_server_response("pid", { ignore_error => 1, maxretry => 10 })
         :                        0;
    if ( ! $pid || ! kill(0, $pid) ) { return 0; }
    $pid_of{$svtype} = $pid;
    logger->info("Got pid of $svtype : $pid");
    return 1;
}

sub stop_main_server {
    return stop_server_sentinel("main");
}
sub stop_work_server {
    return stop_server_sentinel("work");
}
sub stop_resolve_server {
    return stop_server_sentinel("resolve");
}
sub stop_server_sentinel {
    my $svtype = shift || "";
    my $pid = $pid_of{$svtype};
    if ( $pid && ! kill(0, $pid) ) {
        $pid_of{$svtype} = undef;
        return 1;
    }

    my $ret = $svtype eq "main"    ? $scli->request_main_server("stop",    { ignore_error => 1, maxretry => 15 })
            : $svtype eq "work"    ? $scli->request_work_server("stop",    { ignore_error => 1, maxretry => 15 })
            : $svtype eq "resolve" ? $scli->request_resolve_server("stop", { ignore_error => 1, maxretry => 15 })
            :                        undef;
    if ( $ret ) {
        logger->info("Request stop to $svtype");
        $pid_of{$svtype} = undef;
        return 1;
    }
    elsif ( $pid ) {
        logger->info("Send SIGTERM to $svtype");
        kill 'TERM', $pid or logger->error("Failed kill $svtype server");
        $pid_of{$svtype} = undef;
        return 1;
    }

    return 1;
}



sub show_server_status {
    my $mainstat    = $scli->get_main_server_response("status",    { ignore_error => 1, maxretry => 5 });
    my $workstat    = $scli->get_work_server_response("status",    { ignore_error => 1, maxretry => 5 });
    my $resolvestat = $scli->get_resolve_server_response("status", { ignore_error => 1, maxretry => 5 });
    chomp $mainstat;
    chomp $workstat;
    chomp $resolvestat;
    if ( ! $mainstat )    { $mainstat    = $pid_of{main}    && kill(0, $pid_of{main})    ? "Busy" : "Not running"; }
    if ( ! $workstat )    { $workstat    = $pid_of{work}    && kill(0, $pid_of{work})    ? "Busy" : "Not running"; }
    if ( ! $resolvestat ) { $resolvestat = $pid_of{resolve} && kill(0, $pid_of{resolve}) ? "Busy" : "Not running"; }
    my $ret = "Main Server is $mainstat.\n";
    $ret .= "Work Server is $workstat.\n";
    $ret .= "Resolve Server is $resolvestat.\n";
    return $ret;
}

sub start_server {
    my $cachedir = get_config("cachedir");
    if ( ! $cachedir ) {
        logger->fatal("Required information for cachedir. For more information, run 'plsense --help'");
        return;
    }
    if ( ! -d $cachedir && ! mkdir($cachedir) ) {
        logger->fatal("Failed create cache directory [$cachedir]");
        return;
    }
    if ( ! verify_commands() ) { return; }
    my $mainret = start_main_server();
    my $workret = start_work_server();
    my $resolveret = start_resolve_server();
    return $mainret && $workret && $resolveret ? "Done\n" : "Failed\n";
}

sub stop_server {
    my $mainret = stop_main_server();
    my $workret = stop_work_server();
    my $resolveret = stop_resolve_server();
    return $mainret && $workret && $resolveret ? "Done\n" : "Failed\n";
}

sub refresh_server {
    my $loc = get_current_location() or return "Failed\n";
    my $currfilepath = $loc =~ m{ ^ File: \s+ ([^\n]*?) $ }xms ? $1 : "";
    my $currmdlnm = $loc =~ m{ ^ Module: \s+ ([^\n]*?) $ }xms ? $1 : "";
    my $currmtdnm = $loc =~ m{ ^ Sub: \s+ ([^\n]*?) $ }xms ? $1 : "";

    my @reqsvrs;
    SVTYPE:
    foreach my $svtype ( "main", "work", "resolve" ) {
        my $pid = $pid_of{$svtype};
        if ( ! $pid ) {
            $pid = $svtype eq "main"    ? $scli->get_main_server_response("pid",    { maxretry => 20 })
                 : $svtype eq "work"    ? $scli->get_work_server_response("pid",    { maxretry => 20 })
                 : $svtype eq "resolve" ? $scli->get_resolve_server_response("pid", { maxretry => 20 })
                 :                        0;
            $pid_of{$svtype} = $pid;
            logger->info("Got pid of $svtype : $pid");
        }
        if ( ! $pid ) {
            logger->error("Can't restart $svtype server cause failed to get pid");
            next SVTYPE;
        }
        logger->info("Send SIGUSR1 to $svtype");
        kill 'USR1', $pid or logger->error("Failed kill $svtype server");
        push @reqsvrs, $svtype;
    }

    my $reqopt = { ignore_error => 1, retryinterval => 3, maxretry => 100 };
    SVTYPE:
    foreach my $svtype ( @reqsvrs ) {
        my $pid = $svtype eq "main"    ? $scli->get_main_server_response("pid", $reqopt)
                : $svtype eq "work"    ? $scli->get_work_server_response("pid", $reqopt)
                : $svtype eq "resolve" ? $scli->get_resolve_server_response("pid", $reqopt)
                :                        0;
        if ( ! $pid ) {
            logger->error("Can't check $svtype server alive cause failed to get pid");
        }
        $pid_of{$svtype} = $pid;
        logger->info("Got pid of $svtype : $pid");
    }

    if ( ! $pid_of{main} || ! $pid_of{work} ) { return; }
    set_currentfile($currfilepath);
    set_currentmodule($currmdlnm);
    set_currentmethod($currmtdnm);

    return "Done\n";
}

sub open_file {
    my $filepath = shift || "";
    return $scli->get_work_server_response("open $filepath");
}

sub update_file {
    my $filepath = shift || "";
    $scli->request_work_server("buildrf $filepath");
    return;
}

sub remove_file {
    my $filepath = shift || "";
    $scli->request_main_server("remove $filepath");
    $scli->request_resolve_server("remove $filepath");
    return;
}

sub remove_all {
    $scli->get_main_server_response("removeall");
    $scli->get_work_server_response("removeall");
    $scli->get_resolve_server_response("removeall");
    return "Done\n";
}

sub help_module {
    my $mdlnm = shift || "";
    return $scli->get_main_server_response("modhelp $mdlnm");
}

sub help_method {
    my $arg = shift || "";
    $arg =~ s{ ^\s+ }{}xms;
    my @e = split m{ \s+ }xms, $arg;
    my $mtdnm = shift @e || "";
    my $mdlnm = shift @e || "";
    return $scli->get_main_server_response("subhelp $mtdnm $mdlnm");
}

sub help_variable {
    my $arg = shift || "";
    $arg =~ s{ ^\s+ }{}xms;
    my @e = split m{ \s+ }xms, $arg;
    my $varnm = shift @e || "";
    my $mdlnm = shift @e || "";
    return $scli->get_main_server_response("varhelp $varnm $mdlnm");
}

sub help_last_assist {
    my $candidate = shift || "";
    return $scli->get_main_server_response("assisthelp $candidate");
}

sub help_code {
    my $code = shift || "";
    return $scli->get_main_server_response("codehelp $code");
}

sub get_method_information {
    my $code = shift || "";
    return $scli->get_main_server_response("subinfo $code");
}

sub set_currentfile {
    my $filepath = shift || "";
    return $scli->get_work_server_response("current $filepath");
}

sub set_currentmodule {
    my $mdlnm = shift || "";
    return $scli->get_main_server_response("onmod $mdlnm");
}

sub set_currentmethod {
    my $mtdnm = shift || "";
    return $scli->get_main_server_response("onsub $mtdnm");
}

sub get_current_location {
    return $scli->get_main_server_response("location");
}

sub add_source {
    my $code = shift || "";
    $scli->request_resolve_server("codeadd $code");
    return;
}

sub assist_coding {
    my $code = shift || "";
    return $scli->get_main_server_response("codeassist $code");
}

sub get_process_list {
    return $scli->get_work_server_response("ps");
}

sub get_task_queue {
    return $scli->get_work_server_response("queue");
}

sub is_ready {
    my $mdl_or_file = shift || "";
    return $scli->get_main_server_response("ready $mdl_or_file");
}

sub debug_stocked {
    my $ret = "[MainServer]\n";
    $ret .= $scli->get_main_server_response("debugstk");
    $ret .= "[ResolveServer]\n";
    $ret .= $scli->get_resolve_server_response("debugstk");
    return $ret;
}

sub debug_module {
    my $mdl_or_file = shift || "";
    return $scli->get_main_server_response("debugmod $mdl_or_file");
}

sub debug_routing {
    my $regexp = shift || "";
    return $scli->get_main_server_response("debugrt $regexp");
}

sub debug_substitute {
    my $regexp = shift || "";
    return $scli->get_resolve_server_response("debugsubst $regexp");
}

sub debug_lexer {
    my $code = shift || "";
    return $scli->get_main_server_response("debuglex $code");
}

sub update_config {
    create_global_config() or return;
    set_primary_config( cachedir => undef,
                        port1    => undef,
                        port2    => undef,
                        port3    => undef,
                        maxtasks => undef, );
    setup_config("", 1) or return;
    return "Finished update config\n";
}

sub update_loglevel {
    my $arg = shift || "";
    $arg =~ s{ ^\s+ }{}xms;
    $arg =~ s{ \s+$ }{}xms;
    my @e = split m{ \s+ }xms, $arg;
    my $loglvl = shift @e || "";
    my $svtype = lc( shift @e || "" );
    if ( $svtype eq "all" || $svtype eq "main" ) {
        $scli->request_main_server("loglvl $loglvl");
    }
    if ( $svtype eq "all" || $svtype eq "work" ) {
        $scli->request_work_server("loglvl $loglvl");
    }
    if ( $svtype eq "all" || $svtype eq "resolve" ) {
        $scli->request_resolve_server("loglvl $loglvl");
    }
    if ( ! $svtype ) {
        update_logger_level($loglvl);
    }
    return;
}

sub explore_package {
    my $pkg_regexp = shift || "";
    return $scli->get_main_server_response("explore $pkg_regexp");
}


sub verify_commands {
    my @chkcommands = ("which", "perldoc");
    CHK_COMMAND:
    foreach my $command ( @chkcommands ) {
        my $ret = qx{ which $command 2>/dev/null };
        if ( ! $ret ) {
            logger->fatal("Can't execute '$command'.");
            return 0;
        }
    }
    return 1;
}

