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

// Definiciones de tipos para interoperabilidad con LLVM
typedef double centella_float;
typedef char *centella_string;

#define MAX_LINE_LENGTH 4096
#define MAX_TOKEN_LENGTH 256

// Estructura para mantener el estado de un archivo abierto
typedef struct {
  FILE *fp;
  char buffer[MAX_LINE_LENGTH];
  char *current_ptr;
} CentellaFile;

// Abrir archivo de forma robusta
void *centella_open(char *filename, char *mode) {
  FILE *fp = fopen(filename, mode);
  if (!fp) {
    fprintf(stderr, "Error: No se pudo abrir el archivo '%s'\n", filename);
    exit(1);
  }
  CentellaFile *cf = (CentellaFile *)malloc(sizeof(CentellaFile));
  cf->fp = fp;
  cf->current_ptr = NULL; // Indica que necesitamos leer una nueva línea
  return (void *)cf;
}

// Cerrar archivo
void centella_close(void *file) {
  if (file) {
    CentellaFile *cf = (CentellaFile *)file;
    fclose(cf->fp);
    free(cf);
  }
}

// Comparación de cadenas (retorna 1.0 si son iguales, 0.0 si no)
double centella_str_eq(char *a, char *b) {
  if (!a || !b)
    return 0.0;
  return strcmp(a, b) == 0 ? 1.0 : 0.0;
}

// Comparación de cadenas (retorna 1.0 si son diferentes, 0.0 si no)
double centella_str_ne(char *a, char *b) {
  if (!a || !b)
    return 1.0; // Si uno es NULL, son diferentes (asumiendo que no ambos son
                // NULL)
  return strcmp(a, b) != 0 ? 1.0 : 0.0;
}

// Función auxiliar para verificar si un caracter es delimitador
int is_delimiter(char c) {
  return c == ',' || c == ';' || c == '\t' || c == '\n' || c == '\r';
}

// Leer el siguiente token del archivo
// Retorna 1 si leyó algo, 0 si fin de archivo/línea
int read_token(CentellaFile *cf, char *output_buffer, int max_len) {
  if (!cf->current_ptr || *cf->current_ptr == '\0' ||
      *cf->current_ptr == '\n') {
    // Leer nueva línea
    if (!fgets(cf->buffer, MAX_LINE_LENGTH, cf->fp)) {
      return 0; // EOF
    }
    cf->current_ptr = cf->buffer;
  }

  char *p = cf->current_ptr;

  // Saltar espacios iniciales pero NO delimitadores (una coma seguida de otra
  // coma es un campo vacío)
  while (*p && isspace(*p) && !is_delimiter(*p))
    p++;

  if (*p == '\0' || *p == '\n' || *p == '\r') {
    cf->current_ptr = p;
    return 0; // Fin de linea física
  }

  int i = 0;
  int in_quotes = 0;

  if (*p == '"') {
    in_quotes = 1;
    p++; // Saltar comilla inicial
  }

  while (*p && i < max_len - 1) {
    if (in_quotes) {
      if (*p == '"') {
        if (*(p + 1) == '"') {
          // Doble comilla escapada ("") -> "
          output_buffer[i++] = '"';
          p += 2;
        } else {
          // Fin de comillas
          in_quotes = 0;
          p++;
          break;
        }
      } else {
        output_buffer[i++] = *p++;
      }
    } else {
      if (is_delimiter(*p)) {
        break; // Fin del token
      }
      output_buffer[i++] = *p++;
    }
  }

  output_buffer[i] = '\0';

  // Consumir el delimitador si existe
  if (*p && is_delimiter(*p)) {
    // Excepto si es nueva línea, eso lo dejamos par la próxima llamada para
    // indicar fin de registro
    if (*p != '\n' && *p != '\r') {
      p++;
    }
  }

  cf->current_ptr = p;
  return 1;
}

// Función principal para leer una fila de datos en variables
// Formato: "%s %lf %s" etc.
// Argumentos variables: punteros a las variables donde guardar los datos.
// Retorna 1 si leyó exitosamente la fila completa, 0 si EOF.
int centella_read_row(void *file, char *fmt, ...) {
  CentellaFile *cf = (CentellaFile *)file;

  // Verificar si estamos al final del archivo antes de intentar leer
  if (feof(cf->fp) && (!cf->current_ptr || *cf->current_ptr == '\0'))
    return 0;

  va_list args;
  va_start(args, fmt);

  char token[MAX_TOKEN_LENGTH];
  int fields_read = 0;
  int expected_fields = strlen(
      fmt); // Asumimos que fmt es simplificado "snn..." (s=string, n=number)

  // Bucle para leer los campos esperados
  for (int i = 0; i < expected_fields; i++) {
    // Intentar leer token
    int has_token = read_token(cf, token, MAX_TOKEN_LENGTH);

    // Si no hay token (ej. linea vacia o incompleta), pero esperabamos mas, es
    // un problema Pero si es la primera llamada y da EOF, simplemente
    // retornamos 0
    if (!has_token && i == 0) {
      va_end(args);
      return 0;
    }

    char type = fmt[i];
    if (type == 'n') { // Number (double)
      double *d_ptr = va_arg(args, double *);
      if (has_token && strlen(token) > 0) {
        *d_ptr = atof(token);
      } else {
        *d_ptr = 0.0;
      }
    } else if (type == 's') { // String
      char *s_buffer = va_arg(args, char *);
      // IMPORTANTE: En LLVM allocamos buffers fijos para strings.
      // Asumimos que el buffer destino tiene tamaño suficiente.
      if (has_token) {
        strncpy(s_buffer, token, MAX_TOKEN_LENGTH - 1);
        s_buffer[MAX_TOKEN_LENGTH - 1] = '\0';
      } else {
        s_buffer[0] = '\0';
      }
    }
    fields_read++;
  }

  va_end(args);

  // Forzar avance de línea si quedan cosas (ej. comentarios o columnas extra)
  // Simplemente invalidamos el puntero actual para que read_token lea la sig
  // linea la proxima vez
  if (cf->current_ptr) {
    while (*cf->current_ptr && *cf->current_ptr != '\n')
      cf->current_ptr++;
  }

  return fields_read == expected_fields;
}

// Escribir en archivo (wrapper de vfprintf)
void centella_write(void *file, char *fmt, ...) {
  if (!file)
    return;
  CentellaFile *cf = (CentellaFile *)file;
  va_list args;
  va_start(args, fmt);
  vfprintf(cf->fp, fmt, args);
  va_end(args);
}

// Verifica si 'haystack' contiene 'needle'
double centella_str_contains(char *haystack, char *needle) {
  if (!haystack || !needle)
    return 0.0;
  return strstr(haystack, needle) != NULL ? 1.0 : 0.0;
}

// Verifica si 'str' empieza con 'prefix'
double centella_str_starts_with(char *str, char *prefix) {
  if (!str || !prefix)
    return 0.0;
  size_t len_pre = strlen(prefix);
  size_t len_str = strlen(str);
  if (len_str < len_pre)
    return 0.0;
  return strncmp(str, prefix, len_pre) == 0 ? 1.0 : 0.0;
}
