/**
 * SPDX-FileCopyrightText: (C) 2005 Sébastien Laoût <slaout@linux62.org>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "tag.h"

#include <KActionCollection>
#include <KLocalizedString>

#include <QIcon>
#include <QLocale>
#include <QtCore/QDir>
#include <QtCore/QList>
#include <QtCore/QTextStream>
#include <QtGui/QFont>
#include <QtXml/QDomDocument>

#include "basketscene.h"
#include "bnpview.h"
#include "common.h"
#include "debugwindow.h"
#include "gitwrapper.h"
#include "global.h"
#include "tools.h"
#include "xmlwork.h"

/** class State: */

State::State(const QString &id, Tag *tag)
    : m_id(id)
    , m_name()
    , m_emblem()
    , m_bold(false)
    , m_italic(false)
    , m_underline(false)
    , m_strikeOut(false)
    , m_textColor()
    , m_fontName()
    , m_fontSize(-1)
    , m_backgroundColor()
    , m_textEquivalent()
    , m_onAllTextLines(false)
    , m_allowCrossReferences(true)
    , m_parentTag(tag)
{
}

State::~State()
{
}

State *State::nextState(bool cycle /*= true*/)
{
    if (!parentTag())
        return nullptr;

    List states = parentTag()->states();
    // The tag contains only one state:
    if (states.count() == 1)
        return nullptr;
    // Find the next state:
    for (List::iterator it = states.begin(); it != states.end(); ++it)
        // Found the current state in the list:
        if (*it == this) {
            // Find the next state:
            State *next = *(++it);
            if (it == states.end())
                return (cycle ? states.first() : 0);
            return next;
        }
    // Should not happens:
    Q_ASSERT(false);
    return nullptr;
}

QString State::fullName()
{
    if (!parentTag() || parentTag()->states().count() == 1)
        return (name().isEmpty() && parentTag() ? parentTag()->name() : name());
    return QString(i18n("%1: %2", parentTag()->name(), name()));
}

QFont State::font(QFont base)
{
    if (bold())
        base.setBold(true);
    if (italic())
        base.setItalic(true);
    if (underline())
        base.setUnderline(true);
    if (strikeOut())
        base.setStrikeOut(true);
    if (!fontName().isEmpty())
        base.setFamily(fontName());
    if (fontSize() > 0)
        base.setPointSize(fontSize());
    return base;
}

QString State::toCSS(const QString &gradientFolderPath, const QString &gradientFolderName, const QFont &baseFont)
{
    QString css;
    if (bold())
        css += " font-weight: bold;";
    if (italic())
        css += " font-style: italic;";
    if (underline() && strikeOut())
        css += " text-decoration: underline line-through;";
    else if (underline())
        css += " text-decoration: underline;";
    else if (strikeOut())
        css += " text-decoration: line-through;";
    if (textColor().isValid())
        css += " color: " + textColor().name() + ';';
    if (!fontName().isEmpty()) {
        QString fontFamily = Tools::cssFontDefinition(fontName(), /*onlyFontFamily=*/true);
        css += " font-family: " + fontFamily + ';';
    }
    if (fontSize() > 0)
        css += " font-size: " + QString::number(fontSize()) + "px;";
    if (backgroundColor().isValid()) {
        css += " background-color: " + backgroundColor().name() + ";";
    }

    if (css.isEmpty())
        return QString();
    else
        return "   .tag_" + id() + " {" + css + " }\n";
}

