#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <time.h>

#include <inttypes.h>
#include <strings.h>

#include "truco_server_config.h"
#include "../truco.h"

#define MAX 80
#define SA struct sockaddr

#define INVALID_FD (-1)
#define JUGADOR_INVALIDO_SERVER (MAX_JUGADORES_SERVER+1)
#define MESA_INVALIDA (MAX_MESAS+1)

#define PAQUETE_MAX_ARGS 3

typedef int socket_t;

#define DEBUG 1

#if DEBUG == 1
    #define DEBUG_PRINT(line) do { line; } while(0)
#else
    #define DEBUG_PRINT(line) do { } while(0)
#endif

/* MACROS NEEDED FOR BUFFERS */
#define STR(x) #x
#define XSTR(x) STR(x)
#define DIGITS(x) (sizeof(XSTR(x)) - 1)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define STRLEN_CT(s) (sizeof(s) - 1)

#define SOCKET_IO_ERROR_CHECK(x) ((x) <= 0)
#define SOCKET_ERROR_CHECK(x) ((x) < 0)

#define COMMON_PACKET_BYTES_EXTENSION (5) ///<PP \n\0
#define SUM_COMMON_EXT(a) ((size_t)((a) + (COMMON_PACKET_BYTES_EXTENSION))) ///<Apply COMMON_PACKET_BYTES_EXTENSION to (a)

#define A_BUFFER_SIZE (1 + 1 + DIGITS(MAX_JUGADORES_MESA) + 1 + 1 + 1)

typedef struct{
    socket_t fd;
    char ip[INET_ADDRSTRLEN];
    time_t ultimo_latido;
} Conn;

#define ROL_JUGADOR 0
#define ROL_ANFITRION 1

typedef struct{
    char apodo[MAX_LONGITUD_APODO];
    Conn *conn;
    uint8_t mesa_id;

    uint8_t mesa_rol;
} Jugador;

typedef struct{
    uint16_t jugadores[MAX_JUGADORES_MESA];
    EstadoPartida estado_partida;

    uint8_t numero_jugadores;
    uint8_t partida_iniciada;
} Mesa;

Conn static conexiones[MAX_CONEXIONES];
Jugador static jugadores[MAX_JUGADORES_SERVER];
Mesa static mesas[MAX_MESAS];

/**Use memcpy to copy *src to *dst and make sure there is a \0 at the end (dst_size)*/
static void safe_strcpy(char *dst, const char *src, size_t dst_size){
    size_t len = strlen(src);

    if (len >= dst_size)
        len = dst_size - 1;

    memcpy(dst, src, len);
    dst[len] = '\0';
}

/*PACKET SEND LOGIC*/

/**Sends *paquete to fd (fd/socket) */
static void enviar_a_cliente(const char *paquete, socket_t sockfd){
    DEBUG_PRINT(printf("ENVIANDO %s A %d\n", paquete, sockfd));

    // if(WS_LISTEN_PORT > 0 && TCS_getConnByFd(fd)->flags & CONN_IS_WS_FLAG){
    //     websocket_send_text(fd, paquete);
    //     return;
    // }
    int n = send(sockfd, paquete, strlen(paquete), MSG_NOSIGNAL);
    if (SOCKET_IO_ERROR_CHECK(n)) perror("send");
}

/**Sends *paquete to a jugador of jugador_id (jugadores[i]) as long as he is not leaving. */
static void enviar_paquete(const char *paquete, uint8_t jugadorId){
    if (jugadorId >= MAX_JUGADORES_SERVER) return;
    socket_t fd = jugadores[jugadorId].conn->fd;
    // if (SOCKET_ERROR_CHECK(fd) || (jugadores[jugador_id].flags & jugador_LEAVING_FLAG)) return;
    enviar_a_cliente(paquete, fd);
}

/**Sends *paquete to area of areaId (areaList[i]) as long as they joined the server and are not leaving. */
static void enviar_paquete_a_mesa(const char *paquete, uint8_t mesaId){
    for (uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
        uint16_t jugador = mesas[mesaId].jugadores[i];
        
        if(jugador == JUGADOR_INVALIDO_SERVER) continue;

        enviar_paquete(paquete, mesas[mesaId].jugadores[i]);
    } 
}

