/*
    Fichier cyladdval.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


    Programme permettant de mettre à jour la variable d'un fichier
    cyloop lorsqu'elle mémorise les valeurs de cette variable.
*/


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "types.h"
#include "limites.h"
#include "messages.h"
#include "majcycle.h"


// prototypes
void pondere (int coef_pond);
void addval (char *chaineval, uint16 num_instant, int majcmoy);
void addvalint32 (int32 val, uint16 num_instant, int majcmoy);
void addvalfloat (float val, uint16 num_instant, int majcmoy);
int  testval (char *valeur);



// variables globales (pour éviter des tas de passages de paramètre)
FILE        *descfic;  // descripteur du fichier cyloop
desc_cyloop entetefic; // entête du fichier cyloop
desc_var    entetevar; // entête de la variable du fichier cyloop



// programme principal

main (int narg, char *varg[])
{
    char      *nomfic;       // nom du fichier cyloop
    struct tm stm;           // structure contenant la date et l'heure courante
    uint32    timep;         // date et heure dans un uint32
    int       maj_entetefic; // indicateur pour mise à jour fichier
    uint16    num_instant;     // numéro de la donnée à incrémenter
    uint32    duree_cycle;   // durée du cycle


    // mémorisation du nom de la commande
    memcom (*varg);

    // test des paramètres
    if (--narg > 1 && testval (varg [2]))
        // récupération du nom de fichier et rajout éventuel du suffixe
        nomfic = recup_nomfic (varg [1]);
    else
    {
        // "Nom de fichier manquant ou valeur de la variable manquante"
        // "Syntaxe : %s nom_fichier_cyloop valeur"
        affiche_err ("MANQUE_NOM_OU_VAR");
        psyntaxe ("SYNT_CYLADDVAL");
    }

    // ouverture en lecture / écriture du fichier cyloop
    descfic = fopen (nomfic, "r+");

    if (! descfic)
        // "Fichier_cyloop %s inexistant ou protégé en lecture ou écriture"
        err_fatale_arg ("ACCES_CYLOOP_RW",
                                                                   nomfic);

    // lecture de l'entête du fichier cyloop
    fread (&entetefic, 16, 1, descfic);

    // vérification de la signature
    if (entetefic.signature [0] != 'c' || entetefic.signature [1] != 'y'
                                       || entetefic.signature [2] != 'l')
    {
        // "Le fichier %s n'est pas un fichier_cyloop"
        aff_err_arg ("FICH_NON_CYLOOP", nomfic);
        fclose (descfic);
        return;
    }

    // vérification de la valeur nb_var
    if (entetefic.nb_var)
    {
        // "Ce fichier cyloop utilise une liste de variables"
        // "utiliser la version complète de cyloop"
        affiche_err ("CYLOOP_LISTEVAR");
        affiche_err ("UTIL_VCOMPLETE");
        fclose (descfic);
        return;
    }

    // lecture de la partie fixe de l'entête de la variable du fichier cyloop
    fread (&entetevar, 6, 1, descfic);

    // vérification de la nature de la variable
    if (entetevar.typedon & compteur)
    {
        // "La variable du fichier cyloop est un compteur : utiliser cylincr"
        affiche_err ("VAR_COMPTEUR");
        fclose (descfic);
        return;
    }

    // fin de lecture de l'entête de la variable si nécessaire
    if (! (entetevar.typedon & cmoy_sep))
        fread (&entetevar.coef_moy, 2, 1, descfic);

    // récupérer la date et l'heure courante
    time (&timep);

    // si le début du cycle est ultérieur à l'instant présent
    if (timep < entetefic.deb_cycle)
    {
        fclose (descfic);

        // "Régression dans le temps : une intervention manuelle "
        // "sur le fichier cyloop ou l'horloge est nécessaire"
        affiche_err ("REGRES_TEMPS");
        err_fatale ("REGRES_TEMPS2");
    }

    // initialisation indicateurs de mise à jour du fichier
    maj_entetefic = NON;

    // si on utilise l'heure locale
    if (! (entetefic.caract_gen & temps_utc))
    {
        // récupérer le détail de la date et de l'heure locale
        localtime_r (&timep, &stm);

        // si changement d'heure été/hiver
        if ((entetefic.etat_cyloop & heure_ete) != stm.tm_isdst)
            // prendre en compte ce changement
            maj_entetefic = maj_type_heure (stm.tm_isdst, timep);
    }

    // si changement de cycle
    if (entetefic.deb_cycle + entetefic.duree_cycle <= timep)
    {
        // prise en compte de ce changement de cycle
        changement_cycle (timep);

        // une manière de mémoriser un début de cycle
        entetevar.der_enreg = 0;

        // on reportera la mise à jour dans le fichier cyloop
        maj_entetefic = OUI;
    }

    // si mise à jour de l'entête du fichier cyloop nécessaire
    if (maj_entetefic)
    {
        // recopier la nouvelle entête
        rewind (descfic);
        fwrite (&entetefic, 16, 1, descfic);
    }

    // récupération de la durée du cycle actuel
    duree_cycle = duree_cycle_actuel (&timep);

    // calcul de l'instant du cycle à mettre à jour
    num_instant = calcul_instant (timep, duree_cycle);

    // si on a changé de cycle et une seule valeur coef_moy pour la variable
    if (! entetevar.der_enreg && !(entetevar.typedon & cmoy_sep))
    {
        // mettre à jour cette valeur
        if (entetevar.ponderation)
            entetevar.coef_moy += 256;
        else
            entetevar.coef_moy ++;

        // y compris dans le fichier cyloop
        fseek (descfic, 22, SEEK_SET);
        fwrite (&entetevar.coef_moy, 2, 1, descfic);
    }

    // si c'est la première mise à jour effectuée durant ce cycle
    // ou si on passe à un autre instant du même cycle
    if (entetevar.der_enreg < num_instant)
    {
        // mettre à jour le numéro de l'instant du cycle traité
        entetevar.der_enreg = num_instant;

        // y compris dans le fichier cyloop
        fseek (descfic, 16, SEEK_SET);
        fwrite (&entetevar.der_enreg, 2, 1, descfic);

        // mettre à jour la valeur de la variable et si nécessaire
        // le champ coef_moy pour l'instant du cycle concerné
        addval (varg [2], num_instant, OUI);
    }
    // sinon, si on a déjà mis à jour la variable pour cet instant du cycle
    else if (entetevar.der_enreg == num_instant)
    {
        // si on est autorisé à le mettre à jour plusieur
        // fois pour chacun des instants du cycle
        if (entetevar.typedon & cumul_donnees)
            // mettre à jour la valeur de la variable et si nécessaire
            // le champ coef_moy pour l'instant du cycle concerné
            addval (varg [2], num_instant, entetevar.typedon & cpt_toute_don);
    }
    // sinon, il y a régression dans le temps
    else
    {
        // vérifier si elle est supérieure à 2 heures
        if (calcul_instant (timep + 7200, duree_cycle) < entetevar.der_enreg)
        {
            // vérifier si elle est supérieure à 1 journée
            // et afficher le message d'erreur approprié
            if (calcul_instant (timep + secjour, duree_cycle) <
                                                 entetevar.der_enreg)
            {
                // "Forte régression dans le temps : une intervention manuelle "
                // "sur le fichier cyloop ou l'horloge est nécessaire"
                affiche_err ("FORTE_REGR_TEMPS");
                affiche_err ("REGRES_TEMPS2");
            }
            else
              // "Régression dans le temps : vérifier l'horloge de l'ordinateur"
                affiche_err ("REGR_VERIF_HORL");
        }
    }

    // le fichier cyloop a été mis à jour
    fclose (descfic);
}



