#include <ctype.h>
#include <stdio.h>
#include <string.h>

#include "bio_internal.h"

#define BIO_LINE_MAX 2048u
#define BIO_OPCODE_TOKEN_MAX 64u

static char *skip_ws(char *p) {
    while (*p != '\0' && isspace((unsigned char)*p)) {
        ++p;
    }
    return p;
}

static void strip_comment(char *line) {
    int in_quote = 0;
    for (char *p = line; *p != '\0'; ++p) {
        if (*p == '"') {
            in_quote = !in_quote;
            continue;
        }
        if (!in_quote && *p == '#') {
            *p = '\0';
            return;
        }
    }
}

static void trim_right(char *s) {
    size_t n = strlen(s);
    while (n > 0u && isspace((unsigned char)s[n - 1u])) {
        s[n - 1u] = '\0';
        --n;
    }
}

static int copy_token(char **cursor, char *out, size_t out_cap) {
    char *p = skip_ws(*cursor);
    size_t n = 0;
    if (*p == '\0') {
        return 0;
    }
    while (*p != '\0' && !isspace((unsigned char)*p)) {
        if (n + 1u >= out_cap) {
            return -1;
        }
        out[n++] = *p++;
    }
    out[n] = '\0';
    *cursor = p;
    return 1;
}

static int copy_operand_value(char **cursor, char *out, size_t out_cap) {
    char *p = *cursor;
    size_t n = 0;
    if (*p == '"') {
        ++p;
        while (*p != '\0' && *p != '"') {
            if (n + 1u >= out_cap) {
                return -1;
            }
            out[n++] = *p++;
        }
        if (*p != '"') {
            return -1;
        }
        ++p;
    } else {
        while (*p != '\0' && !isspace((unsigned char)*p)) {
            if (n + 1u >= out_cap) {
                return -1;
            }
            out[n++] = *p++;
        }
    }
    out[n] = '\0';
    *cursor = p;
    return 1;
}

static bio_v1_status parse_operands(char *cursor, uint32_t line_no,
                                    bio_operands *ops) {
    memset(ops, 0, sizeof(*ops));
    ops->line = line_no;

    while (1) {
        char *key_start = skip_ws(cursor);
        char *eq = NULL;
        size_t key_len = 0;
        if (*key_start == '\0') {
            return BIO_OK;
        }
        if (ops->count >= BIO_MAX_OPERANDS) {
            return BIO_ERR_OPERAND;
        }

        eq = strchr(key_start, '=');
        if (eq == NULL) {
            return BIO_ERR_OPERAND;
        }
        key_len = (size_t)(eq - key_start);
        if (key_len == 0u || key_len >= BIO_MAX_KEY_BYTES) {
            return BIO_ERR_OPERAND;
        }
        for (size_t i = 0; i < key_len; ++i) {
            if (isspace((unsigned char)key_start[i])) {
                return BIO_ERR_OPERAND;
            }
            ops->pairs[ops->count].key[i] = key_start[i];
        }
        ops->pairs[ops->count].key[key_len] = '\0';

        cursor = eq + 1;
        if (copy_operand_value(&cursor, ops->pairs[ops->count].value,
                               BIO_MAX_VALUE_BYTES) <= 0) {
            return BIO_ERR_OPERAND;
        }
        ops->count += 1u;
    }
}

bio_v1_status bio_run_file_with_context(bio_context *ctx, const char *path) {
    FILE *f = NULL;
    char line[BIO_LINE_MAX];
    uint32_t line_no = 0;

    if (ctx == NULL || path == NULL || *path == '\0') {
        return BIO_ERR_ARGS;
    }

    f = fopen(path, "rb");
    if (f == NULL) {
        return BIO_ERR_IO;
    }

    while (fgets(line, sizeof(line), f) != NULL) {
        char opcode_token[BIO_OPCODE_TOKEN_MAX];
        bio_opcode opcode = BIO_OPCODE_COUNT;
        bio_operands ops;
        char *cursor = line;
        int token_rc = 0;
        bio_v1_status rc = BIO_OK;

        ++line_no;
        strip_comment(line);
        trim_right(line);
        cursor = skip_ws(line);
        if (*cursor == '\0') {
            continue;
        }

        token_rc = copy_token(&cursor, opcode_token, sizeof(opcode_token));
        if (token_rc <= 0) {
            fclose(f);
            return BIO_ERR_PARSE;
        }
        if (!bio_opcode_from_token(opcode_token, &opcode)) {
            fprintf(stderr, "bio: parse error line=%u unknown_opcode=%s\n", line_no,
                    opcode_token);
            fclose(f);
            return BIO_ERR_OPCODE;
        }

        rc = parse_operands(cursor, line_no, &ops);
        if (rc != BIO_OK) {
            fprintf(stderr, "bio: parse error line=%u opcode=%s operands\n", line_no,
                    opcode_token);
            fclose(f);
            return rc;
        }

        rc = g_bio_dispatch[(unsigned)opcode].fn(ctx, &ops);
        if (rc != BIO_OK) {
            fclose(f);
            return rc;
        }
        if (ctx->halted) {
            break;
        }
    }

    bio_context_close(ctx);
    fclose(f);
    return BIO_OK;
}