typedef struct {
    char **args;
    uint16_t tamano_paquete;
    Conn *conn;
    Jugador *jugador;
    Mesa *jugador_mesa;
    uint8_t jugador_id;
    uint8_t numero_args;
} TCS_ContextoPaquete;

typedef void (*HandlerFunc)(TCS_ContextoPaquete *ctx);
typedef ResultadoJugada (*HandlerJugada)(TCS_ContextoPaquete *ctx);


/**
    Make an 16 bit unsigned int out of a string. Loops through it and 
    returns 0 if/when no numbers are found. 
*/
uint16_t static parseInt(const char *s)
{
    uint16_t value = 0;
    while (*s >= '0' && *s <= '9') value = value * 10 + (*s++ - '0');
    return value;
}

Conn* TCS_getConnByFd(socket_t fd) {
    for (uint16_t i = 0; i < MAX_CONEXIONES; i++) if (conexiones[i].fd == fd) return &conexiones[i];
    return NULL;
}

uint16_t TCS_encontrar_jugador_id_por_conn(Conn *conn){
    for (uint16_t i = 0; i < MAX_JUGADORES_SERVER; i++) {
        if(jugadores[i].conn != conn) continue;

        return i;
    }
    
    return JUGADOR_INVALIDO_SERVER;
}

uint8_t TCS_obtener_posicion_jugador_mesa_por_jugador_id(uint16_t mesa_id, uint16_t jugador_id){
    for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
        if(mesas[mesa_id].jugadores[i] == jugador_id){
            return i;
        }
    }
    return JUGADOR_INVALIDO;
}

void TCS_closeAndClearConn(Conn *c){
    close(c->fd);
    c->fd = INVALID_FD;
    c->ip[0] = '\0';
}

void TCS_reiniciar_mesa(uint16_t mesa_id){
    mesas[mesa_id] = (Mesa){0};
    
    for(uint8_t j = 0; j < MAX_JUGADORES_MESA; j++){
        mesas[mesa_id].jugadores[j] = JUGADOR_INVALIDO_SERVER;
    }
}

void TCS_reiniciar_jugador(uint16_t jugador_id){
    jugadores[jugador_id] = (Jugador){0};

    jugadores[jugador_id].mesa_id = MESA_INVALIDA;
}

void TCS_setear_anfitrion_sala(uint16_t mesa_id, uint8_t asiento_nuevo_anfitrion, uint8_t asiento_anfitrion_actual){
    jugadores[mesas[mesa_id].jugadores[asiento_nuevo_anfitrion]].mesa_rol = ROL_ANFITRION;

    if(asiento_anfitrion_actual != MAX_JUGADORES_MESA) jugadores[mesas[mesa_id].jugadores[asiento_anfitrion_actual]].mesa_rol = ROL_JUGADOR;

    char a_buffer[A_BUFFER_SIZE];
    sprintf(a_buffer, "A %u\n", asiento_nuevo_anfitrion);
    enviar_paquete_a_mesa(a_buffer, mesa_id);
}

void TCS_abandonar_mesa(uint16_t mesa_id, uint16_t jugador_id){
    Mesa *mesa = &mesas[mesa_id];

    uint8_t pos_jugador = TCS_obtener_posicion_jugador_mesa_por_jugador_id(mesa_id, jugador_id);

    if(pos_jugador != JUGADOR_INVALIDO_SERVER){
        mesa->jugadores[pos_jugador] = JUGADOR_INVALIDO_SERVER;
        char sj_buffer[SUM_COMMON_EXT(MAX_JUGADORES_MESA)];
        sprintf(sj_buffer, "SJ %u\n", pos_jugador);
        enviar_paquete_a_mesa(sj_buffer, mesa_id);
    }

    mesa->numero_jugadores--;

    if(mesa->numero_jugadores == 0) TCS_reiniciar_mesa(mesa_id);
    else if(jugadores[jugador_id].mesa_rol == ROL_ANFITRION){
        for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
            if(mesa->jugadores[i] == JUGADOR_INVALIDO_SERVER) continue;
            
            TCS_setear_anfitrion_sala(mesa_id, i, pos_jugador);
        }
    }

    jugadores[jugador_id].mesa_rol = ROL_JUGADOR;
    jugadores[jugador_id].mesa_id = MESA_INVALIDA;
}

