/**
    console event handling and command execution

    Copyright (c) 2020-2023 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#ifndef _BSD_SOURCE
#define _BSD_SOURCE /* usleep */
#endif

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "sssl.h"
#include "file.h"
#include "keygen.h"
#include "network.h"
#include "mainline.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "limit.h"
#include "proxy.h"
#include "proxies.h"
#include "nat.h"
#include "client.h"
#include "msg.h"
#include "xfer.h"
#include "audio.h"
#include "api.h"
#include "console.h"

#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>

#include <sys/stat.h>

#ifndef _WIN32
#include <unistd.h>
#elif ! defined(S_ISREG)
#define S_ISREG(mode) ((mode) & (_S_IFREG))
#endif

#define CONSOLE_MAX_ARGS 20

static simtype console_handlers_table; /* keyed by command name, value is command index + 1 */

static simnumber console_ping_id;
static simtype console_entropy_string;

static const char *console_convert_error (simnumber error) {
  return convert_error (error == (int) error ? (int) error : SIM_NO_ERROR, true);
}

static const char *sim_console_get_error (int error, char output[SIM_SIZE_ERROR]) {
  return sim_error_get_message (error, 0, true, output);
}

static void sim_console_log_array (int level, const simtype array,
                                   const char *prefix, const char *string, const char *suffix) {
  unsigned i;
  LOG_PRINT_ANY_ (level, "%s%s%s", prefix, string, suffix);
  for (i = 1; i <= array.len; i++)
    LOG_PRINT_ANY_ (level, " %s", array.arr[i].str);
  LOG_PRINT_ANY_ (level, "\n");
}

static void console_handle_contact (simnumber id, const simtype event) {
  simnumber err = table_get_number (event, SIM_EVENT_CONTACT);
  if (err == SIM_OK) {
    LOG_PRINT_NOTE_ ("* %s CONTACT\n", contact_get_nick (id, NULL));
  } else
    LOG_PRINT_NOTE_ ("* %s ERROR (%s)\n", contact_get_nick (id, NULL), console_convert_error (err));
}

static void console_handle_status (simnumber id, const simtype event) {
  simtype status = table_get_type_number (event, CONTACT_KEY_STATUS), line = table_get_string (event, CONTACT_KEY_LINE);
  simtype edit = table_get_type_number (event, CONTACT_KEY_EDIT), nick = table_get_string (event, CONTACT_KEY_OWN_NICK);
  simtype flags = table_get_type_number (event, CONTACT_KEY_FLAGS), array;
  if (flags.typ != SIMNIL && param_get_number ("rights.typing") <= 0) {
    if ((array = sim_convert_flags_to_strings (flags.num, SIM_ARRAY (contact_right_names))).len) {
      LOG_PRINT_ (SIM_LOG_NOTE,
                  sim_console_log_array (SIM_LOG_NOTE, array, "* ", contact_get_nick (id, NULL), " RIGHTS"));
    } else
      LOG_PRINT_NOTE_ ("* %s RIGHTS none\n", contact_get_nick (id, NULL));
    array_free (array);
  }
  if (status.typ != SIMNIL) {
    const char *newnick = id == CONTACT_ID_TEST ? CONTACT_VIP_TEST : contact_get_nick (id ? id : CONTACT_ID_SELF, NULL);
    if (status.num == SIM_STATUS_INVISIBLE) {
      LOG_PRINT_NOTE_ ("* %s LOGOFF (gone)\n", newnick);
    } else if (status.num == SIM_STATUS_OFF) {
      LOG_PRINT_NOTE_ ("* %s LOGOFF (offline)\n", newnick);
    } else if (status.num == SIM_STATUS_ON) {
      LOG_PRINT_NOTE_ ("* %s LOGON (online)\n", newnick);
    } else
      LOG_PRINT_NOTE_ ("* %s LOGON (%s)\n", newnick, CONTACT_LOOKUP_STATUS_NAME (status.num));
  }
  if (nick.typ != SIMNIL)
    LOG_PRINT_NOTE_ ("* %s NICK %s\n", nick.str, contact_get_nick (id, NULL));
  if (line.typ != SIMNIL)
    LOG_PRINT_NOTE_ ("* %s LINE %.*s\n", contact_get_nick (id, NULL), line.len, line.str);
  if (edit.typ != SIMNIL)
    LOG_PRINT_NOTE_ ("* %s EDIT %lld\n", contact_get_nick (id, NULL), edit.num);
}

static void console_handle_msg (simnumber id, const simtype event) {
  LOG_PRINT_NOTE_ ("%s: %s\n", contact_get_nick (id, NULL), table_get_string (event, SIM_CMD_MSG_TEXT).str);
}

static void console_handle_edit (simnumber id, const simtype event) {
  simtype text = table_get_string (event, SIM_CMD_MSG_TEXT);
  LOG_PRINT_NOTE_ ("%lld. %s: %s\n", table_get_number (event, SIM_EVENT_EDIT), contact_get_nick (id, NULL), text.str);
}

static void console_handle_sent (simnumber id, const simtype event) {
  unsigned i;
  const char *nick = contact_get_nick (CONTACT_ID_SELF, NULL);
  simtype indexes = table_get_array_number (event, SIM_EVENT_MSG), text;
  simnumber t;
  for (i = 1; i <= indexes.len; i++)
    if ((text = msg_get_text (id, (unsigned) indexes.arr[i].num, &t)).typ == SIMPOINTER) {
      LOG_PRINT_NOTE_ ("%s: %s\n", nick, text.str);
    } else if (! SIM_STRING_CHECK_DIFF_CONST (text.ptr, "FILE ")) {
      text.str += SIM_STRING_GET_LENGTH_CONST ("FILE ");
      if (t >= 0) {
        LOG_PRINT_NOTE_ ("* %s %s <%lld:%02d:%02d>\n", nick, text.str, t / 3600, (int) (t / 60 % 60), (int) (t % 60));
      } else
        LOG_PRINT_NOTE_ ("* %s %s\n", nick, text.str);
    }
}

static void console_handle_audio (simnumber id, const simtype event) {
  simtype oldstate = table_get_string (event, SIM_EVENT_AUDIO);
  simtype newstate = table_get_string (event, CONTACT_KEY_AUDIO);
  if (! string_check_diff (newstate, CONTACT_AUDIO_INCOMING)) {
    LOG_PRINT_NOTE_ ("* %s CALLING\n", contact_get_nick (id, NULL));
  } else if (! string_check_diff (newstate, CONTACT_AUDIO_RINGING)) {
    LOG_PRINT_NOTE_ ("* %s RINGING\n", contact_get_nick (id, NULL));
  } else if (! string_check_diff (newstate, CONTACT_AUDIO_TALKING))
    if (! string_check_diff (oldstate, CONTACT_AUDIO_OUTGOING))
      LOG_PRINT_NOTE_ ("* %s ANSWERED\n", contact_get_nick (id, NULL));
}

static void console_handle_speech (simnumber id, const simtype event) {
  if (table_get_number (event, SIM_EVENT_SPEECH_PROBABILITY) == SIM_EVENT_SPEECH_END)
    LOG_PRINT_NOTE_ ("* SPEECH (%s)\n", console_convert_error (table_get_number (event, SIM_EVENT_SPEECH)));
}

static void console_handle_keygen (simnumber id, const simtype event) {
  simnumber err = table_get_number (event, SIM_EVENT_KEYGEN), sec;
  if ((sec = table_get_number (event, SIM_EVENT_KEYGEN_TIME)) == 0) {
    simtype addr = table_get_string (event, SIM_EVENT_KEYGEN_ADDRESS);
    if (! id) {
      LOG_PRINT_NOTE_ ("* KEYGEN (%s)\n", console_convert_error (err));
    } else if (err != SIM_OK) {
      LOG_PRINT_NOTE_ ("* %020llu KEYGEN %s (%s)\n", id, addr.str, console_convert_error (err));
    } else
      LOG_PRINT_NOTE_ ("* %020llu KEYGEN %s\n", id, addr.str);
  } else
    LOG_PRINT_NOTE_ ("* KEYGEN up to %lld seconds\n", sec);
}

static void console_handle_history (simnumber id, const simtype event) {
  if (table_get_number (event, SIM_EVENT_HISTORY) && log_protect_ (NULL, SIM_LOG_NOTE)) {
    simtype text = table_get_string (event, SIM_CMD_MSG_TEXT);
    char *arg = NULL;
    if (table_get_number (event, SIM_CMD_MSG_STATUS) != SIM_MSG_INCOMING)
      id = CONTACT_ID_SELF;
    if (! SIM_STRING_CHECK_DIFF_CONST (text.ptr, "CALL ")) {
      arg = strchr ((char *) (text.str += SIM_STRING_GET_LENGTH_CONST ("CALL ")), ' ');
    } else if (! SIM_STRING_CHECK_DIFF_CONST (text.ptr, "FILE ")) {
      if (SIM_STRING_CHECK_DIFF_CONST ((char *) (text.str += SIM_STRING_GET_LENGTH_CONST ("FILE ")), "SEND "))
        arg = strchr (text.ptr, ' ');
      if (! arg && id == CONTACT_ID_SELF)
        id = 0;
    }
    if (arg) {
      char *s = strchr (arg + 1, ' ');
      const char *nick = contact_get_nick (id, NULL);
      *arg++ = 0;
      LOG_PRINT_NOTE_ ("* %s %s (%s)%s", nick, text.str, console_convert_error (atoi (arg)), s ? s : "");
      *--arg = ' ';
    } else if (id)
      LOG_PRINT_NOTE_ ("* %s %s", contact_get_nick (id, NULL), text.str);
    if (id) {
      if (table_get_number (event, SIM_CMD_MSG_RECEIVED)) {
        simnumber t = table_get_number (event, SIM_CMD_MSG_TIME);
        if (! t)
          t = table_get_number (event, SIM_CMD_MSG_RECEIVED);
        if ((t = time (NULL) - t) >= 0)
          LOG_PRINT_NOTE_ (" <%lld:%02d:%02d>", t / 3600, (int) (t / 60 % 60), (int) (t % 60));
      }
      LOG_PRINT_NOTE_ ("\n");
    }
    log_unprotect_ ();
  }
}

