#include <arpa/inet.h> // inet_addr()
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <strings.h> // bzero()
#include <sys/socket.h>
#include <unistd.h> // read(), write(), close()

#include "truco.h"
#include "truco_cmd_render.h"

#define MAX 80
#define PORT 8080
#define SA struct sockaddr

#define PAQUETE_MAX_ARGS 5

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

#define DEBUG 1

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

int sockfd = 0;

#define ESTADO_CONECTANDO 0
#define ESTADO_EN_LOBBY 1
#define ESTADO_EN_SALA 2
#define ESTADO_EN_PARTIDA 3

//cliente
uint8_t estado = ESTADO_CONECTANDO;

//sala
uint8_t es_host = 0;

//partida-sala
uint8_t jugadores = 0;
uint8_t numero_jugador = JUGADOR_INVALIDO;

uint8_t cartas_recibidas[3];
uint8_t cartas_bazas[3][6];
uint8_t jugador_turno_actual = JUGADOR_INVALIDO;
uint8_t maximo_envido_cantado = 0;
uint8_t ronda_estado = 0;
uint8_t ultimo_canto = CANTO_INVALIDO;
uint8_t baza_actual = 0;
uint16_t flags_ronda = 0;
uint8_t puntaje[2];
uint8_t envido = 0;
typedef struct {
    char **args;
    uint8_t numero_args;
} ContextoPaquete;

typedef void (*HandlerFunc)(ContextoPaquete *ctx);

typedef struct {
    const char *nombre_paquete;
    HandlerFunc handler;
} PaqueteManejador;

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

void enviar_paquete(const char *paquete){
    DEBUG_PRINT(printf("ENVIANDO %s\n", paquete));
    int n = send(sockfd, paquete, strlen(paquete), MSG_NOSIGNAL);
}


void handle_HI(ContextoPaquete *ctx){
    printf("Conexión satisfactoria. Intentando unir a mesa 0...\n");
    enviar_paquete("UM 0\n");
    estado = ESTADO_EN_LOBBY;
}

void handle_UM(ContextoPaquete *ctx){
    printf("Unido a mesa %s.\n", ctx->args[0]);

    printf("Asiento %u asignado.\n", parseInt(ctx->args[1])+1);
    numero_jugador = parseInt(ctx->args[1]);

    estado = ESTADO_EN_SALA;

    jugadores++;
}

void handle_NJ(ContextoPaquete *ctx){
    printf("Nuevo jugador en asiento %u.\n", parseInt(ctx->args[0])+1);
    jugadores++;
}

void handle_A(ContextoPaquete *ctx){
    uint8_t host = parseInt(ctx->args[0]);
    if(host == numero_jugador){
        printf("Eres el anfitrión de la sala.\n");
        es_host = 1;
    }else{
        printf("Jugador %u es el anfitrión de la sala.\n", host+1);
        es_host = 0;
    } 
}

void handle_SJ(ContextoPaquete *ctx){
    printf("Jugador en asiento %u se fue de la sala.\n", parseInt(ctx->args[0])+1);
    jugadores--;
}

void handle_EP(ContextoPaquete *ctx){
    printf("La partida ha comenazdo.\n");
    estado = ESTADO_EN_PARTIDA;
}

void handle_NR(ContextoPaquete *ctx){
    baza_actual = 0;
    maximo_envido_cantado = 0;
    flags_ronda = 0;
    envido = 0;
    jugador_turno_actual = JUGADOR_INVALIDO;
    printf("Comienza una nueva ronda. Jugador %s es el mano.\n", ctx->args[0]);
}

void handle_MC(ContextoPaquete *ctx){
    printf("Recibido:\n");
    for(uint8_t i = 0; i < ctx->numero_args; i++){
        uint8_t carta_i = parseInt(ctx->args[i]);
        cartas_recibidas[i] = carta_i;
        printf("%u de %s\n",
            cartas[carta_i].valor_carta,
            nombrePalo(cartas[carta_i].palo)
        );
    }
        uint8_t max_envido_en_mano = 0;

        for(uint8_t i = 0; i < 3; i++){
            uint8_t carta_i = cartas_recibidas[i];
            
            for(uint8_t j = 0; j < 3; j++){
                if(j == i) continue;
                uint8_t carta_j = cartas_recibidas[j];

                if(cartas[carta_i].palo == cartas[carta_j].palo){
                    uint8_t valor_envido_pares = 20 + valor_tanto_carta(carta_i) + valor_tanto_carta(carta_j);
                    if(valor_envido_pares > max_envido_en_mano) max_envido_en_mano = valor_envido_pares;
                }
            }

            if(valor_tanto_carta(carta_i) > max_envido_en_mano) max_envido_en_mano = valor_tanto_carta(carta_i);
        }

        envido = max_envido_en_mano;
}

