#include <stdint.h>

#define SET_BIT(var, pos)      ((var) |= (1U << (pos)))
#define CLEAR_BIT(var, pos)    ((var) &= ~(1U << (pos)))
#define IS_BIT_SET(var, pos)   (((var) >> (pos)) & 1U)

#define PALO_ESPADA 0
#define PALO_BASTO  1
#define PALO_ORO    2
#define PALO_COPA   3

#define TOTAL_CARTAS 40
#define MAX_JUGADORES 6
#define MAX_CARTAS_MANO 3
#define MAX_BAZAS_RONDA 3

#define CARTA_INVALIDA (TOTAL_CARTAS + 1)
#define JUGADOR_INVALIDO (MAX_JUGADORES +1)
#define EQUIPO_INVALIDO 2

typedef struct{
    uint8_t palo;
    uint8_t valor_carta;
} Carta;

const Carta cartas[TOTAL_CARTAS] = {
    {PALO_ESPADA, 1},
    {PALO_BASTO,  1},
    {PALO_ESPADA, 7},
    {PALO_ORO,    7},

    {PALO_ESPADA, 3},
    {PALO_BASTO,  3},
    {PALO_ORO,    3},
    {PALO_COPA,   3},

    {PALO_ESPADA, 2},
    {PALO_BASTO,  2},
    {PALO_ORO,    2},
    {PALO_COPA,   2},

    {PALO_ORO,    1},
    {PALO_COPA,   1},

    {PALO_ESPADA, 12},
    {PALO_BASTO,  12},
    {PALO_ORO,    12},
    {PALO_COPA,   12},

    {PALO_ESPADA, 11},
    {PALO_BASTO,  11},
    {PALO_ORO,    11},
    {PALO_COPA,   11},

    {PALO_ESPADA, 10},
    {PALO_BASTO,  10},
    {PALO_ORO,    10},
    {PALO_COPA,   10},

    {PALO_BASTO,  7},
    {PALO_COPA,   7},

    {PALO_BASTO,  6},
    {PALO_ORO,    6},
    {PALO_COPA,   6},
    {PALO_ESPADA, 6},

    {PALO_BASTO,  5},
    {PALO_ORO,    5},
    {PALO_COPA,   5},
    {PALO_ESPADA, 5},

    {PALO_BASTO,  4},
    {PALO_ORO,    4},
    {PALO_COPA,   4},
    {PALO_ESPADA, 4},
};

/*Posibles estados de ronda:
    -------------------------------
    Envido
    Envido Envido
    Real Envido
    Falta Envido

    Truco
    Retruco
    Vale 4

    Flor
    Contraflor
    Contraflor al resto

    Parda
    -------------------------------

    Con esto en mente puedo tener todo el estado en un int de 16 bits.
    El caso de la parda es obvio, 
    pero el del envido y flor es más útil, porque como estan separados y en secuencia,
    puedo ver cómo están encadenados por ejemplo 
    los envidos con solo iterar desde el bit 0 hasta el 3, lo cual me deja sumar puntos más facil, de hecho
    "no quiero" con encadenados es más elegante de lo que parece, porque es sumar el valor numérico de los bits 0-3 lol.
*/

#define ESTADO_ENVIDO 0
#define ESTADO_ENVIDO_ENVIDO 1
#define ESTADO_REAL_ENVIDO 2
#define ESTADO_FALTA_ENVIDO 3

#define ESTADO_TRUCO 4
#define ESTADO_RETRUCO 5
#define ESTADO_VALE_CUATRO 6
#define ESTADO_FLOR 7
#define ESTADO_CONTRAFLOR 8
#define ESTADO_CONTRAFLOR_AL_RESTO 9
#define ESTADO_PARDA 10

#define ENVIDO_PUNTOS 2
#define REAL_ENVIDO_PUNTOS 3

