#include "gerber274xparser.h"
#include <QDebug>
#include <QtMath>
#include <QTextStream>
#include <QJSEngine>
#include <QJSValue>
#include <QPen>
#include <QPainterPath>
#include <QPainterPathStroker>
#include <QIODevice>
#include <QtMath>
#include "../core/util.h"


class PcbCamGerber274XParserPrivate
{
    Q_DECLARE_PUBLIC(PcbCamGerber274XParser)
public:
    PcbCamGerber274XParserPrivate(PcbCamGerber274XParser *qptr): q_ptr(qptr){ }
    ~PcbCamGerber274XParserPrivate() {}


    struct RegxMatchResult {
        bool hasMatch {false};
        QMap<QString, QString> captures;
    };


    enum InterpolationMode {
        LinearMode = 1,
        ClockwiseCircularMode = 2,
        CounterclockwiseCircularMode = 3,
    };

    enum QuadrantMode {
        SingleQuadrant,
        MultiQuadrant,
    };


    struct GraphicsState {
        PcbCam::UnitsType units {PcbCam::Units_Inch};
        PcbCam::ZerosOmit zerosOmit {PcbCam::ZerosOmit_Leading};
        PcbCam::CoordNotation coordNotation {PcbCam::CoordAbsoulte};
        int integerDigits {0};
        int decimalDigits {0};
        QPointF currentPoint;
        int currentAperture;
        InterpolationMode interpolationMode {LinearMode};
        QuadrantMode quadrantMode {SingleQuadrant};
        bool regionMode {false};
        PcbCam::Polarity polarity {PcbCam::Positive};
        bool mirror {false};
        int rotation {0};
        int operationMode {2}; //D01 D02 D03
        bool srMode {false};
        int srRepeatsX {1};
        int srRepeatsY {1};
        double srStepI {0.0};
        double srStepJ {0.0};
    };


    QStringList errorList;
    QList<PcbCamOdbFeatureInfo> featureList;
    PcbCamGerber274XParserPrivate::GraphicsState graphicsState;
    QPainterPath curRegionPath;

    QMap<int, PcbCamGerber274XParser::ApertureInfo> apertures;
    QMap<QString, QStringList> apertureMacroDefines;

    QJSEngine scriptEngine;

    QMap<PcbCamGerber274XParser::Cmd, QRegularExpression> paramRegxs{
        { PcbCamGerber274XParser::Cmd::COORD,  QRegularExpression("^(G(?<cmd>0*[123]))?(?<coord>[XYIJ][XYIJ0-9\\+\\-]+)?(D(?<op>0*[123]))?$")},
        { PcbCamGerber274XParser::Cmd::APERTURE_SELECT,  QRegularExpression("^(?<cmd>G54|G55)?D(?<dcode>\\d+)$")},

        { PcbCamGerber274XParser::Cmd::FS, QRegularExpression("^(?<cmd>FS)(?<zero>[LTD])?(?<notation>[AI])([NGDM]\\d+)?X(?<wx>[0-7])(?<dx>[0-7])Y(?<wy>[0-7])(?<dy>[0-7])$") },
        { PcbCamGerber274XParser::Cmd::MO, QRegularExpression("^(?<cmd>MO)(?<mo>(MM|IN))$")},
        { PcbCamGerber274XParser::Cmd::LP, QRegularExpression("^(?<cmd>LP)(?<lp>(D|C))$")},
        { PcbCamGerber274XParser::Cmd::AD_CIRCLE, QRegularExpression("^(?<cmd>AD)D(?<dcode>\\d+)(?<shape>C)[,]?(?<modifiers>[^,%]*)$")},
        { PcbCamGerber274XParser::Cmd::AD_RECT, QRegularExpression("^(?<cmd>AD)D(?<dcode>\\d+)(?<shape>R)[,]?(?<modifiers>[^,%]*)$")},
        { PcbCamGerber274XParser::Cmd::AD_OBROUND, QRegularExpression("^(?<cmd>AD)D(?<dcode>\\d+)(?<shape>O)[,]?(?<modifiers>[^,%]*)$")},
        { PcbCamGerber274XParser::Cmd::AD_POLY, QRegularExpression("^(?<cmd>AD)D(?<dcode>\\d+)(?<shape>P)[,]?(?<modifiers>[^,%]*)$")},
        { PcbCamGerber274XParser::Cmd::AD_MACRO, QRegularExpression("^(?<cmd>AD)D(?<dcode>\\d+)(?<name>[a-zA-Z_$\\.][a-zA-Z_$\\.0-9+\\-]+)[,]?(?<modifiers>[^,%]*)$")},
        { PcbCamGerber274XParser::Cmd::AM, QRegularExpression("^(?<cmd>AM)(?<name>[a-zA-Z_$\\.][a-zA-Z_$\\.0-9\\+\\-]+)\\*(?<modifiers>.*)\\*$")},
        { PcbCamGerber274XParser::Cmd::IF, QRegularExpression("^(?<cmd>IF)(?<filename>.*)$")},
        { PcbCamGerber274XParser::Cmd::REGION_MODE, QRegularExpression("^(?<cmd>G3[67])$")},
        { PcbCamGerber274XParser::Cmd::QUAD_MODE, QRegularExpression("^(?<cmd>G7[45])$")},
        { PcbCamGerber274XParser::Cmd::SR, QRegularExpression("^(?<cmd>SR)(?<coord>[XYIJ0-9\\+\\-\\.]+)?")},

        // 以下为旧版格式命令

        { PcbCamGerber274XParser::Cmd::INN, QRegularExpression("^(?<cmd>IN)(?<name>.*)")},
        { PcbCamGerber274XParser::Cmd::IP, QRegularExpression("^(?<cmd>IP)(?<mode>(POS|NEG))")},
        { PcbCamGerber274XParser::Cmd::IR, QRegularExpression("^(?<cmd>IR)(?<angle>[\\+\\-]?\\d+>)")},
        { PcbCamGerber274XParser::Cmd::OF, QRegularExpression("^(?<cmd>IO)(A(?<a>[\\+\\-]?\\d+(\\.?\\d+)?))?(B(?<b>[\\+\\-]?\\d+(\\.?\\d+)?))?")},

        { PcbCamGerber274XParser::Cmd::AS, QRegularExpression("^(?<cmd>AS)(?<mode>(AXBY|AYBX))")},
        { PcbCamGerber274XParser::Cmd::MI, QRegularExpression("^(?<cmd>MI)(A(?<a>0|1))?(B(?<b>0|1))?")},
        { PcbCamGerber274XParser::Cmd::OF, QRegularExpression("^(?<cmd>OF)(A(?<a>[\\+\\-]?\\d+(\\.?\\d+)?))?(B(?<b>[\\+\\-]?\\d+(\\.?\\d+)?))?")},
        { PcbCamGerber274XParser::Cmd::SF, QRegularExpression("^(?<cmd>SF)(?<discarded>.*)")},
        { PcbCamGerber274XParser::Cmd::LN, QRegularExpression("^(?<cmd>LN)(?<name>.*)")},

        { PcbCamGerber274XParser::Cmd::DEPRECATED_UNIT, QRegularExpression("^(?<cmd>G7[01])")},
        { PcbCamGerber274XParser::Cmd::DEPRECATED_FORMAT, QRegularExpression("^(?<cmd>G9[01])")},
    };

