/**
 * @file main.c
 * @brief Gestión de plataforma de pujas de consolas.
 * @date 26/03/2025
 * @author Daniel Callero Costales hola@danicallero.es
 *
 * @note Proyecto compartido con fines educativos. Se desaconseja la entrega propia o con fines de plagio.
 */
 
#include <stdio.h>
#include <string.h>
 
#include "types.h"
#include "console_list.h"
 
/**
 * @brief Número máximo de caracteres por línea en el archivo de entrada.
 */
#define MAX_BUFFER 255
 
/**
 * @brief Transforma un valor tConsoleBrand a una cadena de caracteres equivalente.
 *
 * @param[in] brand Valor enum de tipo tConsoleBrand.
 *
 * @retval string Cadena de caracteres equivalente al tConsoleBrand dado, o "unknown" si se ha pasado otro enum.
 */
char *consoleBrand2String(const tConsoleBrand brand) {
    switch (brand) {
        case nintendo: return "nintendo";
        case sega: return "sega";
        default: return "unknown";
    }
}
 
/**
 * @brief Transforma las mayúsculas de un string a minúsculas.
 *
 * @param[in,out] str Puntero a un string.
 *
 * @pre El puntero 'str' debe apuntar a un string válido.
 * @post El string se modifica y ya no contiene mayúsculas.
 *
 * @attention Esta función modifica 'str' diréctamente, NO devuelve un duplicado.
 */
void toLower(char *str) {
    for (; *str; str++) {
        if (*str >= 'A' && *str <= 'Z') { /* Solo se convierten caracteres de la 'A' a la 'Z', minúsculas y otros
                                           *   caracteres quedan tal cual.
                                           */
            *str += 'a' - 'A'; //Se convierten los caracteres usando su ASCII (gracias C por hacerlo tu solito).
        }
    }
}
 
/**
 * @brief Transforma una cadena de caracteres en su valor equivalente del enum tConsoleBrand.
 *
 * @param[in,out] source Cadena que representa una marca.
 * @param[out] dest Puntero a una variable de tipo tConsoleBrand donde se almacenará el valor del enum.
 *
 * @retval true La marca existe dentro de los valores definidos en tConsoleBrand
 * @retval false No se encontró la marca entre los valores de tConsoleBrand.
 *
 * @post –  La cadena 'source' se convierte a minúsculas al llamar a la función.
 * @post –  Si la función devuelve true, 'dest' cambiará su valor y contendrá el valor correspondiente del enum tConsoleBrand.
 *
 * @attention Llamar a esta función transformará el string 'source' que se le pase a minúsculas.
 */
bool string2ConsoleBrand(char *source, tConsoleBrand *dest) {
    bool out = false; //Auxiliar donde se recoge el output que devolverá return.
 
    toLower(source);  //Se pasa a minúsculas el source para simplificar la comprobación.
    if (strcmp(source, "nintendo") == 0) {
        *dest = nintendo;
        out = true;
    } else if (strcmp(source, "sega") == 0) {
        *dest = sega;
        out = true;
    }
    if (!out) printf("+ Error: Console brand \"%s\" is not among expected brands.\n", source);
    return out;
}
 
/**
 * @brief Transforma de forma segura un valor de tipo string a float.
 *
 * @param[in] str Puntero al string que se quiere transformar.
 *
 * @return Valor float del string si es válido, -1.0f si hay error de formato.
 */
float safeStr2float(const char *str) {
    char *endptr; //Variable donde 'strtof' guarda el último puntero del string.
    const float value = strtof(str, &endptr); //Valor convertido a float.
 
    if (*endptr != '\0') {
        //printf("Error: Invalid number format: %s\n", str);
        return -1.0f; //Se define un valor negativo para esta aplicación, los precios negativos no existen.
    }
    return value;
}
 
/**
 * @brief Vacía los contenidos de una pila tStack.
 *
 * @param[in,out] stack Puntero a la pila tStack que se quiere vaciar.
 *
 * @pre El puntero debe apuntar a una pila inicializada.
 * @post La pila queda vaciada.
 */
