/**
 * This file is part of JS8Call.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 .
 *
 * (C) 2018 Jordan Sherer  - All Rights Reserved
 *
 **/
#include "jsc_checker.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include "jsc.h"
#include "varicode.h"
const int CORRECT = QTextFormat::UserProperty + 10;
const QString ALPHABET = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
JSCChecker::JSCChecker(QObject *parent) :
    QObject(parent)
{
}
bool cursorHasProperty(const QTextCursor &cursor, int property){
    if(property < QTextFormat::UserProperty) {
        return false;
    }
    if(cursor.charFormat().intProperty(property) == 1) {
        return true;
    }
    const QList& formats = cursor.block().layout()->additionalFormats();
    int pos = cursor.positionInBlock();
    foreach(const QTextLayout::FormatRange& range, formats) {
        if(pos > range.start && pos <= range.start + range.length && range.format.intProperty(property) == 1) {
            return true;
        }
    }
    return false;
}
QString nextChar(QTextCursor c){
    QTextCursor cur(c);
    cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
    return cur.selectedText();
}
bool isNumeric(QString s){
    return s.indexOf(QRegExp("^\\d+$")) == 0;
}
bool isWordChar(QString ch){
    return ch.contains(QRegExp("^\\w$"));
}
void JSCChecker::checkRange(QTextEdit* edit, int start, int end)
{
    if(end == -1){
        QTextCursor tmpCursor(edit->textCursor());
        tmpCursor.movePosition(QTextCursor::End);
        end = tmpCursor.position();
    }
    // stop contentsChange signals from being emitted due to changed charFormats
    edit->document()->blockSignals(true);
    qDebug() << "checking range " << start << " - " << end;
    QTextCharFormat errorFmt;
    errorFmt.setFontUnderline(true);
    errorFmt.setUnderlineColor(Qt::red);
    errorFmt.setUnderlineStyle(QTextCharFormat::WaveUnderline);
    QTextCharFormat defaultFormat = QTextCharFormat();
    auto cursor = edit->textCursor();
    cursor.beginEditBlock();
    {
        cursor.setPosition(start);
        while(cursor.position() < end) {
            bool correct = false;
            cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
            if(cursor.selectedText() == "@"){
                cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
                cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
            }
            if(cursorHasProperty(cursor, CORRECT)){
                correct = true;
            } else {
                QString word = cursor.selectedText();
                // three or less is always "correct"
                if(word.length() < 4 || isNumeric(word)){
                    correct = true;
                } else {
                    bool found = false;
                    quint32 index = JSC::lookup(word, &found);
                    if(found){
                        correct = JSC::map[index].size == word.length();
                    }
                    if(!correct){
                        correct = Varicode::isValidCallsign(word, nullptr);
                    }
                }
            }
            if(correct){
                QTextCharFormat fmt = cursor.charFormat();
                fmt.setFontUnderline(defaultFormat.fontUnderline());
                fmt.setUnderlineColor(defaultFormat.underlineColor());
                fmt.setUnderlineStyle(defaultFormat.underlineStyle());
                cursor.setCharFormat(fmt);
            } else {
                cursor.mergeCharFormat(errorFmt);
            }
            // Go to next word start
            //while(cursor.position() < end && !isWordChar(nextChar(cursor))){
            //    cursor.movePosition(QTextCursor::NextCharacter);
            //}
            cursor.movePosition(QTextCursor::NextCharacter);
        }
    }
    cursor.endEditBlock();
    edit->document()->blockSignals(false);
}
QSet oneEdit(QString word, bool includeAdditions, bool includeDeletions){
    QSet all;
    // 1-edit distance words (i.e., prefixed/suffixed/edited characters)
    for(int i = 0; i < 26; i++){
        if(includeAdditions){
            auto prefixed = ALPHABET.mid(i, 1) + word;
            all.insert(prefixed);
            auto suffixed = word + ALPHABET.mid(i, 1);
            all.insert(suffixed);
        }
        for(int j = 0; j < word.length(); j++){
            auto edited = word.mid(0, j) + ALPHABET.mid(i, 1) + word.mid(j + 1, word.length() - j);
            all.insert(edited);
        }
    }
    // 1-edit distance words (i.e., removed characters)
    if(includeDeletions){
        for(int j = 0; j < word.length(); j++){
            auto deleted = word.mid(0, j) + word.mid(j + 1, word.length() - j);
            all.insert(deleted);
        }
    }
    return all;
}
QMap candidates(QString word, bool includeTwoEdits){
    // one edit
    QSet one = oneEdit(word, true, true);
    // two edits
    QSet two;
    if(includeTwoEdits){
        foreach(auto w, one){
            two |= oneEdit(w, false, false);
        }
    }
    // existence check
    QMap m;
    quint32 index;
    foreach(auto w, one | two){
        if(JSC::exists(w, &index)){
            m[index] = w;
        }
    }
    return m;
}
QStringList JSCChecker::suggestions(QString word, int n, bool *pFound){
    QStringList s;
    qDebug() << "computing suggestions for word" << word;
    QMap m;
    bool prefixFound = false;
    // lookup actual word prefix that is not a single character
    quint32 index = JSC::lookup(word, &prefixFound);
    if(prefixFound){
        auto t = JSC::map[index];
        if(t.size > 1){
            m[index] = QString::fromLatin1(t.str, t.size);
        }
    }
    // compute suggestion candidates
    m.unite(candidates(word, false));
    // return in order of probability (i.e., index rank)
    int i = 0;
    foreach(auto key, m.uniqueKeys()){
        if(i >= n){
            break;
        }
        qDebug() << "suggest" << m[key] << key;
        s.append(m[key]);
        i++;
    }
    if(pFound) *pFound = prefixFound;
    return s;
}