#include "drillparser.h"
#include <QFile>
#include <QSet>
#include <QTextStream>
#include <QDebug>
#include <QtMath>
#include <functional>
#include "../core/util.h"


class PcbCamDrillToolData: public QSharedData
{
public:
    PcbCamDrillToolData() {}
    PcbCamDrillToolData(const PcbCamDrillToolData &other) : QSharedData(other) {}
    ~PcbCamDrillToolData() {}

    int T {0};
    double F {0.0};
    double C {0.0};
    double S {0.0};
    double R {0.0};
    double H {0.0};
    double Z {0.0};
};

class PcbCamDrillTool
{
public:
    PcbCamDrillTool() : d(new PcbCamDrillToolData) {}
    PcbCamDrillTool(const PcbCamDrillTool &rhs): d(rhs.d) {}
    PcbCamDrillTool &operator=(const PcbCamDrillTool &rhs)
    {
        if (this != &rhs) d.operator=(rhs.d);
        return *this;
    }
    ~PcbCamDrillTool() {}

    void setT(int val) {d->T = val;}
    int T() const {return d->T;}

    void setF(double val) {d->F = val;}
    double F() const {return d->F;}

    void setC(double val) {d->C = val;}
    double C() const {return d->C;}

    void setS(double val) {d->S = val;}
    double S() const {return d->S;}

    void setR(double val) {d->R = val;}
    double R() const {return d->R;}

    void setH(double val) {d->H = val;}
    double H() const {return d->H;}

    void setZ(double val) {d->Z = val;}
    double Z() const {return d->Z;}

    QString symbolName() const {
        return QString("r%1").arg(PcbCamUtil::formatNumber(d->C * 1000, 6));
    }

private:
    QSharedDataPointer<PcbCamDrillToolData> d;
};


class PcbCamDrillParserPrivate
{
    Q_DECLARE_PUBLIC(PcbCamDrillParser)
public:

    enum class Section {
        None,
        Head,
        Body,
    };

    enum class Cmd {
        UNKNOW = -1,
        EMPTY = 0,
        INCH_METRIC = 1,     // INCH,LZ 或 METRIC,000.000 :单位及省零模式(TZ=Traling, LZ=Leading)
        FMAT = 2,       // FMAT,2 :格式标准Excellon2格式
        VER = 3,        // VER,1  :Selection of X and Y Axis Version
        TCODE = 4,        // T01C0.2 //刀具信息
        POSXY = 5,       // X0001Y0001 : 坐标
        K = 6,          // (K) 禁用顺序优化
        F = 7,          // F07 Worktable feed rate of 70 IPM or 70 mm/s for routing

        G70_74 = 1170,     // Posalux格式时排版变换指令; G70,G71,G72,G73,G74
        M02_EMPTY = 2003,   // 每次排版后有一行空白的M02

        G00 = 1000, //  Route Mode (X#Y#) перемещение.
        G01 = 1001, //  Linear (Straight Line) Mode
        G02 = 1002, //  Circular CW Mode
        G03 = 1003, //  Circular CCW Mode
        G04 = 1004, //  X# Variable Dwell
        G05 = 1005, //  Drill Mode (G81)
        G07 = 1007, //  Override current tool feed or speed
        G32 = 1032, // Routed Circle Canned Cycle (X#Y#A#)
        G33 = 1033, // CCW G34,#(,#)	Select Vision Tool
        G34 = 1034, // CW G33X#Y#A#	Routed Circle Canned Cycle

        G35 = 1035, // Single Point Vision Offset (Relative to Work Zero)  (X#Y#)
        G36 = 1036, // Multipoint Vision Translation (Relative to Work Zero)  (X#Y#)
        G37 = 1037, // Cancel Vision Translation or Offset (From G35 or G36)
        G38 = 1038, // Vision Corrected Single Hole Drilling (Relative to Work Zero)  (X#Y#)
        G39 = 1039, // Vision System Autocalibration  (X#Y#)
        G40 = 1040, // Cutter Compensation Off
        G41 = 1041, // Cutter Compensation Left
        G42 = 1042, // Cutter Compensation Right
        G45 = 1045, // Single Point Vision Offset (Relative to G35 or G36)  (X#Y#)
        G46 = 1046, // Multipoint Vision Translation (Relative to G35 or G36)  (X#Y#)
        G47 = 1047, // Cancel Vision Translation or Offset (From G45 or G46)
        G48 = 1048, // Vision Corrected Single Hole Drilling (Relative to G35 or G36)  (X#Y#)
        G82 = 1082, // Dual In Line Package  (G81)
        G83 = 1083, // Eight Pin L Pack
        G84 = 1084, // Circle
        G85 = 1085, // Slot
        G87 = 1087, // Routed Step Slot Canned Cycle
        G90 = 1090, // Absolute Mode
        G91 = 1091, // Incremental Input Mode
        G93 = 1093, // Zero Set (X#Y#)

        M00 = 2000, //  End of Program - No Rewind (X#Y#)
        M01 = 2001, //  End of Pattern
        M02 = 2002, //  Repeat Pattern Offset ((M02)X#Y#)
        //    M02 = 2, //  Swap Axes ((M02)XYM70)
        //    M02 = 2, //  Mirror Image X Axis ((M02)XYM80)
        //    M02 = 2, //  Mirror Image Y Axis ((M02)XYM90)
        M06 = 2006, //  Optional Stop (X#Y#)
        M08 = 2008, //  End of Step and Repeat
        M09 = 2009, //  Stop for Inspection (X#Y#)
        M12 = 2012, // ATS Laser Cutting转换程序里有，暂不知道用途
        M14 = 2014, // Z Axis Route Position With Depth Controlled Contouring
        M15 = 2015, // Z Axis Route Position
        M16 = 2016, // Retract With Clamping - вытянуть с фиксацией.
        M17 = 2017, // Retract Without Clamping
        M18 = 2018, // Command tool tip check
        M19 = 2019, // Binary Map
        M25 = 2025, // Beginning of Pattern
        M29 = 2029, // Poslaux中有M29,暂时还不知道有何用途
        M30 = 2030, // End of Program Rewind (X#Y#)
        M45 = 2045, // Long Operator message on multiple\ part program lines (,long message\)
        M47 = 2047, // Operator Message (,text)
        M48 = 2048, // The beginning of a heade,same as %
        M50 = 2050, // Vision Step and Repeat Pattern Start (,#)
        M51 = 2051, // Vision Step and Repeat Rewind (,#)
        M52 = 2052, // Vision Step and Repeat Offset Counter Control (#)
        M60 = 2060, // Reference Scaling enable
        M61 = 2061, // Reference Scaling disable
        M62 = 2062, // Turn on peck drilling
        M63 = 2063, // Turn off peck drilling
        M71 = 2071, // Metric Measuring Mode
        M72 = 2072, // Inch Measuring Mode
        M83 = 2083, // M83,ZOT0.0
        M95 = 2095, // % end header
        M97 = 2097, // Canned Text (,text)
        M98 = 2098, // Canned Text (,text)
        M99 = 2099, // User Defined Stored Pattern (,subprogram)
        M100 = 2100, //Marco
        REMARK = 9000, // ATS laser cutting (remark_1,"X50.51Y277.975")
    };