void clearStack(tStack *stack) {
    while (!isEmptyStack(*stack)) { //Se atraviesa el stack mientras no esté vacío.
        pop(stack);
    }
}
 
/**
 * @brief Gestiona de forma unificada la eliminación de consolas cumpliendo con la precondición de vaciar la pila de
 * pujas previamente.
 *
 * @param[in] pos Posición tPosL del elemento que se quiere eliminar.
 * @param[in,out] list Puntero a la lista en la que se encuentra el item.
 *
 * @pre - 'list' debe apuntar a una tList inicializada.
 * @pre - La tPosL 'pos' debe ser una posición válida que contenga un elemento de la lista.
 * @pre - El puntero 'item' debe apuntar al tItemL que se encuentra en la posición 'pos'.
 *
 * @post - La pila de pujas del elemento 'item' queda vaciada.
 * @post - El elemento 'item' se elimina de la lista, y como consecuencia otros elementos pueden haber sido desplazados.
 */
void handleDeleteConsole(tPosL pos, tList *list) {
    tItemL item = getItem(pos, *list);
    clearStack(&item.bidStack);
    item.bidCounter = 0;
    updateItem(item, pos, list); //Actualizamos el item con la pila borrada.
    deleteAtPosition(pos, list);
}
 
/**
 * @brief Copia de forma segura una cadena de caracteres de un string de origen a otro de destino, asegurando que no se
 * produzca overflow y avisando de posibles errores.
 *
 * @param[out] dest Puntero al de destino, que se sobreescribirá con los contenidos de 'src'.
 * @param[in] src Puntero al string de origen. No se modifica.
 * @param[in] size Valor de tipo size_t. Tamaño máximo del string incluyendo la terminación '\0'.
 * @param[in] label Nombre descriptivo del string que se imprimirá en el mensaje de error.
 *
 * @retval true La cadena de caracteres se copió exitosamente.
 * @retval false La cadena de caracteres no se copió exitosamente, se imprime en consola un mensaje explicativo del error.
 */
bool safeStrCpy(char *dest, const char *src, const size_t size, const char *label) {
    bool out = false; //Auxiliar donde se recoge el output que devolverá return.
 
    if (dest == NULL || src == NULL || size == 0) {
        printf("+ Error: missing parameters for: %s\n", label);
    } else if (strlen(src) >= size) {
        printf("+ Error: Invalid string size for parameter: %s\n", label);
    } else {
        strncpy(dest, src, size - 1);
        dest[size - 1] = '\0';
        out = true;
    }
    return out;
}
 
/**
 * @brief Procesa el comando 'N' para añadir una nueva consola en una lista tList.
 *
 * @param[in] commandNumber Cadena de caracteres que contiene el número de comando. Este valor solo ayuda a mantener
 * registro de la petición, no tiene otro uso.
 * @param[in] consoleId_p Cadena que contiene el identificador de la consola a insertar.
 * @param[in] sellerId_p Cadena que contiene el identificador del vendedor.
 * @param[in] consoleBrand_p Cadena que representa la marca de la consola.
 * @param[in] consolePrice_p Cadena que representa el precio de la consola.
 * @param[in,out] list Puntero a la lista donde se guardará el item.
 *
 * @post - Se imprime el resultado y se modifica la lista si el comando está bien formulado.
 * @post - Se imprime '+ Error:' seguido de un aviso en caso de fallo.
 *
 * @remark No permite la inserción de duplicados, cadenas de caracteres con strings de longitud inválida, y evita errores
 * básicos.
 */