void handle_TB(ContextoPaquete *ctx){
    jugador_turno_actual = parseInt(ctx->args[0]);
    ronda_estado = RONDA_ESTADO_JUGANDO_BAZA;

    printf("Turno de jugador %u\n", jugador_turno_actual+1);
}

void handle_TC(ContextoPaquete *ctx){
    jugador_turno_actual = parseInt(ctx->args[0]);
    ronda_estado = RONDA_ESTADO_EN_CANTO;

    printf("Jugador %u responde a %s\n", jugador_turno_actual+1, nombreCanto(parseInt(ctx->args[1])));
}

void handle_TT(ContextoPaquete *ctx){
    jugador_turno_actual = parseInt(ctx->args[0]);
    ronda_estado = RONDA_ESTADO_RONDA_TANTOS;

    printf("Turno de jugador %u de cantar sus tantos\n", jugador_turno_actual+1);
}

void handle_JC(ContextoPaquete *ctx){
    uint8_t carta_jugada_id = parseInt(ctx->args[1]);
    uint8_t jugador_i = parseInt(ctx->args[0]);

    //me saco la carta de la mano
    if(jugador_i == numero_jugador){
        for(uint8_t i = 0; i < 3; i++){
            if(cartas_recibidas[i] != carta_jugada_id) continue;

            cartas_recibidas[i] = CARTA_INVALIDA;
            break;
        } 
    }

    printf("Jugador %u jugó: %u de %s\n\n", 
    jugador_i+1, cartas[carta_jugada_id].valor_carta, 
    nombrePalo(cartas[carta_jugada_id].palo));

    cartas_bazas[baza_actual][jugador_i] = carta_jugada_id;
}

void handle_C(ContextoPaquete *ctx){
    uint8_t canto_i = parseInt(ctx->args[1]);
    if(canto_i < FLAG_INVALIDA){
        SET_BIT(flags_ronda, canto_i);
        ultimo_canto = canto_i;
    }else ultimo_canto = CANTO_INVALIDO;

    printf("Jugador %u cantó: %s\n", parseInt(ctx->args[0])+1, nombreCanto(canto_i));
}

void handle_CT(ContextoPaquete *ctx){
    //NO ESTOY SETEANDO EL EQUIPO CON MAYOR ENVIDO OJO!!!!!!!!!!!!
    uint8_t tantos_cantados = parseInt(ctx->args[1]);
    if(tantos_cantados > maximo_envido_cantado) maximo_envido_cantado = tantos_cantados;
    printf("Jugador %u cantó %u puntos de tantos.\n", parseInt(ctx->args[0])+1, tantos_cantados);
}

void handle_B(ContextoPaquete *ctx){
    baza_actual = parseInt(ctx->args[0]);
}

void handle_NE(ContextoPaquete *ctx){
    SET_BIT(flags_ronda, FLAG_ENVIDO_NO_DISPONIBLE);
}

void handle_TR(ContextoPaquete *ctx){
    if(*ctx->args[0] == '1') SET_BIT(flags_ronda, FLAG_TIENE_RETRUCO);
}

void handle_SP(ContextoPaquete *ctx){
    uint8_t puntuador = parseInt(ctx->args[0]);
    uint8_t puntos = parseInt(ctx->args[1]);
    puntaje[puntuador] += puntos;
    printf("%s %u anotó %u punto(s). Ahora tiene %u puntos\n", jugador_o_equipo(jugadores), puntuador+1, 
    puntos, puntaje[puntuador]);
}

void handle_TP(ContextoPaquete *ctx){
    printf("%s %s gana la partida.\n", jugador_o_equipo(jugadores), ctx->args[0]);
}

uint8_t recorrer_tabla_despacho_comparar_y_manejar(PaqueteManejador 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;

        tabla[i].handler(ctx);
        return 1;
    }
    return 0;
}

PaqueteManejador paquetesConectandoTablaDespacho[] = {
    {"HI", handle_HI},
};

void handle_X(ContextoPaquete *ctx){
    printf("Acción inválida\n");
}

PaqueteManejador paquetesSalaTablaDespacho[] = {
    {"UM", handle_UM},
    {"NJ", handle_NJ},
    {"A", handle_A},
    {"SJ", handle_SJ},
    {"EP", handle_EP},
};

