#include "tb_mesures.h"
#include "shipheart.h"

//#include <QDateTime>
#include <QElapsedTimer>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
#include <QXYSeries>


//============================================================================
// Prototypes :
//============================================================================
qreal conversion_unite(qreal val, quint8 type_unite, quint32 unite);


//============================================================================
// Création du modèle
//============================================================================
TbMesuresModel::TbMesuresModel() :
    QObject() {

}



//================================================================================================================================
// Fonction :   Fonction principale de mise à jour des data à partir des données de la BDD. Les données proprement dites sont
//              stockées dans le vecteur 'points'. Et les valeurs de min et max en x et y sont également calculées.
// Reçoit   :   Le pointeur sur la série (donc la courbe),
//              le début de la requête à exécuter, en UTC (jusqu'aux filtre sur les Dates),
//              la fin de la requete à exécuter, en UTC (après le filtre sur les Dates). En général, comprend les GROUP BY et les SORT,
//              la date de début de la courbe,
//              la date de fin de la courbe,
//              le coefficient valeur/unité du capteur en cours (pour pouvoir calculer les valeurs réelles de Y auxquelles on ne touchera ensuite plus jusqu'à l'affichage de la courbe),
//              le fuseau horaire afin de retourner les points avec des DateTime dans le bon fuseau (NB : l'heure d'été peut être ajoutée dedans lors de l'appel).
// Retour   :   Une liste de chaine contenant le nombre de lignes retournées, les points m_xMin(x, y), m_xMax(x, y), m_yMin(x, y),
//              m_yMax(x, y) et la valeur m_yAvg.
//================================================================================================================================
QStringList TbMesuresModel::maj_data(QAbstractSeries *serie, QString table, quint32 id, quint32 id_variable, QDateTime date_debut, QDateTime date_fin, quint32 regroupement_donnees, qreal coeff_valeur_unite, qreal fuseau, quint8 type_unite, quint32 unite)
{
QStringList retour;
QVector<QPointF> temp_points;                   // Vecteur temporaire
QPointF m_xMin;
QPointF m_xMax;
QPointF m_yMin;
QPointF m_yMax;
qreal xVal, yVal;
qreal xVal_prec, yVal_prec;
qreal xCumul, yCumul;
qreal moyenne;
quint32 diviseur;
QString requete;
QSqlQuery query;
QSqlRecord resultat;
qint32 nb_lignes;
quint32 i;
bool flag_premier_point_ok;

QElapsedTimer myTimer;
myTimer.start();


    //-----------------------------------------------
    // Init des valeurs par défaut :
    nb_lignes = 0;
    temp_points.reserve(1500);                          // A priori, on réserve la mémoire pour 1500 lignes (soit un peu plus que 24h x 60 min.). Mais la taille sera agrandie en dynamique si besoin...
    m_xMin.ry() = std::numeric_limits<qreal>::max();
    m_xMax.ry() = std::numeric_limits<qreal>::min();
    m_yMin.ry() = std::numeric_limits<qreal>::max();
    m_yMax.ry() = std::numeric_limits<qreal>::min();
    if (coeff_valeur_unite == 0) diviseur = 1;
    else diviseur = coeff_valeur_unite;
    xCumul = 0;
    yCumul = 0;

    query.setForwardOnly(true);

    //-----------------------------------------------
    // Recherche du point de départ :
    requete = " SELECT Date, Valeur FROM " + table + " WHERE ID='" + QString::number(id) + "' AND ID_Variable='" + QString::number(id_variable) + "' ";
    requete += " AND Date<'" + date_debut.toString(FORMAT_DATE_HEURE_BDD) + "' ";
//    requete += " AND Valeur IS NOT NULL ";
    requete += " ORDER BY Date DESC LIMIT 1 ";
//qDebug() << "Requête de recherche du premier point : " << requete;

    yVal = 0;
    flag_premier_point_ok = false;
    if (query.exec(requete) == true)        // Exécution sans erreur de la requête :
    {
        if (query.next())                       // Cas normal où on a trouvé un point précédent l'intervalle :
        {
            yVal = conversion_unite(query.record().value(1).toReal() / diviseur, type_unite, unite);
            flag_premier_point_ok = true;

        }
        else qDebug() << "La requête de recherche du premier point n'a trouvé aucune valeur précédent le début de la courbe. La valeur Y initiale est fixée à " + QString::number(yVal);
    }
    else qDebug() << "/!\\ Erreur d'exécution de la requête de recherche du premier point. La valeur Y initiale est fixée à " + QString::number(yVal);

    xVal = date_debut.toMSecsSinceEpoch()  + (fuseau * 60 * 60 * 1000); // On ajuste immédiatement les valeurs de date au fuseau en cours
    m_xMin = QPointF(xVal, yVal);                                       // Mémorisation du point de début de l'axe X

    if (flag_premier_point_ok)
    {
        temp_points << QPointF(xVal, yVal);                                 // Ajout à la courbe, comme premier point...

        if (yVal < m_yMin.ry()) m_yMin = QPointF(xVal, yVal);               // Mise à jour du mini
        if (yVal > m_yMax.ry()) m_yMax = QPointF(xVal, yVal);               // Mise à jour du maxi

        nb_lignes ++;
    }

    //-------------------------------------------------------------------
    // Génération de la requête principale de récupération des data de la courbe :
    if (regroupement_donnees <= 1)
    {
        requete = " SELECT Date, Valeur FROM " + table + " WHERE ID='" + QString::number(id) + "' AND ID_Variable='" + QString::number(id_variable) + "' ";
        requete += " AND Date>'" + date_debut.toString(FORMAT_DATE_HEURE_BDD) + "' AND Date<='" + date_fin.toString(FORMAT_DATE_HEURE_BDD) + "' ";
        requete += " AND Valeur IS NOT NULL ";
        requete += " ORDER BY Date ";
    }
    else
    {
        requete = " SELECT Date, AVG(Valeur) FROM " + table + " WHERE ID='" + QString::number(id) + "' AND ID_Variable='" + QString::number(id_variable) + "' ";
        requete += " AND Date>'" + date_debut.toString(FORMAT_DATE_HEURE_BDD) + "' AND Date<='" + date_fin.toString(FORMAT_DATE_HEURE_BDD) + "' ";
        requete += " AND Valeur IS NOT NULL ";
        requete += " GROUP BY (UNIX_TIMESTAMP(Date) DIV " + QString::number(regroupement_donnees) + ") ORDER BY Date ";
    }
//qDebug() << "Requête générée : " << requete;


    //-----------------------------------------------
    // Récupération des données dans la BDD :
    if (query.exec(requete) == true)                // TRUE = pas d'erreur
    {
        nb_lignes += query.size();

//qDebug() << "Nombre d'enregistrements récupérés par la requête : " << nb_lignes << " et temps écoulé : " << myTimer.elapsed() << "ms";

        //-----------------------------------------------
        // Traitement des tous les enregistrements récupérés :
        for (i = 0; i < (quint32)nb_lignes; i ++)
        {
            if (!query.next()) break;                                               // Ce break ne devrait pas arriver puisqu'on a compté la taille du query avant de lancer la boucle...

            resultat = query.record();

            // Mise à jour des valeurs précédentes avant de calculer les nouvelles :
            xVal_prec = xVal;
            yVal_prec = yVal;

            // Mise à jour des nouvelles valeurs :
            xVal = resultat.value(0).toDateTime().toMSecsSinceEpoch() + (fuseau * 60 * 60 * 1000);      // On ajuste immédiatement les valeurs de date au fuseau en cours
            yVal = resultat.value(1).toReal() / diviseur;

            // Ajustement des valeurs en fonction de la conversion d'unité concernée :
            yVal = conversion_unite(yVal, type_unite, unite);

//qDebug() << "Nouveau point " << i << " : Date = " << resultat.value(0).toDateTime() << " / " << xVal << " et y = " << yVal;

            // Calcul des cumuls :
            if ((flag_premier_point_ok) || (i > 0))           // Si le premier point n'a pas été trouvé, on ne prends pas en compte l'intervalle entre le départ et le premier point dans la fenêtre
            {
                yCumul += (xVal - xVal_prec) * (yVal_prec);
                xCumul += (xVal - xVal_prec);
            }

            // Calcul des points yMin et yMax :
            if (yVal < m_yMin.ry()) m_yMin = QPointF(xVal, yVal);
            if (yVal > m_yMax.ry()) m_yMax = QPointF(xVal, yVal);

            // Ajout des points dans la série temporaire :
            temp_points << QPointF(xVal, yVal_prec);        // Ajout d'un point avec la valeur courante de X mais avec le Y du point précédent (permet de "maintenir" la valeur précédente jusqu'au point en cours).
            temp_points << QPointF(xVal, yVal);             // Point en cours
        }

//qDebug() << "Fin de boucle et temps écoulé : " << myTimer.elapsed() << "ms";

        //-----------------------------------------------
        // Calcul du point de fin :
        if (nb_lignes > 0)                  // Si aucun point n'a été trouvé, il ne faut pas ajouter le dernier point (qui n'aura aucune signification)
        {
            xVal_prec = xVal;
            yVal_prec = yVal;
            xVal = date_fin.toMSecsSinceEpoch()  + (fuseau * 60 * 60 * 1000);   // On ajuste immédiatement les valeurs de date au fuseau en cours
            yCumul += (xVal - xVal_prec) * (yVal_prec);                         // Dernier calcul de cumul
            xCumul += (xVal - xVal_prec);                                       // Dernier calcul de cumul
            m_xMax = QPointF(xVal, yVal);                                       // Mémorisation du point de fin de l'axe X. NB : la valeur de yVal est volontairement laissée inchangée (valeur Y du dernier point). Au passage, on évite les ajustements d'unités...
            temp_points << QPointF(xVal, yVal);                                 // Ajout à la courbe, comme dernier point...
            nb_lignes ++;
        }

        //-----------------------------------------------
        // Mise à jour des valeurs de la courbe :
        if (serie)
        {
            QXYSeries *xySeries = static_cast<QXYSeries *>(serie);
            xySeries->replace(temp_points);                                         // Mise à jour des valeurs de la courbe avec le vecteur temporaire (Use replace instead of clear + append, it's optimized for performance)
        }
        else qDebug() << "Valeur de 'series' non valide !";

//qDebug() << "yMin : " << m_yMin.ry() << " et yMax : " << m_yMax.ry();

        //-----------------------------------------------
        // Préparation des valeurs de retour :
        // NB : le 'f' est nécessaire pour les dates ('-----rx()') au moins ; sinon la précision n'est pas suffisante !!
        if (xCumul != 0) moyenne = yCumul / xCumul;
        else moyenne = 0;

        retour << QString::number(nb_lignes);
        retour = retour << QString::number(m_xMin.rx(), 'f') << QString::number(m_xMin.ry(), 'f') << QString::number(m_xMax.rx(), 'f') << QString::number(m_xMax.ry(), 'f');
        retour = retour << QString::number(m_yMin.rx(), 'f') << QString::number(m_yMin.ry(), 'f') << QString::number(m_yMax.rx(), 'f') << QString::number(m_yMax.ry(), 'f');
        retour = retour << QString::number(moyenne, 'f');

//qDebug() << "FIN de maj_data : " << nb_lignes << " enregistrements en " << myTimer.elapsed() << "ms\n";

    }
    else            // Si une erreur lors de la requête a été détectée :
    {
        qDebug() << "/!\\ Erreur à l'exécution de la requête principale de 'maj_data()' ";
        retour << QString::number(0);
    }

//qDebug() << "FIN de maj_data";
    return(retour);
}