void processNewCommand(char *commandNumber, char *consoleId_p, char *sellerId_p, char *consoleBrand_p,
                       const char *consolePrice_p, tList *list) {
    tItemL newItem; //Elemento item que se rellenará con la información del item a insertar.
    tPosL pos; //La posición en la lista del item a insertar.
    tConsoleBrand brand; //Variable donde se guarda la conversión de string a enum.
    float priceFloat; //Variable precio transformado a float para poder pasarlo al TAD.
 
 
    //Se asegura que el código no se rompa si faltan parámetros.
    if (!consoleId_p || !sellerId_p || !consoleBrand_p || !consolePrice_p || !list) {
        printf("%s N: console %s seller %s brand %s price %s\n", commandNumber, consoleId_p, sellerId_p, consoleBrand_p,
            consolePrice_p);
        printf("%s N\n+ Error: New not possible\n", commandNumber);
        return;
    }
    priceFloat = safeStr2float(consolePrice_p);
 
    printf("%s N: console %s seller %s brand %s price %.2f\n", commandNumber, consoleId_p, sellerId_p, consoleBrand_p,
           priceFloat);
 
    pos = findItem(consoleId_p, *list);
    if (pos == LNULL && priceFloat >= 0) {
        if (safeStrCpy(newItem.consoleId, consoleId_p, NAME_LENGTH_LIMIT, "ConsoleId") &&
            safeStrCpy(newItem.seller, sellerId_p, NAME_LENGTH_LIMIT, "SellerId") &&
            string2ConsoleBrand(consoleBrand_p, &brand)) {
 
            newItem.consoleBrand = brand;
            newItem.consolePrice = priceFloat;
            newItem.bidCounter = 0;
            createEmptyStack(&newItem.bidStack); //El stack se inicializa como vacío.
 
            if (insertItem(newItem, list)) {
                printf("* New: console %s seller %s brand %s price %.2f\n", newItem.consoleId, newItem.seller,
                       consoleBrand_p,
                       newItem.consolePrice);
            } else
                printf("+ Error: New not possible\n"); //Se produjo un fallo en el TAD.
        } else {
            printf("+ Error: New not possible\n"); //fallo en manipulación de strings.
        }
    } else {
        printf("+ Error: New not possible\n"); //elemento duplicado o precio negativo.
    }
}
 
/**
 * @brief Procesa el comando 'D' para eliminar una consola de la lista.
 *
 * @param[in] commandNumber Cadena con el número de comando.
 * @param[in] consoleId_p Identificador de la consola a eliminar.
 * @param[in,out] list Lista en la que se encuentra la consola (tList).
 *
 * @post Se imprime el resultado y se modifica la lista si el comando es válido.
 * @post Imprime "+ Error: Delete not possible" si el elemento no se encuentra.
 */
void processDeleteCommand(char *commandNumber, char *consoleId_p, tList *list) {
    tPosL pos;   //Posición del item sobre el que se puja.
    tItemL item; //Item que se elimina.
 
    printf("%s D: console %s\n", commandNumber, consoleId_p);
 
    if (consoleId_p != NULL) {
        pos = findItem(consoleId_p, *list); //También actúa como isEmptyList si devuelve LNULL.
        if (pos != LNULL) {
            item = getItem(pos,*list); //Se duplica antes de borrar para poder hacer el print.
 
            printf("* Delete: console %s seller %s brand %s price %.2f bids %d\n", item.consoleId, item.seller,
                consoleBrand2String(item.consoleBrand), item.consolePrice, item.bidCounter);
 
            handleDeleteConsole(pos, list);
        } else {
            printf("+ Error: Delete not possible\n"); //No se ha encontrado item o la lista se encuentra vacía.
        }
    } else {
        printf("+ Error: Delete not possible\n"); //missing params
    }
}
 
/**
 * @brief Procesa el comando 'B' para realizar una puja sobre una consola existente.
 *
 * @param[in] commandNumber Cadena con el número de comando.
 * @param[in] consoleId_p Identificador de la consola.
 * @param[in] bidderId_p Identificador del pujador.
 * @param[in] consolePrice_p Cadena con el importe de la puja.
 * @param[in,out] list Lista en la que se guardan los valores (tList).
 *
 * @post Se imprime el resultado y se actualiza el precio y contador de pujas si la puja es válida.
 * @post Se imprime "+ Error: Bid not possible" si la consola no existe, el vendedor es el pujador,
 * el pujador actual ya era el último pujador, la puja no supera la anterior, o hay overflow.
 */