PaqueteManejador paquetesPartidaTablaDespacho[] = {
    {"NR", handle_NR},
    {"MC", handle_MC},
    {"JC", handle_JC},
    {"C", handle_C},
    {"CT", handle_CT},
    {"B", handle_B},
    {"NE", handle_NE},
    {"TR", handle_TR},
    {"SP", handle_SP},
    {"TP", handle_TP},
};

PaqueteManejador paquetesTurnoTablaDespacho[] = {
    {"TB", handle_TB},
    {"TC", handle_TC},
    {"TT", handle_TT},
    {"X", handle_X},
};

void procesar_paquete(char *paquete){
    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("Packet: %s | args=%u\n", paquete, numero_args));

    ContextoPaquete ctx = {
        .args = args,
        .numero_args = numero_args
    };

    recorrer_tabla_despacho_comparar_y_manejar(paquetesConectandoTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesConectandoTablaDespacho));
    // recorrer_tabla_despacho_comparar_y_manejar(paquetesGeneralesTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesGeneralesTablaDespacho)); 
    recorrer_tabla_despacho_comparar_y_manejar(paquetesSalaTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesSalaTablaDespacho));
    recorrer_tabla_despacho_comparar_y_manejar(paquetesPartidaTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesPartidaTablaDespacho));
    uint8_t viene_paquete_turno = recorrer_tabla_despacho_comparar_y_manejar(paquetesTurnoTablaDespacho, paquete, &ctx, ARRAY_SIZE(paquetesTurnoTablaDespacho));


    switch(estado){
        case ESTADO_CONECTANDO:
        break;
        case ESTADO_EN_SALA:
                uint8_t accion = 0;
    
                if(es_host){
                    printf("3: Empezar Partida\n");
                    printf("\nAcción: ");
                    scanf("%u", &accion);
                }

                switch(accion){
                    case 3:
                        if(jugadores % 2){
                            printf("Se necesita un número par de jugadores para iniciar.\n");
                            break;
                        }
                        enviar_paquete("EP\n");
                        break;
                    default: break;
                }
            break;
        break;
        case ESTADO_EN_PARTIDA:
            if(!viene_paquete_turno ||
            jugador_turno_actual != numero_jugador) return;

            mostrar_controles_y_manejar_accion_online(ronda_estado, flags_ronda, 
            cartas_recibidas, envido, 
            maximo_envido_cantado, ultimo_canto,
            numero_jugador % 2);
        break;
        default: break;
    }

}

int main(){
    int local_sockfd, connfd;
    struct sockaddr_in servaddr, cli;

    // socket create and verification
    local_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (local_sockfd == -1) {
        printf("socket creation failed...\n");
        return 1;
    }
    else
        printf("Socket successfully created..\n");
        sockfd = local_sockfd;
    bzero(&servaddr, sizeof(servaddr));

    // assign IP, PORT
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    servaddr.sin_port = htons(PORT);

    // connect the client socket to server socket
    if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr))
        != 0) {
        printf("connection with the server failed...\n");
        return 1;
    }
    else
        printf("connected to the server..\n");

    char paquete[MAX];
    int n;

    enviar_paquete("TCCHI\n");

    for (;;) {
        n = read(sockfd, paquete, sizeof(paquete) - 1);

        if (n < 0) {
            perror("read");
            break;
        }

        if (n == 0) {
            printf("El servidor cerró la conexión.\n");
            break;
        }

        paquete[n] = '\0';
        
        // printf("From Server: %s", paquete);

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

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

    // close the socket
    close(sockfd);
}

ResultadoJugada TCIC_jugar_baza(EstadoPartida *partida, uint8_t carta_i, uint16_t _partida_i){
    char jcBuffer[sizeof("JC 00\n")];
    sprintf(jcBuffer, "JC %u\n", cartas_recibidas[carta_i]);
    enviar_paquete(jcBuffer);
};

ResultadoJugada TCIC_cantar(EstadoPartida *_partida, uint8_t canto, uint16_t _partida_i){
    char cBuffer[sizeof("C 00\n")];
    sprintf(cBuffer, "C %u\n", canto);
    enviar_paquete(cBuffer);    
};

ResultadoJugada TCIC_cantar_tanto(EstadoPartida *_partida, uint16_t _partida_i){
    char ctBuffer[sizeof("CT 00\n")];
    sprintf(ctBuffer, "CT %u\n", envido);
    enviar_paquete(ctBuffer);
};