//================================================================================================================================
// Fonction :   Calcule une valeur dans l'unité demandée.
// Reçoit   :   La valeur à convertir (dans l'une des unités de base : °C ou litres. Les autres sont laissées inchangées
// Retour   :   la valeur converte
//================================================================================================================================
qreal conversion_unite(qreal val, quint8 type_unite, quint32 unite)
{
qreal retour = val;

    switch(type_unite)
    {
        //-----------------------
        case UNITE_VOLUME :
        {
        switch(unite)
            {
                case UNITE_VOLUME_LITRES:           { break;  }                                         // Aucune conversion, on reste en litres
                case UNITE_VOLUME_US_GALLONS:       { retour = val / 3.785411784;   break;  }           // Conversion litres => US Ga
                case UNITE_VOLUME_IMP_GALLONS:      { retour = val / 4.54609;       break;  }           // Conversion litres => Imp. Ga
                default :                           { break; }                                          // Aucune conversion, on reste en ... ce qu'on est.
            }
            break;
        }

        //-----------------------
        case UNITE_TEMPERATURE :
        {
        switch(unite)
            {
                case UNITE_TEMPERATURE_CELSIUS:     { break;  }                                         // Aucune conversion, on reste en °C
                case UNITE_TEMPERATURE_FAHRENHEIT:  { retour = (1.8 * val) + 32;    break;  }           // Conversion °C => °F
                default :                           { break; }                                          // Aucune conversion, on reste en ... ce qu'on est.
            }
            break;
        }

        //-----------------------
        default: break;
    }

    return (retour);
}