static void console_handle_net (simnumber id, const simtype event) {
  simtype type = table_get_string (event, SIM_EVENT_NET), key, val;
  if (! string_check_diff (type, SIM_EVENT_NET_PONG) && console_ping_id && id == console_ping_id) {
    simtype pong = table_get_type_number (event, SIM_EVENT_NET_PONG);
    if (pong.typ != SIMNIL) {
      LOG_PRINT_NOTE_ ("* %s PING %lld ms\n", contact_get_nick (id, NULL), pong.num);
    } else
      LOG_PRINT_NOTE_ ("* %s PING\n", contact_get_nick (id, NULL));
    console_ping_id = 0;
  } else if (! string_check_diff (type, SIM_EVENT_NET_STATUS)) {
    simnumber err = table_get_number (event, SIM_EVENT_NET_STATUS);
    LOG_PRINT_NOTE_ ("* DHT %s (%lld)\n", err > 0 ? "up" : "down", err);
  } else if (! string_check_diff (type, SIM_EVENT_NET_LIMIT)) {
    LOG_PRINT_NOTE_ ("* LIMIT ERROR (%s)\n", console_convert_error (table_get_number (event, SIM_EVENT_NET_LIMIT)));
  } else if (log_protect_ (NULL, SIM_LOG_NOTE)) {
    if (! string_check_diff (type, SIM_EVENT_NET_DEVICE)) {
      simwalker ctx;
      for (table_walk_first (&ctx, event); (val = table_walk_next (&ctx, &key)).typ != SIMNIL;)
        if ((val.typ == SIMPOINTER || val.typ == SIMSTRING) && string_check_diff (key, SIM_EVENT_NET))
          LOG_PRINT_NOTE_ ("* DEVICE %s (%s)\n", key.str, val.str);
    } else if (! string_check_diff (type, SIM_EVENT_NET_CONNECT)) {
      simnumber err = table_get_number (event, SIM_EVENT_NET_CONNECT);
      simtype sndcipher = table_get_string (event, SIM_EVENT_NET_CONNECT_ENCRYPT);
      simtype rcvcipher = table_get_string (event, SIM_EVENT_NET_CONNECT_DECRYPT);
      if (err == SIM_OK)
        LOG_PRINT_NOTE_ ("* %s CONNECTED (%s:%s)\n", contact_get_nick (id, NULL), rcvcipher.str, sndcipher.str);
    } else if (! string_check_diff (type, SIM_EVENT_NET_DISCONNECT)) {
      simnumber err = table_get_number (event, SIM_EVENT_NET_DISCONNECT);
      if (err != SIM_OK) {
        LOG_PRINT_NOTE_ ("* %s DISCONNECT", contact_get_nick (id, NULL));
        if (err == SIM_SOCKET_END || err == SIM_SERVER_TIMEOUT) {
          LOG_PRINT_NOTE_ (err == SIM_SOCKET_END ? " (EOF)" : " (TIMEOUT)");
        } else
          LOG_PRINT_NOTE_ (" (%s)", console_convert_error (err));
        LOG_PRINT_NOTE_ ("\n");
      } else
        LOG_PRINT_NOTE_ ("* %s DISCONNECTED\n", contact_get_nick (id, NULL));
    } else if (! string_check_diff (type, SIM_EVENT_NET_TRAVERSE)) {
      simnumber err = table_get_number (event, SIM_EVENT_NET_TRAVERSE);
      if (err != SIM_NAT_TRAVERSE_ATTEMPT && err != SIM_NAT_REVERSE_FINISHED) {
        simtype status = table_get_string (event, SIM_EVENT_NET_TRAVERSE_STATUS);
        LOG_PRINT_NOTE_ ("* %s TRAVERSE%s", contact_get_nick (id, NULL), status.str);
        if (err != SIM_OK)
          LOG_PRINT_NOTE_ (" (%s)", console_convert_error (err));
        LOG_PRINT_NOTE_ ("\n");
      }
    }
    log_unprotect_ ();
  }
}

static void console_handle_error (simnumber id, const simtype event) {
  simtype type = table_get_string (event, SIM_EVENT_ERROR), val = table_get_key (event, type), name;
  simnumber err = val.typ == SIMNUMBER ? val.num : 0;
  if (! string_check_diff (type, SIM_EVENT_ERROR_CIPHER)) {
    if (val.typ == SIMSTRING || val.typ == SIMPOINTER)
      LOG_PRINT_NOTE_ ("* %s CIPHER (%s)\n", contact_get_nick (id, NULL), val.str);
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_SYSTEM)) {
    if (val.typ == SIMSTRING || val.typ == SIMPOINTER)
      LOG_PRINT_NOTE_ ("* %s OS (%s)\n", contact_get_nick (id, NULL), val.str);
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_INSTALL)) {
    LOG_PRINT_NOTE_ ("* INIT ERROR (%s)\n", console_convert_error (err));
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_SOCKS)) {
    LOG_PRINT_NOTE_ ("* SOCKS ERROR (%s)\n", console_convert_error (err));
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_PROTOCOL)) {
    LOG_PRINT_NOTE_ ("* %s ERROR (%s)\n", contact_get_nick (id, NULL), console_convert_error (err));
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_KEY)) {
    LOG_PRINT_NOTE_ ("* %s ERROR (%s)\n", contact_get_nick (id, NULL), console_convert_error (SIM_CRYPT_BAD_KEY));
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_XFER) && err != SIM_OK) {
    if ((name = table_get_string (event, SIM_EVENT_ERROR_XFER_NAME)).typ != SIMNIL) {
      LOG_PRINT_NOTE_ ("* %s XFER (%s) %s\n", contact_get_nick (id, NULL), console_convert_error (err), name.str);
    } else
      LOG_PRINT_NOTE_ ("* %s XFER (%s)\n", contact_get_nick (id, NULL), console_convert_error (err));
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_AUDIO)) {
    LOG_PRINT_NOTE_ ("* %s AUDIO (%s)\n", id ? contact_get_nick (id, NULL) : "ERROR", console_convert_error (err));
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_INIT)) {
    name = table_get_string (event, SIM_EVENT_ERROR_INIT_NAME);
    LOG_PRINT_NOTE_ ("* %s INIT %s (%s) %s\n", id ? contact_get_nick (id, NULL) : "ERROR",
                     table_get_string (event, SIM_EVENT_ERROR_INIT_TYPE).str, console_convert_error (err), name.str);
  } else if (! string_check_diff (type, SIM_EVENT_ERROR_INIT_LOCAL)) {
    LOG_PRINT_NOTE_ ("* LOCAL ERROR (%s)\n", console_convert_error (err));
  } else {
    if (string_check_diff (type, SIM_EVENT_ERROR_FILE_CREATE))
      if (string_check_diff (type, SIM_EVENT_ERROR_FILE_OPEN))
        if (string_check_diff (type, SIM_EVENT_ERROR_FILE_CLOSE))
          if (string_check_diff (type, SIM_EVENT_ERROR_FILE_LOAD))
            if (string_check_diff (type, SIM_EVENT_ERROR_FILE_SAVE))
              if (string_check_diff (type, SIM_EVENT_ERROR_FILE_WRITE))
                if (string_check_diff (type, SIM_EVENT_ERROR_FILE_DELETE))
                  if (string_check_diff (type, SIM_EVENT_ERROR_FILE_WIPE))
                    if (string_check_diff (type, SIM_EVENT_ERROR_FILE_RENAME))
                      if (string_check_diff (type, SIM_EVENT_ERROR_FILE_COPY))
                        return;
    LOG_PRINT_NOTE_ ("* %s ERROR (file-%s): %s\n", contact_get_nick (id, NULL), type.str, console_convert_error (err));
  }
}

static int sim_audio_device_init_ (void) {
  int err;
  PTH_PROTECT_UNPROTECT_ (err = audio_init_ (SIM_STATUS_INVISIBLE));
  return err;
}

static simbool sim_audio_device_check_exists__ (void) {
  simtype contact = sim_contact_get_ (CONTACT_ID_TEST, 0);
  simnumber status = contact.typ != SIMNIL ? table_get_number (contact, CONTACT_KEY_STATUS) : SIM_STATUS_OFF;
  sim_contact_free_ (contact);
  return status == SIM_STATUS_ON;
}

static char *sim_console_parse_arg (char **arg, simbool quote) {
  char *next = *arg;
  while (next && *next == ' ')
    next++;
  if (next && *next) {
    int end = quote && (*next == '"' || *next == '\'') ? *next++ : ' ';
    if ((next = strchr (*arg = next, end)) != NULL)
      for (*next++ = 0; *next == ' '; next++) {}
  } else
    *arg = next = NULL;
  return next;
}

static int sim_console_parse_args (char *args[CONSOLE_MAX_ARGS], char *arg, simbool quote) {
  int i = -1, j = 0;
  while (++i < CONSOLE_MAX_ARGS) {
    args[i] = arg;
    if ((arg = sim_console_parse_arg (&args[i], quote)) != NULL)
      ++j;
  }
  return args[i - 1] ? 0 : j;
}

#define param_check_exists(param) (param_get_limits (param, NULL, NULL, NULL, NULL) == SIM_OK)

