#include "WedgeCommands.h"
#include <QDate>
#include <QMap>
#include <QRegularExpression>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextEdit>
#include <QtGlobal>
namespace WedgeCommands {
namespace {
// sequential eval
bool applyOps(double seed, const QString &tail, double &res) {
static const QRegularExpression splitRegex("[\\+\\-\\*\\/=]");
QString ops;
for (const QChar ch : tail)
if (QStringLiteral("+-*/").contains(ch)) ops += ch;
const QStringList terms = tail.split(splitRegex, Qt::SkipEmptyParts);
if (terms.size() != ops.size()) return false;
res = seed;
for (int i = 0; i < ops.size(); ++i) {
const QChar op = ops.at(i);
QString term = terms.at(i);
const bool pct = term.endsWith('%');
if (pct) term.chop(1);
bool ok = false;
const double num = term.toDouble(&ok);
if (!ok) return false;
const double next = pct
? ((op == '+' || op == '-') ? res * (num / 100.0) : num / 100.0)
: num;
switch (op.unicode()) {
case '+': res += next; break;
case '-': res -= next; break;
case '*': res *= next; break;
default: res /= next; break;
}
}
return true;
}
bool evalSeq(const QString &expr, double &res) {
int i = 0;
while (i < expr.size() && !QStringLiteral("+-*/=").contains(expr.at(i))) ++i;
bool ok = false;
const double head = expr.left(i).toDouble(&ok);
return ok && applyOps(head, expr.mid(i), res);
}
QString fmtNum(double v) {
QString s = QString::number(v, 'f', 2);
if (s.contains('.')) {
while (s.endsWith('0')) s.chop(1);
if (s.endsWith('.')) s.chop(1);
}
return s;
}
}
QString otcDateResult(int day, QString mon, int year) {
static QMap<QString, int> months = {
{"jan", 1}, {"feb", 2}, {"mar", 3}, {"apr", 4}, {"may", 5}, {"jun", 6},
{"jul", 7}, {"aug", 8}, {"sep", 9}, {"oct", 10}, {"nov", 11}, {"dec", 12}
};
int m = months.value(mon.toLower(), 0);
if (m == 0) return "";
QDate now = QDate::currentDate();
QDate target(year > 0 ? year : now.year(), m, day);
if (!target.isValid()) return "";
if (year <= 0 && target < now) target = target.addYears(1);
QDate otcStart(target.year(), 3, 20);
if (target < otcStart) otcStart = otcStart.addYears(-1);
return QString("(%1)=%2").arg(otcStart.daysTo(target) + 1).arg(qMax(qint64(0), now.daysTo(target)));
}
Result apply(QTextEdit *editor, int key) {
Result r;
if (!editor) return r;
QTextCursor cursor = editor->textCursor();
QString lineText = cursor.block().text().left(cursor.positionInBlock());
static QRegularExpression mathRegex("(?<=\\s|^|=)((\\d+(\\.\\d+)?%?[\\+\\-\\*\\/])+\\d+(\\.\\d+)?%?)=$");
static QRegularExpression dateRegex("(?<=\\s|^)(\\d{1,2})([a-zA-Z]{3})(\\d{2})?\\(\\)=$");
static QRegularExpression listRegex("^(\\s*)(\\d+)\\.\\s");
static QRegularExpression hrRegex("(?<=\\s|^)hr\\((\\d+)\\)$");
static QRegularExpression dashRegex("(?<=\\s|^)dash\\((\\d+)\\)$");
auto mMatch = mathRegex.match(lineText);
if (mMatch.hasMatch()) {
double head = 0;
if (evalSeq(mMatch.captured(1), head)) {
const QString resStr = fmtNum(head);
// chain recalc
static QRegularExpression chainRegex("^((?:[\\+\\-\\*\\/]\\d+(?:\\.\\d+)?%?)+)=(-?\\d+(?:\\.\\d+)?)?");
const QString rest = cursor.block().text().mid(cursor.positionInBlock());
double acc = resStr.toDouble();
QString tail;
int used = 0;
for (;;) {
const QRegularExpressionMatch c = chainRegex.match(rest.mid(used));
if (!c.hasMatch()) break;
double v = 0;
if (!applyOps(acc, c.captured(1), v)) break;
const QString shown = fmtNum(v);
acc = shown.toDouble();
tail += c.captured(1) + "=" + shown;
used += c.capturedLength();
}
editor->blockSignals(true);
cursor.beginEditBlock();
cursor.insertText(resStr);
if (used > 0) {
QTextCursor t(cursor);
t.setPosition(cursor.position() + used, QTextCursor::KeepAnchor);
t.insertText(tail);
}
cursor.endEditBlock();
editor->blockSignals(false);
r.changed = true;
}
r.consumed = (key == Qt::Key_Space);
return r;
}
auto dMatch = dateRegex.match(lineText);
if (dMatch.hasMatch()) {
const int year = dMatch.captured(3).isEmpty() ? -1 : 2000 + dMatch.captured(3).toInt();
QString res = otcDateResult(dMatch.captured(1).toInt(), dMatch.captured(2), year);
if (!res.isEmpty()) {
editor->blockSignals(true);
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 3);
cursor.insertText(res);
editor->blockSignals(false);
r.changed = true;
}
r.consumed = (key == Qt::Key_Space);
return r;
}
auto hMatch = hrRegex.match(lineText);
if (hMatch.hasMatch()) {
int count = hMatch.captured(1).toInt();
if (count > 0) {
QString hrline = QString("\u2014").repeated(qMin(count, 100));
editor->blockSignals(true);
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, hMatch.capturedLength());
cursor.insertText(hrline);
editor->blockSignals(false);
r.changed = true;
}
r.consumed = (key == Qt::Key_Space);
return r;
}
auto emMatch = dashRegex.match(lineText);
if (emMatch.hasMatch()) {
int counts = emMatch.captured(1).toInt();
if (counts > 0) {
QString dashes = QString("\u2014 ").repeated(qMin(counts, 100));
editor->blockSignals(true);
cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, emMatch.capturedLength());
cursor.insertText(dashes);
editor->blockSignals(false);
r.changed = true;
}
r.consumed = (key == Qt::Key_Space);
return r;
}
if (key != Qt::Key_Space) {
auto lMatch = listRegex.match(lineText);
if (lMatch.hasMatch()) {
QString ws = lMatch.captured(1);
int num = lMatch.captured(2).toInt();
QString prefix = "\n" + ws + QString::number(num + 1) + ". ";
editor->blockSignals(true);
cursor.beginEditBlock();
cursor.insertText(prefix);
QTextBlock nextBlock = cursor.block().next();
int nextExpected = num + 1;
int i = 0;
while (nextBlock.isValid()) {
auto m = listRegex.match(nextBlock.text());
if (m.hasMatch() && m.captured(1) == ws && m.captured(2).toInt() == nextExpected) {
int foundNum = m.captured(2).toInt();
QTextCursor nextCursor(nextBlock);
nextCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m.capturedLength());
nextCursor.insertText(m.captured(1) + QString::number(foundNum + 1) + ". ");
nextExpected++;
} else if (i > 0) break;
nextBlock = nextBlock.next(); i++;
}
cursor.endEditBlock();
editor->blockSignals(false);
r.changed = true;
r.consumed = true;
return r;
}
static QRegularExpression bulletRegex("^([ \\t]*)(-) ");
auto bMatch = bulletRegex.match(lineText);
if (bMatch.hasMatch()) {
editor->blockSignals(true);
cursor.insertText("\n" + bMatch.captured(1) + "- ");
editor->blockSignals(false);
r.changed = true;
r.consumed = true;
return r;
}
static QRegularExpression indentRegex("^(\\s+)");
auto iMatch = indentRegex.match(lineText);
if (iMatch.hasMatch()) {
QString indent = "\n" + iMatch.captured(1);
editor->blockSignals(true);
cursor.insertText(indent);
editor->blockSignals(false);
r.changed = true;
r.consumed = true;
return r;
}
}
return r;
}
}