/*
    Fichier messages.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-en.txt , licence-es.txt , licence-pt.txt ,
    licence-eo.txt , licence-eo-utf.txt

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


    Bibliothèque de fonctions utilisant des fichiers de données
    pour dialoguer avec l'utilisateur dans sa langue.
*/


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "messages.h"


// #define DEBUG


#define  szlig   120  // longueur maximale des lignes des fichiers de données



/* variables globales au source */

char *ncom;  // nom de (ou chemin d'accès à) la commande en cours d'exécution
char repert_com [szchemin] = ""; // répertoire où est implantée cette commande



/* mémorise le nom ou le chemin d'accès de la commande en cours d'exécution */

void memcom (char *arg0)
{
    ncom = arg0;

    // si le chemin d'accès à la commande est un chemin relatif
    if (*ncom != '/')
        // chercher le chemin d'accès absolu avant un éventuel
        // chdir dans le programme, on évitera ainsi une erreur
        // d'ouverture ultérieure du fichier des messages
        dircom ();
}



/* retourne le nom ou le chemin d'accès de la commande en cours d'exécution */

char *nomcom ()
{
    return (ncom);
}



/* retourne le nom du répertoire où est la commande en cours d'exécution */

char *dircom ()
{
    int derslash;  // position du dernier slash rencontré dans ncom
    char *path;    // variable d'environnement PATH
    char chemcom [szchemin]; // chemin d'accès complet à la commande
    int  i, j;     // indices pour parcourir et recopier ncom et path


    // si le nom de ce répertoire n'est pas encore mémorisé
    if (! *repert_com)
    {
        // initialisation
        derslash = 0;
        i = 0;
        j = 0;

        // recopier ncom dans repert_com et chercher la position du dernier /
        while (ncom [i])
        {
            // on saute les ./ trouvés pendant la recopie
            while (ncom [i] == '.' && ncom [i+1] == '/')
                i = i + 2;

            // recopier un nom de répertoire (ou le nom du fichier de commande)
            while (ncom [i] != '/' && ncom [i])
                repert_com [j++] = ncom [i++];

            // si on arrive sur un /
            if (ncom [i] == '/')
            {
                // mémoriser la position du / dans repert_com
                derslash = j;

                // et le recopier
                repert_com [j++] = ncom [i++];
            }
        }

        // on ne conserve dans repert_com que le nom
        // du répertoire où est implantée la commande
        repert_com [derslash] = '\0';

        // si aucun nom de répertoire n'a été trouvé
        if (! *repert_com)
        {
            // on va tester les chemins de la variable d'environnement PATH
            path = getenv ("PATH");

            i = 0;

            do
            {
                j = 0;

                // recopier l'un de ces chemins
                while (path [i] != ':' && path [i])
                {
                    repert_com [j++] = path [i++];
                }

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

                // générer le chemin d'accès à la commande
                sprintf (chemcom, "%s/%s", repert_com, ncom);
            }
            // terminé si ce chemin d'accès est le bon
            while (access (chemcom, X_OK) != 0 && path [i++]);
        }

        // si le chemin d'accès trouvé est un chemin d'accès relatif
        // on va le transformer en chemin d'accès absolu.
        // la transformation optimisée est complexe, mais nécessaire
        // pour le cas ou le programme fait un chdir avant d'ouvrir
        // les fichier des messages ou un autre fichier de données
        if (*repert_com != '/')
        {
            // récupérer le nom du répertoire courant
            strcpy (chemcom, repert_com);
            getcwd (repert_com, szchemin);

            // on va y accoler le chemin d'accès relatif au
            // répertoire trouvé en traitant les ../

            // initialisation
            i = 0;
            j = strlen (repert_com);

            // on rajoute / au répertoire courant
            repert_com [j++] = '/';
            repert_com [j]  = '\0';

            while (chemcom [i])
            {
                // si on a le répertoire ..
                if (chemcom [i] == '.' && chemcom [i+1] == '.'
                       && (chemcom [i+2] == '/' || chemcom [i+2] == '\0'))
                {
                    // se positionner après le .. terminal
                    i = i + 2;

                    // ou après le ../
                    if (chemcom [i] == '/')
                        i++;

                    // remonter d'un répertoire dans repert_com
                    do
                        j--;
                    while (j > 1 && repert_com [j-1] != '/');

                    // on ne supprime pas le premier /
                    if (j == 0)
                        j++;
                }
                // sinon (cas général)
                else
                {
                    // recopier un nom de répertoire
                    while (chemcom [i] != '/' && chemcom [i] && j < szchemin)
                        repert_com [j++] = chemcom [i++];

                    repert_com [j++] = chemcom [i++];
                }
            }
        }

        j = strlen (repert_com);

        // enlever les /. terminaux
        while (j > 1 && repert_com [j-1] == '.' && repert_com [j-2] == '/')
            j = j - 2;

        repert_com [j] = '\0';
    }

    // retourner le nom du répertoire
    return (repert_com);
}



