/*
 * infoarq - Exibe metadado #infoarq de arquivo(s).
 * Uso: pp --infoarq=<arquivo-ou-coringa>
 *      pp --infoarq <arquivo-ou-coringa>
 *      pp --infoarq:<arquivo-ou-coringa>   (quando suportado pelo roteador pp)
 */

#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fnmatch.h>
#include <limits.h>
#include <sys/stat.h>

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --infoarq=<arquivo-ou-coringa>\n"
        "  pp --infoarq <arquivo-ou-coringa>\n"
        "  pp --infoarq:<arquivo-ou-coringa>\n"
        "  pp --infoarq=<arquivo-ou-coringa> --filtro=<termo>\n"
        "\n"
        "Descrição:\n"
        "  Sem coringa, procura o primeiro comentário contendo '#infoarq' no arquivo informado\n"
        "  e exibe somente a informação após ':' ou '='.\n"
        "  Com coringa (*, ? ou []), procura recursivamente a partir do diretório atual\n"
        "  e exibe '<arquivo>: <infoarq>' para cada arquivo encontrado.\n"
        "  Com --filtro=<termo>, exibe somente os resultados cujo conteúdo #infoarq\n"
        "  contenha o termo informado. Se nenhum resultado casar, nada é exibido.\n"
        "\n"
        "Exemplos:\n"
        "  pp --infoarq=/var/www/html/sisc/ajuda-conectores/manual-conectores.html\n"
        "  pp --infoarq=manual-*\n"
        "  pp --infoarq=manual-* --filtro=conector\n",
        stdout);
}

static char *trim_inicio(char *s)
{
    while (s && *s && isspace((unsigned char)*s)) s++;
    return s;
}

static void trim_fim(char *s)
{
    if (!s) return;
    size_t n = strlen(s);
    while (n > 0 && isspace((unsigned char)s[n - 1])) {
        s[--n] = '\0';
    }
}

static int termina_com(const char *s, const char *sufixo)
{
    size_t ns = strlen(s), nx = strlen(sufixo);
    return ns >= nx && strcmp(s + ns - nx, sufixo) == 0;
}

static void remover_fechadores_comentario(char *s)
{
    int mudou = 1;
    while (mudou) {
        mudou = 0;
        trim_fim(s);
        if (termina_com(s, "-->")) {
            s[strlen(s) - 3] = '\0';
            mudou = 1;
        } else if (termina_com(s, "*/")) {
            s[strlen(s) - 2] = '\0';
            mudou = 1;
        }
    }
    trim_fim(s);
}

static int extrair_info_da_linha(char *linha, char **out)
{
    char *p = strstr(linha, "#infoarq");
    if (!p) return 0;

    p += strlen("#infoarq");
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p == ':' || *p == '=') p++;
    while (*p && isspace((unsigned char)*p)) p++;

    char *info = trim_inicio(p);
    trim_fim(info);
    remover_fechadores_comentario(info);
    info = trim_inicio(info);

    *out = info;
    return 1;
}

static char ascii_lower_char(unsigned char c)
{
    if (c >= 'A' && c <= 'Z') return (char)(c - 'A' + 'a');
    return (char)c;
}

static int texto_contem_ci(const char *texto, const char *agulha)
{
    size_t n;

    if (!texto || !agulha) return 0;
    n = strlen(agulha);
    if (n == 0) return 1;

    for (const char *p = texto; *p; p++) {
        size_t i = 0;
        while (i < n && p[i] &&
               ascii_lower_char((unsigned char)p[i]) == ascii_lower_char((unsigned char)agulha[i])) {
            i++;
        }
        if (i == n) return 1;
    }

    return 0;
}

static int contem_coringa(const char *s)
{
    return s && strpbrk(s, "*?[") != NULL;
}

static int contem_barra(const char *s)
{
    return s && strchr(s, '/') != NULL;
}

static const char *base_nome(const char *caminho)
{
    const char *p = strrchr(caminho, '/');
    return p ? p + 1 : caminho;
}

static int caminho_monta(char *dest, size_t tam, const char *dir, const char *nome)
{
    int n;

    if (strcmp(dir, ".") == 0) {
        n = snprintf(dest, tam, "./%s", nome);
    } else {
        n = snprintf(dest, tam, "%s/%s", dir, nome);
    }

    return n >= 0 && (size_t)n < tam;
}

static const char *caminho_exibicao(const char *caminho)
{
    if (strncmp(caminho, "./", 2) == 0) return caminho + 2;
    return caminho;
}

