#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <ctype.h>



/*las funciones print y show, la hemos puesto por separado, pero se poddrian haber puesto juntas 
la funcion print simplemente escribe, y se llama en show para hacer el codigo mas limpio
*/


//se recibe una estructa de tipo passwd, y accedemos a ella tal cual estamos viendo, no es nada tecnico ni se necesita pensar
void print_user(struct passwd *pw) {// Imprime todos los campos de la estructura passwd de un usuario

    printf("Usuario:\n");
    printf("Nombre: %s\n", pw->pw_gecos);
    printf("Login: %s\n", pw->pw_name);
    printf("Password: %s\n", pw->pw_passwd);
    printf("UID: %d\n", pw->pw_uid);
    printf("Home: %s\n", pw->pw_dir);
    printf("Shell: %s\n", pw->pw_shell);
    printf("Número de grupo principal: %d\n", pw->pw_gid);
}

// Busca un usuario por UID o nombre y llama a print_user
void show_user(char *arg) {//recibe un arg que puede ser numero o string
    struct passwd *pw;//creamos una estructura tipo passwd
    if (isdigit(arg[0]))//si es digito, convertimos de tipo cadena a tipo entero y buscamos el usuario con ese uid
        pw = getpwuid(atoi(arg));
    else
        pw = getpwnam(arg);//si no es numero, simplemente buscamos el usuario por el nombre 
    if (pw == NULL) {//si esta vacio, es que no se encuentra, el user no existe
        fprintf(stderr, "Usuario no encontrado: %s\n", arg);
        exit(1);
    }
    print_user(pw);//llamamos a la funcion de arriba 
}

// Igual que show_user pero devuelve la estructura en vez de imprimirla
struct passwd *get_user(char *arg) {
    struct passwd *pw;
    if (isdigit(arg[0]))
        pw = getpwuid(atoi(arg));
    else
        pw = getpwnam(arg);
    return pw;
}

// Imprime todos los campos de la estructura group de un grupo
//igual que user pero con cosas de group
void print_group(struct group *gr) {
    printf("Grupo:\n");
    printf("Nombre del grupo: %s\n", gr->gr_name);
    printf("GID: %d\n", gr->gr_gid);
    printf("Miembros secundarios:\n");
    for (int i = 0; gr->gr_mem[i] != NULL; i++)
        printf("%s\n", gr->gr_mem[i]);
}

// Busca un grupo por GID o nombre y llama a print_group
//igual que user pero version group
void show_group(char *arg) {
    struct group *gr;
    if (isdigit(arg[0]))
        gr = getgrgid(atoi(arg));
    else
        gr = getgrnam(arg);
    if (gr == NULL) {
        fprintf(stderr, "Grupo no encontrado: %s\n", arg);
        exit(1);
    }
    print_group(gr);
}

// Recorre todos los grupos del sistema y los imprime
void show_allgroups() {
    struct group *gr;//puntero a una estructura tipo group 
    //grent viene de group entry, del fichero /etc/group
    setgrent();//se rebobina al principio del fichero 
    while ((gr = getgrent()) != NULL)//lee la siguiente entrada hasta que una vez de NULL
        print_group(gr);//imprime el grupo 
    endgrent();//cierra el fichero se haya abierto
}

// Imprime el mensaje de ayuda
void print_help(char *prog) {
    printf("Uso del programa: %s [opciones]\n", prog);
    printf("Opciones:\n");
    printf("  -h, --help                          Imprimir esta ayuda\n");
    printf("  -u, --user (<nombre>|<uid>)         Información sobre el usuario\n");
    printf("  -a, --active                        Información sobre el usuario activo actual\n");
    printf("  -m, --maingroup                     Además de info de usuario, imprimir la info de su grupo principal\n");
    printf("  -g, --group (<nombre>|<gid>)        Información sobre el grupo\n");
    printf("  -s, --allgroups                     Muestra info de todos los grupos del sistema\n");
}

