/*
    Fichier sjfmails.c
    Auteur Bernard Chardonneau

    Logiciel libre, droits d'utilisation précisés en français
    dans le fichier : licence-fr.txt

    Traductions des droits d'utilisation dans les fichiers :
    licence-de.txt , licence-en.txt , licence-es.txt ,
    licence-it.txt , licence-nl.txt , licence-pt.txt ,
    licence-eo.txt , licence-eo-utf.txt

    Droits d'utilisation également sur la page web :
    http://libremail.tuxfamily.org/voir.php?page=droits


    Ce programme affiche les caractéristique de chaque mail
    mémorisé dans un fichier.

    Il y a 2 formats d'affichage.

    Le format détaillé (option -d) donne plusieurs lignes d'information :
    - date et heure d'envoi
    - expéditeur et destinataire(s) + éventuelle adresse de réponse
    - sujet du mail
    - format du message (texte, html ...)
    - éventuellement premières lignes du message

    Le format simplifié (option -s) affiche une ligne par message :
      expéditeur     sujet     date et heure d'envoi
     destinataire    sujet     date et heure d'envoi
    selon qu'il s'agisse d'un mail reçu ou envoyé (ou à envoyer)

    Par défaut, on analyse les mails du répertoire courant.
    On peut aussi indiquer un autre répertoire de recherche.
*/


#define appli   // pour la déclaration de variables globales à l'application

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include "messages.h"
#include "buflect.h"
#include "fmail.h"
#include "trtentete.h"
#include "trtligne.h"


#define max_nbmail_ini  500  // nombre max initial de fichiers pour tri par noms


/* type de données */
typedef char descmail [12];


/* prototypes */
void lister_mails ();
int  ajoutlistepossible ();
void trinoms ();
void affiche_liste ();
void aff_detail (char *nomfic);
void aff_resume (char *nomfic);


/* variables globales au source
   (pour éviter des tonnes de passages de paramètres) */


int  nblig_mes;             // nombre de lignes à afficher pour chaque message
descmail *fichmail;         // noms des fichiers mail
int  nbmails;               // nombre de fichiers mail mémorisés
int  maxnbmails;            // nombre maximum de fichiers mail mémorisés
int  opt_aff;               // option affichage (simple = -s, détaillé = -d)
int  opt_inv;               // option inversion de l'ordre d'affichage des mails
int  opt_n;                 // options pour l'affichage des mails non lus
int  sz_nom, sz_avantdate;  // largeur des champs affichés si option -s
int  test_xorig;            // traitement ou non du champ X-Original-From


/* programme principal */

