#include <atomic>
#include <borealis.hpp>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <functional>
#include <mutex>
#include <string>
#include <thread>

extern "C" {
#include <switch.h>
#include <unistd.h>

#include "bench.h"
#include "cpu_stress.h"
#include "gpu_bw.h"
#include "gpu_stress.h"
#include "hoc_clk.h"
#include "mt_cpu.h"
#include "mt_gpu.h"
#include "run_furmark.h"
}

static std::string fstr(const char *f, double v) {
    char b[64];
    std::snprintf(b, sizeof(b), f, v);
    return b;
}
static std::string fstru(const char *f, unsigned long long v) {
    char b[64];
    std::snprintf(b, sizeof(b), f, v);
    return b;
}

static brls::Label *makeRow(brls::Box *parent, const std::string &name) {
    auto *row = new brls::Box(brls::Axis::ROW);
    row->setMarginBottom(8.0f);
    auto *n = new brls::Label();
    n->setText(name);
    n->setGrow(1.0f);
    auto *v = new brls::Label();
    v->setText("-");
    row->addView(n);
    row->addView(v);
    parent->addView(row);
    return v;
}

struct StatCells {
    brls::Label *load = nullptr, *clock = nullptr, *volt = nullptr, *vddq = nullptr, *temp = nullptr;
};

static brls::Label *statCell(brls::Box *row, float fs) {
    auto *l = new brls::Label();
    if (fs > 0.0f)
        l->setFontSize(fs);
    row->addView(l);
    return l;
}
static void statSep(brls::Box *row, float fs) {
    auto *s = new brls::Label();
    s->setText("|");
    if (fs > 0.0f)
        s->setFontSize(fs);
    s->setTextColor(nvgRGB(120, 120, 120));
    s->setMarginLeft(7.0f);
    s->setMarginRight(7.0f);
    row->addView(s);
}
static void fmtLoad(brls::Label *l, unsigned val, bool isRam) {
    char b[32];
    if (isRam)
        std::snprintf(b, sizeof b, "%u.%u GB/s", val / 1000u, (val % 1000u) / 100u);
    else
        std::snprintf(b, sizeof b, "%u%%", val);
    l->setText(b);
}
static void fmtClock1(brls::Label *l, uint32_t hz) {
    char b[32];
    std::snprintf(b, sizeof b, "%u.%u MHz", hz / 1000000u, (hz / 100000u) % 10u);
    l->setText(b);
}
static void fmtVolt(brls::Label *l, uint32_t uv) {
    char b[32];
    std::snprintf(b, sizeof b, "%u mV", uv / 1000u);
    l->setText(b);
}
static void fmtTemp(brls::Label *l, int32_t mc) {
    char b[32];
    std::snprintf(b, sizeof b, "%d°C", mc / 1000);
    l->setText(b);
}

class SysInfoTab : public brls::Box {
    public:
    SysInfoTab() {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        auto *clk = new brls::Header();
        clk->setTitle("Clocks");
        this->addView(clk);
        cpuR = makeCompRow(this, "CPU");
        gpuR = makeCompRow(this, "GPU");
        ramR = makeRamRow(this);

        auto *sys = new brls::Header();
        sys->setTitle("System");
        this->addView(sys);
        mode = makeRow(this, "Mode");
        threads = makeRow(this, "Threads");

        hocclk_init();
        refresh();
    }

    void frame(brls::FrameContext *ctx) override {
        if (++tick >= 15) {
            tick = 0;
            refresh();
        }
        brls::Box::frame(ctx);
    }