    QRegularExpression coordRegx {"([XYIJ])([\\+\\-\\.0-9]+)"};

    RegxMatchResult getRegxCaptures(const QRegularExpression iRegx, const QString &iText) {
        RegxMatchResult result;
        auto match = iRegx.match(iText);
        result.hasMatch = match.hasMatch();
        if (match.hasMatch()) {
            QMap<QString, QString> caps;
            for (auto capname: iRegx.namedCaptureGroups()) {
                if (!capname.isEmpty()) {
                    caps.insert(capname, match.captured(capname));
                }
            }
            result.captures = caps;
        }
        return result;
    }

    double coord2inch(const QString &iStr) const {
        if (iStr.isEmpty()) return 0.0;


        double value {0.0};
        if (graphicsState.zerosOmit == PcbCam::ZerosOmit_Training) {
            QString str = iStr;
            int i = graphicsState.integerDigits;
            int d = graphicsState.decimalDigits;
            int c = i + d;
            QString sign;
            if (str.startsWith("-") || str.startsWith("+")) {
                sign = str.left(1);
                str.remove(0, 1);
            }
            if (str.length() < c) {
                str.append(QString("0").repeated(c - str.length()));
            }
            str.insert(i, ".");
            str.prepend(sign);
            value = str.toDouble();
        }
        else {
            QString str = iStr;
            int i = graphicsState.integerDigits;
            int d = graphicsState.decimalDigits;
            int c = i + d;
            QString sign;
            if (str.startsWith("-") || str.startsWith("+")) {
                sign = str.left(1);
                str.remove(0, 1);
            }
            if (str.length() < c) {
                str.prepend(QString("0").repeated(c - str.length()));
            }
            str.insert(str.length() - d, ".");
            str.prepend(sign);
            value = str.toDouble();
        }

        if (graphicsState.units == PcbCam::Units_Mm) {
            value /= 25.4;
        }
        return value;
    }

    double val2inch(const QString &iStr) {
        auto value = iStr.toDouble();
        if (graphicsState.units == PcbCam::Units_Mm) {
            value /= 25.4;
        }
        return value;
    }

    QPointF calcArcCenter(const QPointF &iPs, const QPointF &iPe, double iOffsetI, double iOffsetJ, bool iCw, QuadrantMode iQuadrantMode) {
        if (iQuadrantMode == MultiQuadrant) {
            return QPointF(iPs.x() + iOffsetI, iPs.y() + iOffsetJ);
        }
        else {
            QLineF line(iPs, iPe);
            QTransform trans;
            trans.translate(-iPs.x(), -iPs.y());
            line = trans.map(line);
            double lineangle = line.angle();
            line.setAngle(0);
            double radius = qSqrt(iOffsetI*iOffsetI + iOffsetJ*iOffsetJ);
            double l = qSqrt(radius*radius - (line.length()/2.0)*(line.length()/2.0));
            QPointF c1(line.length()/2.0, l);
            QPointF c2(line.length()/2.0, -l);

            QLineF linecs1(c1, line.p1());
            QLineF linece1(c1, line.p2());

            QPointF finalCenter;
            if (iCw) {
                if (linecs1.angleTo(linece1) <= 180) {
                    finalCenter = c1;
                }
                else {
                    finalCenter = c2;
                }
            }
            else {
                if (linecs1.angleTo(linece1) > 180) {
                    finalCenter = c1;
                }
                else {
                    finalCenter = c2;
                }
            }


            trans.reset();
            trans.rotate(-lineangle);
            finalCenter = trans.map(finalCenter);
            trans.reset();
            trans.translate(iPs.x(), iPs.y());
            finalCenter = trans.map(finalCenter);
            return finalCenter;
        }

    }