    struct RowData {
        Section section {PcbCamDrillParserPrivate::Section::None}; //位于哪个区段
        QString rawString; //原始字符
        int group {0}; //分组
        int lineNumber {0}; //行号
        PcbCamDrillTool tool; //当前刀号
        bool isRepeatBlock {false}; //是否在Repeat区块内
        Cmd cmd {PcbCamDrillParserPrivate::Cmd::UNKNOW};
        int tcode {-1};
        PcbCam::CoordNotation coordMode {PcbCam::CoordAbsoulte};

        QPointF origin {0.0, 0.0};
        QPointF lastPos {0.0, 0.0};
        QPointF pos {0.0, 0.0};  //hole中心，slot起点，G84中心点
        QPointF pos2 {0.0, 0.0}; //slot终点
        double  size {0.0}; //G84 size, G02 G03圆弧半径
        QTransform transform; //Repeat时的座标变换
        QList<RowData> repeatRows;

        RowData transformed(const QTransform &iTransform) const {
            RowData res = *this;
            res.pos = iTransform.map(this->pos);
            res.pos2 = iTransform.map(this->pos2);
            res.lastPos = iTransform.map(this->lastPos);
            return res;
        }

    };

    PcbCamDrillParserPrivate(PcbCamDrillParser *qptr) : q_ptr(qptr){}
    ~PcbCamDrillParserPrivate() {}

    PcbCamDrillTool parseToolString(const QString &ilinestr)
    {
        PcbCamDrillTool tool;
        //T01C0.41S120.F80R600Z-.13H2000
        QRegularExpressionMatch match = QRegularExpression("^T([0-9]{1,2})").match(ilinestr);
        if (match.hasMatch()) tool.setT(match.captured(1).toInt());

        match = QRegularExpression("C([+-]?[0-9.]+)").match(ilinestr);
        if (match.hasMatch()) tool.setC(unitsConvert(match.captured(1).toDouble(), statement.toolUnits, PcbCam::Units_Inch));

        match = QRegularExpression("S([+-]?[0-9\\.]+)").match(ilinestr);
        if (match.hasMatch()) tool.setS(match.captured(1).toDouble());

        match = QRegularExpression("F([+-]?[0-9\\.]+)").match(ilinestr);
        if (match.hasMatch()) tool.setF(match.captured(1).toDouble());

        match = QRegularExpression("R([+-]?[0-9\\.]+)").match(ilinestr);
        if (match.hasMatch()) tool.setR(match.captured(1).toDouble());

        match = QRegularExpression("Z([+-]?[0-9\\.]+)").match(ilinestr);
        if (match.hasMatch()) tool.setZ(match.captured(1).toDouble());

        match = QRegularExpression("H([+-]?[0-9\\.]+)").match(ilinestr);
        if (match.hasMatch()) tool.setH(match.captured(1).toDouble());

        return tool;
    }