void State::merge(const List &states, State *result, int *emblemsCount, bool *haveInvisibleTags, const QColor &backgroundColor)
{
    *result = State(); // Reset to default values.
    *emblemsCount = 0;
    *haveInvisibleTags = false;

    for (List::const_iterator it = states.begin(); it != states.end(); ++it) {
        State *state = *it;
        bool isVisible = false;
        // For each property, if that properties have a value (is not default) is the current state of the list,
        // and if it haven't been set to the result state by a previous state, then it's visible and we assign the property to the result state.
        if (!state->emblem().isEmpty()) {
            ++*emblemsCount;
            isVisible = true;
        }
        if (state->bold() && !result->bold()) {
            result->setBold(true);
            isVisible = true;
        }
        if (state->italic() && !result->italic()) {
            result->setItalic(true);
            isVisible = true;
        }
        if (state->underline() && !result->underline()) {
            result->setUnderline(true);
            isVisible = true;
        }
        if (state->strikeOut() && !result->strikeOut()) {
            result->setStrikeOut(true);
            isVisible = true;
        }
        if (state->textColor().isValid() && !result->textColor().isValid()) {
            result->setTextColor(state->textColor());
            isVisible = true;
        }
        if (!state->fontName().isEmpty() && result->fontName().isEmpty()) {
            result->setFontName(state->fontName());
            isVisible = true;
        }
        if (state->fontSize() > 0 && result->fontSize() <= 0) {
            result->setFontSize(state->fontSize());
            isVisible = true;
        }
        if (state->backgroundColor().isValid() && !result->backgroundColor().isValid() && state->backgroundColor() != backgroundColor) { // vv
            result->setBackgroundColor(state->backgroundColor());                                                                        // This is particular: if the note background color is the same as the basket one, don't use that.
            isVisible = true;
        }
        // If it's not visible, well, at least one tag is not visible: the note will display "..." at the tags arrow place to show that:
        if (!isVisible)
            *haveInvisibleTags = true;
    }
}

void State::copyTo(State *other)
{
    other->m_id = m_id;
    other->m_name = m_name;
    other->m_emblem = m_emblem;
    other->m_bold = m_bold;
    other->m_italic = m_italic;
    other->m_underline = m_underline;
    other->m_strikeOut = m_strikeOut;
    other->m_textColor = m_textColor;
    other->m_fontName = m_fontName;
    other->m_fontSize = m_fontSize;
    other->m_backgroundColor = m_backgroundColor;
    other->m_textEquivalent = m_textEquivalent;
    other->m_onAllTextLines = m_onAllTextLines; // TODO
    other->m_allowCrossReferences = m_allowCrossReferences;
    // TODO: other->m_parentTag;
}

/** class Tag: */

Tag::List Tag::all = Tag::List();

long Tag::nextStateUid = 1;

QRegularExpression Tag::regexpDetectTags = QRegularExpression();

QHash<QString, State*> Tag::dictStatesByEquiv = QHash<QString, State*>();

long Tag::getNextStateUid()
{
    return nextStateUid++; // Return the next Uid and THEN increment the Uid
}

Tag::Tag()
{
    static int tagNumber = 0;
    ++tagNumber;
    QString sAction = "tag_shortcut_number_" + QString::number(tagNumber);

    KActionCollection *ac = Global::bnpView->actionCollection();
    m_action = ac->addAction(sAction, Global::bnpView, SLOT(activatedTagShortcut()));
    m_action->setText("FAKE TEXT");
    m_action->setIcon(QIcon::fromTheme("FAKE ICON"));

    ac->setShortcutsConfigurable(m_action, false); // We do it in the tag properties dialog

    m_inheritedBySiblings = false;
}

Tag::~Tag()
{
    delete m_action;
}

void Tag::setName(const QString &name)
{
    m_name = name;
    m_action->setText("TAG SHORTCUT: " + name); // TODO: i18n  (for debug purpose only by now).
}

State *Tag::stateById(const QString &id)
{
    for (List::iterator it = all.begin(); it != all.end(); ++it)
        for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2)
            if ((*it2)->id() == id)
                return *it2;
    return nullptr;
}

State *Tag::stateByTextEquiv(const QString &text)
{
    if (!dictStatesByEquiv.contains(text))
        return nullptr;

    return dictStatesByEquiv.value(text);
}

Tag *Tag::tagForKAction(QAction *action)
{
    for (List::iterator it = all.begin(); it != all.end(); ++it)
        if ((*it)->m_action == action)
            return *it;
    return nullptr;
}