int main (int nbarg, char *varg[])
{
    int  largeur;    // largeur de l'affichage si option -s
    char *dirmails;


    // récupération du nom de l'exécutable
    memcom (*varg);

    // se positionner sur le premier argument de sjfmails
    varg ++;
    nbarg --;

    // initialisation options de fonctionnement
    opt_aff = 0;
    opt_inv = 0;
    opt_n   = 0;
    nblig_mes = 0;
    largeur = 0;   // la valeur sera fixée plus tard

    // récupération des options de fonctionnement éventuelles
    while (nbarg > 0 && **varg == '-' && varg [0][2] == '\0')
    {
        switch (varg [0][1])
        {
            // option affichage détaillé
            case 'd' : opt_aff = 1;
                       varg ++;
                       nbarg --;

                       // récupérer le nombre de lignes
                       // après entête s'il y a lieu
                       if (nbarg > 0)
                       {
                           nblig_mes = atoi (*varg);

                           if (nblig_mes)
                           {
                               varg ++;
                               nbarg --;
                           }
                       }

                       break;

            // option largeur de l'affichage simplifié
            case 'w' : varg ++;
                       nbarg --;

                       // récupération de la largeur de l'affichage
                       largeur = atoi (*varg);

            // option affichage simplifié
            case 's' : // décompter l'argument traité
                       varg ++;
                       nbarg --;
                       break;

            // option inversion de l'ordre d'affichage
            case 'i' :
            case 'r' : opt_inv = 1;
                       varg ++;
                       nbarg --;
                       break;

            // option -n pour mettre en évidence les mails non lus
            // ou option -N pour n'afficher que les mails non lus
            case 'n' :
            case 'N' : opt_n = varg [0][1];
                       varg ++;
                       nbarg --;
                       break;

            default  : // "Option %s incorrecte"
                       aff_err_arg ("ERR_OPTION", *varg);

                       // rappeller la syntaxe de la commande
                       psyntaxe ("SYNT_SJFMAILS");
        }
    }

    // valeur par défaut de la largeur d'affichage si elle n'a pas été fixée
    if (! largeur)
        largeur = 80;

    // calcul de la taille des différentes zones d'affichage si option -s
    sz_nom = (largeur / 4) - 1;
    sz_avantdate = largeur - 13;

    // correction si option -n
    if (opt_n == 'n')
        sz_nom ++;

    // récupération éventuelle du nom du répertoire des mails
    if (nbarg == 1)
    {
        dirmails = *varg;

        // positionnement dans ce répertoire
        if (chdir (dirmails) < 0)
        {
            // "Répertoire %s inexistant"
            aff_err_arg ("REPERT_INEXISTANT", dirmails);
            return (0);
        }
    }

    // si aucune erreur dans la liste des paramètres
    if (nbarg < 2)
    {
        // fixer la taille initiale de la liste des fichiers mail
        maxnbmails = max_nbmail_ini;

        // allouer la liste des fichiers mail
        fichmail = malloc (12 * maxnbmails);

        // vérification allocation
        if (! fichmail)
            // "Manque de place mémoire, le logiciel %s ne peut fonctionner"
            errfatale ("MANQUE_MEMOIRE", nomcom ());

        // récupération de la liste des fichiers mail
        lister_mails ();

        // ordonnés par noms de fichiers
        trinoms ();

        // affichage détaillé ou résumé de l'entête des mails
        affiche_liste ();
    }
    else
        // "Syntaxe : %s [-(s|d [nb_lignes_messages])]
        //               [-w largeur] [-i] [-(n|N)] [répertoire_fichiers]"
        psyntaxe ("SYNT_SJFMAILS");

    // pour faire plaisir à gcc qui veut une fonction main de type int
    return (0);
}



/* récupère le nombre de mails */

void lister_mails ()
{
    DIR  *repert;
    struct dirent *entree;
    int  debordement;
    char *varenv_xorig;


    // initialisation
    nbmails = 0;
    debordement = 0;

    // déterminer si on va traiter les champs X-Original-From
    varenv_xorig = getenv ("libremail_xorig");

    if (varenv_xorig)
        test_xorig = (tolower (*varenv_xorig) != 'n');
    else
        test_xorig = 1;

    // accéder au répertoire
    repert = opendir (".");

    // lire un nom de fichier
    entree = readdir (repert);

    // tant qu'il y a des noms de fichier à lire
    while (entree)
    {
        // vérifier le nom du fichier
        if ((strlen (entree->d_name) ==  8 && opt_n != 'N') ||
            (strlen (entree->d_name) == 10 && entree->d_name [8] == '.'))
        {
            // si on ne peut pas rajouter des fichiers dans la liste
            if (! ajoutlistepossible ())
            {
                // si c'est la première fois
                if (! debordement)
                {
                    // message d'avertissement
             // "Manque de place mémoire, ordre des fichiers mail non respecté"
                    affiche_msg ("DEBORD_NBMAXFIC");

                    // qu'on n'affichera pas plusieurs fois
                    debordement = 1;
                }

                // ordonner les noms de fichiers mail déjà mémorisés
                trinoms ();

                // affichage détaillé ou résumé de l'entête de ces mails
                affiche_liste ();

                // on recommence avec une liste vide pour continuer
                nbmails = 0;
            }

            // mémoriser le nom
            strcpy (fichmail [nbmails++], entree->d_name);
        }

        // et lire le nom du fichier suivant
        entree = readdir (repert);
    }
}