    double drillStr2Double(const QString &iStr, const PcbCamDrillParser::DrillStatement &iFormat, PcbCam::UnitsType iDestUnits)
    {

        if (iStr.isEmpty()) return 0.0;

        double value {0.0};
        if (iFormat.zerosOmit == PcbCam::ZerosOmit_Decimal) {
            value = iStr.toDouble();
        }
        else if (iFormat.zerosOmit == PcbCam::ZerosOmit_Training) {
            QString str = iStr;
            int i = iFormat.integerDigits;
            int d = iFormat.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 = iFormat.integerDigits;
            int d = iFormat.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 (iFormat.units == PcbCam::Units_Mm && iDestUnits == PcbCam::Units_Inch) {
            value /= 25.4;
        }
        else if (iFormat.units == PcbCam::Units_Inch && iDestUnits == PcbCam::Units_Mm) {
            value *= 25.4;
        }

        return value;
    }

    QPointF drillCatchXy(const QString &iStr, const PcbCamDrillParser::DrillStatement &iFormat, PcbCam::UnitsType iDestUnits, const QPointF &iDefault)
    {
        QPointF res = iDefault;
        QRegularExpression regx("X([+-]?[0-9.]*)");
        QRegularExpression regy("Y([+-]?[0-9.]*)");
        auto matchx = regx.match(iStr);
        if (matchx.hasMatch()) {
            res.setX(drillStr2Double(matchx.captured(1), iFormat, iDestUnits));
        }
        auto matchy = regy.match(iStr);
        if (matchy.hasMatch()) {
            res.setY(drillStr2Double(matchy.captured(1), iFormat, iDestUnits));
        }
        return res;
    }

    double unitsConvert(double iValue, PcbCam::UnitsType iSrcUnits, PcbCam::UnitsType iDestUnits)
    {
        if (iSrcUnits == PcbCam::Units_Mm && iDestUnits == PcbCam::Units_Inch) {
            return iValue / 25.4;
        }
        else if (iSrcUnits == PcbCam::Units_Inch && iDestUnits == PcbCam::Units_Mm) {
            return iValue * 25.4;
        }
        return iValue;
    }

    QPointF calcCircleCenter(QPointF p1, QPointF p2, double dRadius, double clockwise) const
    {
        double x1 = p1.x();
        double y1 = p1.y();
        double x2 = p2.x();
        double y2 = p2.y();
        double mid_x = (x1 + x2) / 2.0;
        double mid_y = (y1 + y2) / 2.0;

        double mo1 = qSqrt(qAbs(dRadius * dRadius - ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 4));
        double mo2 = qSqrt(qAbs(dRadius * dRadius - ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 4));
        double lx = (x2 - x1) / qSqrt(qAbs((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)));
        double ly = (y2 - y1) / qSqrt(qAbs((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)));

        double center2_x = mid_x + mo1 * ly;
        double center2_y = mid_y + mo1 * (-lx);

        double center1_x = mid_x + mo2 * (-ly);
        double center1_y = mid_y + mo2 * lx;

        QPointF center1(center1_x, center1_y);
        QPointF center2(center2_x, center2_y);

        if (clockwise) {
            return center1;
        }
        else {
            return center2;
        }
    }

    PcbCamOdbFeatureInfo createFeature(const RowData &iRowData) {
        PcbCamOdbFeatureInfo feat;
        if (iRowData.cmd == PcbCamDrillParserPrivate::Cmd::G85) { //Slot
            feat.type = PcbCam::LineFeature;
            feat.ps = iRowData.pos;
            feat.pe = iRowData.pos2;
            feat.symbol = iRowData.tool.symbolName();
        }
        else if (iRowData.cmd == PcbCamDrillParserPrivate::Cmd::G84) { //扩孔
            feat.type = PcbCam::PadFeature;
            feat.ps = iRowData.pos;
            feat.symbol = QString("r%1").arg(PcbCamUtil::formatNumber(iRowData.size*1000, 6));
        }
        else if (iRowData.cmd == PcbCamDrillParserPrivate::Cmd::POSXY) {
            feat.type = PcbCam::PadFeature;
            feat.ps = iRowData.pos;
            feat.symbol = iRowData.tool.symbolName();
        }
        else if (iRowData.cmd == PcbCamDrillParserPrivate::Cmd::G01) {
            feat.type = PcbCam::LineFeature;
            feat.ps = iRowData.pos;
            feat.pe = iRowData.pos2;
            feat.symbol = iRowData.tool.symbolName();
        }
        else if (iRowData.cmd == PcbCamDrillParserPrivate::Cmd::G02) {
            feat.type = PcbCam::ArcFeature;
            feat.cw = true;
            feat.ps = iRowData.pos;
            feat.pe = iRowData.pos2;
            feat.pc = calcCircleCenter(feat.ps, feat.pe, iRowData.size, feat.cw);
            feat.symbol = iRowData.tool.symbolName();
        }
        else if (iRowData.cmd == PcbCamDrillParserPrivate::Cmd::G03) {
            feat.type = PcbCam::ArcFeature;
            feat.cw = false;
            feat.ps = iRowData.pos;
            feat.pe = iRowData.pos2;
            feat.pc = calcCircleCenter(feat.ps, feat.pe, iRowData.size, feat.cw);
            feat.symbol = iRowData.tool.symbolName();
        }
        return feat;
    }


    using WalkCallback = std::function<bool(const RowData &iRowData)>;
    bool walkDrillRows(const QStringList &iDrillRows, const PcbCamDrillParser::DrillStatement &iStatement, WalkCallback iCallback) {
        auto statement = iStatement;
        auto globalUnits = PcbCam::Units_Inch;

        QTransform g74Trans = QTransform( 0, 1, 1,  0, 0, 0);
        QTransform g71Trans = QTransform(-1,  0,  0,  1, 0, 0);
        QTransform g72Trans = QTransform( 1,  0,  0, -1, 0, 0);

        QMap<int, PcbCamDrillTool> toolMap;
        QSet<int> transSet;
        QPointF lastM02Pos {0.0, 0.0};
        QList<PcbCamDrillParserPrivate::RowData> repeat_rows;
        PcbCamDrillParserPrivate::RowData rowData;

        QString linestr;
        for (const auto l : iDrillRows) {
            linestr = l.trimmed();
            rowData.rawString = linestr;
            rowData.lineNumber++;
            rowData.cmd = PcbCamDrillParserPrivate::Cmd::UNKNOW;
            if (linestr.isEmpty()) {
                rowData.cmd = PcbCamDrillParserPrivate::Cmd::EMPTY;
            }
            else if (rowData.section == PcbCamDrillParserPrivate::Section::None) {
                if (linestr.startsWith("M48")) {
                    // M48 :程序头部开始
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M48;
                    rowData.section = PcbCamDrillParserPrivate::Section::Head;
                }
                else if (linestr == "%") {
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::EMPTY;
                }
            }
            else if (rowData.section == PcbCamDrillParserPrivate::Section::Head) {
                if (linestr == "%" || linestr == "M95") {
                    // %                :-头部结束,Body开始
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M95;
                    rowData.section = PcbCamDrillParserPrivate::Section::Body;
                    rowData.tcode = -1;
                }
                else if (linestr.startsWith("T")) {
                    // T01C0.41S120.F80R600Z-.13H2000 :刀具信息
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::TCODE;
                    auto tool =  this->parseToolString(linestr);
                    toolMap.insert(tool.T(), tool);
                    rowData.tcode = tool.T();
                    rowData.tool = tool;
                }
                else if (linestr.startsWith("METRIC")) {
                    // METRIC,TZ        :公制单位及省零模式(TZ=Traling, LZ=Leading)
                    // METRIC,000.000
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::INCH_METRIC;
                }
                else if (linestr.startsWith("INCH")) {
                    // INCH,LZ          :英制单位及省零模式(TZ=Traling, LZ=Leading)
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::INCH_METRIC;
                }
                else if (linestr.startsWith("FMAT")) {
                    //FMAT,2            :格式标准Excellon2格式
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::FMAT;
                }
                else if (linestr.startsWith("VER")) {
                    //VER,1             :Selection of X and Y Axis Version
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::VER;
                }
                else if (linestr.startsWith("M100")) {
                    //M100  :宏命令
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M100;
                }
            }
            else if (rowData.section == PcbCamDrillParserPrivate::Section::Body) {
                if (linestr.startsWith("X") || linestr.startsWith("Y")) {
                    if (linestr.contains("G85")) {
                        //X03Y05G85X03Y047  :slot
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::G85;
                        QPointF p1, p2;
                        auto tmplist = linestr.split("G85", Qt::SkipEmptyParts);
                        if (rowData.coordMode == PcbCam::CoordIncremental) {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, QPointF(0.0, 0.0));
                            p1 += rowData.lastPos;
                            p2 = this->drillCatchXy(tmplist.value(1), statement, globalUnits, QPointF(0.0, 0.0));
                            p2 += p1;
                        }
                        else {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, QPointF(0.0, 0.0));
                            p2 = this->drillCatchXy(tmplist.value(1), statement, globalUnits, QPointF(0.0, 0.0));
                        }
                        rowData.pos = p1;
                        rowData.pos2 = p2;
                    }
                    else if (linestr.contains("G84")) {
                        //X009Y046G84X005   :扩孔
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::G84;
                        auto tmplist = linestr.split("G84", Qt::SkipEmptyParts);
                        QPointF p1;
                        if (rowData.coordMode == PcbCam::CoordIncremental) {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, QPointF(0.0, 0.0));
                            p1 += rowData.lastPos;
                        }
                        else {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, QPointF(0.0, 0.0));
                        }
                        rowData.pos = p1;
                        rowData.size = this->drillStr2Double(tmplist.value(1).mid(1), statement, globalUnits);
                    }
                    else {
                        //X13.560Y602.390   :圆孔
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::POSXY;
                        QPointF p1;
                        if (rowData.coordMode == PcbCam::CoordIncremental) {
                            p1 = this->drillCatchXy(linestr, statement, globalUnits, QPointF(0.0, 0.0));
                            p1 += rowData.lastPos;
                        }
                        else {
                            p1 = this->drillCatchXy(linestr, statement, globalUnits, rowData.lastPos);
                        }
                        rowData.pos = p1;
                    }
                }
                else if (linestr.startsWith("T")) {
                    //T02   :刀号选择
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::TCODE;
                    QRegularExpressionMatch match = QRegularExpression("^T([0-9]{2})").match(linestr);
                    rowData.tcode = match.captured(1).toInt();
                    rowData.tool = toolMap.value(rowData.tcode);
                }
                else if (linestr.startsWith("G00")) {
                    //G00X#Y#   :快速移动至此点
                    //G00 turns the routing mode on and the drilling mode off. This command is required before any routing can be performed. An X and Y
                    //coordinate must be provided to move the worktable to a starting point for routing. When the CNC-7 encounters this command, the
                    //worktable moves to the X,Y coordinate. The spindles will not plunge into the work until a plunge command (e.g. M15) is given.
                    //Compensation is automatically turned off during the move and can be turned on again after the move. The G00 command remains in
                    //effect until another G00 command, or a G01, G02, G03, or G05 command is encountered. Do not use this command when the Z-axis is in
                    //the rout position. The tool can be damaged by a high speed move.
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G00;
                    rowData.pos = this->drillCatchXy(linestr.mid(2), statement, globalUnits, rowData.lastPos);
                }
                else if (linestr.startsWith("G01")) {
                    //G01(X#)(Y#)  :直线插补
                    //G01 turns on linear interpolation mode. This means that the machine will begin routing in a straight line. If you supply an X and/or Y
                    //coordinate with the command, the machine will rout a straight line from the current position to the coordinate position. If you do not supply
                    //coordinates, the CNC-7 will look for coordinates in a succeeding block, and rout to the first coordinate found. Unless a different rate has
                    //been set, linear movement will occur at a default rate of 100 IPM (42.3 mm/s) at 100% feed rate. This can be overridden with the F#
                    //command, described in the Tool Commands section of this chapter, or with the FEED RATE buttons on the Touch Screen.
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G01;
                    rowData.pos = rowData.lastPos;
                    rowData.pos2 = this->drillCatchXy(linestr.mid(2), statement, globalUnits, rowData.lastPos);

                }
                else if (linestr.startsWith("G02")) {
                    //G02(X#)(Y#)(A#) G02(X#)(Y#)(I#J#) :顺圆差补
                    //G02 turns on circular interpolation mode and sets clockwise direction of travel. If you supply an X and/or Y coordinate with the command,
                    //the worktable will move to that coordinate position. The move will be made along an arc in a clockwise direction at a controlled velocity. If
                    //you do not supply coordinates, the CNC-7 will look for coordinates in a succeeding block, and rout to the first coordinate found. The arc
                    //must be equal to or less than 180 degrees. The arc radius or the arc center offset is specified either by the A# command or the I#J#
                    //command. These commands are indicated as optional. If they are not included in the G02 command, they must be included in a previous
                    //block of the program, either alone or with another routing command. The A# and I#J# commands are discussed in the next sections.
                    //Unless a different rate has been set, movement will occur at a default rate of 100 IPM (42.3 mm/s) at 100% feed rate. This can be
                    //overridden with the F# command, described in the Tool Commands section of this chapter, or with the FEED RATE switches on the Touch
                    //Screen.
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G02;
                    if (linestr.contains("A")) {
                        auto tmplist = linestr.mid(2).split("A", Qt::SkipEmptyParts);
                        QPointF p1;
                        if (rowData.coordMode == PcbCam::CoordIncremental) {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, QPointF(0.0, 0.0));
                            p1 += rowData.lastPos;
                        }
                        else {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, rowData.lastPos);
                        }
                        rowData.pos = rowData.lastPos;
                        rowData.pos2 = p1;
                        rowData.size = this->drillStr2Double(tmplist.value(1), statement, globalUnits);
                    }
                    else {
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::UNKNOW;
                    }
                }
                else if (linestr.startsWith("G03")) {
                    //G03(X#)(Y#)(A#) G03(X#)(Y#)(I#J#) :逆圆差补
                    //G03 turns on circular interpolation mode and sets counterclockwise direction of travel. If you supply an X and/or Y coordinate with the
                    //command, the worktable will move to that coordinate position. The move will be made along an arc in a counterclockwise direction at a
                    //controlled velocity. If you do not supply coordinates, the CNC-7 will look for coordinates in a succeeding block, and rout to the first
                    //coordinate found. The arc must be equal to or less than 180 degrees. The arc radius or the arc center offset is specified either by the A#
                    //command or the I#J# command. If they are not included in the G03 command, they must be included in a previous block of the program,
                    //either alone or with another routing command. The A# and I#J# commands are discussed in the next sections. Unless a different rate has
                    //been set, movement will occur at a default rate of 100 IPM (42.3 mm/s) at 100% feed rate. This can be overridden with the F# command,
                    //described in the Tool Commands section of this chapter, or with the FEED RATE switches on the Touch Screen.
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G03;
                    if (linestr.contains("A")) {
                        auto tmplist = linestr.mid(2).split("A", Qt::SkipEmptyParts);
                        QPointF p1;
                        if (rowData.coordMode == PcbCam::CoordIncremental) {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, QPointF(0.0, 0.0));
                            p1 += rowData.lastPos;
                        }
                        else {
                            p1 = this->drillCatchXy(tmplist.value(0), statement, globalUnits, rowData.lastPos);
                        }
                        rowData.pos = rowData.lastPos;
                        rowData.pos2 = p1;
                        rowData.size = this->drillStr2Double(tmplist.value(1), statement, globalUnits);
                    }
                    else {
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::UNKNOW;
                    }
                }
                else if (linestr.startsWith("G04")) {
                    //G04X#     :停止一段时间
                    //G04 Halts the machine for the time you specify in place of #. This command is used, for example, to cool a router bit after a long cut. The
                    //dwell time is interpreted as 1 millisecond per increment in the current coordinate measurement mode (inch or metric). The dwell time may
                    //be programmed from 1 to 10 seconds of 1 msec. If you program beyond these limits, or if you do not supply a value, the dwell time
                    //defaults to 10 seconds.
                    //Example of usage:   G04X001 = 1 second
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G04;
                }
                else if (linestr.startsWith("G40")) {
                    //G40   :关闭补偿
                    //G40 turns cutter compensation off. This command is programmed in a block by itself. Cutter compensation is discussed with the G41 and
                    //G42 commands below, and with the Cutter Compensation Page.
                    //THIS COMMAND MUST NOT BE USED WHILE PLUNGED!
                    //Example of usage:
                    //G40
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G40;
                }
                else if (linestr.startsWith("G41")) {
                    //G41   :左补偿
                    //G41 turns cutter compensation on for the tool being used to rout. The compensation path is left of the part relative to the direction that the
                    //tool is moving.
                    //THIS COMMAND MUST NOT BE USED WHILE PLUNGED! A Compensation Index must be specified on the Cutter Information Page for
                    //the tool being used. Without an Index there will be no compensation applied to the tool after compensation is turned on. A value must be
                    //assigned to the index number. Compensation will continue for all routing moves until a G00, G40, or G42 command is encountered, or the
                    //part program ends. The command must be programmed in a block by itself.
                    //Example of usage: G41
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G41;
                }
                else if (linestr.startsWith("G42")) {
                    //G42   :右补偿
                    //G42 turns cutter compensation on for the tool being used to rout. The compensation path is right of the part relative to the direction that
                    //the tool is moving.
                    //THIS COMMAND MUST NOT BE USED WHILE PLUNGED! A Compensation Index must be specified on the Cutter Information Page for
                    //the tool being used. Without an index there will be no compensation applied to the tool after compensation is turned on. A value must be
                    //assigned to the index number. Compensation will continue for all routing moves until a G00, G40, or G41 command is encountered, or the
                    //part program ends. This command must be programmed in a block by itself.
                    //Example of usage: G42
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G42;
                }
                else if (linestr.startsWith("G82")) {
                    //TODO 暂不支持G82
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::G82;
                }
                else if (linestr.startsWith("G83")) {
                    //TODO 暂不支持G83
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::G83;
                }
                else if (linestr == "G90") {
                    //G90   :绝对座标
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G90;
                    rowData.coordMode = PcbCam::CoordAbsoulte;
                }
                else if (linestr == "G91") {
                    //G91   :相对对座标
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G91;
                    rowData.coordMode = PcbCam::CoordIncremental;
                }
                else if (linestr.startsWith("G93")) {
                    //G93X0325Y015  :零点设置
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::G93;
                    rowData.origin = this->drillCatchXy(linestr, statement, globalUnits, QPointF(0.0, 0.0));
                }
                else if (linestr.startsWith("M12")) {
                    //M12   :ATS Laser Cutting转换程序用，暂不知道用途
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M12;
                }
                else if (linestr.startsWith("M14")) {
                    //M14   :Z Axis Rout Position with Depth Controlled Contouring
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M14;
                }
                else if (linestr.startsWith("M15")) {
                    //M15   :Z Axis Rout Positiong
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M15;
                }
                else if (linestr.startsWith("M16")) {
                    //M16   :Retract with Clamping
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M16;
                }
                else if (linestr.startsWith("M17")) {
                    //M17   :Retract without Clamping
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M17;
                }
                else if (linestr.startsWith("M18")) {
                    //M18   :Tool tip checkProgrammed tool tip check
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M18;
                }
                else if (linestr.startsWith("M19")) {
                    //M19   :<M19,#####>: Binary Map Commands
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M19;
                }
                else if (linestr == "M25") {
                    //M25   :图形Step&Repeat块开始标记 后续的M02命令可以重复介于M25与M01之间的部分
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M25;
                    rowData.isRepeatBlock = true;
                    repeat_rows.clear();
                }
                else if (linestr == "M30") {
                    //M30   :程序结束
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M30;
                }
                else if (linestr.startsWith("M45")) {
                    //M45,text\text\text    :Operator Lone Message
                    //TODO 暂不支持M45
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M45;
                }
                else if (linestr.startsWith("M47")) {
                    //M47,text  :Operator Message
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M47;
                }
                else if (linestr.startsWith("M60")) {
                    //M60   :Reference Scaling enable
                    //TODO 暂不支持M60
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M60;
                }
                else if (linestr.startsWith("M61")) {
                    //M61   :Reference Scaling disable
                    //TODO 暂不支持M61
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M61;
                }
                else if (linestr.startsWith("M62")) {
                    //M62   :Turn on peck drilling
                    //TODO 暂不支持M62
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M62;
                }
                else if (linestr.startsWith("M63")) {
                    //M63  :Turn off peck drilling
                    //TODO 暂不支持M63
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M63;
                }
                else if (linestr.startsWith("M71")) {
                    //M71   :Metric Measuring Mode
                    //TODO 暂不支持M71
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M71;
                }
                else if (linestr.startsWith("M83")) {
                    //<M83,ZUT28.3>
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M83;
                }
                else if (linestr.startsWith("M96")) {
                    //M96,OFF   :Select Spindle Group
                    //TODO 暂不支持M96
                    //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M96
                }
                else if (linestr.startsWith("M97")) {
                    //M97,0*    M97,text    :X方向的canned text
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M97;
                }
                else if (linestr.startsWith("M98")) {
                    //M98,0*    M98,text    :Y方向的canned text
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M98;
                }
                else if (linestr.startsWith("M100")) {
                    //M100  :宏命令
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::M100;
                }
                else if (linestr.startsWith("(K)")) {
                    //K   :表示不能调整顺序
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::K;
                }
                else if (linestr.startsWith("F")) {
                    //F   :F07 Worktable feed rate of 70 IPM or 70 mm/s for routing
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::F;
                }
                else if (linestr.startsWith("remark", Qt::CaseInsensitive)) {
                    // remark_1,"X50.51Y277.975"
                    // remark_2,"X50.51Y558.475"
                    // ATS的Laser Cutting程序里有，表示排版座标
                    rowData.cmd = PcbCamDrillParserPrivate::Cmd::REMARK;
                }
                else if (statement.format == PcbCamDrillParser::DrillFormat_Excellon2) {
                    /********repeat example
                    M25
                    X034193Y010866
                    Y009055
                    Y012677
                    X034626Y013583
                    Y009961
                    M01
                    M02X000000Y025394
                    M02X000000Y023425
                    M02X037295Y-195277
                    M02X000000Y023425
                    M02
                    M08
                    *******************/

                    if (linestr == "M01") {
                        //M01   :Repeat块结束 indicates the end of the part program section which is to be repeated. This command is programmed in a block by itself.
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M01;
                        rowData.isRepeatBlock = false;
                        rowData.repeatRows = repeat_rows;
                        lastM02Pos = QPointF(0.0, 0.0);
                        rowData.lastPos = QPointF(0.0, 0.0);
                    }
                    else if (linestr.startsWith("M02")) {
                        //M02X#Y#M80    :Pattern Repeat
                        // The M02 command causes a repeat of all the commands between the M25 command and the M01 command. The M02 command is
                        // incremental. This means that the coordinate X#Y# is the distance from the beginning of the last pattern, not the distance from Work Zero.
                        // A separate M02 command is required for each repeat of the pattern of set of patterns. After the last M02 repeat command, an extra M02
                        // command which requires no coordinates, must be added in a block by itself. This will clear a counter built into the system software. The
                        // M02 command must always occur after the M01 command discussed above, and before the M08 command discussed below.
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M02;
                        transSet.clear();
                        if (linestr.toUpper() == "M02" || linestr.toUpper() == "M02XY") {
                            //空白的M02,一般在M08之前会有一个不带座标的M02
                            rowData.cmd = PcbCamDrillParserPrivate::Cmd::M02_EMPTY;
                        }
                        else if (linestr.contains("M70") || linestr.contains("M80") || linestr.contains("M90")) {
                            //Repeat时旋转镜像
                            if (linestr.contains("M70")) {
                                //Swap AxisM70
                                //Mirror Image and Swap Axis
                                //You can make better use of PC board materials and reduce setup time by turning the axis of the boards by 180 degrees, or by reversing
                                //the axis to create a mirror image, or both. Excellon provides you with three commands which enable you to reverse and/or rotate the axis
                                //of a pattern, or an entire PC board. These commands are step and repeat commands and must be used in combination with the M25 and
                                //M01 commands, described earlier in this chapter. The swap rotates the pattern 90 degrees and makes a mirror image by changing the X
                                //axis to Y, and the Y axis to X. This command is used in a step and repeat offset block only, as shown
                                transSet.insert(74);
                            }
                            if (linestr.contains("M80")) {
                                //M80 creates a mirror image of a pattern or group of patterns by reversing the sign of the X axis coordinates. All X+ coordinates will be
                                //changed to X-, and all X- coordinates will be changed to X+. The Y coordinates remain the same. This command is used in a step and
                                //repeat offset block only.
                                transSet.insert(72);
                            }
                            if (linestr.contains("M90")) {
                                //M90 creates a mirror image of a pattern or group of patterns by reversing the sign of the Y axis coordinates. All Y+ coordinates will be
                                //changed to Y-, and all Y- coordinates will be changed to Y+. The X coordinates remain the same.
                                //This command is used in a step and repeat offset block only.
                                transSet.insert(71);
                            }
                        }

                        QPointF orgOffset = this->drillCatchXy(linestr.mid(2), statement, globalUnits, QPointF{0.0, 0.0});
                        QPointF offset = lastM02Pos + orgOffset;
                        lastM02Pos = offset;
                        rowData.pos = offset;
                        rowData.pos2 = orgOffset;

                        QTransform transform;
                        if (transSet.contains(74)) transform *= g74Trans;
                        if (transSet.contains(71)) transform *= g71Trans;
                        if (transSet.contains(72)) transform *= g72Trans;
                        QTransform trans(transform.m11(), transform.m12(), transform.m21(), transform.m22(), offset.x(), offset.y());
                        rowData.transform = trans;
                    }
                    else if (linestr.startsWith("M08")) {
                        //M08 EndofStepandRepeat
                        //M08indicatestheendofallstepandrepeatcommands.IfallM02commandshavenotbeencompleted,theCNC-7willreturntothelast
                        //startofpatterncommandandrepeat.Whenallpatternshavebeencompleted,theprogramwillcontinueonpasttheM08,findingeitheran endofprogramcommandormoreprograminformation.AnM30endofprogramcommandmaybecombinedwiththiscommand,
                        //otherwiseitisprogrammedinablockbyitself.
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M08;
                    }
                    else if (linestr.startsWith("G05")) {
                        //G05   :drill mode = Excellon1<G81>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::G05;
                    }
                    else if (linestr.startsWith("M00")) {
                        //Excellon2 <M00> = Excellon1 <M02> End of Program, no rewind
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M00;
                    }
                    else if (linestr.startsWith("M06")) {
                        //<M06X#Y#> :Optional Stop = Excellon1<M01>
                        //TODO 暂不支持M06
                        //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M06;
                    }
                    else if (linestr.startsWith("M09")) {
                        //<M09X#Y#> :Stop for Inspection = Excellon1 <M00>
                        //TODO 暂不支持M09
                        //rowData.cmd = PcbCamDrillParserPrivate::Cmd::M09;
                    }
                    else if (linestr.startsWith("M72")) {
                        //M72   :Inch Measuring Mode = Excellon1 <M70>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M72;
                    }
                } // end of Format_Excellon2
                else if (statement.format == PcbCamDrillParser::DrillFormat_Posalux) {
                    /********repeat example
                    M25
                    X104.146Y131.245
                    X104.146Y131.892
                    X104.146Y132.540
                    X104.882Y129.949
                    X103.409Y129.949
                    M100,M97F
                    X103.194Y103.551(K)
                    M24
                    M26XY109.100
                    M26XY218.200
                    M26XY327.300
                    M26XY436.400
                    G74G71
                    M26X360.285Y22.915
                    M26X469.385Y22.915
                    M26X469.385Y290.215
                    M26X469.385Y379.315
                    M26X469.385Y468.415
                    G73
                    G70

                    M26X416.400Y
                    M26X416.400Y109.100
                    M26X416.400Y218.200
                    M26X416.400Y327.300
                    M26X416.400Y436.400
                    M27
                    *******************/

                    if (linestr == "M24") {
                        //M24   :Repeat块结束 = Excellon2<M01>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M01;
                        rowData.isRepeatBlock = false;
                        rowData.repeatRows = repeat_rows;
                    }
                    else if (linestr.contains(QRegularExpression("G7[01234]"))) {
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::G70_74;
                        if (linestr.contains("G74")) transSet.insert(74);
                        if (linestr.contains("G71")) transSet.insert(71);
                        if (linestr.contains("G72")) transSet.insert(72);
                        if (linestr.contains("G73")) transSet.remove(74);
                        if (linestr.contains("G70")) {
                            transSet.remove(71);
                            transSet.remove(72);
                        }
                    }
                    else if (linestr.startsWith("M26")) {
                        //M26X416.400Y109.100   :Pattern Repeat = Excellon <M02>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M02;
                        QPointF offset = this->drillCatchXy(linestr.mid(2), statement, globalUnits, QPointF{0.0, 0.0});
                        QTransform transform;
                        if (transSet.contains(74)) transform *= g74Trans;
                        if (transSet.contains(71)) transform *= g71Trans;
                        if (transSet.contains(72)) transform *= g72Trans;
                        QTransform trans(QTransform(transform.m11(), transform.m12(), transform.m21(), transform.m22(), offset.x(), offset.y()));
                        rowData.pos = offset;
                        rowData.transform = trans;
                    }
                    else if (linestr.startsWith("M27")) {
                        //M27 = Excellon2 <M08>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M08;
                    }
                    else if (linestr.startsWith("G81")) {
                        //G81   :drill mode = Excellon2 <G05>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::G05;
                    }
                    else if (linestr.startsWith("M00")) {
                        //<M00><M00X#Y#> = Excellon2 <M09>:Stop for Inspection
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M09;
                    }
                    else if (linestr.startsWith("M01")) {
                        //<M01> = Excellon2 <M06>:Optional Stop
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M06;
                    }
                    else if (linestr.startsWith("M02")) {
                        //<M02> = Excellon2 <M00>: End of Program, no rewind
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M00;
                    }
                    else if (linestr.startsWith("M70")) {
                        //<M70>:Inch Measuring Mode = Excellon2 <M72>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M72;
                    }
                    else if (linestr.startsWith("M29")) {
                        //M29   :暂不知道用途是什么，ATS的程序里有
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M29;
                    }

                } // end of Format_Poslaux
                else if (statement.format == PcbCamDrillParser::DrillFormat_Excellon1) {
                    if (linestr == "M24") {
                        //M24   :Repeat块结束 = Excellon2<M01>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M01;
                        rowData.isRepeatBlock = false;
                        rowData.repeatRows = repeat_rows;
                        lastM02Pos = QPointF(0.0, 0.0);
                        rowData.lastPos = QPointF(0.0, 0.0);
                    }
                    else if (linestr.startsWith("M26")) {
                        //M26X#Y#M23    :Pattern Repeat = Excellon <M02>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M02;
                        transSet.clear();
                        if (linestr.toUpper() == "M26" || linestr.toUpper() == "M26XY") {
                            //空白的M02,一般在M08之前会有一个不带座标的M02
                            rowData.cmd = PcbCamDrillParserPrivate::Cmd::M02_EMPTY;
                        }
                        else if (linestr.contains("M23") || linestr.contains("M21") || linestr.contains("M22")) {
                            //Repeat时旋转镜像
                            if (linestr.contains("M23")) {
                                //M23   = Excellon2 <M70>
                                transSet.insert(74);
                            }
                            if (linestr.contains("M21")) {
                                //M21 = Excellon2 <M80>
                                transSet.insert(72);
                            }
                            if (linestr.contains("M22")) {
                                //M22 = Excellon2 <M90>
                                transSet.insert(71);
                            }
                        }

                        QPointF orgOffset = this->drillCatchXy(linestr.mid(2), statement, globalUnits, QPointF{0.0, 0.0});
                        QPointF offset = lastM02Pos + orgOffset;
                        lastM02Pos = offset;
                        rowData.pos = offset;
                        rowData.pos2 = orgOffset;

                        QTransform transform;
                        if (transSet.contains(74)) transform *= g74Trans;
                        if (transSet.contains(71)) transform *= g71Trans;
                        if (transSet.contains(72)) transform *= g72Trans;
                        QTransform trans(transform.m11(), transform.m12(), transform.m21(), transform.m22(), offset.x(), offset.y());
                        rowData.transform = trans;
                    }
                    else if (linestr.startsWith("M27")) {
                        //M27 = Excellon2 <M08>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M08;
                    }
                    else if (linestr.startsWith("G81")) {
                        //G81   :drill mode = Excellon2<G05>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::G05;
                    }
                    else if (linestr.startsWith("M00")) {
                        //<M00><M00X#Y#> = Excellon2 <M09>:Stop for Inspection
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M09;
                    }
                    else if (linestr.startsWith("M01")) {
                        //<M01> = Excellon2 <M06>:Optional Stop
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M06;
                    }
                    else if (linestr.startsWith("M02")) {
                        //<M02> = Excellon2 <M00>: End of Program, no rewind
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M00;
                    }
                    else if (linestr.startsWith("M70")) {
                        //<M70>:Inch Measuring Mode = Excellon2 <M72>
                        rowData.cmd = PcbCamDrillParserPrivate::Cmd::M72;
                    }

                } // end of Format_Excellon1

                if (rowData.isRepeatBlock && rowData.cmd != PcbCamDrillParserPrivate::Cmd::M25) {
                    repeat_rows << rowData;
                }

            } // end of body

            //callback here
            bool ok = iCallback(rowData);
            if (!ok) return false;

            if (rowData.cmd == PcbCamDrillParserPrivate::Cmd::POSXY || rowData.cmd == PcbCamDrillParserPrivate::Cmd::G84 || rowData.cmd == PcbCamDrillParserPrivate::Cmd::G00) {
                rowData.lastPos = rowData.pos;
            }
            else if (rowData.cmd == PcbCamDrillParserPrivate::Cmd::G85 || rowData.cmd == PcbCamDrillParserPrivate::Cmd::G01 || rowData.cmd == PcbCamDrillParserPrivate::Cmd::G02 || rowData.cmd == PcbCamDrillParserPrivate::Cmd::G03 ) {
                rowData.lastPos = rowData.pos2;
            }
        } // end of while
        return true;
    }

    QString filePath;
    PcbCamDrillParser::DrillStatement statement;
    QMap<int, PcbCamDrillTool> tools;
    QList<PcbCamOdbFeatureInfo> features;
    QStringList errorList;