    private:
    static StatCells makeCompRow(brls::Box *parent, const char *name) {
        auto *row = new brls::Box(brls::Axis::ROW);
        row->setMarginBottom(10.0f);
        auto *n = new brls::Label();
        n->setText(name);
        n->setGrow(1.0f);
        row->addView(n);
        StatCells c;
        c.load = statCell(row, 0);
        statSep(row, 0);
        c.clock = statCell(row, 0);
        statSep(row, 0);
        c.volt = statCell(row, 0);
        statSep(row, 0);
        c.temp = statCell(row, 0);
        parent->addView(row);
        return c;
    }
    static StatCells makeRamRow(brls::Box *parent) {
        auto *row = new brls::Box(brls::Axis::ROW);
        row->setMarginBottom(10.0f);
        auto *n = new brls::Label();
        n->setText("RAM");
        n->setGrow(1.0f);
        row->addView(n);
        StatCells c;
        c.load = statCell(row, 0);
        statSep(row, 0);
        c.clock = statCell(row, 0);
        statSep(row, 0);
        c.volt = statCell(row, 0);
        statSep(row, 0);
        c.vddq = statCell(row, 0);
        statSep(row, 0);
        c.temp = statCell(row, 0);
        parent->addView(row);
        return c;
    }
    void setRow(StatCells &r, unsigned loadOrBw, bool isRam, uint32_t hz, uint32_t uv, int32_t mc) {
        fmtLoad(r.load, loadOrBw, isRam);
        fmtClock1(r.clock, hz);
        fmtVolt(r.volt, uv);
        fmtTemp(r.temp, mc);
    }
    void naRow(StatCells &r) {
        r.load->setText("N/A");
        r.clock->setText("-");
        r.volt->setText("-");
        r.temp->setText("-");
    }
    void refresh() {
        sysinfo_t s;
        bench_get_sysinfo(&s);
        mode->setText(s.is_4gb ? "Application" : "Applet");
        threads->setText(fstru("%llu", (unsigned long long)s.threads));

        HocClkContext c;
        if (hocclk_get(&c)) {
            setRow(cpuR, c.stable.partLoad[3] / 10, false, c.stable.freqs[0], c.stable.voltages[2], c.stable.temps[5]);
            setRow(gpuR, c.stable.partLoad[2] / 10, false, c.stable.freqs[1], c.stable.voltages[3], c.stable.temps[6]);
            setRow(ramR, c.stable.partLoad[6], true, c.stable.freqs[2], c.stable.voltages[1], c.stable.temps[7]);
            fmtVolt(ramR.vddq, c.stable.voltages[4]);
        } else {
            naRow(cpuR);
            naRow(gpuR);
            naRow(ramR);
            ramR.vddq->setText("-");
        }
    }
    StatCells cpuR, gpuR, ramR;
    brls::Label *mode, *threads;
    int tick = 0;
};

class BenchTab : public brls::Box {
    public:
    BenchTab() {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        runBtn = new brls::Button();
        runBtn->setText("Run");
        runBtn->registerClickAction([this](brls::View *) {
            start();
            return true;
        });
        this->addView(runBtn);

        status = new brls::Label();
        status->setText("Idle");
        status->setMarginTop(8.0f);
        status->setMarginBottom(6.0f);
        this->addView(status);

        bar = new brls::Box(brls::Axis::ROW);
        bar->setHeight(18.0f);
        bar->setWidthPercentage(100.0f);
        bar->setMarginBottom(14.0f);
        barFill = new brls::Rectangle();
        barFill->setColor(nvgRGB(0, 193, 210));
        barFill->setWidthPercentage(0.0f);
        barTrack = new brls::Rectangle();
        barTrack->setColor(nvgRGB(48, 48, 54));
        barTrack->setGrow(1.0f);
        bar->addView(barFill);
        bar->addView(barTrack);
        this->addView(bar);

        auto *h1 = new brls::Header();
        h1->setTitle("GPU bandwidth");
        this->addView(h1);
        gpuCopy = makeRow(this, "GPU Copy");
        gpuRead = makeRow(this, "GPU Read");
        gpuWrite = makeRow(this, "GPU Write");

        auto *h2 = new brls::Header();
        h2->setTitle("CPU bandwidth");
        this->addView(h2);
        cpuCopy = makeRow(this, "CPU Copy");
        cpuRead = makeRow(this, "CPU Read");
        cpuWrite = makeRow(this, "CPU Write");

        auto *h3 = new brls::Header();
        h3->setTitle("RAM latency");
        this->addView(h3);
        l2 = makeRow(this, "L2");
        ram = makeRow(this, "Full RAM");
    }

