/**
    command-line test program with batch capability

    Copyright (c) 2020-2022 The Creators of Simphone

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

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500 /* sigaction */
#endif

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

#include "simapi.h"
#if defined(SIM_TYPE_CHECKS) && ! SIM_TYPE_CHECKS
#include "table.h"
#endif

#include "file.h"
#include "socket.h"
#include "keygen.h"
#include "param.h"
#include "msg.h"
#include "audio.h"

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

#include <signal.h>

#ifdef _WIN32
static HANDLE log_console = INVALID_HANDLE_VALUE;
#endif

#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif

#ifndef STDERR_FILENO
#define STDERR_FILENO 2
#endif

#ifndef HAVE_LIBPTH
#undef PTH_UNPROTECT_PROTECT_
#define PTH_UNPROTECT_PROTECT_(CODE) (CODE)
#endif

#define SOUND_CALL 0
#define SOUND_RING 1
#define SOUND_CONNECT 2

static const char *sound_names[] = { "call", "ring", "connect" };
static int sound_counts[SIM_ARRAY_SIZE (sound_names)];
static simtype sound_id_table; /* keyed by contact id, value is SOUND_xxx + 1 */

static simnumber sound_play_ticks[] = { 0, 0, 0, 0, 0, 0 };
static const char *sound_play_delays[] = { "sound.delay.msg", "sound.delay.msg", "sound.delay.xfer",
                                           "sound.delay.xfer", "sound.delay.xfer", "sound.delay.xfer" };
static const char *sound_play_names[] = { "msg", "msgrecv", "receive", "received", "sent", "cancelled" };

#define SOUND_PLAY_MSG 0
#define SOUND_PLAY_MSGRECV 1
#define SOUND_PLAY_RECEIVE 2
#define SOUND_PLAY_RECEIVED 3
#define SOUND_PLAY_SENT 4
#define SOUND_PLAY_CANCELLED 5

static simbool signal_terminated_flag = false, signal_running_flag = false;
static const char *escape_command = "\x1b[1A", *login_user = "", *login_pass = "";
static const char *user_command = NULL, *login_command = NULL, *signal_command = NULL;
static int speed_test_factor = 1000000000;

static int set_params_length = 0;
static const char *set_params[1000];

static simnumber query_id = 0;
static char command_prompt[256] = "";

static simevent *old_contact, *old_status, *old_msg, *old_edit, *old_sent, *old_notsent;
static simevent *old_audio, *old_keygen, *old_history, *old_net, *old_error, *old_log;

static simbool sound_play (const char *sound, simbool single) {
  int err = SIM_OK;
  if (sim_status_get (NULL) != SIM_STATUS_BUSY) {
    simtype param = sim_string_cat ("sound.play.", sound), enabled = param_get_any (sim_get_pointer (param));
    sim_string_free (param);
    if (sim_get_type (enabled) == SIMNIL || sim_get_number (enabled) > 0)
      err = audio_start_sound (sound, single);
  }
  return err == SIM_OK;
}

static void sound_check_play (int sound) {
  simnumber tick = sim_get_tick ();
  if (tick && tick - sound_play_ticks[sound] >= param_get_number (sound_play_delays[sound]))
    sound_play (sound_play_names[sound], true);
  sound_play_ticks[sound] = tick;
}

static void sound_start (simnumber id, int sound) {
  if (! sim_table_get_key_number (sound_id_table, sim_pointer_new_length (&id, sizeof (id))))
    if (sound_play (sound_names[sound], false)) {
      sim_table_set_key_number (sound_id_table, sim_string_copy_length (&id, sizeof (id)), sound + 1);
      sound_counts[sound]++;
    }
}

static void sound_stop (simnumber id, int sound) {
  if (sim_table_get_key_number (sound_id_table, sim_pointer_new_length (&id, sizeof (id))) == sound + 1) {
    sim_table_delete_key (sound_id_table, sim_pointer_new_length (&id, sizeof (id)));
    if (! --sound_counts[sound])
      audio_stop_sound_ (sound_names[sound]);
  }
}

#ifndef _WIN32
static void signal_handle_interrupt (int sig) {
  signal_terminated_flag = true;
  fprintf (stderr, "\ncaught signal %d\n", sig);
}

static void signal_handle_user (int sig, siginfo_t *siginfo, void *context) {
  signal_running_flag = sig == SIGUSR1;
}
#endif