/* retourne la langue de l'utilisateur dans une chaine de 2 caractères */

char *getlang ()
{
    char *langue;           // contenu de la variable d'environnement $LANG
    static char retour [3]; // chaine de caractères retournée


    // récupérer la variable d'environnement $LANG
    langue = (char *) getenv ("LANG");

    // si cette variable d'environnement a été déclarée
    if (langue)
    {
        // on retournera les 2 premiers caractères de cette variable
        retour [0] = langue [0];
        retour [1] = langue [1];
        retour [2] = '\0';
    }
    // sinon
    else
        // on retournera une chaine vide
        *retour = '\0';

    // retourner le résultat
    return (retour);
}



/* vérifie si le jeu de caractères UTF-8 est utilisé */

int util_utf8 ()
{
    static int testacces; // mémorise si on a déjà appelé cette fonction
    static int restest;   // résultat de la vérification
    char       *langue;   // caractère de la variable d'environnement $LANG
    int        lg_langue; // longueur de la chaine $LANG


    // si on appelle cette fonction pour la première fois
    if (! testacces)
    {
        // récupérer la variable d'environnement $LANG
        langue = (char *) getenv ("LANG");

        // initialisation
        restest = 0;

        // si cette variable d'environnement est initialisée et se
        // termine par UTF-8, ou UTF8 en majuscules ou minuscules
        // alors on utilise ce jeu de caractères
        if (langue) // && strlen (langue) > 5)
        {
            // compter le nombre de caractères de $LANG
            lg_langue = 0;

            while (*langue)
            {
                langue ++;
                lg_langue ++;
            }

            // et se positionner sur le dernier
            langue --;

            // si $LANG assez long et se termine par '8'
            if (lg_langue > 5 && *langue == '8')
            {
                // sauter le '-' précédent éventuel
                if (*(--langue) == '-')
                    langue --;

                // vérifier et mémoriser si chaine "utf" ou "UTF"
                if ((*(langue-2) | 0x20) == 'u' && (*(langue-1) | 0x20) == 't'
                                                && (*langue | 0x20) == 'f')
                    restest = 1;
            }
        }

        // on ne repassera plus dans ce bloc d'instructions
        testacces = 1;
    }

    // retourner le résultat du test
    return restest;
}



/* retourne le chemin d'accès à un fichier de données
   après avoir choisi le fichier le mieux adapté compte tenu :
   - de la langue de l'utilisateur
   - de l'indicateur de prise en compte du jeu de caractères utf-8
   - de la présence ou non du fichier deflang-cyloop
*/


