/*
 * listar-specs - Lista specs divididas em dois grupos:
 *   spec-diretivas/ (sistema atual) e core/biblioteca-specs/ (biblioteca central)
 * Uso: pp --listar-specs[=<filtro>]
 */

#define _GNU_SOURCE
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include "../restricao.h"

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --listar-specs[=<filtro>]\n"
        "  pp --listar-specs <filtro>\n"
        "  pp --listar-specs --ajuda:<grupo>\n"
        "\n"
        "Descricao:\n"
        "  Lista specs disponiveis divididas em dois grupos:\n"
        "  primeiro spec-diretivas/ (sistema atual), depois\n"
        "  core/biblioteca-specs/ (biblioteca central do SISC).\n"
        "\n"
        "Grupos de ajuda: uso, filtro, comportamento, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo. Ex: pp --listar-specs --ajuda:filtro\n"
        "\n"
        "2) Filtro [filtro entrada]\n"
        "  =<filtro>                      Filtra pelo trecho informado\n"
        "  <filtro>                       Forma alternativa como argumento separado\n"
        "  Sem filtro                     Lista todos os itens\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --listar-specs=seguranca  ou  pp --listar-specs seguranca\n"
        "\n"
        "3) Comportamento [comportamento]\n"
        "  Cada grupo e ordenado alfabeticamente. A busca por trecho e case-insensitive.\n"
        "  Specs presentes em spec-diretivas/ nao sao repetidas no grupo biblioteca-specs/.\n"
        "\n"
        "4) Exemplos [exemplos]\n"
        "  pp --listar-specs\n"
        "  pp --listar-specs=seguranca\n"
        "  pp --listar-specs seguranca\n",
        stdout);
}

static int termina_com(const char *s, const char *suf)
{
    size_t a = strlen(s), b = strlen(suf);
    return a >= b && strcasecmp(s + a - b, suf) == 0;
}

static int cmp_str(const void *a, const void *b)
{
    return strcasecmp(*(const char *const *)a, *(const char *const *)b);
}

static int nome_contem(const char *nome, const char *filtro)
{
    if (!filtro || !*filtro) return 1;
    return !!strcasestr(nome, filtro);
}

/* Adiciona nome ao array se passar no filtro e nao for duplicado */
static int adicionar_se_valido(char ***nomes, int *total, int *cap, const char *nome, const char *filtro)
{
    if (strncmp(nome, "spec_", 5) != 0) return 0;
    if (!termina_com(nome, ".json")) return 0;
    if (!strcmp(nome, "spec_modelo.json")) return 0;
    if (!nome_contem(nome, filtro)) return 0;

    /* Verifica duplicata */
    for (int i = 0; i < *total; i++)
        if (!strcmp((*nomes)[i], nome)) return 0;

    if (*total >= *cap) {
        *cap *= 2;
        char **nv = realloc(*nomes, (size_t)(*cap) * sizeof(char *));
        if (!nv) return -1;
        *nomes = nv;
    }
    (*nomes)[*total] = strdup(nome);
    if (!(*nomes)[*total]) return -1;
    (*total)++;
    return 1;
}

/* Lista um diretorio e adiciona ao array */
static int listar_diretorio(const char *dir, char ***nomes, int *total, int *cap, const char *filtro)
{
    DIR *d = opendir(dir);
    if (!d) return 0;

    struct dirent *e;
    while ((e = readdir(d)) != NULL) {
        adicionar_se_valido(nomes, total, cap, e->d_name, filtro);
    }
    closedir(d);
    return 1;
}

static void imprimir_grupo(char **nomes, int total, const char *titulo)
{
    if (total == 0) return;
    qsort(nomes, (size_t)total, sizeof(char *), cmp_str);
    printf("\n--- %s (%d spec%s) ---\n", titulo, total, total == 1 ? "" : "s");
    for (int i = 0; i < total; i++)
        printf("  %s\n", nomes[i]);
}

int main(int argc, char **argv)
{
    if (restricao_verificar("listar-specs") != 0) return 1;

    const char *filtro = "";

    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "--ajuda") || !strcmp(argv[i], "-h")) {
            uso();
            return 0;
        }
        if (argv[i][0] != '-' && !*filtro) {
            filtro = argv[i];
            continue;
        }
        uso();
        return 1;
    }

    char raiz[PATH_MAX];
    if (!getcwd(raiz, sizeof(raiz))) {
        perror("listar-specs: getcwd");
        return 1;
    }

    /* --- Grupo 1: spec-diretivas/ --- */
    char dir1[PATH_MAX * 2];
    snprintf(dir1, sizeof(dir1), "%s/spec-diretivas", raiz);

    int cap = 32, total1 = 0;
    char **nomes1 = malloc((size_t)cap * sizeof(char *));
    if (!nomes1) return 1;

    listar_diretorio(dir1, &nomes1, &total1, &cap, filtro);

    /* --- Grupo 2: core/biblioteca-specs/ --- */
    char dir2[PATH_MAX * 2];
    dir2[0] = '\0';
    {
        char raiz_sisc[PATH_MAX];
        snprintf(raiz_sisc, sizeof(raiz_sisc), "%s", raiz);
        char *slash = strrchr(raiz_sisc, '/');
        if (slash && slash != raiz_sisc) {
            *slash = '\0';
            snprintf(dir2, sizeof(dir2), "%s/core/biblioteca-specs", raiz_sisc);
        }
    }

    int total2 = 0;
    int cap2 = total1 > 32 ? total1 * 2 : 32;
    char **nomes2 = malloc((size_t)cap2 * sizeof(char *));
    if (!nomes2) { 
        for (int i = 0; i < total1; i++) free(nomes1[i]);
        free(nomes1);
        return 1;
    }

    if (dir2[0]) {
        /* Copia nomes do grupo 1 como base de exclusao de duplicatas */
        for (int i = 0; i < total1; i++) {
            if (total2 >= cap2) { cap2 *= 2; nomes2 = realloc(nomes2, (size_t)cap2 * sizeof(char *)); }
            nomes2[total2++] = strdup(nomes1[i]);
        }
        listar_diretorio(dir2, &nomes2, &total2, &cap2, filtro);
        /* Remove do grupo 2 os que ja estao no grupo 1 */
        int j = 0;
        for (int i = 0; i < total2; i++) {
            int dup = 0;
            for (int k = 0; k < total1; k++)
                if (!strcmp(nomes2[i], nomes1[k])) { dup = 1; break; }
            if (!dup) {
                if (i != j) {
                    free(nomes2[j]);
                    nomes2[j] = nomes2[i];
                    nomes2[i] = NULL;
                }
                j++;
            } else {
                free(nomes2[i]);
                nomes2[i] = NULL;
            }
        }
        /* Libera strings duplicadas que ficaram */
        for (int i = j; i < total2; i++) free(nomes2[i]);
        total2 = j;
    }

    /* --- Impressao --- */
    if (total1 == 0 && total2 == 0) {
        printf("Nenhuma spec encontrada");
        if (filtro && *filtro) printf(" com '%s'", filtro);
        printf(".\n");
    } else {
        imprimir_grupo(nomes1, total1, "spec-diretivas");
        imprimir_grupo(nomes2, total2, "biblioteca-specs");
    }

    /* Cleanup */
    for (int i = 0; i < total1; i++) free(nomes1[i]);
    free(nomes1);
    for (int i = 0; i < total2; i++) free(nomes2[i]);
    free(nomes2);

    return 0;
}
