/* ****************************************************
   Modul :processing

   In diesem Modul befinden sich Routinen, die mit der Handhabung,
   Eintragung und Berechnung von Daten beschäftigt sind, sowie
   der Ansteuerung von Hardware Modulen

   Autor : Christian Julius (12.2015)
   ************************************************ */


/* -------------- Standard Include Files ----------------- */
#include <stdint.h>
#include <stdbool.h>

/* --------------- Special Include Files / Librarys ------ */
#include "tm_stm32f4_adc.h"
#include "tm_stm32f4_hd44780.h"

/* -------------- Projekt   Include Files ---------------- */
#include "processing.h"
#include "config.h"
#include "tools.h"
#include "rtc_user.h"
#include "eeprom.h"

/* -------------- Typen Deklarationen--------------------- */
/* -------------- Defintionen ---------------------------- */

#define E2P_HISTORY_ENTRIES      0x03FE               // u16: Anzahl Einträge in History
#define E2P_HISTORY_DATA_START   0x0400               // Start der fortlaufenden Daten
#define E2P_HISTORY_DATA_END     0xFF00               // Ende der fortlaufenden Daten

/* -------------- Privat globale Variablen --------------- */

/* -------------- Privat Funktionen Prototypen ----------- */
static float proc_CalcRegr(const size_t nrDataPoints, struct data_t const data[]);
uint8_t proc_CalcZambretti (const Work_t *W);

////////////////////////////////////////////////////////
// Friert die letzten Daten der Historie ein und zaehlt
// Counter höher
////////////////////////////////////////////////////////
void proc_FreezeHistory(Work_t *W)
{
    // ptr hochzaehlen und begrenzen
    if (W->ptr < HISTORY_MAX_IDX )
        W->ptr++;
    else {
        // Werte nach links durchschieben
        for (uint16_t i = 1;i <= HISTORY_MAX_IDX;i++) {
            W->History[i-1] = W->History[i];
            // Heraus geschobene Werte im EEPROM als Historie speichern
            // TODO .......

        }
    }
    // Letzten Wert eintragen
    W->History[W->ptr].Pressure    = W->P_Now;
    W->History[W->ptr].Temperature = W->T_Now;
    W->History[W->ptr].unix_stamp  = Time.unix;

    /* stetige Anpassung der absolut gemessenen Druckgrenzen an die dyn. Grenzen
       in der Historie. Damit ziehen sich die absoluten Grenzen bis zu einem
       definierten Abstand an die dynamischen schrittweise heran  */

    if ((W->P_MaxInHistory + P_DELTA) < W->P_MaxGraph)
        W->P_MaxGraph--;

    if ((W->P_MinInHistory - P_DELTA) < W->P_MinGraph)
        W->P_MinGraph++;

    /* Das gleiche auch für die Temperaturgrenzen des Bargraphen
       aber hier geschieht dieses nach einer gewissen Zeitdauer */

    if (W->T_MaxInHistory < W->T_MaxGraph)
        W->T_MaxGraph--;
    if (W->T_MinInHistory > W->T_MinGraph)
        W->T_MinGraph++;


}

////////////////////////////////////////////////////////
// Traegt neue Daten in Work an aktueller Stelle ein
// Berechnet Mittelwerte und Maxima
////////////////////////////////////////////////////////

