// schema-validate -- KEKS data structures validator
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include <keks/atom.h>
#include <keks/err.h>
#include <keks/items.h>
#include <keks/schema.h>
#include <string.h>

#include "../lib/mmap.h"
#include "../lib/stdinread.h"

static bool
parse(struct KEKSItems *items, const unsigned char *buf, const size_t len)
{
    const ptrdiff_t itemsInitialLen = 2048;
    enum KEKSErr err = KEKSErrInvalid;
    size_t off = 0;
RetryAfterMagic:
    err = KEKSItemsInit(items, itemsInitialLen);
    if (err != KEKSErrNo) {
        fprintf(stderr, "err: %s\n", KEKSErr2Str(err));
        return false;
    }
    err = KEKSItemsParse(items, &off, buf, len);
    if (err != KEKSErrNo) {
        fprintf(stderr, "err: %s\n", KEKSErr2Str(err));
        KEKSItemsFree(items);
        return false;
    }
    struct KEKSItem *item = &(items->list[0]);
    if (item->atom.typ == KEKSItemMagic) {
        KEKSItemsFree(items);
        goto RetryAfterMagic;
    }
    return true;
}

int
main(int argc, char **argv)
{
    if (argc < 3) {
        fputs("Usage: schema-validate SCHEMA.keks SCHEMA-NAME <DATA.keks\n", stderr);
        return EXIT_FAILURE;
    }

    size_t schemaLen = 0;
    unsigned char *schemaBuf = NULL;
    if (!Mmap(&schemaBuf, &schemaLen, argv[1])) {
        return EXIT_FAILURE;
    }
    struct KEKSItems schema;
    if (!parse(&schema, schemaBuf, schemaLen)) {
        return EXIT_FAILURE;
    }

    size_t dataLen = 0;
    size_t dataCap = (size_t)1 << (size_t)10;
    unsigned char *dataBuf = NULL;
    if (!stdinread(&dataBuf, &dataLen, &dataCap)) {
        fprintf(stderr, "stdinread(): %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    struct KEKSItems data;
    if (!parse(&data, dataBuf, dataLen)) {
        return EXIT_FAILURE;
    }

    size_t idxSchema = KEKSItemsGetByKey(&schema, 0, argv[2]);
    if (idxSchema == 0) {
        fputs("can not find specified schema name\n", stderr);
        return EXIT_FAILURE;
    }
    struct KEKSSchemaErr err = KEKSSchemaValidate(&schema, &data, idxSchema, 0);
    if (err.code != KEKSSchemaErrNo) {
        switch (err.code) {
        case KEKSSchemaErrInvalidSchema:
            fputs("invalid schema: ", stderr);
            break;
        case KEKSSchemaErrUnexpectedState:
            fputs("unexpected state: ", stderr);
            break;
        case KEKSSchemaErrInvalidData:
            fputs("invalid data: ", stderr);
            break;
        case KEKSSchemaErrInvalid:
        case KEKSSchemaErrNo:
        default:
            break;
        }
        fprintf(
            stderr, "schema:%zu data:%zu: %s\n", err.offSchema, err.offData, err.msg);
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