    QPainterPath rotatePath(const QPainterPath &iPath, double angle, const QPointF &iCenter = QPointF(0.0, 0.0)) {
        if (qFuzzyCompare(angle, 0.0)) return iPath;
        QTransform mat;
        mat.rotate(angle);
        return mat.map(iPath);
    }

    PcbCamGerber274XParser::ApertureInfo createCircleAperture(const QMap<QString, QString> &iParam) {
        PcbCamGerber274XParser::ApertureInfo res;
        res.dcode = iParam.value("dcode").toInt();
        auto sizes = iParam.value("modifiers").split("X", Qt::SkipEmptyParts, Qt::CaseInsensitive);
        if (sizes.count() == 1) {
            res.name = QString("r%1").arg(val2inch(sizes.value(0))*1000);
            res.isStandard = true;
        }
        else if (sizes.count() == 2) {
            res.name = QString("donut_r%1x%2").arg(val2inch(sizes.value(0))*1000).arg(val2inch(sizes.value(1))*1000);
            res.isStandard = true;
        }
        else if (sizes.count() == 3) {
            res.isStandard = false;

            auto outer_diameter = val2inch(sizes.value(0));
            auto inner_width = val2inch(sizes.value(1));
            auto inner_height = val2inch(sizes.value(2));

            PcbCamOdbFeatureInfo feat;
            feat.type = PcbCam::SurfaceFeature;
            QPainterPath path;
            path.addEllipse(QPointF(0.0, 0.0), outer_diameter, outer_diameter);
            path.addRect(-inner_width/2.0, -inner_height/2.0, inner_width, inner_height);
            feat.path = path;

            res.features << feat;
        }
        else {
            res.name = "r0";
            res.isStandard = true;
        }
        return res;
    }

    PcbCamGerber274XParser::ApertureInfo createRectAperture(const QMap<QString, QString> &iParam) {
        PcbCamGerber274XParser::ApertureInfo res;
        res.dcode = iParam.value("dcode").toInt();
        auto sizes = iParam.value("modifiers").split("X", Qt::SkipEmptyParts, Qt::CaseInsensitive);
        if (sizes.count() == 2) {
            res.name = QString("rect%1x%2").arg(val2inch(sizes.value(0))*1000).arg(val2inch(sizes.value(1))*1000);
            res.isStandard = true;
        }
        else if (sizes.count() == 3) {
            res.isStandard = false;

            auto width = val2inch(sizes.value(0));
            auto height = val2inch(sizes.value(1));
            auto hole_diam = val2inch(sizes.value(2));

            PcbCamOdbFeatureInfo feat;
            feat.type = PcbCam::SurfaceFeature;
            QPainterPath path;
            path.addRect(-width/2.0, -height/2.0, width, height);
            path.addEllipse(QPointF(0.0, 0.0), hole_diam, hole_diam);
            feat.path = path;
            res.features << feat;
        }
        else if (sizes.count() == 4) {
            res.isStandard = false;
            auto width = val2inch(sizes.value(0));
            auto height = val2inch(sizes.value(1));
            auto hole_w = val2inch(sizes.value(2));
            auto hole_h = val2inch(sizes.value(3));

            PcbCamOdbFeatureInfo feat;
            feat.type = PcbCam::SurfaceFeature;
            QPainterPath path;
            path.addRect(-width/2.0, -height/2.0, width, height);
            path.addRect(-hole_w/2.0, -hole_h/2.0, hole_w, hole_h);
            feat.path = path;
            res.features << feat;
        }
        else {
            res.name = "r0";
            res.isStandard = true;
        }
        return res;
    }

