#include "prism/internal/parser.h"

#include "prism/internal/allocator.h"
#include "prism/internal/comments.h"
#include "prism/internal/diagnostic.h"
#include "prism/internal/encoding.h"
#include "prism/internal/magic_comments.h"

#include <stdlib.h>

/**
 * Register a callback that will be called whenever prism changes the encoding
 * it is using to parse based on the magic comment.
 */
void
pm_parser_encoding_changed_callback_set(pm_parser_t *parser, pm_encoding_changed_callback_t callback) {
    parser->encoding_changed_callback = callback;
}

/**
 * Register a callback that will be called whenever a token is lexed.
 */
void
pm_parser_lex_callback_set(pm_parser_t *parser, pm_lex_callback_t callback, void *data) {
    parser->lex_callback.callback = callback;
    parser->lex_callback.data = data;
}

/**
 * Returns the opaque data that is passed to the lex callback when it is called.
 */
void *
pm_parser_lex_callback_data(const pm_parser_t *parser) {
    return parser->lex_callback.data;
}

/**
 * Returns the raw pointer to the start of the source that is being parsed.
 */
const uint8_t *
pm_parser_start(const pm_parser_t *parser) {
    return parser->start;
}

/**
 * Returns the raw pointer to the end of the source that is being parsed.
 */
const uint8_t *
pm_parser_end(const pm_parser_t *parser) {
    return parser->end;
}

/**
 * Returns the line that the parser was considered to have started on.
 *
 * @param parser the parser whose start line we want to get
 * @return the line that the parser was considered to have started on
 */
int32_t
pm_parser_start_line(const pm_parser_t *parser) {
    return parser->start_line;
}

/**
 * Returns the name of the encoding that is being used to parse the source.
 */
const char *
pm_parser_encoding_name(const pm_parser_t *parser) {
    return parser->encoding->name;
}

/**
 * Returns the width of the character at the given pointer in the encoding that
 * is being used to parse the source.
 */
size_t
pm_parser_encoding_char_width(const pm_parser_t *parser, const uint8_t *start, ptrdiff_t remaining) {
    return parser->encoding->char_width(start, remaining);
}

/**
 * Returns whether or not the parser is using the US-ASCII encoding.
 */
bool
pm_parser_encoding_us_ascii(const pm_parser_t *parser) {
    return parser->encoding == PM_ENCODING_US_ASCII_ENTRY;
}

/**
 * Returns the filepath that is being used to parse the source.
 */
const pm_string_t *
pm_parser_filepath(const pm_parser_t *parser) {
    return &parser->filepath;
}

/**
 * Find a constant in the parser's constant pool. Returns the id of the
 * constant, or 0 if the constant is not found.
 */
pm_constant_id_t
pm_parser_constant_find(const pm_parser_t *parser, const uint8_t *start, size_t length) {
    return pm_constant_pool_find(&parser->constant_pool, start, length);
}

/**
 * Returns the frozen string literal value of the parser.
 */
int8_t
pm_parser_frozen_string_literal(const pm_parser_t *parser) {
    return parser->frozen_string_literal;
}

/**
 * Returns the line offsets that are associated with the given parser.
 *
 * @param parser the parser whose line offsets we want to get
 * @return the line offsets that are associated with the given parser
 */
const pm_line_offset_list_t *
pm_parser_line_offsets(const pm_parser_t *parser) {
    return &parser->line_offsets;
}

/**
 * Returns the location of the __DATA__ section that is associated with the
 * given parser, if it exists.
 */
const pm_location_t *
pm_parser_data_loc(const pm_parser_t *parser) {
    return &parser->data_loc;
}

/**
 * Returns whether the given parser is continuable, meaning that it could become
 * valid if more input were appended, as opposed to being definitively invalid.
 */
bool
pm_parser_continuable(const pm_parser_t *parser) {
    return parser->continuable;
}

/**
 * Returns the lex state of the parser. Note that this is an internal detail,
 * and we are purposefully not returning an instance of the internal enum that
 * we use to track this. This is only exposed because we need it for some very
 * niche use cases. Most consumers should avoid this function.
 */
int
pm_parser_lex_state(const pm_parser_t *parser) {
    return (int) parser->lex_state;
}