/*
    Envido:
        - Se pueden cantar todas las variantes excepto "envido envido",
        que solo se le responde al envido.
        - Sólo se puede cantar en la primera baza
        - No se puede cantar si se está en truco
        - Si se dice "no quiero", el envidador gana 1 punto por el envido no querido.
        Pero si hay envidos encadenados, el envidador se lleva los puntos apostados hasta ahora
        Ejemplo:
        Envido (+2) -> Envido (+2) -> No quiero = 2 puntos (se anula el último +2)
        Envido (+2) -> Real Envido (+3) -> No quiero = 2 puntos (se anula el último +3)
        Envido (+2) -> Real Envido (+3) -> Falta Envido (0) -> No quiero = 5 puntos 
        (no se puede cantar más de la falta así que digamos por ahora que es 0, aunque si se pudiese
        cantar algo como un super mega falta envido, entonces tal vez sería +puntaje que faltaba al
        jugador con más puntos para ganar? me inclino a creer que no porque cuando se canta la falta
        y uno gana, simplemente gana los puntos que le faltan al jugador con mas puntos, así que supongo
        que sería solo eso.)
        Envido (+2) -> Envido (+2) -> Real Envido (+3) -> Falta Envido (0) -> NQ = 7 puntos.
        - Si un jugador dice "truco" en primera baza, 
        se le puede reclamar "envido va primero" para cantar envido "fuera de turno" 
        A MENOS QUE el que cante sea el último jugador de la baza (pie).
        - Cuando se acepta el envido, empieza una ronda en la que se dicen los puntajes empezando 
        desde el jugador mano, quien tiene que anunciar su puntaje obligatoriamente (no puede decir son buenas
        ya que no hay comparación aún). 
        - Los jugadores siguientes pueden anunciar su puntaje si tienen más puntos de envido, 
        o deben decir son buenas si tienen menos (o si simplemente no quieren decir
        cuantos puntos de envido tienen realmente, por lo que el "son buenas" siempre es una opción
        para todos menos el mano).
        - Al sumar los puntos de envido (tantos), se suman a partir de tanto 
        las cartas jugadas como las que se tengan en mano.
        - Si se tienen dos o más cartas del mismo palo, el envido tiene 20 puntos de base. 
        Si la carta tiene un valor de entre 10 y 12, suma 0 puntos.
        - Si el jugador declara sus tantos, debe ser su mayor puntaje.
*/


uint8_t seed = 13;

typedef struct{
    uint8_t jugadores_cartas[MAX_JUGADORES][MAX_CARTAS_MANO];
    uint8_t cartas_bazas[MAX_BAZAS_RONDA][MAX_JUGADORES];

    uint8_t equipos_puntos[2];
    uint8_t max_puntos;

    uint16_t flags_ronda;

    uint8_t numero_jugadores;

    uint8_t jugador_mano;
    uint8_t jugador_turno_actual;
    uint8_t jugador_inicial_baza;

    uint8_t baza_actual;
    uint8_t baza_puntos;
} EstadoPartida;

#define EVENTO_JUGADA_INVALIDA 0 //default
#define EVENTO_CARTA_JUGADA 1
#define EVENTO_NUEVA_BAZA 2
#define EVENTO_RONDA_TERMINADA 3
#define EVENTO_VICTORIA 4

typedef struct{
    uint8_t evento;

    uint8_t carta;

    uint8_t ganador_baza;
    uint8_t equipo_ganador;
} ResultadoJugada;

uint8_t rand8() {
    seed ^= (uint8_t)(seed << 7);
    seed ^= (uint8_t)(seed >> 5);
    seed ^= (uint8_t)(seed << 3);
    return seed;
}

void repartir(EstadoPartida *partida){
    uint64_t usadas = 0;

    for(uint8_t jugador_i = 0; jugador_i < partida->numero_jugadores; jugador_i++){
        for(uint8_t carta_i = 0; carta_i < MAX_CARTAS_MANO; carta_i++){
            uint8_t indice;

            do {indice = rand8() % TOTAL_CARTAS;} while(usadas & (1ULL << indice));

            usadas |= (1ULL << indice);

            partida->jugadores_cartas[jugador_i][carta_i] = indice;
        }
    }
}

void limpiar_ronda(EstadoPartida *partida){
    partida->baza_actual = 0;
    partida->baza_puntos = 0;
    //resetear todas las flags
    partida->flags_ronda = 0;

    for(uint8_t b = 0; b < MAX_BAZAS_RONDA; b++){
        for(uint8_t j = 0; j < MAX_JUGADORES; j++){
            partida->cartas_bazas[b][j] = CARTA_INVALIDA;
        }
    }

    for(uint8_t j = 0; j < MAX_JUGADORES; j++){
        for(uint8_t c = 0; c < MAX_CARTAS_MANO; c++){
            partida->jugadores_cartas[j][c] = CARTA_INVALIDA;
        }
    }
}

