#include "job.h"
#include <QVariantMap>
#include <QVariantList>
#include <QFile>
#include <QProcess>
#include <QElapsedTimer>
#include <QPen>
#include <QFileInfo>
#include <QDir>
#include <QSet>
#include <QJsonDocument>
#include <QDebug>
#include "../parser/parserhelper.h"
#include "../parser/odbmatrixparser.h"
#include "../parser/odbedadataparser.h"
#include "../parser/odbcomponentparser.h"
#include "../parser/gerber274xparser.h"
#include "../parser/drillparser.h"
#include "./common.h"
#include "./util.h"
#include "./symbolfactory.h"
#include "./config.h"
#include "./core.h"


class PcbCamJobPrivate
{
    Q_DECLARE_PUBLIC(PcbCamJob)
public:
    explicit PcbCamJobPrivate(PcbCamJob *qptr) : q_ptr(qptr) {}
    ~PcbCamJobPrivate()
    {
        qDeleteAll(stepsMap);
        qDeleteAll(layersMap);
        qDeleteAll(symbolsMap);
        qDeleteAll(fontsMap);
        stepsMap.clear();
        layersMap.clear();
        symbolsMap.clear();
        fontsMap.clear();
        auto dataio = PcbCamCore::instance()->dataIo();
        if (dataio != nullptr) {
            dataio->clearJobDataCache(jobPath);
        }
    }


    //料号名
    QString jobname;

    //ODB++主版本号
    uint odbVersionMajor {0};

    //ODB++次版本号
    uint odbVersionMinor {0};

    //创建时间
    QDateTime creationDate;

    //保存时间
    QDateTime saveDate;

    //保存此料号数据的App名称
    QString saveApp;

    //保存此料号数据的用户名；
    QString saveUser;

    //料号属性
    QVariantMap jobAttrMap;

    //自定义属性定义
    QHash<QString, PcbCamAttrDefine> userAttrDefineMap;


    //Steps
    QHash<QString, PcbCamStep*> stepsMap;

    //Layers
    QHash<QString, PcbCamLayer*> layersMap;

    //料号路径
    QString jobPath;

    //日志中显示的料号路径
    QString logJobPath;

    //层别
    QList<PcbCamMatrixLayer> matrixLayers;

    QHash<QString, int> matrixSteps;

    QHash<QString, PcbCamSymbol*> symbolsMap;

    QHash<QString, PcbCamFont*> fontsMap;

    int getMaxStepNum() const {
        int ret = 0;
        for (auto n : matrixSteps.values()) {
            if (ret < n) ret = n;
        }
        return ret;
    }

    int getMaxLayerRow() const {
        int ret = 0;
        for (auto layer: matrixLayers) {
            if (ret < layer.row()) ret = layer.row();
        }
        return ret;
    }

    int getMaxLayerId() const {
        int ret = 0;
         for (auto layer: matrixLayers) {
             if (ret < layer.id()) ret = layer.id();
         }
         return ret;
    }

    int getMaxConstructSymbolNum() const {
        QRegularExpression regx("^construct\\+(\\d+)");
        int ret = 0;
        for (auto sym : symbolsMap.keys()) {
            auto match = regx.match(sym);
            if (match.hasMatch()) {
                int n = match.captured(1).toInt();
                if (ret < n) ret = n;
            }
        }
        return ret;
    }

protected:
    PcbCamJob * const q_ptr;
};

PcbCamJob::PcbCamJob(QObject *parent) : QObject(parent),
    d_ptr(new PcbCamJobPrivate(this))
{

}

PcbCamJob::~PcbCamJob()
{

}

QString PcbCamJob::jobname() const
{
    Q_D(const PcbCamJob);
    return d->jobname;
}

void PcbCamJob::setJobname(const QString &iJobname)
{
    Q_D(PcbCamJob);
    d->jobname = iJobname;
}

uint PcbCamJob::odbVersionMajor() const
{
    Q_D(const PcbCamJob);
    return d->odbVersionMajor;
}

void PcbCamJob::setOdbVersionMajor(uint iVersion)
{
    Q_D(PcbCamJob);
    d->odbVersionMajor = iVersion;
}

uint PcbCamJob::odbVersionMinor() const
{
    Q_D(const PcbCamJob);
    return d->odbVersionMinor;
}

void PcbCamJob::setOdbVersionMinor(uint iVersion)
{
    Q_D(PcbCamJob);
    d->odbVersionMinor = iVersion;
}

QDateTime PcbCamJob::creationDate() const
{
    Q_D(const PcbCamJob);
    return d->creationDate;
}

void PcbCamJob::setCreationDate(QDateTime iDt)
{
    Q_D(PcbCamJob);
    d->creationDate = iDt;
}

QDateTime PcbCamJob::saveDate() const
{
    Q_D(const PcbCamJob);
    return d->saveDate;
}

void PcbCamJob::setSaveDate(QDateTime iDt)
{
    Q_D(PcbCamJob);
    d->saveDate = iDt;
}

QString PcbCamJob::saveApp() const
{
    Q_D(const PcbCamJob);
    return d->saveApp;
}

void PcbCamJob::setSaveApp(const QString &iApp)
{
    Q_D(PcbCamJob);
    d->saveApp = iApp;
}

QString PcbCamJob::saveUser() const
{
    Q_D(const PcbCamJob);
    return d->saveUser;
}

void PcbCamJob::setSaveUser(const QString &iUser)
{
    Q_D(PcbCamJob);
    d->saveUser = iUser;
}

