#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h> // read(), write(), close()
#include <arpa/inet.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 4 ///<PP \0
#define SUM_COMMON_EXT(a) ((size_t)((a) + (COMMON_PACKET_BYTES_EXTENSION))) ///<Apply COMMON_PACKET_BYTES_EXTENSION to (a)

#define A_BUFFER_SIZE 3 + DIGITS(MAX_JUGADORES_MESA)

typedef struct{
    socket_t fd;
    char ip[INET_ADDRSTRLEN];
} 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 conexiones[MAX_CONEXIONES];
Jugador jugadores[MAX_JUGADORES_SERVER];
Mesa mesas[MAX_MESAS];

/**Use memcpy to copy *src to *dst and make sure there is a \0 at the end (dst_size)*/
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) */
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 && 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. */
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. */
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;
} ContextoPaquete;

typedef void (*HandlerFunc)(ContextoPaquete *ctx);
typedef ResultadoJugada (*HandlerJugada)(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 parseInt(const char *s)
{
    uint16_t value = 0;
    while (*s >= '0' && *s <= '9') value = value * 10 + (*s++ - '0');
    return value;
}

Conn* 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 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 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 closeAndClearConn(Conn *c){
    close(c->fd);
    c->fd = INVALID_FD;
    c->ip[0] = '\0';
}

void 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 reiniciar_jugador(uint16_t jugador_id){
    jugadores[jugador_id] = (Jugador){0};

    jugadores[jugador_id].mesa_id = MESA_INVALIDA;
}

void 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 abandonar_mesa(uint16_t mesa_id, uint16_t jugador_id){
    Mesa *mesa = &mesas[mesa_id];

    uint8_t pos_jugador = 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) 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;
            
            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];
    closeAndClearConn(jugador->conn);
    // jugador->flags |= jugador_LEAVING_FLAG;
    // if(jugador->flags & jugador_JOINED_FLAG) jugadorCount--;
    if(jugador->mesa_id != MESA_INVALIDA){
        abandonar_mesa(jugador->mesa_id, jugador_id);
    }
    reiniciar_jugador(jugador_id);
}

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

    if(jugador_id == JUGADOR_INVALIDO_SERVER){
        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 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;
} CommonPacketHandler;

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

void handle_UM(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) 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);
        }
    }
}

void handle_CA(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 = 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 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 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);
    }

    anunciar_turno_baza_en_mesa(mesa_id);
}

void handle_EP(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;

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

CommonPacketHandler commonPacketDispatchTable[] = {
    {"UM", handle_UM, 1}
}; /**< This is where most packets are declared. Refer to the official network protocol,
CommonPacketHandler (to understand how to implement a new package) and ContextoPaquete*/

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

ResultadoJugada handle_JC(ContextoPaquete *ctx){
    uint8_t carta_id = parseInt(ctx->args[0]);
    uint8_t pos_jugador = 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) + 3)];

        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(ContextoPaquete *ctx){
    uint8_t canto_id = parseInt(ctx->args[0]);

    uint8_t pos_jugador = 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[sizeof("C 0 00\n")];

    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(ContextoPaquete *ctx){
    uint8_t tantos = parseInt(ctx->args[0]);

    uint8_t pos_jugador = 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 + 2)];
    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;
}

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

void recorrer_tabla_despacho_comparar_y_manejar(CommonPacketHandler tabla[], char *paquete, 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 procesar_paquete(socket_t connfd, char *paquete){

    Conn *c = 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) 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 = 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("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];

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

    if(jugador->mesa_id == MESA_INVALIDA){
        recorrer_tabla_despacho_comparar_y_manejar(commonPacketDispatchTable, paquete, &ctx, ARRAY_SIZE(commonPacketDispatchTable));
    }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_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);
                nueva_ronda(jugador->mesa_id); 
                break;
            }

            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 iniciar_servidor(uint16_t puerto){

    for(uint16_t i = 0; i < MAX_CONEXIONES; i++) conexiones[i].fd = INVALID_FD;
    for(uint16_t i = 0; i < MAX_JUGADORES_SERVER; i++) reiniciar_jugador(i);
    for(uint16_t i = 0; i < MAX_MESAS; i++) 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;

    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt");
        close(sockfd);
        return 1;
    }

    // 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;
        }

        //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].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);
                }
            }
        }

        //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;

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

                if (n <= 0) {
                    desconectar_cliente_o_conn(&conexiones[i]);
                    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)); 
                            procesar_paquete(connfd, start); 
                        }
                        break;
                    }
                    if (*p == '\n') {
                        *p = '\0';
                        DEBUG_PRINT(printf("PROCESANDO: %s\n", start));
                        procesar_paquete(connfd, start);
                        start = p + 1;
                    }
                    p++;
                }

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