### Copyright (C) 2004-2019 Eduardo M Kalinowski <eduardo@kalinowski.com.br>
###
### This program is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation; either version 2 of the License, or
### (at your option) any later version.
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### GNU General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with this program; if not, write to the Free Software
### Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
### MA 02110-1301, USA.

###
### kildclient.pl
###
### This file contains some built-in Perl functions used by
### KildClient.
###
### This is not the best place to define your own functions.
### To do that, you should configure your world to load a
### script file, and define your functions there.

use strict;

use JSON;
use MSDP;

our $__helpfile;
our @PLUGINDIRS;

# Convenience wrapper
sub _ { return ::gettext($_[0]); }

{
  package World;

  sub echonl {
    my $self = shift;

    foreach (@_) {
      $self->echo("$_\n");
    }
  }


  sub sendecho {
    my $self = shift;

    my $prevstate = $self->commandecho();
    $self->commandecho(1);
    $self->send(@_);
    $self->commandecho($prevstate);
  }


  sub sendnoecho {
    my $self = shift;

    my $prevstate = $self->commandecho();
    $self->commandecho(0);
    $self->send(@_);
    $self->commandecho($prevstate);
  }


  sub mlsend {
    my $self = shift;

    if (scalar @_ == 0) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'mlsend'));
      return;
    }

    my $args = shift;
    if ($args->{initialtext} && ref($args->{initialtext}) ne 'ARRAY') {
      $args->{initialtext} = [ split(/\n/, $args->{initialtext}) ];
    }

    if ($args->{finaltext} && ref($args->{finaltext}) ne 'ARRAY') {
      $args->{finaltext} = [ split(/\n/, $args->{finaltext}) ];
    }

    return $self->_mlsend($args);
  }


  sub sendlines {
    my $self = shift;

    if (scalar @_ == 0) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'sendlines'));
      return;
    }

    my $args = { initialtext => $_[0] };
    if (scalar @_ >= 2) {
      $args->{delay} = $_[1];
    }
    if (scalar @_ >= 3) {
      $args->{linesatime} = $_[2];
    }

    return $self->mlsend($args);
  }


  sub sendfile {
    my $self = shift;

    if (scalar @_ == 0) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'sendfile'));
      return;
    }

    my $args = { file => $_[0] };
    if (scalar @_ >= 2) {
      $args->{delay} = $_[1];
    }
    if (scalar @_ >= 3) {
      $args->{linesatime} = $_[2];
    }

    return $self->_mlsend($args);
  }


  sub path {
    my $self = shift;
    my $path = $_[0];
    if (!$path) {
      return;
    }

    my ($num, $cmd, @cmdlist);

    # Splits the string in groups formed by an optional number
    # followed by the command and adds that to the list.
    do {
      $path =~ /(\d*)([[:alpha:]]|\{[^}]+\})/;
      $num = $1;
      $cmd = $2;

      $path = substr($path, $+[0]);

      $num = 1 if ($num eq '');
      $cmd =~ s/\{(.+)\}/$1/;

      my $i;
      for ($i = 0; $i < $num; ++$i) {
        push(@cmdlist, $cmd);
      }
    } until ($path eq '');

    # Sends the commands
    $self->sendlines(\@cmdlist);
  }


  sub prev {
    my $self = shift;
    my $num = $_[0];

    if (!defined $num) {
      $num = 1;
    }

    $self->next(-$num);
  }


  sub gag {
    my $self    = shift;
    my $pattern = shift;

    if (!$pattern) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'gag'));
      return;
    }

    $self->trigger({ pattern => $pattern, gag => 1 });
  }


  sub _listobject {
    my $self = shift;
    my ($target, $function, $objnum, $extrafirstarg) = @_;

    if (defined($target)) {
      if ($target =~ /^\d+$/) {
        if ($extrafirstarg) {
          eval("\$self->$function(\$extrafirstarg, $target)");
        } else {
          eval("\$self->$function($target)");
        }
      } else {
        my @found = $self->_getobjectnumber($target,
                                            $objnum,
                                            0,
                                            $extrafirstarg);
        if (scalar @found == 0) {
          $self->echonl($self->_getnosuchmsg($objnum));
        } else {
          foreach (@found) {
            if ($extrafirstarg) {
              eval("\$self->$function(\$extrafirstarg, $_)");
            } else {
              eval("\$self->$function($_)");
            }
          }
        }
      }
    } else {
      if ($extrafirstarg) {
        eval("\$self->$function(\$extrafirstarg)");
      } else {
        eval("\$self->$function()");
      }
    }
  }


  sub listtrigger {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_listobject($_, '_listtrigger', 0);
      }
    } else {
      $self->_listobject(undef,  '_listtrigger', 0);
    }
  }


  sub listalias {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_listobject($_, '_listalias', 1);
      }
    } else {
      $self->_listobject(undef, '_listalias', 1);
    }
  }


  sub listmacro {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_listobject($_, '_listmacro', 2);
      }
    } else {
      $self->_listobject(undef, '_listmacro', 2);
    }
  }


  sub listtimer {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_listobject($_, '_listtimer', 3);
      }
    } else {
      $self->_listobject(undef, '_listtimer', 3);
    }
  }


  sub listhook {
    my $self  = shift;
    my $event = shift;

    if (@_) {
      foreach (@_) {
        $self->_listobject($_, '_listhook', 4, $event);
      }
    } else {
      $self->_listobject(undef, '_listhook', 4, $event);
    }
  }


  sub gettriggernumber {
    my $self = shift;

    if (scalar @_ != 1) {
      $self->echonl(sprintf(_('%s: Wrong number of arguments.'),
                            'gettriggernumber'));
      return undef;
    }
    my $name = shift;

    my @found = $self->_getobjectnumber($name,
                                        0,    # Trigger
                                        0,    # By name
                                        undef);
    return @found;
  }


  sub getaliasnumber {
    my $self = shift;

    if (scalar @_ != 1) {
      $self->echonl(sprintf(_('%s: Wrong number of arguments.'),
                            'getaliasnumber'));
      return undef;
    }
    my $name = shift;

    my @found = $self->_getobjectnumber($name,
                                        1,    # Alias
                                        0,    # By name
                                        undef);
    return @found;
  }


  sub getmacronumber {
    my $self = shift;

    if (scalar @_ != 1) {
      $self->echonl(sprintf(_('%s: Wrong number of arguments.'),
                            'getmacronumber'));
      return undef;
    }
    my $name = shift;

    my @found = $self->_getobjectnumber($name,
                                        2,    # Macro
                                        0,    # By name
                                        undef);
    return @found;
  }


  sub gettimernumber {
    my $self = shift;

    if (scalar @_ != 1) {
      $self->echonl(sprintf(_('%s: Wrong number of arguments.'),
                            'gettimerrnumber'));
      return undef;
    }
    my $name = shift;

    my @found = $self->_getobjectnumber($name,
                                        3,    # Timer
                                        0,    # By name
                                        undef);
    return @found;
  }


  sub gethooknumber {
    my $self = shift;

    if (scalar @_ != 2) {
      $self->echonl(sprintf(_('%s: Wrong number of arguments.'),
                            'gethooknumber'));
      return undef;
    }
    my $event = shift;
    my $name  = shift;

    my @found = $self->_getobjectnumber($name,
                                        4,    # Hook
                                        0,    # By name
                                        $event);
    return @found;
  }


  sub _delobject {
    my $self    = shift;
    my ($target,
        $function,
        $objnum,
        $extrafirstarg) = @_;
    my $deleted = 0;

    if (!defined($target)) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), $function));
      return undef;
    }

    if ($target =~ /^\d+$/) {
      if ($extrafirstarg) {
        eval("\$deleted += \$self->_$function(\$extrafirstarg, $target)");
      } else {
        eval("\$deleted += \$self->_$function($target)");
      }
    } else {
      my @found = reverse($self->_getobjectnumber($target,
                                                  $objnum,
                                                  0,
                                                  $extrafirstarg));
      foreach (@found) {
        if ($extrafirstarg) {
          eval("\$deleted += \$self->_$function(\$extrafirstarg, $_)");
        } else {
          eval("\$deleted += \$self->_$function($_)");
        }
      }
    }

    unless ($self->{SILENT}) {
      $self->echonl($self->_getdelmsg($deleted, $objnum));
    }

    return $deleted;
  }

  sub deltrigger {
    my $self = shift;
    return $self->_delobject($_[0], 'deltrigger', 0);
  }


  sub delalias {
    my $self = shift;
    return $self->_delobject($_[0], 'delalias', 1);
  }


  sub delmacro {
    my $self = shift;
    return $self->_delobject($_[0], 'delmacro', 2);
  }


  sub deltimer {
    my $self = shift;
    return $self->_delobject($_[0], 'deltimer', 3);
  }


  sub delhook {
    my $self  = shift;
    return $self->_delobject($_[1], 'delhook', 4, $_[0]);
  }


  sub _moveobject {
    my $self    = shift;
    my ($old,
        $new_pos,
        $function,
        $objnum,
        $extrafirstarg) = @_;
    my $ok;

    if (defined($old)) {
      if ($old =~ /^\d+$/) {
        if ($extrafirstarg) {
          eval("\$ok = \$self->_$function(\$extrafirstarg, $old, $new_pos)");
        } else {
          eval("\$ok = \$self->_$function($old, $new_pos)");
        }
        if (!$ok) {
          $self->echonl($self->_getnosuchmsg($objnum));
        }
      } else {
        my @found = $self->_getobjectnumber($old,
                                            $objnum,
                                            0,
                                            $extrafirstarg);
        if (scalar @found == 0) {
          $self->echonl($self->_getnosuchmsg($objnum));
        } else {
          if ($extrafirstarg) {
            eval("\$ok += \$self->_$function(\$extrafirstarg, $found[0], $new_pos)");
          } else {
            eval("\$ok += \$self->_$function($found[0], $new_pos)");
          }

          unless ($self->{SILENT}) {
            $self->echonl(_('Successfully moved.'));
          }
        }
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), $function));
    }
  }


  sub movetrigger {
    my $self = shift;
    $self->_moveobject($_[0], $_[1], 'movetrigger', 0);
  }


  sub movealias {
    my $self = shift;
    $self->_moveobject($_[0], $_[1], 'movealias', 1);
  }


  sub movemacro {
    my $self = shift;
    $self->_moveobject($_[0], $_[1], 'movemacro', 2);
  }


  sub movetimer {
    my $self = shift;
    $self->_moveobject($_[0], $_[1], 'movetimer', 3);
  }


  sub movehook {
    my $self  = shift;
    my $event = shift;
    $self->_moveobject($_[0], $_[1], 'movehook', 4, $event);
  }


  sub _enadisobject {
    my $self    = shift;
    my ($target,
        $newvalue,
        $function,
        $objnumber,
        $extrafirstarg) = @_;

    if ($target =~ /^\d+$/) {
      if ($extrafirstarg) {
        eval("\$self->$function(\$extrafirstarg, $target, { enabled => $newvalue })");
      } else {
        eval("\$self->$function($target, { enabled => $newvalue })");
      }
    } else {
      my @found = $self->_getobjectnumber($target,
                                          $objnumber,
                                          0,
                                          $extrafirstarg);
      if (scalar @found == 0) {
        $self->echonl($self->_getnosuchmsg($objnumber));
      } else {
        foreach (@found) {
          if ($extrafirstarg) {
            eval("\$self->$function(\$extrafirstarg, $_, { enabled => $newvalue })");
          } else {
            eval("\$self->$function($_, { enabled => $newvalue })");
          }
        }
      }
    }
  }


  sub enatrigger {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 1, 'trigger', 0);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'enatrigger'));
    }
  }


  sub distrigger {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 0, 'trigger', 0);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'distrigger'));
    }
  }


  sub enaalias {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 1, 'alias', 1);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'enaalias'));
    }
  }


  sub disalias {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 0, 'alias', 1);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'disalias'));
    }
  }


  sub enamacro {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 1, 'macro', 2);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'enamacro'));
    }
  }


  sub dismacro {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 0, 'macro', 2);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'dismacro'));
    }
  }


  sub enatimer {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 1, 'timer', 3);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'enatimer'));
    }
  }


  sub distimer {
    my $self = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 0, 'timer', 3);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'distimer'));
    }
  }


  sub enahook {
    my $self  = shift;
    my $event = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 1, 'hook', 4, $event);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'enahook'));
    }
  }


  sub dishook {
    my $self  = shift;
    my $event = shift;

    if (@_) {
      foreach (@_) {
        $self->_enadisobject($_, 0, 'hook', 4, $event);
      }
    } else {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'dishook'));
    }
  }


  sub silentenatrigger {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->enatrigger(@_);
    $self->{SILENT} = $silent;
  }


  sub silentdistrigger {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->distrigger(@_);
    $self->{SILENT} = $silent;
  }


  sub silentenaalias {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->enaalias(@_);
    $self->{SILENT} = $silent;
  }


  sub silentdisalias {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->disalias(@_);
    $self->{SILENT} = $silent;
  }


  sub silentenamacro {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->enamacro(@_);
    $self->{SILENT} = $silent;
  }


  sub silentdismacro {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->dismacro(@_);
    $self->{SILENT} = $silent;
  }


  sub silentenatimer {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->enatimer(@_);
    $self->{SILENT} = $silent;
  }


  sub silentdistimer {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->distimer(@_);
    $self->{SILENT} = $silent;
  }


  sub silentenahook {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->enahook(@_);
    $self->{SILENT} = $silent;
  }


  sub silentdishook {
    my $self = shift;

    my $silent = $self->{SILENT};
    $self->{SILENT} = 1;
    $self->dishook(@_);
    $self->{SILENT} = $silent;
  }


  sub triggerenabled {
    my $self    = shift;
    my $trigger = shift;

    if (!defined($trigger)) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'triggerenabled'));
      return undef;
    }
    return $self->_objectenabled($trigger, 0);
  }


  sub aliasenabled {
    my $self  = shift;
    my $alias = shift;

    if (!defined($alias)) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'aliasenabled'));
      return undef;
    }
    return $self->_objectenabled($alias, 1);
  }


  sub macroenabled {
    my $self  = shift;
    my $macro = shift;

    if (!defined($macro)) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'macroenabled'));
      return undef;
    }
    return $self->_objectenabled($macro, 2);
  }


  sub timerenabled {
    my $self  = shift;
    my $timer = shift;

    if (!defined($timer)) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'timerenabled'));
      return undef;
    }
    return $self->_objectenabled($timer, 3);
  }


  sub hookenabled {
    my $self  = shift;
    my ($event, $hook) = @_;

    if (!defined($event) || !defined($hook)) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'hookenabled'));
      return undef;
    }
    return $self->_objectenabled($hook, 4, $event);
  }


  sub _getdelmsg {
    my $self = shift;
    my ($deleted, $objnum) = @_;

    my $msg;
    if (!$deleted) {
      return $self->_getnosuchmsg($objnum);
    } else {
      my $objname;
      if ($objnum == 0) { $objname = ::ngettext('trigger', 'triggers', $deleted); }
      if ($objnum == 1) { $objname = ::ngettext('alias',   'aliases',  $deleted); }
      if ($objnum == 2) { $objname = ::ngettext('macro',   'macros',   $deleted); }
      if ($objnum == 3) { $objname = ::ngettext('timer',   'timers',   $deleted); }
      if ($objnum == 4) { $objname = ::ngettext('hook',    'hooks',    $deleted); }

      return sprintf(::ngettext('One %1$s deleted',
                                '%2$d %1$s deleted', $deleted),
                     $objname, $deleted);
    }
  }

  sub _getnosuchmsg {
    my $self = shift;
    my ($objnum) = @_;

    if ($objnum == 0) { return _('No such trigger'); }
    if ($objnum == 1) { return _('No such alias');   }
    if ($objnum == 2) { return _('No such macro');   }
    if ($objnum == 3) { return _('No such timer');   }
    if ($objnum == 4) { return _('No such hook');    }
  }


  sub writetolog {
    my $self = shift;

    foreach my $line (@_) {
      $self->_writetolog(::stripansi(::stripcolorize($line)));
    }
  }


  sub echonlandlog {
    my $self = shift;

    foreach my $line (@_) {
      $self->echonl($line);
      $self->writetolog($line);
    }
  }


  sub _getplugininfo {
    my $self = shift;
    my $file = shift;

    unless (open(PLUGIN, "<$file")) {
      return _('Could not open file.');
    }

    my $nameline   = <PLUGIN>;
    my $versline   = <PLUGIN>;
    my $descline   = <PLUGIN>;
    my $authorline = <PLUGIN>;
    chomp($nameline);
    chomp($versline);
    chomp($descline);
    chomp($authorline);

    my ($name, $vers, $desc, $author);
    my $fail = 0;
    if ($nameline !~ /^package ([a-zA-Z_][a-zA-Z_0-9]*);/) {
      $fail = 1;
    } else {
      $name = $1;
    }
    if ($versline !~ /^#: Version: (.*)$/) {
      $fail = 1;
    } else {
      $vers = $1;
    }
    if ($descline !~ /^#: Description: (.*)$/) {
      $fail = 1;
    } else {
      $desc = $1;
    }
    if ($authorline !~ /^#: Author: (.*)$/) {
      $author = _('Unknown');
    } else {
      $author = $1;
    }
    CORE::close(PLUGIN);

    if ($fail) {
      return _('Invalid plugin file.');
    } else {
      return (undef, $name, $vers, $desc, $author);
    }
  }


  sub _loadplugin {
    my $self = shift;
    my $file = shift;

    unless(-r $file) {
      foreach (@::PLUGINDIRS) {
        if (-r $_ . "/$file") {
          $file = $_ . "/$file";
          last;
        } elsif (-r $_ . "/$file.pl") {
          $file = $_ . "/$file.pl";
          last;
        }
      }
    }

    my ($error, $name, $vers, $desc, $author) = $self->_getplugininfo($file);
    if ($error) {
      return $error;
    }

    my $prevversion = $self->getpluginversion($name);
    if ($prevversion) {
      if ($prevversion eq $vers) {
        return _('Plugin already loaded. Try enabling it.');
      } else {
        return sprintf(_('Version %s of this plugin is already loaded.'),
                       $prevversion);
      }
    }

    $self->_startplugin($name, $vers, $desc, $author);
    do $file;

    if ($@) {
      $self->_stopplugin(0);
      return sprintf(_('Error in plugin file: %s'), $@);
    } else {
      $self->_stopplugin(1);
      return (undef, $name);
    }
  }


  sub loadplugin {
    my $self = shift;

    if ($self->{WORLDPTR} != $::world->{WORLDPTR}) {
      $::world->echonl(_('Cannot load a plugin in another world.'));
      return 0;
    }

    unless (defined($_[0])) {
      $self->echonl(_('Plugin not specified'));
      return 0;
    }

    my ($error, $name) = $self->_loadplugin($_[0]);
    if ($error) {
      $self->echonl($error);
      $self->echonl(_('Plugin not loaded.'));
      return 0;
    } else {
      $self->echonl(sprintf(_("Plugin '%s' loaded."), $name));
      return 1;
    }
  }


  sub requireplugin {
    my $self = shift;
    my $plugin = $_[0];

    die _('Plugin not specified') unless defined($plugin);

    my $prevversion = $self->getpluginversion($plugin);
    return 1 if ($prevversion); # This plugin is already loaded

    my ($error, $dummy) = $self->_loadplugin($plugin);
    die sprintf(_('Could not load plugin %s'), $plugin) if ($error);

    return 1;
  }


  sub _enadisplugin {
    my $self       = shift;
    my ($pluginname, $newval) = @_;

    my $pluginver = $self->getpluginversion($pluginname);
    unless ($pluginver) {
      $self->echonl(_('Plugin not loaded.'));
      return 0;
    }

    # Triggers
    my @found = $self->_getobjectnumber($pluginname, 0, 1);
    foreach (@found) {
      $self->trigger($_, { enabled => $newval });
    }

    # Aliases
    @found = $self->_getobjectnumber($pluginname, 1, 1);
    foreach (@found) {
      $self->alias($_, { enabled => $newval });
    }

    # Macros
    @found = $self->_getobjectnumber($pluginname, 2, 1);
    foreach (@found) {
      $self->macro($_, { enabled => $newval });
    }

    # Timers
    @found = $self->_getobjectnumber($pluginname, 3, 1);
    foreach (@found) {
      $self->timer($_, { enabled => $newval });
    }

    # Hooks
    foreach my $hookname (qw/OnConnect OnReceivedText OnSentCommand
                             OnGetFocus OnLoseFocus/) {
      @found = $self->_getobjectnumber($pluginname, 4, 1, $hookname);
      foreach (@found) {
        $self->hook($hookname, $_, { enabled => $newval });
      }
    }

    return 1;
  }


  sub enaplugin {
    my $self = shift;
    my $pluginname = shift;
    my $prevstate;

    $prevstate = $self->{SILENT};
    $self->{SILENT} = 1;

    if (!$pluginname) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'enaplugin'));
      return;
    }

    # See if there is a custom enabler
    eval {
      if ($pluginname->ENABLE()) {
        $self->_markpluginenadis($pluginname, 1);
      }
    };
    # But if not, use the standard one, which enables all items
    if ($@) {
      if ($self->_enadisplugin($pluginname, 1)) {
        unless ($prevstate) {
          $self->echonl(_('Plugin enabled.'));
        }
        $self->_markpluginenadis($pluginname, 1);
      }
    }

    $self->{SILENT} = $prevstate;
  }


  sub displugin {
    my $self = shift;
    my $pluginname = shift;
    my $prevstate;

    $prevstate = $self->{SILENT};
    $self->{SILENT} = 1;

    if (!$pluginname) {
      $self->echonl(sprintf(_('%s: Too few arguments.'), 'displugin'));
      return;
    }

    # See if there is a custom disaabler
    eval {
      if ($pluginname->DISABLE()) {
        $self->_markpluginenadis($pluginname, 0);
      }
    };
    # But if not, use the standard one, which disables all items
    if ($@) {
      if ($self->_enadisplugin($pluginname, 0)) {
        unless ($prevstate) {
          $self->echonl(_('Plugin disabled.'));
        }
        $self->_markpluginenadis($pluginname, 0);
      }
    }

    $self->{SILENT} = $prevstate;
  }


  sub sendserverdata {
    my $self = shift;
    my ($data) = @_;

    my $protocol = $data->{'protocol'};
    my $converted;
    if ($protocol eq "GMCP") {
      $converted = ::_preparegmcpdata($data);
    } elsif ($protocol eq "MSDP") {
      $converted = ::_preparemsdpdata($data);
    } else {
      $self->echonl(_('Unknown protocol'));
      return;
    }

   $self->_sendserverdata($protocol, $converted);
  }


  sub sendmsdpcommand {
    my $self = shift;

    my $msdp = MSDP->new();
    my $converted = $msdp->encode_command(@_);

   $self->_sendserverdata('MSDP', $converted);
  }
}


