#include "qt.h"
#include <main.h>
#include <QtDebug>
#include <QTextDocument>
#include <QQuickTextDocument>
#include <QNetworkInterface>
#include <QHostAddress>

#if (defined Q_OS_UNIX) && !(defined NO_USB)
  #include "usb/usb.h"
#endif

#ifdef PLUGIN
  #include <ecl_fun_plugin.h>
#else
  #include <ecl_fun.h>
#endif

Q_DECLARE_METATYPE (RegularExpression*)
Q_DECLARE_METATYPE (TextBlock*)
Q_DECLARE_METATYPE (TextCharFormat*)
Q_DECLARE_METATYPE (TextCursor*)
Q_DECLARE_METATYPE (SyntaxHighlighter*)
Q_DECLARE_METATYPE (QTextDocument*)

QObject* ini() {
  static QObject* qt = nullptr;
  if (qt == nullptr) {
    qt = new QT;
#ifdef PLUGIN
    ini_lisp(); // see 'ecl_fun_plugin.h'
#endif
  }
  return qt;
}

SyntaxHighlighter::SyntaxHighlighter(QTextDocument* doc)
  : QSyntaxHighlighter(doc) {}

void SyntaxHighlighter::highlightBlock(const QString& text) { // override
  ecl_fun("editor:highlight-block", VAR(SyntaxHighlighter*, this), text);
}

// new 

QVariant QT::makeObject(const QVariant& vName, const QVariant& vArg) {
  QString name(vName.toString());
  QVariant vObject;
  if (name == "QTextCharFormat") {
    vObject = VAR_NEW(TextCharFormat);
  } else if (name == "QSyntaxHighlighter") {
    auto doc = VAL(vArg, QTextDocument*);
    if (doc != nullptr) {
      vObject = VAR_NEW_1(SyntaxHighlighter, doc);
    }
  } else if (name == "QRegularExpression") {
    vObject = VAR_NEW(RegularExpression);
  }
  return vObject;
}

// connect

QVariant QT::connectDocumentChanged(const QVariant& vDocument, const QVariant& vWhich) {
  auto doc = VAL(vDocument, QTextDocument*);
  if (doc != nullptr) {
    QString which = vWhich.toString();
    if (which == "edit") {
      connect(doc, &QTextDocument::blockCountChanged,
              [](int count) {
                ecl_fun("editor:edit-line-count-changed", count);
              });
      connect(doc, &QTextDocument::contentsChanged,
              []() {
                ecl_fun("editor:edit-contents-changed");
              });
    } else if (which == "command") {
      connect(doc, &QTextDocument::blockCountChanged,
              [](int count) {
                ecl_fun("editor:command-line-count-changed", count);
              });
      connect(doc, &QTextDocument::contentsChanged,
              []() {
                ecl_fun("editor:command-contents-changed");
              });
    }
    connect(doc, &QTextDocument::cursorPositionChanged,
            [](const QTextCursor& cursor) {
              TextCursor cursor2(cursor);
              ecl_fun("editor:cursor-position-changed", VAR(TextCursor*, &cursor2));
            });
    return true;
  }
  return false;
}

QVariant QT::connectKeyPressed() {
#ifdef Q_OS_IOS
  GuiApplication* sender = static_cast<GuiApplication*>(qGuiApp);
  connect(sender, &GuiApplication::keyPressed,
          [](const QString& key, const QString& objectName) {
            ecl_fun("editor:key-pressed", key, objectName);
          });
#endif
  return QVariant();
}


// methods
//
// pointers stored in a QVariant are type checked (if QObject derived)

QVariant QT::block2(const QVariant& vCursor) {
  auto cursor = VAL(vCursor, TextCursor*);
  if (cursor != nullptr) {
    TextBlock* tmp = new TextBlock(cursor->block());
    DELETE_LATER(tmp);
    return VAR(TextBlock*, tmp);
  }
  return QVariant();
}

QVariant QT::blockNumber(const QVariant& vBlock) {
  auto block = VAL(vBlock, TextBlock*);
  if (block != nullptr) {
    return block->blockNumber();
  }
  return QVariant();
}

QVariant QT::characterAt(const QVariant& vDocument, const QVariant& vPos) {
  auto document = VAL(vDocument, QTextDocument*);
  if (document != nullptr) {
    return VAR(QChar, document->characterAt(vPos.toInt()));
  }
  return QVariant();
}

QVariant QT::clearUndoRedoStacks(const QVariant& vDocument) {
  auto document = VAL(vDocument, QTextDocument*);
  if (document != nullptr) {
    document->clearUndoRedoStacks();
  }
  return QVariant();
}

QVariant QT::currentBlockState(const QVariant& vHighlighter) {
  auto highlighter = VAL(vHighlighter, SyntaxHighlighter*);
  if (highlighter != nullptr) {
    return highlighter->currentBlockState();
  }
  return QVariant();
}

QVariant QT::exactMatch(const QVariant& vRegExp, const QVariant& vText) {
  auto regExp = VAL(vRegExp, RegularExpression*);
  if (regExp != nullptr) {
    QString text = vText.toString();
    return (regExp->match(text).capturedLength() == text.length());
  }
  return QVariant();
}

QVariant QT::find2(const QVariant& vDocument, const QVariant& vWhat, const QVariant& vPos) {
  auto document = VAL(vDocument, QTextDocument*);
  if (document != nullptr) {
#if QT_VERSION < 0x060000
    const int type = vWhat.type();
#else
    const int type = vWhat.typeId();
#endif
    QTextCursor cursor;
    if (type == QMetaType::QString) {
      cursor = document->find(vWhat.toString(), vPos.toInt());
    } else {
      auto regexp = VAL(vWhat, RegularExpression*);
      if (regexp != nullptr) {
        cursor = document->find(*regexp, vPos.toInt());
      }
    }
    return QVariantList()
             << cursor.selectionStart()
             << cursor.selectionEnd()
             << cursor.selectedText();
  }
  return QVariant();
}