// test de la valeur passée en paramètre

int testval (char *valeur)
{
    // saut du signe - éventuel
    if (*valeur == '-')
        valeur++;

    // si la valeur commence par des chiffres
    if ('0' <= *valeur && *valeur <= '9')
    {
        // les parcourrir
        do
            valeur++;
        while ('0' <= *valeur && *valeur <= '9');

        // si plus rien après les chiffres
        if (! *valeur)
            // la valeur est un entier
            return OUI;

        // si partie décimale
        if (*valeur == '.' || *valeur == ',')
        {
            // la parcourir
            do
                valeur++;
            while ('0' <= *valeur && *valeur <= '9');
        }

        // si exposant
        if (tolower (*valeur) == 'e')
        {
            // on va l'analyser
            valeur ++;

            // saut du signe - éventuel
            if (*valeur == '-')
                valeur++;

            // si l'exposant contient des chiffres
            if ('0' <= *valeur && *valeur <= '9')
            {
                // les parcourrir
                do
                    valeur++;
                while ('0' <= *valeur && *valeur <= '9');
            }
        }

        // succés de l'analyse si l'exploration de la valeur est terminé
        return (*valeur == '\0');
    }
    // sinon, contenu de la valeur incorrect
    else
        return NON;
}



// calcul de pondération sur les cycles antérieurs