    ~BenchTab() override {
        if (ctx)
            bench_end(ctx);
    }

    void frame(brls::FrameContext *fc) override {
        if (running) {
            if (primed) {

                primed = false;
            } else {
                const char *label = "";
                float frac = 0.0f;
                bool more = bench_step(ctx, &res, &label, &frac);
                setProgress(frac);
                if (more) {
                    status->setText(fstr("%.0f%%", frac * 100.0f) + "   " + label);
                    primed = true;
                } else {
                    showResults();
                    bench_end(ctx);
                    ctx = nullptr;
                    running = false;
                    status->setText("Done");
                    appletSetAutoSleepDisabled(false);
                }
            }
        }
        brls::Box::frame(fc);
    }

    private:
    void setProgress(float f) {
        barFill->setWidthPercentage(f * 100.0f);
    }

    void showResults() {
        gpuCopy->setText(fstr("%.1f MB/s", res.gpu_copy));
        gpuRead->setText(fstr("%.1f MB/s", res.gpu_read));
        gpuWrite->setText(fstr("%.1f MB/s", res.gpu_write));
        cpuCopy->setText(fstr("%.1f MB/s", res.cpu_copy));
        cpuRead->setText(fstr("%.1f MB/s", res.cpu_read));
        cpuWrite->setText(fstr("%.1f MB/s", res.cpu_write));
        l2->setText(fstr("%.1f ns", res.l2_ns));
        ram->setText(fstr("%.1f ns", res.ram_ns));
    }

    void start() {
        if (running)
            return;
        memset(&res, 0, sizeof(res));
        ctx = bench_begin();
        if (!ctx) {
            status->setText("Out of memory");
            return;
        }
        running = true;
        primed = true;
        setProgress(0.0f);
        status->setText("Running benchmark...");
        appletSetAutoSleepDisabled(true);
    }

    bench_ctx *ctx = nullptr;
    bool running = false;
    bool primed = false;
    bench_results_t res{};
    brls::Button *runBtn;
    brls::Label *status;
    brls::Box *bar;
    brls::Rectangle *barFill, *barTrack;
    brls::Label *gpuCopy, *gpuRead, *gpuWrite, *cpuCopy, *cpuRead, *cpuWrite, *l2, *ram;
};

struct StressShared {
    std::atomic<bool> running{ false }, stop{ false };
    std::atomic<double> gflops{ 0.0 };
    std::atomic<uint64_t> dispatches{ 0 }, mismatches{ 0 };
    std::thread worker;
};

class StressTab : public brls::Box {
    public:
    StressTab() {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        toggle = new brls::Button();
        toggle->setText("Start GPU stress");
        toggle->registerClickAction([this](brls::View *) {
            onToggle();
            return true;
        });
        this->addView(toggle);

        statusL = new brls::Label();
        statusL->setText("Stopped");
        statusL->setMarginTop(8.0f);
        statusL->setMarginBottom(8.0f);
        this->addView(statusL);

        auto *h = new brls::Header();
        h->setTitle("Info");
        this->addView(h);
        gflops = makeRow(this, "GFLOPS");
        dispatches = makeRow(this, "Dispatches");
        mismatches = makeRow(this, "Mismatches");

        auto *info = new brls::Label();
        info->setText("GPU stress test, Mismatches > 0 indicate instability.");
        info->setMarginTop(16.0f);
        this->addView(info);
    }

    ~StressTab() override {
        stopWorker();
    }

    void willDisappear(bool resetState = false) override {
        stopWorker();
        brls::Box::willDisappear(resetState);
    }