void desconectar_cliente(uint16_t jugador_id){
    Jugador *jugador = &jugadores[jugador_id];
    TCS_closeAndClearConn(jugador->conn);
    // jugador->flags |= jugador_LEAVING_FLAG;
    // if(jugador->flags & jugador_JOINED_FLAG) jugadorCount--;
    if(jugador->mesa_id != MESA_INVALIDA){
        TCS_abandonar_mesa(jugador->mesa_id, jugador_id);
    }
    TCS_reiniciar_jugador(jugador_id);
}

void desconectar_cliente_o_conn(Conn *conn){
    uint16_t jugador_id = TCS_encontrar_jugador_id_por_conn(conn);

    if(jugador_id == JUGADOR_INVALIDO_SERVER){
        TCS_closeAndClearConn(conn);
        return;
    }

    desconectar_cliente(jugador_id);
}

/**
  Common Packet Handler Structure

  This is what goes in commonPacketDispatchTable[]. 
  Adding a new paquete is just creating this struct and its handler.

  \param nombre_paquete
    Name of the paquete to match when comparing the first argument received in the paquete at processPacket()
  \param handler
    HandlerFunc that receives the TCS_ContextoPaquete to process the paquete.
  \param requiredArgs
    Amount of required args to validate before shooting the handler.
*/
typedef struct {
    const char *nombre_paquete;
    HandlerFunc handler;
    uint8_t requiredArgs;
} TCS_CommonPacketHandler;

typedef struct {
    const char *nombre_paquete;
    HandlerJugada handler;
    uint8_t requiredArgs;
} TCS_PaqueteJugadaManejador;

static void handle_UM(TCS_ContextoPaquete *ctx){
    uint16_t mesa_id = parseInt(ctx->args[0]);
    if(mesa_id > MAX_MESAS || ctx->jugador->mesa_id == mesa_id) return;

    uint8_t silla_encontrada = MAX_JUGADORES_MESA;

    uint8_t primer_jugador = 1;


    for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
        if(mesas[mesa_id].jugadores[i] != JUGADOR_INVALIDO_SERVER){
            primer_jugador = 0;
            continue;
        }

        if(silla_encontrada != MAX_JUGADORES_MESA) continue;

        //anoto el jugador en la mesa y la mesa en el jugador
        mesas[mesa_id].jugadores[i] = ctx->jugador_id;
        ctx->jugador->mesa_id = mesa_id;
        silla_encontrada = i;
    }

    if(silla_encontrada == MAX_JUGADORES_MESA) return;

    mesas[mesa_id].numero_jugadores++;

    char umBuffer[SUM_COMMON_EXT(DIGITS(MAX_MESAS) + 1 + DIGITS(MAX_JUGADORES_MESA))];
    sprintf(umBuffer, "UM %u %u\n", mesa_id, silla_encontrada);

    enviar_paquete(umBuffer, ctx->jugador_id);

    if(primer_jugador) TCS_setear_anfitrion_sala(mesa_id, silla_encontrada, MAX_JUGADORES_MESA);
    else ctx->jugador->mesa_rol = ROL_JUGADOR;

    for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){

        uint16_t jugador_i_id = mesas[mesa_id].jugadores[i];
        if(jugador_i_id == JUGADOR_INVALIDO_SERVER || i == silla_encontrada) continue;

        Jugador *p_jugador_i = &jugadores[jugador_i_id];

        char njBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_SERVER) + 1 + MAX_LONGITUD_APODO)];
        sprintf(njBuffer, "NJ %u %s\n", silla_encontrada, ctx->jugador->apodo);
        enviar_paquete(njBuffer, jugador_i_id);

        sprintf(njBuffer, "NJ %u %s\n", i, p_jugador_i->apodo);
        enviar_paquete(njBuffer, ctx->jugador_id);

        //le decimos al jugador que entró si p_jugador_i es el host
        if(p_jugador_i->mesa_rol == ROL_ANFITRION){
            char a_buffer[A_BUFFER_SIZE];
            sprintf(a_buffer, "A %u\n", i);
            enviar_paquete(a_buffer, ctx->jugador_id);
        }
    }
}

