#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 enum{
    FLAG_MESA_INICIALIZADA,
    FLAG_PARTIDA_INICIADA,
} FlagsMesa;

typedef struct{
    EstadoPartida estado_partida;
    uint16_t jugadores[MAX_JUGADORES_MESA];
    uint8_t flags_mesa;
} Mesa;

static Conn conexiones[MAX_CONEXIONES];
static Jugador jugadores[MAX_JUGADORES_SERVER];
static Mesa 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, uint16_t jugadorId){
    if (jugadorId >= MAX_JUGADORES_SERVER) return;
    socket_t fd = jugadores[jugadorId].conn->fd;
    if (SOCKET_ERROR_CHECK(fd)) 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, uint16_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;
}

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

static uint8_t TCS_obtener_numero_jugadores_mesa(uint16_t mesa_id){
    uint8_t numero_jugadores = 0;

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

        numero_jugadores++;
    }

    return numero_jugadores;
}

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

static uint16_t obtener_jugador_id_por_asiento_i(uint16_t mesa_id, uint8_t asiento_i){
    return mesas[mesa_id].jugadores[asiento_i];
}

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 i = 0; i < MAX_JUGADORES_MESA; i++){
        mesas[mesa_id].jugadores[i] = 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_mesa(uint16_t mesa_id, uint8_t asiento_nuevo_anfitrion){
    uint16_t jugador_id = obtener_jugador_id_por_asiento_i(mesa_id, asiento_nuevo_anfitrion);

    if(jugador_id >= JUGADOR_INVALIDO_SERVER) return;

    jugadores[jugador_id].mesa_rol = ROL_ANFITRION;

    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_manejar_turno_bot(uint16_t 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);
    }

    if(!TCS_obtener_numero_jugadores_mesa(mesa_id)) TCS_reiniciar_mesa(mesa_id);
    else{
        if(jugadores[jugador_id].mesa_rol == ROL_ANFITRION){
            uint8_t hay_host = 0;

            for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
                uint16_t jugador_id = obtener_jugador_id_por_asiento_i(mesa_id, i);

                if(jugador_id >= JUGADOR_INVALIDO_SERVER) continue;
                if(jugadores[jugador_id].mesa_rol == ROL_ANFITRION) {hay_host = 1; break;}
                
            }

            if(!hay_host){
                for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
                    if(mesa->jugadores[i] >= JUGADOR_INVALIDO_SERVER) continue;
                    
                    TCS_setear_anfitrion_mesa(mesa_id, i);
                    break;
                }
            }
        }

        if(IS_BIT_SET(mesa->flags_mesa, FLAG_PARTIDA_INICIADA) && obtener_jugador_turno_actual(&mesa->estado_partida) == pos_jugador){
            TCS_manejar_turno_bot(mesa_id);
        }

    
    }

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

    enviar_paquete("V\n", jugador_id);
}