int console_set_param (const char *param, int permanent) {
  char *args[CONSOLE_MAX_ARGS], errbuf[SIM_SIZE_ERROR], *last = NULL;
  int argc = sim_console_parse_args (args, strcpy (errbuf, param ? param : ""), false), ret = -1, l = errno = 0;
  if (args[0] && args[1]) {
    char *s = args[1], *p, *t;
    long num = strtol (s, &p, 0);
    int type = SIMSTRING;
    if (strcmp (args[0], "log"))
      type = ! strcmp (args[0], "sound.play") ? SIMNUMBER : param_get_type (args[0]);
    if ((type == SIMPOINTER || type == SIMSTRING || (type == SIMNIL && permanent >= SIM_PARAM_USER)) && param) {
      for (t = strchr (param, ' '); *t == ' '; t++) {}
      for (l = strlen (t); *t && t[l - 1] == ' '; t[--l] = 0) {}
      ret = param_set_string (args[0], pointer_new (t), permanent) == SIM_OK;
    } else if (type == SIMARRAY_STRING) {
      simtype array = array_new_strings (args[argc] && args[argc][0] ? argc : --argc);
      while (argc) {
        array.arr[argc] = string_copy (args[argc]);
        l = strlen (args[argc--]);
        if (! ret)
          args[argc + 1][l] = ' ';
        ret = 0;
      }
      ret = param_set_strings (args[0], array, permanent) == SIM_OK;
      array_free (array);
    } else if (type != SIMNUMBER) {
      LOG_PRINT_DEBUG_ ("SET bad %s: %s\n", type == SIMNIL ? "parameter" : "type", args[0]);
      return 0;
    } else if (! errno && *s && ! *p && num == (int) num) {
      ret = param_set_number (args[0], (int) num, permanent) == SIM_OK;
    } else {
      LOG_PRINT_DEBUG_ ("SET %s: bad number %s\n", args[0], args[1]);
      return 0;
    }
  } else if (! args[0] || param_get_type (args[0]) == SIMNIL) {
    simbool found = false;
    simwalker ctx;
    simtype key, match = pointer_new ("");
    const char *description;
    if (args[0] && strcmp (args[0], "*") && (match = pointer_new (args[0] + (*args[0] == '*'))).len)
      if (match.str[match.len - 1] != '.')
        match = string_cat (match.ptr, ".");
    while (table_walk_first (&ctx, param_table)) {
      char *next = NULL;
      while (table_walk_next (&ctx, &key).typ != SIMNIL)
        if (! match.len || ! strncmp (key.ptr, match.ptr, match.len))
          if (contact_get_nick (CONTACT_ID_SELF, NULL) || table_get_key_number (param_login_table, key) & 1) {
            found = true;
            if (param_check_exists (key.ptr))
              if (param_get_saved (key.ptr).typ != SIMNIL || ! args[0] || *args[0] != '*')
                if (! args[0] || strcmp (args[0], "*") || SIM_STRING_CHECK_DIFF_CONST (key.ptr, "log."))
                  if ((! last || strcmp (key.ptr, last) > 0) && (! next || strcmp (key.ptr, next) < 0))
                    next = key.ptr;
          }
      if ((last = next) == NULL)
        break;
      if (param) {
        LOG_PRINT_INFO_SIMTYPE_ (param_get_any (next), 0, "%c%s = ",
                                 ! param_check_exists (next) || param_get_saved (next).typ != SIMNIL ? '*' : ' ', next);
      } else if (param_get_limits (next, NULL, NULL, NULL, &description) == SIM_OK && description) {
        LOG_PRINT_INFO_ ("*%s\n", next);
        LOG_PRINT_INFO_ ("%s\n", description);
      }
    }
    string_free (match);
    if (! found)
      LOG_PRINT_DEBUG_ ("SET %s: bad parameter\n", args[0]);
    return -found;
  }
  if (strcmp (args[0], "log") && strcmp (args[0], "sound.play")) {
    const char *s = ! param_check_exists (args[0]) || param_get_saved (args[0]).typ != SIMNIL ? "*" : " ";
    if (param_get_type (args[0]) == SIMARRAY_STRING) {
      LOG_PRINT_ (SIM_LOG_INFO, sim_console_log_array (SIM_LOG_INFO, param_get_strings (args[0]), s, args[0], " ="));
    } else
      LOG_PRINT_INFO_SIMTYPE_ (param_get_any (args[0]), 0, "%s%s = ", s, args[0]);
  }
  if (! ret)
    LOG_PRINT_DEBUG_ ("SET %s: bad value %s\n", args[0], args[1]);
  return ret;
}

static int sim_console_exec_set_ (char *arg) {
  int err;
  if (! arg) {
    LOG_PRINT_XTRA_ ("usage: set [*][prefix[.]]\n");
    LOG_PRINT_XTRA_ ("usage: set <name> [value]\n");
    LOG_PRINT_XTRA_ ("usage: set log[.console] [level]\n");
    return SIM_OK;
  }
  sim_protect_ ();
  if ((err = console_set_param (arg, SIM_PARAM_PERMANENT)) <= 0) {
    err = ! err && *arg ? SIM_CONSOLE_BAD_PARAM : SIM_OK;
  } else if ((err = param_save ()) != SIM_OK)
    LOG_PRINT_DEBUG_ ("SAVE: %s\n", convert_error (err, false));
  sim_unprotect_ ();
  return err;
}

static int sim_console_exec_unset_ (char *arg) {
  int err;
  if (! arg || ! *arg) {
    LOG_PRINT_XTRA_ ("usage: unset <name>\n");
    return arg ? SIM_CONSOLE_NO_PARAM : SIM_OK;
  }
  sim_protect_ ();
  if ((err = param_unset (arg, SIM_PARAM_PERMANENT)) != SIM_OK) {
    LOG_PRINT_DEBUG_ ("UNSET %s: %s\n", arg, convert_error (err, false));
  } else if ((err = param_save ()) != SIM_OK) {
    LOG_PRINT_DEBUG_ ("SAVE: %s\n", convert_error (err, false));
  } else
    console_set_param (arg, SIM_PARAM_PERMANENT); /* LOG_PRINT_INFO_SIMTYPE_ (param_get_any (arg), 0, "%s = ", arg); */
  sim_unprotect_ ();
  return err;
}