void processBidCommand(char *commandNumber, char *consoleId_p, char *bidderId_p, char *consolePrice_p, tList *list) {
    tPosL pos;          //Posición del item sobre el que se puja.
    tItemL item;        //Item sobre el que se puja.
    tItemS stackItem;   //Elemento de la pila de pujas que guarda los metadatos de la misma.
    float highestBid;   //Valor numérico de la puja más alta (si no hay pujas será el precio original).
    char *highestBidStr;//String del mayor pujador (si no hay pujas será el vendedor).
    float bidPrice;     //Variable precio como float para poder pasarlo al TAD y hacer comparaciones.
 
    //Se asegura que el código no se rompa si faltan parámetros.
    if (!bidderId_p || !consolePrice_p || !list) {
        printf("%s B\n+ Error: Bid not possible\n", commandNumber);
        return;
    }
    bidPrice = safeStr2float(consolePrice_p);
    pos = findItem(consoleId_p, *list);
 
    printf("%s B: console %s bidder %s price %.2f\n", commandNumber, consoleId_p, bidderId_p, bidPrice);
 
    if (bidPrice >= 0 && pos != LNULL) {
        item = getItem(pos, *list);
 
        //Los operadores ternarios permiten operar siempre las comprobaciones, independientemente de si existen pujas o no.
        stackItem = isEmptyStack(item.bidStack) ? (tItemS){.consolePrice = -1, .bidder = ""} : peek(item.bidStack);
        highestBid = (stackItem.consolePrice == -1) ? item.consolePrice : stackItem.consolePrice;
        highestBidStr = (stackItem.consolePrice == -1) ? item.seller : stackItem.bidder;
 
        if (strcmp(highestBidStr, bidderId_p) == 0 || strcmp(item.seller, bidderId_p) == 0 || bidPrice <= highestBid) {
            //El vendedor de un item, o el pujador actual, no puede pujar en su propio item.
            printf("+ Error: Bid not possible\n");
 
        } else {
            if (!safeStrCpy(stackItem.bidder, bidderId_p, NAME_LENGTH_LIMIT, "BidderId")) {
                printf("+ Error: Bid not possible\n");
            } else {
                stackItem.consolePrice = bidPrice;
 
                if (push(stackItem, &item.bidStack)) {
                    item.bidCounter++;
                    updateItem(item, pos, list);
                } else {
                    printf("+ Error: Bid not possible\n");
                }
 
                printf("* Bid: console %s bidder %s brand %s price %.2f bids %d\n", item.consoleId, stackItem.bidder,
                    consoleBrand2String(item.consoleBrand), stackItem.consolePrice, item.bidCounter);
            }
        }
    } else {
        printf("+ Error: Bid not possible\n"); //Puja negativa, lista vacía o item no existente.
 
    }
}
 
/**
 * @brief Procesa el comando 'A' para adjudicar una consola al mayor postor.
 *
 * @param[in] commandNumber Cadena con el número de comando.
 * @param[in] consoleId_p Identificador de la consola a adjudicar.
 * @param[in,out] list Lista en la que se encuentra la consola (tList).
 *
 * @post Imprime "* Award: console CC bidder UU brand BB price PP" con los datos del ganador.
 * @post Elimina la consola de la lista tras vaciar su pila de pujas.
 * @post Imprime "+ Error: Award not possible" si no existe la consola o no hay pujas.
 */
void processAwardCommand(char *commandNumber, char *consoleId_p, tList *list ) {
    tPosL pos;  //La posición en la lista del item a adjudicar.
    tItemL item;//El item que se quiere adjudicar.
    tItemS top; //El item en la pila de pujas que guarda los datos de la mayor puja.
 
    printf("%s A: console %s\n", commandNumber, consoleId_p);
 
    if (consoleId_p != NULL) {
        pos = findItem(consoleId_p, *list);
        if (pos != LNULL) {
            item = getItem(pos, *list);
            if (isEmptyStack(item.bidStack)) { //No se han encontrado pujas.
                printf("+ Error: Award not possible\n");
            } else {
                top = peek(item.bidStack);
                printf("* Award: console %s bidder %s brand %s price %.2f\n", item.consoleId, top.bidder,
                    consoleBrand2String(item.consoleBrand) ,top.consolePrice);
                handleDeleteConsole(pos, list);
            }
        } else {
            printf("+ Error: Award not possible\n"); //No se ha encontrado el item o la lista está vacía.
        }
    } else {
        printf("+ Error: Award not possible\n"); //Missing params
    }
}
 