QVariant QT::findBlockByLineNumber(const QVariant& vDocument, const QVariant& vNumber) {
  auto document = VAL(vDocument, QTextDocument*);
  if (document != nullptr) {
    TextBlock* tmp = new TextBlock(document->findBlockByLineNumber(vNumber.toInt()));
    DELETE_LATER(tmp);
    return VAR(TextBlock*, tmp);
  }
  return QVariant();
}

QVariant QT::lineCount(const QVariant& vDocument) {
  auto document = VAL(vDocument, QTextDocument*);
  if (document != nullptr) {
    return document->lineCount();
  }
  return QVariant();
}

QVariant QT::next(const QVariant& vBlock) {
  auto block = VAL(vBlock, TextBlock*);
  if (block != nullptr) {
    TextBlock* tmp = new TextBlock(block->next());
    DELETE_LATER(tmp);
    return VAR(TextBlock*, tmp);
  }
  return QVariant();
}

QVariant QT::position2(const QVariant& vCursor) {
  auto cursor = VAL(vCursor, TextCursor*);
  if (cursor != nullptr) {
    return cursor->position();
  }
  return QVariant();
}

QVariant QT::positionInBlock(const QVariant& vCursor) {
  auto cursor = VAL(vCursor, TextCursor*);
  if (cursor != nullptr) {
    return cursor->positionInBlock();
  }
  return QVariant();
}

QVariant QT::previous(const QVariant& vBlock) {
  auto block = VAL(vBlock, TextBlock*);
  if (block != nullptr) {
    TextBlock* tmp = new TextBlock(block->previous());
    DELETE_LATER(tmp);
    return VAR(TextBlock*, tmp);
  }
  return QVariant();
}

QVariant QT::previousBlockState(const QVariant& vHighlighter) {
  auto highlighter = VAL(vHighlighter, SyntaxHighlighter*);
  if (highlighter != nullptr) {
    return highlighter->previousBlockState();
  }
  return QVariant();
}

QVariant QT::setCurrentBlockState(const QVariant& vHighlighter, const QVariant& vState) {
  auto highlighter = VAL(vHighlighter, SyntaxHighlighter*);
  if (highlighter != nullptr) {
    highlighter->setCurrentBlockState(vState.toInt());
  }
  return QVariant();
}

QVariant QT::setFormat(const QVariant& vObject, const QVariant& vFormat) {
  auto format = VAL(vObject, TextCharFormat*);
  if (format != nullptr) {
    auto map = VAL(vFormat, QVariantMap);
    format->setForeground(QColor(map.value("color").toString()));
    if (map.contains("bold")) {
      format->setFontWeight(QFont::Bold);
    }
    if (map.contains("italic")) {
      format->setFontItalic(true);
    }
    return true;
  }
  auto highlighter = VAL(vObject, SyntaxHighlighter*);
  if (highlighter != nullptr) {
    auto args = VAL(vFormat, QVariantList);
    auto start = args.at(0).toInt();
    auto count = args.at(1).toInt();
    auto format = VAL(args.at(2), TextCharFormat*);
    if (format != nullptr) {
      highlighter->setFormat(start, count, *format);
    } else {
      highlighter->setFormat(start, count, QColor(args.at(2).toString()));
    }
    return true;
  }
  return false;
}

QVariant QT::setPattern(const QVariant& vObject, const QVariant& vArg) {
  auto regexp = VAL(vObject, RegularExpression*);
  if (regexp != nullptr) {
    regexp->setPattern(vArg.toString());
    regexp->optimize();
  }
  return QVariant();
}

QVariant QT::text(const QVariant& vBlock) {
  auto block = VAL(vBlock, TextBlock*);
  if (block != nullptr) {
    return block->text();
  }
  return QVariant();
}

QVariant QT::textDocument(const QVariant& vDocument) {
  auto document = VAL(vDocument, QQuickTextDocument*);
  if (document != nullptr) {
    return VAR(QTextDocument*, document->textDocument());
  }
  return QVariant();
}

// etc

QVariant QT::localIp() {
  // Returns the local IP string. Private networks may use:
  // 10.*.*.*
  // 172.16.*.*
  // 192.168.*.*
  const auto addresses = QNetworkInterface::allAddresses();
  QStringList ips;
  for (QHostAddress adr : addresses) {
    if (adr.protocol() == QAbstractSocket::IPv4Protocol) {
      QString ip(adr.toString());
      if (ip.startsWith("10.") ||
          ip.startsWith("172.16.") ||
          ip.startsWith("192.168.")) {
        ips << ip;
      }
    }
  }
  if (!ips.isEmpty()) {
    // hack for rare, ambiguous cases
    ips.sort();
    return ips.first();
  }
  return QVariant();
}

// USB

#if (defined Q_OS_UNIX) && !(defined NO_USB)
QVariant QT::connectUsb() {
  if (usb == nullptr) {
    usb = new USB();
    if (usb->connect2()) {
      connect(usb, &USB::receivingDone,
              [](const QByteArray& data) {
                ecl_fun("ed:received-from-ulisp", QString::fromUtf8(data));
              });
      return true;
    }
  }
  return QVariant();
}

QVariant QT::sendToUlisp(const QVariant &vData) {
  if (usb != nullptr) {
    usb->write2(vData.toByteArray());
  }
  return QVariant();
}
#endif

