/***************************************************************************
    File                 : AbstractAspect.cpp
    Project              : SciDAVis
    --------------------------------------------------------------------
    Copyright            : (C) 2007 by Knut Franke, Tilman Benkert
    Email (use @ for *)  : knut.franke*gmx.de, thzs*gmx.net
    Description          : Base class for all persistent objects in a Project.

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  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 2 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, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/
#include "core/AbstractAspect.h"
#include "core/AspectPrivate.h"
#include "core/aspectcommands.h"
#include "core/future_Folder.h"
#include "lib/XmlStreamReader.h"

#include <QIcon>
#include <QMenu>
#include <QMessageBox>
#include <QStyle>
#include <QApplication>
#include <QXmlStreamWriter>

AbstractAspect::AbstractAspect(const QString &name) : d_aspect_private(new Private(this, name)) { }

AbstractAspect::~AbstractAspect()
{
    delete d_aspect_private;
}

void AbstractAspect::writeCommentElement(QXmlStreamWriter *writer) const
{
    writer->writeStartElement("comment");
    QString temp = comment();
    temp.replace("\n", "\\n");
    writer->writeCDATA(temp);
    writer->writeEndElement();
}

bool AbstractAspect::readCommentElement(XmlStreamReader *reader)
{
    Q_ASSERT(reader->isStartElement() && reader->name() == "comment");
    QString temp = reader->readElementText();
    temp.replace("\\n", "\n");
    setComment(temp);
    return true;
}

void AbstractAspect::writeBasicAttributes(QXmlStreamWriter *writer) const
{
    writer->writeAttribute("creation_time", creationTime().toString("yyyy-dd-MM hh:mm:ss:zzz"));
    writer->writeAttribute("caption_spec", captionSpec());
    writer->writeAttribute("name", name());
}

bool AbstractAspect::readBasicAttributes(XmlStreamReader *reader)
{
    QString prefix(tr("XML read error: ", "prefix for XML error messages"));
    QString postfix(tr(" (non-critical)", "postfix for XML error messages"));

    QXmlStreamAttributes attribs = reader->attributes();
    QString str;

    // read name
    str = attribs.value(reader->namespaceUri().toString(), "name").toString();
    if (str.isEmpty()) {
        reader->raiseWarning(prefix + tr("aspect name missing or empty") + postfix);
    }
    setName(str);
    // read creation time
    str = attribs.value(reader->namespaceUri().toString(), "creation_time").toString();
    QDateTime creation_time = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz");
    if (str.isEmpty() || !creation_time.isValid()) {
        reader->raiseWarning(tr("Invalid creation time for '%1'. Using current time.").arg(name()));
        setCreationTime(QDateTime::currentDateTime());
    } else
        setCreationTime(creation_time);
    // read caption spec
    str = attribs.value(reader->namespaceUri().toString(), "caption_spec").toString();
    setCaptionSpec(str);

    return true;
}

AbstractAspect *AbstractAspect::parentAspect() const
{
    return d_aspect_private->parent();
}

void AbstractAspect::addChild(AbstractAspect *child)
{
    Q_CHECK_PTR(child);
    QString new_name = d_aspect_private->uniqueNameFor(child->name());
    beginMacro(tr("%1: add %2.").arg(name()).arg(new_name));
    if (new_name != child->name()) {
        info(tr("Renaming \"%1\" to \"%2\" in order to avoid name collision.")
                     .arg(child->name())
                     .arg(new_name));
        child->setName(new_name);
    }
    exec(new AspectChildAddCmd(d_aspect_private, child, d_aspect_private->childCount()));
    completeAspectInsertion(child, d_aspect_private->childCount() - 1);
    endMacro();
}

void AbstractAspect::insertChild(AbstractAspect *child, int index)
{
    Q_CHECK_PTR(child);
    QString new_name = d_aspect_private->uniqueNameFor(child->name());
    beginMacro(tr("%1: insert %2 at position %3.").arg(name()).arg(new_name).arg(index + 1));
    if (new_name != child->name()) {
        info(tr("Renaming \"%1\" to \"%2\" in order to avoid name collision.")
                     .arg(child->name())
                     .arg(new_name));
        child->setName(new_name);
    }
    exec(new AspectChildAddCmd(d_aspect_private, child, index));
    completeAspectInsertion(child, index);
    endMacro();
}

void AbstractAspect::removeChild(AbstractAspect *child, bool detach)
{
    Q_ASSERT(indexOfChild(child) != -1);
    beginMacro(tr("%1: remove %2.").arg(name()).arg(child->name()));
    prepareAspectRemoval(child);
    exec(new AspectChildRemoveCmd(d_aspect_private, child, detach));
    endMacro();
}

void AbstractAspect::reparentChild(AbstractAspect *new_parent, AbstractAspect *child)
{
    Q_ASSERT(new_parent != NULL);
    reparentChild(new_parent, child, new_parent->childCount());
}

void AbstractAspect::reparentChild(AbstractAspect *new_parent, AbstractAspect *child, int new_index)
{
    Q_ASSERT(indexOfChild(child) != -1);
    Q_ASSERT(new_index > 0 && new_index <= new_parent->childCount());
    Q_ASSERT(new_parent != NULL);
    QString new_name = new_parent->d_aspect_private->uniqueNameFor(child->name());
    beginMacro(tr("%1: move %2 to %3.").arg(name()).arg(child->name()).arg(new_parent->name()));
    if (new_name != child->name()) {
        info(tr("Renaming \"%1\" to \"%2\" in order to avoid name collision.")
                     .arg(child->name())
                     .arg(new_name));
        child->setName(new_name);
    }
    prepareAspectRemoval(child);
    exec(new AspectChildReparentCmd(d_aspect_private, new_parent->d_aspect_private, child,
                                    new_index));
    new_parent->completeAspectInsertion(child, new_index);
    endMacro();
}

void AbstractAspect::removeChild(int index)
{
    Q_ASSERT(index >= 0 && index <= childCount());
    removeChild(d_aspect_private->child(index));
}

AbstractAspect *AbstractAspect::child(int index) const
{
    Q_ASSERT(index >= 0 && index <= childCount());
    return d_aspect_private->child(index);
}

int AbstractAspect::childCount() const
{
    return d_aspect_private->childCount();
}

int AbstractAspect::indexOfChild(const AbstractAspect *child) const
{
    return d_aspect_private->indexOfChild(child);
}

void AbstractAspect::moveChild(int from, int to)
{
    Q_ASSERT(0 <= from && from < d_aspect_private->childCount());
    Q_ASSERT(0 <= to && to < d_aspect_private->childCount());
    exec(new AspectChildMoveCmd(d_aspect_private, from, to));
}

void AbstractAspect::exec(QUndoCommand *cmd)
{
    Q_CHECK_PTR(cmd);
    QUndoStack *stack = undoStack();
    if (stack)
        stack->push(cmd);
    else {
        cmd->redo();
        delete cmd;
    }
}

void AbstractAspect::beginMacro(const QString &text)
{
    QUndoStack *stack = undoStack();
    if (stack)
        stack->beginMacro(text);
}

void AbstractAspect::endMacro()
{
    QUndoStack *stack = undoStack();
    if (stack)
        stack->endMacro();
}

QString AbstractAspect::name() const
{
    return d_aspect_private->name();
}

void AbstractAspect::setName(const QString &value)
{
    if (value.isEmpty()) {
        setObjectName("1");
        return;
    }
    if (value == d_aspect_private->name())
        return;
    // Until we get around to completely sanitizing the project file format, we have to remove
    // characters that can easily break file save/restore.
    // FIXME: once the project file format is fully XML-based (i.e. able to escape special
    // characters), this can be removed
    QString sanitized_value = value;
    sanitized_value.remove(QChar('\n'));
    sanitized_value.remove(QChar('\r'));
    sanitized_value.remove(QChar('\t'));
    if (sanitized_value != value)
        info(tr("Tabs and line breaks in object names are currently not supported. They have been "
                "removed."));
    if (d_aspect_private->parent()) {
        QString new_name = d_aspect_private->parent()->uniqueNameFor(sanitized_value);
        if (new_name != sanitized_value)
            info(tr("Intended name \"%1\" diverted to \"%2\" in order to avoid name collision.")
                         .arg(sanitized_value)
                         .arg(new_name));
        exec(new AspectNameChangeCmd(d_aspect_private, new_name));
    } else
        exec(new AspectNameChangeCmd(d_aspect_private, sanitized_value));
}

QString AbstractAspect::comment() const
{
    return d_aspect_private->comment();
}

void AbstractAspect::setComment(const QString &value)
{
    if (value == d_aspect_private->comment())
        return;
    exec(new AspectCommentChangeCmd(d_aspect_private, value));
}

QString AbstractAspect::captionSpec() const
{
    return d_aspect_private->captionSpec();
}

void AbstractAspect::setCaptionSpec(const QString &value)
{
    if (value == d_aspect_private->captionSpec())
        return;
    exec(new AspectCaptionSpecChangeCmd(d_aspect_private, value));
}

void AbstractAspect::setCreationTime(const QDateTime &time)
{
    if (time == d_aspect_private->creationTime())
        return;
    exec(new AspectCreationTimeChangeCmd(d_aspect_private, time));
}

QDateTime AbstractAspect::creationTime() const
{
    return d_aspect_private->creationTime();
}

QString AbstractAspect::caption() const
{
    return d_aspect_private->caption();
}

QIcon AbstractAspect::icon() const
{
    return QIcon();
}

QMenu *AbstractAspect::createContextMenu() const
{
    QMenu *menu = new QMenu();

    const QStyle *widget_style = qApp->style();
    QAction *action_temp;

    action_temp = menu->addAction(QObject::tr("&Remove"), this, SLOT(remove()));
    action_temp->setIcon(widget_style->standardIcon(QStyle::SP_TrashIcon));

    return menu;
}

future::Folder *AbstractAspect::folder()
{
    if (inherits("future::Folder"))
        return static_cast<future::Folder *>(this);
    AbstractAspect *parent_aspect = parentAspect();
    while (parent_aspect && !parent_aspect->inherits("future::Folder"))
        parent_aspect = parent_aspect->parentAspect();
    return static_cast<future::Folder *>(parent_aspect);
}

bool AbstractAspect::isDescendantOf(AbstractAspect *other)
{
    if (other == this)
        return true;
    AbstractAspect *parent_aspect = parentAspect();
    while (parent_aspect) {
        if (parent_aspect == other)
            return true;
        parent_aspect = parent_aspect->parentAspect();
    }
    return false;
}

QString AbstractAspect::uniqueNameFor(const QString &current_name) const
{
    return d_aspect_private->uniqueNameFor(current_name);
}

QList<AbstractAspect *> AbstractAspect::descendantsThatInherit(const char *class_name)
{
    QList<AbstractAspect *> list;
    if (inherits(class_name))
        list << this;
    for (int i = 0; i < childCount(); i++)
        list << child(i)->descendantsThatInherit(class_name);
    return list;
}

void AbstractAspect::removeAllChildAspects()
{
    beginMacro(tr("%1: remove all children.").arg(name()));
    for (int i = childCount() - 1; i >= 0; i--)
        removeChild(i);
    endMacro();
}

QVariant AbstractAspect::global(const QString &key)
{
    QString qualified_key = QString(staticMetaObject.className()) + "/" + key;
    QVariant result = ApplicationWindow::getSettings().value(qualified_key);
    if (result.isValid())
        return result;
    else
        return Private::g_defaults[qualified_key];
}

void AbstractAspect::setGlobal(const QString &key, const QVariant &value)
{
    ApplicationWindow::getSettings().setValue(QString(staticMetaObject.className()) + "/" + key,
                                              value);
}

void AbstractAspect::setGlobalDefault(const QString &key, const QVariant &value)
{
    Private::g_defaults[QString(staticMetaObject.className()) + "/" + key] = value;
}