QMap<QString, QString> Tag::loadTags(const QString &path /* = QString()*/ /*, bool merge = false*/)
{
    QMap<QString, QString> mergedStates;

    bool merge = !path.isEmpty();
    QString fullPath = (merge ? path : Global::savesFolder() + "tags.xml");
    QString doctype = "basketTags";

    QDir dir;
    if (!dir.exists(fullPath)) {
        if (merge)
            return mergedStates;
        DEBUG_WIN << "Tags file does not exist: Creating it...";
        createDefaultTagsSet(fullPath);
    }

    QScopedPointer<QDomDocument> document(XMLWork::openFile(doctype, fullPath));
    if (!document) {
        DEBUG_WIN << "<font color=red>FAILED to read the tags file</font>";
        return mergedStates;
    }

    QDomElement docElem = document->documentElement();
    if (!merge)
        nextStateUid = docElem.attribute("nextStateUid", QString::number(nextStateUid)).toLong();

    QDomNode node = docElem.firstChild();
    while (!node.isNull()) {
        QDomElement element = node.toElement();
        if ((!element.isNull()) && element.tagName() == "tag") {
            Tag *tag = new Tag();
            // Load properties:
            QString name = XMLWork::getElementText(element, "name");
            QString shortcut = XMLWork::getElementText(element, "shortcut");
            QString inherited = XMLWork::getElementText(element, "inherited", "false");
            tag->setName(name);
            tag->setShortcut(QKeySequence(shortcut));
            tag->setInheritedBySiblings(XMLWork::trueOrFalse(inherited));
            // Load states:
            QDomNode subNode = element.firstChild();
            while (!subNode.isNull()) {
                QDomElement subElement = subNode.toElement();
                if ((!subElement.isNull()) && subElement.tagName() == "state") {
                    State *state = new State(subElement.attribute("id"), tag);
                    state->setName(XMLWork::getElementText(subElement, "name"));
                    state->setEmblem(XMLWork::getElementText(subElement, "emblem"));
                    QDomElement textElement = XMLWork::getElement(subElement, "text");
                    state->setBold(XMLWork::trueOrFalse(textElement.attribute("bold", "false")));
                    state->setItalic(XMLWork::trueOrFalse(textElement.attribute("italic", "false")));
                    state->setUnderline(XMLWork::trueOrFalse(textElement.attribute("underline", "false")));
                    state->setStrikeOut(XMLWork::trueOrFalse(textElement.attribute("strikeOut", "false")));
                    QString textColor = textElement.attribute("color", QString());
                    state->setTextColor(textColor.isEmpty() ? QColor() : QColor(textColor));
                    QDomElement fontElement = XMLWork::getElement(subElement, "font");
                    state->setFontName(fontElement.attribute("name", QString()));
                    QString fontSize = fontElement.attribute("size", QString());
                    state->setFontSize(fontSize.isEmpty() ? -1 : fontSize.toInt());
                    QString backgroundColor = XMLWork::getElementText(subElement, "backgroundColor", QString());
                    state->setBackgroundColor(backgroundColor.isEmpty() ? QColor() : QColor(backgroundColor));
                    QDomElement textEquivalentElement = XMLWork::getElement(subElement, "textEquivalent");
                    state->setTextEquivalent(textEquivalentElement.attribute("string", QString()));
                    state->setOnAllTextLines(XMLWork::trueOrFalse(textEquivalentElement.attribute("onAllTextLines", "false")));
                    QString allowXRef = XMLWork::getElementText(subElement, "allowCrossReferences", "true");
                    state->setAllowCrossReferences(XMLWork::trueOrFalse(allowXRef));
                    tag->appendState(state);
                }
                subNode = subNode.nextSibling();
            }
            // If the Tag is Valid:
            if (tag->countStates() > 0) {
                // Rename Things if Needed:
                State *firstState = tag->states().first();
                if (tag->countStates() == 1 && firstState->name().isEmpty())
                    firstState->setName(tag->name());
                if (tag->name().isEmpty())
                    tag->setName(firstState->name());
                // Add or Merge the Tag:
                if (!merge) {
                    all.append(tag);
                } else {
                    Tag *similarTag = tagSimilarTo(tag);
                    // Tag does not exists, add it:
                    if (similarTag == nullptr) {
                        // We are merging the new states, so we should choose new and unique (on that computer) ids for those states:
                        for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it) {
                            State *state = *it;
                            QString uid = state->id();
                            QString newUid = "tag_state_" + QString::number(getNextStateUid());
                            state->setId(newUid);
                            mergedStates[uid] = newUid;
                        }
                        // TODO: if shortcut is already assigned to a previous note, do not import it, keep the user settings!
                        all.append(tag);
                        // Tag already exists, rename to their ids:
                    } else {
                        State::List::iterator it2 = similarTag->states().begin();
                        for (State::List::iterator it = tag->states().begin(); it != tag->states().end(); ++it, ++it2) {
                            State *state = *it;
                            State *similarState = *it2;
                            QString uid = state->id();
                            QString newUid = similarState->id();
                            if (uid != newUid)
                                mergedStates[uid] = newUid;
                        }
                        delete tag; // Already exists, not to be merged. Delete the shortcut and all.
                    }
                }
            }
        }
        node = node.nextSibling();
    }
    updateCaches();

    return mergedStates;
}