protected:
    PcbCamDrillParser * const q_ptr;
};


PcbCamDrillParser::PcbCamDrillParser():
    d_ptr(new PcbCamDrillParserPrivate(this))
{

}

PcbCamDrillParser::PcbCamDrillParser(const QString &iFilePath, const DrillStatement &iStatement):
    d_ptr(new PcbCamDrillParserPrivate(this))
{
    Q_D(PcbCamDrillParser);
    d->filePath = iFilePath;
    d->statement = iStatement;
}

PcbCamDrillParser::~PcbCamDrillParser()
{

}

void PcbCamDrillParser::setFilePath(const QString &iFilePath)
{
    Q_D(PcbCamDrillParser);
    d->filePath = iFilePath;
}

QString PcbCamDrillParser::filePath() const
{
    Q_D(const PcbCamDrillParser);
    return d->filePath;
}

void PcbCamDrillParser::setStatement(const DrillStatement &iStatement)
{
    Q_D(PcbCamDrillParser);
    d->statement = iStatement;
}

PcbCamDrillParser::DrillStatement PcbCamDrillParser::statement() const
{
    Q_D(const PcbCamDrillParser);
    return d->statement;
}

bool PcbCamDrillParser::parser()
{
    Q_D(PcbCamDrillParser);
    d->tools.clear();
    d->features.clear();
    d->errorList.clear();

    QFile drlfile(d->filePath);
    if (!drlfile.open(QFile::ReadOnly|QFile::Text)) return false;

    QStringList drillrows;

    QTextStream stream(&drlfile);
    QString line;
    while (stream.readLineInto(&line)) {
        drillrows.append(line);
    }
    drlfile.close();


    return d->walkDrillRows(drillrows, d->statement, [this](const PcbCamDrillParserPrivate::RowData &rowdata){
        if (rowdata.section == PcbCamDrillParserPrivate::Section::Head ) {
            if (rowdata.cmd == PcbCamDrillParserPrivate::Cmd::TCODE) {
                auto tool = rowdata.tool;
                d_func()->tools.insert(tool.T(), tool);
            }
        }
        else if (rowdata.section == PcbCamDrillParserPrivate::Section::Body) {
            if (rowdata.cmd == PcbCamDrillParserPrivate::Cmd::POSXY
                    || rowdata.cmd == PcbCamDrillParserPrivate::Cmd::G85
                    || rowdata.cmd == PcbCamDrillParserPrivate::Cmd::G84
                    || rowdata.cmd == PcbCamDrillParserPrivate::Cmd::G01
                    || rowdata.cmd == PcbCamDrillParserPrivate::Cmd::G02
                    || rowdata.cmd == PcbCamDrillParserPrivate::Cmd::G03)
            {
                auto feat = d_func()->createFeature(rowdata);
                d_func()->features.append(feat);
            }
            else if (rowdata.cmd == PcbCamDrillParserPrivate::Cmd::M02) {
                for (const auto &r : rowdata.repeatRows) {
                    auto row = r;
                    row.pos = rowdata.transform.map(row.pos);
                    row.pos2 = rowdata.transform.map(row.pos2);
                    auto feat = d_func()->createFeature(row);
                    d_func()->features.append(feat);
                }
            }
        }
        return true;
    });

}

