/* Copyright (c) RISC OS Developments 2026. All rights reserved.
 * SPDX-License-Identifier: CDDL-1.0
 * BenchIO: FileSwitch read/write benchmark for a mounted LanMan98 directory.
 */
#include <kernel.h>
#include <swis.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BLOCK_SIZE 1024
#define DEFAULT_MIB 16
#define MAX_MIB 128

static int monotonic_time(unsigned int *result)
{
    _kernel_swi_regs r;
    _kernel_oserror *error;

    error = _kernel_swi(OS_ReadMonotonicTime, &r, &r);
    if(error)
    {
        fprintf(stderr, "Timer: %s\n", error->errmess);
        return 0;
    }
    *result = (unsigned int) r.r[0];
    return 1;
}

static int open_file(const char *path, int reason)
{
    _kernel_swi_regs r;
    _kernel_oserror *error;

    r.r[0] = reason;
    r.r[1] = (int) path;
    error = _kernel_swi(OS_Find, &r, &r);
    if(error)
    {
        fprintf(stderr, "Open: %s\n", error->errmess);
        return 0;
    }
    return r.r[0];
}

static int close_file(int handle)
{
    _kernel_swi_regs r;
    _kernel_oserror *error;

    r.r[0] = 0;
    r.r[1] = handle;
    error = _kernel_swi(OS_Find, &r, &r);
    if(error)
    {
        fprintf(stderr, "Close: %s\n", error->errmess);
        return 0;
    }
    return 1;
}

static int transfer(int reason, int handle, unsigned char *buffer)
{
    _kernel_swi_regs r;
    _kernel_oserror *error;

    r.r[0] = reason;
    r.r[1] = handle;
    r.r[2] = (int) buffer;
    r.r[3] = BLOCK_SIZE;
    error = _kernel_swi(OS_GBPB, &r, &r);
    if(error)
    {
        fprintf(stderr, "%s: %s\n", reason == 2 ? "Write" : "Read",
                error->errmess);
        return 0;
    }
    if(r.r[3] != 0)
    {
        fprintf(stderr, "Short %s: %d bytes missing\n",
                reason == 2 ? "write" : "read", r.r[3]);
        return 0;
    }
    return 1;
}

static int object_exists(const char *path)
{
    _kernel_swi_regs r;
    _kernel_oserror *error;

    r.r[0] = 17;
    r.r[1] = (int) path;
    error = _kernel_swi(OS_File, &r, &r);
    return error ? -1 : r.r[0] != 0;
}

static int remove_file(const char *path)
{
    _kernel_swi_regs r;
    _kernel_oserror *error;

    r.r[0] = 6;
    r.r[1] = (int) path;
    error = _kernel_swi(OS_File, &r, &r);
    if(error)
    {
        fprintf(stderr, "Remove %s: %s\n", path, error->errmess);
        return 0;
    }
    return 1;
}

static void report(const char *label, unsigned int bytes,
                   unsigned int start, unsigned int end)
{
    unsigned int ticks;
    double seconds;
    double mib;

    ticks = end - start;
    seconds = ticks / 100.0;
    mib = bytes / 1048576.0;
    if(ticks == 0)
        printf("%s: under 0.01 s; use a larger file\n", label);
    else
        printf("%s: %.2f MiB in %.2f s = %.2f MiB/s\n",
               label, mib, seconds, mib / seconds);
}

static void tag_block(unsigned char *buffer, unsigned int block)
{
    buffer[0] = (unsigned char) block;
    buffer[1] = (unsigned char) (block >> 8);
    buffer[2] = (unsigned char) (block >> 16);
    buffer[3] = (unsigned char) (block >> 24);
}

int main(int argc, char **argv)
{
    char path[512];
    unsigned char pattern[BLOCK_SIZE], buffer[BLOCK_SIZE];
    unsigned int start, end, bytes, block, blocks, stamp;
    int handle, exists, suffix, size_mib, okay, i;
    char *endptr;
    long parsed;

    if(argc < 2 || argc > 3)
    {
        fprintf(stderr, "Usage: BenchIO <mounted-directory> [MiB: 1-128]\n");
        return 1;
    }
    size_mib = DEFAULT_MIB;
    if(argc == 3)
    {
        parsed = strtol(argv[2], &endptr, 10);
        if(!argv[2][0] || *endptr || parsed < 1 || parsed > MAX_MIB)
        {
            fprintf(stderr, "MiB must be a number from 1 to 128\n");
            return 1;
        }
        size_mib = (int) parsed;
    }
    if(strlen(argv[1]) > sizeof(path) - 24)
    {
        fprintf(stderr, "Directory path is too long\n");
        return 1;
    }
    if(!monotonic_time(&stamp))
        return 1;
    for(suffix = 0; suffix < 100; suffix++)
    {
        sprintf(path, "%s.LM98Bench%08X%02d", argv[1], stamp, suffix);
        exists = object_exists(path);
        if(exists < 0)
        {
            fprintf(stderr, "Cannot inspect target directory\n");
            return 1;
        }
        if(!exists)
            break;
    }
    if(suffix == 100)
    {
        fprintf(stderr, "No free benchmark filename\n");
        return 1;
    }
    for(i = 0; i < BLOCK_SIZE; i++)
        pattern[i] = (unsigned char) (i * 37 + 11);
    bytes = (unsigned int) size_mib * 1048576U;
    blocks = bytes / BLOCK_SIZE;
    printf("FileSwitch benchmark: %u MiB, 1 KiB requests, %s\n",
           (unsigned int) size_mib, argv[1]);
    if(!monotonic_time(&start))
        return 1;
    handle = open_file(path, 0x8F);
    if(!handle)
        return 1;
    okay = 1;
    for(block = 0; block < blocks; block++)
    {
        tag_block(pattern, block);
        if(!transfer(2, handle, pattern))
        {
            okay = 0;
            break;
        }
    }
    if(!close_file(handle))
        okay = 0;
    if(!monotonic_time(&end))
        okay = 0;
    if(!okay)
    {
        remove_file(path);
        return 1;
    }
    report("Write", bytes, start, end);

    if(!monotonic_time(&start))
    {
        remove_file(path);
        return 1;
    }
    handle = open_file(path, 0x4F);
    if(!handle)
    {
        remove_file(path);
        return 1;
    }
    okay = 1;
    for(block = 0; block < blocks; block++)
    {
        if(!transfer(4, handle, buffer))
        {
            okay = 0;
            break;
        }
        tag_block(pattern, block);
        if(memcmp(pattern, buffer, BLOCK_SIZE) != 0)
        {
            fprintf(stderr, "Data mismatch at offset %u\n", block * BLOCK_SIZE);
            okay = 0;
            break;
        }
    }
    if(!close_file(handle))
        okay = 0;
    if(!monotonic_time(&end))
        okay = 0;
    if(okay)
        report("Read (verified)", bytes, start, end);
    if(!remove_file(path))
        okay = 0;
    return okay ? 0 : 1;
}