sub colorize {
  my %colorsubst = ( "&0",     "\e[0m",     # Reset everything
                     "&x",     "\e[0;30m",  # Set foreground colors
                     "&r",     "\e[0;31m",
                     "&g",     "\e[0;32m",
                     "&O",     "\e[0;33m",
                     "&b",     "\e[0;34m",
                     "&p",     "\e[0;35m",
                     "&c",     "\e[0;36m",
                     "&w",     "\e[0;37m",
                     "&z",     "\e[1;30m",
                     "&d",     "\e[39m",    # Set default foreground color
                     "&R",     "\e[1;31m",  # Set bold foreground color
                     "&G",     "\e[1;32m",
                     "&Y",     "\e[1;33m",
                     "&B",     "\e[1;34m",
                     "&P",     "\e[1;35m",
                     "&C",     "\e[1;36m",
                     "&W",     "\e[1;37m",
                     "&D",     "\e[1;39m",  # Set bold default fore color
                     "\\^x",   "\e[40m",    # Set background colors
                     "\\^r",   "\e[41m",
                     "\\^g",   "\e[42m",
                     "\\^O",   "\e[43m",
                     "\\^b",   "\e[44m",
                     "\\^p",   "\e[45m",
                     "\\^c",   "\e[46m",
                     "\\^w",   "\e[47m",
                     "\\^d",   "\e[49m",    # Set default background color
                     "\\^z",   "\e[5;40m",  # Set light background colors
                     "\\^R",   "\e[5;41m",
                     "\\^G",   "\e[5;42m",
                     "\\^Y",   "\e[5;43m",
                     "\\^B",   "\e[5;44m",
                     "\\^P",   "\e[5;45m",
                     "\\^C",   "\e[5;46m",
                     "\\^W",   "\e[5;47m",
                     "\\^D",   "\e[5;49m",  # Set light default back color
                     "&_",     "\e[4m",     # Underline on
                     "&=",     "\e[21m",    # Double underline on
                     "\\^_",   "\e[24m",    # All underline off
                     "&\\|",   "\e[9m",     # Strikethrough on
                     "\\^\\|", "\e[29m",    # Strikethrough off
                     "&/",     "\e[3m",     # Italics on
                     "\\^/",   "\e[23m",    # Italics off
                     "&i",     "\e[7m",     # Reverse video on
                     "\\^i",   "\e[27m",    # Inverse video off
                     "&h",     "\e[8m",     # Hidden mode on
                     "\\^h",   "\e[28m",    # Hidden mode off
                   );

  my $str = $_[0];

  if (!$str) {
    return "";
  }

  # Escape
  $str =~ s#&&#\x{99}\x{100}\x{101}\x{102}\x{105}\x{106}#g;
  $str =~ s#\^\^#\x{106}\x{105}\x{102}\x{101}\x{100}\x{99}#g;

  $str =~ s{(&|\^)([0-5])([0-5])([0-5])X}
           {"\e[" .
            ($1 eq '&' ? '38' : '48') .
            ";5;" .
            (($2*36 + $3*6 + $4) + 16) .
            "m"}eg;
  $str =~ s{(&|\^)([0-9]|[01][0-9]|2[0-3])G}
           {"\e[" .
            ($1 eq '&' ? '38' : '48') .
            ';5;' .
            ($2 + 232) .
            'm'}eg;

  foreach my $i (keys %colorsubst) {
    $str =~ s/$i/$colorsubst{$i}/g;
  }

  # Unescape
  $str =~ s#\x{99}\x{100}\x{101}\x{102}\x{105}\x{106}#&#g;
  $str =~ s#\x{106}\x{105}\x{102}\x{101}\x{100}\x{99}#\^#g;

  return $str;
}