/**
 * @brief Procesa el comando 'S' para mostrar estadísticas y listado de las consolas registradas.
 *
 * @param[in] commandNumber Cadena con el número de comando.
 * @param[in] list Lista en la que se guardan los valores (tList).
 *
 * @post Imprime por cada consola:
 *       - "Console CC seller UU brand BB price PP bids II top bidder UU"
 *       - O "Console CC seller UU brand BB price PP. No bids" si no hay pujas.
 * @post Imprime un resumen por marca con número de consolas, suma de precios y precio medio.
 * @post Imprime la "Top bid", la que muestra mayor incremento porcentual.
 * @post Imprime "+ Error: Stats not possible" si la lista está vacía.
 */
void processStatsCommand(char *commandNumber, tList list) {
    int countNintendo = 0, countSega = 0;       //Contador del número de consolas de la marca.
    float sumNintendo = 0.0f, sumSega = 0.0f;   //Sumador de la suma de precios de todas las consolas de la marca.
    float avgNintendo, avgSega;                 //Precio medio de las consolas de la marca.
 
    tPosL pos;                //Posición del elemento para el que se realizarán comprobaciones e impresiones en consola.
    tPosL topItemPos = NULL;  //Auxiliar que guarda la posición del item con la mejor puja.
    float maxIncrease = 0.0f; //Auxiliar que guarda el precio del item con la mejor puja.
 
    float increase;     //Precio de la puja más alta. (para cambiar en cada iteración)
    float topBid;       //Mayor incremento en precio. (para cambiar en cada iteración)
 
    tItemL topItem;     //Auxiliar donde se guardará la consola con mayor puja.
    tItemS topStack;    //Auxiliar donde se guardará la mayor puja.
 
    printf("%s S\n", commandNumber);
 
    if (!isEmptyList(list)) {
        pos = first(list);
 
        while (pos != LNULL) { /*Se atraviesa toda la lista hasta llegar al final para calcular # de consolas por marca,
                                *y hacer el listing de todas en consola.
                                */
            tItemL item = getItem(pos, list); //El item guardado en la posición que estamos comprobando.
 
            printf("Console %s seller %s brand %s price %.2f", item.consoleId, item.seller,
                consoleBrand2String(item.consoleBrand), item.consolePrice);
 
            if (isEmptyStack(item.bidStack)) { //No se han creado pujas.
                printf(". No bids\n");
            } else {
                topBid = peek(item.bidStack).consolePrice;
                increase = ((topBid - item.consolePrice) / item.consolePrice) * 100.0f;
 
                if (increase > maxIncrease) {
                    maxIncrease = increase;
                    topItemPos = pos;
                }
                printf(" bids %d top bidder %s\n", item.bidCounter, peek(item.bidStack).bidder);
            }
 
 
            if (item.consoleBrand == nintendo) { //Se aumenta el contador y el sumatorio.
                countNintendo++;
                sumNintendo += item.consolePrice;
            } else if (item.consoleBrand == sega) {
                countSega++;
                sumSega += item.consolePrice;
            }
 
            pos = next(pos, list);
        }
 
        avgNintendo = (countNintendo > 0) ? sumNintendo / (float)countNintendo : 0; //cálculo de promedio que evita div/0
        avgSega = (countSega > 0) ? sumSega / (float)countSega : 0;                 //cálculo de promedio que evita div/0
 
        printf("\nBrand     Consoles    Price  Average\n");
        printf("Nintendo  %8d %8.2f %8.2f\n", countNintendo, sumNintendo, avgNintendo);
        printf("Sega      %8d %8.2f %8.2f\n", countSega, sumSega, avgSega);
 
        if (topItemPos != NULL) {
            topItem = getItem(topItemPos, list);
            topStack = peek(topItem.bidStack);
            printf("Top bid: console %s seller %s brand %s price %.2f bidder %s top price %.2f increase %.2f%%\n",
                topItem.consoleId, topItem.seller, consoleBrand2String(topItem.consoleBrand), topItem.consolePrice,
                topStack.bidder, topStack.consolePrice, maxIncrease);
        } else {
            printf("Top bid not possible\n");
        }
    } else {
        printf("+ Error: Stats not possible\n"); //La lista se encuentra vacía.
    }
}
 
