#include "lexer.h"

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

struct lexer {
    const char *current;    // el siguiente carácter por leer
    int line;
    int column;
};

Lexer *lexer_new(const char *source) {
    Lexer *lexer = malloc(sizeof *lexer);
    if (lexer == NULL) {
        return NULL;
    }
    lexer->current = source;
    lexer->line = 1;
    lexer->column = 1;
    return lexer;
}

void lexer_free(Lexer *lexer) {
    free(lexer);
}

// Consume un carácter y lleva la cuenta de la línea y la columna.
static void advance(Lexer *lexer) {
    if (*lexer->current == '\n') {
        lexer->line++;
        lexer->column = 1;
    } else {
        lexer->column++;
    }
    lexer->current++;
}

// Salta lo que separa a los tokens.
// Laboratorio, parte 1: aquí también se saltan los comentarios.
static void skip_blanks(Lexer *lexer) {
    while (isspace((unsigned char) *lexer->current)) {
        advance(lexer);
    }
}

static int starts_name(char c) {
    return isalpha((unsigned char) c) || c == '_';
}

static int continues_name(char c) {
    return isalnum((unsigned char) c) || c == '_';
}

static int is_digit(char c) {
    return isdigit((unsigned char) c);
}

// Un token que ya se reconoció: guarda su tipo y su texto.
static Token finish(Token token, TokenKind kind, const char *text) {
    token.kind = kind;
    snprintf(token.text, sizeof token.text, "%s", text);
    return token;
}

// Lee desde start hasta el lexer actual y lo copia a token->text.
// Devuelve 0 si no cabe. Mide primero: no se escribe nada fuera del arreglo.
static int copy_text(Token *token, const char *start, const Lexer *lexer) {
    size_t length = (size_t) (lexer->current - start);
    if (length >= sizeof token->text) {
        return 0;
    }
    memcpy(token->text, start, length);
    token->text[length] = '\0';
    return 1;
}

// NUMBER: dígitos y una parte decimal opcional. El punto solo entra si le sigue un dígito.
static Token read_number(Lexer *lexer, Token token) {
    const char *start = lexer->current;
    while (is_digit(*lexer->current)) {
        advance(lexer);
    }
    if (lexer->current[0] == '.' && is_digit(lexer->current[1])) {
        advance(lexer);
        while (is_digit(*lexer->current)) {
            advance(lexer);
        }
    }
    if (!copy_text(&token, start, lexer)) {
        return finish(token, TOKEN_ERROR, "número demasiado largo");
    }
    token.kind = TOKEN_NUMBER;
    token.number = strtod(token.text, NULL);
    return token;
}

// Una palabra reservada, o TOKEN_NAME si no es ninguna.
static TokenKind keyword_or_name(const char *text) {
    static const struct {
        char word[8];
        TokenKind kind;
    } keywords[] = {
        {"let", TOKEN_LET}, {"print", TOKEN_PRINT}, {"def", TOKEN_DEF},
        {"if", TOKEN_IF}, {"then", TOKEN_THEN}, {"else", TOKEN_ELSE},
    };
    size_t count = sizeof keywords / sizeof keywords[0];
    for (size_t i = 0; i < count; i++) {
        if (strcmp(text, keywords[i].word) == 0) {
            return keywords[i].kind;
        }
    }
    return TOKEN_NAME;
}

// NAME: letra o guion bajo, y luego letras, dígitos o guiones bajos.
static Token read_name(Lexer *lexer, Token token) {
    const char *start = lexer->current;
    while (continues_name(*lexer->current)) {
        advance(lexer);
    }
    if (!copy_text(&token, start, lexer)) {
        return finish(token, TOKEN_ERROR, "nombre demasiado largo");
    }
    token.kind = keyword_or_name(token.text);
    return token;
}

// Un carácter que no empieza ningún token. Lo consume, para que se pueda seguir.
static Token unexpected(Lexer *lexer, Token token) {
    unsigned char c = (unsigned char) *lexer->current;
    advance(lexer);
    token.kind = TOKEN_ERROR;
    if (isprint(c)) {
        snprintf(token.text, sizeof token.text, "carácter inesperado '%c'", c);
    } else {
        snprintf(token.text, sizeof token.text, "carácter inesperado (byte %d)", c);
    }
    return token;
}

Token lexer_next(Lexer *lexer) {
    skip_blanks(lexer);

    Token token = {.kind = TOKEN_END, .number = 0.0, .text = "", .line = lexer->line, .column = lexer->column};
    char c = *lexer->current;
    if (c == '\0') {
        return token;
    }
    if (is_digit(c)) {
        return read_number(lexer, token);
    }
    if (starts_name(c)) {
        return read_name(lexer, token);
    }

    // Laboratorio, parte 1: aquí faltan los comparadores.
    switch (c) {
        case '+': advance(lexer); return finish(token, TOKEN_PLUS, "+");
        case '-': advance(lexer); return finish(token, TOKEN_MINUS, "-");
        case '*': advance(lexer); return finish(token, TOKEN_STAR, "*");
        case '/': advance(lexer); return finish(token, TOKEN_SLASH, "/");
        case '(': advance(lexer); return finish(token, TOKEN_LPAREN, "(");
        case ')': advance(lexer); return finish(token, TOKEN_RPAREN, ")");
        case ',': advance(lexer); return finish(token, TOKEN_COMMA, ",");
        case '=': advance(lexer); return finish(token, TOKEN_ASSIGN, "=");
        default: return unexpected(lexer, token);
    }
}
