#include "xmlprojectmanager.h"

#include <xmlconfig.h>
#include <QDir>
#include <QRandomGenerator>
#include <QDateTime>
#include <QTextStream>
#include <QCoreApplication>
#include <QIcon>

using namespace Project;
using namespace Config;

class Impl_ProjectException : public ProjectException
{
public:
    Impl_ProjectException(const QString& title, const QString& reason)
    {
        temp_string = QString("%1：%2").arg(title, reason);
        title_length = title.length();
    }

private:
    int title_length;
    QString temp_string;

    // exception interface
public:
    virtual const char *what() const override{
        return temp_string.toLocal8Bit();
    }

    // ParseException interface
public:
    virtual QString reason() const override{return temp_string.mid(title_length+1);}
    virtual QString title() const override{ return temp_string.mid(0, title_length);}
};


XMLProjectManager::XMLProjectManager(QObject *parent)
    : QObject(parent),
      project_config(new XMLConfig(this)),
      project_structure(new QStandardItemModel(this)),
      open_status(false)
{

}

XMLProjectManager::~XMLProjectManager(){
    delete project_config;
}

QStandardItemModel *XMLProjectManager::model() const
{
    return this->project_structure;
}

QString XMLProjectManager::suffix() const
{
    return "nsf";
}

bool XMLProjectManager::isOpen() const noexcept
{
    return open_status;
}

bool XMLProjectManager::isModified() const noexcept
{
    return true;
}

/*
 * <?xml version='1.0'?>
 * <project name="project-name" config="xml-path">
 *      <package name="name" path="filepath">
 *          <file name="name" path="filepath"/>
 *          <package name="name2" path="filepath" />
 *      </package>
 * </project>
 */
void XMLProjectManager::openProject(const QString &project_file)
{
    filepath_store = project_file;
    QFileInfo info(project_file);
    if(!info.exists())
        throw new Impl_ProjectException("打开失败", "指定的项目文件不存在："+project_file);

    // 获取项目文件内容
    QFile project_in(project_file);
    if(!project_in.open(QIODevice::ReadOnly|QIODevice::Text))
        throw new Impl_ProjectException("打开失败", "指定的项目文件无法打开：" + project_file);

    // 载入文件内容
    QDomDocument doc;
    QString err; int row, col;
    if(!doc.setContent(&project_in, false, &err, &row, &col))
        throw new Impl_ProjectException("打开失败", QString("项目文件格式错误：%1，row：%2，col：%3").arg(err).arg(row).arg(col));

    // 构建项目组织树
    auto root_elm = doc.documentElement();
    auto name = root_elm.attribute("name");
    auto pnode = new ProjectNode(NodeType::ROOT, name);
    pnode->setFile(root_elm.attribute("config"));
    project_structure->appendRow(pnode);

    // 载入项目配置
    auto config = root_elm.attribute("config");
    auto config_path = info.dir().filePath(config);
    project_config->loadFile(config_path);

    structure_parser(root_elm, pnode);

    open_status = true;
    pnode->setIcon(QIcon(":/icons/toplevel.png"));
}

void XMLProjectManager::newProject(const QString &project_dir, const QString &name)
{
    // 确定目标项目文件
    filepath_store = fmtedProjectPath(project_dir, name);

    QFileInfo info(filepath_store);
    if(info.exists())
        throw new Impl_ProjectException("新建错误", "指定路径的项目文件已经存在，无法构建新项目");

    // 构建项目表示树
    auto project_node = new ProjectNode(NodeType::ROOT, name);
    project_node->setFile(".project_config.xml");
    project_structure->appendRow(project_node);

    QDir root(project_dir);
    project_config->loadFile(root.filePath(project_node->file()));

    // 写出到磁盘
    save();
    open_status = true;
    project_node->setIcon(QIcon(":/icons/toplevel.png"));
}

QString XMLProjectManager::fmtedProjectPath(const QString &project_dir, const QString &name) const
{
    QDir root(project_dir);
    auto project_file = root.filePath(name + ".nsf");

    return project_file;
}