QVariant PcbCamJob::jobAttr(const QString &iName) const
{
    Q_D(const PcbCamJob);
    return d->jobAttrMap.value(iName.toLower());
}

bool PcbCamJob::hasJobAttr(const QString &iName) const
{
    Q_D(const PcbCamJob);
    return d->jobAttrMap.contains(iName.toLower());
}

void PcbCamJob::setJobAttr(const QString &iName, const QVariant &iValue)
{
    Q_D(PcbCamJob);
    d->jobAttrMap.insert(iName.toLower(), iValue);
}

PcbCamAttrDefine PcbCamJob::userAttrDefine(const QString &iName) const
{
    Q_D(const PcbCamJob);
    return d->userAttrDefineMap.value(iName.toLower());
}

QStringList PcbCamJob::userAttrDefineKeys() const
{
    Q_D(const PcbCamJob);
    return d->userAttrDefineMap.keys();
}

QList<PcbCamAttrDefine> PcbCamJob::userAttrDefineValues() const
{
    Q_D(const PcbCamJob);
    return d->userAttrDefineMap.values();
}

void PcbCamJob::addUserAttrDefine(const PcbCamAttrDefine &iAttrDefine)
{
    Q_D(PcbCamJob);
    d->userAttrDefineMap.insert(iAttrDefine.name().toLower(), iAttrDefine);
}

PcbCamStep *PcbCamJob::step(const QString &iStepName) const
{
    Q_D(const PcbCamJob);
    return d->stepsMap.value(iStepName.toLower(), nullptr);
}

QHash<QString, PcbCamStep *> PcbCamJob::allSteps() const
{
    Q_D(const PcbCamJob);
    return d->stepsMap;
}

//void PcbCamJob::addStep(PcbCamStep *iStep)
//{
//    addStep(QVector<PcbCamStep*>() << iStep);
//}

//void PcbCamJob::addStep(const QVector<PcbCamStep *> &iStep)
//{
//    Q_D(PcbCamJob);
//    QStringList stepNames;
//    for (PcbCamStep *step: iStep) {
//        QString stepName = step->name().toLower();
//        stepNames.append(stepName);
//        if (d->stepsMap.contains(stepName)) {
//            delete d->stepsMap[stepName];
//        }
//        d->stepsMap.insert(stepName, step);
//        d->matrixSteps.insert(stepName, d->stepsMap.count() + 1);
//    }
//    emit actionNotified("add_step", QVariantMap{{"steps",stepNames}});
//}

bool PcbCamJob::createStep(const QString &iStepName)
{
    Q_D(PcbCamJob);
    if (isStepExists(iStepName)) return false;
    auto step = new PcbCamStep;
    step->setName(iStepName.toLower());
    d->stepsMap.insert(step->name(), step);
    d->matrixSteps.insert(step->name(), d->getMaxStepNum() + 1);
    return true;
}

bool PcbCamJob::deleteStep(const QString &iStepName)
{
    Q_D(PcbCamJob);
    if (!isStepExists(iStepName)) return false;
    auto step = d->stepsMap[iStepName];
    delete step;
    d->stepsMap.remove(iStepName);
    d->matrixSteps.remove(iStepName);
    return true;
}

bool PcbCamJob::renameStep(const QString &iOldStepName, const QString &iNewStepName)
{
    Q_D(PcbCamJob);
    if (!isStepExists(iOldStepName)) return false;
    auto step = d->stepsMap[iOldStepName];
    step->setName(iNewStepName.toLower());
    d->stepsMap.remove(iOldStepName);
    d->stepsMap.insert(iNewStepName.toLower(), step);
    auto stepSeq = d->matrixSteps.value(iOldStepName);
    d->matrixSteps.remove(iOldStepName);
    d->matrixSteps.insert(iNewStepName.toLower(), stepSeq);
    // TODO 改名后应该把其它step的SR中的stepname也改掉；还有加载原始文件路径问题
    return true;
}

bool PcbCamJob::createLayer(const QString &iLayerName, PcbCam::LayerContext iLayerContext, const QString &iLayerType)
{
    Q_D(PcbCamJob);
    PcbCamMatrixLayer layer;
    layer.setName(iLayerName);
    layer.setContext(iLayerContext);
    layer.setType(iLayerType);
    layer.setRow(d->getMaxLayerRow() + 1);
    layer.setId(d->getMaxLayerId() + 1);
    d->matrixLayers.append(layer);
    QVariantMap userData;
    userData.insert("name", iLayerName);
    userData.insert("context", (int)iLayerContext);
    userData.insert("type", iLayerType);
    emit actionNotified("create_layer", QVariant::fromValue(userData));
    return true;
}

bool PcbCamJob::isStepExists(const QString &iStepName) const
{
    Q_D(const PcbCamJob);
    return d->stepsMap.contains(iStepName.toLower());
}

PcbCamLayer *PcbCamJob::layer(const QString &iStepName, const QString &iLayerName)
{
    Q_D(const PcbCamJob);
    loadStepLayer(iStepName, iLayerName);
    PcbCamLayer *layer =  d->layersMap.value(iStepName.toLower() + "/" + iLayerName.toLower(), nullptr);
    if (layer == nullptr && isStepExists(iStepName)) {
        loadStepLayer(iStepName, iLayerName);
        layer = new PcbCamLayer(this);
        layer->setName(iLayerName.toLower());
        layer->setStep(iStepName.toLower());
    }
    return layer;
}

bool PcbCamJob::isStepLayerExists(const QString &iStepName, const QString &iLayerName) const
{
    Q_D(const PcbCamJob);
    return d->layersMap.contains(iStepName.toLower() + "/" + iLayerName.toLower());
}