int main(int argc, char *argv[]) {

    //la finalidad de las flags, no es más que decir, esta opción se activa esta opción no se activa
    // Flags para saber qué opciones activó el usuario
    int flag_user = 0, flag_group = 0, flag_active = 0;
    int flag_maingroup = 0, flag_allgroups = 0, flag_help = 0;
    char *arg_user = NULL, *arg_group = NULL;

    //significa que el usuario no ha puesto más que el ./ej1, esto equivale a las opciones -a -m
    if (argc == 1) {
        flag_active = 1;
        flag_maingroup = 1;
    }

    // Array de opciones largas
    struct option long_options[] = {//array de estructuras de tipo option
        {"user",      required_argument, NULL, 'u'},//user es el nombre largo, el segundo arugmento es para decir si requiere argumento
        {"group",     required_argument, NULL, 'g'},//NULL, este campo no lo hemos visto
        {"active",    no_argument,       NULL, 'a'},//carácter que devolverá getopt_long cuando detecte --user o -u
        {"maingroup", no_argument,       NULL, 'm'},
        {"allgroups", no_argument,       NULL, 's'},
        {"help",      no_argument,       NULL, 'h'},
        {0, 0, 0, 0}//se pone para indicar que el array ha terminado
    };

    // Bucle que detecta las opciones
    int opt;// se va a usar para el switch y el whil, guarda la opcion detectada
    while ((opt = getopt_long(argc, argv, "u:g:amsh", long_options, NULL)) != -1) {
    //mientras que opt reciba algo que no sea -1
    //get opt_long, avanza argc veces en argv, es decir, lee de alli, u:.. son lasopciones cortas : es que lleva argumento
    //es el array que revisa, NULL es por un parametro opcional que no usamos
        switch (opt) {
            //para el switch simplemente conforme aumenta el bucle con switch se revisan las letras, si se da 
            //su caso es pone en 1, si tiene arugmento en la variable creada al principio, se guarda optarg que es una variable global de getopt
            case 'u': flag_user = 1;      arg_user = optarg;  break;
            case 'g': flag_group = 1;     arg_group = optarg; break;
            case 'a': flag_active = 1;                        break;
            case 'm': flag_maingroup = 1;                     break;
            case 's': flag_allgroups = 1;                     break;
            case 'h': flag_help = 1;                          break;
            default:  flag_help = 1;                          break;
        }
    }

    // Validación de combinaciones
   // --help va con cualquier cosa, muestra ayuda y sale
    if (flag_help) {
        print_help(argv[0]);
        exit(0);
    }

    // -m sola no tiene sentido, necesita -u o -a para saber de qué usuario sacar el grupo
    if (flag_maingroup && !flag_user && !flag_active) {
        fprintf(stderr, "La opción --maingroup solo puede acompañar a --user o --active\n");
        print_help(argv[0]);
        exit(1);
    }

    // -u y -a son incompatibles, -a ya es el usuario actual, no puedes pedir dos usuarios
    if (flag_user && flag_active) {
        fprintf(stderr, "Las opciones --user y --active son incompatibles\n");
        print_help(argv[0]);
        exit(1);
    }

    // -u y -g son incompatibles, no está en la lista de combinaciones válidas del enunciado
    if (flag_user && flag_group) {
        fprintf(stderr, "Las opciones --user y --group son incompatibles\n");
        print_help(argv[0]);
        exit(1);
    }

    // -a y -g son incompatibles por el mismo motivo que -u y -g
    if (flag_active && flag_group) {
        fprintf(stderr, "Las opciones --active y --group son incompatibles\n");
        print_help(argv[0]);
        exit(1);
    }

    // -s va siempre sola, muestra todos los grupos del sistema y no tiene sentido combinarlo
    if (flag_allgroups && (flag_user || flag_active || flag_group || flag_maingroup)) {
        fprintf(stderr, "La opción --allgroups no puede acompañar a ninguna otra opción\n");
        print_help(argv[0]);
        exit(1);
    }

    // Funcionalidad
    //no tiene misterio, si su flag es uno, lanzas su función
    if (flag_user)
        show_user(arg_user);

    if (flag_active)
        show_user(getenv("USER"));

    if (flag_maingroup) {
        struct passwd *pw;
        if (flag_user)
            pw = get_user(arg_user);
        else
            pw = getpwnam(getenv("USER"));
        if (pw == NULL) {
            fprintf(stderr, "Usuario no encontrado\n");
            exit(1);
        }
        struct group *gr = getgrgid(pw->pw_gid);
        print_group(gr);
    }

    if (flag_group)
        show_group(arg_group);

    if (flag_allgroups)
        show_allgroups();

    return 0;
}

