/*
 * Deskflow -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2008 Volker Lanz (vl@fidra.de)
 *
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 *
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "KeySequence.h"

#include <QtCore>
#include <QtGui>

// this table originally comes from Qt sources (gui/kernel/qkeysequence.cpp)
// and is heavily modified for Deskflow
static const struct {
  int key;
  const char *name;
} keyname[] = {
    {Qt::Key_Space, "Space"},
    {Qt::Key_Escape, "Escape"},
    {Qt::Key_Tab, "Tab"},
    {Qt::Key_Backtab, "LeftTab"},
    {Qt::Key_Backspace, "BackSpace"},
    {Qt::Key_Return, "Return"},
    {Qt::Key_Insert, "Insert"},
    {Qt::Key_Delete, "Delete"},
    {Qt::Key_Pause, "Pause"},
    {Qt::Key_Print, "Print"},
    {Qt::Key_SysReq, "SysReq"},
    {Qt::Key_Home, "Home"},
    {Qt::Key_End, "End"},
    {Qt::Key_Left, "Left"},
    {Qt::Key_Up, "Up"},
    {Qt::Key_Right, "Right"},
    {Qt::Key_Down, "Down"},
    {Qt::Key_PageUp, "PageUp"},
    {Qt::Key_PageDown, "PageDown"},
    {Qt::Key_CapsLock, "CapsLock"},
    {Qt::Key_NumLock, "NumLock"},
    {Qt::Key_ScrollLock, "ScrollLock"},
    {Qt::Key_Menu, "Menu"},
    {Qt::Key_Help, "Help"},
    {Qt::Key_Enter, "KP_Enter"},
    {Qt::Key_Clear, "Clear"},

    {Qt::Key_Back, "WWWBack"},
    {Qt::Key_Forward, "WWWForward"},
    {Qt::Key_Stop, "WWWStop"},
    {Qt::Key_Refresh, "WWWRefresh"},
    {Qt::Key_VolumeDown, "AudioDown"},
    {Qt::Key_VolumeMute, "AudioMute"},
    {Qt::Key_VolumeUp, "AudioUp"},
    {Qt::Key_MediaPlay, "AudioPlay"},
    {Qt::Key_MediaStop, "AudioStop"},
    {Qt::Key_MediaPrevious, "AudioPrev"},
    {Qt::Key_MediaNext, "AudioNext"},
    {Qt::Key_HomePage, "WWWHome"},
    {Qt::Key_Favorites, "WWWFavorites"},
    {Qt::Key_Search, "WWWSearch"},
    {Qt::Key_Standby, "Sleep"},
    {Qt::Key_LaunchMail, "AppMail"},
    {Qt::Key_LaunchMedia, "AppMedia"},
    {Qt::Key_Launch0, "AppUser1"},
    {Qt::Key_Launch1, "AppUser2"},
    {Qt::Key_Select, "Select"},

    {0, 0}};

KeySequence::KeySequence() : m_Sequence(), m_Modifiers(0), m_IsValid(false) {}

bool KeySequence::isMouseButton() const {
  return !m_Sequence.isEmpty() && m_Sequence.last() < Qt::Key_Space;
}

QString KeySequence::toString() const {
  QString result;

  for (int i = 0; i < m_Sequence.size(); i++) {
    result += keyToString(m_Sequence[i]);

    if (i != m_Sequence.size() - 1)
      result += "+";
  }

  return result;
}

bool KeySequence::appendMouseButton(int button) { return appendKey(button, 0); }

bool KeySequence::appendKey(int key, int modifiers) {
  if (m_Sequence.size() == 4)
    return true;

  switch (key) {
  case Qt::Key_AltGr:
    return false;

  case Qt::Key_Control:
  case Qt::Key_Alt:
  case Qt::Key_Shift:
  case Qt::Key_Meta:
  case Qt::Key_Menu: {
    int mod = modifiers & (~m_Modifiers);
    if (mod) {
      m_Sequence.append(mod);
      m_Modifiers |= mod;
    }
  } break;

  default:
    // see if we can handle this key, if not, don't accept it
    if (keyToString(key).isEmpty())
      break;

    m_Sequence.append(key);
    setValid(true);
    return true;
  }

  return false;
}

void KeySequence::loadSettings(QSettings &settings) {
  sequence().clear();
  int num = settings.beginReadArray("keys");
  for (int i = 0; i < num; i++) {
    settings.setArrayIndex(i);
    sequence().append(settings.value("key", 0).toInt());
  }
  settings.endArray();

  setModifiers(0);
  setValid(true);
}

void KeySequence::saveSettings(QSettings &settings) const {
  settings.beginWriteArray("keys");
  for (int i = 0; i < sequence().size(); i++) {
    settings.setArrayIndex(i);
    settings.setValue("key", sequence()[i]);
  }
  settings.endArray();
}

QString KeySequence::keyToString(int key) {
  // nothing there?
  if (key == 0)
    return "";

  // a hack to handle mouse buttons as if they were keys
  if (key < Qt::Key_Space) {
    switch (key) {
    case Qt::LeftButton:
      return "1";
    case Qt::RightButton:
      return "2";
    case Qt::MiddleButton:
      return "3";
    }

    return "4"; // qt only knows three mouse buttons, so assume it's an unknown
                // fourth one
  }

  // modifiers?
  if (key & Qt::ShiftModifier)
    return "Shift";

  if (key & Qt::ControlModifier)
    return "Control";

  if (key & Qt::AltModifier)
    return "Alt";

  if (key & Qt::MetaModifier)
    return "Meta";

  // treat key pad like normal keys (FIXME: we should have another lookup table
  // for keypad keys instead)
  key &= ~Qt::KeypadModifier;

  // a printable 7 bit character?
  if (key < 0x80 && key != Qt::Key_Space)
    return QChar(key & 0x7f).toLower();

  // a function key?
  if (key >= Qt::Key_F1 && key <= Qt::Key_F35)
    return QString::fromUtf8("F%1").arg(key - Qt::Key_F1 + 1);

  // a special key?
  int i = 0;
  while (keyname[i].name) {
    if (key == keyname[i].key)
      return QString::fromUtf8(keyname[i].name);
    i++;
  }

  // representable in ucs2?
  if (key < 0x10000)
    return QString("\\u%1").arg(
        QChar(key).toLower().unicode(), 4, 16, QChar('0'));

  // give up, deskflow probably won't handle this
  return "";
}

bool KeySequence::operator==(const KeySequence &ks) const {
  return m_Sequence == ks.m_Sequence && m_Modifiers == ks.m_Modifiers &&
         m_IsValid == ks.m_IsValid;
}