void XMLProjectManager::closeProject()
{
    open_status = false;
    project_structure->clear();
}

void XMLProjectManager::save()
{
    project_config->save();

    QDomDocument doc;
    auto pnode = static_cast<ProjectNode*>(project_structure->item(0));
    doc.appendChild(doc.createProcessingInstruction("xml", "version='1.0'"));
    auto project_elm = doc.createElement("project");
    project_elm.setAttribute("name", pnode->text());
    project_elm.setAttribute("config", pnode->file());
    doc.appendChild(project_elm);

    structure_severlize(pnode, project_elm);

    QFile records(filepath_store);
    if(!records.open(QIODevice::WriteOnly|QIODevice::Text))
        throw new Impl_ProjectException("保存错误", "保存过程中，指定项目文件无法打开保存：" + filepath_store);

    QTextStream txout(&records);
    doc.save(txout, 4);
    txout.flush();
}

QString XMLProjectManager::projectName() const
{
    return project_structure->item(0)->text();
}

void XMLProjectManager::resetProjectName(const QString &name)
{
    this->project_structure->item(0)->setText(name);
}

Configration *XMLProjectManager::configraions() const
{
    return this->project_config;
}

QDir XMLProjectManager::projectDir() const
{
    return QFileInfo(filepath_store).absoluteDir();
}

void XMLProjectManager::structure_parser(QDomElement struct_elm, ProjectNode *pnode)
{
    auto children = struct_elm.childNodes();

    for(auto idx=0; idx<children.count(); ++idx) {
        auto node = children.at(idx);
        if(node.isElement()){
            auto xnode = node.toElement();

            ProjectNode *node = nullptr;
            if(xnode.tagName()=="package"){
                node = new ProjectNode(NodeType::GROUP, xnode.attribute("name"));
            }
            else if(xnode.tagName() == "file"){
                node = new ProjectNode(NodeType::FILE, xnode.attribute("name"));
                node->setFile(xnode.attribute("path"));
                all_files_managed << node->file();
            }

            pnode->appendRow(node);
            structure_parser(xnode, node);
        }
    }
}

void XMLProjectManager::structure_severlize(ProjectNode *pnode, QDomElement &elm)
{
    auto doc = elm.ownerDocument();
    for(auto idx=0; idx<pnode->rowCount(); ++idx){
        auto item = static_cast<ProjectNode*>(pnode->child(idx));

        QDomElement e = doc.createElement("package");
        e.setAttribute("name", item->text());

        if(item->nodeType() == NodeType::FILE){
            e.setTagName("file");
            e.setAttribute("path", item->file());
        }

        elm.appendChild(e);
        structure_severlize(item, e);
    }
}

QModelIndex XMLProjectManager::signFile(const QModelIndex &package_path, const QString &name, const QString &path_on_disk)
{
    if(!package_path.isValid())
        throw new Impl_ProjectException("参数错误", "指定的package_path无效");

    auto group = static_cast<ProjectNode*>(project_structure->itemFromIndex(package_path));
    if(group->nodeType() == NodeType::FILE)
        throw new Impl_ProjectException("参数错误", "传入了文件逻辑路径，而不是包路径");

    for(auto idx=0; idx<group->rowCount(); ++idx){
        auto child = static_cast<ProjectNode*>(group->child(idx));
        if(child->text() == name)
            throw new Impl_ProjectException("参数错误", "传入了重复的节点名称：" + name);
    }

    if(all_files_managed.contains(path_on_disk))
        throw new Impl_ProjectException("参数错误", "指定路径文件已经纳入项目管理："+path_on_disk);

    auto filenode = new ProjectNode(NodeType::FILE, name);
    filenode->setFile(path_on_disk);
    all_files_managed << path_on_disk;

    group->appendRow(filenode);

    return filenode->index();
}