char *chemfichlang (char *nomfic, int utf8)
{
    static char chemfic [szchemin]; // chemin d'accès au fichier de données
    char ficdeflang [szchemin];     // chemin d'accès au fichier "deflang-..."
    char *pos_suffixe;              // posit 1er caract du suffixe dans chemfic
    char langue [3];                // langue de l'utilisateur
    int  sans_suff, avec_suff;      // indicateur de présence des fichiers


    // générer le chemin d'accès au fichier sans suffixe

    // si cyloop est implanté localement
    if (strcmp (dircom (), "/usr/bin") != 0)
        // fichiers de données dans le même répertoire que les commandes
        sprintf (chemfic, "%s/%s", dircom (), nomfic);
    // sinon, les commandes de cyloop sont dans /usr/bin
    else
        // et les fichiers de données dans /usr/share/cyloop
        sprintf (chemfic, "/usr/share/cyloop/%s", nomfic);

    // récupérer la langue de l'utilisateur
    strcpy (langue, getlang ());

    // si une langue a été définie
    if (*langue)
    {
        // mémoriser la possibilité d'accès en lecture du fichier sans suffixe
        sans_suff = (access (chemfic, R_OK) == 0);

        // chercher la position où rajouter un suffixe au fichier
        pos_suffixe = chemfic + strlen (chemfic);

        // si jeu de caractères UTF-8 utilisé et à prendre en compte
        if (utf8)
        {
            // rajouter au chemin d'accès la langue suivie de "-utf"
            sprintf (pos_suffixe, ".%s-utf", langue);

            // si ce fichier n'existe pas ou n'est pas accessible en lecture
            if (access (chemfic, R_OK) < 0)
                // supprimer le "-utf" à la fin du chemin d'accès
                pos_suffixe [3] = '\0';
        }
        // sinon
        else
            // rajouter au chemin d'accès l'indication de la langue
            sprintf (pos_suffixe, ".%s", langue);

        // mémoriser la possibilité d'accès en lecture du fichier avec suffixe
        avec_suff = (access (chemfic, R_OK) == 0);

        // si les fichiers avec et sans suffixe sont tous
        // deux accessibles en lecture, ou si aucun ne l'est
        if (sans_suff == avec_suff)
        {
            // créer le chemin d'accès au fichier "deflang-cyloop"
            sprintf (ficdeflang, "%s/deflang-cyloop", dircom ());

            // si ce fichier existe
            if (access (ficdeflang, F_OK) == 0)
                // revenir au nom de fichier sans suffixe
                *pos_suffixe = '\0';

            // sinon, on utilisera le nom de fichier avec suffixe
        }
        // sinon si le fichier sans suffixe est le seul accessible en lecture
        else if (sans_suff && (! avec_suff))
            // revenir au nom de fichier sans suffixe
            *pos_suffixe = '\0';

        // sinon le fichier avec suffixe est le seul accessible en lecture
        // son nom est déjà mémorisé dans chemfic
    }

    // retourner le chemin d'accès au fichier
    return chemfic;
}



/* sortie du programme sur impossibilité d'afficher un message */

void erreur_mess (int numerr, char *info)
{
    // messages mémorisés en dur dans le logiciel dans des tableaux
    // statiques pour éviter allocation mémoire et initialisation

    static char *fichmanquant [] =
    {
         "fr Fichier %s manquant ou protégé en lecture",
//       "de In Lektüre geschützte oder fehlende Kartei %s",
         "en File %s missing or read protected",
         "eo Dosiero %s mankante au protektita en lego",
         "es Fichero %s que falta o protegido en lectura",
//       "it Archivio %s che manca o protetto in lettura",
//       "nl Bestand dat %s of dat in lezing wordt beschermd gebrek heeft aan",
         "pt Ficheiro %s que falta ou protegido em leitura",
         NULL   // pour terminer la liste
    };

    static char *mnemomanquant [] =
    {
         "fr Mnémonique MNEMO_ABSENT manquant dans le fichier %s",
//       "de MNEMO_ABSENT Mnemonik, die in der Kartei %s fehlt",
         "en Mnemonic MNEMO_ABSENT missing in the file %s",
         "eo Mnemoniko MNEMO_ABSENT mankante en la dosiero %s",
         "es Mnemotecnia MNEMO_ABSENT que falta en el fichero %s",
//       "it Mnémonique MNEMO_ABSENT che manca nell'archivio %s",
//       "nl Mnémonique MNEMO_ABSENT die in het bestand %s gebrek heeft aan",
         "pt Mnémonique MNEMO_ABSENT que falta no ficheiro %s",
         NULL   // pour terminer la liste
    };

    char **listemessages;   // pour sélectionner une liste de messages
    char langue [3];        // langue de l'utilisateur
    char mess_err [szlig];  // contenu du message récupéré
    int  i;                 // compteur dans la liste des messages


    // sélectionner la bonne liste de messages
    if (numerr == 1)
        listemessages = fichmanquant;
    else
        listemessages = mnemomanquant;

    // récupérer la langue de l'utilisateur
    strcpy (langue, getlang ());

    // rechercher le message dans la bonne langue
    i = 0;

    while (listemessages [i] && (listemessages [i][0] != langue [0]
                              || listemessages [i][1] != langue [1]))
        i++;

    // si le message a été trouvé
    if (listemessages [i])
    {
        // le mettre en forme
        sprintf (mess_err, listemessages [i] + 3, info);

        // si on utilise l'encodage UTF-8
        if (util_utf8 ())
            // convertir les caractères accentués du message
            conv_iso_utf8 (mess_err);

        // afficher le message d'erreur
        fprintf (stderr, "\n%s\n", mess_err);
    }
    // sinon
    else
    {
        // on va afficher le message dans toutes les langues disponibles
        i = 0;

        while (listemessages [i])
        {
            // récupérer un message et le mettre en forme
            sprintf (mess_err, listemessages [i++] + 3, info);

            // si on utilise l'encodage UTF-8 convertir les caractères accentués
            if (util_utf8 ())
                conv_iso_utf8 (mess_err);

            // afficher le message d'erreur
            fprintf (stderr, "\n%s\n", mess_err);
        }
    }

    // sortir du programme
    exit (-1);
}