    PcbCamGerber274XParser::ApertureInfo createObroundAperture(const QMap<QString, QString> &iParam) {
        PcbCamGerber274XParser::ApertureInfo res;
        res.dcode = iParam.value("dcode").toInt();
        auto sizes = iParam.value("modifiers").split("X", Qt::SkipEmptyParts, Qt::CaseInsensitive);
        if (sizes.count() == 2) {
            res.name = QString("oval%1x%2").arg(val2inch(sizes.value(0))*1000).arg(val2inch(sizes.value(1))*1000);
            res.isStandard = true;
        }
        else if (sizes.count() >= 3) {
            res.isStandard = false;

            auto w = val2inch(sizes.value(0));
            auto h = val2inch(sizes.value(1));
            auto hole_w = val2inch(sizes.value(2));

            PcbCamOdbFeatureInfo feat;
            feat.type = PcbCam::SurfaceFeature;
            QPainterPath path;

            if (w > h) {
                qreal rad = h / 2.0;
                path.moveTo(-w/2.0 + rad, -h/2.0);
                path.lineTo(w/2.0 - rad, -h/2.0);
                path.arcTo(w/2.0 -rad*2.0, -h/2.0, h, h, 90, -180);
                path.lineTo(-w/2.0 + rad, h/2.0);
                path.arcTo(-w/2.0, -h/2.0, h, h, -90, -180);
                path.closeSubpath();
            }
            else {
                qreal rad = w/2.0;
                path.moveTo(w/2.0, -h/2.0 + rad);
                path.lineTo(w/2.0, h/2.0 - rad);
                path.arcTo(-w/2.0, h/2.0 - rad*2.0, w, w, 0, -180);
                path.lineTo(-w/2.0,-h/2.0 + rad);
                path.arcTo(-w/2.0, -h/2.0, w, w, 180, -180);
                path.closeSubpath();
            }

            if (sizes.count() == 3) {
                path.addEllipse(QPointF(0.0, 0.0), hole_w, hole_w);
            }
            else {
                auto hole_h = val2inch(sizes.value(3));
                path.addRect(-hole_w/2.0, -hole_h/2.0, hole_w, hole_h);
            }

            feat.path = path;
            res.features << feat;
        }
        else {
            res.name = "r0";
            res.isStandard = true;
        }
        return res;
    }

    PcbCamGerber274XParser::ApertureInfo createPolyAperture(const QMap<QString, QString> &iParam) {
        PcbCamGerber274XParser::ApertureInfo res;
        res.dcode = iParam.value("dcode").toInt();
        auto sizes = iParam.value("modifiers").split("X", Qt::SkipEmptyParts, Qt::CaseInsensitive);

        res.isStandard = false;
        auto diam = val2inch(sizes.value(0));
        auto edge = sizes.value(1).toInt();

        PcbCamOdbFeatureInfo feat;
        feat.type = PcbCam::SurfaceFeature;
        QPainterPath path;

        QPointF first_point(diam/2.0, 0.0);
        QTransform mat;
        double ra = 360.0/edge;
        path.moveTo(first_point);
        for (int n = 1; n <= edge; n++) {
            mat.reset();
            mat.rotate(ra*n);
            path.lineTo(mat.map(first_point));
        }
        path.closeSubpath();

        path = rotatePath(path, sizes.value(2, "0").toDouble());

        if (sizes.count() > 3) {
            auto hole_diam = val2inch(sizes.value(3));
            path.addEllipse(QPointF(0.0, 0.0), hole_diam, hole_diam);
        }

        feat.path = path;
        res.features << feat;
        return res;
    }

