#include "memory.h"
#include "common/io.h"

const char* ffDetectMemory(FFMemoryResult* ram) {
    char buf[PROC_FILE_BUFFSIZ];
    ssize_t nRead = ffReadFileData("/proc/meminfo", ARRAY_SIZE(buf) - 1, buf);
    if (nRead < 0) {
        return "ffReadFileData(\"/proc/meminfo\", ARRAY_SIZE(buf)-1, buf)";
    }
    buf[nRead] = '\0';

    uint64_t memTotal = 0,
             memAvailable = 0,
             shmem = 0,
             memFree = 0,
             buffers = 0,
             cached = 0,
             sReclaimable = 0;

    char* token = nullptr;
    if ((token = memmem(buf, (size_t) nRead, "MemTotal:", strlen("MemTotal:"))) != nullptr) {
        memTotal = (uint64_t) strtoull(token + strlen("MemTotal:"), nullptr, 10);
    } else {
        return "MemTotal not found in /proc/meminfo";
    }

    if ((token = memmem(buf, (size_t) nRead, "MemAvailable:", strlen("MemAvailable:"))) != nullptr) {
        memAvailable = (uint64_t) strtoull(token + strlen("MemAvailable:"), nullptr, 10);
    }

    if (memAvailable == 0 || memAvailable >= memTotal) { // MemAvailable can be unreasonable. #1988
        if ((token = memmem(buf, (size_t) nRead, "MemFree:", strlen("MemFree:"))) != nullptr) {
            memFree = (uint64_t) strtoull(token + strlen("MemFree:"), nullptr, 10);
        }

        if ((token = memmem(buf, (size_t) nRead, "Buffers:", strlen("Buffers:"))) != nullptr) {
            buffers = (uint64_t) strtoull(token + strlen("Buffers:"), nullptr, 10);
        }

        if ((token = memmem(buf, (size_t) nRead, "Cached:", strlen("Cached:"))) != nullptr) {
            cached = (uint64_t) strtoull(token + strlen("Cached:"), nullptr, 10);
        }

        if ((token = memmem(buf, (size_t) nRead, "Shmem:", strlen("Shmem:"))) != nullptr) {
            shmem = (uint64_t) strtoull(token + strlen("Shmem:"), nullptr, 10);
        }

        if ((token = memmem(buf, (size_t) nRead, "SReclaimable:", strlen("SReclaimable:"))) != nullptr) {
            sReclaimable = (uint64_t) strtoull(token + strlen("SReclaimable:"), nullptr, 10);
        }

        memAvailable = memFree + buffers + cached + sReclaimable - shmem;
    }

    ram->bytesTotal = memTotal * 1024lu;
    ram->bytesUsed = (memTotal - memAvailable) * 1024lu;

    uint64_t arcSize = 0;
    nRead = ffReadFileData("/proc/spl/kstat/zfs/arcstats", ARRAY_SIZE(buf) - 1, buf);
    if (nRead > 0) {
        buf[nRead] = '\0';
        // 24 1 0x01 148 40256 2053603602 851614020716
        // name                            type data
        // hits                            4    752

        // Skip first line
        const char* p = memchr(buf, '\n', (size_t) nRead);
        if (__builtin_expect(p != nullptr, true)) {
            ++p;
            nRead -= (p - buf);
            assert(nRead > 0);

            // Find line offset of data
            const char* pData = memmem(p, (size_t) nRead, "data\n", strlen("data\n"));
            if (__builtin_expect(pData != nullptr, true)) {
                uint32_t dataOffset = (uint32_t) (pData - p);
                p += dataOffset + strlen("data\n");
                nRead -= (ssize_t) (dataOffset + strlen("data\n"));
                assert(nRead > 0);

                if ((token = memmem(p, (size_t) nRead, "\nsize ", strlen("\nsize "))) != nullptr) {
                    arcSize = (uint64_t) strtoull(token + 1 + dataOffset, nullptr, 10);
                    if (arcSize > 0) {
                        uint64_t arcMin = 0;
                        if ((token = memmem(p, (size_t) nRead, "\nc_min ", strlen("\nc_min "))) != nullptr) {
                            arcMin = (uint64_t) strtoull(token + 1 + dataOffset, nullptr, 10);
                            if (arcSize > arcMin) {
                                arcSize -= arcMin;
                                if (ram->bytesUsed > arcSize) {
                                    ram->bytesUsed -= arcSize;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return nullptr;
}