/* retourne le message associé au mnémonique passé en paramètre */

char *message (char *mnemonique)
{
    static int testacces = 0;    // mémorise si on a déjà appelé cette fonction
    static char ficmes [szchemin]; // chemin d'accès au fichier mess-cyloop
    static FILE *desc_ficmes;    // descripteur du fichier mess-cyloop
    static char mess_lu [szlig]; // message récupéré dans le fichier
    char ligne [szlig];          // contenu d'une ligne du fichier
    int  retourdeb;              // mémorise si retour en début de fichier
    int  i, j;                   // indices pour traitements chaines caractères


#ifdef DEBUG
    printf ("Appel de message (%s)\n", mnemonique);
    sleep (1);
#endif

    // si on appelle cette fonction pour la première fois
    if (! testacces)
    {
        // générer le chemin d'accès au fichier mess-cyloop
        // en tenant compte de la langue et du jeu de caractères
        strcpy (ficmes, chemfichlang ("mess-cyloop", util_utf8 ()));

        // tenter de l'ouvrir en lecture
        desc_ficmes = fopen (ficmes, "r");

        // si le fichier mess-cyloop n'a pas pu être ouvert
        if (!desc_ficmes)
            // message d'erreur fatale
            // "Fichier %s manquant ou protégé en lecture"
            erreur_mess (1, ficmes);

        // on ne repassera plus dans ce bloc d'instructions
        testacces = 1;

        // on est au début du fichier
        retourdeb = 1;
    }
    // sinon
    else
        // on va commencer par tester la chaine qui suit la dernière trouvée
        retourdeb = 0;

    // le fichier ficmes-cyloop a pu être ouvert
    // on va l'explorer pour trouver le mnémonique

    // premier essai à partir de la ligne qui suit celle du message précédent
    fgets (ligne, szlig, desc_ficmes);

    do
    {
        // comparaison du mnémonique de la ligne
        // lue avec celui passé en paramètre
        i = 0;

        while (ligne [i] == mnemonique [i])
            i++;

        // si mnémonique trouvé
        if (mnemonique [i] == '\0' && (ligne [i] == ' ' || ligne [i] == '\t'))
        {
            // se positionner sur le message correspondant
            while (ligne [i] != '"' && ligne [i] != '\n' && ligne [i])
                i++;

            j = 0;
            i++;

            // on va recopier le message
            while (ligne [i] != '"' && ligne [i] != '\n' && ligne [i])
            {
                // si \ dans le message, on convertit le caractère spécial
                if (ligne [i] == '\\')
                {
                    switch (ligne [++i])
                    {
                        case 'n' : mess_lu [j++] = '\n';
                                   break;

                        case 'r' : mess_lu [j++] = '\r';
                                   break;

                        case 't' : mess_lu [j++] = '\t';
                                   break;

                        case '\\': mess_lu [j++] = '\\';
                                   break;

                        default  : mess_lu [j++] = '\\';
                                   mess_lu [j++] = ligne [i];
                    }

                    // et on passe au caractère suivant
                    i++;
                }
                // sinon, simple copie du caractère
                else
                    mess_lu [j++] = ligne [i++];
            }

            // terminer le message
            mess_lu [j] = '\0';

            // si on utilise l'encodage UTF-8
            if (util_utf8 ())
                // convertir les caractères accentués du message
                conv_iso_utf8 (mess_lu);

            // retourner le message trouvé
            return (mess_lu);
        }

        // si on vient de tester sans succès la chaine du fichier
        // qui suit celle du message demandé juste auparavant
        if (! retourdeb)
        {
            // reprendre la recherche en début de fichier
            rewind (desc_ficmes);

            // on explorera le fichier jusqu'à la fin si nécessaire
            retourdeb = 1;
        }
    }
    // continuer la recherche ligne par ligne jusqu'en fin de fichier
    while (fgets (ligne, szlig, desc_ficmes));

    // mnémonique non trouvé dans le fichier mess-cyloop
    if (strcmp ("MNEMO_ABSENT", mnemonique) != 0)
        // sortie sur message d'erreur
        err_fatale_arg ("MNEMO_ABSENT", mnemonique);
    else
        // "Mnémonique MNEMO_ABSENT manquant dans le fichier des messages"
        erreur_mess (2, ficmes);
}