void pondere (int coef_pond)
{
    uint16 coef_moy; // coef_moy si distinct pour chaque instant du cycle
    int32  valeur32b;  // valeur à pondérer si elle est entière sur 32 bits
    float  valeurf;    // valeur à pondérer si elle est en virgule flottante
    uint32 valmaj;     // valeur sur 32 bits pour calcul de pondération
    uint32 positfic;   // positionnement sur une nouvelle valeur du cycle
    int    tailledon;  // nombre d'octets des données pour un instant du cycle
    int    i;          // compteur


    // nombre d'octets pour chaque instant du cycle
    if (entetevar.typedon & mem_min_max)
        tailledon = 12;
    else
        tailledon = 4;

    // si un coefficient coef_moy par instant du cycle
    if (entetevar.typedon & cmoy_sep)
    {
        // corriger le calcul de tailledon
        tailledon = tailledon + 2;

        // position des premières données dans le fichier
        positfic = 22;

        // si valeurs en virgule flottante
        if (entetevar.typedon & val_float)
        {
            // mise à jour des valeurs de coef_moy et de la variable
            for (i = entetefic.nb_don_cycle; i > 0; i--)
            {
                // positionnement dans le fichier
                fseek (descfic, positfic, SEEK_SET);

                // récupération de la valeur du coef_moy
                fread (&coef_moy, 2, 1, descfic);

                // récupération de la valeur de la variable en virgule flottante
                fread (&valeurf, 4, 1, descfic);

                // calcul de pondération pour coef_moy
                // le calcul de multiplication et division est fait sur 32 bits
                valmaj = coef_moy;
                coef_moy = (valmaj * coef_pond) >> 8;

                // calcul de la nouvelle valeur
                valeurf = valeurf * (coef_pond / 256.0);

                // mettre à jour les valeurs dans le fichier
                fseek (descfic, -6, SEEK_CUR);
                fwrite (&coef_moy, 2, 1, descfic);
                fwrite (&valeurf, 4, 1, descfic);

                // position des données pour l'instant suivant dans le fichier
                positfic = positfic + tailledon;
            }
        }
        // sinon (valeurs de la variable entière sur 32 bits)
        else
        {
            // mise à jour des valeurs de coef_moy et de la variable 32 bits
            for (i = entetefic.nb_don_cycle; i > 0; i--)
            {
                // positionnement dans le fichier
                fseek (descfic, positfic, SEEK_SET);

                // récupération de la valeur du coef_moy
                fread (&coef_moy, 2, 1, descfic);

                // récupération de la valeur de la variable sur 32 bits
                fread (&valeur32b, 4, 1, descfic);

                // calcul de pondération pour coef_moy
                // le calcul de multiplication et division est fait sur 32 bits
                valmaj = coef_moy;
                coef_moy = (valmaj * coef_pond) >> 8;

                // calcul de pondération pour la valeur de la variable
                // si risque de débordement lors du calcul
                if (valeur32b & 0xFF000000)
                    // faire la division d'abord
                    valeur32b = (valeur32b >> 8) * coef_pond;

                // sinon faire la multiplication d'abord
                else
                    valeur32b = (valeur32b * coef_pond) >> 8;

                // mettre à jour les valeurs dans le fichier
                fseek (descfic, -6, SEEK_CUR);
                fwrite (&coef_moy, 2, 1, descfic);
                fwrite (&valeur32b, 4, 1, descfic);

                // position des données pour l'instant suivant dans le fichier
                positfic = positfic + tailledon;
            }
        }
    }
    // sinon (coefficient coef_moy global à la variable)
    else
    {
        // position des premières données dans le fichier
        positfic = 24;

        // mettre à jour ce coefficient
        // le calcul de multiplication et division est fait sur 32 bits
        valmaj = entetevar.coef_moy;
        entetevar.coef_moy = (valmaj * coef_pond) >> 8;

        // ce coefficient sera mis à jour plus tard dans le fichier

        // si valeurs en virgule flottante
        if (entetevar.typedon & val_float)
        {
            // mise à jour des valeurs en virgule flottante
            for (i = entetefic.nb_don_cycle; i > 0; i--)
            {
                // positionnement dans le fichier
                fseek (descfic, positfic, SEEK_SET);

                // récupération de la valeur de la variable en virgule flottante
                fread (&valeurf, 4, 1, descfic);

                // calcul de la nouvelle valeur
                valeurf = valeurf * (coef_pond / 256.0);

                // mettre à jour la valeur de la variable dans le fichier
                fseek (descfic, -4, SEEK_CUR);
                fwrite (&valeurf, 4, 1, descfic);

                // position des données pour l'instant suivant dans le fichier
                positfic = positfic + tailledon;
            }
        }
        // sinon (valeurs de la variable entière sur 32 bits)
        else
        {
            // mise à jour des valeurs entières sur 32 bits
            for (i = entetefic.nb_don_cycle; i > 0; i--)
            {
                // positionnement dans le fichier
                fseek (descfic, positfic, SEEK_SET);

                // récupération de la valeur de la variable sur 32 bits
                fread (&valeur32b, 4, 1, descfic);

                // si risque de débordement lors du calcul
                if (valeur32b & 0xFF000000)
                    // faire la division d'abord
                    valeur32b = (valeur32b >> 8) * coef_pond;

                // sinon faire la multiplication d'abord
                else
                    valeur32b = (valeur32b * coef_pond) >> 8;

                // mettre à jour la valeur de la variable dans le fichier
                fseek (descfic, -4, SEEK_CUR);
                fwrite (&valeur32b, 4, 1, descfic);

                // position des données pour l'instant suivant dans le fichier
                positfic = positfic + tailledon;
            }
        }
    }
}