/* vérifie si l'on peut insérer un nom de fichier de plus dans le
   tableau fichmail, et redimmensionne ce tableau si nécessaire */


int ajoutlistepossible ()
{
    descmail *nouvtableau; // adresse du tableau de remplacement
    int  nouvtaille;       // et sa taille
    int  element;          // numéro d'élément dans les tableaux
    int  numcar;           // numéro de caractère dans un nom de fichier


    // cas simple : il reste au moins une place de libre dans le tableau
    if (nbmails + 1 < maxnbmails)
        return (1);

    // calculer la nouvelle taille du tableau
    // l'augmentation est alternativement de 50 % ou 33 % de manière
    // à ce que la taille double après 2 réallocations
    if (maxnbmails % 3)
        nouvtaille = maxnbmails + (maxnbmails / 2);
    else
        nouvtaille = maxnbmails + (maxnbmails / 3);

    // allocation mémoire
    nouvtableau = malloc (12 * nouvtaille);

    // vérification allocation
    if (nouvtableau)
    {
        // copie du contenu de l'ancien tableau dans le nouveau
        for (element = 0; element < maxnbmails; element ++)
            for (numcar = 0; numcar < 12; numcar ++)
                nouvtableau [element][numcar] = fichmail [element][numcar];

        // destruction de l'ancien tableau
        free (fichmail);

        // que l'on remplace par le nouveau
        fichmail = nouvtableau;
        maxnbmails = nouvtaille;
    }

    // retourne le résultat de la possibilité d'insertion d'éléments
    return (nbmails + 1 < maxnbmails);
}



/* tri alphabétique des noms de fichiers mail */

void trinoms ()
{
    char tampon [12];      // chaine de caractères pour permutation
    char carfic1, carfic2; // caractère de 2 noms de fichiers que l'on compare
    int  i;                // indice dans la liste
    int  j;                // numéro de caractère des noms de fichiers


    // initialisation
    i = 0;

    // tant que la liste des noms de fichiers n'a pas été parcourue en entier
    while (i < nbmails - 1)
    {
        // positionnement sur le premier caractère du numéro d'ordre
        j = 1;

        // tant qu'on a le même caractère dans les 2 fichiers
        while (fichmail [i][j] == fichmail [i+1][j])
            // passer au caractère suivant
            j++;

        // si un caractère est une lettre, (fichier issu de Micro$oft Internet
        // mail), on le fait passer avant les chiffres dans la numérotation

        if (fichmail [i][j] & 0x40)
            // lettre codée de 01h à 1Ah après transformation
            carfic1 = fichmail [i][j] & 0x1F;
        else
            // chiffre codé de 30h à 39h conservé tel quel
            carfic1 = fichmail [i][j];

        if (fichmail [i+1][j] & 0x40)
            carfic2 = fichmail [i+1][j] & 0x1F;
        else
            carfic2 = fichmail [i+1][j];

        // on compare 2 éléments consécutifs
        if (carfic1 > carfic2)
        {
            // interversion de 2 noms
            strcpy (tampon, fichmail [i]);
            strcpy (fichmail [i], fichmail [i+1]);
            strcpy (fichmail [i+1], tampon);

            // ce qui peut supposer de comparer un nom déplacé avec le précédent
            if (i > 0)
                i--;
            else
                i++;
        }
        else
            i++;    // on progresse dans la liste
    }
}



/* affiche les informations concernant les fichiers mails mémorisés */