    PcbCamGerber274XParser::ApertureInfo createSpecialAperture(const QMap<QString, QString> &iParam) {
        PcbCamGerber274XParser::ApertureInfo res;
        res.dcode = iParam.value("dcode").toInt();
        res.isStandard = false;

        auto sizes = iParam.value("modifiers").split("X", Qt::SkipEmptyParts, Qt::CaseInsensitive);
        auto defstrs = this->apertureMacroDefines.value(iParam.value("name"));
        if (!sizes.isEmpty()) {
            for (int i = 0; i < sizes.count(); ++i) {
                scriptEngine.globalObject().setProperty(QString("$%1").arg(i+1), sizes.value(i).toDouble());
            }
        }

        for (auto line : defstrs) {
            if (line.startsWith('$')) {
                line.replace("x", "*", Qt::CaseInsensitive);
                scriptEngine.evaluate(line);
            }
            else {
                auto items = line.split(',', Qt::SkipEmptyParts);
                for (auto &it : items) {
                    if (it.contains('$')) {
                        it.replace("x", "*", Qt::CaseInsensitive);
                        it = QString("%1").arg(scriptEngine.evaluate("(" + it + ")").toNumber());
                    }
                }

                int prim_code = items.value(0).toInt();

                if (prim_code == 0) {
                    // comment
                }
                else if (prim_code == 1) {
                    auto diam = val2inch(items.value(2));
                    auto cx = val2inch(items.value(3));
                    auto cy = val2inch(items.value(4));

                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = items.value(1) == "1" ? PcbCam::Positive : PcbCam::Negative;
                    feat.type = PcbCam::SurfaceFeature;
                    QPainterPath path;
                    path.addEllipse(QPointF(cx, cy), diam/2.0, diam/2.0);
                    path = rotatePath(path, items.value(5, "0").toDouble());
                    feat.path = path;
                    res.features << feat;
                }
                else if (prim_code == 20 || prim_code == 2) {
                    auto diam = val2inch(items.value(2));
                    auto xs = val2inch(items.value(3));
                    auto ys = val2inch(items.value(4));
                    auto xe = val2inch(items.value(5));
                    auto ye = val2inch(items.value(6));

                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = items.value(1) == "1" ? PcbCam::Positive : PcbCam::Negative;
                    feat.type = PcbCam::SurfaceFeature;
                    QPainterPath path;
                    path.moveTo(xs, ys);
                    path.lineTo(xe, ye);
                    QPen pen;
                    pen.setCapStyle(Qt::FlatCap);
                    pen.setWidthF(diam);
                    QPainterPathStroker stroker(pen);
                    path = stroker.createStroke(path);
                    path = rotatePath(path, items.value(7, "0").toDouble());
                    feat.path = path;
                    res.features << feat;
                }
                else if (prim_code == 21) {
                    auto w = val2inch(items.value(2));
                    auto h = val2inch(items.value(3));
                    auto xc = val2inch(items.value(4));
                    auto yc = val2inch(items.value(5));

                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = items.value(1) == "1" ? PcbCam::Positive : PcbCam::Negative;
                    feat.type = PcbCam::SurfaceFeature;
                    QPainterPath path;
                    path.addRect(xc-w/2.0, yc-h/2.0, w, h);
                    path = rotatePath(path, items.value(6, "0").toDouble());
                    feat.path = path;

                    res.features << feat;
                }
                else if (prim_code == 22) {
                    auto w = val2inch(items.value(2));
                    auto h = val2inch(items.value(3));
                    auto x = val2inch(items.value(4));
                    auto y = val2inch(items.value(5));

                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = items.value(1) == "1" ? PcbCam::Positive : PcbCam::Negative;
                    feat.type = PcbCam::SurfaceFeature;
                    QPainterPath path;
                    path.addRect(x, y, w, h);
                    path = rotatePath(path, items.value(6, "0").toDouble());
                    feat.path = path;

                    res.features << feat;
                }
                else if (prim_code == 4) {
                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = items.value(1) == "1" ? PcbCam::Positive : PcbCam::Negative;
                    feat.type = PcbCam::SurfaceFeature;

                    int pcount = items.value(2).toInt();

                    QPainterPath path;
                    path.moveTo(val2inch(items.value(3)), val2inch(items.value(4)));
                    for (int n = 1; n <= pcount; n++) {
                        path.lineTo(val2inch(items.value(3 + 2*n)), val2inch(items.value(4 + 2*n)));
                    }
                    path = rotatePath(path, items.value(-1, "0").toDouble());
                    feat.path = path;

                    res.features << feat;
                }
                else if (prim_code == 5) {
                    int edge = items.value(2).toInt();
                    double xc = val2inch(items.value(3));
                    double yc = val2inch(items.value(4));
                    double diam = val2inch(items.value(5));

                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = items.value(1) == "1" ? PcbCam::Positive : PcbCam::Negative;
                    feat.type = PcbCam::SurfaceFeature;

                    QPointF first_point(diam/2.0, 0.0);
                    QPainterPath path;
                    QTransform mat;
                    double ra = 360.0/edge;
                    path.moveTo(first_point);
                    for (int n = 1; n <= edge; n++) {
                        mat.reset();
                        mat.rotate(ra*n);
                        path.lineTo(mat.map(first_point));
                    }
                    path.closeSubpath();
                    mat.reset();
                    mat.translate(xc, yc);
                    path = mat.map(path);

                    path = rotatePath(path, items.value(6, "0").toDouble());
                    feat.path = path;
                    res.features << feat;
                }
                else if (prim_code == 6) {

                    double xc = val2inch(items.value(1));
                    double yc = val2inch(items.value(2));
                    double diam = val2inch(items.value(3));
                    double ring_thk = val2inch(items.value(4));
                    double ring_gap = val2inch(items.value(5));
                    int ring_count = items.value(6).toInt();
                    double hair_thk = val2inch(items.value(7));
                    double hair_length = val2inch(items.value(8));

                    PcbCamOdbFeatureInfo feat;
                    feat.type = PcbCam::SurfaceFeature;
                    QPainterPath path;
                    for (int n = 0; n < ring_count; ++n) {
                        double outer_diam = diam - n * (ring_thk + ring_gap) * 2.0;
                        double inner_diam = outer_diam - 2.0 * ring_thk;
                        if (outer_diam > 0.0) {
                            path.addEllipse(-outer_diam/2.0, -outer_diam/2.0, outer_diam, outer_diam);
                        }
                        if (inner_diam > 0.0) {
                            path.addEllipse(-inner_diam/2.0, -inner_diam/2.0, inner_diam, inner_diam);
                        }
                    }

                    if (hair_thk > 0.0 && hair_length > 0.0) {
                        QPainterPath hairpath1;
                        hairpath1.addRect(-hair_length/2.0, -hair_thk/2.0, hair_length, hair_thk);
                        QPainterPath hairpath2;
                        hairpath2.addRect(-hair_thk/2.0, -hair_length/2.0, hair_thk, hair_length);
                        auto hairpath = hairpath1.united(hairpath2);
                        QTransform mat;
                        mat.scale(1000, 1000);
                        hairpath = mat.map(hairpath);
                        path = mat.map(path);
                        path = path.united(hairpath);
                        mat.reset();
                        mat.scale(0.001, 0.001);
                        path = mat.map(path);

                    }

                    QTransform mat;
                    mat.translate(xc, yc);
                    path = mat.map(path);

                    path = rotatePath(path, items.value(9, "0").toDouble());
                    feat.path = path;
                    res.features << feat;
                }
                else if (prim_code == 7) {
                    auto xc = val2inch(items.value(1));
                    auto yc = val2inch(items.value(2));
                    auto outer_diam = val2inch(items.value(3));
                    auto inner_diam = val2inch(items.value(4));
                    auto gap = val2inch(items.value(5));

                    PcbCamOdbFeatureInfo feat;
                    feat.type = PcbCam::SurfaceFeature;
                    qreal pie_angle = 360.0/4;
                    qreal half_inner_gap_angle = (180.0/M_PI) * (qAsin(gap/inner_diam));
                    qreal half_outer_gap_angle = (180.0/M_PI) * (qAsin(gap/outer_diam));
                    qreal inner_start_angle = 45.0 +  half_inner_gap_angle;
                    qreal inner_pie_angle = pie_angle - 2 * half_inner_gap_angle;
                    qreal outer_start_angle = 45.0 + half_outer_gap_angle;
                    qreal outer_pie_angle = pie_angle - 2 * half_outer_gap_angle;
                    QPainterPath path;
                    for (int i = 0; i < 4; ++i) {
                        path.arcMoveTo(-outer_diam/2.0, -outer_diam/2.0, outer_diam, outer_diam, outer_start_angle);
                        path.arcTo(-outer_diam/2.0, -outer_diam/2.0, outer_diam, outer_diam, outer_start_angle, outer_pie_angle);
                        path.arcTo(-inner_diam/2.0, -inner_diam/2.0, inner_diam, inner_diam, inner_start_angle + inner_pie_angle, 0);
                        path.arcTo(-inner_diam/2.0, -inner_diam/2.0, inner_diam, inner_diam, inner_start_angle + inner_pie_angle, -inner_pie_angle);
                        path.closeSubpath();
                        inner_start_angle += pie_angle;
                        outer_start_angle += pie_angle;
                    }

                    QTransform trans;
                    trans.scale(1, -1);
                    path = trans.map(path);

                    QTransform mat;
                    mat.translate(xc, yc);
                    path = mat.map(path);

                    path = rotatePath(path, items.value(9, "0").toDouble());
                    feat.path = path;
                    res.features << feat;
                }


            }
        }
        return res;
    }