void iniciar_ronda(EstadoPartida *partida){
    partida->jugador_turno_actual = partida->jugador_mano;
    limpiar_ronda(partida);
    repartir(partida);
}

uint8_t iniciar_partida(EstadoPartida *partida, uint8_t jugadores, uint8_t max_puntos){
    //rechazar jugadores impar, 0, 1 (redundante pero porque por ahi despues meto gallo) o mayores
    //a MAX_JUGADORES.
    if(jugadores <= 1 || jugadores > MAX_JUGADORES || jugadores % 2 != 0) return 0;

    partida->numero_jugadores = jugadores;
    partida->max_puntos = max_puntos;
    for(uint8_t i = 0; i < 2; i++) partida->equipos_puntos[i] = 0;

    partida->jugador_mano = 0;
    partida->jugador_turno_actual = 0;
    partida->jugador_inicial_baza = 0;

    limpiar_ronda(partida);
    iniciar_ronda(partida);
    return 1;
}


void nueva_baza(EstadoPartida *partida, uint8_t jugador_inicial){
    partida->jugador_inicial_baza = jugador_inicial;
    partida->jugador_turno_actual = partida->jugador_inicial_baza;
    partida->baza_actual++;
}

void alterar_resultado_jugada_si_puntuador_gano(EstadoPartida *partida, ResultadoJugada *resultado_jugada, uint8_t equipo_puntuador_i){
    if(partida->equipos_puntos[equipo_puntuador_i] < partida->max_puntos) return;
    resultado_jugada->evento = EVENTO_VICTORIA;
}

void terminar_ronda(EstadoPartida *partida, ResultadoJugada *resultado_jugada, uint8_t equipo_ganador_i){

    //le damos un punto al ganador por ganar la ronda, y luego
    //iteramos sobre los bits que le corresponde a cada truco cantado
    //desde el mayor canto posible hasta el menor (vale cuatro a truco normal).
    //si encontramos un bit seteado, el equipo gana los puntos de:
    //i - ESTADO_TRUCO + 1. 
    //supongamos que la ronda terminó en retruco, osea, tengo 011. 
    //este for se va a detener en el 0[1]1 y le va a sumar a el/los ganadores 2. (5 - 4 + 1 = 2) y romper.
    //Teniendo en cuenta que ya le dimos un punto por ganar, el jugador se llevó 3 puntos,
    //osea, el valor de un retruco
    //así tenemos una sola branch (la que revisa los bits seteados) y solo el estado del iterador.

    partida->equipos_puntos[equipo_ganador_i] += 1;

    for(uint8_t i = ESTADO_VALE_CUATRO; i >= ESTADO_TRUCO; i++){
        if(!IS_BIT_SET(partida->flags_ronda, i)) continue;

        partida->equipos_puntos[equipo_ganador_i] += (i - ESTADO_TRUCO + 1);
        break;
    }

    alterar_resultado_jugada_si_puntuador_gano(partida, resultado_jugada, equipo_ganador_i);

    if(resultado_jugada->evento == EVENTO_VICTORIA) return;

    partida->jugador_mano++;
    if(partida->jugador_mano == partida->numero_jugadores) partida->jugador_mano = 0;
    iniciar_ronda(partida);
}