Tag *Tag::tagSimilarTo(Tag *tagToTest)
{
    // Tags are considered similar if they have the same name, the same number of states, in the same order, and the same look.
    // Keyboard shortcut, text equivalent and onEveryLines are user settings, and thus not considered during the comparison.
    // Default tags (To Do, Important, Idea...) do not take into account the name of the tag and states during the comparison.
    // Default tags are equal only if they have the same number of states, in the same order, and the same look.
    // This is because default tag names are translated differently in every countries, but they are essentially the same!
    // User tags begins with "tag_state_" followed by a number. Default tags are the other ones.

    // Browse all tags:
    for (List::iterator it = all.begin(); it != all.end(); ++it) {
        Tag *tag = *it;
        bool same = true;
        bool sameName;
        bool defaultTag = true;
        // We test only name and look. Shortcut and whenever it is inherited by sibling new notes are user settings only!
        sameName = tag->name() == tagToTest->name();
        if (tag->countStates() != tagToTest->countStates())
            continue; // Tag is different!
        // We found a tag with same name, check if every states/look are same too:
        State::List::iterator itTest = tagToTest->states().begin();
        for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2, ++itTest) {
            State *state = *it2;
            State *stateToTest = *itTest;
            if (state->id().startsWith(QLatin1String("tag_state_")) || stateToTest->id().startsWith(QLatin1String("tag_state_"))) {
                defaultTag = false;
            }
            if (state->name() != stateToTest->name()) {
                sameName = false;
            }
            if (state->emblem() != stateToTest->emblem()) {
                same = false;
                break;
            }
            if (state->bold() != stateToTest->bold()) {
                same = false;
                break;
            }
            if (state->italic() != stateToTest->italic()) {
                same = false;
                break;
            }
            if (state->underline() != stateToTest->underline()) {
                same = false;
                break;
            }
            if (state->strikeOut() != stateToTest->strikeOut()) {
                same = false;
                break;
            }
            if (state->textColor() != stateToTest->textColor()) {
                same = false;
                break;
            }
            if (state->fontName() != stateToTest->fontName()) {
                same = false;
                break;
            }
            if (state->fontSize() != stateToTest->fontSize()) {
                same = false;
                break;
            }
            if (state->backgroundColor() != stateToTest->backgroundColor()) {
                same = false;
                break;
            }
            // Text equivalent (as well as onAllTextLines) is also a user setting!
        }
        // We found an existing tag that is "exactly" the same:
        if (same && (sameName || defaultTag))
            return tag;
    }

    // Not found:
    return nullptr;
}

void Tag::saveTags()
{
    DEBUG_WIN << "Saving tags...";
    saveTagsTo(all, Global::savesFolder() + "tags.xml");

    GitWrapper::commitTagsXml();
}