static void handle_CA(TCS_ContextoPaquete *ctx){

    //puede ser el asiento deseado o el del jugador que se quiere cambiar
    uint8_t asiento = parseInt(ctx->args[0]);

    uint8_t pos_jugador = TCS_obtener_posicion_jugador_mesa_por_jugador_id(ctx->jugador->mesa_id, ctx->jugador_id);

    if(asiento >= MAX_JUGADORES_MESA || pos_jugador >= MAX_JUGADORES_MESA) return;

    char caBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA) + 1 + DIGITS(MAX_JUGADORES_MESA))];

    sprintf(caBuffer, "CA %u %u\n", pos_jugador, asiento);

    //si es el asiento deseado, me tengo que fijar que no haya nadie.
    //si lo hay, cambio de lugares si es que es el host.
    if(ctx->numero_args < 2){
        if(ctx->jugador_mesa->jugadores[asiento] != JUGADOR_INVALIDO_SERVER && 
        ctx->jugador->mesa_rol != ROL_ANFITRION) return;

        ctx->jugador_mesa->jugadores[pos_jugador] = ctx->jugador_mesa->jugadores[asiento];
        ctx->jugador_mesa->jugadores[asiento] = ctx->jugador_id;
    }else{
        if(ctx->jugador->mesa_rol != ROL_ANFITRION) return;

        uint8_t asiento_2 = parseInt(ctx->args[1]);

        if(asiento_2 >= MAX_JUGADORES_MESA) return;

        uint16_t jugador_cambiado_1 = ctx->jugador_mesa->jugadores[asiento];

        ctx->jugador_mesa->jugadores[asiento] = ctx->jugador_mesa->jugadores[asiento_2];
        ctx->jugador_mesa->jugadores[asiento_2] = jugador_cambiado_1;
    }

    enviar_paquete_a_mesa(caBuffer, ctx->jugador->mesa_id);
}

void TCS_anunciar_turno_baza_en_mesa(uint16_t mesa_id){
    //turno baza
    char tbBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA))];
    sprintf(tbBuffer, "TB %u\n", mesas[mesa_id].estado_partida.jugador_turno_actual);
    enviar_paquete_a_mesa(tbBuffer, mesa_id);
}

void TCS_nueva_ronda(uint16_t mesa_id){
    for(uint8_t i = 0; i < mesas[mesa_id].numero_jugadores; i++){
        uint16_t jugador_id = mesas[mesa_id].jugadores[i];

        //mostrar cartas
        char mcBuffer[SUM_COMMON_EXT(sizeof("49 49 49"))];
        char *buffer_ptr = mcBuffer;

        buffer_ptr += sprintf(mcBuffer, "MC");
        for(uint8_t j = 0; j < 3; j++){
            uint8_t carta_id = mesas[mesa_id].estado_partida.jugadores_cartas[i][j];
            if(carta_id == CARTA_INVALIDA) continue;
            buffer_ptr += sprintf(buffer_ptr, " %u", carta_id);
        }
        
        sprintf(buffer_ptr, "\n");
        enviar_paquete(mcBuffer, jugador_id);
    }

    TCS_anunciar_turno_baza_en_mesa(mesa_id);
}

static void handle_EP(TCS_ContextoPaquete *ctx){
    if(ctx->jugador->mesa_rol != ROL_ANFITRION) return;

    if(!iniciar_partida(&ctx->jugador_mesa->estado_partida, ctx->jugador_mesa->numero_jugadores, 30)) return;

    enviar_paquete_a_mesa("EP\n", ctx->jugador->mesa_id);

    ctx->jugador_mesa->partida_iniciada = 1;

    TCS_nueva_ronda(ctx->jugador->mesa_id);
}

static void handle_L(TCS_ContextoPaquete *ctx){
    ctx->conn->ultimo_latido = time(NULL);
    enviar_paquete("L\n", ctx->jugador_id);
}

TCS_CommonPacketHandler commonPacketDispatchTable[] = {
    {"L", handle_L, 0}
}; /**< This is where most packets are declared. Refer to the official network protocol,
TCS_CommonPacketHandler (to understand how to implement a new package) and TCS_ContextoPaquete*/

TCS_CommonPacketHandler paquetesVestibuloTablaDespacho[] = {
    {"UM", handle_UM, 1},
};

TCS_CommonPacketHandler paquetesMesaTablaDespacho[] = {
    {"CA", handle_CA, 2},
    {"EP", handle_EP, 0}
};