// rajoute la valeur contenue dans chaineval à l'instant num_instant du cycle

void addval (char *chaineval, uint16 num_instant, int majcmoy)
{
    // selon le type des données mémorisées
    if (entetevar.typedon & val_float)
        // appeler la bonne fonction d'ajout
        addvalfloat (atof (chaineval), num_instant, majcmoy);
    else
        addvalint32 (atol (chaineval), num_instant, majcmoy);
}



// rajoute la valeur val (entière 32 bits) à l'instant num_instant du cycle

void addvalint32 (int32 val, uint16 num_instant, int majcmoy)
{
    long   posval;     // position de la valeur à mettre à jour dans le fichier
    uint16 coef_moy; // coefficient coef_moy pour la valeur à mettre à jour
    int32  valeur;     // valeur à mettre à jour
    int32  nouv_val;   // valeur mise à jour
    int32  ajout;      // val à rajouter à la variable avec coef multiplicateur
    int32  min, max;   // valeurs min et max mémorisées
    int    majfich;    // indique si on devra mettre à jour le fichier
    int    maj_minmax; // indique si on devra mettre à jour la valeur min ou max


    // initialisation
    majfich = OUI;
    maj_minmax = NON;

    // pour le calcul de position, le premier instant
    // du cycle n'entrainera pas de décalage
    num_instant --;

    // calcul de la position dans le fichier de la valeur à mettre à jour
    posval = 24;

    if (entetevar.typedon & cmoy_sep)
        posval += num_instant * 2;

    if (entetevar.typedon & mem_min_max)
        posval += num_instant * 12;
    else
        posval += num_instant * 4;

    // si coefficient coef_moy à mettre à jour
    if (majcmoy && (entetevar.typedon & cmoy_sep))
    {
        // se positionner dans le fichier sur la zone à modifier
        fseek (descfic, posval - 2, SEEK_SET);

        // récupérer et mettre à jour le coefficient coef_moy
        fread (&coef_moy, 2, 1, descfic);

        if (entetevar.ponderation)
            coef_moy += 256;
        else
            coef_moy += 1;
    }
    // sinon, se positionner sur la valeur de la variable
    else
        fseek (descfic, posval, SEEK_SET);

    // récupération de la valeur de la variable
    fread (&valeur, 4, 1, descfic);

    // si valeurs min et max à mettre à jour
    if (entetevar.typedon & mem_min_max)
    {
        // récupérer les valeurs mémorisées dans le fichier
        fread (&min, 4, 1, descfic);
        fread (&max, 4, 1, descfic);

        // les actualiser si nécessaire
        if (val < min)
        {
            min = val;
            maj_minmax = OUI;
        }

        // pas de else if, la première fois, il faut actualiser min et max
        if (val > max)
        {
            max = val;
            maj_minmax = OUI;
        }
    }

    // si calcul de pondération
    if (entetevar.ponderation)
    {
        // calculer la valeur à ajouter
        ajout = val * 256;

        // si débordement numérique lors de ce calcul
        if (ajout / 256 != val)
        {
            // "Valeur à rajouter trop grande"
            // "Il faudrait utiliser une variable en virgule flottante"
            affiche_err ("VAL_TROP_GRANDE");
            affiche_err ("BESOIN_VARFLOAT");

            // on ajoutera une valeur plus petite
            if (val > 0)
                ajout = max_int32;
            else
                ajout = min_int32;
        }
    }
    // sinon, simple ajout de la valeur passée en paramètre
    else
        ajout = val;

    // calculer la nouvelle valeur
    nouv_val = valeur + ajout;

    // si variable et valeur à rajouter du même signe
    if ((float) valeur * (float) ajout > 0)
    {
        // et que le signe a changé lors de l'addition
        if ((float) nouv_val * (float) ajout <= 0)
        {
            // nouvelle valeur de la variable
            if (valeur < 0)
                nouv_val = min_int32;
            else
                nouv_val = max_int32;

            // si on était déjà sur la valeur limite
            if (valeur == nouv_val)
            {
                // message d'erreur adapté
                // "Rappel : Débordement numérique pour la variable"
                affiche_err ("PAR_DEBORD_NUM");

                // pas la peine de mettre à jour le fichier pour la variable
                majfich = NON;
            }
            // sinon
            else
            {
                // message d'erreur
                // "Débordement numérique pour la variable"
                affiche_err ("DEBORD_NUMERIQUE");
            }

            // fin du message d'erreur
            // "Il faudrait la convertir en nombre flottant"
            affiche_err ("BESOIN_CONVFLOAT");
        }
    }

    // prise en compte de la nouvelle valeur
    valeur = nouv_val;

    // si coefficient coef_moy à mettre à jour
    if (majcmoy && (entetevar.typedon & cmoy_sep))
    {
        // se positionner dans le fichier sur la zone à modifier
        fseek (descfic, posval - 2, SEEK_SET);

        // mettre à jour le coefficient coef_moy
        fwrite (&coef_moy, 2, 1, descfic);
    }
    // sinon
    else if (majfich | maj_minmax)
    {
        // se positionner dans le fichier sur la zone à modifier
        fseek (descfic, posval, SEEK_SET);
    }

    // mettre à jour si nécessaire la valeur de la variable
    if (majfich | maj_minmax)
        fwrite (&valeur, 4, 1, descfic);

    // mettre à jour si nécessaire la valeur min ou max
    if (maj_minmax)
    {
        fwrite (&min, 4, 1, descfic);
        fwrite (&max, 4, 1, descfic);
    }
}