void desconectar_cliente(uint16_t jugador_id){
    Jugador *jugador = &jugadores[jugador_id];
    if(jugador->mesa_id != MESA_INVALIDA){
        TCS_abandonar_mesa(jugador->mesa_id, jugador_id);
    }
    TCS_closeAndClearConn(jugador->conn);
    // jugador->flags |= jugador_LEAVING_FLAG;
    // if(jugador->flags & jugador_JOINED_FLAG) jugadorCount--;
    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 setear_max_jugadores_mesa(uint16_t mesa_id, uint8_t max_jugadores_mesa){

    Mesa *m = &mesas[mesa_id];

    if(max_jugadores_mesa < TCS_obtener_numero_jugadores_mesa(mesa_id)) return;

    if(max_jugadores_mesa % 2 != 0 ||
    max_jugadores_mesa > 6 ||
    max_jugadores_mesa == 0) return;

    m->estado_partida.numero_jugadores = max_jugadores_mesa;

    char mjBuffer[SUM_COMMON_EXT(2)];
    sprintf(mjBuffer, "MJ %u\n", max_jugadores_mesa);
    enviar_paquete_a_mesa(mjBuffer, mesa_id);
}

static void setear_max_puntaje_mesa(uint16_t mesa_id, uint8_t max_puntaje_mesa){
    Mesa *m = &mesas[mesa_id];

    if(m->estado_partida.max_puntos == max_puntaje_mesa) return;

    if(max_puntaje_mesa != 15 && max_puntaje_mesa != 30) return;

    m->estado_partida.max_puntos = max_puntaje_mesa;

    char mpBuffer[SUM_COMMON_EXT(2)];
    sprintf(mpBuffer, "MP %u\n", max_puntaje_mesa);

    enviar_paquete_a_mesa(mpBuffer, mesa_id);
}

static void setear_flags_mesa(uint16_t mesa_id, uint8_t flags){
    Mesa *m = &mesas[mesa_id];

    if(m->estado_partida.flags_partida == flags) return;

    m->estado_partida.flags_partida = flags;

    char fp_buffer[SUM_COMMON_EXT(DIGITS(255))];
    sprintf(fp_buffer, "FP %u\n", m->estado_partida.flags_partida);

    enviar_paquete_a_mesa(fp_buffer, mesa_id);
}



static void inicializar_mesa(uint16_t mesa_id, uint8_t max_jugadores_mesa, uint8_t max_puntaje_mesa, uint8_t flags){

    // const Mesa *m = &mesas[mesa_id];

    SET_BIT(flags, FLAG_MESA_INICIALIZADA);

    setear_max_jugadores_mesa(mesa_id, max_jugadores_mesa);
    setear_max_puntaje_mesa(mesa_id, max_puntaje_mesa);

    mesas[mesa_id].flags_mesa = flags;
}

static void TCS_unir_jugador_a_mesa(uint16_t jugador_id, uint16_t mesa_id){
    if(mesa_id >= MESA_INVALIDA) return;

    Mesa *m = &mesas[mesa_id];
    Jugador *j = &jugadores[jugador_id];

    uint8_t jugadores_mesa = TCS_obtener_numero_jugadores_mesa(mesa_id);
    
    if((jugadores_mesa > 0 && jugadores_mesa >= m->estado_partida.numero_jugadores) ||
    IS_BIT_SET(m->flags_mesa, FLAG_PARTIDA_INICIADA) ||
    j->mesa_id == mesa_id) return;


    uint8_t silla_encontrada = MAX_JUGADORES_MESA;

    uint8_t es_primer_jugador = 1;

    for(uint8_t i = 0; i < MAX_JUGADORES_MESA; i++){
        if(m->jugadores[i] != JUGADOR_INVALIDO_SERVER){
            es_primer_jugador = 0;
            continue;
        }

        if(silla_encontrada != MAX_JUGADORES_MESA) continue;

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

    if(silla_encontrada == MAX_JUGADORES_MESA) return;

    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, jugador_id);

    if(es_primer_jugador){
        if(!IS_BIT_SET(m->flags_mesa, FLAG_MESA_INICIALIZADA)){
            //mesa 1v1 a 15 sin flor
            //clasico mano a mano
            inicializar_mesa(mesa_id, 2, 15, 0);
        }
        TCS_setear_anfitrion_mesa(mesa_id, silla_encontrada);
    } 
    else j->mesa_rol = ROL_JUGADOR;

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

        uint16_t jugador_i_id = m->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, j->apodo);
        enviar_paquete(njBuffer, jugador_i_id);

        sprintf(njBuffer, "NJ %u %s\n", i, p_jugador_i->apodo);
        enviar_paquete(njBuffer, 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, jugador_id);
        }
    }

    char mjBuffer[SUM_COMMON_EXT(2)];
    sprintf(mjBuffer, "MJ %u\n", m->estado_partida.numero_jugadores);
    enviar_paquete(mjBuffer, jugador_id);

    char mpBuffer[SUM_COMMON_EXT(2)];
    sprintf(mpBuffer, "MP %u\n", m->estado_partida.max_puntos);
    enviar_paquete(mpBuffer, jugador_id);

    char fp_buffer[SUM_COMMON_EXT(3)];
    sprintf(fp_buffer, "FP %u\n", m->estado_partida.flags_partida);
    enviar_paquete(fp_buffer, jugador_id);

    enviar_paquete("TU\n", jugador_id);
}