/**
 * Returns the location associated with the given comment.
 */
pm_location_t
pm_comment_location(const pm_comment_t *comment) {
    return comment->location;
}

/**
 * Returns the type associated with the given comment.
 */
pm_comment_type_t
pm_comment_type(const pm_comment_t *comment) {
    return comment->type;
}

/**
 * Returns the number of comments associated with the given parser.
 */
size_t
pm_parser_comments_size(const pm_parser_t *parser) {
    return parser->comment_list.size;
}

/**
 * Iterates over the comments associated with the given parser and calls the
 * given callback for each comment.
 */
void
pm_parser_comments_each(const pm_parser_t *parser, pm_comment_callback_t callback, void *data) {
    const pm_list_node_t *current = parser->comment_list.head;
    while (current != NULL) {
        const pm_comment_t *comment = (const pm_comment_t *) current;
        callback(comment, data);
        current = current->next;
    }
}

/**
 * Returns the location associated with the given magic comment key.
 */
pm_location_t
pm_magic_comment_key(const pm_magic_comment_t *magic_comment) {
    return magic_comment->key;
}

/**
 * Returns the location associated with the given magic comment value.
 */
pm_location_t
pm_magic_comment_value(const pm_magic_comment_t *magic_comment) {
    return magic_comment->value;
}

/**
 * Returns the number of magic comments associated with the given parser.
 */
size_t
pm_parser_magic_comments_size(const pm_parser_t *parser) {
    return parser->magic_comment_list.size;
}

/**
 * Iterates over the magic comments associated with the given parser and calls
 * the given callback for each magic comment.
 */
void
pm_parser_magic_comments_each(const pm_parser_t *parser, pm_magic_comment_callback_t callback, void *data) {
    const pm_list_node_t *current = parser->magic_comment_list.head;
    while (current != NULL) {
        const pm_magic_comment_t *magic_comment = (const pm_magic_comment_t *) current;
        callback(magic_comment, data);
        current = current->next;
    }
}

/**
 * Returns the number of errors associated with the given parser.
 */
size_t
pm_parser_errors_size(const pm_parser_t *parser) {
    return parser->error_list.size;
}

/**
 * Returns the number of warnings associated with the given parser.
 */
size_t
pm_parser_warnings_size(const pm_parser_t *parser) {
    return parser->warning_list.size;
}

static inline void
pm_parser_diagnostics_each(const pm_list_t *list, pm_diagnostic_callback_t callback, void *data) {
    const pm_list_node_t *current = list->head;
    while (current != NULL) {
        const pm_diagnostic_t *diagnostic = (const pm_diagnostic_t *) current;
        callback(diagnostic, data);
        current = current->next;
    }
}

/**
 * Iterates over the errors associated with the given parser and calls the
 * given callback for each error.
 */
void
pm_parser_errors_each(const pm_parser_t *parser, pm_diagnostic_callback_t callback, void *data) {
    pm_parser_diagnostics_each(&parser->error_list, callback, data);
}

/**
 * Iterates over the warnings associated with the given parser and calls the
 * given callback for each warning.
 */
void
pm_parser_warnings_each(const pm_parser_t *parser, pm_diagnostic_callback_t callback, void *data) {
    pm_parser_diagnostics_each(&parser->warning_list, callback, data);
}

/**
 * Returns the number of constants in the constant pool associated with the
 * given parser.
 */
size_t
pm_parser_constants_size(const pm_parser_t *parser) {
    return parser->constant_pool.size;
}

/**
 * Iterates over the constants in the constant pool associated with the given
 * parser and calls the given callback for each constant.
 */
void
pm_parser_constants_each(const pm_parser_t *parser, pm_constant_callback_t callback, void *data) {
    for (uint32_t index = 0; index < parser->constant_pool.size; index++) {
        const pm_constant_t *constant = &parser->constant_pool.constants[index];
        callback(constant, data);
    }
}

/**
 * Returns a pointer to the constant at the given id in the constant pool
 * associated with the given parser.
 */
const pm_constant_t *
pm_parser_constant(const pm_parser_t *parser, pm_constant_id_t constant_id) {
    return pm_constant_pool_id_to_constant(&parser->constant_pool, constant_id);
}