void affiche_liste ()
{
    int  numail;


    // affichage détaillé ou résumé de l'entête des mails
    if (opt_aff)
    {
        // l'ordre d'affichage dépend de l'option -i éventuelle
        if (opt_inv)
        {
            for (numail = nbmails -1; numail >= 0; numail--)
                aff_detail (fichmail [numail]);
        }
        else
        {
            for (numail = 0; numail < nbmails; numail++)
                aff_detail (fichmail [numail]);
        }
    }
    else
    {
        if (opt_inv)
        {
            for (numail = nbmails -1; numail >= 0; numail--)
                aff_resume (fichmail [numail]);
        }
        else
        {
            for (numail = 0; numail < nbmails; numail++)
                aff_resume (fichmail [numail]);
        }
    }
}



/* lit l'entête d'un mail et affiche certaines lignes */

void aff_detail (char *nomfic)
{
    // buffers pour affichage ordonné des caractéristiques du message
    char bufFrom [120], bufTo [120], bufCc [120], bufReply [120],
         bufSubject [120], bufContent [120], bufXorig [120];
    int  date_ok;  // indique si le champ date a été lu
    int i, j;      // simples compteurs


    // ouverture du fichier mail
    fmail = fopen (nomfic, "r");

    // arrêt si erreur imprévue
    if (fmail == 0)
    {
        // "Impossible d'ouvrir le fichier %s"
        aff_err_arg ("IMPOS_OUVR_FICH", nomfic);
        return;
    }

    // initialisation
    bufFrom [0]    = '\0';
    bufTo [0]      = '\0';
    bufCc [0]      = '\0';
    bufReply [0]   = '\0';
    bufSubject [0] = '\0';
    bufContent [0] = '\0';
    bufXorig [0]   = '\0';
    date_ok = 0;

    // tantqu'on peut lire une ligne de l'entête du mail
    while (lire_fmail () && buf_lect [0] != '\0')
    {
        // si la ligne correspond au champ sujet
        if (start ("Subject"))
        {
            // mémoriser la ligne
            memconvbuf (bufSubject);

            // on va tester la ligne qui suit
            lire_fmail ();
            majlignentete ();

            // si c'est encore une ligne du sujet
            if (*buf_lect == ' ' || *buf_lect == '\t')
            {
                // initialisation compteurs
                i = 1;
                j = strlen (bufSubject);

                // compléter le sujet du mail
                while (buf_lect [i] && j < sizeof (bufSubject) - 1)
                    bufSubject [j++] = buf_lect [i++];

                // terminer la chaine de caractères
                bufSubject [j] = '\0';
            }
        }

        // mémorisation des autres caractéristiques importantes
        if (start ("Date"))
        {
            // pas la peine de mémoriser la date si on l'affiche en premier
            // "Fichier mail %s\n"
            printf (message ("AFF_NOM_FICMAIL"), nomfic);
            puts (buf_lect);
            date_ok = 1;  // on sait que c'est un fichier mail
        }
        else if (start ("From"))
            memconvbuf (bufFrom);
        else if (start ("X-Original-From") && test_xorig)
            memconvbuf (bufXorig);
        else if (start ("To"))
            memconvbuf (bufTo);
        else if (start ("Cc"))
            memconvbuf (bufCc);
        else if (start ("Reply-To"))
            memconvbuf (bufReply);
        else if (start ("Content-Type"))
        {
            membuf (bufContent);

            if (recup_ctype () == TextPlain)
                lire_charset ();
        }
        else if (start ("Content-Transfer-Encoding"))
            mem_encodage ();
    }

    // si l'on n'a pas trouvé de ligne Date: From: ou Subject:
    if (! date_ok && ! *bufFrom && ! *bufSubject)
        return;   // ce n'est pas un fichier mail

    // affichage des caractéristiques mémorisées
    if (bufFrom [0])
        puts (bufFrom);
    if (bufXorig [0])
        puts (bufXorig);
    if (bufTo [0])
        puts (bufTo);
    if (bufCc [0])
        puts (bufCc);
    if (bufReply [0])
        puts (bufReply);
    if (bufSubject [0])
        puts (bufSubject);
    if (bufContent [0])
        puts (bufContent);

    // lecture et affichage du début du message
    i = 0;

    while (i++ < nblig_mes && lire_fmail ())
    {
        // on prend en compte les nouveaux champs Content éventuels
        if (start ("Content-Type") && recup_ctype () == TextPlain)
            lire_charset ();
        else if (start ("Content-Transfer-Encoding"))
            mem_encodage ();

        // convertir la ligne lue et l'afficher
        majligne ();
        printf ("%s", buf_lect);
    }

    putchar ('\n');

    // libérer le fichier mail
    fclose (fmail);
}