sub stripcolorize {
  my $string = shift;

  if (!$string) {
    return '';
  }

  $string =~ s/(&|^)[dxrgObpcwzRGYBPCW]//g;

  return $string;
}


sub __sethelpfile {
  $__helpfile = $_[0];
}


sub help {
  my ($topic) = @_;

  $topic ||= "index";
  $topic =~ s/\$/\\\$/;

  open(HFILE, "<$__helpfile") or die(_('Cannot open help file.'));

  my $outputting = 0;
  while (<HFILE>) {
    chomp;
    if (!$outputting) {
      next if ($_ !~ "\\[$topic\\]");
      $outputting = 1;
    } else {
      if ($_ =~ /^\[/) {
        close(HFILE);
        return;
      }

      if ($_ =~ /^[&^]/) {
        $::world->echonl(::colorize($_));
      } else {
        $::world->echonl($_);
      }
    }
  }

  close(HFILE);
  $::world->echonl(_('Help topic not found.'));
}


sub _parseserverdata {
  if (scalar @_ < 1) {
    $::world->echonl(sprintf(_('%s: This is an internal function, do not call directly.'), '_parseserverdata'));
    return;
  }

  # Clear previous data
  %::server_data = ();
  $::server_data{'protocol'} = $_[0];

  if ($::server_data{'protocol'} eq "GMCP") {
    _parsegmcpserverdata();
  } elsif ($::server_data{'protocol'} eq "MSDP") {
    _parsemsdpserverdata();
  } else {
    $::server_data{'error'} = "Unknown protocol";
    $::server_data{'data'} = $::_serverdata;
  }
}


sub _parsegmcpserverdata {
  my ($namespace, $data) = split(/\s+/, $::_serverdata, 2);
  my ($p1, $p2, $p3) = split(/\./, $namespace, 3);

  if (defined $p3) {
    $::server_data{'package'}    = $p1;
    $::server_data{'subpackage'} = $p2;
    $::server_data{'message'}    = $p3;
  } else {
    $::server_data{'package'}    = $p1;
    $::server_data{'message'}    = $p2;
  }

  if (defined $data) {
    eval {
      my $json = JSON->new->allow_nonref;

      $::server_data{'data'} = $json->decode($data);
      1;
    } or do {
      $::server_data{'error'} = $@;
      $::server_data{'data'} = $::_serverdata;
    }
  }
}


sub _parsemsdpserverdata {
  eval {
    my $msdp = MSDP->new();
    $::server_data{'data'} = $msdp->decode($::_serverdata);
    1;
  } or do {
    $::server_data{'error'} = $@;
    $::server_data{'data'} = $::_serverdata;
  }
}


sub _preparegmcpdata {
  my ($data) = @_;

  my $converted = $data->{'package'};
  if (defined $data->{'subpackage'}) {
    $converted .= ".";
    $converted .= $data->{'subpackage'};
  }
  if (defined $data->{'message'}) {
    $converted .= ".";
    $converted .= $data->{'message'};
  }

  if (exists $data->{'data'}) {
    my $json = JSON->new->allow_nonref;
    $converted .= " ";
    $converted .= $json->encode($data->{'data'});
  }

  return $converted;
}


sub _preparemsdpdata {
  my ($data) = @_;

  my $msdp = MSDP->new();
  return $msdp->encode($data->{'data'});
}