//================================================================================================================================
// Fonction :   Fonction de copie des points d'une série (par exemple, une LineSerie d'un graphique) dans ceux d'une autre série.
// Reçoit   :   Le pointeur sur la série source et un sur la série destinataire.
// Retour   :   Rien
//================================================================================================================================
void TbMesuresModel::copie_series(QAbstractSeries *serie_source, QAbstractSeries *serie_destination)
{
QVector<QPointF> tempo;
qint64 i;
qint64 nb;
qint64 taille_destination;

//QElapsedTimer myTimer;
//myTimer.start();

    if ((serie_source) && (serie_destination))
    {
        QXYSeries *xySerie_source = static_cast<QXYSeries *>(serie_source);
        QXYSeries *xySerie_destination = static_cast<QXYSeries *>(serie_destination);

        // Copie des points de la source vers destination :
        nb = xySerie_source->count();                                                           // Nombre de points à transférer
        for (i = 0; i < nb; i ++)                                                               // Boucle sur tous les points de la source
        {
            tempo << xySerie_source->at(i);                                                      // Copie de chaque point dans une structure temporaire
        }

        xySerie_destination->replace(tempo);                                                     // Copie de la structure temporaire dans la série destination

        // Suppression des éventuels points de la destination si la source est plus petite :
        taille_destination = xySerie_destination->count();
        if (taille_destination > nb) xySerie_destination->removePoints(nb, (taille_destination - nb));

//qDebug() << "Copie Série faite. Nb d'enregistrements copiés = " << nb << "Temps d'exécution = " << myTimer.elapsed() << "ms\n";
    }
    else qDebug() << "Copie série impossible car au moins un pointeur (source et/ou destination) est NULL\n";
}

