#include "document.h"
#include "designerScene.h"

#include "graphicsItem/graphicsBaseItem.h"
#include "graphicsItem/graphicsItemGroup.h"
#include "util/serializationRegistry.h"
#include "global.h"

#include <QFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <QGraphicsScene>

#include <QUndoStack>
#include <QUndoCommand>

Document::Document(QObject *parent)
    : QObject(parent)
    , m_pScene(nullptr)
    , m_pUndoStack(nullptr)
    , m_bModified(false)
    , m_nSavedIndex(0)
{
    setupNewScene();

    m_pUndoStack = new QUndoStack(this);
    connect(m_pUndoStack, &QUndoStack::indexChanged, this, [this](int idx) {
        setModified(idx != m_nSavedIndex);
    });
}

Document::~Document()
{
}

void Document::setupNewScene()
{
    if (m_pScene) {
        m_pScene->clear();
    } else {
        m_pScene = new DesignerScene(this);
    }
}

DesignerScene *Document::scene() const
{
    return m_pScene;
}

QUndoStack *Document::undoStack() const
{
    return m_pUndoStack;
}

bool Document::isModified() const
{
    return m_bModified;
}

QString Document::filePath() const
{
    return m_sFilePath;
}

void Document::setModified(bool modified)
{
    if (m_bModified != modified) {
        m_bModified = modified;
        emit modifiedChanged(m_bModified);
    }
}

void Document::execute(QUndoCommand *cmd)
{
    m_pUndoStack->push(cmd);
}

void Document::markDirty()
{
    setModified(true);
}

void Document::clear()
{
    m_nSavedIndex = 0;
    m_pUndoStack->clear();
    setupNewScene();
    m_sFilePath.clear();
    setModified(false);
    emit filePathChanged(m_sFilePath);
}

bool Document::save()
{
    if (m_sFilePath.isEmpty())
        return false;
    return saveAs(m_sFilePath);
}

bool Document::saveAs(const QString &filePath)
{
    QJsonArray items;
    const auto sceneItems = m_pScene->items();
    for (QGraphicsItem *item : sceneItems) {
        if (item->parentItem())
            continue;
        QJsonObject obj;
        if (auto *group = dynamic_cast<GraphicsItemGroup *>(item))
            obj = group->serialize();
        else if (auto *shape = dynamic_cast<IShape *>(item))
            obj = shape->serialize();
        if (!obj.isEmpty())
            items.append(obj);
    }

    QJsonObject root;
    root["version"] = "1.0";
    root["items"] = items;

    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly))
        return false;

    file.write(QJsonDocument(root).toJson());
    file.close();

    m_sFilePath = filePath;
    m_nSavedIndex = m_pUndoStack->index();
    setModified(false);
    emit filePathChanged(m_sFilePath);
    emit saved();
    return true;
}

bool Document::load(const QString &filePath)
{
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly))
        return false;

    QByteArray data = file.readAll();
    file.close();

    QJsonParseError error;
    QJsonDocument doc = QJsonDocument::fromJson(data, &error);
    if (error.error != QJsonParseError::NoError)
        return false;

    QJsonObject root = doc.object();

    m_nSavedIndex = 0;
    m_pUndoStack->clear();
    setupNewScene();

    QJsonArray items = root["items"].toArray();
    for (const QJsonValue &val : items) {
        QGraphicsItem *item = createItemFromJson(val.toObject());
        if (item)
            m_pScene->addItem(item);
    }

    m_pScene->clearSelection();

    m_sFilePath = filePath;
    m_nSavedIndex = 0;
    setModified(false);
    emit filePathChanged(m_sFilePath);
    emit loaded();
    return true;
}