bool PcbCamJob::renameLayer(const QString &iOldLayerName, const QString &iNewLayerName)
{
    Q_D(PcbCamJob);
    auto old_ln = iOldLayerName.toLower();
    auto new_ln = iNewLayerName.toLower();
    if (isLayerExists(new_ln)) return false;
    for (auto &layer : d->matrixLayers) {
        if (layer.name() == old_ln) {
            layer.setName(iNewLayerName.toLower());

            for (auto key: d->layersMap.keys()) {
                auto tmp = key.split("/");
                auto step = tmp.value(0);
                auto ln = tmp.value(1);
                if (ln == old_ln) {
                    auto steplayer = d->layersMap.value(key);
                    steplayer->setName(new_ln);
                    d->layersMap.remove(key);
                    d->layersMap.insert(step + "/" + new_ln, steplayer);
                }
            }
            QVariantMap userData;
            userData.insert("old_name", iOldLayerName);
            userData.insert("new_name", iNewLayerName);
            emit actionNotified("rename_layer", QVariant::fromValue(userData));
            return true;
        }
    }
    return false;
}

void PcbCamJob::setLayerType(const QString &iLayerName, const QString &iLayerType)
{
    Q_D(PcbCamJob);
    for (auto &layer : d->matrixLayers) {
        if (layer.name() == iLayerName) {
            layer.setType(iLayerType);
            break;
        }
    }
}

void PcbCamJob::setLayerContext(const QString &iLayerName, PcbCam::LayerContext iLayerContext)
{
    Q_D(PcbCamJob);
    for (auto &layer : d->matrixLayers) {
        if (layer.name() == iLayerName) {
            layer.setContext(iLayerContext);
            break;
        }
    }
}

void PcbCamJob::setLayerPolarity(const QString &iLayerName, PcbCam::Polarity iLayerPolarity)
{
    Q_D(PcbCamJob);
    for (auto &layer : d->matrixLayers) {
        if (layer.name() == iLayerName) {
            layer.setPolarity(iLayerPolarity);
            break;
        }
    }
}

void PcbCamJob::setLayerSpan(const QString &iLayerName, const QString &iStartLayer, const QString &iEndLayer)
{
    Q_D(PcbCamJob);
    for (auto &layer : d->matrixLayers) {
        if (layer.name() == iLayerName) {
            layer.setStartName(iStartLayer);
            layer.setEndName(iEndLayer);
            break;
        }
    }
}

void PcbCamJob::setLayerRow(const QString &iLayerName, int iRow)
{
    Q_D(PcbCamJob);
    for (auto &layer : d->matrixLayers) {
        if (layer.name() == iLayerName) {
            layer.setRow(iRow);
            break;
        }
    }
}

void PcbCamJob::setStepWorkLayer(const QString &iStepName, const QString &iLayerName)
{
    auto oldwoklayer = stepWorkLayer(iStepName);
    this->loadStepLayer(iStepName, iLayerName);
    auto newworklayer = this->layer(iStepName, iLayerName);
    if (oldwoklayer == newworklayer) return;
    if (newworklayer != nullptr) newworklayer->setWorkLayer(true);
    if (oldwoklayer != nullptr) {
        oldwoklayer->setWorkLayer(false);
        if (!oldwoklayer->isAffectedLayer()) {
            oldwoklayer->clearSelection();
        }
    }

}

void PcbCamJob::setStepLayerAffected(const QString &iStepName, const QString &iLayerName, bool iAffected)
{
    if (iAffected) {
        this->loadStepLayer(iStepName, iLayerName);
    }
    auto layer = this->layer(iStepName, iLayerName);
    if (layer != nullptr) {
        layer->setAffectedLayer(iAffected);
        if (iAffected == false) {
            layer->clearSelection();
        }
    }
}

void PcbCamJob::setStepLayerDisplayColor(const QString &iStepName, const QString &iLayerName, const QString iColor)
{
    auto layer = this->layer(iStepName, iLayerName);
    if (layer != nullptr) {
        layer->setDisplayColor(iColor);
    }
}


void PcbCamJob::clearStepAffectedLayer(const QString &iStepName)
{
    Q_D(PcbCamJob);
    for (auto layer: d->layersMap.values()) {
        if (layer->step() != iStepName) continue;
        layer->setAffectedLayer(false);
        layer->clearSelection();
    }
}

void PcbCamJob::resetStepAttectedLayer(const QString &iStepName, const QStringList &iAffectedLayers)
{
    Q_D(PcbCamJob);
    for (auto l : iAffectedLayers) {
        this->loadStepLayer(iStepName, l);
    }
    for (auto layer: d->layersMap.values()) {
        if (layer->step() != iStepName) continue;
        if (iAffectedLayers.contains(layer->name())) {
            layer->setAffectedLayer(true);
        }
        else {
            layer->setAffectedLayer(false);
            if (!layer->isWorkLayer()) {
                layer->clearSelection();
            }
        }
    }
}

void PcbCamJob::clearStepWorkLayer(const QString &iStepName)
{
    Q_D(PcbCamJob);
    for (auto layer: d->layersMap.values()) {
        if (layer->step() != iStepName) continue;
        layer->setWorkLayer(false);
        layer->clearSelection();
    }
}

void PcbCamJob::clearStepDisplayLayer(const QString &iStepName)
{
    Q_D(PcbCamJob);
    for (auto layer: d->layersMap.values()) {
        if (layer->step() != iStepName) continue;
        layer->setDisplayColor("");
    }
}