static void handle_contact (simnumber id, const simtype event) {
  if (old_contact)
    old_contact (id, event);
  if (sim_table_get_number (event, SIM_EVENT_CONTACT) == SIM_OK)
    sound_play ("contact", true);
}

static void handle_status (simnumber id, const simtype event) {
  simtype status, nick = sim_table_get_string (event, CONTACT_KEY_NICK);
  if (query_id && query_id == id && sim_get_type (nick) != SIMNIL)
    sprintf (command_prompt, "\r%s: ", sim_get_pointer (nick));
  if (old_status)
    old_status (id, event);
  if (sim_get_type (status = sim_table_get (event, CONTACT_KEY_STATUS)) == SIMNUMBER) {
    if (sim_get_number (status) == SIM_STATUS_OFF) {
      sound_play ("logoff", true);
    } else if (sim_get_number (status) != SIM_STATUS_INVISIBLE)
      if (sim_table_get_number (event, SIM_EVENT_STATUS) == SIM_STATUS_OFF)
        sound_play ("logon", true);
  }
}

static void handle_msg (simnumber id, const simtype event) {
  if (old_msg)
    old_msg (id, event);
  sound_check_play (id == query_id ? SOUND_PLAY_MSGRECV : SOUND_PLAY_MSG);
}

static void handle_edit (simnumber id, const simtype event) {
  if (old_edit)
    old_edit (id, event);
  sound_check_play (id == query_id ? SOUND_PLAY_MSGRECV : SOUND_PLAY_MSG);
}

static void handle_sent (simnumber id, const simtype event) {
  if (old_sent)
    old_sent (id, event);
  if (sim_table_get_number (event, SIM_EVENT_SENT))
    sound_play ("msgresent", true);
}

static void handle_notsent (simnumber id, const simtype event) {
  unsigned i;
  simtype indexes, text;
  if (old_notsent)
    old_notsent (id, event);
  if (sim_table_get_number (event, SIM_EVENT_NOTSENT))
    sound_play ("msgnotsent", true);
  indexes = sim_table_get_array_number (event, SIM_EVENT_MSG);
  for (i = 1; i <= sim_get_length (indexes); i++)
    if ((text = msg_get_text (id, (unsigned) sim_array_get_number (indexes, i), NULL)).typ == SIMPOINTER)
      log_note_ (NULL, "* %s UNSENT: %s\n", contact_get_nick (id, NULL), sim_get_pointer (text));
}

static void handle_audio (simnumber id, const simtype event) {
  simtype oldstate, newstate;
  if (old_audio)
    old_audio (id, event);
  oldstate = sim_table_get_string (event, SIM_EVENT_AUDIO);
  newstate = sim_table_get_string (event, CONTACT_KEY_AUDIO);
  if (! sim_string_check_diff (newstate, CONTACT_AUDIO_INCOMING)) {
    sound_start (id, SOUND_CALL);
  } else if (! sim_string_check_diff (oldstate, CONTACT_AUDIO_INCOMING))
    sound_stop (id, SOUND_CALL);
  if (! sim_string_check_diff (newstate, CONTACT_AUDIO_CALLING)) {
    sound_start (id, SOUND_CONNECT);
  } else if (! sim_string_check_diff (newstate, CONTACT_AUDIO_RINGING)) {
    sound_stop (id, SOUND_CONNECT);
    sound_start (id, SOUND_RING);
  } else if (! sim_string_check_diff (oldstate, CONTACT_AUDIO_OUTGOING)) {
    sound_stop (id, SOUND_CONNECT);
    sound_stop (id, SOUND_RING);
  }
}

static void handle_keygen (simnumber id, const simtype event) {
  if (old_keygen)
    old_keygen (id, event);
  if (id)
    sound_play ("keygen", true);
}