/**
 * @brief Elimina de la lista aquellas consolas que no tengan ninguna puja.
 *
 * @param[in] commandNumber Cadena con el número de comando.
 * @param[in,out] list Lista en la que se guardan los valores (tList).
 *
 * @post Por cada consola sin pujas, imprime "Removing console CC seller UU brand BB price PP bids II" y la elimina de
 *       la lista.
 * @post Imprime "+ Error: Remove not possible" si no hay consolas o ninguna sin pujas.
 */
void processRemoveCommand(char *commandNumber, tList *list) {
    bool removed = false;     //Auxiliar que lleva registro de si se ha eliminado alguna consola.
    tPosL pos;                //Posición del elemento para el que se realizarán comprobaciones.
    tItemL item;              //El item que se quiere comprobar.
 
    printf("%s R\n", commandNumber);
 
    if (!isEmptyList(*list)) {
        pos = first(*list);     //Llamamos a first después de comprobar que la lista no esté vacía, por la preCD.
 
        while (pos != LNULL) {
            item = getItem(pos, *list);
 
            if (isEmptyStack(item.bidStack)) {
                removed = true;
                printf("Removing console %s seller %s brand %s price %.2f bids %d\n", item.consoleId, item.seller,
                    consoleBrand2String(item.consoleBrand), item.consolePrice, item.bidCounter);
 
                handleDeleteConsole(pos, list);
                pos = first(*list);//No es elegante, no es bonito, y no me gusta, pero como me han obligado a usar aliasing
                                   //para eliminaciones, es la forma que se me ocurre de asegurar integridad
            } else {
                pos = next(pos, *list);
            }
        }
 
        if (!removed) {
            printf("+ Error: Remove not possible\n");
        }
    } else {
        printf("+ Error: Remove not possible\n"); //lista vacía.
    }
}
 
/**
 * @brief Procesa el comando 'I' para invalidar las consolas con pujas irregulares.
 *
 * @param[in] commandNumber Cadena con el número de comando.
 * @param[in,out] list Lista en la que se guardan los valores (tList).
 *
 * @post Calcula el número medio de pujas y define un rango = 2 × media. Se eliminarán las consolas cuyo contador exceda
 *       el rango se eliminarán, mostrando el siguiente mensaje para cada consola eliminada:
 *       "* InvalidateBids: console CC seller UU brand BB price PP bids II average bids AA".
 * @post Imprime "+ Error: InvalidateBids not possible" si la lista está vacía o no cumple condición.
 */
void processInvalidateBidsCommand(char *commandNumber, tList *list) {
    int totalBids = 0, numConsoles = 0; //Auxiliar contador.
    tPosL pos;                          //Posición del elemento para el que se realizarán comprobaciones.
    float averageBids;                  //Promedio de las pujas.
    float range;                        //Rango de las pujas.
    bool invalidated = false;           //Auxiliar que lleva registro de si se ha invalidado alguna puja.
 
    printf("%s I\n", commandNumber);
 
    if (!isEmptyList(*list)) {
        pos = first(*list);
 
        //Se atraviesa la lista para recoger los promedios.
        while (pos != LNULL) {
            tItemL item = getItem(pos, *list);
            totalBids += item.bidCounter;
            numConsoles++;
            pos = next(pos, *list);
        }
 
        averageBids = (float)totalBids / (float)numConsoles;
        range = 2 * averageBids;
 
        pos = first(*list);
 
        //Se atraviesa la lista y se eliminan las pujas si se supera el rango.
        while (pos != LNULL) {
            tItemL item = getItem(pos, *list);
 
            if ((float)item.bidCounter > range) {
                clearStack(&item.bidStack);
 
                printf("* InvalidateBids: console %s seller %s brand %s price %.2f bids %d average bids %.2f\n", item.consoleId,
                    item.seller, consoleBrand2String(item.consoleBrand), item.consolePrice, item.bidCounter, averageBids);
                item.bidCounter = 0;
                updateItem(item,pos,list);
                invalidated = true;
            }
 
            pos = next(pos, *list);
        }
 
        if (!invalidated) {
            printf("+ Error: InvalidateBids not possible\n"); //Ninguna consola fue invalidada.
        }
    } else {
        printf("+ Error: InvalidateBids not possible\n"); //lista vacía.
    }
}
 