/* lit l'entête d'un mail et affiche un résumé sur une ligne */

void aff_resume (char *nomfic)
{
    // buffers pour mémoriser les caractéristiques principales du message
    char bufFromTo [120], bufSubject [120], bufDate [120];
    // les noms de mois dans les champs Date: des mails
    static char *mois [] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
    int affichedest; // indique si on doit afficher le champ destinataire
    int i, j;        // simples compteurs


    // ouverture du fichier mail
    fmail = fopen (nomfic, "r");

    // arrêt si erreur imprévue
    if (fmail == 0)
    {
        // "Impossible d'ouvrir le fichier %s"
        aff_err_arg ("IMPOS_OUVR_FICH", nomfic);
        return;
    }

    // déterminer le champ à afficher entre expéditeur et destinataire
    affichedest = (tolower (*nomfic) == 'e') || (tolower (*nomfic) == 's');

    // initialisation
    bufFromTo [0]  = '\0';
    bufSubject [0] = '\0';
    bufDate [0]    = '\0';

    // tantqu'on peut lire une ligne de l'entête du mail
    while (lire_fmail () && buf_lect [0] != '\0')
    {
        // si la ligne correspond au champ sujet
        if (start ("Subject"))
        {
            // mémoriser la ligne
            memconvbuf (bufSubject);

            // on va tester la ligne qui suit
            lire_fmail ();
            majlignentete ();

            // si c'est encore une ligne du sujet
            if (*buf_lect == ' ' || *buf_lect == '\t')
            {
                // initialisation compteurs
                i = 1;
                j = strlen (bufSubject);

                // compléter le sujet du mail
                while (buf_lect [i] && j < sizeof (bufSubject) - 1)
                    bufSubject [j++] = buf_lect [i++];

                // terminer la chaine de caractères
                bufSubject [j] = '\0';
            }
        }

        // mémorisation des autres caractéristiques importantes
        if (start ("Date"))
            membuf (bufDate);
        else if (start ("From") && ! affichedest && ! *bufFromTo)
            memconvbuf (bufFromTo);
        else if (start ("X-Original-From") && test_xorig)
            memconvbuf (bufFromTo);
        else if (start ("To") && affichedest)
            memconvbuf (bufFromTo);
    }

    // si l'on n'a pas trouvé de ligne Date: From: ou Subject:
    if (! *bufDate && ! *bufFromTo && ! *bufSubject)
        return;   // ce n'est pas un fichier mail

    // si on doit afficher l'information sur les mails non lus
    if (opt_n == 'n')
    {
        // le faire
        if (strlen (nomfic) == 10 && nomfic [8] == '.')
            putchar ('N');
        else
            putchar (' ');

        putchar (' ');

        // déjà 2 caractères écrits dans la ligne
        i = 2;
    }
    // sinon
    else
        // on est en début de ligne
        i = 0;

    // affichage du nom de l'expéditeur ou du destinataire du message
    if (*bufFromTo)
    {
        // sauter le mot clé  From: , To:  ou  X-Original-From:
        if (affichedest)
            j = 4;
        else if (tolower (*bufFromTo) == 'x')
            j = 16;
        else
            j = 6;

        while (bufFromTo [j] == '"' || bufFromTo [j] == ' ')
            j++;

        putchar (bufFromTo [j++]);

        // si affichage avec un jeu de caractères ISO-8859
        if (! util_utf8 ())
        {
            // on affichera sz_nom caractères maximum
            while (++i < sz_nom && bufFromTo [j] && bufFromTo [j] != '<'
                         && bufFromTo [j] != '"' && bufFromTo [j] != '=')
                putchar (bufFromTo [j++]);
        }
        // sinon (affichage avec le jeu de caractères UTF-8)
        else
        {
            // on affichera toujours sz_nom caractères maximum
            while (++i < sz_nom && bufFromTo [j] && bufFromTo [j] != '<'
                         && bufFromTo [j] != '"' && bufFromTo [j] != '=')
            {
                // mais certains caractères ne seront pas comptés

                /* il serait plus logique de ne pas compter les caractères
                   entre 0x80 et 0xBF, mais certains caractères UTF8 codés
                   sur 3 (ou 4 ?) octets s'affichent en largeur double, ce
                   qui décale les autres colonnes et génère un saut de ligne
                   intempestif après la date */

                if ((bufFromTo [j] & 0xC0) == 0xC0)
                    i--;

                putchar (bufFromTo [j++]);
            }
        }
    }

    // complété par des blancs
    while (i++ <= sz_nom)
        putchar (' ');

    // affichage du sujet du message
    if (*bufSubject)
    {
        j = 8;

        // on saute les blancs inutiles
        while (bufSubject [j] == ' ')
            j++;

        // si affichage avec un jeu de caractères ISO-8859
        if (! util_utf8 ())
        {
            // on affichera jusqu'à sz_avantdate caractères maximum
            while (++i < sz_avantdate && bufSubject [j])
            {
                // si présence d'une tabulation
                if (bufSubject [j] == '\t')
                {
                    // afficher un blanc pour une meilleure mise en page
                    putchar (' ');
                    j++;
                }
                else
                    // sinon affichage du caractère
                    putchar (bufSubject [j++]);
            }
        }
        // sinon (affichage avec le jeu de caractères UTF-8)
        else
        {
            // on affichera jusqu'à sz_avantdate caractères maximum
            while (++i < sz_avantdate && bufSubject [j])
            {
                // si présence d'une tabulation
                if (bufSubject [j] == '\t')
                {
                    // afficher un blanc pour une meilleure mise en page
                    putchar (' ');
                    j++;
                }
                // sinon affichage du caractère
                else
                {
                    // mais certains caractères ne seront pas comptés
                    // même remarque que pour bufFromTo
                    if ((bufSubject [j] & 0xC0) == 0xC0)
                        i--;

                    putchar (bufSubject [j++]);
                }
            }
        }
    }
    else
        i++;    // pour corriger l'alignement

    // complété par des blancs
    while (i++ <= sz_avantdate)
        putchar (' ');

    // affichage de la date et de l'heure du message
    if (*bufDate)
    {
        j = 6;

        // jour du mois
        while (bufDate [j] < '0' || bufDate [j] > '9')
            j++;

        printf ("%02d", atoi (bufDate + j));

        while (bufDate [j] > ' ')
            j++;

        j++;

        // mois
        i = 0;

        while (i < 12 && (toupper (bufDate [j]) != mois [i][0]
                     || tolower (bufDate [j+1]) != mois [i][1]
                     || tolower (bufDate [j+2]) != mois [i][2]))
            i++;

        printf ("/%02d/", i + 1);

        // année sur 2 chiffres
        while (bufDate [j] < '0' || bufDate [j] > '9')
            j++;

        printf ("%02d", atoi (bufDate + j) % 100);

        while (bufDate [j] > ' ')
            j++;

        // heure, sans les secondes ni le fuseau horaire
        for (i = 0; i < 6; i++)
            putchar (bufDate [j++]);
    }

    // passage à la ligne
    putchar ('\n');

    // libérer le fichier mail
    fclose (fmail);
}