static void handle_history (simnumber id, const simtype event) {
  const char *s;
  simtype text;
  if (old_history)
    old_history (id, event);
  s = sim_get_pointer (text = sim_table_get_string (event, SIM_CMD_MSG_TEXT));
  if (! SIM_STRING_CHECK_DIFF_CONST (s, "FILE ") || ! SIM_STRING_CHECK_DIFF_CONST (s, "CALL ")) {
    simbool incoming = sim_table_get_number (event, SIM_CMD_MSG_STATUS) == SIM_MSG_INCOMING;
    char *arg = strchr (s = strchr (s, ' ') + 1, ' '), ch = 0;
    if (arg) {
      ch = *++arg;
      *arg = 0;
    }
    if (! SIM_STRING_CHECK_DIFF_CONST (s, "CALL ")) {
      if (incoming ? ! strcmp (s, "BUSY ") : ! strcmp (s, "FAILED ") || ! strcmp (s, "ABORT ")) {
        sound_play ("failed", true);
      } else if (! strcmp (s, "HANGUP ") || ! strcmp (s, "HUNGUP ")) {
        sound_play ("disconnect", true);
      } else if (incoming && ! strcmp (s, "BUSY")) {
        sound_play ("busy", true);
      } else if (! strcmp (s, "HANGUP") || ! strcmp (s, "HUNGUP") || ! strcmp (s, "ABORT") ||
                 (! incoming && (! strcmp (s, "FAILED") || ! strcmp (s, "BUSY"))))
        sound_play ("hangup", true);
    } else if (incoming && ! strcmp (s, "SEND ")) {
      sound_check_play (SOUND_PLAY_RECEIVE);
    } else if (! strcmp (s, "RECV ") && arg) {
      int sound = arg[1] != ' ' || ch != '0' ? SOUND_PLAY_CANCELLED : incoming ? SOUND_PLAY_SENT : SOUND_PLAY_RECEIVED;
      sound_check_play (sound);
    } else if (! strcmp (s, "REJECT ") || ! strcmp (s, "CANCEL "))
      sound_check_play (SOUND_PLAY_CANCELLED);
    if (arg)
      *arg = ch;
  }
}

static void handle_net (simnumber id, const simtype event) {
  if (old_net)
    old_net (id, event);
  if (! sim_string_check_diff (sim_table_get_string (event, SIM_EVENT_NET), SIM_EVENT_NET_STATUS))
    sound_play (sim_table_get_number (event, SIM_EVENT_NET_STATUS) > 0 ? "up" : "down", true);
}

static void handle_error (simnumber id, const simtype event) {
  simtype type;
  if (old_error)
    old_error (id, event);
  if (! sim_string_check_diff (type = sim_table_get_string (event, SIM_EVENT_ERROR), SIM_EVENT_ERROR_PROTOCOL)) {
    sound_play ("protocol", true);
  } else if (! sim_string_check_diff (type, SIM_EVENT_ERROR_KEY))
    sound_play ("tofu", false);
}

static void sim_handle_log (simnumber id, const simtype event) {
  simtype text;
  if (old_log)
    old_log (id, event);
  if (sim_get_type (text = sim_table_get (event, SIM_CMD_MSG_TEXT)) == SIMPOINTER) {
    char buf[SIM_MAX_NICK_LENGTH + 4];
    sprintf (buf, "\r%*s\r", SIM_MAX_NICK_LENGTH + 1, "");
    sim_file_write (STDERR_FILENO, buf, strlen (buf));
#ifdef _WIN32
    if (log_console != INVALID_HANDLE_VALUE) {
      unsigned len = sim_get_length (text);
      simtype ucs = sim_convert_utf_to_ucs (sim_get_pointer (text), &len);
      if (sim_get_type (ucs) == SIMNIL) {
        len = sim_get_length (text = sim_pointer_new ("*** non-utf-8 string discarded ***"));
        ucs = sim_convert_utf_to_ucs (sim_get_pointer (text), &len);
      }
      if (sim_get_type (ucs) != SIMNIL)
        WriteConsoleW (log_console, sim_get_pointer (ucs), len, (PDWORD) &len, NULL);
      sim_string_free (ucs);
    } else
#endif
      sim_file_write (STDERR_FILENO, sim_get_pointer (text), sim_get_length (text));
    sim_file_write (STDERR_FILENO, "\n", 1);
    if (*command_prompt)
      sim_file_write (STDERR_FILENO, command_prompt, strlen (command_prompt));
  }
}

#if HAVE_LIBPTH
static simevent *old_tick;

static void handle_tick_ (simnumber id, const simtype event) {
  static int tick_count = 0;
  if (old_tick)
    old_tick (id, event);
  if (! tick_count++)
    while (! pth_thread_yield_ (1)) {}
  tick_count--;
}
#endif