PcbCamLayer *PcbCamJob::stepWorkLayer(const QString &iStepName) const
{
    Q_D(const PcbCamJob);
    for (const auto layer: d->layersMap.values()) {
        if (layer->step() == iStepName && layer->isWorkLayer()) {
            return layer;
        }
    }
    return nullptr;
}

QList<PcbCamLayer *> PcbCamJob::stepAffectedLayer(const QString &iStepName) const
{
    Q_D(const PcbCamJob);
    QList<PcbCamLayer *> result;
    for (const auto layer: d->layersMap.values()) {
        if (layer->step() == iStepName && layer->isAffectedLayer()) {
            result.append(layer);
        }
    }
    return result;
}

QList<PcbCamLayer *> PcbCamJob::stepDispalyLayer(const QString &iStepName) const
{
    Q_D(const PcbCamJob);
    QList<PcbCamLayer *> result;
    for (const auto layer : d->layersMap.values()) {
        if (layer->step() == iStepName && !layer->displayColor().isEmpty()) {
            result.append(layer);
        }
    }
    return result;
}

QString PcbCamJob::jobPath() const
{
    Q_D(const PcbCamJob);
    return d->jobPath;
}

void PcbCamJob::setJobPath(const QString &iPath)
{
    Q_D(PcbCamJob);
    d->jobPath = iPath;
    d->logJobPath = iPath;
    d->jobname = QFileInfo(iPath).fileName();
}

void PcbCamJob::setLogJobPath(const QString &iPath)
{
    Q_D(PcbCamJob);
    d->logJobPath = iPath;
}

QList<PcbCamMatrixLayer> PcbCamJob::matrixLayers() const
{
    Q_D(const PcbCamJob);
    return d->matrixLayers;
}

QHash<QString, int> PcbCamJob::matrixSteps() const
{
    Q_D(const PcbCamJob);
    return d->matrixSteps;
}

bool PcbCamJob::isLayerExists(const QString &iLayerName) const
{
    Q_D(const PcbCamJob);
    auto ln = iLayerName.toLower();
    for (auto layer: d->matrixLayers) {
        if (layer.name() == ln) {
            return true;
        }
    }
    return false;
}

PcbCamMatrixLayer PcbCamJob::matrixLayer(const QString &iLayerName) const
{
    Q_D(const PcbCamJob);
    auto ln = iLayerName.toLower();
    for (auto layer: d->matrixLayers) {
        if (layer.name() == ln) {
            return layer;
        }
    }
    return PcbCamMatrixLayer();
}

bool PcbCamJob::isSymbolExists(const QString &iSymbolName) const
{
    Q_D(const PcbCamJob);
    return d->symbolsMap.contains(iSymbolName);
}

PcbCamSymbol *PcbCamJob::symbol(const QString &iSymbolName) const
{
    Q_D(const PcbCamJob);
    return d->symbolsMap.value(iSymbolName, nullptr);
}

QHash<QString, PcbCamSymbol*> PcbCamJob::symbols() const
{
    Q_D(const PcbCamJob);
    return d->symbolsMap;
}

bool PcbCamJob::isFontExists(const QString &iFontName) const
{
    Q_D(const PcbCamJob);
    return d->fontsMap.contains(iFontName);
}

PcbCamFont *PcbCamJob::font(const QString &iFontName) const
{
    Q_D(const PcbCamJob);
    return d->fontsMap.value(iFontName, nullptr);
}

void PcbCamJob::openJob()
{
    readMiscInfo();
    readMiscUserattr();
    readMiscAttrlist();
    readMatrix();
    readSteps();
}

void PcbCamJob::loadStepLayer(const QString &iStep, const QString &iLayer)
{
    Q_D(PcbCamJob);
    //QElapsedTimer et; et.start();
    if (!isStepExists(iStep) || iLayer.isEmpty()) {
        return;
    }
    PcbCamStep *step = this->step(iStep);

    if (!d->layersMap.contains(iStep.toLower() + "/" + iLayer.toLower())) {
        QSet<QString> srsteps;
        for (const PcbCamStepRepeat &sr : step->repeats()) {
            srsteps.insert(sr.step());
        }

        for (const QString &srstep: srsteps) {
            this->loadStepLayer(srstep, iLayer);
        }

        PcbCamLayer *layer = new PcbCamLayer(this);
        layer->setName(iLayer.toLower());
        layer->setStep(step->name());

        if (iLayer.startsWith("comp_+_")) {
            QList<PcbCamSymbolUser*> pkgSymbols = step->edaPackageSymbols();
            if (pkgSymbols.isEmpty()) {
                auto pkgs = readEdaPackagesFeature(iStep);
                for (int i = 0; i < pkgs.count(); i++) {
                    auto userSymbol = new PcbCamSymbolUser(QString("%1/pkgs/%2").arg(iStep).arg(i));
                    auto feats = pkgs.at(i);
                    for (const PcbCamOdbFeatureInfo &f : feats) {
                        userSymbol->addFeature(createFeature(f));
                    }
                    pkgSymbols.append(userSymbol);
                }
                step->setEdaPackageSymbols(pkgSymbols);
            }

            QList<PcbCamOdbFeatureInfo> feats = readLayerComponent(iStep, iLayer);
            for (int i = 0; i < feats.count(); i++) {
                auto f = feats.at(i);
                if (iLayer.endsWith("_bot")) { //背面的需Mirror
                    f.cw = !f.cw;
                }
                auto feat = createFeature(f);
                feat->setSymbol(pkgSymbols.value(f.symbol.toInt(), nullptr));
                layer->addFeature(feat);
            }
        }
        else {
            QList<PcbCamOdbFeatureInfo> feats = readLayerFeature(iStep, iLayer);
            for (const PcbCamOdbFeatureInfo &f : feats) {
                if (!f.symbol.isEmpty() && !d->symbolsMap.contains(f.symbol)) {
                    this->loadSymbol(f.symbol);
                }
                if (f.type == PcbCam::TextFeature && !d->fontsMap.contains(f.font)) {
                    this->loadFont(f.font);
                }

                layer->addFeature(createFeature(f));
            }
        }

        QString err;
        QVariantMap attr = PcbCamParserHelper::parseOdbStructuredText(readJobFile("steps/"+iStep.toLower()+"/layers/"+iLayer.toLower()+"/attrlist"));
        if (!err.isEmpty()) {
            notifyMessage(err, "ERROR");
        }
        layer->initAttr(attr);
        d->layersMap.insert(layer->step() + "/" + layer->name(), layer);

    }

}