void Tag::saveTagsTo(QList<Tag *> &list, const QString &fullPath)
{
    // Create Document:
    QDomDocument document(/*doctype=*/"basketTags");
    QDomElement root = document.createElement("basketTags");
    root.setAttribute("nextStateUid", static_cast<long long int>(nextStateUid));
    document.appendChild(root);

    // Save all tags:
    for (List::iterator it = list.begin(); it != list.end(); ++it) {
        Tag *tag = *it;
        // Create tag node:
        QDomElement tagNode = document.createElement("tag");
        root.appendChild(tagNode);
        // Save tag properties:
        XMLWork::addElement(document, tagNode, "name", tag->name());
        XMLWork::addElement(document, tagNode, "shortcut", tag->shortcut().toString());
        XMLWork::addElement(document, tagNode, "inherited", XMLWork::trueOrFalse(tag->inheritedBySiblings()));
        // Save all states:
        for (State::List::iterator it2 = (*it)->states().begin(); it2 != (*it)->states().end(); ++it2) {
            State *state = *it2;
            // Create state node:
            QDomElement stateNode = document.createElement("state");
            tagNode.appendChild(stateNode);
            // Save state properties:
            stateNode.setAttribute("id", state->id());
            XMLWork::addElement(document, stateNode, "name", state->name());
            XMLWork::addElement(document, stateNode, "emblem", state->emblem());
            QDomElement textNode = document.createElement("text");
            stateNode.appendChild(textNode);
            QString textColor = (state->textColor().isValid() ? state->textColor().name() : QString());
            textNode.setAttribute("bold", XMLWork::trueOrFalse(state->bold()));
            textNode.setAttribute("italic", XMLWork::trueOrFalse(state->italic()));
            textNode.setAttribute("underline", XMLWork::trueOrFalse(state->underline()));
            textNode.setAttribute("strikeOut", XMLWork::trueOrFalse(state->strikeOut()));
            textNode.setAttribute("color", textColor);
            QDomElement fontNode = document.createElement("font");
            stateNode.appendChild(fontNode);
            fontNode.setAttribute("name", state->fontName());
            fontNode.setAttribute("size", state->fontSize());
            QString backgroundColor = (state->backgroundColor().isValid() ? state->backgroundColor().name() : QString());
            XMLWork::addElement(document, stateNode, "backgroundColor", backgroundColor);
            QDomElement textEquivalentNode = document.createElement("textEquivalent");
            stateNode.appendChild(textEquivalentNode);
            textEquivalentNode.setAttribute("string", state->textEquivalent());
            textEquivalentNode.setAttribute("onAllTextLines", XMLWork::trueOrFalse(state->onAllTextLines()));
            XMLWork::addElement(document, stateNode, "allowCrossReferences", XMLWork::trueOrFalse(state->allowCrossReferences()));
        }
    }

    // Write to Disk:
    if (!FileStorage::safelySaveToFile(fullPath, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + document.toString()))
        DEBUG_WIN << "<font color=red>FAILED to save tags</font>!";
}

void Tag::copyTo(Tag *other)
{
    other->m_name = m_name;
    other->m_action->setShortcut(m_action->shortcut());
    other->m_inheritedBySiblings = m_inheritedBySiblings;
}