static void sim_escape (void) {
  if (escape_command) {
#ifdef _WIN32
    CONSOLE_SCREEN_BUFFER_INFO info;
    HANDLE con = GetStdHandle (STD_ERROR_HANDLE);
    if (con != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo (con, &info)) {
      info.dwCursorPosition.Y--;
      info.dwCursorPosition.X = 0;
      SetConsoleCursorPosition (con, info.dwCursorPosition);
      fprintf (stderr, "%*s\r", info.dwSize.X - 1, "");
    }
#else
    fprintf (stderr, "%s\r", escape_command);
#endif
  }
}

static void exec_commands_ (const char *commands) {
  int err = SIM_OK;
  const char *next = commands;
  while (*next && ! signal_terminated_flag && signal_running_flag) {
    const char *command = next;
    while (*next && *next != '\n' && *next != '\r')
      next++;
    if (*command != '#') {
      unsigned len = next - command;
      char *cmd = sim_new (len + 1);
      memcpy (cmd, command, len);
      cmd[len] = 0;
      if (*next)
        next++;
      log_xtra_ (NULL, "%s\n", cmd);
      if ((err = sim_console_exec__ (cmd)) == SIM_CONSOLE_BAD_CMD)
        log_debug_ (NULL, "%s: %s\n", sim_error_get (err), cmd);
      sim_free (cmd, len + 1);
    }
  }
  if (err == SIM_CONSOLE_QUIT_CMD)
    signal_terminated_flag = true;
  signal_running_flag = false;
}

static char *get_line_ (void) {
  static char command_buffer[4096];
  unsigned len;
  for (;;) {
#ifdef _WIN32
    if (log_console != INVALID_HANDLE_VALUE) {
      simtype utf;
      unsigned res = sizeof (command_buffer) / sizeof (unsigned short) - 1;
      HANDLE con = GetStdHandle (STD_INPUT_HANDLE);
      PTH_UNPROTECT_PROTECT_ (res = ReadConsoleW (con, command_buffer, res, (PDWORD) &len, NULL));
      if (! res) {
        fprintf (stderr, "read: %s\n", sim_error_get (GetLastError ()));
        return NULL;
      }
      if (sim_get_type (utf = sim_convert_ucs_to_utf ((unsigned short *) command_buffer, &len)) == SIMNIL) {
        log_warn_ (NULL, "read: BAD CHARACTER\n");
        return "";
      }
      memcpy (command_buffer, sim_get_pointer (utf), len);
      sim_string_free (utf);
      break;
    }
#else
    if (signal_terminated_flag)
      return NULL;
    if (signal_running_flag)
      exec_commands_ (signal_command);
    PTH_PROTECT_UNPROTECT_ (len = socket_select_readable_ (STDIN_FILENO, 0, 100));
    if ((int) len <= 0) {
      if (! len || errno == EINTR)
        continue;
      log_error_ (NULL, "SELECT: %s\n", sim_error_get (errno));
      return NULL;
    }
#endif
    PTH_PROTECT_UNPROTECT_ (len = pth_read_ (STDIN_FILENO, command_buffer, sizeof (command_buffer) - 1));
    if ((int) len <= 0) {
#ifndef _WIN32
      if ((int) len < 0 && errno == EINTR)
        continue;
#endif
      log_debug_ (NULL, "read: %s\n", sim_error_get ((int) len < 0 ? errno : SIM_OK));
      return NULL;
    }
    break;
  }
  while (len) {
    if (command_buffer[len - 1] != '\n' && command_buffer[len - 1] != '\r')
      if (command_buffer[len - 1] != ' ' && command_buffer[len - 1] != '\t')
        break;
    --len;
  }
  command_buffer[len] = 0;
  return command_buffer;
}