/* Retorna: 0 exibiu #infoarq; 1 nao encontrou; 2 erro; 3 encontrou, mas nao passou no filtro. */
static int exibir_info_arquivo(const char *arquivo, int prefixar_arquivo, int avisar_sem_info, const char *filtro, int *respostas_emitidas)
{
    FILE *f = fopen(arquivo, "rb");
    if (!f) {
        fprintf(stderr, "infoarq: nao foi possivel abrir '%s': %s\n", arquivo, strerror(errno));
        return 2;
    }

    char *linha = NULL;
    size_t cap = 0;
    ssize_t n;
    int rc = 1;

    while ((n = getline(&linha, &cap, f)) != -1) {
        (void)n;
        char *info = NULL;
        if (extrair_info_da_linha(linha, &info)) {
            if (filtro && *filtro && !texto_contem_ci(info ? info : "", filtro)) {
                rc = 3;
            } else {
                if (respostas_emitidas && *respostas_emitidas > 0) {
                    fputs("---\n", stdout);
                }
                if (prefixar_arquivo) {
                    printf("%s: %s\n", caminho_exibicao(arquivo), info ? info : "");
                } else {
                    printf("%s\n", info ? info : "");
                }
                if (respostas_emitidas) (*respostas_emitidas)++;
                rc = 0;
            }
            break;
        }
    }

    if (ferror(f)) {
        fprintf(stderr, "infoarq: erro ao ler '%s': %s\n", arquivo, strerror(errno));
        rc = 2;
    } else if (rc == 1 && avisar_sem_info && (!filtro || !*filtro)) {
        fprintf(stderr, "infoarq: nenhum comentario #infoarq encontrado em '%s'.\n", arquivo);
    }

    free(linha);
    fclose(f);
    return rc;
}

static int padrao_casa(const char *padrao, const char *caminho)
{
    const char *rel = caminho_exibicao(caminho);

    if (contem_barra(padrao)) {
        return fnmatch(padrao, rel, 0) == 0;
    }

    return fnmatch(padrao, base_nome(rel), 0) == 0;
}

static int buscar_recursivo(const char *dir, const char *padrao, const char *filtro, int *arquivos_casaram, int *infos_exibidas, int *erros)
{
    struct dirent **lista = NULL;
    int total = scandir(dir, &lista, NULL, alphasort);

    if (total < 0) {
        fprintf(stderr, "infoarq: nao foi possivel abrir diretorio '%s': %s\n", dir, strerror(errno));
        (*erros)++;
        return 0;
    }

    for (int i = 0; i < total; i++) {
        struct dirent *ent = lista[i];

        if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
            char caminho[PATH_MAX];
            if (!caminho_monta(caminho, sizeof(caminho), dir, ent->d_name)) {
                fprintf(stderr, "infoarq: caminho grande demais em '%s/%s'\n", dir, ent->d_name);
                (*erros)++;
            } else {
                struct stat st;
                if (lstat(caminho, &st) != 0) {
                    fprintf(stderr, "infoarq: nao foi possivel acessar '%s': %s\n", caminho, strerror(errno));
                    (*erros)++;
                } else if (S_ISDIR(st.st_mode)) {
                    buscar_recursivo(caminho, padrao, filtro, arquivos_casaram, infos_exibidas, erros);
                } else if (S_ISREG(st.st_mode) && padrao_casa(padrao, caminho)) {
                    int r;
                    (*arquivos_casaram)++;
                    r = exibir_info_arquivo(caminho, 1, 0, filtro, infos_exibidas);
                    if (r == 2) (*erros)++;
                }
            }
        }

        free(ent);
    }
    free(lista);

    return 0;
}

int main(int argc, char *argv[])
{
    const char *alvo = NULL;
    const char *filtro = NULL;
    int pedido_ajuda = 0;

    for (int i = 1; i < argc; i++) {
        const char *a = argv[i];

        if (strcmp(a, "--ajuda") == 0 || strcmp(a, "--help") == 0 || strcmp(a, "-h") == 0) {
            pedido_ajuda = 1;
        } else if (strncmp(a, "--filtro=", 9) == 0) {
            filtro = a + 9;
            if (!*filtro) {
                fprintf(stderr, "infoarq: --filtro requer um termo nao vazio.\n");
                return 1;
            }
        } else if (strcmp(a, "--filtro") == 0) {
            if (i + 1 >= argc || !argv[i + 1][0]) {
                fprintf(stderr, "infoarq: --filtro requer um termo nao vazio.\n");
                return 1;
            }
            filtro = argv[++i];
        } else if (strncmp(a, "--", 2) == 0) {
            fprintf(stderr, "infoarq: opcao desconhecida '%s'.\n", a);
            return 1;
        } else if (!alvo) {
            alvo = a;
        } else {
            fprintf(stderr, "infoarq: argumento excedente '%s'.\n", a);
            uso();
            return 1;
        }
    }

    if (pedido_ajuda) {
        uso();
        return 0;
    }

    if (!alvo) {
        uso();
        return 1;
    }

    if (!contem_coringa(alvo)) {
        int r = exibir_info_arquivo(alvo, 0, 1, filtro, NULL);
        return r == 0 ? 0 : 1;
    }

    int arquivos_casaram = 0;
    int infos_exibidas = 0;
    int erros = 0;

    buscar_recursivo(".", alvo, filtro, &arquivos_casaram, &infos_exibidas, &erros);

    if (arquivos_casaram == 0) {
        fprintf(stderr, "infoarq: nenhum arquivo encontrado para o coringa '%s' a partir do diretorio atual.\n", alvo);
        return 1;
    }
    if (infos_exibidas == 0) {
        if (!filtro || !*filtro) {
            fprintf(stderr, "infoarq: nenhum comentario #infoarq encontrado nos arquivos que casaram com '%s'.\n", alvo);
        }
        return 1;
    }

    return erros ? 1 : 0;
}