static void handle_LM(TCS_ContextoPaquete *ctx){

    char LM_buffer[SUM_COMMON_EXT((DIGITS(MAX_MESAS) + sizeof(" 1 6 30 255")) * MAX_MESAS)];
    char *buffer_ptr = LM_buffer;

    buffer_ptr += sprintf(buffer_ptr, "LM");

    for(uint16_t i = 0; i < MAX_MESAS; i++){

        Mesa *m = &mesas[i];

        uint8_t jugadores_en_mesa = TCS_obtener_numero_jugadores_mesa(i);

        printf("%u\n", jugadores_en_mesa);

        if(!IS_BIT_SET(m->flags_mesa, FLAG_MESA_INICIALIZADA) || 
        IS_BIT_SET(m->flags_mesa, FLAG_PARTIDA_INICIADA) ||
        m->estado_partida.numero_jugadores == jugadores_en_mesa) continue;

        buffer_ptr += sprintf(buffer_ptr, 
        " %u %u %u %u %u",
        i,
        jugadores_en_mesa, 
        m->estado_partida.numero_jugadores, 
        m->estado_partida.max_puntos,
        m->estado_partida.flags_partida);
    }

    buffer_ptr += sprintf(buffer_ptr, "\n");

    enviar_paquete(LM_buffer, ctx->jugador_id);
}

static void handle_CM(TCS_ContextoPaquete *ctx){
    uint16_t mesa_libre = MESA_INVALIDA;

    for(uint16_t i = 0; i < MESA_INVALIDA; i++){
        if(IS_BIT_SET(mesas[i].flags_mesa, FLAG_MESA_INICIALIZADA)) continue;

        mesa_libre = i;
        break;
    }

    if(mesa_libre == MESA_INVALIDA) return;

    TCS_unir_jugador_a_mesa(ctx->jugador_id, mesa_libre);
}

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

    TCS_unir_jugador_a_mesa(ctx->jugador_id, mesa_id);
}

static void handle_A(TCS_ContextoPaquete *ctx){

    if(ctx->jugador->mesa_rol != ROL_ANFITRION) return;

    uint8_t asiento_i = parseInt(ctx->args[0]);

    if(asiento_i >= JUGADOR_INVALIDO) return;
    
    TCS_setear_anfitrion_mesa(ctx->jugador->mesa_id, asiento_i);

}

static void handle_SM(TCS_ContextoPaquete *ctx){
    TCS_abandonar_mesa(ctx->jugador->mesa_id, 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]);

    if(asiento >= MAX_JUGADORES_MESA) return;

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

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

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

        if(pos_jugador == asiento) return;

        ctx->jugador_mesa->jugadores[pos_jugador] = ctx->jugador_mesa->jugadores[asiento];
        ctx->jugador_mesa->jugadores[asiento] = ctx->jugador_id;

        sprintf(caBuffer, "CA %u %u\n", pos_jugador, asiento);
    }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;

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

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