ResultadoJugada handle_JC(TCS_ContextoPaquete *ctx){
    uint8_t carta_id = parseInt(ctx->args[0]);
    uint8_t pos_jugador = TCS_obtener_posicion_jugador_mesa_por_jugador_id(ctx->jugador->mesa_id, ctx->jugador_id);

    for (uint8_t i = 0; i < 3; i++){

        if(ctx->jugador_mesa->estado_partida.jugadores_cartas[pos_jugador][i] != carta_id) continue;

        ResultadoJugada resultado_jugada = jugar_baza(&ctx->jugador_mesa->estado_partida, pos_jugador, i);

        if(resultado_jugada.carta == CARTA_INVALIDA) return resultado_jugada;


        char jc_buffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA) + 1 + DIGITS(CARTA_INVALIDA))];

        sprintf(jc_buffer, "JC %u %u\n", pos_jugador, resultado_jugada.carta);
        enviar_paquete_a_mesa(jc_buffer, ctx->jugador->mesa_id);

        return resultado_jugada;
    }

    //hack: crear una jugada invalida
    return jugar_baza(&ctx->jugador_mesa->estado_partida, pos_jugador, CARTA_INVALIDA);
}

ResultadoJugada handle_C(TCS_ContextoPaquete *ctx){
    uint8_t canto_id = parseInt(ctx->args[0]);

    uint8_t pos_jugador = TCS_obtener_posicion_jugador_mesa_por_jugador_id(ctx->jugador->mesa_id, ctx->jugador_id);

    ResultadoJugada resultado_jugada = cantar(&ctx->jugador_mesa->estado_partida, pos_jugador, canto_id);

    if(resultado_jugada.canto == CANTO_INVALIDO) return resultado_jugada;

    char c_buffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA) + 1 + DIGITS(CANTO_INVALIDO))];

    sprintf(c_buffer, "C %u %u\n", pos_jugador, resultado_jugada.canto);

    enviar_paquete_a_mesa(c_buffer, ctx->jugador->mesa_id);

    return resultado_jugada;
}

ResultadoJugada handle_CT(TCS_ContextoPaquete *ctx){
    uint8_t tantos = parseInt(ctx->args[0]);

    uint8_t pos_jugador = TCS_obtener_posicion_jugador_mesa_por_jugador_id(ctx->jugador->mesa_id, ctx->jugador_id);

    ResultadoJugada resultado_jugada = cantar_tanto(&ctx->jugador_mesa->estado_partida, pos_jugador, tantos);

    if(!IS_BIT_SET(resultado_jugada.eventos, EVENTO_TANTOS_CANTADOS)) return resultado_jugada;

    //cantar tantos. este es el mayor tanto actualmente.
    char ct_buffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA) + 1 + DIGITS(33))];
    sprintf(ct_buffer, "CT %u %u\n", pos_jugador, resultado_jugada.tantos_cantados);

    enviar_paquete_a_mesa(ct_buffer, ctx->jugador->mesa_id);

    return resultado_jugada;
}

TCS_PaqueteJugadaManejador paquetesPartidaTablaDespacho[] = {
    {"JC", handle_JC, 1},
    {"C", handle_C, 1},
    {"CT", handle_CT, 1}
};

static void recorrer_tabla_despacho_comparar_y_manejar(TCS_CommonPacketHandler tabla[], char *paquete, TCS_ContextoPaquete *ctx, size_t tamano_tabla){
    for(uint8_t i = 0; i < tamano_tabla; i++){

        if(strcmp(tabla[i].nombre_paquete, paquete)) continue;

        if(ctx->numero_args < tabla[i].requiredArgs) return;
        tabla[i].handler(ctx);
        break;
    }
}

