#include "track_remove_parser.h"
#include <stdio.h>

typedef enum {
    TR_TOK_RANGE,
    TR_TOK_END_OF_FILE,
    TR_TOK_INVALID
} TrackRemoveToken;

typedef struct {
    const char *str;
    size_t size;
    size_t offset;
    Range range;
} TrackRemoveTokenizer;

static int is_number(char c) {
    return c >= '0' && c <= '9';
}

/* Returns non-0 value on overflow */
static int string_to_num_unchecked(const char *str, size_t size, int *number) {
    int result = 0;
    for(size_t i = 0; i < size; ++i) {
        const int result_before = result;
        result *= 10;
        result += (str[i] - '0');
        /* overflow */
        if(result <= result_before)
            return 1;
    }
    *number = result;
    return 0;
}

static void track_remover_tokenizer_init(TrackRemoveTokenizer *self, const char *str, size_t size) {
    self->str = str;
    self->size = size;
    self->offset = 0;
    self->range.start = 0;
    self->range.end = 0;
}

static int track_remover_tokenizer_get_char(TrackRemoveTokenizer *self) {
    if(self->offset < self->size)
        return self->str[self->offset];
    else
        return '\0';
}

static TrackRemoveToken track_remover_tokenizer_next(TrackRemoveTokenizer *self) {
    char c;
    for(;;) {
        c = track_remover_tokenizer_get_char(self);
        if(c != ' ' && c != '\t')
            break;
        ++self->offset;
    }

    if(c >= '1' && c <= '9') {
        size_t number_start = self->offset;
        ++self->offset;

        for(;;) {
            c = track_remover_tokenizer_get_char(self);
            if(!is_number(c))
                break;
            ++self->offset;
        }

        if(string_to_num_unchecked(self->str + number_start, self->offset - number_start, &self->range.start) != 0)
            return TR_TOK_INVALID;
        
        self->range.end = self->range.start;
        if(c != '-')
            return TR_TOK_RANGE;

        ++self->offset;
        c = track_remover_tokenizer_get_char(self);
        if(!is_number(c))
            return TR_TOK_INVALID;

        number_start = self->offset;
        ++self->offset;

        for(;;) {
            c = track_remover_tokenizer_get_char(self);
            if(!is_number(c))
                break;
            ++self->offset;
        }

        if(string_to_num_unchecked(self->str + number_start, self->offset - number_start, &self->range.end) != 0)
            return TR_TOK_INVALID;

        return TR_TOK_RANGE;
    } else if(c == '\0') {
        return TR_TOK_END_OF_FILE;
    } else {
        return TR_TOK_INVALID;
    }
}

int track_remove_parser_parse(const char *str, size_t size, Buffer /*Range*/ *ranges) {
    int num_ranges = 0;
    TrackRemoveTokenizer tokenizer;
    track_remover_tokenizer_init(&tokenizer, str, size);

    for(;;) {
        TrackRemoveToken token = track_remover_tokenizer_next(&tokenizer);
        if(token == TR_TOK_RANGE) {
            if(tokenizer.range.start > tokenizer.range.end) {
                fprintf(stderr, "Invalid number range. The start range can't be past the end range\n");
                return 1;
            }

            buffer_append(ranges, &tokenizer.range, sizeof(tokenizer.range));
            ++num_ranges;
        } else if(token == TR_TOK_END_OF_FILE) {
            break;
        } else {
            fprintf(stderr, "Invalid input. Expected numbers and/or number ranges\n");
            return 1;
        }
    }

    if(num_ranges == 0) {
        fprintf(stderr, "Invalid input. Expected numbers and/or number ranges\n");
        return 1;
    }

    return 0;
}