static ResultadoJugada TCS_jugar_carta(uint16_t mesa_i, uint8_t jugador_i, uint8_t carta_i){

    Mesa *mesa = &mesas[mesa_i];

    ResultadoJugada resultado_jugada = jugar_baza(&mesa->estado_partida, jugador_i, carta_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", jugador_i, resultado_jugada.carta);
    enviar_paquete_a_mesa(jc_buffer, mesa_i);

    return resultado_jugada;
}

static ResultadoJugada TCS_cantar(uint16_t mesa_i, uint8_t jugador_i, uint8_t canto_i){
    Mesa *mesa = &mesas[mesa_i];

    ResultadoJugada resultado_jugada = cantar(&mesa->estado_partida, jugador_i, canto_i);

    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", jugador_i, resultado_jugada.canto);
    enviar_paquete_a_mesa(c_buffer, mesa_i);

    return resultado_jugada;
}

static ResultadoJugada TCS_cantar_tantos(uint16_t mesa_i, uint8_t jugador_i, uint8_t tantos){
    Mesa *mesa = &mesas[mesa_i];

    ResultadoJugada resultado_jugada = cantar_tanto(&mesa->estado_partida, jugador_i, 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", jugador_i, resultado_jugada.tantos_cantados);

    enviar_paquete_a_mesa(ct_buffer, mesa_i);

    return resultado_jugada;
}


void TCS_nueva_ronda(uint16_t mesa_id){
    char nrBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA))];
    sprintf(nrBuffer, "NR %u\n", mesas[mesa_id].estado_partida.jugador_turno_actual);
    enviar_paquete_a_mesa(nrBuffer, mesa_id);

    for(uint8_t i = 0; i < mesas[mesa_id].estado_partida.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);

    uint8_t jugador_turno_actual = obtener_jugador_turno_actual(&mesas[mesa_id].estado_partida);
    uint8_t jugador_turno_actual_id = obtener_jugador_id_por_asiento_i(mesa_id, jugador_turno_actual);

    if(jugador_turno_actual_id != JUGADOR_INVALIDO_SERVER) return;

    TCS_manejar_turno_bot(mesa_id);
}

void TCS_procesar_resultado_jugada(ResultadoJugada *resultado_jugada, uint16_t mesa_id){

    if(!resultado_jugada->eventos) return;

    Mesa *m = &mesas[mesa_id];

    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, 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_tanto[0]);
        if(resultado_jugada->cartas_mayor_tanto[1] != CARTA_INVALIDA) buffer_ptr += sprintf(buffer_ptr, " %u", resultado_jugada->cartas_mayor_tanto[1]);
        sprintf(buffer_ptr, "\n");

        enviar_paquete_a_mesa(emBuffer, 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);
        CLEAR_BIT(m->flags_mesa, FLAG_PARTIDA_INICIADA);
        enviar_paquete_a_mesa(tpBuffer, mesa_id);
        return;
    }

    if(IS_BIT_SET(resultado_jugada->eventos, EVENTO_RONDA_TERMINADA)){
        TCS_nueva_ronda(mesa_id); 
        return;
    }

    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, 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", m->estado_partida.baza_actual);

        enviar_paquete_a_mesa(b_buffer, mesa_id);
    }

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

    if(IS_BIT_SET(resultado_jugada->eventos, EVENTO_FLOR_NO_DISPONIBLE)){
        enviar_paquete_a_mesa("NF\n", 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(m->estado_partida.flags_ronda, FLAG_TIENE_RETRUCO));
        enviar_paquete_a_mesa(trBuffer, mesa_id);
    }

    uint8_t jugador_turno_actual = obtener_jugador_turno_actual(&m->estado_partida);
    uint8_t jugador_turno_actual_id = obtener_jugador_id_por_asiento_i(mesa_id, jugador_turno_actual);

    ResultadoJugada resultado;
    
    switch(m->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", jugador_turno_actual);
            enviar_paquete_a_mesa(tbBuffer, 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",
            jugador_turno_actual,
            m->estado_partida.canto_ofrecido);
            enviar_paquete_a_mesa(tcBuffer, mesa_id);
            break;
        }
        case RONDA_ESTADO_RONDA_TANTOS: {
            //turno tantos
            char ttBuffer[SUM_COMMON_EXT(DIGITS(MAX_JUGADORES_MESA))];
            sprintf(ttBuffer, "TT %u\n", jugador_turno_actual);
            enviar_paquete_a_mesa(ttBuffer, mesa_id);
            break;
        }
        default: break;
    }

    if(jugador_turno_actual_id != JUGADOR_INVALIDO_SERVER) return;

    TCS_manejar_turno_bot(mesa_id);
}