void XMLProjectManager::rename(const QModelIndex &node, const QString &name)
{
    auto item = this->model()->itemFromIndex(node);

    auto parent = item->parent();
    for(auto idx=0; idx<parent->rowCount(); ++idx)
        if(item != parent->child(idx) && item->text() == parent->child(idx)->text())
            throw new Impl_ProjectException("参数错误", "传入了重复的节点名称：" + name);

    item->setText(name);
}

QList<QModelIndex> XMLProjectManager::filesGather(const QModelIndex &node) const
{
    QList<QModelIndex> rets;
    if(!node .isValid())
        return rets;

    auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(node));
    switch (item->nodeType()) {
        case NodeType::ROOT:
        case NodeType::GROUP:
            for(auto idx=0; idx<item->rowCount(); ++idx)
                rets.append(filesGather(item->child(idx)->index()));
            break;
        case NodeType::FILE:
            rets.append(item->index());
            break;
    }
    return rets;
}

void XMLProjectManager::deleteTarget(const QModelIndex &node_path)
{
    if(!node_path.isValid())
        throw new Impl_ProjectException("参数错误", "指定的节点路径无效");

    auto xnode = static_cast<ProjectNode*>(project_structure->itemFromIndex(node_path));
    if(xnode->nodeType() == NodeType::ROOT)
        throw new Impl_ProjectException("参数错误", "不允许删除项目节点");

    xnode->parent()->removeRow(xnode->row());
}

QList<std::tuple<QString, QFileInfo> > XMLProjectManager::filesWithEnds(const QString &suffix) const
{
    auto root_project = project_structure->item(0);
    return nodes_search(static_cast<ProjectNode*>(root_project), suffix);
}

QModelIndex XMLProjectManager::newPackage(const QList<QString> &path)
{
    auto pnode = project_structure->item(0);
    return groups_rebuild(static_cast<ProjectNode*>(pnode), path);
}

QStringList XMLProjectManager::packagePath(const QModelIndex &path) const
{
    auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(path));

    auto project_node = project_structure->item(0);
    auto query_path = [project_node](QStandardItem *it) -> QStringList{
        QStringList values;
        while (it != project_node) {
            values.insert(0, it->text());
            it = it->parent();
        }
        return values;
    };

    if(item->nodeType() == NodeType::GROUP)
        return query_path(item);
    else if(item->nodeType() == NodeType::FILE)
        return query_path(item->parent());
    else
        return QStringList();
}

QFileInfo XMLProjectManager::queryInfo(const QModelIndex &path)
{
    if(!path.isValid())
        throw new Impl_ProjectException("参数错误", "指定的路径参数无效");

    auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(path));
    if(item->nodeType() == NodeType::GROUP){
        return QFileInfo(QFileInfo(filepath_store).canonicalPath());
    }

    return QFileInfo(QFileInfo(filepath_store).dir().filePath(item->file()));
}

QModelIndex XMLProjectManager::queryIndex(const QFileInfo &file)
{
    auto xinfo = query_index(file, project_structure->item(0)->index());

    if(xinfo.isValid())
        return xinfo;

    return QModelIndex();
}

ProjectNode *XMLProjectManager::node_follows(ProjectNode *pnode, const QList<QString> &path_remains)
{
    if(path_remains.size()==0)
        return nullptr;

    for(auto idx=0; idx<pnode->rowCount(); ++idx){
        auto node = static_cast<ProjectNode*>(pnode->child(idx));
        if(node->text() == path_remains[0]){
            if(path_remains.size() == 1)
                return node;
            else
                return node_follows(node, path_remains.mid(1));
        }
    }

    return nullptr;
}

QList<std::tuple<QString, QFileInfo>> XMLProjectManager::nodes_search(ProjectNode *pnode, const QString &suffix) const
{
    QDir root_dir = QFileInfo(this->filepath_store).absoluteDir();
    QList<std::tuple<QString, QFileInfo>> infos_return;

    for(auto idx=0; idx<pnode->rowCount(); ++idx){
        auto item = static_cast<ProjectNode*>(pnode->child(idx));

        if(item->nodeType() == NodeType::FILE){
            auto file_path = root_dir.filePath(item->file());
            QFileInfo xinfo(file_path);
            if(xinfo.suffix() == suffix)
                infos_return << std::make_tuple(item->text(), xinfo);
        }
        else{
            auto elist = nodes_search(item, suffix);
            infos_return.append(elist);
        }
    }

    return infos_return;
}