    void frame(brls::FrameContext *ctx) override {
        if (sh.running.load()) {
            gflops->setText(fstr("%.1f", sh.gflops.load()));
            dispatches->setText(fstru("%llu", (unsigned long long)sh.dispatches.load()));
            mismatches->setText(fstru("%llu", (unsigned long long)sh.mismatches.load()));
        }
        brls::Box::frame(ctx);
    }

    private:
    void onToggle() {
        if (sh.running.load())
            stopWorker();
        else
            startWorker();
    }
    void startWorker() {
        if (sh.worker.joinable())
            sh.worker.join();
        sh.stop.store(false);
        sh.running.store(true);
        toggle->setText("Stop GPU stress");
        statusL->setText("Running...");
        sh.worker = std::thread([this] {
            appletSetAutoSleepDisabled(true);
            uint64_t totD = 0, totM = 0;
            while (!sh.stop.load()) {
                double g = 0;
                uint64_t d = 0, m = 0;
                if (!gpu_stress_run(&g, &d, &m))
                    break;
                totD += d;
                totM += m;
                sh.gflops.store(g);
                sh.dispatches.store(totD);
                sh.mismatches.store(totM);
            }
            appletSetAutoSleepDisabled(false);
            sh.running.store(false);
        });
    }
    void stopWorker() {
        sh.stop.store(true);
        if (sh.worker.joinable())
            sh.worker.join();
        gpu_stress_shutdown();
        sh.running.store(false);
        if (toggle)
            toggle->setText("Start GPU stress");
        if (statusL)
            statusL->setText("Stopped");
    }
    StressShared sh;
    brls::Button *toggle;
    brls::Label *statusL, *gflops, *dispatches, *mismatches;
};

class FurmarkTab : public brls::Box {
    public:
    FurmarkTab(int which, const char *desc) : which(which) {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        toggle = new brls::Button();
        toggle->setText("Start");
        toggle->registerClickAction([this](brls::View *) {
            onToggle();
            return true;
        });
        this->addView(toggle);

        statusL = new brls::Label();
        statusL->setText("Stopped");
        statusL->setMarginTop(8.0f);
        statusL->setMarginBottom(12.0f);
        this->addView(statusL);

        auto *info = new brls::Label();
        info->setText(desc);
        info->setMarginBottom(8.0f);
        this->addView(info);

        auto *h = new brls::Header();
        h->setTitle("Info");
        this->addView(h);
        if (which == 3) {
            gpuFpsL = makeRow(this, "GPU FPS");
            cpuFpsL = makeRow(this, "CPU FPS");
        } else {
            gpuFpsL = makeRow(this, "FPS");
        }
    }

    ~FurmarkTab() override {
        if (run_furmark_running())
            run_furmark_stop();
    }

    void willDisappear(bool resetState = false) override {
        if (run_furmark_running())
            run_furmark_stop();
        brls::Box::willDisappear(resetState);
    }

    void frame(brls::FrameContext *fc) override {
        bool r = run_furmark_running() != 0;
        if (r != shown) {
            shown = r;
            toggle->setText(r ? "Stop" : "Start");
            statusL->setText(r ? "Running..." : "Stopped");
            if (!r) {
                gpuFpsL->setText("-");
                if (cpuFpsL)
                    cpuFpsL->setText("-");
            }
        }
        if (r) {
            gpuFpsL->setText(fstr("%.1f", run_furmark_fps()));
            if (cpuFpsL)
                cpuFpsL->setText(fstr("%.1f", run_furmark_cpu_fps()));
        }
        brls::Box::frame(fc);
    }

    private:
    void onToggle() {
        if (run_furmark_running())
            run_furmark_stop();
        else
            run_furmark_start(which);
    }
    int which;
    bool shown = false;
    brls::Button *toggle;
    brls::Label *statusL;
    brls::Label *gpuFpsL = nullptr;
    brls::Label *cpuFpsL = nullptr;
};

