/**
 * minishell.c
 * Version: 1.0
 * Fecha: 15/12/2024
 * 
 * Autores:
 * Luis Miguel Herrá Alpuente, Miguel de Sande Moreno,
 * Matías Vásquez Herbozo y Marcos Bonilla de las Morenas
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "ejecutar.h"
#include "entrada_minishell.h"
#include "internas.h"

/**
 * mostrar_bienvenida - Muestra un mensaje de bienvenida con arte ASCII.
 */
void mostrar_bienvenida() {
    printf("Bienvenido a nuestra Minishell\n");
    printf("        _.---._\n");
    printf("    .'''.'/|\\`.'''.\n");
    printf("   :  .' / | \\ `.  :\n");
    printf("   '.'  /  |  \\  `.'\n");
    printf("    `. /   |   \\ .'\n");
    printf("      `-.__|__.-'\n");
    printf("\n");
}

/**
 * manejar_sigchild - Maneja la señal SIGCHLD para evitar procesos zombi.
 *
 * @signo: Número de la señal recibida (no se usa en esta implementación).
 */
static void manejar_sigchild(int signo) {
    (void)signo; // Para evitar warnings por el parámetro no utilizado
    int estado;
    // Recoger el estado de un hijo que ha terminado
    waitpid(-1, &estado, WNOHANG);
}

/**
 * recortar_espacios - Elimina los espacios iniciales y finales de una cadena.
 *
 * @str: Puntero a la cadena de caracteres que será modificada.
 *
 * Retorna: Un puntero a la cadena modificada.
 */
char *recortar_espacios(char *str) {
    // Esta función elimina los espacios iniciales
    while (isspace((unsigned char)*str)) {
        str++;
    }

    // Comprobamos si el string está vacío una vez hayamos
    // eliminados los espacios iniciales
    if (*str == 0) {
        return str;
    }

    // Eliminamos los espacios finales
    char *end = str + strlen(str) - 1;
    while (end > str && isspace((unsigned char)*end)) {
        end--;
    }

    // Colocamos el "terminador nulo" al final del string
    *(end + 1) = '\0';
    return str;
}

/**
 * main - Punto de entrada de la Minishell.
 *
 * @argc: Número de argumentos.
 * @argv: Arreglo de cadenas con los argumentos.
 *
 * Este programa implementa un shell básico que procesa órdenes separadas por
 * punto y coma (;), verifica si son órdenes internas o externas y las ejecuta.
 *
 * Retorna: 0 si se ejecuta correctamente.
 */
int main(int argc, char *argv[]) {

    // Para, más adelante, poder acceder al archivo con el historial
    char ruta_binario[1024] = ""; // Variable para almacenar la ruta

    if (argc < 1) {
        fprintf(stderr, "Error: No se pudo determinar el nombre del ejecutable.\n");
        return 1;
    }

    // Resolver la ruta absoluta del binario y almacenarla en la variable global
    if (realpath(argv[0], ruta_binario) == NULL) {
        perror("Error al resolver la ruta del ejecutable");
        return 1;
    }

    // Para que no se generen warnings por los argumentos no utilizados
    (void)argc;

    char buf[BUFSIZ];
    struct sigaction sa;

    mostrar_bienvenida();

    memset(&sa, 0, sizeof(sa)); // Configuración del manejador para la señal
                                // SIGCHLD, inicializado en 0

    // Asignada la función manejadora de la señal
    sa.sa_handler = manejar_sigchild;
    sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; // No se generan señales cuando un
                                             // hijo para o reanuda su ejecución
    sigaction(SIGCHLD, &sa, NULL); // Instalar el manejador de señales

    while (1) {
        imprimir_prompt();
        leer_linea_ordenes(buf);

        char *token;
        const char delim[] = ";";

        // Dividimos en órdenes usando ';' como delimitador
        token = strtok(buf, delim);
        while (token != NULL) {
            // Limpiamos espacios del token usando la función
            // "recortar_espacios"
            token = recortar_espacios(token);

            // Verificamos si el token está vacío después de eliminar espacios
            if (*token == '\0') {
                token = strtok(NULL, delim);
                continue;
            }

            // Guardamos la orden en el historial
            guardar_historial(token, ruta_binario);

            // Procesamos el comando actual
            if (strcmp(token, "exit") == 0) {
                exit(EXIT_SUCCESS);
            } else if (es_ord_interna(token)) {
                ejecutar_ord_interna(token);
            } else {
                ejecutar_linea_ordenes(token);
            }

            // Obtenemos el siguiente token
            token = strtok(NULL, delim);
        }
    }
}