// rajoute la valeur val (flottante) à l'instant num_instant du cycle

void addvalfloat (float val, uint16 num_instant, int majcmoy)
{
    long   posval;     // position de la valeur à mettre à jour dans le fichier
    uint16 coef_moy; // coefficient coef_moy pour la valeur à mettre à jour
    float  valeur;     // valeur à mettre à jour
    float  min, max;   // valeurs min et max mémorisées
    int    maj_minmax; // indique si on devra mettre à jour la valeur min ou max


    // initialisation
    maj_minmax = NON;

    // pour le calcul de position, le premier instant
    // du cycle n'entrainera pas de décalage
    num_instant --;

    // calcul de la position dans le fichier de la valeur à mettre à jour
    posval = 24;

    if (entetevar.typedon & cmoy_sep)
        posval += num_instant * 2;

    if (entetevar.typedon & mem_min_max)
        posval += num_instant * 12;
    else
        posval += num_instant * 4;

    // si coefficient coef_moy à mettre à jour
    if (majcmoy && (entetevar.typedon & cmoy_sep))
    {
        // se positionner dans le fichier sur la zone à modifier
        fseek (descfic, posval - 2, SEEK_SET);

        // récupérer et mettre à jour le coefficient coef_moy
        fread (&coef_moy, 2, 1, descfic);

        if (entetevar.ponderation)
            coef_moy += 256;
        else
            coef_moy += 1;
    }
    // sinon, se positionner sur la valeur de la variable
    else
        fseek (descfic, posval, SEEK_SET);

    // récupération de la valeur de la variable
    fread (&valeur, 4, 1, descfic);

    // calculer sa nouvelle valeur (pas de test de débordement ici)
    valeur += val;

    // si valeurs min et max à mettre à jour
    if (entetevar.typedon & mem_min_max)
    {
        // récupérer les valeurs mémorisées dans le fichier
        fread (&min, 4, 1, descfic);
        fread (&max, 4, 1, descfic);

        // les actualiser si nécessaire
        if (val < min)
        {
            min = val;
            maj_minmax = OUI;
        }

        // pas de else if, la première fois, il faut actualiser min et max
        if (val > max)
        {
            max = val;
            maj_minmax = OUI;
        }
    }

    // si coefficient coef_moy à mettre à jour
    if (majcmoy && (entetevar.typedon & cmoy_sep))
    {
        // se positionner dans le fichier sur la zone à modifier
        fseek (descfic, posval - 2, SEEK_SET);

        // mettre à jour le coefficient coef_moy
        fwrite (&coef_moy, 2, 1, descfic);
    }
    // sinon
    else
    {
        // se positionner dans le fichier sur la zone à modifier
        fseek (descfic, posval, SEEK_SET);
    }

    // mettre à jour la valeur de la variable
    fwrite (&valeur, 4, 1, descfic);

    // mettre à jour si nécessaire la valeur min ou max
    if (maj_minmax)
    {
        fwrite (&min, 4, 1, descfic);
        fwrite (&max, 4, 1, descfic);
    }
}