class MemtesterTab : public brls::Box {
    public:
    MemtesterTab() {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        auto *modeRow = new brls::Box(brls::Axis::ROW);
        modeRow->setMarginBottom(10.0f);
        auto *ml = new brls::Label();
        ml->setText("Mode");
        ml->setGrow(1.0f);
        modeRow->addView(ml);
        modeVal = new brls::Label();
        modeVal->setText(modeName(mode));
        modeRow->addView(modeVal);
        this->addView(modeRow);

        auto *hint = new brls::Label();
        hint->setText("Use L / R to select the memtester mode. Press A to run.");
        hint->setFontSize(15.0f);
        hint->setTextColor(nvgRGB(150, 150, 150));
        hint->setMarginBottom(14.0f);
        this->addView(hint);

        toggle = new brls::Button();
        toggle->setText("Start");
        toggle->registerClickAction([this](brls::View *) {
            onToggle();
            return true;
        });
        this->addView(toggle);

        this->registerAction("Prev mode", brls::ControllerButton::BUTTON_LB, [this](brls::View *) {
            cycle(-1);
            return true;
        });
        this->registerAction("Next mode", brls::ControllerButton::BUTTON_RB, [this](brls::View *) {
            cycle(1);
            return true;
        });

        statusL = new brls::Label();
        statusL->setText("Stopped");
        statusL->setMarginTop(10.0f);
        statusL->setMarginBottom(8.0f);
        this->addView(statusL);

        bar = new brls::Box(brls::Axis::ROW);
        bar->setHeight(18.0f);
        bar->setWidthPercentage(100.0f);
        bar->setMarginBottom(14.0f);
        barFill = new brls::Rectangle();
        barFill->setColor(nvgRGB(0, 193, 210));
        barFill->setWidthPercentage(0.0f);
        barTrack = new brls::Rectangle();
        barTrack->setColor(nvgRGB(48, 48, 54));
        barTrack->setGrow(1.0f);
        bar->addView(barFill);
        bar->addView(barTrack);
        this->addView(bar);

        auto *h = new brls::Header();
        h->setTitle("Info");
        this->addView(h);
        rowA = makeRow(this, "Loops");
        rowB = makeRow(this, "Mismatches");
        rowC = makeRow(this, "Detail");

        updateBar();
    }

    void setProgress(float f) {
        barFill->setWidthPercentage(f * 100.0f);
    }

    void updateBar() {
        bar->setVisibility(isGpu() ? brls::Visibility::GONE : brls::Visibility::VISIBLE);
    }

    ~MemtesterTab() override {
        stopAny();
    }

    void willDisappear(bool resetState = false) override {
        stopAny();
        brls::Box::willDisappear(resetState);
    }

    void frame(brls::FrameContext *ctx) override {
        if (isGpu()) {
            mt_gpu_status_t s;
            mt_gpu_get(&s);
            if (s.running || lastRunning) {
                rowA->setText(fstru("%llu", (unsigned long long)s.loop));
                rowB->setText(fstru("%llu", (unsigned long long)s.mismatches));
                rowC->setText(fstru("%llu MB tested", (unsigned long long)s.size_mb));
                statusL->setText(s.error ? std::string("Error: ") + s.status : std::string(s.status));
            }
            syncToggle(s.running != 0);
        } else {
            mt_cpu_status_t s;
            mt_cpu_get(&s);
            if (mt_cpu_running() || lastRunning) {
                rowA->setText(fstru("%llu", (unsigned long long)s.loop));
                rowB->setText(fstru("%llu", (unsigned long long)s.mismatches));
                char d[96];
                if (mode == 1)
                    std::snprintf(d, sizeof d, "%llu MB, burn-in x%llu",
                                  (unsigned long long)s.total_mb, (unsigned long long)s.burnin_iters);
                else
                    std::snprintf(d, sizeof d, "%llu MB, %d threads",
                                  (unsigned long long)s.total_mb, s.threads);
                rowC->setText(d);
                setProgress(mt_cpu_running() ? s.progress : 0.0f);
                if (mt_cpu_running())
                    statusL->setText(std::string(s.mismatches ? "Errors found! Running - " : "Running - ") +
                                     (s.test ? s.test : ""));
            }
            syncToggle(mt_cpu_running() != 0);
        }
        brls::Box::frame(ctx);
    }