static const char *parse_options (const char **argv) {
  int i, j;
  unsigned bits = SIM_INIT_BIT_INSTALL;
  for (i = 1; argv[i]; i++) {
    char c = argv[i][0];
    if (c != '-' || ((c = argv[i][1]) != 0 && argv[i][2]))
      c = 0;
    switch (c) {
      case 'r':
        if ((user_command = argv[++i]) == NULL)
          return argv[i - 1];
        break;
      case 'l':
        if ((login_command = argv[++i]) == NULL)
          return argv[i - 1];
        break;
      case 'c':
        if ((signal_command = argv[++i]) == NULL)
          return argv[i - 1];
        break;
      case 'u':
        if ((login_user = argv[++i]) == NULL)
          return argv[i - 1];
        break;
      case 'p':
        if ((login_pass = argv[++i]) == NULL)
          return argv[i - 1];
        break;
      case 's':
        set_params[set_params_length++] = argv[i + 1] ? argv[++i] : "";
        break;
      case 'd':
        escape_command = NULL;
        break;
#ifndef _WIN32
      case 'e':
        escape_command = argv[++i];
        break;
#endif
      case 't':
        if (argv[i + 1]) {
          speed_test_factor = atoi (argv[++i]);
          bits = (bits & ~SIM_INIT_BIT_INSTALL) | SIM_INIT_BIT_NOAUDIO;
          login_user = NULL;
          break;
        }
      case 0:
        if (argv[i][0] == '-' && argv[i][1] == 'n') {
          for (j = 2; (c = argv[i][j]) != 0; j++)
            switch (c) {
              default:
                fprintf (stderr, "Unknown option: %s\n", argv[i]);
                return argv[i];
              case 'a':
                bits |= SIM_INIT_BIT_NOAUDIO;
                break;
              case 'd':
                bits |= SIM_INIT_BIT_NODHT;
                break;
              case 'c':
                bits |= SIM_INIT_BIT_NOCRASH;
                break;
              case 'i':
                bits &= ~SIM_INIT_BIT_INSTALL;
                break;
              case 'u':
                login_user = NULL;
                break;
              case 'p':
                login_pass = NULL;
            }
          break;
        }
      default:
        fprintf (stderr, "Unknown option: %s\n", argv[i]);
      case 'h':
        return argv[i];
    }
  }
  sim_init_bits (bits);
  return NULL;
}

