// ============================================================================
//  qt_calculator —— Day2 下午案例参考实现：简易计算器
//  对应教案：OUTLINE_4DAY.md Day2 下午「信号槽机制 → 项目案例：简易计算器」
//  配套文档：docs/teaching/examples/calculator/README.md
//  手工维护，非生成产物（同 student_manager/qthread_worker 等教学补充示例）。
//
//  教学重点（对应当天知识点，逐条在代码里标注）：
//    1. 信号槽两种写法都用上：数字/运算符键用「lambda + this 作 context」批量
//       绑定，等号/清空/退格键用「成员函数指针」绑定——当天两种写法都要会认；
//    2. 所有 connect 只在构造函数里执行一次，不给「重复 connect 导致槽触发
//       两次」的坑（当天★坑卡）留生长空间；
//    3. QGridLayout 排键盘（布局原理 Day3 下午讲透，今天先用后学）；
//    4. 中文/符号按钮文字用 QStringLiteral + 源文件 UTF-8，呼应当天编码坑卡。
//
//  离屏验证（自测模式）：QT_QPA_PLATFORM=offscreen tools/run_qt.sh qt_calculator
//  自测流程：直接调用与按钮绑定的同一批处理函数（等价于点击），依次验证
//  「7+3=」「6÷0=」「4.5+0.5=」「12 退格」「2+3+4= 链式运算」，全部通过退出码 0，
//  任一步失败退出码 1。
// ============================================================================

#include <QApplication>
#include <QDebug>
#include <QGridLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>

class Calculator : public QWidget {
    Q_OBJECT
public:
    explicit Calculator(QWidget* parent = nullptr) : QWidget(parent) {
        setWindowTitle(QStringLiteral("简易计算器"));

        m_display = new QLineEdit(QStringLiteral("0"), this);
        m_display->setReadOnly(true);   // 只显示结果，不接受直接键入
        m_display->setAlignment(Qt::AlignRight);

        auto* mainLayout = new QVBoxLayout(this);
        mainLayout->addWidget(m_display);
        mainLayout->addLayout(buildKeypad());
    }

    // 供自测直接调用，效果等价于点击对应按钮（同一批处理函数）。
    void onDigit(int digit) {
        if (m_error) onClear();
        if (m_startNew || m_display->text() == QStringLiteral("0")) {
            m_display->setText(QString::number(digit));
            m_startNew = false;
        } else {
            m_display->setText(m_display->text() + QString::number(digit));
        }
    }

    void onDot() {
        if (m_error) onClear();
        if (m_startNew) {
            m_display->setText(QStringLiteral("0."));
            m_startNew = false;
            return;
        }
        if (!m_display->text().contains(QLatin1Char('.')))
            m_display->setText(m_display->text() + QStringLiteral("."));
    }

    void onOperator(QChar op) {
        if (m_error) return;
        if (!m_startNew) compute();   // 连续按运算符：先把上一次算掉，再开始下一次（链式计算）
        m_pendingValue = m_display->text().toDouble();
        m_pendingOp = op;
        m_startNew = true;
    }

    void onEquals() {
        if (m_error) return;
        compute();
        m_pendingOp = QChar();   // 等号后清空待运算符：再按数字视为全新一次输入
    }

    void onClear() {
        m_display->setText(QStringLiteral("0"));
        m_pendingValue = 0.0;
        m_pendingOp = QChar();
        m_startNew = false;
        m_error = false;
    }

    void onBackspace() {
        if (m_error) { onClear(); return; }
        QString text = m_display->text();
        text.chop(1);
        m_display->setText(text.isEmpty() ? QStringLiteral("0") : text);
    }

    QString displayText() const { return m_display->text(); }

private:
    QGridLayout* buildKeypad() {
        auto* grid = new QGridLayout;   // 布局原理 Day3 下午讲透，今天先会用：行列坐标摆键盘

        addDigit(grid, 7, 0, 0); addDigit(grid, 8, 0, 1); addDigit(grid, 9, 0, 2);
        addOperator(grid, QStringLiteral("÷"), QLatin1Char('/'), 0, 3);

        addDigit(grid, 4, 1, 0); addDigit(grid, 5, 1, 1); addDigit(grid, 6, 1, 2);
        addOperator(grid, QStringLiteral("×"), QLatin1Char('*'), 1, 3);

        addDigit(grid, 1, 2, 0); addDigit(grid, 2, 2, 1); addDigit(grid, 3, 2, 2);
        addOperator(grid, QStringLiteral("-"), QLatin1Char('-'), 2, 3);

        addDigit(grid, 0, 3, 0);

        auto* btnDot = new QPushButton(QStringLiteral("."), this);
        grid->addWidget(btnDot, 3, 1);
        connect(btnDot, &QPushButton::clicked, this, [this] { onDot(); });

        // 等号/清空/退格各自唯一，用成员函数指针连接——信号槽的另一种常见写法。
        auto* btnEquals = new QPushButton(QStringLiteral("="), this);
        grid->addWidget(btnEquals, 3, 2);
        connect(btnEquals, &QPushButton::clicked, this, &Calculator::onEquals);

        addOperator(grid, QStringLiteral("+"), QLatin1Char('+'), 3, 3);

        auto* btnClear = new QPushButton(QStringLiteral("C"), this);
        grid->addWidget(btnClear, 4, 0);
        connect(btnClear, &QPushButton::clicked, this, &Calculator::onClear);

        auto* btnBackspace = new QPushButton(QStringLiteral("⌫"), this);
        grid->addWidget(btnBackspace, 4, 1);
        connect(btnBackspace, &QPushButton::clicked, this, &Calculator::onBackspace);

        return grid;
    }