    private:
    static const char *modeName(int m) {
        switch (m) {
            case 0: return "CPU - Memtester";
            case 1: return "CPU - Memtester + BW Burn-in";
            case 2: return "GPU - Memtester (Fast)";
            case 3: return "GPU - Memtester (Full)";
        }
        return "";
    }
    bool isGpu() const {
        return mode >= 2;
    }
    bool anyRunning() {
        return mt_cpu_running() || mt_gpu_running();
    }
    void cycle(int dir) {
        if (anyRunning())
            return;
        mode = (mode + dir + 4) % 4;
        modeVal->setText(modeName(mode));
        updateBar();
    }
    void onToggle() {
        if (anyRunning())
            stopAny();
        else
            startAny();
    }
    void startAny() {
        rowA->setText("-");
        rowB->setText("-");
        rowC->setText("-");
        switch (mode) {
            case 0: mt_cpu_start(0); break;
            case 1: mt_cpu_start(1); break;
            case 2: mt_gpu_start(0); break;
            case 3: mt_gpu_start(1); break;
        }
    }
    void stopAny() {
        if (mt_cpu_running())
            mt_cpu_stop();
        if (mt_gpu_running())
            mt_gpu_stop();
    }
    void syncToggle(bool running) {
        if (running == lastRunning)
            return;
        lastRunning = running;
        toggle->setText(running ? "Stop" : "Start");
        if (!running)
            statusL->setText("Stopped");
    }
    int mode = 0;
    bool lastRunning = false;
    brls::Label *modeVal, *statusL, *rowA, *rowB, *rowC;
    brls::Button *toggle;
    brls::Box *bar;
    brls::Rectangle *barFill, *barTrack;
};

class CpuStressTab : public brls::Box {
    public:
    CpuStressTab() {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        auto *modeRow = new brls::Box(brls::Axis::ROW);
        modeRow->setMarginBottom(10.0f);
        auto *ml = new brls::Label();
        ml->setText("Mode");
        ml->setGrow(1.0f);
        modeRow->addView(ml);
        modeVal = new brls::Label();
        modeVal->setText(modeName(mode));
        modeRow->addView(modeVal);
        this->addView(modeRow);

        auto *hint = new brls::Label();
        hint->setText("Use L / R to select the CPU stress mode. Press A to run.");
        hint->setFontSize(15.0f);
        hint->setTextColor(nvgRGB(150, 150, 150));
        hint->setMarginBottom(14.0f);
        this->addView(hint);

        toggle = new brls::Button();
        toggle->setText("Start");
        toggle->registerClickAction([this](brls::View *) {
            onToggle();
            return true;
        });
        this->addView(toggle);

        this->registerAction("Prev mode", brls::ControllerButton::BUTTON_LB, [this](brls::View *) {
            cycle(-1);
            return true;
        });
        this->registerAction("Next mode", brls::ControllerButton::BUTTON_RB, [this](brls::View *) {
            cycle(1);
            return true;
        });

        statusL = new brls::Label();
        statusL->setText("Stopped");
        statusL->setMarginTop(10.0f);
        statusL->setMarginBottom(8.0f);
        this->addView(statusL);

        auto *h = new brls::Header();
        h->setTitle("Info");
        this->addView(h);
        rowA = makeRow(this, "Iterations");
        rowB = makeRow(this, "Mismatches");
        rowC = makeRow(this, "Threads");
    }

    ~CpuStressTab() override {
        if (cpu_stress_running())
            cpu_stress_stop();
    }

    void willDisappear(bool resetState = false) override {
        if (cpu_stress_running())
            cpu_stress_stop();
        brls::Box::willDisappear(resetState);
    }