void proc_UpdateHistory(Work_t *W) {

    float   temp_sum, pressure_sum;

    PWR_BackupAccessCmd(ENABLE);

    // Feld als mit Werten beschrieben markieren
    W->id = 0x1234;     // Feld als beschrieben markieren

    /* BMP180 Sensor auslesen */
    W->T_Now = BMP180_ReadTemperature();
    W->P_Now = BMP180_ReadPressure();

     // Daten in Historie eintragen
     W->History[W->ptr].Pressure    = W->P_Now;
     W->History[W->ptr].Temperature = W->T_Now;

    // Dynamische Bereichsgrenzen prüfen und ggf. neu setzen
    if ((uint32_t)W->P_Now < W->P_MinGraph)
         W->P_MinGraph -= P_DELTA;
    if ((uint32_t)W->P_Now > W->P_MaxGraph)
         W->P_MaxGraph += P_DELTA;

    if (W->T_Now > W->T_MaxGraph)
        W->T_MaxGraph = W->T_Now;
    if (W->T_Now < W->T_MinGraph)
        W->T_MinGraph = W->T_Now;

    /* Mittelwerte berechnen. ptr zeigt auf nächstes freies Feld */
    temp_sum                  = 0.0;
    pressure_sum              = 0.0;
    W->P_MaxInHistory        = 0;
    W->T_MaxInHistory        = 0;
    W->P_MinInHistory        = 9999;
    W->T_MinInHistory        = 9999;

    for (uint16_t i = 0; i <= W->ptr;i++) {
        // Maxima und Minima setzen
        if (W->History[i].Pressure > W->P_MaxInHistory) {
            W->P_MaxInHistory = W->History[i].Pressure;
        }

        if (W->History[i].Pressure < W->P_MinInHistory)
            W->P_MinInHistory = W->History[i].Pressure;

        if (W->History[i].Temperature > W->T_MaxInHistory)
            W->T_MaxInHistory = W->History[i].Temperature;

        if (W->History[i].Temperature < W->T_MinInHistory)
            W->T_MinInHistory = W->History[i].Temperature;

        temp_sum      += W->History[i].Temperature;
        pressure_sum  += W->History[i].Pressure;
    }

    // Werte eintragen in Arbeitsstruct

    // Sonderfall, nur ein Wert
    if (W->ptr == 0) {
        W->T_AvgInHistory = W->T_Now;
        W->P_AvgInHistory = W->P_Now;
    } else {
        W->T_AvgInHistory = temp_sum / (W->ptr + 1);
        W->P_AvgInHistory = pressure_sum / (W->ptr + 1);
    }

    /* Die Zeichengrenzen müssen um die Historie liegen! */
    if (W->P_MaxInHistory >= W->P_MaxGraph)
        W->P_MaxGraph = W->P_MaxInHistory + P_DELTA;

    if (W->P_MinInHistory <= W->P_MinGraph)
        W->P_MinGraph = W->P_MinInHistory - P_DELTA;

    /* ---- Zustand: Hoch/Tief setzen ------    */
    if (W->P_Now > (PRESSURE_NORMAL + PRES_TOLERANZ))
        W->gebiet = Hoch;
    else
        if (W->P_Now < (PRESSURE_NORMAL - PRES_TOLERANZ))
        W->gebiet = Tief;
     else
       W->gebiet = Neutral;

    /* Ausgleichsgerade berechnen */
    W->RegrSteigung = proc_CalcRegr(W->ptr,W->History);

    /* Zambretti Buchstabe berechnen und eintragen */
    W->ZFN = proc_CalcZambretti(W);

    // Update wurde durchgeführt
    flags.gfx_need_update = false;

}

///////////////////////////////////////////////////
//      Berechnet eine Ausgleichsgerade
///////////////////////////////////////////////////

static float proc_CalcRegr(const size_t nrDataPoints, struct data_t const data[])
{
      #define XQ          (RASTER / 2.0)   // Mittelwert [xq] des Zeitrahmens ist fix

      double    Xi_Xq     = 0.0;
      double    Yi_Yq     = 0.0;
      double    Xi_Xq2    = 0.0;
      double    XiXqYiYq  = 0.0;
      double    Yq        = 0.0;

      // Ausreichend Werte vorhanden?
      if (nrDataPoints < RASTER)  return 0.0;

     // Berechnung der Mittelwerte des Druckes
      for (uint16_t i = (nrDataPoints - RASTER); i < nrDataPoints;i++)
          Yq = Yq + data[i].Pressure;

      Yq /= RASTER;

      // Summe [Xi - Xq], [Yi - yq] und [Xi - Xq]2 berechnen
      for (uint16_t i = (nrDataPoints - RASTER); i < nrDataPoints;i++) {
         Xi_Xq2 += pow(i - XQ,2);
         Xi_Xq  += (i - XQ);
         Yi_Yq = data[i].Pressure - Yq;
         XiXqYiYq += (Xi_Xq * Yi_Yq);
      }

      return XiXqYiYq / Xi_Xq2;
}

/*
    A=1,B=2,C=3,D=4,E=5,F=6,G=7,H=8,I=9,J=10 K=11 L=12 M=13 N=14 O=15 P=16
    Q=17 R=18 S=19 T=20 U=21 V=22 W=23 X=24 Y=25 Z=26
*/

// Die erste Null ist nur fuer Z Offset, da Z ab 1 anfaengt
const char const falling[10] = {0,1,2,4,8,15,18,21,22,24};
const char const steady [11] = {0,1,2,5,11,14,16,19,23,24,26};
const char const rising [14] = {0,1,2,3,6,7,9,10,12,13,17,20,25,26};