void Tag::createDefaultTagsSet(const QString &fullPath)
{
    QString xml = QString(
                      "<!DOCTYPE basketTags>\n"
                      "<basketTags>\n"
                      "  <tag>\n"
                      "    <name>%1</name>\n" // "To Do"
                      "    <shortcut>Ctrl+1</shortcut>\n"
                      "    <inherited>true</inherited>\n"
                      "    <state id=\"todo_unchecked\">\n"
                      "      <name>%2</name>\n" // "Unchecked"
                      "      <emblem>tag_checkbox</emblem>\n"
                      "      <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"false\" color=\"\" />\n"
                      "      <font name=\"\" size=\"\" />\n"
                      "      <backgroundColor></backgroundColor>\n"
                      "      <textEquivalent string=\"[ ]\" onAllTextLines=\"false\" />\n"
                      "    </state>\n"
                      "    <state id=\"todo_done\">\n"
                      "      <name>%3</name>\n" // "Done"
                      "      <emblem>tag_checkbox_checked</emblem>\n"
                      "      <text bold=\"false\" italic=\"false\" underline=\"false\" strikeOut=\"true\" color=\"\" />\n"
                      "      <font name=\"\" size=\"\" />\n"
                      "      <backgroundColor></backgroundColor>\n"
                      "      <textEquivalent string=\"[x]\" onAllTextLines=\"false\" />\n"
                      "    </state>\n"
                      "  </tag>\n"
                      "\n"
                      "  <tag>\n"
                      "    <name>%4</name>\n" // "Progress"
                      "    <shortcut>Ctrl+2</shortcut>\n"
                      "    <inherited>true</inherited>\n"
                      "    <state id=\"progress_000\">\n"
                      "      <name>%5</name>\n" // "0 %"
                      "      <emblem>tag_progress_000</emblem>\n"
                      "      <textEquivalent string=\"[    ]\" />\n"
                      "    </state>\n"
                      "    <state id=\"progress_025\">\n"
                      "      <name>%6</name>\n" // "25 %"
                      "      <emblem>tag_progress_025</emblem>\n"
                      "      <textEquivalent string=\"[=   ]\" />\n"
                      "    </state>\n"
                      "    <state id=\"progress_050\">\n"
                      "      <name>%7</name>\n" // "50 %"
                      "      <emblem>tag_progress_050</emblem>\n"
                      "      <textEquivalent string=\"[==  ]\" />\n"
                      "    </state>\n"
                      "    <state id=\"progress_075\">\n"
                      "      <name>%8</name>\n" // "75 %"
                      "      <emblem>tag_progress_075</emblem>\n"
                      "      <textEquivalent string=\"[=== ]\" />\n"
                      "    </state>\n"
                      "    <state id=\"progress_100\">\n"
                      "      <name>%9</name>\n" // "100 %"
                      "      <emblem>tag_progress_100</emblem>\n"
                      "      <textEquivalent string=\"[====]\" />\n"
                      "    </state>\n"
                      "  </tag>\n"
                      "\n")
                      .arg(i18n("To Do"), i18n("Unchecked"), i18n("Done")) // %1 %2 %3
                      .arg(i18n("Progress"), i18n("0 %"), i18n("25 %"))    // %4 %5 %6
                      .arg(i18n("50 %"), i18n("75 %"), i18n("100 %"))      // %7 %8 %9
        + QString(
              "  <tag>\n"
              "    <name>%1</name>\n" // "Priority"
              "    <shortcut>Ctrl+3</shortcut>\n"
              "    <inherited>true</inherited>\n"
              "    <state id=\"priority_low\">\n"
              "      <name>%2</name>\n" // "Low"
              "      <emblem>tag_priority_low</emblem>\n"
              "      <textEquivalent string=\"{1}\" />\n"
              "    </state>\n"
              "    <state id=\"priority_medium\">\n"
              "      <name>%3</name>\n" // "Medium
              "      <emblem>tag_priority_medium</emblem>\n"
              "      <textEquivalent string=\"{2}\" />\n"
              "    </state>\n"
              "    <state id=\"priority_high\">\n"
              "      <name>%4</name>\n" // "High"
              "      <emblem>tag_priority_high</emblem>\n"
              "      <textEquivalent string=\"{3}\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <name>%5</name>\n" // "Preference"
              "    <shortcut>Ctrl+4</shortcut>\n"
              "    <inherited>true</inherited>\n"
              "    <state id=\"preference_bad\">\n"
              "      <name>%6</name>\n" // "Bad"
              "      <emblem>tag_preference_bad</emblem>\n"
              "      <textEquivalent string=\"(*  )\" />\n"
              "    </state>\n"
              "    <state id=\"preference_good\">\n"
              "      <name>%7</name>\n" // "Good"
              "      <emblem>tag_preference_good</emblem>\n"
              "      <textEquivalent string=\"(** )\" />\n"
              "    </state>\n"
              "    <state id=\"preference_excellent\">\n"
              "      <name>%8</name>\n" // "Excellent"
              "      <emblem>tag_preference_excellent</emblem>\n"
              "      <textEquivalent string=\"(***)\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <name>%9</name>\n" // "Highlight"
              "    <shortcut>Ctrl+5</shortcut>\n"
              "    <state id=\"highlight\">\n"
              "      <backgroundColor>#ffffcc</backgroundColor>\n"
              "      <textEquivalent string=\"=>\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n")
              .arg(i18n("Priority"), i18n("Low"), i18n("Medium"))      // %1 %2 %3
              .arg(i18n("High"), i18n("Preference"), i18n("Bad"))      // %4 %5 %6
              .arg(i18n("Good"), i18n("Excellent"), i18n("Highlight")) // %7 %8 %9
        + QString(
              "  <tag>\n"
              "    <name>%1</name>\n" // "Important"
              "    <shortcut>Ctrl+6</shortcut>\n"
              "    <state id=\"important\">\n"
              "      <emblem>tag_important</emblem>\n"
              "      <backgroundColor>#ffcccc</backgroundColor>\n"
              "      <textEquivalent string=\"!!\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <name>%2</name>\n" // "Very Important"
              "    <shortcut>Ctrl+7</shortcut>\n"
              "    <state id=\"very_important\">\n"
              "      <emblem>tag_important</emblem>\n"
              "      <text color=\"#ffffff\" />\n"
              "      <backgroundColor>#ff0000</backgroundColor>\n"
              "      <textEquivalent string=\"/!\\\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <name>%3</name>\n" // "Information"
              "    <shortcut>Ctrl+8</shortcut>\n"
              "    <state id=\"information\">\n"
              "      <emblem>dialog-information</emblem>\n"
              "      <textEquivalent string=\"(i)\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <name>%4</name>\n" // "Idea"
              "    <shortcut>Ctrl+9</shortcut>\n"
              "    <state id=\"idea\">\n"
              "      <emblem>ktip</emblem>\n"
              "      <textEquivalent string=\"%5\" />\n" // I.
              "    </state>\n"
              "  </tag>"
              "\n"
              "\n"
              "  <tag>\n"
              "    <name>%6</name>\n" // "Title"
              "    <shortcut>Ctrl+0</shortcut>\n"
              "    <state id=\"title\">\n"
              "      <text bold=\"true\" />\n"
              "      <textEquivalent string=\"##\" />\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <name>%7</name>\n" // "Code"
              "    <state id=\"code\">\n"
              "      <font name=\"monospace\" />\n"
              "      <textEquivalent string=\"|\" onAllTextLines=\"true\" />\n"
              "      <allowCrossReferences>false</allowCrossReferences>\n"
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <state id=\"work\">\n"
              "      <name>%8</name>\n" // "Work"
              "      <text color=\"#ff8000\" />\n"
              "      <textEquivalent string=\"%9\" />\n" // W.
              "    </state>\n"
              "  </tag>"
              "\n"
              "\n")
              .arg(i18n("Important"), i18n("Very Important"), i18n("Information"))    // %1 %2 %3
              .arg(i18n("Idea"), i18nc("The initial of 'Idea'", "I."), i18n("Title")) // %4 %5 %6
              .arg(i18n("Code"), i18n("Work"), i18nc("The initial of 'Work'", "W."))  // %7 %8 %9
        + QString(
              "  <tag>\n"
              "    <state id=\"personal\">\n"
              "      <name>%1</name>\n" // "Personal"
              "      <text color=\"#008000\" />\n"
              "      <textEquivalent string=\"%2\" />\n" // P.
              "    </state>\n"
              "  </tag>\n"
              "\n"
              "  <tag>\n"
              "    <state id=\"funny\">\n"
              "      <name>%3</name>\n" // "Funny"
              "      <emblem>tag_fun</emblem>\n"
              "    </state>\n"
              "  </tag>\n"
              "</basketTags>\n"
              "")
              .arg(i18n("Personal"), i18nc("The initial of 'Personal'", "P."), i18n("Funny")); // %1 %2 %3

    // Write to Disk:
    QFile file(fullPath);
    if (file.open(QIODevice::WriteOnly)) {
        QTextStream stream(&file);
        stream.setCodec("UTF-8");
        stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
        stream << xml;
        file.close();
    } else
        DEBUG_WIN << "<font color=red>FAILED to create the tags file</font>!";
}