int main (int argc, const char **argv) {
  int err = sim_init_memory ();
  const char *cmd;
  simtype contacts, seed;
#ifndef _WIN32
  struct sigaction action;
#endif
  if ((cmd = parse_options (argv)) != NULL || speed_test_factor <= 0) {
    if (! SIM_STRING_CHECK_DIFF_CONST (cmd, "---")) {
      if (err != SIM_OK || (err = sim_init_ (login_user, 0, "simc " PACKAGE_VERSION)) != SIM_OK) {
        fprintf (stderr, "init: %s (error %d)\n", sim_error_get (err), err);
      } else if ((err = sim_exit_ ()) != SIM_OK)
        fprintf (stderr, "EXIT: %s\n", sim_error_get (err));
      return err == SIM_OK ? 0 : -2;
    }
    fprintf (stderr, "usage: %s [options]\n", argv[0]);
    fprintf (stderr, "  -u \"<directory>\"    \tset user home directory\n");
    fprintf (stderr, "  -p \"<password>\"     \tauto login with password\n");
    fprintf (stderr, "  -nu                 \tstart with no home directory\n");
    fprintf (stderr, "  -np                 \tdo not login automatically\n");
    fprintf (stderr, "  -r \"<command>\"      \trun commands after login\n");
    fprintf (stderr, "  -l \"<command>\"      \trun commands before login\n");
#ifndef _WIN32
    fprintf (stderr, "  -c \"<command>\"      \trun commands on SIGUSR1/SIGUSR2\n");
#endif
    fprintf (stderr, "  -s \"<param> <value>\"\tset parameter after login\n");
    fprintf (stderr, "  -d                  \tuse dumb terminal\n");
#ifndef _WIN32
    fprintf (stderr, "  -e `tput cuu 1`     \tuse smart terminal\n");
#endif
    fprintf (stderr, "  -h                  \tprint usage help\n");
    fprintf (stderr, "  -t 1                \tperform speed test\n");
    return -1;
  }
  if (err != SIM_OK || (err = sim_init_ (login_user, 0, "simc " PACKAGE_VERSION)) != SIM_OK) {
    fprintf (stderr, "init: %s (error %d)\n", sim_error_get (err), err);
    return -2;
  }
  if (speed_test_factor < 1000000000) {
    while (set_params_length--)
      PTH_PROTECT_UNPROTECT_ (console_set_param (set_params[set_params_length], SIM_PARAM_TEMPORARY));
    PTH_PROTECT_UNPROTECT_ (key_print_speed_ (speed_test_factor));
    return 0;
  }
  if ((err = sim_console_load_ (LOG_NO_BUFFER)) != SIM_OK) {
    fprintf (stderr, "init: error %d\n", err);
    return -2;
  }
#ifdef _WIN32
  if (escape_command && (log_console = GetStdHandle (STD_ERROR_HANDLE)) < 0) {
    fprintf (stderr, "init: (error %lu)\n", GetLastError ());
    return -2;
  }
#endif
  sim_event_register_ (SIM_EVENT_CONTACT, handle_contact, &old_contact);
  sim_event_register_ (SIM_EVENT_STATUS, handle_status, &old_status);
  sim_event_register_ (SIM_EVENT_MSG, handle_msg, &old_msg);
  sim_event_register_ (SIM_EVENT_EDIT, handle_edit, &old_edit);
  sim_event_register_ (SIM_EVENT_SENT, handle_sent, &old_sent);
  sim_event_register_ (SIM_EVENT_NOTSENT, handle_notsent, &old_notsent);
  sim_event_register_ (SIM_EVENT_AUDIO, handle_audio, &old_audio);
  sim_event_register_ (SIM_EVENT_KEYGEN, handle_keygen, &old_keygen);
  sim_event_register_ (SIM_EVENT_HISTORY, handle_history, &old_history);
  sim_event_register_ (SIM_EVENT_NET, handle_net, &old_net);
  sim_event_register_ (SIM_EVENT_ERROR, handle_error, &old_error);
  sim_event_register_ (SIM_EVENT_LOG, sim_handle_log, &old_log);
#if HAVE_LIBPTH
  sim_event_register_ (SIM_EVENT_TICK, handle_tick_, &old_tick);
#endif
  if (login_command) {
    signal_running_flag = true;
    exec_commands_ (login_command);
  }
  if (login_user && login_pass) {
    if ((err = sim_init_user_ (login_pass)) == SIM_OK)
      sim_param_set_ ("rights.typing", sim_number_new (0), SIM_PARAM_TEMPORARY);
  } else if (login_pass && *login_pass) {
    seed = sim_pointer_new (login_pass);
    if ((err = sim_key_generate_ (&seed, sim_nil (), 0, SIM_KEY_NONE)) == SIM_OK)
      log_info_ (NULL, "Generating a private key from seed...\n");
  } else if (login_pass)
    log_info_ (NULL, "please use the 'keygen' and 'init none' commands to log in.\n");
  if (err != SIM_OK)
    log_debug_ (NULL, "INIT: %s\n", sim_error_get (err));
  if (err == SIM_FILE_LOCKED)
    return -3;
  log_xtra_ (NULL, "simc " PACKAGE_VERSION " %s\n", sim_get_version (NULL, NULL));
#ifndef _WIN32
  signal (SIGTERM, signal_handle_interrupt);
  signal (SIGHUP, signal_handle_interrupt);
  signal (SIGINT, signal_handle_interrupt);
#endif
  if (err != SIM_OK && err != SIM_KEY_NO_KEY) {
    log_info_ (NULL, "please enter your password or secret key (empty line to abort):\n");
    if ((cmd = get_line_ ()) == NULL) {
      sim_exit_ ();
      return 0;
    }
    if ((err = sim_init_user_ (cmd)) == SIM_OK) {
      sim_param_set_ ("rights.typing", sim_number_new (0), SIM_PARAM_TEMPORARY);
    } else
      log_debug_ (NULL, "INIT: %s\n", sim_error_get (err));
  }
  if (err == SIM_KEY_NO_KEY) {
    log_info_ (NULL, "please use the 'call' and 'keygen' commands to generate a key.\n");
    err = SIM_OK;
  }
  if (login_user && (! login_pass || err != SIM_OK))
    log_info_ (NULL, "please use the 'init' command to log in.\n");
  log_xtra_ (NULL, "type 'help' to list available commands.\n");
  strcpy (command_prompt, "\rsimc> ");
  sound_id_table = sim_table_new (sim_get_length (contacts = sim_contact_get_ (0, CONTACT_BIT_DEFAULT)));
  sim_contact_free_ (contacts);
#ifndef _WIN32
  memset (&action, 0, sizeof (action));
  action.sa_sigaction = signal_handle_user;
  sigemptyset (&action.sa_mask);
  if (signal_command)
    sigaction (SIGUSR1, &action, NULL);
  sigaction (SIGUSR2, &action, NULL);
#endif
  while (set_params_length--)
    PTH_PROTECT_UNPROTECT_ (console_set_param (set_params[set_params_length], SIM_PARAM_TEMPORARY));
  if (user_command) {
    signal_running_flag = true;
    exec_commands_ (user_command);
  }
  while (! signal_terminated_flag) {
    int len;
    char nick[SIM_SIZE_NICK];
    if (! query_id) {
      if (! sim_contact_get_nick_ (0, nick)) {
        strcpy (command_prompt, "\rlogin> ");
      } else if (! *nick) {
        strcpy (command_prompt, "\rsimc> ");
      } else
        sprintf (command_prompt, "\r%s> ", nick);
    }
    len = sim_file_write (STDERR_FILENO, command_prompt, strlen (command_prompt));
    if (len != (int) strlen (command_prompt)) {
      log_debug_ (NULL, "write: %s\n", sim_error_get (len < 0 ? errno : SIM_OK));
      break;
    }
    if ((cmd = get_line_ ()) == NULL)
      break;
    if (! query_id) {
      if ((err = sim_console_exec__ (cmd)) == SIM_CONSOLE_BAD_CMD) {
        char *arg;
        while (*cmd == ' ')
          cmd++;
        if ((arg = strchr (cmd, ' ')) != NULL)
          while (*arg == ' ')
            *arg++ = 0;
        if (strcmp (cmd, "query")) {
          log_debug_ (NULL, "%s: %s\n", sim_error_get (SIM_CONSOLE_BAD_CMD), cmd);
        } else if (! arg || ! *arg) {
          log_xtra_ (NULL, "usage: query <nick>\n");
        } else if ((err = sim_contact_connect_ (query_id = sim_contact_find_ (arg))) == SIM_OK) {
          sprintf (command_prompt, "\r%s: ", arg);
          log_info_ (NULL, "end query with '.' on a line by itself\n");
        } else {
          query_id = 0;
          log_debug_ (NULL, "CONNECT %s: %s\n", arg, sim_error_get (err));
          sim_contact_disconnect_ (sim_contact_find_ (arg));
        }
      } else if (err != SIM_OK) {
        if (err == SIM_CONSOLE_QUIT_CMD)
          break;
        if (! strcmp (cmd, "help query")) {
          sim_escape ();
          log_xtra_ (NULL, "usage: query <nick>\n");
        } else if (! strcmp (cmd, "dump sounds")) {
          unsigned i;
          sim_escape ();
          if (log_protect_ (NULL, SIM_LOG_INFO)) {
            for (i = 0; i < SIM_ARRAY_SIZE (sound_names); i++)
              log_info_ (NULL, "%s=%d ", sound_names[i], sound_counts[i]);
            log_info_ (NULL, "\n");
            if (sim_table_count (sound_id_table)) {
              simtype key, val;
              simwalker ctx;
              sim_table_walk_first (&ctx, sound_id_table);
              while (sim_get_type (val = sim_table_walk_next_number (&ctx, &key)) != SIMNIL)
                log_info_ (NULL, "%s: %s\n", sim_contact_get_nick_ (*(simnumber *) sim_get_pointer (key), nick),
                           sim_get_number (val) ? sound_names[sim_get_number (val) - 1] : 0);
            }
            log_unprotect_ ();
          }
        }
      } else if (! strcmp (cmd, "help")) {
        log_info_ (NULL, "init        initialize (log in)\n");
        log_info_ (NULL, "uninit      uninitialize (log out)\n");
        log_info_ (NULL, "sleep       pause for some time\n");
        log_info_ (NULL, "query       chat with contact\n");
      } else if (! strcmp (cmd, "dump") || ! strcmp (cmd, "help dump"))
        log_xtra_ (NULL, "usage: dump sounds      \tshow currently active sounds\n");
    } else if (cmd[0] != '.' || cmd[1]) {
      simtype tmp = sim_string_concat ("msg \"", sim_contact_get_nick_ (query_id, nick), "\" ", cmd, NULL);
      sim_escape ();
      sim_console_exec__ (sim_get_pointer (tmp));
      sim_string_free (tmp);
    } else {
      if ((err = sim_contact_disconnect_ (query_id)) != SIM_OK)
        log_debug_ (NULL, "DISCONNECT %s: %s\n", sim_contact_get_nick_ (query_id, nick), sim_error_get (err));
      query_id = 0;
    }
  }
  err = sim_exit_user_ ();
  if (err != SIM_OK && err != SIM_API_INIT)
    log_debug_ (NULL, "UNINIT: %s\n", sim_error_get (err));
  fprintf (stderr, "\r");
  sim_table_free (sound_id_table);
  if ((err = sim_exit_ ()) != SIM_OK)
    fprintf (stderr, "EXIT: %s\n", sim_error_get (err));
  return 0;
}