/* convertit les caractères spéciaux du message
   passé en paramètre dans le format UTF-8 */

void conv_iso_utf8 (char *mess)
{
    int i, j;  // compteurs de caractères


    // initialisation
    i = 0;
    j = 0;

    // compter les caractères spéciaux
    while (mess [i])
    {
        if (mess [i++] & 0x80)
            j++;
    }

    // terminé si pas de caractères spéciaux
    if (j == 0)
        return;

    // décaler la chaine à convertir de j caractères
    j = j + i;

    while (i >= 0)
        mess [j--] = mess [i--];

    // remettre les caractères à la bonne place
    // en convertissant les caractères spéciaux
    do
    {
        // si caractère spécial
        if (mess [++j] & 0x80)
        {
            // on le remplace par les 2 caractères de l'encodage UTF-8
            if (mess [j] & 0x40)
            {
                mess [++i] = 'Ã';
                mess [++i] = mess [j] ^ 0x40;
            }
            else
            {
                mess [++i] = 'Â';
                mess [++i] = mess [j];
            }
        }
        // sinon
        else
            // recopier le caractère sans conversion
            mess [++i] = mess [j];
    }
    while (mess [j]);
}



/* affichage d'un texte puis rester en fin de ligne */

void affiche_msg (char *mnemonique)
{
    printf (message (mnemonique));
}



/* affichage d'une ligne de texte puis passage à la ligne */

void affiche_ligne (char *mnemonique)
{
    puts (message (mnemonique));
}



/* affichage d'un message d'erreur simple */

void affiche_err (char *mnemonique)
{
    fputs (message (mnemonique), stderr);
    fputc ('\n', stderr);
}



/* affichage d'un message d'erreur avec argument */

void aff_err_arg (char *mnemonique, char *argument)
{
    fprintf (stderr, message (mnemonique), argument);
    fputc ('\n', stderr);
}



/* affichage d'un message d'erreur avec argument numérique */

void aff_err_argnum (char *mnemonique, int argument)
{
    fprintf (stderr, message (mnemonique), argument);
    fputc ('\n', stderr);
}



/* sortie du programme sur erreur fatale (message simple) */

void err_fatale (char *mnemonique)
{
    // écrire le message d'erreur
    affiche_err (mnemonique);

    // sortir du programme
    exit (-1);
}



/* sortie du programme sur erreur fatale (message avec argument) */

void err_fatale_arg (char *mnemonique, char *argument)
{
    // écrire le message d'erreur
    aff_err_arg (mnemonique, argument);

    // sortir du programme
    exit (-1);
}



/* sortie du programme en rappellant la syntaxe d'une commande */

void psyntaxe (char *mnemonique)
{
    err_fatale_arg (mnemonique, nomcom ());
}



/* sortie du programme en rappellant la syntaxe d'une commande sur 2 lignes */

void psyntaxe2 (char *mnemonique1, char *mnemonique2)
{
    aff_err_arg (mnemonique1, nomcom ());
    err_fatale  (mnemonique2);
}