/* Berechnung des Z Faktors nach Zembretti */
uint8_t proc_CalcZambretti (const Work_t *W)
{
    uint8_t Z,ZFN;
    float limit;

    // Jahreszeit berücksichtigen
    uint8_t fac = (Time.month >= 10 || Time.month <= 3) ? 1:0;

    if (W->RegrSteigung < -0.2) {
        // Druck fallend, Druck limitieren auf Tabelle
        limit = (W->P_Now > 1050) ? 1050 : W->P_Now;
        limit = (W->P_Now < 985)  ? 985  : W->P_Now;
        Z = round(-0.1268 * limit + 133.54);
        ZFN = falling[Z] + fac;
    }
    if (W->RegrSteigung > 0.2) {
        // Druck steigend
        limit = (W->P_Now > 1030) ? 1030 : W->P_Now;
        limit = (W->P_Now < 947)  ? 947  : W->P_Now;
        Z = round(-0.1559 * limit + 161.26);
        ZFN = rising[Z] + fac;
    }
    else {
        // Druck gleichbleibend
        limit = (W->P_Now > 1033) ? 1033 : W->P_Now;
        limit = (W->P_Now < 960)  ? 960  : W->P_Now;
        Z = round(-0.1343 * limit + 139.47);
        ZFN = steady[Z] + fac;
    }

    return ZFN;
}


/*  ////////////////////////////////////////////
    LED Anzeige berechnen und einstellen
*/  //////////////////////////////////////////////

void proc_HandleLEDs(Work_t *Druck)
{
    static _Bool lcd_switch = true;         // Umschalter LCD Display

    int8_t wertung = 0;

    // Helligkeit bestimmen, Nacht?
    uint16_t LDRValue = TM_ADC_Read(ADC1, ADC_Channel_1);
    if (LDRValue < 1400) flags.night = true;
    if (LDRValue > 2500) flags.night = false;

    // Bei Dunkelheit wird generell Display + LEDs abgeschaltet, egal wie DIP Schalter stehen
    if (flags.night) {
        // Grafik Display aus
        GPIO_ResetPin(GPIOD,RELAIS);
        // Alle LED aus
        GPIO_ResetPin(GPIOD,LED_ROT | LED_GRN | LED_GLB);
        GPIO_ResetPin(GPIOE,WLED_ALL);
        tls_ResetWetterLEDs();
        // LCD Backlight aus
        GPIO_ResetBits(GPIOC,LCD_BACKLIGHT);
        flags.backlight = false;
        return;
    } else {
        // Bei Tageslicht
        GPIO_SetBits(GPIOD,RELAIS);
    }


   /* --- Backlight vom LCD schalten --- */
   if ( !flags.backlight && (Time.hours >= SonnenZeiten.SAG_Std) && (Time.minutes >= SonnenZeiten.SAG_Min))
         flags.backlight = true;

   if ( flags.backlight && (Time.hours >= SonnenZeiten.SUG_Std) && (Time.minutes >= SonnenZeiten.SUG_Min))
         flags.backlight = false;

    if (flags.backlight && Get_DIP_Switch(1)) {
        TM_HD44780_DisplayOn();
        GPIO_SetBits(GPIOC,LCD_BACKLIGHT);
    }
    else {
        GPIO_ResetBits(GPIOC,LCD_BACKLIGHT);
        TM_HD44780_DisplayOff();
    }


    /* ----- Darstellungen im LCD Display ----- */
    flags.backlight = true;
    if (flags.backlight) {
        TM_HD44780_Clear();
        char buf[20];
        if (lcd_switch) {
           sprintf(buf,"P: %4.1f mBar\n\rT: %3.1f Celsius",Druck->P_Now,Druck->T_Now);
           TM_HD44780_Puts(0, 0, buf);
           lcd_switch = false;
        } else {
           sprintf(buf,"SA: %02u:%02u Uhr",SonnenZeiten.SAG_Std,SonnenZeiten.SAG_Min);
           TM_HD44780_Puts(0, 0, buf);
           sprintf(buf,"SU: %02u:%02u Uhr",SonnenZeiten.SUG_Std,SonnenZeiten.SUG_Min);
           TM_HD44780_Puts(0, 1, buf);
           lcd_switch = true;
        }
    }

    /* ------- Berechnungen der LED im Normalbetrieb ------ */

    /* --- Alle LEDs löschen  --- */
    GPIO_ResetPin(GPIOD,LED_ROT | LED_GRN | LED_GLB);    // Hoch / Tief Anzeige

    if (!flags.night) {
        switch (Druck->gebiet) {
        case Hoch    : GPIO_SetPin(GPIOD,LED_GRN);
                       break;
        case Neutral : GPIO_SetPin(GPIOD,LED_GLB);
                       break;
        case Tief    : GPIO_SetPin(GPIOD,LED_ROT);
                       break;
        }
    }

    /* --- Berechnung der Leuchtdioden Anzeige über den Trend --- */

    // Aktueller Druck über Mittelwert?
    if (Druck->P_Now > (Druck->P_AvgInHistory + 1.0))
        wertung++;
    if (Druck->P_Now < (Druck->P_AvgInHistory - 1.0))
        wertung--;

    // Steigung der Ausgleichsgeraden
    if (Druck->RegrSteigung > 0.3) {
        wertung++;
        if (Druck->RegrSteigung > 0.6) {
            wertung++;
            if (Druck->RegrSteigung > 0.8)
                wertung++;

         }
    } else
    if (Druck->RegrSteigung < -0.3) {
        wertung--;
        if (Druck->RegrSteigung < -0.6) {
            wertung--;
            if (Druck->RegrSteigung < -0.8)
                wertung--;

        }
    }

    /* -4 < Wertung < +4 */

    if (!flags.night) {
        tls_ResetWetterLEDs();                               // Wettertendenz

        switch (wertung) {
        case -4:    tls_SetWetterLED(LROT1,LED_BLINK);
                    break;
        case -3:    tls_SetWetterLED(LROT1,LED_ON);
                    break;
        case -2:    tls_SetWetterLED(LROT1,LED_ON);
                    tls_SetWetterLED(LROT2,LED_ON);
                    break;
        case -1:    tls_SetWetterLED(LROT2,LED_ON);
                    break;
        case  0:    tls_SetWetterLED(LGLB,LED_ON);
                    break;
        case  1:    tls_SetWetterLED(LGRN,LED_ON);
                    break;
        case  2:    tls_SetWetterLED(LGRN,LED_ON);
                    tls_SetWetterLED(LBLAU,LED_ON);
                    break;
        case  3:    tls_SetWetterLED(LBLAU,LED_ON);
                    break;
        case  4:    tls_SetWetterLED(LBLAU,LED_BLINK);
                    break;

        default:    tls_SetWetterLED(LGLB,LED_ON);
                    break;
        }
    }
}




