/** Copyright (c) 2020-2022 The Creators of Simphone

    class Settings (QDialog): display and edit configuration, including audio devices
    class RadioButtonSetting (QRadioButton): radio-button with a scaled value
    class SpinBoxSetting (QSpinBox): numeric input field with a checkbox and a scaled value
    class CheckBoxSetting (QCheckBox): check-box with a default value

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

#include "settings.h"
#include "ui_settings.h"

#include "qtfix.h"
#include "contacts.h"
#include "transfers.h"
#include "login.h"

#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <QSettings>
#endif
#include <QDir>

#define INVALID_DEVICE Settings::tr("INVALID DEVICE: ")

enum {
  INV_output = 0x01,
  INV_input = 0x02,
  INV_ring = 0x04
};

int Settings::mc_active = 0;
int Settings::mc_current = -1;
QStringList Settings::mc_autostartArguments;

int Settings::mc_statisticsInterval = 1;
int Settings::mc_statisticsIncrement = 1;
simunsigned Settings::mc_statisticsIncrementTime;
bool Settings::mc_statisticsDecrement;
QList<double> Settings::mc_statisticsDownload;
QList<double> Settings::mc_statisticsUpload;

bool Settings::mc_invalidOutput = false;
bool Settings::mc_invalidInput = false;
bool Settings::mc_invalidRing = false;

CheckBoxSetting::CheckBoxSetting(QWidget * parent)
  : Parent(parent), m_dirty(false), m_value(0)
{
  qtfix::fixCheckBoxIcon(this, font());
}

int CheckBoxSetting::getCheckedState(int unchecked, int partial, int checked) const
{
  return checkState() == Qt::Checked ? checked : checkState() == Qt::Unchecked ? unchecked : partial;
}

void CheckBoxSetting::setCheckedState(int unchecked, int checked)
{
  setCheckState(m_value <= unchecked ? Qt::Unchecked : m_value >= checked ? Qt::Checked : Qt::PartiallyChecked);
}

void CheckBoxSetting::set(simnumber value, int unchecked, const char * param)
{
  prepare(value);
  setChecked(m_value > unchecked);
  if (param && m_value == unchecked) prepare(abs(SimParam::getDefault(param, 1)));
}

SpinBoxSetting::SpinBoxSetting(QWidget * parent)
  : Parent(parent), m_value(0), m_defaultValue(0), m_scaleFactor(1), m_checkbox(0)
{
}

void SpinBoxSetting::prepare(QCheckBox * checkBox, const char * param, simnumber value, int scaleFactor)
{
  int max = maximum();
  int min = -max;
  int defaultValue = param ? SimParam::getDefault(param, 0, &max, &min) : 0;

  if (min > 0) {
    setMinimum(min / scaleFactor);
  } else if (!min) {
    min = max;
  }
  setMaximum((abs(min) < abs(max) ? abs(min) : abs(max)) / scaleFactor);

  m_defaultValue = defaultValue;
  m_scaleFactor = scaleFactor;
  m_checkbox = checkBox;

  m_value = value ? int(value) : defaultValue ? defaultValue : param ? 443 : 0;
  setValue(m_value = (abs(m_value) + scaleFactor - 1) / scaleFactor);

  if (!checkBox->isTristate()) {
    checkBox->setChecked(value > 0);
  } else {
    checkBox->setCheckState(!value ? Qt::Unchecked : value > 0 ? Qt::Checked : Qt::PartiallyChecked);
  }
  if (value <= 0) m_value = -m_value;
}

int SpinBoxSetting::getValue() const
{
  int n = value();
  if (m_checkbox->isTristate()) {
    if (m_checkbox->checkState() == Qt::Unchecked) return 0;
    if (!n) n = abs(m_defaultValue);
    return (m_checkbox->checkState() == Qt::Checked ? n : -n) * m_scaleFactor;
  }
  if (!m_checkbox->isChecked()) n = -n;
  return n != m_value ? n * m_scaleFactor : 0;
}

RadioButtonSetting::RadioButtonSetting(QWidget * parent)
  : Parent(parent), m_dirty(false), m_value(0), m_scaleFactor(0)
{
  qtfix::fixRadioButtonIcon(this, font());
}

void RadioButtonSetting::prepare(simnumber value, int scaleFactor)
{
  m_dirty = false;
  m_value = int(value);
  m_scaleFactor = scaleFactor;
  setChecked(true);

  QList<QAbstractButton *> list = group()->buttons();
  for (int i = 0; i < list.size(); i++) {
    if (getScaledValue(-2 - group()->id(list[i])) == value) list[i]->setChecked(true);
  }
}

int RadioButtonSetting::getScaledValue(int value) const
{
  if (!value || !m_scaleFactor) return value;
  if (m_scaleFactor > 0) return m_scaleFactor << (value - 1);
  return value + (value >= -m_scaleFactor);
}

int RadioButtonSetting::getValue()
{
  if (m_dirty) {
    QList<QAbstractButton *> list = group()->buttons();
    for (int i = -2; i > -2 - list.size(); i--) {
      if (group()->button(i)->isChecked()) m_value = getScaledValue(-2 - i);
    }
  }
  return m_value;
}

// l - login settings, i - invalid device, c - main settings, t - main settings while talking
Settings::Settings(QWidget * parent, char code)
  : Parent(parent)
  , ui(new Ui::Settings)
  , m_code(code)
  , m_testing(0)
  , m_password(false)
  , m_invalid(0)
  , m_outputSelected(-1)
  , m_inputSelected(-1)
  , m_ringSelected(-1)
  , m_latency(0)
  , m_autoAnswer(-1)
  , m_statisticsBox(0)
{
  ui->setupUi(this);
  setAttribute(Qt::WA_DeleteOnClose);
  //ui->testButton->setFont(ui->buttonBox->button(QDialogButtonBox::Ok)->font());
  ui->progressBar->setVisible(false);

  qtfix::fixComboBoxIcon(ui->outputList, font());
  qtfix::fixComboBoxIcon(ui->inputList, font());
  qtfix::fixComboBoxIcon(ui->ringList, font());

  connect(SimCore::get(), SIGNAL(signalContactAudioChanged(unsigned, SimAudioState)),
          this, SLOT(onSignalContactAudioChanged(unsigned, SimAudioState)));
  connect(SimCore::get(), SIGNAL(signalInvalidAudioDevice(bool)), this, SLOT(onSignalInvalidAudioDevice(bool)));
  connect(SimCore::get(), SIGNAL(signalSpeech(unsigned, int, int, int)),
          this, SLOT(onSignalSpeech(unsigned, int, int, int)));
  connect(&m_statisticsTimer, SIGNAL(timeout()), this, SLOT(onStatisticsTimeout()));

  int index = code == 'l' || code == 'i' ? 0 : SimParam::get("ui.settings.tab");
  index = !index ? ui->tabWidget->indexOf(ui->audio) : mc_current < 0 ? abs(index) - 1 : mc_current;
  ui->tabWidget->setCurrentIndex(index);
  on_tabWidget_currentChanged(ui->tabWidget->currentIndex());

  enableAudioTab(code == 'c' || code == 'l' || code == 'i');
  if (code == 'l' || code == 'i') hideNonAudioTabs();
  hideAudioSettings(true);

  prepare();

  ++mc_active;
}

Settings::~Settings()
{
  mc_current = ui->tabWidget->count() > 1 ? ui->tabWidget->currentIndex() : -1;
  if (SimCore::get()->isLogged()) {
    if (m_autoAnswer >= 0) SimParam::set("audio.answer", m_autoAnswer, true);
    if (ui->tabWidget->count() > 1 && SimParam::get("ui.settings.tab") <= 0) {
      SimParam::set("ui.settings.tab", -1 - mc_current, false);
    }
  }
  if (m_testing) on_testButton_clicked();
  delete ui;
  --mc_active;
}

void Settings::prepareAudio(bool valid)
{
  // prepare audio devices
  QString qs = "<br/><span style=\" color:#ff0000;\">";
  qs.append(tr("Some of your chosen audio devices have become invalid.")).append("</span><br/>");
  qs.append(tr("<b>Please, choose a valid device.</b>").append("<br/>"));
  ui->invalidLabel->setText(valid && !m_invalid ? "<br/><br/><br/><br/>" : qs);

  simtype st = sim_list_info_(SIM_INFO_BIT_AUDIO);
  simtype it = sim_table_get_array_string(st, SIM_INFO_AUDIO_INPUT);
  simtype ot = sim_table_get_array_string(st, SIM_INFO_AUDIO_OUTPUT);

  QString outDev = m_output = SimParam::getString("audio.device.output");
  QString inDev = m_input = SimParam::getString("audio.device.input");
  QString ringDev = m_ring = SimParam::getString("audio.device.ring");

  int latency = SimParam::get("audio.latency");
  ui->latencyValue->prepare(ui->latency, 0, latency < 0 ? -abs(SimParam::getDefault("audio.latency", 0)) : latency);
  m_latency = ui->latencyValue->value();

  ui->outputList->clear();
  ui->inputList->clear();
  ui->ringList->clear();
  ui->outputList->addItem(tr("SYSTEM DEFAULT"));
  ui->inputList->addItem(tr("SYSTEM DEFAULT"));
  ui->ringList->addItem(tr("SYSTEM DEFAULT"));

  m_invalid = 0;
  bool hasInput = false;
  for (unsigned i = 1; i <= sim_get_length(it); ++i) {
    const char * dname = sim_array_get_pointer(it, i);
    ui->inputList->addItem(dname);
    hasInput = hasInput || inDev == dname;
  }
  if (hasInput) {
    ui->inputList->setCurrentText(inDev);
  } else if (inDev.size()) {
    inDev.insert(0, INVALID_DEVICE);
    ui->inputList->addItem(inDev);
    ui->inputList->setCurrentText(inDev);
    m_invalid |= INV_input;
  } else if (!sim_get_length(it)) {
    m_invalid |= INV_input;
  }

  bool hasOutput = false;
  bool hasRing = false;
  for (unsigned i = 1; i <= sim_get_length(ot); ++i) {
    const char * dname = sim_array_get_pointer(ot, i);
    ui->ringList->addItem(dname);
    ui->outputList->addItem(dname);
    hasOutput = hasOutput || outDev == dname;
    hasRing = hasRing || ringDev == dname;
  }
  if (hasOutput) {
    ui->outputList->setCurrentText(outDev);
  } else if (outDev.size()) {
    outDev.insert(0, INVALID_DEVICE);
    ui->outputList->addItem(outDev);
    ui->outputList->setCurrentText(outDev);
    m_invalid |= INV_output;
  } else if (!sim_get_length(ot)) {
    m_invalid |= INV_output;
  }

  if (hasRing) {
    ui->ringList->setCurrentText(ringDev);
  } else if (ringDev.size()) {
    ringDev.insert(0, INVALID_DEVICE);
    ui->ringList->addItem(ringDev);
    ui->ringList->setCurrentText(ringDev);
    m_invalid |= INV_ring;
  } else if (!sim_get_length(ot)) {
    m_invalid |= INV_ring;
  }

  setInvalidDevices((m_invalid & INV_output) != 0, (m_invalid & INV_input) != 0, (m_invalid & INV_ring) != 0);
  ui->testButton->setEnabled(!m_invalid);
  if (!sim_get_length(it) || !sim_get_length(ot)) {
    qs = "<br/><span style=\" color:#ff0000;\">";
    if (sim_get_length(it)) {
      qs.append(tr("There are no output audio devices in your system.")).append("</span><br/>");
      qs.append(tr("Install or plug in a valid output device."));
    } else if (sim_get_length(ot)) {
      qs.append(tr("There are no input audio devices in your system.")).append("</span><br/>");
      qs.append(tr("Install or plug in a valid input device."));
    } else {
      qs.append(tr("There are no audio devices in your system.")).append("</span><br/>");
      qs.append(tr("Install or plug in a valid device."));
    }
    ui->invalidLabel->setText(qs.append("<br/><br/>"));
  } else {
    ui->invalidLabel->setText(valid && !m_invalid ? "<br/><br/><br/><br/>" : qs.append("<br/>"));
  }

  sim_list_free(st);
}

void Settings::prepare()
{
  simtype params = sim_param_get_(0);

  if (SimCore::get()->isLogged()) {
    m_autoAnswer = int(sim_table_get_number(params, "audio.answer"));
    SimParam::set("audio.answer", SimParam::getDefault("audio.answer", 0), true);
  }

  // initialize Window settings
#ifndef __APPLE__
  simtype versions = sim_list_versions();
  QString os = sim_table_get_pointer(versions, "_os");
  if (os.isEmpty()) os = sim_table_get_pointer(versions, "_OS");
  sim_list_free(versions);
  m_os = os.split(' ')[0];

  ui->blink->setVisible(false);
  ui->taskbar->setVisible(true);
#else
  ui->taskbar->setVisible(false);
  ui->blink->setVisible(true);
  m_os = "Mac OS";
#endif
  ui->tray->prepare(sim_table_get_number(params, "ui.main.tray"));
  if (!ui->tray->getValue()) ui->tray->prepare(-abs(SimParam::getDefault("ui.main.tray", 1)));
  if (QSystemTrayIcon::isSystemTrayAvailable() && SimParam::getDefault("ui.main.tray", 1) > 0) {
    ui->tray->setChecked(ui->tray->getValue() > 0);
  } else {
    ui->tray->setEnabled(false);
  }
  ui->popup->setChecked(sim_table_get_number(params, "ui.main.popup") & 1);
  ui->popup2->setChecked(sim_table_get_number(params, "ui.main.popup") >> 1 & 1);
  ui->taskbar->setChecked(sim_table_get_number(params, "ui.main.taskbar") != 0);
  ui->blink->set(sim_table_get_number(params, "ui.main.blink"), 1);
  ui->doubleclick->set(sim_table_get_number(params, "ui.main.doubleclick"), 0);

#if defined(_WIN32) || defined(__APPLE__) || defined(HAVE_LIBXCB)
  ui->showkey->prepare(sim_table_get_number(params, "ui.main.showkey"));
  ui->hidekey->prepare(sim_table_get_number(params, "ui.main.hidekey"));
  ui->hidekey->setChecked(ui->showkey->getValue() > 0 || ui->hidekey->getValue() > 0);
  ui->hidekey->setEnabled(Contacts::get()->getExtensions() >= 0);
#else
  ui->hidekey->setEnabled(false);
#endif

  ui->autostart->setText(ui->autostart->text().arg(m_os));
#ifdef __unix__
  if (getConfigDir(sim_table_get_number(params, "ui.login.autostarted") != 0).isEmpty()) {
    ui->autostart->setEnabled(false);
    ui->autostart->prepare(sim_table_get_number(params, "ui.main.autostart"));
  } else
#endif
  {
    simnumber autostart = sim_table_get_number(params, "ui.main.autostart");
    ui->autostart->prepare(autostart);
    if (g_exeName.isEmpty() || SimCore::mc_startupUser.isNull() || !qgetenv("SIMHOME").isEmpty()) {
      ui->autostart->setEnabled(false);
    } else {
      ui->autostart->setChecked(autostart > 0 || (autostart < 0 && SimCore::mc_startupUser.isEmpty()));
    }
  }

  // initialize Sounds settings
  QList<QAbstractButton *> list = ui->sound->buttons();
  for (int i = 0; i < list.size(); i++) {
    QString qs = "sound.play.";
    CheckBoxSetting * checkBox = (CheckBoxSetting *)list[i];
    simnumber state = sim_table_get_number(params, qs.append(checkBox->objectName()).toUtf8().data());
    checkBox->prepare(state);
    if (checkBox->isTristate()) {
      checkBox->setCheckedState(3, 7);
    } else {
      checkBox->setChecked(state > 0);
    }
  }

  // initialize Audio settings
  ui->echoValue->prepare(ui->echo, "speex.echo", sim_table_get_number(params, "speex.echo"));
  ui->sample0->prepare(sim_table_get_number(params, "audio.sample"), 8000);

  // initialize Files settings
  ui->notifications->set(sim_table_get_number(params, "ui.xfer.notifications"), 0, "ui.xfer.notifications");
  ui->wipe->set(sim_table_get_number(params, "file.wipe"), 0, "file.wipe");
  ui->wipe->setChecked(!ui->wipe->isChecked());
  ui->keep->setChecked(!sim_table_get_number(params, "xfer.wipe"));
  ui->minmbValue->prepare(ui->minmb, "xfer.minmb", sim_table_get_number(params, "xfer.minmb"));
  ui->filesValue->prepare(ui->files, "xfer.files", sim_table_get_number(params, "xfer.files"));
  ui->downloadValue->prepare(ui->download, "xfer.download", sim_table_get_number(params, "xfer.download"));
  ui->uploadValue->prepare(ui->upload, "xfer.upload", sim_table_get_number(params, "xfer.upload"));

  if (SimCore::mc_startupUser.isNull()) {
    ui->directory->setEnabled(false);
  } else {
    ui->directory->setChecked(sim_get_length(sim_table_get_string(params, "xfer.sent")) != 0);
  }
  ui->md5->setChecked(sim_table_get_number(params, "xfer.md5") != 0);

  // initialize Chat settings
  ui->altarrow->setChecked(sim_table_get_number(params, "ui.chat.altarrow") != 0);
  ui->ctrlenter->setChecked(sim_table_get_number(params, "ui.chat.ctrlenter") != 0);
  ui->doublecopy->setChecked(sim_table_get_number(params, "ui.chat.doubleclick") != 0);
  ui->editValue->prepare(ui->edit, "msg.edit", sim_table_get_number(params, "msg.edit"));

  if (SimCore::mc_startupUser.isNull()) {
    ui->history->setEnabled(false);
  } else {
    ui->history->setChecked(sim_table_get_number(params, "rights.history") != 0);
  }
  ui->typing->set(sim_table_get_number(params, "rights.type"), 0, "rights.type");

#if defined(_WIN32) || defined(__APPLE__) || defined(HAVE_LIBXSS)
  ui->awayValue->prepare(ui->away, "ui.main.away", sim_table_get_number(params, "ui.main.away"), 60);
#endif
  if (!Contacts::get()->getExtensions()) {
    ui->away->setEnabled(false);
    ui->awayValue->setEnabled(false);
  }

  // initialize Network settings
  ui->speedValue->prepare(ui->speed, "limit.maxspeed", sim_table_get_number(params, "limit.maxspeed"));
  ui->portValue->prepare(ui->port, "main.port", sim_table_get_number(params, "main.port"));
  ui->torValue->prepare(ui->tor, "net.tor.port", sim_table_get_number(params, "net.tor.port"));

  simtype host = sim_table_get_string(params, "client.host");
  if (sim_get_pointer(host)) {
    if (*sim_get_pointer(host) != '.') ui->host->setChecked(true);
    ui->hostValue->setText((*sim_get_pointer(host) == '.') + sim_get_pointer(host));
  }

  ui->blacklistValue->prepare(ui->blacklist, "main.blacklist", sim_table_get_number(params, "main.blacklist"));

  // initialize Security settings
  prepareCiphers(&m_preferredCiphers, sim_table_get_array_string(params, "crypto.ciphers"));
  ui->contact0->prepare(sim_table_get_number(params, "contact.strangers"), -1);

  sim_param_free_(params);
}

void Settings::prepareCiphers(QSet<QString> * ciphers, const simtype array)
{
  for (unsigned i = 1; i <= sim_get_length(array); i++) {
    ciphers->insert(sim_array_get_pointer(array, i));
  }

  QList<QAbstractButton *> list = ui->ciphers->buttons();
  for (int i = 0; i < list.size(); i++) {
    list[i]->setChecked(ciphers->contains(list[i]->objectName()));
    ciphers->remove(list[i]->objectName());
  }
}

QString Settings::getFileName(const QString & fileName)
{
#ifdef _WIN32
  DWORD size = MAX_PATH;
  wchar_t exe[MAX_PATH];
  UNIVERSAL_NAME_INFO * info = (UNIVERSAL_NAME_INFO *)exe;
  int err = WNetGetUniversalName((wchar_t *)fileName.utf16(), UNIVERSAL_NAME_INFO_LEVEL, info, &size);
  if (err != NO_ERROR) return fileName;
  return QString::fromUtf16((ushort *)(info->lpUniversalName));
#else
  return fileName.indexOf(' ') >= 0 ? '"' + fileName + '"' : fileName;
#endif
}

#ifdef __unix__
QString Settings::getConfigDir(bool init)
{
  QString qs = qgetenv("XDG_CONFIG_HOME");
  static const char * xdg[] = { "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP", "XDG_SESSION_COOKIE", "XDG_CONFIG_DIRS" };
  for (unsigned i = 0; i < SIM_ARRAY_SIZE(xdg); i++) {
    if (!qgetenv(xdg[i]).isEmpty()) init = true;
  }
  if (qs.isEmpty() && init) {
    qs = qgetenv("HOME");
    if (!qs.isEmpty()) qs.append("/.config");
  }
  return qs.isEmpty() || !QDir(qs).exists() ? QString() : qs;
}

int Settings::writeString(QFile * file, const QString & text)
{
  QByteArray bytes = text.toUtf8();
  qint64 l = file->write(bytes.data(), bytes.size());

  if (l < 0) return file->error();
  return l == bytes.size() ? 0 : -2;
}
#endif

int Settings::setAutostart(int autostart, bool init)
{
  int err = 0;
  QString user = SimCore::mc_startupUser;
  if (user.size() >= 2 && user.at(0) == '.') {
    if (user.at(1) == QDir::separator() || user.at(1) == '/') user = QFileInfo(user).absoluteFilePath();
  }
#ifdef _WIN32
  HKEY hkey;
  simtype data = sim_convert_string_to_locase(user.toUtf8().data());
#else
  simtype data = sim_string_copy(user.toUtf8().data());
#endif
  simtype hash = sim_convert_string_to_hash(data);
  QString name(SIM_UI_NAME);

  if (init) {
    mc_statisticsInterval = 15;
    mc_statisticsIncrement = 1;
  }
  mc_statisticsIncrementTime = 0;
  mc_statisticsDownload.clear();
  mc_statisticsUpload.clear();

  if (sim_get_length(data) && sim_string_check_diff(data, "user")) name.append(".").append(sim_get_pointer(hash));
  sim_string_free(data);
  sim_string_free(hash);
  if (!autostart) {
    err = -1;
#ifdef _WIN32
#define AUTOSTART_KEY L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"
    err = RegOpenKeyEx(HKEY_CURRENT_USER, AUTOSTART_KEY, 0, KEY_SET_VALUE, &hkey);
    if (err == ERROR_SUCCESS) {
      err = RegDeleteValueW(hkey, (wchar_t *)(name.utf16()));
      RegCloseKey(hkey);
      if (err != ERROR_SUCCESS) {
        if (RegOpenKeyEx(HKEY_CURRENT_USER, AUTOSTART_KEY, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {
          if (RegQueryValueExW(hkey, (wchar_t *)(name.utf16()), 0, 0, 0, 0) != ERROR_SUCCESS) {
            err = ERROR_SUCCESS;
          }
          RegCloseKey(hkey);
        } else {
          err = ERROR_SUCCESS;
        }
      } else if (init) {
        log_debug_("ui", "autostart: removed %s\n", name.toUtf8().data());
      }
    }
#elif defined(__APPLE__)
    QFile plist(QDir::homePath() + "/Library/LaunchAgents/" + name + ".plist");

    if (plist.remove()) {
      if (init) log_debug_("ui", "autostart: removed %s\n", name.toUtf8().data());
      err = 0;
    } else {
      err = plist.exists() ? plist.error() : 0;
    }
#elif defined(__unix__)
    QString home = getConfigDir(true);

    if (!home.isEmpty()) {
      QFile file(home.append("/autostart/").append(name).append(".desktop"));

      if (file.remove()) {
        if (init) log_debug_("ui", "autostart: removed %s\n", name.toUtf8().data());
        err = 0;
      } else {
        err = file.exists() ? file.error() : 0;
      }
    }
#endif
  } else if ((autostart > 0 || user.isEmpty()) && !user.isNull()) {
#ifndef __APPLE__
    QString qs(getFileName(g_exeName));
#else
    QString qs(g_exeName);
#endif

    if (g_exeName.isEmpty()) {
      if (init) log_warn_("ui", "autostart: no EXE name\n");
      return -3;
    }
#ifndef __APPLE__
    for (int i = 0; i < mc_autostartArguments.size(); i++) qs.append(" ").append(getFileName(mc_autostartArguments[i]));
    if (!user.isEmpty()) qs.append(" -user ").append(getFileName(user));
    qs.append(" -autostart ").append(QString::number(SimParam::get("ui.login.autostart")));
#endif

    err = -1;
#ifdef _WIN32
    if ((err = RegOpenKeyExW(HKEY_CURRENT_USER, AUTOSTART_KEY, 0, KEY_SET_VALUE, &hkey)) == ERROR_SUCCESS) {
      err = RegSetValueExW(hkey, (wchar_t *)(name.utf16()), 0, REG_SZ,
                           (BYTE *)(qs.utf16()), (qs.size() + 1) * sizeof(wchar_t));
      RegCloseKey(hkey);
    }
    if (err == ERROR_SUCCESS && init) log_debug_("ui", "autostart: added %s\n", name.toUtf8().data());
#elif defined(__APPLE__)
    QSettings plist(QDir::homePath() + "/Library/LaunchAgents/" + name + ".plist", QSettings::NativeFormat);
    QStringList list(qs);

    for (int i = 0; i < mc_autostartArguments.size(); i++) list << mc_autostartArguments[i];
    if (!user.isEmpty()) list << "-user" << user;
    list << "-autostart" << QString::number(SimParam::get("ui.login.autostart"));

    err = 0;
    if (!init || plist.value("ProgramArguments").toStringList().join(' ') != list.join(' ')) {
      plist.setValue("Label", name);
      plist.setValue("ProgramArguments", list);
      plist.setValue("RunAtLoad", true);
      plist.sync();
      err = plist.status();
      if (init && !err) log_debug_("ui", "autostart: added %s\n", name.toUtf8().data());
    }
#elif defined(__unix__)
    QString home = getConfigDir(true);

    if (!home.isEmpty()) {
      QString dir = home.append("/autostart");
      QFile file(home.append("/").append(name).append(".desktop"));

      if (init) {
        err = 0;
        init = false;
        if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
          while (!file.atEnd()) {
            file.unsetError();
            QByteArray line = file.readLine();

            if (line.isEmpty()) {
              if (file.error()) break;
            } else if (QString(line).startsWith("Exec=" + qs + "\n")) {
              init = true;
              break;
            }
          }
          file.close();
        }
      }

      if (!init) {
        init = !err;
        QDir().mkdir(dir);
        file.remove();
        if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Unbuffered | QIODevice::Text)) {
          err = writeString(&file, "[Desktop Entry]\n");
          if (!err) err = writeString(&file, "Encoding=UTF-8\n");
          if (!err) err = writeString(&file, "Version=0.9.4\n");
          if (!err) err = writeString(&file, "Type=Application\n");
          if (!err) err = writeString(&file, "Name=" + name + "\n");
          if (!err) err = writeString(&file, "Comment=\n");
          if (!err) err = writeString(&file, "Exec=" + qs + "\n");
          if (!err) err = writeString(&file, "StartupNotify=false\n");
          if (!err) err = writeString(&file, "Terminal=false\n");
          if (!err) err = writeString(&file, "Hidden=false\n\n");
          if (!err && !file.flush()) err = file.error() ? file.error() : -3;
          file.close();
          if (err) {
            file.remove();
          } else if (init) {
            log_debug_("ui", "autostart: added %s\n", name.toUtf8().data());
          }
        } else {
          err = file.error();
        }
      }
    }
#endif
  }
  return err;
}

void Settings::setInvalidDevices(bool output, bool input, bool ring)
{
  mc_invalidOutput = output, mc_invalidInput = input, mc_invalidRing = ring;
}

void Settings::getInvalidDevices(bool * output, bool * input, bool * ring)
{
  *output = mc_invalidOutput, *input = mc_invalidInput, *ring = mc_invalidRing;
}

bool Settings::setAudioDevices(char selected)
{
  QByteArray output;
  QByteArray input;
  QByteArray ring;
  QString qs;
  simtype params = sim_table_new(3);
  int simres;
  int latency = selected ? ui->latencyValue->getValue() : m_latency;

  if (latency) sim_table_add_number(params, "audio.latency", latency < 0 ? -1 : latency);
  if (!selected) {
    sim_table_add(params, "audio.device.output", sim_string_copy(m_output.toUtf8().data()));
    sim_table_add(params, "audio.device.input", sim_string_copy(m_input.toUtf8().data()));
    sim_table_add(params, "audio.device.ring", sim_string_copy(m_ring.toUtf8().data()));
    simres = SimParam::set(params, true);
  } else {
    if (m_outputSelected >= 0 && ui->outputList) {
      qs = m_outputSelected ? ui->outputList->itemText(m_outputSelected) : "";
      if (!qs.startsWith(INVALID_DEVICE)) {
        output = qs.toUtf8();
        sim_table_add_pointer_length(params, "audio.device.output", output.data(), output.size());
      }
    }
    if (m_inputSelected >= 0 && ui->inputList) {
      qs = m_inputSelected ? ui->inputList->itemText(m_inputSelected) : "";
      if (!qs.startsWith(INVALID_DEVICE)) {
        input = qs.toUtf8();
        sim_table_add_pointer_length(params, "audio.device.input", input.data(), input.size());
      }
    }
    if (m_ringSelected >= 0 && ui->ringList) {
      qs = m_ringSelected ? ui->ringList->itemText(m_ringSelected) : "";
      if (!qs.startsWith(INVALID_DEVICE)) {
        ring = qs.toUtf8();
        sim_table_add_pointer_length(params, "audio.device.ring", ring.data(), ring.size());
      }
    }
    simres = SimParam::set(params, selected == 't');
  }

  if (simres != SIM_OK) {
    qs = tr("Setting audio devices not successful (%1)").arg(simres);
    qtfix::execMessageBox(true, qs, SimCore::getError(simres), this);
  }
  sim_table_free(params);
  return simres == SIM_OK;
}

void Settings::resetStatistics()
{
  mc_statisticsDownload.clear();
  mc_statisticsUpload.clear();
  onStatisticsTimeout();
  mc_statisticsIncrementTime = sim_get_tick();
}

double Settings::addStatistics(QList<double> * statistics, double value)
{
  double average = 0;
  statistics->push_back(value);
  if (statistics->size() > mc_statisticsInterval) statistics->removeAt(0);
  for (int i = statistics->size(); i--;) average += statistics->at(i);
  return average / statistics->size();
}

void Settings::on_statisticsButton_clicked()
{
  fixMessageBox mbox(QMessageBox::NoIcon, tr("Proxy statistics"), QString(), QMessageBox::Ok, this);
  mbox.setTextInteractionFlags(Qt::TextSelectableByMouse);
  mbox.setDefaultButton(QMessageBox::Ok);
  mbox.setEscapeButton(QMessageBox::Ok);
  connect(mbox.addNocloseButton(tr("Slow down"), QMessageBox::ResetRole), SIGNAL(clicked()),
          this, SLOT(onSlowdownButtonClicked()));
  connect(mbox.addNocloseButton(tr("Speed up"), QMessageBox::ResetRole), SIGNAL(clicked()),
          this, SLOT(onSpeedupButtonClicked()));
  m_statisticsBox = &mbox;
  onStatisticsTimeout();
  m_statisticsTimer.start(1000);
  mbox.exec();
  m_statisticsBox = 0;
  m_statisticsTimer.stop();
}

void Settings::on_autoButton_clicked()
{
  static const char * text[6][2] = {
    { QT_TR_NOOP("Auto logout"), QT_TR_NOOP("Auto login") },
    { QT_TR_NOOP("I am willing to enter my secret key each time I start Simphone."),
      QT_TR_NOOP("My filesystem is secure. I take full responsibility for this.") },
    { QT_TR_NOOP("Auto logout is now going to <b>encrypt your key</b> and other Simphone files"
                 " in order to protect them from anyone, who might have access to your filesystem."
                 " You will be asked to enter your secret key now and each time you start Simphone"
                 " from now on.<br/><br/>Are you sure you want to proceed this way?<br/><br/>"),
      QT_TR_NOOP("Auto login is now going to <b>decrypt your key</b> and other Simphone files so that"
                 " you will not be asked for your secret key on start. The key and your Simphone identity"
                 " will then be available to anyone, who has access to your user account at this computer."
                 "<br/><br/>Are you sure you want to proceed this way?<br/><br/>") },
    { QT_TR_NOOP("Check the box to confirm, that you are willing to enter your secret key."),
      QT_TR_NOOP("Check the box to confirm, that your filesystem is secure.") },
    { QT_TR_NOOP("Your key file has been successfully protected."),
      QT_TR_NOOP("Your key file has been unprotected successfully.") }
  };

  int simres = SIM_OK;

  for (;;) {
    QCheckBox * cbox = new QCheckBox(tr(text[1][m_password]));
    cbox->setStyleSheet(qApp->styleSheet() + "QCheckBox { font-weight: bold; }");
    if (!qtfix::execMessageBox(cbox, tr(text[0][m_password]), tr(text[2][m_password]), this, false)) return;
    bool ok = cbox->isChecked();
    delete cbox;
    if (ok) break;
    QMessageBox::critical(this, tr(text[0][m_password]), tr(text[3][m_password]));
  }

  if (!m_password) {
    Login login(0, 0);

    if (login.exec() != QDialog::Accepted) return;
  } else {
    simres = sim_key_set_password_("", SIM_PASSWORD_BIT_OVERWRITE);
  }

  if (simres == SIM_OK) {
    QMessageBox::information(this, tr(text[0][m_password]), tr(text[4][m_password]));
  } else {
    QString simerr = SimCore::getError(simres);
    qtfix::execMessageBox(true, tr("Password removal not successful (%1)").arg(simres), simerr, this);
  }

  on_tabWidget_currentChanged(ui->tabWidget->currentIndex());
}

void Settings::on_downloadButton_clicked()
{
  Transfers::changeDirectory(false);
}

void Settings::on_playButton_clicked()
{
  QList<QAbstractButton *> list = ui->sound->buttons();
  for (int i = 0; i < list.size(); i++) {
    QAbstractButton * checkBox = list[i];
    if (ui->tabWidget->focusWidget() == checkBox) {
      sim_sound_stop_(checkBox->objectName().toUtf8().data());
      int simres = sim_sound_start_(checkBox->objectName().toUtf8().data(), 1);

      checkBox->clearFocus();
      if (simres != SIM_OK) {
        QString error = tr("Playing of sound \"%1\" not successful (%2)").arg(checkBox->objectName()).arg(simres);
        qtfix::execMessageBox(true, error, SimCore::getError(simres), this);
      }
      return;
    }
  }
  QMessageBox::information(this, tr("Play sound"), tr("\nPlease, first right-click the sound you want to hear."));
}

void Settings::on_testButton_clicked()
{
  if (m_testing) {
    setAudioDevices(0);
    if (m_testing != 'e') sim_audio_hangup_(CONTACT_ID_TEST);
    ui->testButton->setText(tr("Start Audio Test"));
    m_testing = 0;
    enableNonAudioTabs(true);
    enableAudioSettings(true);
    ui->progressBar->setVisible(false);
    hideAudioSettings(true);
  } else if (!m_invalid) {
    setAudioDevices('t');
    int simres = sim_audio_call_(CONTACT_ID_TEST);
    if (simres != SIM_OK) {
      QString simerr = SimCore::getError(simres);
      setAudioDevices(0);
      qtfix::execMessageBox(true, tr("Opening audio device not successful (%1)").arg(simres), simerr, this);
      return;
    }
    ui->testButton->setText(tr("Stop Audio Test"));
    enableAudioSettings(false);
    enableNonAudioTabs(false);
    m_testing = 't';
    ui->progressBar->setMinimum(SIM_EVENT_SPEECH_MIN);
    ui->progressBar->setMaximum(SIM_EVENT_SPEECH_MAX);
    ui->progressBar->setValue(SIM_EVENT_SPEECH_MIN);
    hideAudioSettings(false);
    ui->listenLabel->setText(tr("<br/><br/><b>Please, listen to the voice message...</b><br/><br/>"));
  }
}

void Settings::on_enableButton_clicked()
{
  QStringList list;
  simtype params = sim_param_get_(SIM_PARAM_GET_WARNINGS);
  simwalker ctxParams;
  int count = 0;
  if (sim_table_walk_first(&ctxParams, params)) {
    simtype name;
    for (simtype val = sim_table_walk_next_number(&ctxParams, &name); sim_get_pointer(name);) {
      list << sim_get_pointer(name);
      count += SimParam::getDefault(sim_get_pointer(name), 0) != val.num;
      val = sim_table_walk_next_number(&ctxParams, &name);
    }
  }
  sim_param_free_(params);

  const QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
  QString qs = tr("By pushing <b>%1</b>, you will permanently enable all warning messages"
                  " you have disabled previously. You can then disable individual warnings again"
                  " by clicking their checkboxes as they appear.\n\nAre you sure you want to enable all warnings?");
  QString title = tr("Enable all warnings");
  qs = qs.arg(qApp->translate("QShortcut", "Yes"));
  if (!count || QMessageBox::question(this, title, qs, buttons, QMessageBox::Yes) == QMessageBox::Yes) {
    QStringListIterator iterator(list);
    params = sim_table_new(3);
    while (iterator.hasNext()) {
      QByteArray key = iterator.next().toUtf8();
      sim_table_add_key_number(params, sim_string_copy(key.data()), SimParam::getDefault(key.data(), 0));
    }

    int simres = SimParam::set(params, !count);
    if (simres != SIM_OK) {
      QString simerr = SimCore::getError(simres);
      qtfix::execMessageBox(true, tr("Setting of parameters not successful (%1)").arg(simres), simerr, this);
    } else {
      QMessageBox::information(this, title, tr("\nAll warning messages were already enabled."));
    }
    sim_table_free(params);
  }
}

void Settings::on_ciphersButton_clicked()
{
  QSet<QString> ciphers;
  simtype val = sim_nil();

  sim_param_get_limits_("crypto.ciphers", &val, 0, 0, 0);
  prepareCiphers(&ciphers, val);
  sim_array_free(val);
}

void Settings::on_buttonBox_accepted()
{
  //if (m_testing) on_testButton_clicked();

  // set Audio settings
  if (setAudioDevices('p') && m_code != 'l' && ui->echo->checkState() == Qt::Checked && ui->echoValue->value() > 0) {
    if (SimParam::getString("audio.device.input") != SimParam::getString("audio.device.output")) {
      QMessageBox::information(this, tr("Set audio devices"),
                               tr("You have set different input and output audio devices."
                                  "\n\nThis may or may not work, but echo cancellation probably won't."
                                  "\n\nUncheck or partially check echo cancellation, if you do not need it."));
    }
  }
  if (ui->tabWidget->count() == 1) {
    emit accept();
    return;
  }

  simtype params = sim_table_new(0);

  // set Window settings
  int n = ui->doubleclick->getValue() ? abs(ui->doubleclick->getValue()) : SimParam::getMaxValue("ui.main.doubleclick");
  sim_table_add_number(params, "ui.main.doubleclick", (ui->doubleclick->isChecked() ? 1 : -1) * n);
  if (QSystemTrayIcon::isSystemTrayAvailable() && SimParam::getDefault("ui.main.tray", 1) > 0) {
    sim_table_add_number(params, "ui.main.tray", (ui->tray->isChecked() ? 1 : -1) * abs(ui->tray->getValue()));
  }
  sim_table_add_number(params, "ui.main.popup", ui->popup2->isChecked() * 2 + ui->popup->isChecked());
  sim_table_add_number(params, "ui.main.taskbar", ui->taskbar->isChecked());
  sim_table_add_number(params, "ui.main.blink", ui->blink->getValue(ui->blink->isChecked() ? 2 : 1));

  if (ui->hidekey->isEnabled()) {
    int hidekey = abs(ui->hidekey->getValue() ? ui->hidekey->getValue() : SimParam::getDefault("ui.main.hidekey", 0));
    int showkey = abs(ui->showkey->getValue() ? ui->showkey->getValue() : SimParam::getDefault("ui.main.showkey", 0));
    sim_table_add_number(params, "ui.main.hidekey", (ui->hidekey->isChecked() ? 1 : -1) * hidekey);
    sim_table_add_number(params, "ui.main.showkey", (ui->hidekey->isChecked() ? 1 : -1) * showkey);
  }
  sim_table_add_number(params, "ui.main.autostart", ui->autostart->getValue(int(ui->autostart->isChecked())));

  // set Sounds settings
  QList<QAbstractButton *> list = ui->sound->buttons();
  for (int i = 0; i < list.size(); i++) {
    QString qs = "sound.play.";
    CheckBoxSetting * checkBox = (CheckBoxSetting *)list[i];
    int checked = checkBox->isTristate() || !checkBox->getValue() ? 7 : abs(checkBox->getValue());

    sim_table_add_key_number(params, sim_string_copy(qs.append(checkBox->objectName()).toUtf8().data()),
                             checkBox->getValue(checkBox->getCheckedState(-abs(checkBox->getValue()), 4, checked)));
  }

  // set Audio settings
  sim_table_add_number(params, "speex.echo", ui->echoValue->getValue());
  sim_table_add_number(params, "audio.sample", ui->sample0->getValue());

  // set Files settings
  n = (ui->notifications->isChecked() ? 1 : -1) * abs(ui->notifications->getValue());
  sim_table_add_number(params, "ui.xfer.notifications", n);
  sim_table_add_number(params, "file.wipe", (ui->wipe->isChecked() ? -1 : 1) * abs(ui->wipe->getValue()));
  sim_table_add_number(params, "xfer.wipe", !ui->keep->isChecked());
  if ((n = ui->minmbValue->getValue()) != 0) sim_table_add_number(params, "xfer.minmb", n);
  if ((n = ui->filesValue->getValue()) != 0) sim_table_add_number(params, "xfer.files", n);
  if ((n = ui->downloadValue->getValue()) != 0) sim_table_add_number(params, "xfer.download", n);
  if ((n = ui->uploadValue->getValue()) != 0) sim_table_add_number(params, "xfer.upload", n);

  if (ui->directory->isDirty()) {
    simtype val = sim_pointer_new("");
    if (ui->directory->isChecked()) sim_param_get_limits_("xfer.sent", &val, 0, 0, 0);
    sim_table_add(params, "xfer.sent", val);
  }
  sim_table_add_number(params, "xfer.md5", ui->md5->isChecked());

  // set Chat settings
  if (ui->away->isEnabled() && (n = ui->awayValue->getValue()) != 0) sim_table_add_number(params, "ui.main.away", n);
  if ((n = ui->editValue->getValue()) != 0) sim_table_add_number(params, "msg.edit", n);

  sim_table_add_number(params, "ui.chat.altarrow", ui->altarrow->isChecked());
  sim_table_add_number(params, "ui.chat.ctrlenter", ui->ctrlenter->isChecked());
  if (ui->doublecopy->isDirty()) {
    sim_table_add_number(params, "ui.chat.doubleclick", ui->doublecopy->isChecked());
    sim_table_add_number(params, "ui.console.doubleclick", ui->doublecopy->isChecked());
  }
  sim_table_add_number(params, "rights.history", ui->history->isChecked());
  sim_table_add_number(params, "rights.type", (ui->typing->isChecked() ? 1 : -1) * abs(ui->typing->getValue()));

  // set Network settings
  if ((n = ui->speedValue->getValue()) != 0) sim_table_add_number(params, "limit.maxspeed", n);
  if ((n = ui->portValue->getValue()) != 0) sim_table_add_number(params, "main.port", n);
  if ((n = ui->torValue->getValue()) != 0) sim_table_add_number(params, "net.tor.port", n);

  QString qs = ui->host->isChecked() ? "" : ".";
  sim_table_add(params, "client.host", sim_string_copy(qs.append(ui->hostValue->text()).toUtf8().data()));
  if ((n = ui->blacklistValue->getValue()) != 0) sim_table_add_number(params, "main.blacklist", n);

  // set Security settings
  list = ui->ciphers->buttons();
  for (int i = 0; i < list.size(); i++) {
    if (list[i]->isChecked()) m_preferredCiphers.insert(list[i]->objectName());
  }

  QSetIterator<QString> iterator(m_preferredCiphers);
  simtype ciphers = sim_array_new_strings(m_preferredCiphers.size());
  for (unsigned i = 1; i <= sim_get_length(ciphers); i++) {
    sim_array_set(ciphers, i, sim_string_copy(iterator.next().toUtf8().data()));
  }
  sim_table_add(params, "crypto.ciphers", ciphers);

  sim_table_add_number(params, "contact.strangers", ui->contact0->getValue());

  // done
  int simres = SimParam::set(params, false);
  QString simerr = SimCore::getError(simres);
  sim_table_free(params);

  Contacts::get()->rereadSettings();
  SimCore::get()->readSettings();
  if (simres != SIM_OK) {
    qtfix::execMessageBox(true, tr("Setting of parameters not successful (%1)").arg(simres), simerr, this);
    return;
  }

  emit accept();

  if (ui->autostart->isDirty() && (simres = setAutostart(SimParam::get("ui.main.autostart"), false)) != 0) {
    //SimParam::set("ui.main.autostart", !SimParam::get("ui.main.autostart"), false);
    qs = tr("Simphone could not write to the %1 registry.").arg(m_os);
    qtfix::execMessageBox(true, tr("Setting up autostart not successful (error %1)").arg(simres), qs, this);
  }
}

void Settings::on_buttonBox_rejected()
{
  //if (m_testing) on_testButton_clicked();
  emit reject();
}

void Settings::on_outputList_activated(int ndx)
{
  m_outputSelected = ndx;
  if (ui->outputList->itemText(ndx).startsWith(INVALID_DEVICE)) {
    m_invalid |= INV_output;
  } else {
    m_invalid &= ~INV_output;
  }
  ui->testButton->setEnabled(!m_invalid);
}

void Settings::on_inputList_activated(int ndx)
{
  m_inputSelected = ndx;
  if (ui->inputList->itemText(ndx).startsWith(INVALID_DEVICE)) {
    m_invalid |= INV_input;
  } else {
    m_invalid &= ~INV_input;
  }
  ui->testButton->setEnabled(!m_invalid);
}

void Settings::on_ringList_activated(int ndx)
{
  m_ringSelected = ndx;
  if (ui->ringList->itemText(ndx).startsWith(INVALID_DEVICE)) {
    m_invalid |= INV_ring;
  } else {
    m_invalid &= ~INV_ring;
  }
  ui->testButton->setEnabled(!m_invalid);
}

void Settings::on_tabWidget_currentChanged(int index)
{
  int simres = sim_key_set_password_(0, SIM_PASSWORD_BIT_CHECK);
  m_password = simres == SIM_OK;

  ui->enableButton->setVisible(ui->tabWidget->indexOf(ui->window) == index);
  ui->playButton->setVisible(ui->tabWidget->indexOf(ui->sounds) == index);
  ui->testButton->setVisible(ui->tabWidget->indexOf(ui->audio) == index);
  ui->downloadButton->setVisible(ui->tabWidget->indexOf(ui->file) == index);
  ui->autoButton->setText(simres == SIM_API_INIT ? "" : m_password ? tr("Auto Login") : tr("Auto Logout"));
  ui->autoButton->setVisible(simres != SIM_API_INIT && ui->tabWidget->indexOf(ui->security) == index);
  ui->statisticsButton->setVisible(ui->tabWidget->indexOf(ui->network) == index);
}

void Settings::onSlowdownButtonClicked()
{
  if (!mc_statisticsDecrement && mc_statisticsIncrementTime && sim_get_tick() - mc_statisticsIncrementTime < 500) {
    mc_statisticsIncrement++;
  } else {
    mc_statisticsIncrement = 1;
  }
  mc_statisticsInterval += mc_statisticsIncrement;
  if (mc_statisticsInterval > 86400) {
    mc_statisticsInterval = 86400;
    mc_statisticsIncrement = 1;
  }
  resetStatistics();
  mc_statisticsDecrement = false;
}

void Settings::onSpeedupButtonClicked()
{
  if (mc_statisticsInterval > 1) {
    if (mc_statisticsDecrement && mc_statisticsIncrementTime && sim_get_tick() - mc_statisticsIncrementTime < 500) {
      mc_statisticsIncrement++;
    } else {
      mc_statisticsIncrement = 1;
    }
    if (mc_statisticsInterval - mc_statisticsIncrement < 1) {
      mc_statisticsInterval = mc_statisticsIncrement = 1;
    } else {
      mc_statisticsInterval -= mc_statisticsIncrement;
    }
    resetStatistics();
  } else {
    mc_statisticsIncrement = 1;
    mc_statisticsIncrementTime = 0;
  }
  mc_statisticsDecrement = true;
}

void Settings::onStatisticsTimeout()
{
  QString text = "<p><br/><table><tr><td>&nbsp;</td><td><b>";
  text.append(tr("Current:")).append("</b>&nbsp;&nbsp;</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td><b>");
  text.append(tr("Average:")).append("</b>&nbsp;</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td><b>");
  text.append(tr("Previous:")).append("</b>&nbsp;</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr><tr></tr><tr><td>");
  QString qs;
  QByteArray kbps = tr("KBPS").toUtf8();
  simtype contact = sim_contact_get_(CONTACT_ID_PROXY, CONTACT_BIT_STATS);
  simnumber t = sim_table_get_number(contact, CONTACT_KEY_THIS_INPUT_TIME);
  simnumber t1 = sim_table_get_number(contact, CONTACT_KEY_ALL_INPUT_TIME);
  simnumber t2 = sim_table_get_number(contact, CONTACT_KEY_THIS_CLIENT_TIME);
  simnumber t0 = m_statisticsTimer.isActive() ? t2 : 0;
  simnumber n;
  double d;

  n = sim_table_get_number(contact, CONTACT_KEY_THIS_CLIENT_RECEIVED);
  d = addStatistics(&mc_statisticsDownload, double(t0 ? 125 * n / t0 : 0) / 128.0);
  qs.sprintf("<b>%s</b>&nbsp;</td><td><nobr>%.3f&nbsp;%s</nobr>", tr("Download:").toUtf8().data(), d, kbps.data());
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  n = sim_table_get_number(contact, CONTACT_KEY_THIS_INPUT_RECEIVED);
  qs.sprintf("<nobr>%.3f&nbsp;%s</nobr>", double(t ? 125 * n / t : 0) / 128.0, kbps.data());
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  n = sim_table_get_number(contact, CONTACT_KEY_ALL_INPUT_RECEIVED);
  qs.sprintf("<nobr>%.3f&nbsp;%s</nobr>", double(t1 ? 125 * n / t1 : 0) / 128.0, kbps.data());
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr><tr></tr><tr><td>");

  n = sim_table_get_number(contact, CONTACT_KEY_THIS_CLIENT_SENT);
  d = addStatistics(&mc_statisticsUpload, double(t0 ? 125 * n / t0 : 0) / 128.0);
  qs.sprintf("<b>%s</b>&nbsp;</td><td><nobr>%.3f&nbsp;%s</nobr>", tr("Upload:").toUtf8().data(), d, kbps.data());
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  n = sim_table_get_number(contact, CONTACT_KEY_THIS_INPUT_SENT);
  qs.sprintf("<nobr>%.3f&nbsp;%s</nobr>", double(t ? 125 * n / t : 0) / 128.0, kbps.data());
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  n = sim_table_get_number(contact, CONTACT_KEY_ALL_INPUT_SENT);
  qs.sprintf("<nobr>%.3f&nbsp;%s</nobr>", double(t1 ? 125 * n / t1 : 0) / 128.0, kbps.data());
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr><tr></tr><tr><td>");

  n = sim_table_get_number(contact, CONTACT_KEY_THIS_CLIENT_COUNT);
  qs.sprintf("<b>%s</b>&nbsp;</td><td>%d", tr("Connections:").toUtf8().data(), int(n));
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  qs.sprintf("%d", int(sim_table_get_number(contact, CONTACT_KEY_THIS_INPUT_COUNT)));
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  qs.sprintf("%d", int(sim_table_get_number(contact, CONTACT_KEY_ALL_INPUT_COUNT)));
  text.append(qs).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr><tr></tr><tr><td><b>");
  text.append(tr("Time:")).append("</b>&nbsp;</td><td>");

  text.append(Contact::convertTimeToString(mc_statisticsInterval)).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  text.append(Contact::convertTimeToString((t + 500) / 1000)).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td><td>");
  t1 /= 1000;
  text.append(Contact::convertTimeToString(t1)).append("</td><td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr></table><br/>");

  n = sim_table_get_number(contact, CONTACT_KEY_PROXY_SPEED_LIMIT);
  int m = ui->speed->isChecked() ? ui->speedValue->value() * 1024 : -1;

  if (n && m > 0) {
    if (n > m) n = ui->speedValue->value() * 1024;
    qs = tr("%1 of %2 kilobytes per second").arg((n + 512) / 1024).arg((n + 512) / 1024);
  } else {
    int max = SimParam::getMaxValue("limit.maxspeed");
    qs = tr("%1 kilobytes per second").arg(((n ? n : m > 0 ? m : (max + 1) * 1024) + 512) / 1024);
  }
  text.append("<br/><b>").append(tr("Speed limit:")).append("</b> ").append(qs).append("<br/></p>");

  sim_contact_free_(contact);
  if (m_statisticsBox) qtfix::updateMessageBox(m_statisticsBox, text);
}

void Settings::onSignalContactAudioChanged(unsigned id, SimAudioState newState)
{
  if (newState == audio_hangup) {
    enableAudioTab(true);
    if (m_testing && int(id) == SimCore::get()->getTestContactId()) {
      m_testing = 'e';
      on_testButton_clicked();
    }
  }
}

void Settings::onSignalInvalidAudioDevice(bool valid)
{
  if (!valid) {
    if (!ui->audio->isEnabled()) {
      QString qs = "<p><br/><span style=\" color:#ff0000;\">";
      qs.append(tr("Some of your chosen audio devices have become invalid.")).append("</span><br/><br/>");
      qs.append(tr("Plug the device back in or hang up your audio call and"
                   " choose a valid device from the \"Audio\" tab of \"Settings\"."));
      QMessageBox::warning(this, tr("Choose audio devices"), qs.append("<br/></p>"));
    } else {
      ui->tabWidget->setCurrentWidget(ui->audio);
    }
  }
  ui->outputList->clear();
  ui->inputList->clear();
  ui->ringList->clear();
  prepareAudio(valid);
}

void Settings::onSignalSpeech(unsigned id, int, int probability, int)
{
  if (m_testing != 't' || int(id) != SimCore::get()->getTestContactId()) return;

  if (probability == SIM_EVENT_SPEECH_END) {
    ui->progressBar->setVisible(false);
    ui->listenLabel->setText(tr("<br/><br/><b>Recording test has ended.</b><br/>"
                                "<b>You should hear your own voice now.</b><br/>"));
  } else {
    hideAudioSettings(false);
    ui->listenLabel->setText(tr("<br/><b>Please, speak into the microphone...</b><br/>"));
    ui->progressBar->setValue(probability);
    ui->progressBar->setVisible(true);
  }
}

void Settings::hideAudioSettings(bool show)
{
  ui->echo->setVisible(show);
  ui->echo->setEnabled(m_code != 'i' && m_code != 'l');
  ui->echoValue->setVisible(show);
  ui->echoValue->setEnabled(m_code != 'i' && m_code != 'l');
  ui->echoLabel->setVisible(show);
  ui->echoLabel->setEnabled(m_code != 'i' && m_code != 'l');
  ui->latency->setVisible(show);
  ui->latency->setEnabled(m_code != 'i');
  ui->latencyValue->setVisible(show);
  ui->latencyValue->setEnabled(m_code != 'i');
  ui->latencyLabel->setVisible(show);
  ui->latencyLabel->setEnabled(m_code != 'i');
  ui->listenLabel->setText(show ? "<br/>" : "<br/><br/><br/><br/>");
}

void Settings::hideNonAudioTabs()
{
  for (int i = ui->tabWidget->count(); i--;) {
    if (ui->tabWidget->widget(i) != ui->audio) ui->tabWidget->removeTab(i);
  }
  ui->label->hide();
  ui->sample0->hide();
  ui->sample8->hide();
  ui->sample16->hide();
  ui->sample32->hide();
}

void Settings::enableNonAudioTabs(bool enable)
{
  for (int i = ui->tabWidget->count(); i--;) {
    ui->tabWidget->setTabEnabled(i, enable || ui->tabWidget->indexOf(ui->audio) == i);
  }
}

void Settings::enableAudioTab(bool enable)
{
  if (!enable && ui->tabWidget->currentWidget() == ui->audio) ui->tabWidget->setCurrentWidget(ui->window);
  ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(ui->audio), enable);
}

void Settings::enableAudioSettings(bool enable)
{
  ui->buttonBox->setEnabled(enable);
  ui->outputList->setEnabled(enable);
  ui->inputList->setEnabled(enable);
  ui->ringList->setEnabled(enable);
  ui->outputLabel->setEnabled(enable);
  ui->inputLabel->setEnabled(enable);
  ui->ringLabel->setEnabled(enable);
  ui->label->setEnabled(enable);
  ui->sample0->setEnabled(enable);
  ui->sample8->setEnabled(enable);
  ui->sample16->setEnabled(enable);
  ui->sample32->setEnabled(enable);
  ui->echo->setEnabled(enable);
  ui->echoValue->setEnabled(enable);
  ui->echoLabel->setEnabled(enable);
  ui->latency->setEnabled(enable);
  ui->latencyValue->setEnabled(enable);
  ui->latencyLabel->setEnabled(enable);
}