void PcbCamJob::loadSymbol(const QString &iSymbolName)
{
    Q_D(PcbCamJob);
    PcbCamSymbol *sym = PcbCamSymbolFactory::create(iSymbolName);
    if (sym->type() == PcbCam::Sym_User) {
        PcbCamSymbolUser *userSymbol = static_cast<PcbCamSymbolUser*>(sym);
        QList<PcbCamOdbFeatureInfo> feats = readSymbolFeature(iSymbolName);
        for (const PcbCamOdbFeatureInfo &f : feats) {
            if (!f.symbol.isEmpty() && !d->symbolsMap.contains(f.symbol)) {
                this->loadSymbol(f.symbol);
            }
            if (f.type == PcbCam::TextFeature && !d->fontsMap.contains(f.font)) {
                this->loadFont(f.font);
            }
            userSymbol->addFeature(createFeature(f));
        }
    }
    d->symbolsMap.insert(sym->name(), sym);
}

void PcbCamJob::loadFont(const QString &iFontName)
{
    Q_D(PcbCamJob);
    auto fontData = readJobFile("fonts/" + iFontName);
    if (fontData.isEmpty()) {
        QFile sysFontFile(":/res/misc/fonts/" + iFontName);
        if (sysFontFile.open(QFile::ReadOnly | QFile::Text)) {
            fontData = sysFontFile.readAll();
            sysFontFile.close();
        } else {
            notifyMessage(QString("read file %1 failed!").arg(":/res/misc/fonts/" + iFontName), "ERROR");
        }
    }
    QString err;
    PcbCamFont *font = new PcbCamFont();
    font->setName(iFontName);
    if (PcbCamParserHelper::parseOdbFont(fontData, font)) {
        d->fontsMap.insert(iFontName, font);
    } else {
        delete font;
        font = nullptr;
        notifyMessage(err, "ERROR");
    }
}

void PcbCamJob::notifyMessage(const QString &iText, const QString &iType, const QVariant &iUserData) const
{
    emit messageNotified(iText, iType, iUserData);
    PcbCamCore::instance()->notifyMessage(iText, iType, "JOB", iUserData);
}


bool PcbCamJob::readMiscInfo()
{
    QString err;
    QVariantMap data = PcbCamParserHelper::parseOdbStructuredText(readJobFile("misc/info"));
    if (!err.isEmpty()) {
        notifyMessage(err, "ERROR");
        return false;
    }

    this->setOdbVersionMajor(data.value("ODB_VERSION_MAJOR").toInt());
    this->setOdbVersionMinor(data.value("ODB_VERSION_MINOR").toInt());
    this->setCreationDate(QDateTime::fromString(data.value("CREATION_DATE").toString(), "yyyyMMdd.hhmmss"));
    this->setSaveDate(QDateTime::fromString(data.value("SAVE_DATE").toString(), "yyyyMMdd.hhmmss"));
    this->setSaveApp(data.value("SAVE_APP").toString());
    this->setSaveUser(data.value("SAVE_APP").toString());
    return true;
}

bool PcbCamJob::readMiscAttrlist()
{
    Q_D(PcbCamJob);
    QString err;
    QVariantMap data = PcbCamParserHelper::parseOdbStructuredText(readJobFile("misc/attrlist"));
    if (!err.isEmpty()) {
        notifyMessage(err, "ERROR");
        return false;
    }
    d->jobAttrMap = data;

    return true;
}