    QList<PcbCamOdbFeatureInfo> srFeature(const PcbCamOdbFeatureInfo &iFeat) {
        QList<PcbCamOdbFeatureInfo> ret;
        QTransform mat;
        for (int i = 1; i <= graphicsState.srRepeatsX; ++i) {
            for (int j = 1; j <= graphicsState.srRepeatsY; ++j) {
                mat.reset();
                mat.translate((i-1)*graphicsState.srStepI, (j-1)*graphicsState.srStepJ);
                auto feat = iFeat;
                feat.ps = mat.map(feat.ps);
                feat.pe = mat.map(feat.pe);
                feat.pc = mat.map(feat.pc);
                feat.path = mat.map(feat.path);
                ret << feat;
            }
        }
        return ret;
    }


protected:
    PcbCamGerber274XParser * const q_ptr;
};


PcbCamGerber274XParser::PcbCamGerber274XParser()
    :d_ptr(new PcbCamGerber274XParserPrivate(this))
{

}

PcbCamGerber274XParser::~PcbCamGerber274XParser()
{

}

void PcbCamGerber274XParser::parse(QIODevice *iIODevice)
{
    Q_D(PcbCamGerber274XParser);
    d->apertures.clear();
    d->featureList.clear();
    d->errorList.clear();
    d->curRegionPath = QPainterPath();
    d->apertureMacroDefines.clear();

    QString line;
    char c;
    bool inblock = false;
    bool lineend = false;
    while (iIODevice->getChar(&c)) {
        if (c == '\n' || c == '\r') continue;
        if (inblock) {
            if (c == '%') {
                inblock = false;
                lineend = true;
            }
            else if (c == '*' && !line.startsWith("AM")) {
                lineend = true;
            }
            else {
                line.append(c);
            }
        }
        else {
            if (c == '%') {
                inblock = true;
            }
            else if (c == '*') {
                lineend = true;
            }
            else {
                line.append(c);
            }
        }

        if (!lineend) continue;

        auto trimline = line.trimmed().toUpper();
        trimline.replace(" ", "");
        line.clear();
        lineend = false;
        if (trimline.isEmpty()) continue;

        QMapIterator<PcbCamGerber274XParser::Cmd, QRegularExpression> iter(d->paramRegxs);
        while (iter.hasNext()) {
            iter.next();
            auto match = d->getRegxCaptures(iter.value(), trimline);
            if (!match.hasMatch) continue;
            auto cmd = iter.key();
            auto param = match.captures;
            if (cmd == Cmd::COORD) {
                auto gmode_str = param.value("cmd");
                if (!gmode_str.isEmpty()) {
                    auto gmode = gmode_str.toInt();
                    if (gmode == 1) {
                        d->graphicsState.interpolationMode = PcbCamGerber274XParserPrivate::LinearMode;
                    }
                    else if (gmode == 2) {
                        d->graphicsState.interpolationMode = PcbCamGerber274XParserPrivate::ClockwiseCircularMode;
                    }
                    else if (gmode == 3) {
                        d->graphicsState.interpolationMode = PcbCamGerber274XParserPrivate::CounterclockwiseCircularMode;
                    }
                }

                auto op_str = param.value("op");
                if (!op_str.isEmpty()) {
                    d->graphicsState.operationMode = op_str.toInt();
                }

                if (!param.value("coord").isEmpty() || !op_str.isEmpty()) {
                    QPointF pos = d->graphicsState.currentPoint;
                    QRegularExpressionMatchIterator matchiter = d->coordRegx.globalMatch(param.value("coord"));
                    QMap<QString, QString> coordparam;
                     while (matchiter.hasNext()) {
                         auto match = matchiter.next();
                         coordparam.insert(match.captured(1).toLower(), match.captured(2));
                     }
                    auto x_str = coordparam.value("x");
                    auto y_str = coordparam.value("y");
                    if (!x_str.isEmpty()) {
                        pos.setX(d->coord2inch(x_str));
                    }
                    if (!y_str.isEmpty()) {
                        pos.setY(d->coord2inch(y_str));
                    }
                    if (d->graphicsState.regionMode) {
                        if (d->graphicsState.operationMode == 1) { //D01
                            if (d->curRegionPath.isEmpty()) {
                                d->curRegionPath.moveTo(d->graphicsState.currentPoint);
                            }
                            if (d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::LinearMode) {
                                d->curRegionPath.lineTo(pos);
                            }
                            else if (d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::ClockwiseCircularMode
                                     || d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::CounterclockwiseCircularMode)
                            {
                                auto ps = d->graphicsState.currentPoint;
                                bool cw = d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::ClockwiseCircularMode;
                                auto pc = d->calcArcCenter(ps, pos, d->coord2inch(coordparam.value("i", "0")), d->coord2inch(coordparam.value("j", "0")), cw, d->graphicsState.quadrantMode);
                                QVariantMap calcData = PcbCamUtil::calcPathArc(ps, pos, pc, cw);
                                double startAngle = calcData.value("start_angle").toDouble();
                                double sweepLength = calcData.value("sweep_length").toDouble();
                                QRectF arcrect = calcData.value("rect").toRectF();
                                d->curRegionPath.arcTo(arcrect, startAngle, sweepLength);
                            }
                        }
                        else if (d->graphicsState.operationMode == 2) { //D02
                            if (!d->curRegionPath.isEmpty()) {
                                d->curRegionPath.closeSubpath();
                            }
                            d->curRegionPath.moveTo(pos);
                        }
                        else if (d->graphicsState.operationMode == 3) { //D03
                            // not allowed
                        }
                    }
                    else {
                        if (d->graphicsState.operationMode == 1) { //D01
                            if (d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::LinearMode) {
                                PcbCamOdbFeatureInfo feat;
                                feat.polarity = d->graphicsState.polarity;
                                feat.type = PcbCam::LineFeature;
                                feat.ps = d->graphicsState.currentPoint;
                                feat.pe = pos;
                                feat.decode = d->graphicsState.currentAperture;
                                if (d->graphicsState.srMode) {
                                    d->featureList << d->srFeature(feat);
                                }
                                else {
                                    d->featureList << feat;
                                }

                            }
                            else if (d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::ClockwiseCircularMode
                                     || d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::CounterclockwiseCircularMode)
                            {
                                auto ps = d->graphicsState.currentPoint;
                                bool cw = d->graphicsState.interpolationMode == PcbCamGerber274XParserPrivate::ClockwiseCircularMode;
                                auto pc = d->calcArcCenter(ps, pos, d->coord2inch(coordparam.value("i", "0")), d->coord2inch(coordparam.value("j", "0")), cw, d->graphicsState.quadrantMode);
                                PcbCamOdbFeatureInfo feat;
                                feat.polarity = d->graphicsState.polarity;
                                feat.type = PcbCam::ArcFeature;
                                feat.ps = ps;
                                feat.pe = pos;
                                feat.pc = pc;
                                feat.cw = cw;
                                feat.decode = d->graphicsState.currentAperture;
                                if (d->graphicsState.srMode) {
                                    d->featureList << d->srFeature(feat);
                                }
                                else {
                                    d->featureList << feat;
                                }
                            }
                        }
                        else if (d->graphicsState.operationMode == 2) { //D02
                           // only move point
                        }
                        else if (d->graphicsState.operationMode == 3) { //D03
                            PcbCamOdbFeatureInfo feat;
                            feat.polarity = d->graphicsState.polarity;
                            feat.type = PcbCam::PadFeature;
                            feat.ps = pos;
                            feat.decode = d->graphicsState.currentAperture;
                            if (d->graphicsState.srMode) {
                                d->featureList << d->srFeature(feat);
                            }
                            else {
                                d->featureList << feat;
                            }
                        }
                    }

                    d->graphicsState.currentPoint = pos;
                }
            }
            else if (cmd == Cmd::APERTURE_SELECT) {
                d->graphicsState.currentAperture =  param.value("dcode").toInt();
            }
            else if (cmd == Cmd::REGION_MODE) {
                d->graphicsState.regionMode = param.value("cmd") == "G36";
                if (!d->graphicsState.regionMode) {
                    PcbCamOdbFeatureInfo feat;
                    feat.polarity = d->graphicsState.polarity;
                    feat.type = PcbCam::SurfaceFeature;
                    feat.path = d->curRegionPath;
                    if (d->graphicsState.srMode) {
                        d->featureList << d->srFeature(feat);
                    }
                    else {
                        d->featureList << feat;
                    }
                    d->curRegionPath = QPainterPath();
                }
            }
            else if (cmd == Cmd::QUAD_MODE) {
                d->graphicsState.quadrantMode = param.value("cmd") == "G75" ? PcbCamGerber274XParserPrivate::MultiQuadrant : PcbCamGerber274XParserPrivate::SingleQuadrant;
            }
            else if (cmd == Cmd::DEPRECATED_UNIT) {
                d->graphicsState.units = param.value("cmd") == "G70" ? PcbCam::Units_Inch : PcbCam::Units_Mm;
            }
            else if (cmd == Cmd::DEPRECATED_FORMAT) {
                d->graphicsState.coordNotation = param.value("cmd") == "G91" ? PcbCam::CoordIncremental : PcbCam::CoordAbsoulte;
            }

            else if (cmd == Cmd::FS) {
                // Format Sepcification
                if (param.value("zero") == "L") {
                    d->graphicsState.zerosOmit = PcbCam::ZerosOmit_Leading;
                }
                else if (param.value("zero") == "T") {
                    d->graphicsState.zerosOmit = PcbCam::ZerosOmit_Training;
                }
                else {
                    d->graphicsState.zerosOmit = PcbCam::ZerosOmit_None;
                }

                d->graphicsState.coordNotation = param.value("notation") == "I" ? PcbCam::CoordIncremental : PcbCam::CoordAbsoulte;
                d->graphicsState.integerDigits = param.value("wx").toInt();
                d->graphicsState.decimalDigits = param.value("dx").toUInt();
            }
            else if (cmd == Cmd::MO) {
                // Unit Mode
                d->graphicsState.units = param.value("mo") == "IN" ? PcbCam::Units_Inch : PcbCam::Units_Mm;
            }
            else if (cmd == Cmd::LP) {
                // Layer Polarity
                d->graphicsState.polarity = param.value("lp") == "C" ? PcbCam::Negative : PcbCam::Positive;
            }
            else if (cmd == Cmd::AD_CIRCLE) {
                // Aperture Define
                auto ap = d->createCircleAperture(param);
                d->apertures.insert(ap.dcode, ap);
            }
            else if (cmd == Cmd::AD_RECT) {
                auto ap = d->createRectAperture(param);
                d->apertures.insert(ap.dcode, ap);
            }
            else if (cmd == Cmd::AD_OBROUND) {
                auto ap = d->createObroundAperture(param);
                d->apertures.insert(ap.dcode, ap);
            }
            else if (cmd == Cmd::AD_POLY) {
                auto ap = d->createPolyAperture(param);
                d->apertures.insert(ap.dcode, ap);
            }
            else if (cmd == Cmd::AD_MACRO) {
                auto ap = d->createSpecialAperture(param);
                d->apertures.insert(ap.dcode, ap);
            }
            else if (cmd == Cmd::AM) {
                // Aperture Macro
                d->apertureMacroDefines.insert(param.value("name"), param.value("modifiers").split('*', Qt::SkipEmptyParts));
            }
            else if (cmd == Cmd::SR) {
                auto coordstr = param.value("coord");
                d->graphicsState.srMode = !coordstr.isEmpty();
                if (d->graphicsState.srMode) {
                    QRegularExpressionMatchIterator matchiter = d->coordRegx.globalMatch(param.value("coord"));
                    QMap<QString, QString> coordparam;
                     while (matchiter.hasNext()) {
                         auto match = matchiter.next();
                         coordparam.insert(match.captured(1).toLower(), match.captured(2));
                     }
                     d->graphicsState.srRepeatsX = coordparam.value("x", "0").toInt();
                     d->graphicsState.srRepeatsY = coordparam.value("y", "0").toInt();
                     d->graphicsState.srStepI = coordparam.value("i", "0.0").toDouble();
                     d->graphicsState.srStepJ = coordparam.value("j", "0.0").toDouble();
                }
                else {
                    d->graphicsState.srRepeatsX = 1;
                    d->graphicsState.srRepeatsY = 1;
                    d->graphicsState.srStepI = 0.0;
                    d->graphicsState.srStepJ = 0.0;
                }
            }
            else if (cmd == Cmd::IF) {
                // Include File
                // TODO
            }
            else if (cmd == Cmd::INN) {
                // Image Name, just a comment
            }
            else if (cmd == Cmd::LN) {
                // Layer Name, just a comment
            }
            else if (cmd == Cmd::IP) {
                // Image Polarity, IPPOS* or IPNEG*
            }
            else if (cmd == Cmd::AS) {
                // Axis assignment, ASAXBY* or ASAYBX*
                // TODO
            }
            else if (cmd == Cmd::IR) {
                // Image Rotation, IR0 IR90 IR180 IR270
                // TODO
            }
            else if (cmd == Cmd::MI) {
                // Mirror Image %MIA0B0%
                // TODO
            }
            else if (cmd == Cmd::OF) {
                // Image Offset
                // TODO
            }
            else if (cmd == Cmd::SF) {
                // Scale Factor
                // TODO
            }
            break;
        }
    }

}

QList<PcbCamOdbFeatureInfo> PcbCamGerber274XParser::features() const
{
    Q_D(const PcbCamGerber274XParser);
    return  d->featureList;
}

QList<PcbCamGerber274XParser::ApertureInfo> PcbCamGerber274XParser::apertures() const
{
    Q_D(const PcbCamGerber274XParser);
    return d->apertures.values();
}