static int sim_console_exec_dump_ (const char *arg, simnumber id) {
  int err = SIM_OK;
  char *s, errbuf[SIM_SIZE_ERROR];
  sim_protect_ ();
  if (! strcmp (arg, "queue") || ! SIM_STRING_CHECK_DIFF_CONST (arg, "queue ")) {
    LOG_PRINT_ (SIM_LOG_INFO, err = msg_log_queue (NULL, SIM_LOG_INFO, id));
    if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("%s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "stats")) {
    if ((s = strchr (arg + 5, ' ')) != NULL)
      *s = 0;
    LOG_PRINT_ (SIM_LOG_INFO, err = contact_log_stats (NULL, SIM_LOG_INFO, arg + 5, id));
    if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("%s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else if (! strcmp (arg, "dht")) {
    LOG_PRINT_ (SIM_LOG_INFO, main_log_dht (NULL, SIM_LOG_INFO));
#if defined(SIM_MEMORY_CHECK) && defined(HAVE_LIBPTH)
  } else if (! strcmp (arg, "tables")) {
    LOG_PRINT_ (SIM_LOG_INFO, memory_log_tables (NULL, SIM_LOG_INFO));
#endif
  } else if (! strcmp (arg, "searches")) {
    LOG_PRINT_ (SIM_LOG_INFO, main_log_searches (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "proxy")) {
    LOG_PRINT_ (SIM_LOG_INFO, proxy_log_customers (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "proxies")) {
    LOG_PRINT_ (SIM_LOG_INFO, proxy_log_lists (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "nat")) {
    LOG_PRINT_ (SIM_LOG_INFO, nat_log_traversers (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "probes")) {
    LOG_PRINT_ (SIM_LOG_INFO, client_log_probes (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "clients")) {
    LOG_PRINT_ (SIM_LOG_INFO, client_log_clients (NULL, SIM_LOG_INFO, false));
  } else if (! strcmp (arg, "sessions")) {
    LOG_PRINT_ (SIM_LOG_INFO, client_log_clients (NULL, SIM_LOG_INFO, true));
  } else if (! strcmp (arg, "sockets")) {
    LOG_PRINT_ (SIM_LOG_INFO, limit_log_sockets (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "threads")) {
    LOG_PRINT_ (SIM_LOG_INFO, pth_log_threads (NULL, SIM_LOG_INFO));
  } else if (! strcmp (arg, "sound")) {
    LOG_PRINT_ (SIM_LOG_INFO, audio_log_sounds (NULL, SIM_LOG_INFO));
  } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "keys ")) {
#ifdef DONOT_DEFINE
    if (! id) {
      LOG_PRINT_ (SIM_LOG_INFO, file_log_password (NULL, SIM_LOG_INFO, false));
      LOG_PRINT_ (SIM_LOG_INFO, file_log_password (NULL, SIM_LOG_INFO, true));
      /*LOG_PRINT_ (SIM_LOG_INFO, key_log_private (NULL, SIM_LOG_INFO));*/
    }
#endif
    if ((err = ssl_log_keys (NULL, SIM_LOG_INFO, id)) != SIM_OK)
      LOG_PRINT_DEBUG_ ("%s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "error ")) {
    if (! id) {
      for (id = -12345; id <= 0; id++) {
        const char *t = sim_error_peek ((int) id);
        if (SIM_STRING_CHECK_DIFF_CONST (t, "error number "))
          LOG_PRINT_INFO_ ("%lld: \"%s\"\n", id, t);
      }
    } else if (id == 0x100000000LL) {
      *(char *) (long) atoi ("0") = 0;
    } else
      LOG_PRINT_INFO_ ("%s\n", sim_console_get_error ((int) id, errbuf));
  } else if (strcmp (arg, "devices")) {
#if defined(SIM_MEMORY_CHECK) && defined(HAVE_LIBPTH)
    LOG_PRINT_XTRA_ ("usage: dump tables      \tshow hash tables\n");
#endif
    LOG_PRINT_XTRA_ ("usage: dump contacts    \tshow list of contacts\n");
    LOG_PRINT_XTRA_ ("usage: dump xfers [nick]\tshow list of file transfers\n");
    LOG_PRINT_XTRA_ ("usage: dump stats [nick]\tshow data transfer statistics\n");
    LOG_PRINT_XTRA_ ("usage: dump queue [nick]\tshow message queues\n");
    LOG_PRINT_XTRA_ ("usage: dump dht         \tshow peer-to-peer nodes\n");
    LOG_PRINT_XTRA_ ("usage: dump searches    \tshow peer-to-peer searches\n");
    LOG_PRINT_XTRA_ ("usage: dump proxy       \tshow proxy connections\n");
    LOG_PRINT_XTRA_ ("usage: dump proxies     \tshow available proxies\n");
    LOG_PRINT_XTRA_ ("usage: dump nat         \tshow NAT traversal progress\n");
    LOG_PRINT_XTRA_ ("usage: dump probes      \tshow connection attempts\n");
    LOG_PRINT_XTRA_ ("usage: dump clients     \tshow current connections\n");
    LOG_PRINT_XTRA_ ("usage: dump sessions    \tshow currently used keys\n");
    LOG_PRINT_XTRA_ ("usage: dump sockets     \tshow number of open sockets\n");
    LOG_PRINT_XTRA_ ("usage: dump threads     \tshow currently running threads\n");
    LOG_PRINT_XTRA_ ("usage: dump keys <nick> \tshow permanent keys\n");
    LOG_PRINT_XTRA_ ("usage: dump error <code>\tshow message for error code\n");
    LOG_PRINT_XTRA_ ("usage: dump ciphers     \tshow preferred ciphers\n");
    LOG_PRINT_XTRA_ ("usage: dump params      \tshow configuration parameters\n");
    LOG_PRINT_XTRA_ ("usage: dump version     \tshow version numbers\n");
    LOG_PRINT_XTRA_ ("usage: dump devices     \tshow audio devices\n");
    LOG_PRINT_XTRA_ ("usage: dump audio       \tupdate list of audio devices\n");
    LOG_PRINT_XTRA_ ("usage: dump sound       \t[(start|stop|play) <sound>]\n");
    if (*arg)
      err = SIM_CONSOLE_BAD_PARAM;
  } else
    LOG_PRINT_ (SIM_LOG_INFO, audio_log_devices (NULL, SIM_LOG_INFO));
  sim_unprotect_ ();
  return err;
}

static int sim_console_exec_dump__ (char *arg) {
  int err = SIM_OK;
  unsigned i;
  char *args[CONSOLE_MAX_ARGS], errbuf[SIM_SIZE_ERROR], *nick;
  if (! arg)
    arg = "";
  if (! strcmp (arg, "contacts")) {
    simtype contacts = sim_contact_get_ (0, CONTACT_BIT_DEFAULT);
    for (i = 1; i <= contacts.len; i++) {
      simtype tmp = sim_contact_get_ (contacts.arr[i].num, CONTACT_BIT_LINE | CONTACT_BIT_VERSION);
      simtype ascii = sim_convert_type_to_json (tmp);
      sim_contact_free_ (tmp);
      LOG_PRINT_INFO_ ("%020llu:%s,\n", contacts.arr[i].num, ascii.str);
      string_free (ascii);
    }
    sim_contact_free_ (contacts);
  } else if (! strcmp (arg, "ciphers")) {
    simtype tmp = sim_list_info_ (SIM_INFO_BIT_CRYPT);
    LOG_PRINT_INFO_SIMTYPE_ (tmp, 0, " ");
    sim_list_free (tmp);
  } else if (! strcmp (arg, "params") || ! SIM_STRING_CHECK_DIFF_CONST (arg, "params ")) {
    if (strcmp (arg, "params help")) {
      simtype tmp = sim_param_get_ (strcmp (arg, "params") ? arg + SIM_STRING_GET_LENGTH_CONST ("params ") : NULL);
      LOG_PRINT_INFO_SIMTYPE_ (tmp, 0, " ");
      sim_param_free_ (tmp);
    } else
      console_set_param (NULL, SIM_PARAM_PERMANENT);
  } else if (! strcmp (arg, "version")) {
    simtype exe = sim_init_path (SIM_PATH_EXE), tmp = sim_list_versions ();
    LOG_PRINT_INFO_SIMTYPE_ (tmp, LOG_BIT_CONT, "%s ", exe.str);
    sim_list_free (tmp);
    string_free (exe);
    tmp = sim_init_path (SIM_PATH_USER);
    LOG_PRINT_INFO_ (" %s\n", tmp.typ != SIMNIL ? (char *) tmp.str : "");
    string_free (tmp);
  } else if (! strcmp (arg, "audio")) {
    unsigned bits = sim_init_bits (SIM_INIT_BIT_DEFAULT);
    simtype tmp;
    sim_init_bits (bits);
    if (! (bits & SIM_INIT_BIT_NOAUDIO) && (err = sim_audio_device_init_ ()) != SIM_OK)
      LOG_PRINT_DEBUG_ ("%s: %s\n", arg, sim_console_get_error (err, errbuf));
    tmp = sim_list_info_ (SIM_INFO_BIT_AUDIO);
    LOG_PRINT_INFO_SIMTYPE_ (tmp, 0, " ");
    sim_list_free (tmp);
  } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "sound") && strcmp (arg, "sound")) {
    int argc = sim_console_parse_args (args, strcpy (errbuf, arg), false);
    err = SIM_CONSOLE_BAD_PARAM;
    if (argc > 1 && ! strcmp (args[1], "start")) {
      err = sim_sound_start_ (args[2], 0);
    } else if (argc > 1 && ! strcmp (args[1], "play")) {
      err = sim_sound_start_ (args[2], 1);
    } else if (argc > 1 && ! strcmp (args[1], "stop"))
      err = sim_sound_stop_ (args[2]);
    if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("%s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "queue") || ! SIM_STRING_CHECK_DIFF_CONST (arg, "stats") ||
             ! SIM_STRING_CHECK_DIFF_CONST (arg, "xfers") ||
             ! SIM_STRING_CHECK_DIFF_CONST (arg, "keys") || ! SIM_STRING_CHECK_DIFF_CONST (arg, "error")) {
    for (nick = strchr (arg, ' '); nick && *nick == ' '; nick++) {}
    if (nick && *nick) {
      simnumber id = sim_contact_find_ (nick);
      if (! SIM_STRING_CHECK_DIFF_CONST (arg, "error")) {
        err = sim_console_exec_dump_ (arg, strcmp (nick, "null") ? atoi (nick) : 0x100000000LL);
      } else if (! id && SIM_STRING_CHECK_DIFF_CONST (arg, "xfers")) {
        err = sim_console_exec_dump_ (arg, strcmp (nick, CONTACT_VIP_TEST) ? CONTACT_ID_KEYGEN : CONTACT_ID_TEST);
      } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "xfers ")) {
        simtype xfers = sim_xfer_get_ (id, SIM_XFER_GET_RECEIVED | SIM_XFER_GET_SENT, SIM_XFER_BIT_DEFAULT);
        if (id) {
          simtype array = table_detach_array_number (xfers, SIM_CMD_XFER_SEND_HANDLE);
          for (i = 1; i <= array.len; i++) {
            simwalker ctx;
            simtype table = table_new (2), ascii, key, val;
            for (table_walk_first (&ctx, xfers); (val = table_walk_next (&ctx, &key)).typ != SIMNIL;) {
              val = val.typ == SIMARRAY_NUMBER ? val.arr[i] : pointer_new_len (val.arr[i].str, val.arr[i].len);
              table_add_key (table, pointer_new_len (key.str, key.len), val);
            }
            ascii = sim_convert_type_to_json (table);
            LOG_PRINT_INFO_ ("#%019lld:%s,\n", array.arr[i].num, ascii.str);
            string_free (ascii);
            table_free (table);
          }
          array_free (array);
        } else {
          err = SIM_CONTACT_UNKNOWN;
          LOG_PRINT_DEBUG_ ("%s: %s\n", arg, sim_console_get_error (err, errbuf));
        }
        sim_xfer_free_ (xfers);
      } else
        err = sim_console_exec_dump_ (arg, id);
    } else if (! strcmp (arg, "xfers")) {
      simtype contacts = sim_contact_get_ (0, CONTACT_BIT_DEFAULT);
      for (i = 2; i <= contacts.len; i++) {
        simtype info = sim_xfer_get_ (contacts.arr[i].num, SIM_XFER_GET_INFO, SIM_XFER_BIT_DEFAULT), ascii;
        if (! table_get_number (info, SIM_XFER_GET_INFO_PAUSE))
          table_delete (info, SIM_XFER_GET_INFO_PAUSE);
        if ((ascii = sim_convert_type_to_json (info)).len > 2) {
          simtype tmp = sim_contact_get_ (contacts.arr[i].num, 0);
          LOG_PRINT_INFO_ ("%s %s\n", ascii.str, table_get_string (tmp, CONTACT_KEY_NICK).str);
          sim_contact_free_ (tmp);
        }
        string_free (ascii);
        sim_xfer_free_ (info);
      }
      sim_contact_free_ (contacts);
    } else
      err = sim_console_exec_dump_ (arg, 0);
  } else
    err = sim_console_exec_dump_ (arg, 0);
  return err;
}

static int sim_console_exec_user_dump_ (int mode) {
  PTH_PROTECT_UNPROTECT_ (LOG_PRINT_ (SIM_LOG_INFO, contact_log_list (NULL, SIM_LOG_INFO, mode)));
  return SIM_OK;
}

static int sim_console_exec_user__ (char *arg) {
  int err = SIM_API_NO_RIGHT, mode = CONTACT_AUTH_ACCEPTED;
  simnumber id;
  char *args[CONSOLE_MAX_ARGS], errbuf[SIM_SIZE_ERROR];
  sim_console_parse_args (args, arg, true);
  id = ! args[0] || ! args[1] ? 0 : strcmp (args[1], CONTACT_VIP_TEST) ? sim_contact_find_ (args[1]) : CONTACT_ID_TEST;
  if (args[0] && ! strcmp (args[0], "add") && args[1]) {
    if (id) {
      err = sim_contact_set_ (id, CONTACT_KEY_AUTH, number_new (CONTACT_AUTH_ACCEPTED));
    } else
      err = sim_contact_add_ (args[1], &id);
    if (err != SIM_OK) {
      LOG_PRINT_DEBUG_ ("ADD %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    } else if (args[2]) {
      err = sim_contact_set_ (id, CONTACT_KEY_IP, pointer_new (strcmp (args[2], "none") ? args[2] : ""));
      if (err != SIM_OK)
        LOG_PRINT_DEBUG_ ("ADD %s %s: %s\n", args[1], args[2], sim_console_get_error (err, errbuf));
    }
    return err;
  }
  if (args[0] && ! strcmp (args[0], "forget") && args[1]) {
    if ((err = sim_contact_set_ (id, CONTACT_KEY_AUTH, number_new (CONTACT_AUTH_FORGET))) != SIM_OK)
      LOG_PRINT_DEBUG_ ("FORGET %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    return err;
  }
  if (args[0] && ! strcmp (args[0], "delete") && args[1]) {
    if ((err = sim_contact_set_ (id, CONTACT_KEY_AUTH, number_new (CONTACT_AUTH_DELETED))) != SIM_OK)
      LOG_PRINT_DEBUG_ ("DELETE %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    return err;
  }
  if (args[0] && ! strcmp (args[0], "block") && args[1]) {
    if ((err = sim_contact_set_ (id, CONTACT_KEY_AUTH, number_new (CONTACT_AUTH_BLOCKED))) != SIM_OK)
      LOG_PRINT_DEBUG_ ("BLOCK %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    return err;
  }
  if (args[0] && ! strcmp (args[0], "drop")) {
    err = SIM_CONTACT_UNKNOWN;
    if (id || ! args[1])
      err = sim_contact_set_ (id, CONTACT_KEY_AUTH, number_new (CONTACT_AUTH_DROP));
    if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("DROP %s: %s\n", args[1] ? args[1] : "", sim_console_get_error (err, errbuf));
    return err;
  }
  if (args[0] && (! strcmp (args[0], "search") || ! strcmp (args[0], "research")) && args[1]) {
    const char *error;
    if (id) {
      err = sim_contact_search_ (id, ! strcmp (args[0], "research"));
      error = err == MAIN_SEARCH_SKIP ? "skipped" : err != MAIN_SEARCH_NEW ? "failed" : NULL;
      err = SIM_OK;
    } else
      error = sim_console_get_error (err = SIM_CONTACT_UNKNOWN, errbuf);
    if (error)
      LOG_PRINT_DEBUG_ ("SEARCH %s: %s\n", args[1], error);
    return err;
  }
  if (args[0] && ! strcmp (args[0], "rights") && args[1]) {
    if (args[2]) {
      simnumber flags = sim_param_parse_flags ((const char **) args + 2, SIM_ARRAY (contact_right_names));
      if (flags)
        err = id ? sim_contact_set_ (id, CONTACT_KEY_FLAGS, number_new (flags)) : SIM_CONTACT_UNKNOWN;
      if (err != SIM_OK)
        LOG_PRINT_DEBUG_ ("RIGHTS %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    } else {
      simtype contact = id ? sim_contact_get_ (id, 0) : nil ();
      if (contact.typ != SIMNIL) {
        simunsigned flags = table_get_number (contact, CONTACT_KEY_FLAGS);
        simtype array = sim_convert_flags_to_strings (flags, SIM_ARRAY (contact_right_names));
        sim_contact_free_ (contact);
        LOG_PRINT_ (SIM_LOG_INFO, sim_console_log_array (SIM_LOG_INFO, array, "", args[1], ":"));
        array_free (array);
        err = SIM_OK;
      } else
        LOG_PRINT_DEBUG_ ("RIGHTS %s: %s\n", args[1], sim_console_get_error (SIM_CONTACT_UNKNOWN, errbuf));
    }
    return err;
  }
  if (args[0] && ! strcmp (args[0], "verify") && args[1]) {
    err = ! id ? SIM_CONTACT_UNKNOWN : sim_contact_set_ (id, CONTACT_KEY_VERIFY, pointer_new (args[2] ? args[2] : ""));
    if (err != SIM_OK) {
      LOG_PRINT_DEBUG_ ("VERIFY %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    } else
      LOG_PRINT_INFO_ ("%s: %s\n", args[1], args[2] ? "verified" : "unverified");
    return err;
  }
  if (args[0] && (! strcmp (args[0], "rename") || ! strcmp (args[0], "renameme")) && args[1]) {
    if (! strcmp (args[0], "rename")) {
      err = ! id ? SIM_CONTACT_UNKNOWN : sim_contact_set_ (id, CONTACT_KEY_NICK, pointer_new (args[2] ? args[2] : ""));
    } else
      err = sim_contact_set_ (0, CONTACT_KEY_NICK, pointer_new (args[1]));
    if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("RENAME %s: %s\n", args[1], sim_console_get_error (err, errbuf));
    return err;
  }
  err = SIM_OK;
  if (args[0]) {
    if (args[0][0] && ! args[0][1]) {
      switch (args[0][0]) {
        case '*': mode = CONTACT_LOG_SHORT; break;    /* all contacts */
        case '-': mode = CONTACT_AUTH_DELETED; break; /* only blocked and deleted */
        case '_': mode = CONTACT_AUTH_BLOCKED; break; /* only blocked */
        case '+': mode = CONTACT_AUTH_NEW; break;     /* only unaccepted */
        case '=': mode = CONTACT_LOG_LONG; break;     /* all with full details */
        default: arg = NULL, err = SIM_CONSOLE_BAD_PARAM;
      }
    } else
      arg = NULL, err = SIM_CONSOLE_BAD_PARAM;
  }
  if (arg)
    return sim_console_exec_user_dump_ (mode);
  LOG_PRINT_XTRA_ ("usage: user [*] | [-] | [_] | [+] | [=]\n");
  LOG_PRINT_XTRA_ ("usage: user add <id> [ip:port]\n");
  LOG_PRINT_XTRA_ ("usage: user block <nick>\n");
  LOG_PRINT_XTRA_ ("usage: user delete <nick>\n");
  LOG_PRINT_XTRA_ ("usage: user forget <nick>\n");
  LOG_PRINT_XTRA_ ("usage: user rename <nick> [nick]\n");
  LOG_PRINT_XTRA_ ("usage: user verify <nick> [code]\n");
  LOG_PRINT_XTRA_ ("usage: user rights <nick> [rights]\n");
  LOG_PRINT_XTRA_ ("       rights: audio-y audio-n echo-y echo-n xfer-y xfer-n\n");
  LOG_PRINT_XTRA_ ("       rights: utf-y utf-n typing-y typing-n edit-y edit-n type-y type-n\n");
  LOG_PRINT_XTRA_ ("       rights: history-y history-n sound-on-y sound-on-n sound-off-y sound-off-n\n");
  return err;
}

static void sim_console_exec_status_ip_ (void) {
  PTH_PROTECT_UNPROTECT_ (LOG_PRINT_ (SIM_LOG_INFO, network_log_status (NULL, SIM_LOG_INFO)));
}

static int sim_console_exec_status__ (char *arg) {
  int i = SIM_ARRAY_SIZE (contact_status_names), err = SIM_OK;
  char errbuf[SIM_SIZE_ERROR], *a = arg;
  const char *s = sim_console_parse_arg (&a, false);
  if (arg && ! *arg) {
    simtype contact = sim_contact_get_ (CONTACT_ID_SELF, CONTACT_BIT_LINE), line;
    sim_console_exec_status_ip_ ();
    if (contact.typ != SIMNIL && (line = table_get_string (contact, CONTACT_KEY_LINE)).len)
      LOG_PRINT_INFO_ ("'%.*s'\n", line.len, line.str);
    sim_contact_free_ (contact);
  } else if (! a || strcmp (a, "line")) {
    while (i-- && (! a || strcmp (a, contact_status_names[i]))) {}
    if (i >= 0) {
      err = sim_status_set_ (SIM_STATUS_MIN + i);
    } else if ((err = sim_status_exec_ (a, s)) == SIM_API_BAD_TYPE)
      arg = NULL;
  } else
    err = sim_contact_set_ (0, CONTACT_KEY_LINE, pointer_new (s ? s : ""));
  if (! arg) {
    LOG_PRINT_XTRA_ ("usage: status\n");
    LOG_PRINT_XTRA_ ("usage: status on\n");
    LOG_PRINT_XTRA_ ("usage: status off\n");
    LOG_PRINT_XTRA_ ("usage: status away\n");
    LOG_PRINT_XTRA_ ("usage: status busy\n");
    LOG_PRINT_XTRA_ ("usage: status invisible\n");
    LOG_PRINT_XTRA_ ("usage: status line [text]\n");
    err = a ? SIM_CONSOLE_BAD_PARAM : SIM_OK;
  } else if (err != SIM_OK)
    LOG_PRINT_DEBUG_ ("STATUS: %s\n", sim_console_get_error (err, errbuf));
  return err;
}

static int sim_console_exec_connect__ (char *arg) {
  int err = arg ? SIM_CONSOLE_NO_PARAM : SIM_OK;
  char errbuf[SIM_SIZE_ERROR];
  if (! arg || ! *arg) {
    LOG_PRINT_XTRA_ ("usage: connect <nick>\n");
  } else if ((err = sim_contact_connect_ (sim_contact_find_ (arg))) != SIM_OK)
    LOG_PRINT_DEBUG_ ("CONNECT %s: %s\n", arg, sim_console_get_error (err, errbuf));
  return err;
}

static int sim_console_exec_disconnect__ (char *arg) {
  int err = arg ? SIM_CONSOLE_NO_PARAM : SIM_OK;
  char errbuf[SIM_SIZE_ERROR];
  if (! arg || ! *arg) {
    LOG_PRINT_XTRA_ ("usage: disconnect <nick>\n");
  } else if ((err = sim_contact_disconnect_ (sim_contact_find_ (arg))) != SIM_OK)
    LOG_PRINT_DEBUG_ ("DISCONNECT %s: %s\n", arg, sim_console_get_error (err, errbuf));
  return err;
}

static int sim_console_exec_ping__ (char *arg) {
  int err = arg ? SIM_CONSOLE_NO_PARAM : SIM_OK, argc;
  if (arg && *arg) {
    char *args[CONSOLE_MAX_ARGS + 1], errbuf[SIM_SIZE_ERROR];
    args[CONSOLE_MAX_ARGS] = 0;
    sim_console_parse_args (args, arg, true);
    for (argc = 0; args[argc]; argc++) {
      const unsigned bits = SIM_PING_BIT_TCP | SIM_PING_BIT_CONNECT | SIM_PING_BIT_NAT;
      console_ping_id = sim_contact_find_ (args[argc]);
      err = sim_contact_ping_ (console_ping_id, SIM_CMD_PING_NUMBER_CONSOLE, bits);
      if (err != SIM_OK && err != SIM_NAT_TRAVERSE_ATTEMPT && err != SIM_NAT_TRAVERSE_QUIT) {
        console_ping_id = 0;
        LOG_PRINT_DEBUG_ ("PING %s: %s\n", args[argc], sim_console_get_error (err, errbuf));
      } else
        err = SIM_OK;
    }
  } else
    LOG_PRINT_XTRA_ ("usage: ping <nick>\n");
  return err;
}

static int sim_console_exec_msg__ (char *arg) {
  int err = ! arg ? SIM_OK : *arg ? SIM_CONSOLE_BAD_PARAM : SIM_CONSOLE_NO_PARAM;
  const char *text = sim_console_parse_arg (&arg, true);
  char errbuf[SIM_SIZE_ERROR];
  simunsigned idx;
  if (! text) {
    LOG_PRINT_XTRA_ ("usage: msg <nick> <message>\n");
  } else if ((err = sim_msg_send_utf_ (sim_contact_find_ (arg), string_copy (text), &idx)) != SIM_OK)
    LOG_PRINT_DEBUG_ ("MSG %s: %s\n", arg, sim_console_get_error (err, errbuf));
  return err;
}

static int sim_console_exec_edit__ (char *arg) {
  char *nick = NULL;
  long num = arg ? strtol (arg, &nick, 10) : 0;
  int err = ! arg ? SIM_OK : *arg ? SIM_CONSOLE_BAD_PARAM : SIM_CONSOLE_NO_PARAM;
  if (num > 0 && *nick == ' ') {
    const char *text = sim_console_parse_arg (&nick, true);
    char errbuf[SIM_SIZE_ERROR];
    err = sim_msg_edit_utf_ (sim_contact_find_ (nick), text ? string_copy (text) : pointer_new (""), num);
    if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("EDIT %s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else
    LOG_PRINT_XTRA_ ("usage: edit <number> <nick> [message]\n");
  return err;
}

static int sim_console_exec_remove__ (char *arg) {
  char *nick = NULL, errbuf[SIM_SIZE_ERROR];
  long num = arg ? strtol (arg, &nick, 10) : 0;
  int err = ! arg ? SIM_OK : *arg ? SIM_CONSOLE_BAD_PARAM : SIM_CONSOLE_NO_PARAM;
  if (num > 0 && *nick == ' ') {
    sim_console_parse_arg (&nick, true);
    if ((err = sim_msg_remove_ (sim_contact_find_ (nick), num)) != SIM_OK)
      LOG_PRINT_DEBUG_ ("REMOVE %s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else
    LOG_PRINT_XTRA_ ("usage: remove <number> <nick>\n");
  return err;
}

static int sim_console_exec_last__ (char *arg) {
  if (arg && *arg) {
    char *nick = NULL, errbuf[SIM_SIZE_ERROR], buf[SIM_SIZE_TIME];
    long num = strtol (arg, &nick, 0);
    simunsigned count, i = 1;
    if (*nick == ' ') {
      simnumber id;
      int err = SIM_CONTACT_UNKNOWN;
      while (*++nick == ' ') {}
      id = sim_contact_find_ (nick);
      if (num <= 0) {
        err = sim_msg_load_ (id, -num);
      } else if (id) {
        if ((count = sim_msg_count_ (id)) >= (unsigned long) num) {
          i = count - num + 1;
          count = num;
        }
        while (count--) {
          simtype msg = sim_msg_get_ (id, i++);
          simnumber status = table_get_number (msg, SIM_CMD_MSG_STATUS);
          if (status == SIM_MSG_INCOMING || table_get_string (msg, SIM_CMD_MSG_TEXT).typ != SIMNIL) {
            if (log_protect_ (NULL, SIM_LOG_INFO)) {
              if (status < SIM_MSG_INCOMING && status > SIM_MSG_DELIVERED) {
                static const char *console_msg_status_names[] = { "", "-", "?", "!", " ", " ", "+", "*" };
                LOG_PRINT_INFO_ ("%s", console_msg_status_names[status - SIM_MSG_DELIVERED]);
              }
              sim_convert_time_to_string (table_get_number (msg, SIM_CMD_MSG_TIME), buf);
              LOG_PRINT_INFO_ ("%llu. %s %s: ", i - 1, buf, table_get_string (msg, SIM_CMD_MSG_SENDER).str);
              LOG_PRINT_INFO_ ("%s\n", table_get_string (msg, SIM_CMD_MSG_TEXT).str);
              log_unprotect_ ();
            }
          }
          sim_msg_free_ (msg);
        }
        err = SIM_OK;
      }
      if (err != SIM_OK)
        LOG_PRINT_DEBUG_ ("LAST %s: %s\n", nick, sim_console_get_error (err, errbuf));
      return err;
    }
    if (! *nick) {
      int err = SIM_OK;
      if (num > 0) {
        if (sim_param_get_ ("file.log").num)
          err = sim_console_load_ (num);
        if ((count = log_count_ ()) >= (unsigned long) num) {
          i = count - num + 1;
          count = num;
        }
        while (count--) {
          simunsigned datetime = 0;
          const char *level = NULL;
          simtype line = log_get_string_ (i++, &datetime, &level);
          sim_convert_time_to_string (datetime, buf);
          if (level)
            LOG_PRINT_INFO_ ("%s %s %s\n", buf, level, line.str);
        }
      } else
        err = sim_console_load_ (-num);
      if (err != SIM_OK)
        LOG_PRINT_DEBUG_ ("LAST: %s\n", sim_console_get_error (err, errbuf));
      return err;
    }
  }
  LOG_PRINT_XTRA_ ("usage: last <number> [nick]\n");
  LOG_PRINT_XTRA_ ("  number > 0 to show last messages\n");
  LOG_PRINT_XTRA_ ("  number < 0 to load last messages\n");
  LOG_PRINT_XTRA_ ("  number = 0 to unload last messages\n");
  return ! arg ? SIM_OK : *arg ? SIM_CONSOLE_BAD_VALUE : SIM_CONSOLE_NO_PARAM;
}

static int sim_console_exec_send_list_ (simnumber id, simnumber handle) {
  int err = SIM_OK;
  PTH_PROTECT_UNPROTECT_ (LOG_PRINT_ (SIM_LOG_INFO, err = xfer_log_queue (NULL, SIM_LOG_INFO, id, handle)));
  return err;
}

static int sim_console_exec_send__ (char *arg) {
  int err = SIM_API_BAD_TYPE;
  simnumber id, handle;
  char *args[CONSOLE_MAX_ARGS], errbuf[SIM_SIZE_ERROR];
  sim_console_parse_args (args, arg, true);
  if (args[0] && ! strcmp (args[0], "list")) {
    const char *s = args[1] && args[1][0] == '#' ? args[1] : args[1] && args[2] ? args[2] : NULL;
    if (! s || ! sim_convert_string_to_simunsigned (s + 1, &handle))
      handle = 0;
    id = sim_contact_find_ (args[1]);
    err = id || ! args[1] || args[1][0] == '#' ? sim_console_exec_send_list_ (id, handle) : SIM_CONTACT_UNKNOWN;
  } else if (args[0] && ! strcmp (args[0], "file") && args[1] && args[2]) {
    err = sim_xfer_send_file_ (sim_contact_find_ (args[1]), pointer_new (args[2]), NULL);
  } else if ((id = sim_contact_find_ (args[0])) != 0 && args[1] && args[1][0] != '#' && ! args[2]) {
    err = sim_xfer_send_file_ (id, pointer_new (args[1]), NULL);
  } else if (! args[0] || strcmp (args[0], "files") || ! args[1] || ! args[2]) {
    if (args[0]) {
      if (! args[1] || (args[1][0] == '#' && sim_convert_string_to_simunsigned (args[1] + 1, &id))) {
        simtype contacts = sim_contact_get_ (0, CONTACT_BIT_DEFAULT);
        simbool ok = true, nok = true;
        unsigned i;
        for (i = 2; i <= contacts.len; i++)
          if ((err = sim_xfer_set_ (contacts.arr[i].num, id, args[0])) != SIM_OK) {
            ok = false;
            if (err != SIM_API_BAD_PARAM)
              break;
          } else
            nok = false;
        sim_contact_free_ (contacts);
        if (ok || ! nok)
          return SIM_OK;
      } else if (args[1] && (! args[2] || (args[2][0] == '#' && sim_convert_string_to_simunsigned (args[2] + 1, &id))))
        err = sim_xfer_set_ (sim_contact_find_ (args[1]), id, args[0]);
    }
    if (err == SIM_API_BAD_TYPE) {
      LOG_PRINT_XTRA_ ("usage: send list [nick] [#<number>]\n");
      LOG_PRINT_XTRA_ ("usage: send [file] <nick> <pathname>\n");
      LOG_PRINT_XTRA_ ("usage: send files <nick> <pathname>\n");
      LOG_PRINT_XTRA_ ("usage: send %s [nick] #1\n", SIM_XFER_TYPE_PAUSE);
      LOG_PRINT_XTRA_ ("usage: send %s [nick] [#<number>]\n", SIM_XFER_TYPE_PAUSE);
      LOG_PRINT_XTRA_ ("usage: send %s [nick] [#<number>]\n", SIM_XFER_TYPE_CANCEL);
      return ! arg ? SIM_OK : args[0] ? SIM_CONSOLE_BAD_PARAM : SIM_CONSOLE_NO_PARAM;
    }
  } else if ((id = sim_contact_find_ (args[1])) != 0) {
    void *dir = sim_system_dir_open (args[2]);
    if (dir) {
      const char *filename;
      while ((filename = sim_system_dir_read (dir)) != NULL) {
        simtype name = string_concat (args[2], FILE_DIRECTORY_SLASH, filename, NULL);
        struct stat buf;
        if (sim_file_lstat (name.ptr, &buf))
          break;
        if (S_ISREG (buf.st_mode) && (err = sim_xfer_send_file_ (id, name, NULL)) != SIM_OK)
          LOG_PRINT_DEBUG_ ("SEND %s: %s\n", name.str, sim_error_get_message (err, 0, false, errbuf));
        string_free (name);
      }
      err = errno;
      sim_system_dir_close (dir);
    } else
      err = errno ? errno : ENOENT;
  } else
    err = SIM_CONTACT_UNKNOWN;
  if (err != SIM_OK)
    LOG_PRINT_DEBUG_ ("SEND %s: %s\n", arg, sim_console_get_error (err, errbuf));
  return err;
}

static int sim_console_exec_call__ (char *arg) {
  int err = arg ? SIM_CONSOLE_NO_PARAM : SIM_OK;
  if (arg && *arg) {
    simnumber id = CONTACT_ID_KEYGEN;
    char errbuf[SIM_SIZE_ERROR];
    if (! SIM_STRING_CHECK_DIFF_CONST (arg, "KeyGen")) {
      simtype bits = number_new (0);
      sim_param_get_limits_ ("crypto.entropy", &bits, NULL, NULL, NULL);
      arg += SIM_STRING_GET_LENGTH_CONST ("KeyGen") - 1;
      while (*++arg == ' ') {}
      if (*arg)
        bits.num = atoi (arg);
      if ((err = sim_param_set_ ("crypto.entropy", bits, SIM_PARAM_TEMPORARY)) != SIM_OK) {
        LOG_PRINT_DEBUG_ ("SET %lld bits: %s\n", bits.num, sim_console_get_error (err, errbuf));
        return err;
      }
      arg = (char *) "";
    } else if (! strcmp (arg, CONTACT_VIP_TEST)) {
      if (sim_audio_device_check_exists__ ())
        id = CONTACT_ID_TEST;
      arg = (char *) "";
    }
    if ((err = *arg && (id = sim_contact_find_ (arg)) == 0 ? SIM_CONTACT_UNKNOWN : sim_audio_call_ (id)) != SIM_OK)
      LOG_PRINT_DEBUG_ ("CALL %s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else {
    LOG_PRINT_XTRA_ ("usage: call <nick>\n");
    LOG_PRINT_XTRA_ ("usage: call test\n");
    LOG_PRINT_XTRA_ ("usage: call KeyGen [seed-bits]\n");
  }
  return err;
}

static int sim_console_exec_hangup__ (char *arg) {
  int err = SIM_OK;
  if (arg) {
    simnumber id = 0;
    char errbuf[SIM_SIZE_ERROR];
    if (! strcmp (arg, CONTACT_VIP_TEST) || ! strcmp (arg, "KeyGen")) {
      id = ! strcmp (arg, CONTACT_VIP_TEST) && sim_audio_device_check_exists__ () ? CONTACT_ID_TEST : CONTACT_ID_KEYGEN;
      arg = (char *) "";
    }
    if ((err = *arg && (id = sim_contact_find_ (arg)) == 0 ? SIM_CONTACT_UNKNOWN : sim_audio_hangup_ (id)) != SIM_OK)
      LOG_PRINT_DEBUG_ ("HANGUP %s: %s\n", arg, sim_console_get_error (err, errbuf));
  } else
    LOG_PRINT_XTRA_ ("usage: hangup [nick]\n");
  return err;
}

static int sim_console_exec_passwd__ (char *arg) {
  int err = arg ? SIM_CONSOLE_NO_PARAM : SIM_OK;
  if (arg && *arg) {
    err = sim_key_set_password_ (strcmp (arg, "none") ? arg : (arg = (char *) ""), SIM_PASSWORD_BIT_OVERWRITE);
    if (err != SIM_OK) {
      char errbuf[SIM_SIZE_ERROR];
      LOG_PRINT_DEBUG_ ("PASSWD: %s\n", sim_console_get_error (err, errbuf));
    } else if (! *arg)
      LOG_PRINT_INFO_ ("PRIVATE KEY IS NOT PROTECTED BY A PASSWORD.\n");
  } else {
    LOG_PRINT_XTRA_ ("usage: passwd <new-password>\n");
    LOG_PRINT_XTRA_ ("usage: passwd none\n");
  }
  return err;
}

static simtype sim_console_exec_keygen_seed_ (unsigned type, const simtype buffer) {
  simtype seed, seed1, seed2;
  sim_protect_ ();
  seed1 = convert_seed_to_string (buffer, type, false);
  seed2 = convert_seed_to_string (buffer, type, true);
  seed = string_cat_len (seed1.ptr, seed1.len + 1, seed2.ptr, seed2.len);
  string_free (seed2);
  string_free (seed1);
  sim_unprotect_ ();
  return seed;
}

static int sim_console_exec_keygen__ (char *arg) {
  simtype seed;
  unsigned type;
  int err, keybits, seedbits;
  char *bits, errbuf[SIM_SIZE_ERROR];
  if (! arg || ! *arg) {
    LOG_PRINT_XTRA_ ("usage: keygen <seed>\n");
    LOG_PRINT_XTRA_ ("usage: keygen <seed-bits> <key-bits>\n");
    LOG_PRINT_XTRA_ ("usage: keygen <key-bits> [new-password]\n");
    LOG_PRINT_XTRA_ ("usage: keygen <key-bits> nosave\n");
    LOG_PRINT_XTRA_ ("usage: keygen <key-bits> noaudio\n");
    LOG_PRINT_XTRA_ ("recommended bits are 128/4096, 192/8192 and 256/16384\n");
    return arg ? SIM_CONSOLE_NO_PARAM : SIM_OK;
  }
  seed = pointer_new (arg);
  if ((err = sim_key_generate_ (&seed, nil (), 0, SIM_KEY_NONE)) == SIM_OK) {
    LOG_PRINT_INFO_ ("KEYGEN: Generating a private key from seed...\n");
    return SIM_OK;
  }
  if (sim_key_set_password_ (arg, SIM_PASSWORD_BIT_CHECK) == SIM_OK) {
    LOG_PRINT_DEBUG_ ("KEYGEN: %s\n", sim_console_get_error (err, errbuf));
    return err;
  }
  seed = nil ();
  if ((bits = strchr (arg, ' ')) != NULL)
    do {
      *bits++ = 0;
    } while (*bits == ' ');
  if ((seedbits = atoi (arg)) == 0 || ! bits || (keybits = atoi (bits)) == 0) {
    keybits = seedbits;
    seedbits = 0;
  } else if (seedbits < 128 || seedbits > SIM_MAX_SEED_BITS) {
    LOG_PRINT_DEBUG_ ("KEYGEN: seed bits must be between 128 and %d\n", SIM_MAX_SEED_BITS);
    return SIM_CONSOLE_BAD_VALUE;
  }
  for (type = SIM_KEY_BP512R1_RSA16K + 1; type && SSL_RSA_BITS_MIN << (type - 1) != keybits; type--) {}
  if (! type--) {
    if (seedbits || keybits) {
      LOG_PRINT_DEBUG_ ("KEYGEN: key bits must be 2048, 4096, 8192 or 16384\n");
      return SIM_CONSOLE_BAD_VALUE;
    }
  } else if (! seedbits) {
    if (! bits || ! *bits) {
      LOG_PRINT_INFO_ ("KEYGEN: GENERATING A PRIVATE KEY THAT WILL NOT BE PROTECTED BY A PASSWORD...\n");
      seed = pointer_new ("");
    } else if (! strcmp (bits, "noaudio")) {
      simtype buf = string_buffer_new ((type ? type + 1 : 2) << 3);
      if ((err = sim_random_get (buf.str, buf.len)) == SIM_OK)
        seed = sim_console_exec_keygen_seed_ (type, buf);
      string_buffer_free (buf);
      goto done;
    } else if (strcmp (bits, "nosave")) {
      LOG_PRINT_INFO_ ("KEYGEN: Generating a private key that will be protected by a password...\n");
      seed = pointer_new (bits);
    } else
      LOG_PRINT_INFO_ ("KEYGEN: Generating a private key in memory...\n");
    err = sim_key_generate_ (&seed, console_entropy_string, 0, type);
    STRING_FREE (&console_entropy_string, nil ());
  } else if ((err = sim_key_generate_ (&seed, console_entropy_string, seedbits, type)) == SIM_OK) {
    err = sim_key_generate_ (&seed, nil (), 0, SIM_KEY_NONE);
  done:
    LOG_PRINT_INFO_ ("KEYGEN: your secret key is:\n");
    LOG_PRINT_INFO_ ("%s\n", seed.str);
    LOG_PRINT_INFO_ ("\n");
    LOG_PRINT_INFO_ ("KEYGEN: your numeric secret key is:\n");
    LOG_PRINT_INFO_ ("%s\n", seed.str ? (char *) seed.str + strlen (seed.ptr) + 1 : "");
    LOG_PRINT_INFO_ ("\n");
    string_free (seed);
  }
  if (err != SIM_OK)
    LOG_PRINT_DEBUG_ ("KEYGEN: %s\n", sim_console_get_error (err, errbuf));
  return err;
}

static int sim_console_exec_quit (char *arg) {
  if (arg && ! *arg)
    return SIM_CONSOLE_QUIT_CMD;
  LOG_PRINT_XTRA_ ("usage: quit\n");
  return SIM_CONSOLE_BAD_PARAM;
}

static int sim_console_exec_uninit__ (char *arg) {
  int err = arg ? SIM_CONSOLE_BAD_PARAM : SIM_OK;
  if (arg && ! *arg) {
    char errbuf[SIM_SIZE_ERROR];
    if ((err = sim_exit_user_ ()) == SIM_API_INIT) {
      err = SIM_OK;
    } else if (err != SIM_OK)
      LOG_PRINT_DEBUG_ ("UNINIT: %s\n", sim_console_get_error (err, errbuf));
  } else
    LOG_PRINT_XTRA_ ("usage: uninit\n");
  return err;
}

static int sim_console_exec_init__ (char *arg) {
  int err = SIM_CONSOLE_BAD_PARAM;
  if (arg) {
    char errbuf[SIM_SIZE_ERROR];
    sim_console_exec_uninit__ ((char *) "");
    if ((err = sim_init_user_ (strcmp (arg, "none") ? arg : NULL)) != SIM_OK)
      LOG_PRINT_DEBUG_ ("INIT: %s\n", sim_console_get_error (err, errbuf));
  } else {
    LOG_PRINT_XTRA_ ("usage: init [secret-key]\n");
    LOG_PRINT_XTRA_ ("usage: init none\n");
  }
  return err;
}

static int sim_console_exec_sleep_ (char *arg) {
  int msec, err = 0;
  if (arg && (msec = atoi (arg)) > 0) {
    char errbuf[SIM_SIZE_ERROR];
#ifdef HAVE_LIBPTH
    if (msec >= 1000)
#ifdef _WIN32
      pth_sleep_ (msec / 1000);
#else
      err = pth_sleep_ (msec / 1000) ? EINTR : 0;
#endif
    if (! err && (msec %= 1000))
      err = pth_usleep_ (msec * 1000) ? errno : 0;
#elif defined(_WIN32)
    Sleep (msec);
#else
    if (msec >= 1000)
      err = sleep (msec / 1000) ? EINTR : 0;
    if (! err && (msec %= 1000))
#ifndef __ANDROID__
      err = usleep (msec * 1000) ? errno : 0;
#else
      usleep (msec * 1000);
#endif
#endif /* HAVE_LIBPTH */
    if (err)
      LOG_PRINT_DEBUG_ ("SLEEP: %s\n", sim_console_get_error (err, errbuf));
    return err;
  }
  LOG_PRINT_XTRA_ ("usage: sleep <milliseconds>\n");
  return ! arg ? SIM_OK : *arg ? SIM_CONSOLE_BAD_VALUE : SIM_CONSOLE_NO_PARAM;
}

static int sim_console_exec_help__ (char *arg);

static const struct {
  const char *name;
  int (*handler) (char *);
  const char *description;
} console_commands[] = {
  { "help", sim_console_exec_help__, "show help" },
  { "set", sim_console_exec_set_, "show or set configuration" },
  { "unset", sim_console_exec_unset_, "reset parameter to default " },
  { "dump", sim_console_exec_dump__, "show internal structures" },
  { "user", sim_console_exec_user__, "show or manipulate contacts" },
  { "status", sim_console_exec_status__, "set or show online status" },
  { "connect", sim_console_exec_connect__, "connect to contact" },
  { "disconnect", sim_console_exec_disconnect__, "disconnect from contact" },
  { "ping", sim_console_exec_ping__, "check if user is connected" },
  { "msg", sim_console_exec_msg__, "send message to contact" },
  { "edit", sim_console_exec_edit__, "edit sent message" },
  { "remove", sim_console_exec_remove__, "remove message from history" },
  { "last", sim_console_exec_last__, "show or load last messages" },
  { "send", sim_console_exec_send__, "send files or manage transfers" },
  { "call", sim_console_exec_call__, "call contact or test audio" },
  { "hangup", sim_console_exec_hangup__, "stop audio call" },
  { "passwd", sim_console_exec_passwd__, "encrypt secret key with password" },
  { "keygen", sim_console_exec_keygen__, "generate secret key" },
  { "quit", sim_console_exec_quit, "close the console" },
  { "uninit", sim_console_exec_uninit__, NULL },
  { "init", sim_console_exec_init__, NULL },
  { "sleep", sim_console_exec_sleep_, NULL }
};

static int sim_console_exec_help__ (char *arg) {
  unsigned i;
  int err = SIM_OK, min, max;
  char errbuf[SIM_SIZE_ERROR];
  if (! arg) {
    LOG_PRINT_XTRA_ ("usage: help [command]\n");
    LOG_PRINT_XTRA_ ("usage: help set [name]\n");
  } else if (! *arg) {
    LOG_PRINT_INFO_ ("for help on a specific command, use help <command>\n");
    LOG_PRINT_INFO_ ("\n");
    LOG_PRINT_INFO_ ("the following commands are available:\n");
    LOG_PRINT_INFO_ ("\n");
    for (i = 0; i < SIM_ARRAY_SIZE (console_commands); i++)
      if (console_commands[i].description)
        LOG_PRINT_INFO_ ("%-11s %s\n", console_commands[i].name, console_commands[i].description);
  } else if (! SIM_STRING_CHECK_DIFF_CONST (arg, "set ")) {
    simtype def;
    const char *description;
    for (; arg[3] == ' '; arg++) {}
    if ((err = sim_param_get_limits_ (arg + 3, &def, &min, &max, &description)) == SIM_OK) {
      if (description) {
        LOG_PRINT_INFO_ ("%s\n", description);
        LOG_PRINT_INFO_ ("\n");
      }
      LOG_PRINT_INFO_SIMTYPE_ (def, 0, "default = ");
      if (def.typ == SIMNUMBER) {
        LOG_PRINT_INFO_ ("minimal = %d\n", min);
        LOG_PRINT_INFO_ ("maximal = %d\n", max);
      } else if (def.typ != SIMPOINTER)
        array_free (def);
    } else if (! strcmp (arg + 3, "log")) {
      LOG_PRINT_XTRA_ ("usage: set log[.module] [level]\n");
      LOG_PRINT_XTRA_ ("       level: xtra debug info note warn error fatal\n");
    } else
      LOG_PRINT_DEBUG_ ("SET %s: %s\n", arg + 3, sim_console_get_error (err, errbuf));
  } else if ((i = (unsigned) table_get_number (console_handlers_table, arg)) == 0) {
    LOG_PRINT_DEBUG_ ("%s: %s\n", sim_console_get_error (SIM_CONSOLE_BAD_CMD, errbuf), arg);
    err = SIM_CONSOLE_BAD_PARAM;
  } else
    console_commands[i - 1].handler (NULL);
  return err;
}

int sim_console_exec__ (const char *command) {
  int err = SIM_OK;
  if (command) {
    char *arg, *cmd;
    simtype copy = string_copy (command);
    for (cmd = copy.ptr; *cmd == ' '; cmd++) {}
    if ((arg = strchr (cmd, ' ')) != NULL)
      for (; *arg == ' '; *arg++ = 0) {}
    if (*cmd) {
      unsigned i = (unsigned) table_get_number (console_handlers_table, cmd);
      err = i ? console_commands[i - 1].handler (arg ? arg : (char *) "") : SIM_CONSOLE_BAD_CMD;
    }
    string_free (copy);
  } else
    err = SIM_CONSOLE_BAD_CMD;
  return err;
}

void sim_key_set_entropy_ (const void *entropy, unsigned length) {
  sim_protect_ ();
  STRING_FREE (&console_entropy_string, string_copy_len (entropy, length));
  sim_unprotect_ ();
}

void console_init (void) {
  int i = SIM_ARRAY_SIZE (console_commands);
  console_handlers_table = table_new_const (82, NULL);
  for (; i--; table_add_number (console_handlers_table, console_commands[i].name, i + 1)) {}
  console_ping_id = 0;
  console_entropy_string = nil ();
  event_register (SIM_EVENT_CONTACT, console_handle_contact);
  event_register (SIM_EVENT_STATUS, console_handle_status);
  event_register (SIM_EVENT_MSG, console_handle_msg);
  event_register (SIM_EVENT_EDIT, console_handle_edit);
  event_register (SIM_EVENT_SENT, console_handle_sent);
  event_register (SIM_EVENT_AUDIO, console_handle_audio);
  event_register (SIM_EVENT_SPEECH, console_handle_speech);
  event_register (SIM_EVENT_KEYGEN, console_handle_keygen);
  event_register (SIM_EVENT_HISTORY, console_handle_history);
  event_register (SIM_EVENT_NET, console_handle_net);
  event_register (SIM_EVENT_ERROR, console_handle_error);
}

void console_uninit (void) {
  STRING_FREE (&console_entropy_string, nil ());
  TABLE_FREE (&console_handlers_table, nil ());
}