bool PcbCamJob::readMiscUserattr()
{
    Q_D(PcbCamJob);
    d->userAttrDefineMap.clear();

    QString err;
    QFile sysattrfile(":/res/misc/sysattr");
    sysattrfile.open(QFile::ReadOnly | QFile::Text);
    auto sysattrdata = sysattrfile.readAll();
    sysattrfile.close();
    QVariantMap sysdata = PcbCamParserHelper::parseOdbStructuredText(sysattrdata);
    if (!err.isEmpty()) {
        notifyMessage(err, "ERROR");
    }
    sysdata.remove("FORCE_LIB");
    for (const QString &k : sysdata.keys()) {
        for (const QVariant &it : sysdata.value(k).toList()) {
            QVariantMap item = it.toMap();
            PcbCamAttrDefine attr;
            attr.setDataType(PcbCamAttrDefine::dataTypeFromString(k));
            attr.setEntity(PcbCamAttrDefine::entityFromString(item.value("ENTITY").toString()));
            attr.setName(item.value("NAME").toString().toLower());
            attr.setPrompt(item.value("PROMPT").toString());
            attr.setDefaultValue(item.value("DEF"));
            attr.setMinLength(item.value("MIN_LEN").toInt());
            attr.setMaxLength(item.value("MAX_LEN").toInt());
            attr.setOptions(item.value("OPTIONS").toString().split(";", Qt::SkipEmptyParts));
            attr.setDeleted(item.value("DELETED").toString().split(";", Qt::SkipEmptyParts));
            d->userAttrDefineMap.insert(attr.name(), attr);
        }
    }

    QVariantMap userdata = PcbCamParserHelper::parseOdbStructuredText(readJobFile("misc/userattr"));
    if (!err.isEmpty()) {
        notifyMessage(err, "ERROR");
        return false;
    }

    userdata.remove("FORCE_LIB");
    for (const QString &k : userdata.keys()) {
        for (const QVariant &it : userdata.value(k).toList()) {
            QVariantMap item = it.toMap();
            PcbCamAttrDefine attr;
            attr.setDataType(PcbCamAttrDefine::dataTypeFromString(k));
            attr.setEntity(PcbCamAttrDefine::entityFromString(item.value("ENTITY").toString()));
            attr.setName(item.value("NAME").toString().toLower());
            attr.setPrompt(item.value("PROMPT").toString());
            attr.setDefaultValue(item.value("DEF"));
            attr.setMinLength(item.value("MIN_LEN").toInt());
            attr.setMaxLength(item.value("MAX_LEN").toInt());
            attr.setOptions(item.value("OPTIONS").toString().split(";", Qt::SkipEmptyParts));
            attr.setDeleted(item.value("DELETED").toString().split(";", Qt::SkipEmptyParts));
            d->userAttrDefineMap.insert(attr.name(), attr);
        }
    }

    return true;
}

bool PcbCamJob::readMatrix()
{
    Q_D(PcbCamJob);
    QString err;
    QVariantMap data = PcbCamParserHelper::parseOdbStructuredText(readJobFile("matrix/matrix"));
    if (!err.isEmpty()) {
        notifyMessage(err, "ERROR");
        return false;
    }
    d->matrixSteps.clear();
    d->matrixLayers.clear();
    for (const QVariant &step : data.value("STEP").toList()) {
        QVariantMap stepinfo = step.toMap();
        d->matrixSteps.insert(stepinfo.value("NAME").toString().toLower(),
                              stepinfo.value("COL").toInt());
    }
    for (const QVariant &l : data.value("LAYER").toList()) {
        QVariantMap layerinfo = l.toMap();
        PcbCamMatrixLayer layer;
        auto layerid = layerinfo.value("ID", 0).toInt();
        if (layerid <= 0) layerid = d->getMaxLayerId() + 1;
        layer.setId(layerid);
        layer.setRow(layerinfo.value("ROW").toInt());
        layer.setContext(layerinfo.value("CONTEXT").toString()=="BOARD" ? PcbCam::BoardContext : PcbCam::MiscContext);
        layer.setType(layerinfo.value("TYPE").toString().toLower());
        layer.setName(layerinfo.value("NAME").toString().toLower());
        layer.setPolarity(layerinfo.value("POLARITY").toString() == "NEGATIVE" ? PcbCam::Negative : PcbCam::Positive);
        layer.setStartName(layerinfo.value("START_NAME").toString().toLower());
        layer.setEndName(layerinfo.value("END_NAME").toString().toLower());
        layer.setOldName(layerinfo.value("OLD_NAME").toString().toLower());
        d->matrixLayers.append(layer);
    }

    return true;
}

bool PcbCamJob::readSteps()
{
    Q_D(PcbCamJob);
    /*
    notifyMessage(QString("read steps - %1/steps").arg(this->jobPath()), "INFO");
    QString err;
    QByteArray stepsba = readJobFile("steps", &err);
    if (!err.isEmpty()) {
        notifyMessage(QString("read steps - %1/steps failed! %2").arg(this->jobPath()).arg(err), "ERROR");
        return false;
    }

    if (stepsba.isEmpty()) stepsba.append("[]");
    QJsonParseError jsonerr;
    auto jsondoc = QJsonDocument::fromJson(stepsba, &jsonerr);
    if (jsonerr.error != QJsonParseError::NoError) {
        notifyMessage(QString("read steps - %1/steps failed! %2").arg(this->jobPath()).arg(jsonerr.errorString()), "ERROR");
        return false;
    }

    QStringList steps;
    for (auto it : jsondoc.toVariant().toList()) {
        steps << it.toString();
    }
    */

    QStringList steps = d->matrixSteps.keys();
    for (auto stepname : steps) {
        QString err;
        QVariantMap data = PcbCamParserHelper::parseOdbStructuredText(readJobFile("steps/" + stepname + "/stephdr"));
        if (!err.isEmpty()) {
            notifyMessage(err, "ERROR");
        }
        PcbCamStep *step = new PcbCamStep();
        step->setName(stepname);
        step->setDatum(QPointF(data.value("X_DATUM").toDouble(), data.value("Y_DATUM").toDouble()));
        step->setOrigin(QPointF(data.value("X_ORIGIN").toDouble(), data.value("Y_ORIGIN").toDouble()));
        step->setActiveMargin(QMarginsF(data.value("LEFT_ACTIVE").toDouble(),
                                       data.value("TOP_ACTIVE").toDouble(),
                                       data.value("RIGHT_ACTIVE").toDouble(),
                                       data.value("BOTTOM_ACTIVE").toDouble()));

        for (const QVariant &row : data.value("STEP-REPEAT").toList()) {
            QVariantMap srinfo = row.toMap();
            PcbCamStepRepeat sr;
            sr.setStep(srinfo.value("NAME").toString().toLower());
            sr.setX(srinfo.value("X").toDouble());
            sr.setY(srinfo.value("Y").toDouble());
            sr.setDx(srinfo.value("DX").toDouble());
            sr.setDy(srinfo.value("DY").toDouble());
            sr.setNx(srinfo.value("NX").toInt());
            sr.setNy(srinfo.value("NY").toInt());
            sr.setAngle(srinfo.value("ANGLE").toDouble());
            sr.setMirror(srinfo.value("MIRROR").toString() == "YES" ? true : false);
            step->addRepeat(sr);
        }

        QPainterPath profile = PcbCamParserHelper::parseOdbProfile(readJobFile("steps/" + stepname + "/profile"));
        step->setProfile(profile);

        QVariantMap stepattr = PcbCamParserHelper::parseOdbStructuredText(readJobFile("steps/" + stepname + "/attrlist"));
        step->initAttr(stepattr);
        d->stepsMap.insert(stepname, step);
    }
    //addStep(newSteps);
    return true;
}