QStringList PcbCamDrillParser::errors() const
{
    Q_D(const PcbCamDrillParser);
    return d->errorList;
}

QStringList PcbCamDrillParser::symbols() const
{
    Q_D(const PcbCamDrillParser);
    QSet<QString> res;
    for (auto f : d->features) {
        res.insert(f.symbol);
    }
    return res.values();
}

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

PcbCamDrillParser::DrillStatement::DrillStatement(const QVariantMap &iData)
{
    if (iData.contains("format")) {
        QString formatstr = iData.value("format").toString().toUpper();
        if (formatstr == "EXCELLON1") {
            this->format = DrillFormat_Excellon1;
        }
        else if (formatstr == "EXCELLON2") {
            this->format = DrillFormat_Excellon2;
        }
        else if (formatstr == "POSALUX") {
            this->format = DrillFormat_Posalux;
        }
        else {
            this->format = DrillFormat_Unknow;
        }
    }

    if (iData.contains("units")) {
        QString unitstr = iData.value("units").toString().toUpper();
        if (unitstr == "INCH") {
            this->units = PcbCam::Units_Inch;
        }
        else if (unitstr == "MM") {
            this->units = PcbCam::Units_Mm;
        }
        else {
            this->units = PcbCam::Units_Unknow;
        }
    }

    if (iData.contains("toolUnits")) {
        QString toolunitstr = iData.value("toolUnits").toString().toUpper();
        if (toolunitstr == "INCH") {
            this->toolUnits = PcbCam::Units_Inch;
        }
        else if (toolunitstr == "MM") {
            this->toolUnits = PcbCam::Units_Mm;
        }
        else {
            this->toolUnits = PcbCam::Units_Unknow;
        }
    }

    if (iData.contains("zerosOmit")) {
        QString zerostr = iData.value("zerosOmit").toString().toUpper();
        if (zerostr == "DECIMAL") {
            this->zerosOmit = PcbCam::ZerosOmit_Decimal;
        }
        else if (zerostr == "LEADING") {
            this->zerosOmit = PcbCam::ZerosOmit_Leading;
        }
        else if (zerostr == "TRAINING") {
            this->zerosOmit = PcbCam::ZerosOmit_Training;
        }
        else if (zerostr == "NONE") {
            this->zerosOmit = PcbCam::ZerosOmit_None;
        }
        else {
            this->zerosOmit = PcbCam::ZerosOmit_Unknow;
        }
    }

    if (iData.contains("coordMode")) {
        QString coordmodestr = iData.value("coordMode").toString().toUpper();
        if (coordmodestr == "absoulte") {
            this->coordMode = PcbCam::CoordAbsoulte;
        }
        else if (coordmodestr == "incremental") {
            this->coordMode = PcbCam::CoordIncremental;
        }
        else {
            this->coordMode = PcbCam::CoordUnknow;
        }
    }

    if (iData.contains("integerDigits")) {
        this->integerDigits = iData.value("integerDigits").toInt();
    }

    if (iData.contains("decimalDigits")) {
        this->decimalDigits = iData.value("decimalDigits").toInt();
    }
}