void TCS_procesar_paquete(socket_t connfd, char *paquete){

    Conn *c = TCS_getConnByFd(connfd);

    if(!strcmp(paquete, "TCCHI")){
        uint16_t jugador_libre_encontrado_i = JUGADOR_INVALIDO_SERVER;
        uint8_t conn_repetida = 0;

        for (uint16_t i = 0; i < MAX_JUGADORES_SERVER; i++) {
            if (jugadores[i].conn != NULL) continue;
            jugador_libre_encontrado_i = i;

            if(jugadores[i].conn == c){
                conn_repetida = 1;
                break;
            }
        }

        if(conn_repetida) return;

        if(jugador_libre_encontrado_i == JUGADOR_INVALIDO_SERVER) TCS_closeAndClearConn(c);

        jugadores[jugador_libre_encontrado_i].conn = c;
        jugadores[jugador_libre_encontrado_i].mesa_id = MESA_INVALIDA;

        enviar_paquete("HI\n", jugador_libre_encontrado_i);
        return;
    }

    uint8_t jugador_id = TCS_encontrar_jugador_id_por_conn(c);

    

    if(jugador_id == JUGADOR_INVALIDO_SERVER) return;

    char *p = paquete;
    char *start = p;
    uint8_t numero_args = 0;
    char argsStorage[PAQUETE_MAX_ARGS][1] = {{0}};
    char *args[PAQUETE_MAX_ARGS] = { NULL };
    uint8_t firstToken = 1;
    uint16_t tamano_paquete = 0;

    while (1) {
        tamano_paquete++;
        if(tamano_paquete == 65535){
            DEBUG_PRINT(printf("Packet too big!"));
            return;
        }
        if(*p == '\n' || *p == '\0'){
            *p = '\0';
            args[numero_args++] = start;
            break;
        } 
        if (*p == ' ') {
            *p = '\0';
            if(!firstToken) args[numero_args++] = start;
            else firstToken = 0;
            start = p + 1;
            if(numero_args == PAQUETE_MAX_ARGS) break;
        }
        p++;
    }
    for (uint8_t i = numero_args; i < PAQUETE_MAX_ARGS; i++) args[i] = argsStorage[i];
    
    DEBUG_PRINT(printf("ConnFD: %d | Packet: %s | args=%d\n", connfd, paquete, numero_args));

    Jugador *jugador = &jugadores[jugador_id];

    TCS_ContextoPaquete ctx = {
        .jugador = jugador,
        .args = args,
        .numero_args = numero_args,
        .conn = c,
        .tamano_paquete = tamano_paquete,
        .jugador_id = jugador_id
    };

    recorrer_tabla_despacho_comparar_y_manejar(commonPacketDispatchTable, paquete, &ctx, ARRAY_SIZE(commonPacketDispatchTable));

    if(jugador->mesa_id == MESA_INVALIDA){
        recorrer_tabla_despacho_comparar_y_manejar(paquetesVestibuloTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesVestibuloTablaDespacho));
    }else{
        ctx.jugador_mesa = &mesas[jugador->mesa_id];

        if(!ctx.jugador_mesa->partida_iniciada){
            recorrer_tabla_despacho_comparar_y_manejar(paquetesMesaTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesMesaTablaDespacho)); 
            return;
        }

        for(uint8_t i = 0; i < ARRAY_SIZE(paquetesPartidaTablaDespacho); i++){
            if(strcmp(paquetesPartidaTablaDespacho[i].nombre_paquete, paquete)) continue;

            if(ctx.numero_args < paquetesPartidaTablaDespacho[i].requiredArgs) return;
            ResultadoJugada resultado_jugada = paquetesPartidaTablaDespacho[i].handler(&ctx);

            if(resultado_jugada.eventos == 0){
                DEBUG_PRINT(printf("Jugada inválida de %u\n", jugador_id));
                enviar_paquete("X\n", jugador_id);
                return;
            } 

            if(resultado_jugada.puntos_ganados > 0){
                //sumar puntaje
                char spBuffer[SUM_COMMON_EXT(4)];
                sprintf(spBuffer, "SP %u %u\n", resultado_jugada.equipo_ganador, resultado_jugada.puntos_ganados);
                enviar_paquete_a_mesa(spBuffer, jugador->mesa_id);
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_ENVIDO_EN_MESA)){
                //terminar partida
                char emBuffer[SUM_COMMON_EXT(DIGITS(CARTA_INVALIDA) + DIGITS(CARTA_INVALIDA))];

                char *buffer_ptr = emBuffer;

                buffer_ptr += sprintf(buffer_ptr, "EM %u", resultado_jugada.cartas_mayor_envido[0]);
                if(resultado_jugada.cartas_mayor_envido[1] != CARTA_INVALIDA) buffer_ptr += sprintf(buffer_ptr, " %u", resultado_jugada.cartas_mayor_envido[1]);
                sprintf(buffer_ptr, "\n");

                enviar_paquete_a_mesa(emBuffer, jugador->mesa_id);
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_VICTORIA)){
                //terminar partida
                char tpBuffer[SUM_COMMON_EXT(1)];
                sprintf(tpBuffer, "TP %u\n", resultado_jugada.equipo_ganador);
                ctx.jugador_mesa->partida_iniciada = 0;
                enviar_paquete_a_mesa(tpBuffer, jugador->mesa_id);
                break;
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_RONDA_TERMINADA)){
                //nueva ronda
                //TODO: MANDAR UN NR AL INICIO DE LA PARTIDA TAMBIÉN O MANDAR AL MANO EN EL EP?
                char nrBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA))];
                sprintf(nrBuffer, "NR %u\n", ctx.jugador_mesa->estado_partida.jugador_turno_actual);
                enviar_paquete_a_mesa(nrBuffer, jugador->mesa_id);
                TCS_nueva_ronda(jugador->mesa_id); 
                break;
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_CANTO_FLAG_ACTIVADA)){
                char fc_buffer[SUM_COMMON_EXT(DIGITS(FLAG_INVALIDA))];
                sprintf(fc_buffer, "FC %u\n", resultado_jugada.canto_flag_activada);
                enviar_paquete_a_mesa(fc_buffer, jugador->mesa_id);
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_NUEVA_BAZA)){
                char b_buffer[SUM_COMMON_EXT(DIGITS(MAX_BAZAS_RONDA))];
                sprintf(b_buffer, "B %u\n", ctx.jugador_mesa->estado_partida.baza_actual);

                enviar_paquete_a_mesa(b_buffer, jugador->mesa_id);
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_ENVIDO_NO_DISPONIBLE)){
                enviar_paquete_a_mesa("NE\n", jugador->mesa_id);
            }

            if(IS_BIT_SET(resultado_jugada.eventos, EVENTO_TIENE_RETRUCO)){
                char trBuffer[SUM_COMMON_EXT(1)];
                sprintf(trBuffer, "TR %u\n", IS_BIT_SET(ctx.jugador_mesa->estado_partida.flags_ronda, FLAG_TIENE_RETRUCO));
                enviar_paquete_a_mesa(trBuffer, jugador->mesa_id);
            }
            
            switch(ctx.jugador_mesa->estado_partida.ronda_estado){
                case RONDA_ESTADO_JUGANDO_BAZA: {
                    //turno baza
                    char tbBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA))];
                    sprintf(tbBuffer, "TB %u\n", ctx.jugador_mesa->estado_partida.jugador_turno_actual);
                    enviar_paquete_a_mesa(tbBuffer, jugador->mesa_id);
                    break;
                }
                case RONDA_ESTADO_EN_CANTO: {
                    //turno canto
                    char tcBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA) + 1 + DIGITS(CANTO_INVALIDO))];
                    sprintf(tcBuffer, "TC %u %u\n",
                    ctx.jugador_mesa->estado_partida.jugador_turno_canto,
                    ctx.jugador_mesa->estado_partida.canto_ofrecido);
                    enviar_paquete_a_mesa(tcBuffer, jugador->mesa_id);
                    break;
                }
                case RONDA_ESTADO_RONDA_TANTOS: {
                    //turno tantos
                    char ttBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA))];
                    sprintf(ttBuffer, "TT %u\n", ctx.jugador_mesa->estado_partida.jugador_turno_canto);
                    enviar_paquete_a_mesa(ttBuffer, jugador->mesa_id);
                    break;
                }
                default: {break;}
            }
            break;
        }
    }
}

