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

    class ConsoleDialog (QDialog): open a console in login screen
    class MouseEntropyCollector (QObject): collect entropy from mouse movements while audio entropy is being collected

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

#include "consoledialog.h"
#include "ui_consoledialog.h"

#include "contacts.h"
#include "chatframe.h"
#include "qtfix.h"

#include <QKeyEvent>

MouseEntropyCollector::MouseEntropyCollector(QWidget * parent)
  : m_posX(0)
  , m_posY(0)
  , m_count(0)
  , m_timer(parent)
{
  m_timer.setTimerType(Qt::PreciseTimer);
  connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimerTimeout()));
}

void MouseEntropyCollector::start()
{
  m_posX = QCursor::pos().x();
  m_posY = QCursor::pos().y();
  m_count = 0;
  m_elapsedTime.start();
  m_timer.start(1);
}

QByteArray MouseEntropyCollector::stop()
{
  m_timer.stop();
  return getEntropy();
}

void MouseEntropyCollector::clearEntropy()
{
  m_entropy.fill(0);
  m_entropy.clear();
}

QByteArray MouseEntropyCollector::getEntropy()
{
  QByteArray bytes((m_entropy.size() + 7) / 8, 0);
  for (int i = 0; i < m_entropy.size(); i++) {
    bytes[i >> 3] = char(bytes.at(i >> 3) | m_entropy.at(i) << (i & 7));
  }
  return bytes;
}

void MouseEntropyCollector::addEntropy(unsigned value)
{
  while (value) {
    m_entropy.append(value & 1);
    value >>= 1;
  }
}

void MouseEntropyCollector::onTimerTimeout()
{
  QPoint pos = QCursor::pos();
  int x = pos.x();
  int y = pos.y();
  m_count++;
  if (x != m_posX || y != m_posY) {
    m_posX -= x;
    addEntropy(abs(m_posX));
    m_entropy.append(char(m_posX > 0));
    m_posY -= y;
    addEntropy(abs(m_posY));
    m_entropy.append(char(m_posY > 0));
    addEntropy(m_count - 1);
    m_count -= int(m_elapsedTime.restart());
    addEntropy(abs(m_count));
    m_posX = x;
    m_posY = y;
    m_count = 0;
  }
}

ConsoleDialog::ConsoleDialog(QWidget * parent, bool collect)
  : QDialog(parent)
  , ui(new Ui::ConsoleDialog)
  , m_collector(0)
{
  ui->setupUi(this);
  setWindowFlags(Qt::WindowMaximizeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
  int id = SimCore::get()->getTestContactId();
  if (id >= 0) {
    Contact * contact = SimCore::getContact(id);
    if (contact) {
      if (ChatFrame * frame = ChatFrames::get()->getFrame(contact)) {
        delete ui->frame;
        frame->setParent(this);
        ui->frame = frame;
        ui->verticalLayout->addWidget(frame);
        setWindowIcon(QIcon(":/consoleIcon"));
        frame->show();
        connect(frame, SIGNAL(signalContactQuit()), this, SLOT(close()));
        frame->installFilter(this);
        if (collect) {
          m_collector = new MouseEntropyCollector(this);
          connect(SimCore::get(), SIGNAL(signalSpeech(unsigned, int, int, int)),
                  this, SLOT(onSignalSpeech(unsigned, int, int, int)));
          connect(SimCore::get(), SIGNAL(signalContactAudioChanged(unsigned, SimAudioState)),
                  this, SLOT(onSignalContactAudioChanged(unsigned, SimAudioState)));
        }
      }
    }
  }
  setWindowTitle(tr("Simphone console"));
}

ConsoleDialog::~ConsoleDialog()
{
  simtype params = sim_table_new(4);

  if (ui->frame) {
    ((ChatFrame *)ui->frame)->getSettings(params);
    SimParam::set(params);
  }
  sim_table_free(params);

  delete m_collector;
  if (ui->frame) {
    ui->frame->setParent(0);
    ((ChatFrame *)ui->frame)->removeFilter(this);
    ui->frame = 0;
  }
  delete ui;
}

void ConsoleDialog::onSignalSpeech(unsigned, int, int probability, int)
{
  if (probability == SIM_EVENT_SPEECH_END) {
    QByteArray bytes = m_collector->getEntropy();
    sim_key_set_entropy_(bytes.data(), bytes.size());
    bytes.fill(0);
  }
}

void ConsoleDialog::onSignalContactAudioChanged(unsigned, SimAudioState newState)
{
  if (newState == audio_talk) {
    m_collector->start();
  } else if (newState == audio_hangup) {
    QByteArray bytes = m_collector->stop();
    sim_key_set_entropy_(bytes.data(), bytes.size());
    bytes.fill(0);
  }
}

bool ConsoleDialog::checkKbd(QKeyEvent * event)
{
  if (!qtfix::hasAltModifier(event, Qt::AltModifier | Qt::MetaModifier) || event->key() != Qt::Key_Q) return false;

  done(-1);
  return true;
}

bool ConsoleDialog::eventFilter(QObject * obj, QEvent * event)
{
  if (event->type() == QEvent::KeyPress) {
    QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
    if (checkKbd(keyEvent)) return true;
  }

  return Parent::eventFilter(obj, event);
}

void ConsoleDialog::keyPressEvent(QKeyEvent * event)
{
  if (!checkKbd(event) && event->key() != Qt::Key_Escape) QDialog::keyPressEvent(event);
}