QModelIndex XMLProjectManager::groups_rebuild(ProjectNode *pnode, const QList<QString> &path_remains)
{
    // 查找对匹配结果
    for(auto idx=0; idx<pnode->rowCount(); ++idx){
        auto item = static_cast<ProjectNode*>(pnode->child(idx));
        if(item->text() == path_remains[0] && item->nodeType() == NodeType::GROUP){
            if(path_remains.size() > 1)
                return groups_rebuild(item, path_remains.mid(1));
            return item->index();
        }
    }

    auto item = new ProjectNode(NodeType::GROUP, path_remains[0]);
    pnode->appendRow(item);

    if(path_remains.size() > 1)
        return groups_rebuild(item, path_remains.mid(1));

    return item->index();
}

QModelIndex XMLProjectManager::query_index(const QFileInfo &file, const QModelIndex &base)
{
    if(!base.isValid())
        return QModelIndex();

    auto item = static_cast<ProjectNode*>(project_structure->itemFromIndex(base));
    if(item->nodeType() == NodeType::FILE) {
        auto dir = QFileInfo(filepath_store).absoluteDir();
        auto target_info = QFileInfo(dir.filePath(item->file()));
        if(target_info == file)
            return item->index();
    }
    else {
        for(auto idx=0; idx<item->rowCount(); ++idx)
        {
            auto idx_m = query_index(file, item->child(idx)->index());
            if(idx_m.isValid())
                return idx_m;
        }
    }

    return QModelIndex();
}


void XMLProjectManager::moveTo(const QModelIndex &item_path, const QModelIndex &target_group, int index)
{
    if(!item_path.isValid())
        throw new Impl_ProjectException("参数错误", "传入的源文件路径无效");

    if(!target_group.isValid())
        throw new Impl_ProjectException("参数错误", "传入的目标包路径无效");

    auto file = static_cast<ProjectNode*>(project_structure->itemFromIndex(item_path));
    if(file->nodeType() == NodeType::GROUP)
        throw new Impl_ProjectException("参数错误","传入的源文件节点路径无效");

    auto group = static_cast<ProjectNode*>(project_structure->itemFromIndex(target_group));
    if(group->nodeType() != NodeType::GROUP)
        throw new Impl_ProjectException("参数错误", "传入的目标包路径无效");

    if(index >= 0 && index < group->rowCount())
        group->insertRow(index, file);
    else
        group->appendRow(file);
}





// 完成线 =======================================================================================================


ProjectNode::ProjectNode(NodeType t, const QString &name)
    : QStandardItem(name),type_indi(t)
{
    if(t == NodeType::FILE)
    {
        setIcon(QIcon(":/icons/文件.png"));
    }
    else
    {
        setIcon(QIcon(":/icons/集合.png"));
    }

    setEditable(false);
}

NodeType ProjectNode::nodeType() const
{return type_indi;}

void ProjectNode::setFile(const QString &name)
{
    this->filename = name;

    if(name.endsWith("storychain"))
        setIcon(QIcon(":/icons/脉络.png"));
    if(name.endsWith("storyunit"))
        setIcon(QIcon(":/icons/单元.png"));
    if(name.endsWith("storyboard"))
        setIcon(QIcon(":/icons/故事.png"));
    if(name.endsWith("storyvolume"))
        setIcon(QIcon(":/icons/叙述.png"));
    if(name.endsWith("txt"))
        setIcon(QIcon(":/icons/文件.png"));
    if(name.endsWith("storyconcept"))
        setIcon(QIcon(":/icons/概念.png"));
}

QString ProjectNode::file() const
{return filename;}




