    // 数字键：同一个槽 onDigit(int) 处理全部按钮，lambda 捕获各自的数字值，
    // 第五参数传 this 作 context——calculator 销毁时这些连接自动断开（当天坑卡）。
    void addDigit(QGridLayout* grid, int digit, int row, int col) {
        auto* btn = new QPushButton(QString::number(digit), this);
        grid->addWidget(btn, row, col);
        connect(btn, &QPushButton::clicked, this, [this, digit] { onDigit(digit); });
    }

    // 运算符键：同理，lambda + context，一个槽 onOperator(QChar) 处理全部。
    void addOperator(QGridLayout* grid, const QString& label, QChar op, int row, int col) {
        auto* btn = new QPushButton(label, this);
        grid->addWidget(btn, row, col);
        connect(btn, &QPushButton::clicked, this, [this, op] { onOperator(op); });
    }

    void compute() {
        if (m_pendingOp.isNull()) return;   // 还没有待运算符（本次是第一个运算符）
        const double rhs = m_display->text().toDouble();
        double result = 0.0;
        if (m_pendingOp == QLatin1Char('+')) result = m_pendingValue + rhs;
        else if (m_pendingOp == QLatin1Char('-')) result = m_pendingValue - rhs;
        else if (m_pendingOp == QLatin1Char('*')) result = m_pendingValue * rhs;
        else if (m_pendingOp == QLatin1Char('/')) {
            if (rhs == 0.0) { showError(); return; }   // 除零：不崩，给出明确的错误状态
            result = m_pendingValue / rhs;
        }
        m_display->setText(QString::number(result, 'g', 12));
        m_pendingValue = result;
        m_startNew = true;
    }

    void showError() {
        m_display->setText(QStringLiteral("Error"));
        m_error = true;
        m_startNew = true;
    }

    QLineEdit* m_display = nullptr;
    double m_pendingValue = 0.0;
    QChar m_pendingOp;
    bool m_startNew = false;
    bool m_error = false;
};

// ------------------------------------------------------------ 自测入口 ----

static bool expectEqual(const char* label, const QString& actual, const QString& expected) {
    if (actual != expected) {
        qCritical() << "自测失败：" << label << "实际=" << actual << "预期=" << expected;
        return false;
    }
    qInfo() << "自测通过：" << label << "=>" << actual;
    return true;
}

static bool runSelfTest(Calculator* calc) {
    calc->onClear();
    calc->onDigit(7); calc->onOperator(QLatin1Char('+')); calc->onDigit(3); calc->onEquals();
    if (!expectEqual("7 + 3 =", calc->displayText(), QStringLiteral("10"))) return false;

    calc->onClear();
    calc->onDigit(6); calc->onOperator(QLatin1Char('/')); calc->onDigit(0); calc->onEquals();
    if (!expectEqual("6 ÷ 0 =", calc->displayText(), QStringLiteral("Error"))) return false;

    calc->onClear();
    calc->onDigit(4); calc->onDot(); calc->onDigit(5);
    calc->onOperator(QLatin1Char('+'));
    calc->onDigit(0); calc->onDot(); calc->onDigit(5);
    calc->onEquals();
    if (!expectEqual("4.5 + 0.5 =", calc->displayText(), QStringLiteral("5"))) return false;

    calc->onClear();
    calc->onDigit(1); calc->onDigit(2); calc->onBackspace();
    if (!expectEqual("12 退格", calc->displayText(), QStringLiteral("1"))) return false;

    calc->onClear();
    calc->onDigit(2); calc->onOperator(QLatin1Char('+'));
    calc->onDigit(3); calc->onOperator(QLatin1Char('+'));
    calc->onDigit(4); calc->onEquals();
    if (!expectEqual("2 + 3 + 4 =（链式运算）", calc->displayText(), QStringLiteral("9"))) return false;

    return true;
}

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    Calculator calc;
    calc.resize(240, 320);
    calc.show();

    if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
        QTimer::singleShot(0, &calc, [&calc] {
            const bool ok = runSelfTest(&calc);
            QCoreApplication::exit(ok ? 0 : 1);
        });
    }
    return app.exec();
}

#include "qt_calculator.moc"