ResultadoJugada jugar_baza(EstadoPartida *partida, uint8_t jugador_i, uint8_t carta_mano_i){

    ResultadoJugada resultado = {
        .evento = EVENTO_JUGADA_INVALIDA,
        .carta = CARTA_INVALIDA,
        .ganador_baza = JUGADOR_INVALIDO,
        .equipo_ganador = JUGADOR_INVALIDO,
    };

    //si el jugador o la carta son invalidos, salir
    if(jugador_i >= partida->numero_jugadores || carta_mano_i >= MAX_CARTAS_MANO) return resultado;

    uint8_t carta_i = partida->jugadores_cartas[jugador_i][carta_mano_i];
    if(partida->jugador_turno_actual != jugador_i || 
    jugador_i >= partida->numero_jugadores || 
    carta_i == CARTA_INVALIDA) return resultado;

    resultado.carta = carta_i;

    //ya pasó la verificación. todo ok.
    resultado.evento = EVENTO_CARTA_JUGADA;
    //poner la carta en la mesa en la posición del jugador y sacarsela.
    partida->cartas_bazas[partida->baza_actual][jugador_i] = carta_i;
    partida->jugadores_cartas[jugador_i][carta_mano_i] = CARTA_INVALIDA;

    /*
        ahora es turno del siguiente jugador. si llegamos al máximo de jugadores de la partida,
        empezamos por el primer jugador de la mesa, pero si es el del primer jugador que jugó,
        eso significa que terminó la baza.
    */
    partida->jugador_turno_actual++;
    
    if(partida->jugador_turno_actual >= partida->numero_jugadores) partida->jugador_turno_actual = 0;

    if(partida->jugador_turno_actual == partida->jugador_inicial_baza){
        //evaluar y terminar baza/ronda/partida
        uint8_t mayor_carta_i = CARTA_INVALIDA;
        uint8_t ganador_baza = JUGADOR_INVALIDO;
        
        for(uint8_t i = 0; i < partida->numero_jugadores; i++){
            uint8_t carta_actual = partida->cartas_bazas[partida->baza_actual][i];
            if(carta_actual < mayor_carta_i){
                mayor_carta_i = carta_actual;
                ganador_baza = i;
            } 
        }

        // revisamos que no sea parda. 
        // si la carta es una de las cuatro primeras, obvio que no es ya que tienen valores de "fuerza" únicos.
        // de lo contrario, me fijo si hay una carta del mismo valor
        // numérico que la "ganadora". si la hay, (y es de alguien de otro equipo), es parda.
        if(mayor_carta_i > 3){
            for(uint8_t i = 0; i < partida->numero_jugadores; i++){
                uint8_t carta_actual = partida->cartas_bazas[partida->baza_actual][i];

                if(carta_actual != CARTA_INVALIDA && 
                    cartas[carta_actual].valor_carta == cartas[mayor_carta_i].valor_carta &&
                   (i % 2 != ganador_baza % 2)){
                    mayor_carta_i = CARTA_INVALIDA;
                    break;
                }
            }
        }



        //pasar de baza o terminar ronda
        if(partida->baza_actual < MAX_BAZAS_RONDA - 1){

            uint8_t es_primera_baza = partida->baza_actual == 0;
            uint8_t jugador_siguiente_baza = ganador_baza;
            uint8_t hay_mayor_carta = mayor_carta_i != CARTA_INVALIDA;
            uint8_t equipo_ganador = ganador_baza % 2;

            if(hay_mayor_carta){
                //caso normal. hay carta ganadora.

                //solo si el ganador es el equipo 1 seteamos el bit de la baza actual.
                if(equipo_ganador == 1) partida->baza_puntos |= (1 << partida->baza_actual);
                resultado.ganador_baza = ganador_baza;
                resultado.equipo_ganador = equipo_ganador;
            }
            else{
                //parda. mano empieza la siguiente baza
                SET_BIT(partida->flags_ronda, ESTADO_PARDA);
                jugador_siguiente_baza = partida->jugador_mano;
            }

            //si es la primera baza, si los últimos 2 resultados de la baza estan intercalados (osea no gano nadie)
            //o si se esta en parda y no hay carta considerada ganadora,
            //jugar nueva baza. de lo contrario, terminar la ronda.
            if(es_primera_baza || 
            (!(IS_BIT_SET(partida->baza_puntos, partida->baza_actual)) && (IS_BIT_SET(partida->baza_puntos, partida->baza_actual - 1))) &&
            !IS_BIT_SET(partida->flags_ronda, ESTADO_PARDA) && !hay_mayor_carta){
                resultado.evento = EVENTO_NUEVA_BAZA;
                nueva_baza(partida, jugador_siguiente_baza);
            }
            else{
                resultado.evento = EVENTO_RONDA_TERMINADA;
                terminar_ronda(partida, &resultado, equipo_ganador);
            }

        }else{
            uint8_t equipo_ganador = partida->jugador_mano % 2;

            resultado.evento = EVENTO_RONDA_TERMINADA;
            resultado.equipo_ganador = equipo_ganador;
            terminar_ronda(partida, &resultado, equipo_ganador);
        }
    }
    
    return resultado;
}