QVariantMap PcbCamDrillParser::DrillStatement::toMap() const
{
    QString formatStr;
    if (this->format == DrillFormat_Excellon1) {
        formatStr = "excellon1";
    }
    else if (this->format == DrillFormat_Excellon2) {
        formatStr = "excellon2";
    }
    else if (this->format == DrillFormat_Posalux) {
        formatStr = "posalux";
    }
    else if (this->format == DrillFormat_Unknow) {
        formatStr = "unknow";
    }

    QString unitsStr;
    if (this->units == PcbCam::Units_Inch) {
        unitsStr = "inch";
    }
    else if (this->units == PcbCam::Units_Mm) {
        unitsStr = "mm";
    }
    else if (this->units == PcbCam::Units_Unknow) {
        unitsStr = "unknow";
    }

    QString toolUnitsStr;
    if (this->toolUnits == PcbCam::Units_Inch) {
        toolUnitsStr = "inch";
    }
    else if (this->toolUnits == PcbCam::Units_Mm) {
        toolUnitsStr = "mm";
    }
    else if (this->toolUnits == PcbCam::Units_Unknow) {
        toolUnitsStr = "unknow";
    }

    QString zeroStr;
    if (this->zerosOmit == PcbCam::ZerosOmit_Decimal) {
        zeroStr = "decimal";
    }
    else if (this->zerosOmit == PcbCam::ZerosOmit_Leading) {
        zeroStr = "leading";
    }
    else if (this->zerosOmit == PcbCam::ZerosOmit_Training) {
        zeroStr = "training";
    }
    else if (this->zerosOmit == PcbCam::ZerosOmit_None) {
        zeroStr = "none";
    }
    else if (this->zerosOmit == PcbCam::ZerosOmit_Unknow) {
        zeroStr = "unknow";
    }

    QString coordModeStr;
    if (this->coordMode == PcbCam::CoordAbsoulte) {
        coordModeStr = "absoulte";
    }
    else if (this->coordMode == PcbCam::CoordIncremental) {
        coordModeStr = "incremental";
    }
    else if (this->coordMode == PcbCam::CoordUnknow) {
        coordModeStr = "unknow";
    }

    return QVariantMap {
        {"format", formatStr},
        {"units", unitsStr},
        {"toolUnits", toolUnitsStr},
        {"zerosOmit", zeroStr},
        {"coordMode", coordModeStr},
        {"integerDigits", this->integerDigits},
        {"decimalDigits", this->decimalDigits},
    };
}