    void frame(brls::FrameContext *ctx) override {
        cpu_stress_status_t s;
        cpu_stress_get(&s);
        if (cpu_stress_running() || lastRunning) {
            rowA->setText(fstru("%llu", (unsigned long long)s.iters));
            rowB->setText(fstru("%llu", (unsigned long long)s.mismatches));
            rowC->setText(fstru("%llu", (unsigned long long)s.threads));
            if (cpu_stress_running())
                statusL->setText(s.mismatches ? "Errors found! Running..." : "Running...");
        }
        syncToggle(cpu_stress_running() != 0);
        brls::Box::frame(ctx);
    }

    private:
    static const char *modeName(int m) {
        switch (m) {
            case 0: return "Matrixprod";
            case 1: return "Hanoi (verify)";
        }
        return "";
    }
    void cycle(int dir) {
        if (cpu_stress_running())
            return;
        mode = (mode + dir + 2) % 2;
        modeVal->setText(modeName(mode));
    }
    void onToggle() {
        if (cpu_stress_running())
            cpu_stress_stop();
        else {
            rowA->setText("-");
            rowB->setText("-");
            rowC->setText("-");
            cpu_stress_start(mode);
        }
    }
    void syncToggle(bool running) {
        if (running == lastRunning)
            return;
        lastRunning = running;
        toggle->setText(running ? "Stop" : "Start");
        if (!running)
            statusL->setText("Stopped");
    }
    int mode = 0;
    bool lastRunning = false;
    brls::Label *modeVal, *statusL, *rowA, *rowB, *rowC;
    brls::Button *toggle;
};

class CreditsTab : public brls::Box {
    public:
    CreditsTab() {
        this->setAxis(brls::Axis::COLUMN);
        this->setGrow(1.0f);
        this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);

        auto *title = new brls::Label();
        title->setText("Benchmark Toolbox");
        title->setFontSize(26.0f);
        this->addView(title);

        auto *by = new brls::Label();
        by->setText("by Souldbminer and Lightos_, licensed under the GPLv2");
        by->setFontSize(16.0f);
        by->setTextColor(nvgRGB(150, 150, 150));
        this->addView(by);

        auto *h = new brls::Header();
        h->setTitle("Credits");
        this->addView(h);

        makeRow(this, "Memtester")->setText("Simon Kirby, Charles Cazabon, KazushiMe & CTCaer");
        makeRow(this, "FurMark")->setText("StanislavPetrovV & AnxietyTimmy");
        makeRow(this, "GPU Test")->setText("NaGaa95");
        makeRow(this, "Membench")->setText("Siarhei Siamashka, KazushiMe & Lineon");
        makeRow(this, "Stress-ng")->setText("ColinIanKing & Lineon");

        auto *note = new brls::Label();
        note->setText("Thanks to all the original authors.");
        note->setFontSize(15.0f);
        note->setTextColor(nvgRGB(150, 150, 150));
        note->setMarginTop(18.0f);
        this->addView(note);
    }
};

class AppFrame : public brls::TabFrame {
    public:
    AppFrame() {
        box = dynamic_cast<brls::Box *>(this->getView("brls/applet_frame/header_stats"));
        if (box) {
            box->setJustifyContent(brls::JustifyContent::FLEX_END);
            box->setAlignItems(brls::AlignItems::CENTER);
            const float fs = 13.0f;
            for (int i = 0; i < 3; i++) {
                grp[i].load = statCell(box, fs);
                if (i)
                    grp[i].load->setMarginLeft(18.0f);
                statSep(box, fs);
                grp[i].clock = statCell(box, fs);
                statSep(box, fs);
                grp[i].temp = statCell(box, fs);
            }
        }
        hocclk_init();
        update();
    }

    void frame(brls::FrameContext *ctx) override {
        if (box && ++tick >= 12) {
            tick = 0;
            update();
        }
        brls::TabFrame::frame(ctx);
    }