void TCS_manejar_turno_bot(uint16_t mesa_id){
    Mesa *m = &mesas[mesa_id];
    uint8_t jugador_turno_actual = obtener_jugador_turno_actual(&m->estado_partida);

    ResultadoJugada resultado;

    switch(m->estado_partida.ronda_estado){
        case RONDA_ESTADO_JUGANDO_BAZA: {
            for(uint8_t i = 0; i < 3; i++){
                uint8_t carta_i = m->estado_partida.jugadores_cartas[jugador_turno_actual][i];

                if(carta_i == CARTA_INVALIDA) continue;

                resultado = TCS_jugar_carta(mesa_id, jugador_turno_actual, i);
                break;
            }
            break;
        }
        case RONDA_ESTADO_EN_CANTO: {
            resultado = TCS_cantar(mesa_id, jugador_turno_actual, CANTO_NO_QUIERO);
            break;
        }
        case RONDA_ESTADO_RONDA_TANTOS: {
            uint8_t tantos = calcular_envido_o_flor_segun_estado(&m->estado_partida, jugador_turno_actual).valor_tantos;

            if(puede_cantar_mayor_tanto(&m->estado_partida, tantos, jugador_turno_actual)){
                resultado = TCS_cantar_tantos(mesa_id, jugador_turno_actual, tantos);
            }else{
                resultado = TCS_cantar(mesa_id, jugador_turno_actual, CANTO_SON_BUENAS);
            }
            break;
        }
        default: break;
    }

    TCS_procesar_resultado_jugada(&resultado, mesa_id);
}

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

    if(ctx->jugador_mesa->estado_partida.numero_jugadores != TCS_obtener_numero_jugadores_mesa(ctx->jugador->mesa_id)) return;

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

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

    SET_BIT(ctx->jugador_mesa->flags_mesa, FLAG_PARTIDA_INICIADA);

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

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

    uint8_t jugador_id = obtener_jugador_id_por_asiento_i(ctx->jugador->mesa_id, parseInt(ctx->args[0]));

    if(jugador_id == JUGADOR_INVALIDO_SERVER) return;

    TCS_abandonar_mesa(ctx->jugador->mesa_id, jugador_id);
}



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

    setear_max_puntaje_mesa(ctx->jugador->mesa_id, parseInt(ctx->args[0]));
}


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

    setear_max_jugadores_mesa(ctx->jugador->mesa_id, parseInt(ctx->args[0]));
}

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

    setear_flags_mesa(ctx->jugador->mesa_id, parseInt(ctx->args[0]));
}

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[] = {
    {"LM", handle_LM, 0},
    {"CM", handle_CM, 0},
    {"UM", handle_UM, 1},
};

TCS_CommonPacketHandler paquetesMesaTablaDespacho[] = {
    {"SM", handle_SM, 0},
    {"CA", handle_CA, 1},
    {"EP", handle_EP, 0},
    {"A", handle_A, 1},
    {"E", handle_E, 1},
    {"MP", handle_MP, 1},
    {"MJ", handle_MJ, 1},
    {"FP", handle_FP, 1},
};

static 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;
        return TCS_jugar_carta(ctx->jugador->mesa_id, pos_jugador, i);
    }

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

static 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);

    return TCS_cantar(ctx->jugador->mesa_id, pos_jugador, canto_id);
}

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

    return TCS_cantar_tantos(ctx->jugador->mesa_id, pos_jugador, tantos);
}

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);
            return;
        } 

        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));
        return;
    }

    ctx.jugador_mesa = &mesas[jugador->mesa_id];

    if(!IS_BIT_SET(ctx.jugador_mesa->flags_mesa, FLAG_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", ctx.jugador_id));
            enviar_paquete("X\n", ctx.jugador_id);
            return;
        } 

        TCS_procesar_resultado_jugada(&resultado_jugada, ctx.jugador->mesa_id);

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