const QRegularExpression& Tag::regexpDetectTagsInPlainText()
{
    return regexpDetectTags;
}

void Tag::updateCaches()
{
    QString patternAllTags;
    for (Tag* tag: Tag::all)
    {
        for (State* state: tag->states())
        {
            QString textEquivalent = state->textEquivalent().trimmed();
            if (textEquivalent.isEmpty()) continue;

            dictStatesByEquiv[textEquivalent] = state;
            patternAllTags.append(QRegularExpression::escape(textEquivalent)).append("|");
        }
    }
    if (patternAllTags.isEmpty())
    {
        regexpDetectTags.setPattern(QString());
        return;
    }

    patternAllTags.chop(1);
    patternAllTags = QString(("\\G\\s*?(%1)")).arg(patternAllTags);

    regexpDetectTags = QRegularExpression(patternAllTags, QRegularExpression::UseUnicodePropertiesOption);
    regexpDetectTags.optimize();
}

// StateAction
StateAction::StateAction(State *state, const QKeySequence &shortcut, QWidget *parent, bool withTagName)
    : KToggleAction(parent)
    , m_state(state)
{
    setText(m_state->name());

    if (withTagName && m_state->parentTag())
        setText(m_state->parentTag()->name());

    setIcon(QIcon::fromTheme(m_state->emblem()));

    setShortcut(shortcut);
}

StateAction::~StateAction()
{
    // pass
}

#include "moc_tag.cpp"