    private:
    void setGrp(StatCells &g, const char *name, unsigned loadOrBw, bool isRam, uint32_t hz, int32_t mc) {
        char b[48];
        if (isRam)
            std::snprintf(b, sizeof b, "%s %u.%u GB/s", name, loadOrBw / 1000u, (loadOrBw % 1000u) / 100u);
        else
            std::snprintf(b, sizeof b, "%s %u%%", name, loadOrBw);
        g.load->setText(b);
        fmtClock1(g.clock, hz);
        fmtTemp(g.temp, mc);
    }
    void update() {
        if (!box)
            return;
        HocClkContext c;
        if (!hocclk_get(&c)) {
            grp[0].load->setText("hoc:clk N/A");
            grp[0].clock->setText("-");
            grp[0].temp->setText("-");
            grp[1].load->setText("-");
            grp[1].clock->setText("-");
            grp[1].temp->setText("-");
            grp[2].load->setText("-");
            grp[2].clock->setText("-");
            grp[2].temp->setText("-");
            return;
        }
        setGrp(grp[0], "CPU", c.stable.partLoad[3] / 10, false, c.stable.realFreqs[0], c.stable.temps[5]);
        setGrp(grp[1], "GPU", c.stable.partLoad[2] / 10, false, c.stable.realFreqs[1], c.stable.temps[6]);
        setGrp(grp[2], "RAM", c.stable.partLoad[6], true, c.stable.realFreqs[2], c.stable.temps[7]);
    }
    brls::Box *box = nullptr;
    StatCells grp[3];
    int tick = 0;
};

class MainActivity : public brls::Activity {
    public:
    brls::View *createContentView() override {
        auto *tab = new AppFrame();
        tab->setTitle("Benchmark Toolbox");
        tab->setIconFromRes("img/logo.png");
        tab->addTab("System Info", [] { return new SysInfoTab(); });

        tab->addHeader("Combined");
        tab->addTab("Black Hole", [] { return new FurmarkTab(3, "CPU+GPU black-hole."); });

        tab->addHeader("CPU");
        tab->addTab("CPU Stress", [] { return new CpuStressTab(); });
        tab->addTab("CPU Ray Trace", [] { return new FurmarkTab(4, "CPU Path Tracer"); });

        tab->addHeader("GPU");
        tab->addTab("GPU Test", [] { return new StressTab(); });
        tab->addTab("Furmark", [] { return new FurmarkTab(0, "FurMark for Switch (48 step)"); });
        tab->addTab("GPU Path Trace", [] { return new FurmarkTab(2, "GPU Path Tracer"); });

        tab->addHeader("RAM");
        tab->addTab("Memtester", [] { return new MemtesterTab(); });
        tab->addTab("Membench", [] { return new BenchTab(); });
        tab->addTab("Furmark RAM", [] { return new FurmarkTab(1, "FurMark with extra ram stress"); });
        tab->addTab("CPU RAM", [] { return new FurmarkTab(5, "CPU RT with extra RAM stress"); });

        tab->addSeparator();
        tab->addTab("Credits", [] { return new CreditsTab(); });
        return tab;
    }
};

int main(int argc, char *argv[]) {
    (void)argc;
    (void)argv;

    brls::Logger::setLogLevel(brls::LogLevel::INFO);

    if (!brls::Application::init()) {
        brls::Logger::error("Unable to init borealis application");
        return EXIT_FAILURE;
    }

    brls::Application::createWindow("Benchmark Toolbox");
    brls::Application::setGlobalQuit(true);
    brls::Application::pushActivity(new MainActivity());

    while (brls::Application::mainLoop())
        ;

    // Global quit (+) can return from the main loop without destroying the
    // activity, leaving background workers (and their threads / large RAM
    // allocations) live across _exit. Tear them down first.
    if (run_furmark_running())
        run_furmark_stop();
    if (mt_gpu_running())
        mt_gpu_stop();
    if (mt_cpu_running())
        mt_cpu_stop();
    if (cpu_stress_running())
        cpu_stress_stop();

    _exit(EXIT_SUCCESS);
}
