#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#include "ejecutar.h"
#include "libmemoria.h"
#include "parser.h"
#include "redirecciones.h"

/**
 * ejecutar_orden - Ejecuta una órden en un nuevo proceso hijo.
 * 
 * @orden: Cadena que contiene la órden a ejecutar.
 * @entrada: Descriptor de entrada del proceso.
 * @salida: Descriptor de salida del proceso.
 * @backgr: Indicador para ejecutar en segundo plano (1: segundo plano, 0: primer plano).
 * 
 * Retorna: El PID del proceso hijo si se crea exitosamente, -1 en caso de error.
 */
pid_t ejecutar_orden(const char *orden, int entrada, int salida, int *backgr) {
    char **args;
    pid_t pid;
    int ind_entrada = -1, ind_salida = -1;

    // Parsear la orden para obtener los argumentos y los índices de redirección.
    args = parser_orden(orden, &ind_entrada, &ind_salida, backgr);
    if (args == NULL) {
        return -1;
    }

    // Crear un proceso hijo.
    pid = fork();
    if (pid == -1) {
        printf("Error al crear proceso hijo.\n");
        free_argumentos(args);
        return -1;
    }

    if (pid == 0) {
        // En el hijo: Configurar redirección de entrada si aplica.
        if (entrada != STDIN_FILENO) {
            dup2(entrada, STDIN_FILENO);
            close(entrada);
        }
        // Configurar redirección de salida si aplica.
        if (salida != STDOUT_FILENO) {
            dup2(salida, STDOUT_FILENO);
            close(salida);
        }
        // En el hijo: Configurar redirecciones, si es necesario.
        if (ind_entrada != -1) {
            redirec_entrada(args, ind_entrada);
        }
        if (ind_salida != -1) {
            redirec_salida(args, ind_salida);
        }

        // Ejecutar la orden.
        if (execvp(args[0], args) == -1) {
            printf("Error al ejecutar la orden.\n");
            exit(EXIT_FAILURE); // Salir si exec falla.
        }
    }

    // En el padre: Liberar memoria de los argumentos.
    free_argumentos(args);
    return pid;
}

/**
 * ejecutar_linea_ordenes - Ejecuta una línea de órdenes separadas por tuberías.
 * 
 * @orden: Cadena que contiene la línea de órdenes.
 * 
 * Esta función maneja la creación de procesos, redirecciones y sincronización para ejecutar
 * una secuencia de órdenes conectadas mediante tuberías.
 */
void ejecutar_linea_ordenes(const char *orden) {
    char **ordenes;
    int nordenes;
    pid_t *pids;
    int **pipes;
    int backgr = 0;

    // Dividir la línea de órdenes en órdenes individuales.
    ordenes = parser_pipes(orden, &nordenes);
    if (ordenes == NULL) {
        perror("Error al dividir la línea de órdenes.");
        return;
    }
    // Reservar memoria para los PIDs.
    pids = (pid_t *)malloc(sizeof(pid_t) * nordenes);
    if (pids == NULL) {
        perror("Error al reservar memoria para los PIDs.");
        free_ordenes_pipes(ordenes, NULL, 0);
        return;
    }
    // Crear las tuberías.
    pipes = crear_pipes(nordenes);

    // Bucle para ejecutar cada orden.
    for (int i = 0; i < nordenes; i++) {
        int entrada, salida;

        // Configurar entrada y salida según la posición de la orden.
        if (i == 0) {
            // Primera orden.
            entrada = STDIN_FILENO;
            if (nordenes > 1) {
                salida =
                    pipes[0][1]; // Descriptor de escritura del primer pipe.
            } else {
                salida = STDOUT_FILENO; // Salida estándar.
            }
        } else if (i == nordenes - 1) {
            // Última orden.
            entrada = pipes[i - 1][0];
            salida = STDOUT_FILENO;
        } else {
            // Orden intermedia.
            entrada = pipes[i - 1][0];
            salida = pipes[i][1];
        }

        // Ejecutar la orden.
        pids[i] = ejecutar_orden(ordenes[i], entrada, salida, &backgr);

        // Cerrar descriptores ya utilizados en el padre.
        if (i > 0)
            close(pipes[i - 1][0]);
        if (i < nordenes - 1)
            close(pipes[i][1]);
    }
    // Esperar a los hijos (si no están en segundo plano).
    if (backgr == 0 && pids[nordenes - 1] > 0) {
        for (int i = 0; i < nordenes; i++) {
            if (waitpid(pids[i], NULL, 0) == -1) {
                perror("Error al esperar a los hijos.");
            }
        }
    }

    // Liberar memoria.
    free(pids);
    free_ordenes_pipes(ordenes, pipes, nordenes);
}

/**
 * crear_pipes - Crea las tuberías necesarias para conectar n órdenes.
 * 
 * @nordenes: Número de órdenes.
 * 
 * Retorna: Un arreglo de descriptores de tuberías.
 */
int **crear_pipes(int nordenes) {
    int **pipes = NULL;
    int i;

    // Reservar memoria para las tuberías (nordenes - 1)
    pipes = (int **)malloc(sizeof(int *) * (nordenes - 1));
    if (pipes == NULL) {
        perror("Error al reservar memoria para las tuberías");
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < nordenes - 1; i++) {
        pipes[i] = (int *)malloc(sizeof(int) * 2);
        if (pipes[i] == NULL) {
            perror("Error al reservar memoria para una tubería");
            exit(EXIT_FAILURE);
        }

        // Crear cada tubería con pipe()
        if (pipe(pipes[i]) == -1) {
            perror("Error al crear la tubería");
            exit(EXIT_FAILURE);
        }
    }

    return pipes;
}