/**
 * @brief Procesa los comandos recibidos y llama a las funciones correspondientes según el tipo de comando.
 *
 * @param[in] commandNumber Cadena con el número de comando recibido.
 * @param[in] command Carácter que indica el tipo de comando ('N','D','B','A','R','S','I').
 * @param[in] param1 Primer parámetro del comando (si aplica).
 * @param[in] param2 Segundo parámetro del comando (si aplica).
 * @param[in] param3 Tercer parámetro del comando (si aplica).
 * @param[in] param4 Cuarto parámetro del comando (si aplica).
 * @param[in,out] list Puntero a la lista donde se aplican las operaciones.
 *
 * @pre list debe estar inicializada.
 *
 * @post Llama a la función processXCommand correspondiente.
 * @post Si el comando no es reconocido, no realiza ninguna acción.
 *
 * @note Función proporcionada por la universidad, modificada ligeramente.
 */
void processCommand(char *commandNumber, char command, char *param1, char *param2, char *param3, char *param4, tList *list) {
    printf("********************\n");
    switch (command) {
        case 'N':
            processNewCommand(commandNumber, param1, param2, param3, param4, list);
            break;
        case 'D':
            processDeleteCommand(commandNumber, param1, list);
            break;
        case 'B':
            processBidCommand(commandNumber, param1, param2, param3, list);
            break;
        case 'A':
            processAwardCommand(commandNumber, param1, list);
            break;
        case 'R':
            processRemoveCommand(commandNumber,list);
            break;
        case 'S':
            processStatsCommand(commandNumber, *list);
            break;
        case 'I':
            processInvalidateBidsCommand(commandNumber, list);
            break;
        default:
            break;
    }
}
 
/**
 * @brief Lee los comandos desde un archivo y los procesa uno por uno.
 *
 * @param[in] filename Nombre del archivo que contiene los comandos.
 * @param[in,out] list Puntero a la lista donde se almacenará la información.
 *
 * @pre El archivo debe existir y ser accesible.
 * @pre Los comandos deben estar precedidos de su respectivo número de comando.
 *
 * @post Se procesan todos los comandos hasta finalizar el archivo.
 * @post La lista puede quedar modificada por la ejecución de las operaciones.
 * @post Si el archivo no se encuentra, se muestra un mensaje de error.
 *
 * @note Función proporcionada por la universidad, modificada ligeramente.
 */
void readTasks(char *filename, tList *list) {
 
    FILE *f = NULL;
    char *commandNumber, *command, *param1, *param2, *param3, *param4;
    const char delimiters[] = " \n\r";
    char buffer[MAX_BUFFER];
 
    f = fopen(filename, "r");
 
    if (f != NULL) {
 
        while (fgets(buffer, MAX_BUFFER, f)) {
            commandNumber = strtok(buffer, delimiters);
            command = strtok(NULL, delimiters);
            param1 = strtok(NULL, delimiters);
            param2 = strtok(NULL, delimiters);
            param3 = strtok(NULL, delimiters);
            param4 = strtok(NULL, delimiters);
 
            processCommand(commandNumber, command[0], param1, param2, param3, param4, list);
        }
 
        fclose(f);
 
    } else {
        printf("Cannot open file %s.\n", filename);
    }
}
 
 
int main(int nargs, char **args) {
    tList list; //Variable lista que se pasa a las funciones
    createEmptyList(&list); //Se inicializa la lista.
 
    char *file_name = "new.txt";
 
    if (nargs > 1) {
        file_name = args[1];
    } else {
        #ifdef INPUT_FILE
        file_name = INPUT_FILE;
        #endif
    }
 
    readTasks(file_name, &list);
 
    return 0;
}