uint8_t TCS_iniciar_servidor(uint16_t puerto){

    seed = time(NULL);

    for(uint16_t i = 0; i < MAX_CONEXIONES; i++) conexiones[i].fd = INVALID_FD;
    for(uint16_t i = 0; i < MAX_JUGADORES_SERVER; i++) TCS_reiniciar_jugador(i);
    for(uint16_t i = 0; i < MAX_MESAS; i++) TCS_reiniciar_mesa(i);

    int sockfd, connfd, len; 
    struct sockaddr_in servaddr, cli; 
  
    // socket create and verification 
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sockfd == -1) { 
        printf("socket creation failed...\n"); 
        return 1;
    }
    else printf("Socket successfully created..\n"); 
    bzero(&servaddr, sizeof(servaddr)); 
  
    // assign IP, PORT 
    servaddr.sin_family = AF_INET; 
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    servaddr.sin_port = htons(puerto); 
    
    int opt = 1;

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));

    // Binding newly created socket to given IP and verification 
    if (bind(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) {
        perror("bind");
        close(sockfd);
        return 1;
    }else printf("Socket successfully binded..\n"); 
  
    // Now server is ready to listen and verification 
    if ((listen(sockfd, 5)) != 0) { 
        printf("Listen failed...\n"); 
        return 1; 
    }else printf("Server listening..\n"); 
    len = sizeof(cli); 

    
    char paquete[MAX]; 
    int n; 
    for (;;) {
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(sockfd, &readfds);
        
        int maxfd = sockfd;

        // Agregar todos los clientes al select
        for (uint16_t i = 0; i < MAX_CONEXIONES; i++) {

            if (conexiones[i].fd == INVALID_FD) continue;

            FD_SET(conexiones[i].fd, &readfds);

            if (conexiones[i].fd > maxfd) maxfd = conexiones[i].fd;
        }

        int actividad = select(maxfd + 1, &readfds, NULL, NULL, NULL);

        if (actividad < 0) {
            perror("select");
            continue;
        }


        if (actividad < 0) {
            perror("select");
            continue;
        }

        //revisar si el cliente es nuevo

        if(FD_ISSET(sockfd, &readfds)){
            struct sockaddr_in cli;
            socklen_t len = sizeof(cli);
            socket_t connfd = accept(sockfd, (SA*)&cli, &len);
            if(connfd < 0) perror("accept");
            else{
                char ip[16];
                inet_ntop(AF_INET, &cli.sin_addr, ip, sizeof(ip));

                DEBUG_PRINT(printf("Cliente intentando conectar desde %s\n", ip));

                uint8_t conexion_encontrada = 0;

                for (uint16_t i = 0; i < MAX_CONEXIONES; i++) {
                    if (conexiones[i].fd == INVALID_FD) {
                        conexiones[i].fd = connfd;
                        conexiones[i].ultimo_latido = time(NULL);
                        // conexiones[i].lastCh = time(NULL);
                        safe_strcpy(conexiones[i].ip, ip, sizeof(conexiones[i].ip));
                        // if(fd == ws_fd) conexiones[i].flags |= CONN_IS_WS_FLAG;
                        // else validateClientAndStartHandshake(connfd, &master, &conexiones[i]);
                        conexion_encontrada = 1;
                        break;
                    }
                }

                if(!conexion_encontrada){
                    DEBUG_PRINT(printf("Sin slot de conexión para el cliente\n"));
                    close(connfd);
                }
            }
        }

        time_t ahora = time(NULL);

        //loopear conexiones a ver si tienen algo

        for(uint16_t i = 0; i < MAX_CONEXIONES; i++){
            socket_t connfd = conexiones[i].fd;

            if(connfd == INVALID_FD) continue;

            Conn *conn = &conexiones[i];

            if(ahora - conn->ultimo_latido > LATIDO_TIEMPO_LIMITE_SEGUNDOS){
                desconectar_cliente_o_conn(conn);
                continue;
            }

            //si tiene algo...
            if(FD_ISSET(connfd, &readfds)){
                ssize_t n = read(connfd, paquete, MAX - 1);

                if (n == 0) {
                    printf("Cliente desconectado: fd=%d\n", connfd);
                    desconectar_cliente_o_conn(conn);
                    continue;
                }

                if (n < 0) {
                    if (errno == EINTR) {
                        continue;
                    }

                    perror("read");
                    desconectar_cliente_o_conn(conn);
                    continue;
                }

                paquete[n] = '\0';

                char *p = paquete;
                char *start = p;

                while (1) {
                    if(*p == '\0'){
                        if(*start != '\0'){ 
                            DEBUG_PRINT(printf("PROCESANDO: %s\n", start)); 
                            TCS_procesar_paquete(connfd, start); 
                        }
                        break;
                    }
                    if (*p == '\n') {
                        *p = '\0';
                        DEBUG_PRINT(printf("PROCESANDO: %s\n", start));
                        TCS_procesar_paquete(connfd, start);
                        start = p + 1;
                    }
                    p++;
                }

                memset(paquete, 0, MAX);
            }
        }
    }
}