QList<PcbCamOdbFeatureInfo> PcbCamJob::readLayerFeature(const QString &iStep, const QString &iLayer)
{
    QString relpath = "steps/" + iStep.toLower() + "/layers/" + iLayer.toLower() + "/features";
    auto featdata = readJobFile(relpath);

    PcbCamOdbFeatureParser parser(featdata);
    parser.parse();
    QStringList errs = parser.errors();
    if (!errs.isEmpty()) {
        notifyMessage(errs.join(";"), "ERROR");
    }
    return parser.features();

}

QList<PcbCamOdbFeatureInfo> PcbCamJob::readSymbolFeature(const QString &iSymbol)
{
    QString relpath = "symbols/" + iSymbol.toLower() + "/features";
    auto featdata = readJobFile(relpath);

    PcbCamOdbFeatureParser parser(featdata);
    parser.parse();
    QStringList errs = parser.errors();
    if (!errs.isEmpty()) {
        notifyMessage(errs.join(";"), "ERROR");
    }
    return parser.features();
}

QList<QList<PcbCamOdbFeatureInfo>> PcbCamJob::readEdaPackagesFeature(const QString &iStep)
{
    QString relpath = "steps/" + iStep.toLower() + "/eda/data";
    auto featdata = readJobFile(relpath);

    PcbCamOdbEdaDataParser parser(featdata);
    parser.parse();
    QStringList errs = parser.errors();
    if (!errs.isEmpty()) {
        notifyMessage(errs.join(";"), "ERROR");
    }
    return parser.packageFeatures();
}

QList<PcbCamOdbFeatureInfo> PcbCamJob::readLayerComponent(const QString &iStep, const QString &iLayer)
{
    QString relpath = "steps/" + iStep.toLower() + "/layers/" + iLayer.toLower() + "/components";
    auto featdata = readJobFile(relpath);

    PcbCamOdbComponentParser parser(featdata);
    parser.parse();
    QStringList errs = parser.errors();
    if (!errs.isEmpty()) {
        notifyMessage(errs.join(";"), "ERROR");
    }
    return parser.features();
}

bool PcbCamJob::importGerber274X(const QString &iGerberFile, const QString &iStep, const QString &iLayer)
{
    Q_D(PcbCamJob);
    if (!isStepExists(iStep)) createStep(iStep);
    if (!isLayerExists(iLayer)) createLayer(iLayer);
    auto layer = this->layer(iStep, iLayer);
    if (layer == nullptr) return false;
    layer->clearFeatures();

    QFile gbrfile(iGerberFile);
    if (!gbrfile.open(QFile::ReadOnly|QFile::Text)) return false;
    PcbCamGerber274XParser parser;
    parser.parse(&gbrfile);
    gbrfile.close();

    auto apertures = parser.apertures();
    QHash<int, QString> dcodeSymbolMap;
    auto sym_num = d->getMaxConstructSymbolNum();
    for (auto ap : apertures) {
        auto sym_name = ap.name;
        if (!ap.isStandard) {
            sym_name = QString("construct+%1").arg(++sym_num);
        }
        if (!d->symbolsMap.contains(sym_name)) {
            PcbCamSymbol *sym = PcbCamSymbolFactory::create(sym_name);
            if (sym->type() == PcbCam::Sym_User) {
                PcbCamSymbolUser *userSymbol = static_cast<PcbCamSymbolUser*>(sym);
                for (const PcbCamOdbFeatureInfo &f : ap.features) {
                    userSymbol->addFeature(createFeature(f));
                }
            }
            d->symbolsMap.insert(sym->name(), sym);
        }
        dcodeSymbolMap.insert(ap.dcode, sym_name);
    }

    auto features = parser.features();
    int featindex = 1;
    for (auto f: features) {
        if (!dcodeSymbolMap.contains(f.decode)) {
            QString sym_name = "r0";
            dcodeSymbolMap.insert(f.decode, sym_name);
            if (!d->symbolsMap.contains(sym_name)) {
                PcbCamSymbol *sym = PcbCamSymbolFactory::create(sym_name);
                d->symbolsMap.insert(sym_name, sym);
            }
        }
        f.index = featindex++;
        f.symbol = dcodeSymbolMap.value(f.decode);
        layer->addFeature(createFeature(f));
    }
    d->layersMap.insert(layer->step() + "/" + layer->name(), layer);

    return true;
}