/*  **************************************************************************

    Bestimmung, ob eine steigende oder fallende Tendenz des Luftdruckes
    vorliegt. Dazu werden folgende Faktoren ausgewertet:

    1. Absoluter Luftdruck
    2. Steigung des Druckes der letzten 12 Stunden > 0.3 mbar/h  > 1 mBar/h

    Orkantief   : 2 rote LED blinkend
    Tief        : 1 rote LED
    Neutral     : 1 gelbe LED
    Hoch:       : 1 grüne LED
    Extremhoch  : 1 blaue LED blinkend

***************************************************************************** */

/*
Zambretti Prinzip:

For a falling barometer Zf = -0.12P + 130,28........(1)    985 < P < 1050
For a steady barometer Zs =  -0.13P + 138,24........(2)    960 < P < 1033
For a rising barometer Zr =  -0.16P + 160,35........(3)    947 < P < 1030


Forecast number ;	Forecast Letter ;	Forecast Text ;	        Pressure
========================================================================
1;	                A;	Settled Fine;                           1050
2;	                B;	Fine Weather;	                        1040
3;	                D;	Fine Becoming Less Settled;	            1024
4;              	H;	Fairly Fine Showery Later;	            1018
5;	                O;	Showery Becoming more unsettled;        1010
6;              	R;	Unsettled, Rain later;	                1004
7;              	U;	Rain at times, worse later. ;	        998
8;              	V;	Rain at times, becoming very unsettled;	991
9;              	X;	Very Unsettled, Rain;	                985

10;	                A;	Settled Fine;	                        1033
11;	                B;	Fine Weather;	                        1023
12;	                E;	Fine, Possibly showers;	                1014
13;	                K;	Fairly Fine , Showers likely;	        1008
14;	                N;	Showery Bright Intervals;	            1000
15;             	P;	Changeable some rain;	                994
16;             	S;	Unsettled, rain at times;	            989
17;             	W;	Rain at Frequent Intervals;	            981
18;	                X;	Very Unsettled, Rain;	                974
19;             	Z;	Stormy, much rain;	                    960

20;             	A;	Settled Fine;	                        1030
21;             	B;	Fine Weather;	                        1022
22;	                C;	Becoming Fine;                      	1012
23;	                F;	Fairly Fine, Improving;	                1007
24;	                G;	Fairly Fine, Possibly showers, early;	1000
25;	                I;	Showery Early, Improving;	            995
26;	                J;	Changeable Mending;	                    990
27;	                L;	Rather Unsettled Clearing Later;	    984
28;	                M;	Unsettled, Probably Improving;	        978
29;	                Q;	Unsettled, short fine Intervals;	    970
30;	                T;	Very Unsettled, Finer at times;     	965
31;	                Y;	Stormy, possibly improving;	            959
32;	                Z;	Stormy, much rain;	                    947

and the revised equations, using integers to provide the equivalent of 3 decimal place precision, are:

For a falling barometer Z = 130-P/81....................................(4)
For a steady barometer Z = 147 -5P/376.................................(5)
For a rising barometer Z = 179-2P/129.....................................(6)

*/