bool PcbCamJob::importDrill(const QString &iDrlFile, const QString &iStep, const QString &iLayer, const QVariantMap &iStatement)
{
    Q_D(PcbCamJob);
    if (!isStepExists(iStep)) createStep(iStep);
    if (!isLayerExists(iLayer)) createLayer(iLayer);
    auto layer = this->layer(iStep, iLayer);
    if (layer == nullptr) return false;
    layer->clearFeatures();

    PcbCamDrillParser parser(iDrlFile, PcbCamDrillParser::DrillStatement(iStatement));
    parser.parser();
    for (auto symname: parser.symbols()) {
        if (!d->symbolsMap.contains(symname)) {
            auto sym = PcbCamSymbolFactory::create(symname);
            d->symbolsMap.insert(sym->name(), sym);
        }
    }

    auto features = parser.features();
    int featindex = 1;
    for (auto f: features) {
        f.index = featindex++;
        layer->addFeature(createFeature(f));
    }
    d->layersMap.insert(layer->step() + "/" + layer->name(), layer);
    return true;
}

QByteArray PcbCamJob::readJobFile(const QString &iRelPath) const
{
    Q_D(const PcbCamJob);
    QString filepath = d->logJobPath + "/" +  iRelPath;
    notifyMessage(QString("read file - %1").arg(filepath), "INFO");
    QString err;
    auto result = PcbCamCore::instance()->dataIo()->readJobData(this->jobPath(), iRelPath);
    if (!err.isEmpty()) {
        notifyMessage(QString("read file -%1 failed! %2").arg(filepath).arg(err), "ERROR");
    }
    return result;
}

PcbCamFeature *PcbCamJob::createFeature(const PcbCamOdbFeatureInfo &iFeat)
{
    Q_D(const PcbCamJob);
    if (iFeat.type == PcbCam::LineFeature) {
        PcbCamFeatureLine *f = new PcbCamFeatureLine();
        f->setPolarity(iFeat.polarity);
        f->setIndex(iFeat.index);
        f->setDcode(iFeat.decode);
        f->setSymbol(d->symbolsMap[iFeat.symbol]);
        f->setAttr(iFeat.attributes);
        f->setLine(QLineF(iFeat.ps, iFeat.pe));
        return f;
    }
    else if (iFeat.type == PcbCam::PadFeature) {
        PcbCamFeaturePad *f = new PcbCamFeaturePad();
        f->setPolarity(iFeat.polarity);
        f->setIndex(iFeat.index);
        f->setDcode(iFeat.decode);
        f->setSymbol(d->symbolsMap[iFeat.symbol]);
        f->setAttr(iFeat.attributes);
        f->setPos(iFeat.ps);
        f->setOrient(iFeat.orient);
        return f;
    }
    else if (iFeat.type == PcbCam::ArcFeature) {
        PcbCamFeatureArc *f = new PcbCamFeatureArc();
        f->setPolarity(iFeat.polarity);
        f->setIndex(iFeat.index);
        f->setDcode(iFeat.decode);
        f->setSymbol(d->symbolsMap[iFeat.symbol]);
        f->setAttr(iFeat.attributes);
        f->setData(iFeat.ps, iFeat.pe, iFeat.pc, iFeat.cw);
        return f;
    }
    else if (iFeat.type == PcbCam::TextFeature) {
        PcbCamFeatureText *f = new PcbCamFeatureText();
        f->setPolarity(iFeat.polarity);
        f->setIndex(iFeat.index);
        f->setAttr(iFeat.attributes);
        f->setData(iFeat.ps, d->fontsMap.value(iFeat.font), iFeat.orient, iFeat.size, iFeat.widthFactor, iFeat.text, iFeat.version);
        return f;
    }
    else if (iFeat.type == PcbCam::BarcodeFeature) {
        PcbCamFeatureBarcode *f = new PcbCamFeatureBarcode();
        f->setIndex(iFeat.index);
        f->setPos(iFeat.ps);
        f->setBarcode(iFeat.barcode);
        f->setFont(iFeat.font);
        f->setPolarity(iFeat.polarity);
        f->setOrient(iFeat.orient);
        f->setSize(iFeat.size);
        f->setFullAscii(iFeat.fullAscii);
        f->setCheckSum(iFeat.checkSum);
        f->setBackground(iFeat.background);
        f->setAdditionStr(iFeat.additionStr);
        f->setAdditionStrPos(iFeat.additionStrPos);
        f->setText(iFeat.text);
        f->setAttr(iFeat.attributes);
        return f;
    }
    else if (iFeat.type == PcbCam::SurfaceFeature) {
        PcbCamFeatureSurface *f = new PcbCamFeatureSurface();
        f->setIndex(iFeat.index);
        f->setPolarity(iFeat.polarity);
        f->setAttr(iFeat.attributes);
        f->setPath(iFeat.path);
        return f;
    }
    else if (iFeat.type == PcbCam::PathFeature) {
        PcbCamFeaturePath *f = new PcbCamFeaturePath();
        f->setIndex(iFeat.index);
        f->setPolarity(iFeat.polarity);
        f->setAttr(iFeat.attributes);
        f->setPath(iFeat.path);
        f->setWidth(iFeat.widthFactor);
        return f;
    }
    else if (iFeat.type == PcbCam::ComponentFeature) {
        PcbCamFeatureComponent *f = new PcbCamFeatureComponent();
        f->setIndex(iFeat.index);
        f->setPolarity(iFeat.polarity);
        f->setAttr(iFeat.attributes);
        f->setData(iFeat.ps, iFeat.widthFactor, iFeat.cw, iFeat.text, iFeat.font);
        return f;
    }
    return nullptr;
}
