/*
 * Copyright 2015 Mario Richter, Peter Stephan
 * This file is part of NeMo.
 *
 * NeMo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NeMo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NeMo.  If not, see <http://www.gnu.org/licenses/>.
 */

/*!
 * \file http.c
 * \ingroup http
 * \brief Routinen für den Webserver.
 */


#include "main.h"

httpStruct httpTable[TCP_MAX_CONNECTIONS];


/*!
 * \ingroup http
 * \brief Handlerfunktion für den Webserver
 *
 * Diese Funktion ist die Handlerfunktion des Webservers. Sie übernimmt die Verbindungssteuerung,
 * kümmert sich um neu eingetroffene Daten und steuert den Status der Timerfunktion, welche dann
 * die Antwort an die Gegenstelle sendet.
 *
 * \param ind Index auf die Verbindung in der Verbindungsliste
 * \param flag Flags für die TCP-Verbindung
 *
 * \sa httpTimer
 */

void httpHandler(uint8_t ind, uint8_t flag)
{
  uint8_t i;

  switch(flag)
  {
    case TCP_HNDL_ESTABLISHED:

      // Neue Verbindung: Einträge der HTTP-Tabelle zurücksetzen
      httpTable[ind].authOk  = 0;
      httpTable[ind].cmdType = HTTP_CMD_NONE;
      httpTable[ind].status = HTTP_STAT_IDLE;
      httpTable[ind].httpPageIndex = 255;
      httpTable[ind].lineInd = 0;
      break;

    case TCP_HNDL_NEWDATA:

      // Empfangene Zeichen auswerten
      while(tcpConnList[ind].rxread != tcpConnList[ind].rxwrit)
      {
        // Neue Zeile zusammenbauen
        httpTable[ind].line[httpTable[ind].lineInd] = tcpConnList[ind].rxbuf[tcpConnList[ind].rxread];

        // Zeilenumbruch erkannt? Dann Zeile auswerten
        if(httpTable[ind].line[httpTable[ind].lineInd] == '\n')
        {
          // GET-Kommando ?
          if( (httpTable[ind].line[0] == 'G') &&
              (httpTable[ind].line[1] == 'E') &&
              (httpTable[ind].line[2] == 'T') &&
              (httpTable[ind].line[3] == ' '))
          {
            httpTable[ind].cmdType = HTTP_CMD_GET;

            // Dateinamen kopieren
            for(i = 0; (i < HTTP_FILE_LENGTH-1) && (httpTable[ind].line[i+4] != ' '); i++)
            {
              httpTable[ind].fileName[i] = httpTable[ind].line[i+4];
              httpTable[ind].fileName[i+1] = '\0';
            }

            usart_send_string("httpHandler: GET request: ");
            usart_send_line((char *)httpTable[ind].fileName);
          }

          // POST-Komamndo?
          if( (httpTable[ind].line[0] == 'P') &&
              (httpTable[ind].line[1] == 'O') &&
              (httpTable[ind].line[2] == 'S') &&
              (httpTable[ind].line[3] == 'T') &&
              (httpTable[ind].line[4] == ' '))
          {
            httpTable[ind].cmdType = HTTP_CMD_POST;

            // Dateinamen kopieren
            for(i = 0; (i < HTTP_FILE_LENGTH-1) && (httpTable[ind].line[i+5] != ' '); i++)
            {
              httpTable[ind].fileName[i] = httpTable[ind].line[i+5];
              httpTable[ind].fileName[i+1] = '\0';
            }

            usart_send_string("httpHandler: POST request: ");
            usart_send_line((char *)httpTable[ind].fileName);
          }

          // Ist Header zu Ende? (Zeile enthält nur Zeilenumbruch)
          if((httpTable[ind].lineInd < 2) && ((httpTable[ind].line[0] == '\r') || (httpTable[ind].line[0] == '\n')))
          {
            // Wenn die State Machine bereit ist, Dateiübertragung anstarten
            if(httpTable[ind].status == HTTP_STAT_IDLE)
              httpTable[ind].status = HTTP_STAT_INIT_TRANSFER;
          }

          httpTable[ind].lineInd = 0;
        }
        else
        {
          // Prüfe, ob ein Kommando übertragen wurde:
          // POST-Kommando, aktuelles Zeichen ist '*', Zeile beginnt mit "cmd=".
          if( (httpTable[ind].cmdType == HTTP_CMD_POST) &&
              (httpTable[ind].line[httpTable[ind].lineInd] == '*') &&
              (httpTable[ind].line[0] == 'c') &&
              (httpTable[ind].line[1] == 'm') &&
              (httpTable[ind].line[2] == 'd') &&
              (httpTable[ind].line[3] == '='))
          {
            // Zeiger auf Kommandostring an Befehlsroutine übergeben
            httpPostCommand(&httpTable[ind].line[4]);
          }

          if(httpTable[ind].lineInd < (HTTP_LINE_BUFLEN - 1))
            httpTable[ind].lineInd++;
        }

        // TCP-Lesebuffer weiterziehen
        tcpConnList[ind].rxread++;
        if(tcpConnList[ind].rxread >= TCP_RXTX_BUFLEN)
          tcpConnList[ind].rxread = 0;
      }
      break;
  }
}


/*!
 * \ingroup http
 * \brief Timerfunktion für den Webserver
 *
 * Diese Funktion wird zyklisch aufgerufen und kümmert sich um die Abarbeitung der anstehenden
 * Dateiübertragungen. Die Übertragungen werden aus dem \ref httpHandler angetriggert.
 *
 * \sa httpHandler
 */

void httpTimer(void)
{
  uint8_t i, j;
  uint16_t k;
  char * ptr;

  for(i = 0; i < TCP_MAX_CONNECTIONS; i++)
  {
    switch(httpTable[i].status)
    {
      case HTTP_STAT_IDLE:                    // IDLE: Es gibt nichts zu tun ...

        break;

      case HTTP_STAT_INIT_TRANSFER:           // INIT_TRANSFER: Beginn einer Datenübertragung

        j = 0;
        httpTable[i].httpPageIndex = 255;

        // Wurde kein Dateiname angegeben, dann Standardseite anzeigen
        if((httpTable[i].fileName[0] = '/') && (httpTable[i].fileName[1] == '\0'))
          httpTable[i].httpPageIndex = 0;

        // Ansonsten Dateiname in der Dateiliste suchen
        while((httpTable[i].httpPageIndex == 255) && (httpPages[j].fileName != NULL))
        {
          ptr = strstr((const char *)httpTable[i].fileName, (const char *)httpPages[j].fileName);
          if(ptr != NULL)
            httpTable[i].httpPageIndex = j;

          j++;
        }

        // Jetzt den zu übertragenden Header ermitteln
        if(httpTable[i].httpPageIndex == 255)
        {
          httpTable[i].httpPage = NULL;
          httpTable[i].httpHeader = httpNotFoundHeader;
        }
        else
        {
          httpTable[i].httpPage = httpPages[httpTable[i].httpPageIndex].httpPage;

          if(strstr((const char *)httpPages[httpTable[i].httpPageIndex].fileName, ".jpg"))
            httpTable[i].httpHeader = httpJpegHeader;
          else if(strstr((const char *)httpPages[httpTable[i].httpPageIndex].fileName, ".gif"))
            httpTable[i].httpHeader = httpGifHeader;
          else
            httpTable[i].httpHeader = httpHtmlHeader;
        }

        // Jetzt noch die Stringvariablen vorbereiten, damit diese bei der Übertragung aktuell sind
        httpInitStringVars();

        // Soweit, sogut - Im nächsten State kann mit der Übertragung des Headers begonnen werden.
        httpTable[i].readPos = 0;
        httpTable[i].status = HTTP_STAT_HEADER;
        break;

      case HTTP_STAT_HEADER:                  // HEADER: Header übertragen

        // Wenn der Sendebuffer bereit ist, neue Zeichen übertragen
        if(tcpConnList[i].txread == tcpConnList[i].txwrit)
        {
          k = 0;
          while(k < (TCP_RXTX_BUFLEN-2))
          {
            // Bei 3 aufeinanderfolgenden Hashes ist der Header fertig übertragen
            if( (pgm_read_byte(&httpTable[i].httpHeader[httpTable[i].readPos]) == '#') &&
                (pgm_read_byte(&httpTable[i].httpHeader[httpTable[i].readPos+1]) == '#') &&
                (pgm_read_byte(&httpTable[i].httpHeader[httpTable[i].readPos+2]) == '#'))
            {
              // while-Schleife abbrechen
              k = TCP_RXTX_BUFLEN;

              // Falls keine Seite vorhanden, fertig; ansonsten jetzt Seite übertragen
              if(httpTable[i].httpPageIndex == 255)
              {
                httpTable[i].status = HTTP_STAT_IDLE;

                // Verbindung zum schließen vormerken (Wichtig: Sonst stellt der Browser die Seite nicht dar??)
                tcpConnList[i].closeAfterSend = 1;
              }
              else
              {
                httpTable[i].status = HTTP_STAT_PAGE;
                httpTable[i].readPos = 0;
              }
            }
            else
            {
              k++;

              // Nächstes Zeichen übertragen
              tcpConnList[i].txbuf[tcpConnList[i].txwrit++] = pgm_read_byte(&httpTable[i].httpHeader[httpTable[i].readPos++]);

              // Index-Überlauf behandeln
              if(tcpConnList[i].txwrit >= TCP_RXTX_BUFLEN)
                tcpConnList[i].txwrit = 0;
            }
          }
        }
        break;

      case HTTP_STAT_PAGE:

        // Wenn der Sendebuffer bereit ist, neue Zeichen übertragen
        if(tcpConnList[i].txread == tcpConnList[i].txwrit)
        {
          k = 0;
          while(k < (TCP_RXTX_BUFLEN-2))
          {
            // Bei 3 aufeinanderfolgenden Hashes ist die Page fertig übertragen
            if( (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos]) == '#') &&
                (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos+1]) == '#') &&
                (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos+2]) == '#'))
            {
              // while-Schleife abbrechen
              k = TCP_RXTX_BUFLEN;

              // Übertragung ist beendet
              httpTable[i].status = HTTP_STAT_IDLE;

              // Verbindung zum schließen vormerken (Wichtig: Sonst stellt der Browser die Seite nicht dar??)
              tcpConnList[i].closeAfterSend = 1;
            }
            // Bei 3 aufeinanderfolgenden Klammeraffen kommt eine Stringvariable
            else if(  (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos]) == '@') &&
                (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos+1]) == '@') &&
                (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos+2]) == '@'))
            {
              // Hole die Nummer der zu übertragenden Stringvariable
              j =  (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos+3]) - '0') * 10;
              j += (pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos+4]) - '0');

              // Falsche Stringvariable? Dann gleich weiter!
              if((j == 0) || (j > HTTP_STRING_VARS))
              {
                // Stringvariablen im Bereich 61..76 geben die Namen der Lichtprogramme an
                if((j >= 61) && (j <= 76))
                {
                  // Ist das Programm vorhanden?
                  if(!IS_CHARACTER(lightPrograms[j-61][0]))
                  {
                    // Der String "kein Programm" hat 13 Zeichen, plus 2 Zeichen Reserve -> 15 Zeichen notwendig
                    if(((uint16_t)k + 15UL) < ((uint16_t)TCP_RXTX_BUFLEN))
                    {
                      // Angeben, dass kein Programm hinterlegt ist
                      sendStringP(i, PSTR("kein Programm"));

                      // Übertragene Zeichen mit in den aktuellen Durchlauf einrechnen
                      k += 13;

                      // Und jetzt noch die Leseposition um fünf weiterziehen, wie oben
                      httpTable[i].readPos += 5;
                    }
                    else
                    {
                      // Nicht mehr genug Platz für String: while-Schleife abbrechen, und bis zum
                      // nächsten Durchlauf, bis der Sendebuffer leer ist, warten.
                      k = TCP_RXTX_BUFLEN;
                    }
                  }
                  else
                  {
                    if(((uint16_t)strlen((char *)lightPrograms[j-61]) + (uint16_t)k + 2UL) < ((uint16_t)TCP_RXTX_BUFLEN))
                    {
                      // Falls ja, können wir den Programmnamen direkt übertragen
                      sendString(i, (char *)lightPrograms[j-61]);

                      // Übertragene Zeichen mit in den aktuellen Durchlauf einrechnen
                      k += strlen((char *)lightPrograms[j-61]);

                      // Und jetzt noch die Leseposition um fünf weiterziehen, wie oben
                      httpTable[i].readPos += 5;
                    }
                    else
                    {
                      // Nicht mehr genug Platz für String: while-Schleife abbrechen, und bis zum
                      // nächsten Durchlauf, bis der Sendebuffer leer ist, warten.
                      k = TCP_RXTX_BUFLEN;
                    }
                  }
                }
                // Stringvariablen im Bereich 81..96 schalten die Buttons aus, falls kein Programm hinterlegt ist
                else if((j >= 81) && (j <= 96))
                {
                  // Prüfen, ob kein gültiges Lichtprogramm hinterlegt ist, ob also der Button inaktiv werden muss
                  if(!IS_CHARACTER(lightPrograms[j-81][0]))
                  {
                    // Der String " disabled" hat 9 Zeichen, plus 2 Zeichen Reserve -> 11 Zeichen notwendig
                    if(((uint16_t)k + 11UL) < ((uint16_t)TCP_RXTX_BUFLEN))
                    {
                      // Ist noch Platz im Buffer, können wir das disabled-Schlüsselwort direkt übertragen
                      sendStringP(i, PSTR(" disabled"));

                      // Übertragene Zeichen mit in den aktuellen Durchlauf einrechnen
                      k += 9;

                      // Und jetzt noch die Leseposition um fünf weiterziehen, wie oben
                      httpTable[i].readPos += 5;
                    }
                    else
                    {
                      // Nicht mehr genug Platz für String: while-Schleife abbrechen, und bis zum
                      // nächsten Durchlauf, bis der Sendebuffer leer ist, warten.
                      k = TCP_RXTX_BUFLEN;
                    }
                  }
                  else
                  {
                    // Ist ein Programm vorhanden, brauchen wir nichts ausgeben -> Stringvariable überspringen
                    httpTable[i].readPos += 5;
                  }
                }
                // Stringvariable 99 enthält den Einsatzort
                else if(j == 99)
                {
                  // Prüfen, ob wir den Einsatzort noch im aktuellen Durchlauf unterbringen
                  if(((uint16_t)strlen(lightLocation) + (uint16_t)k + 2UL) < ((uint16_t)TCP_RXTX_BUFLEN))
                  {
                    // Falls ja, können wir den Einsatzort direkt übertragen
                    sendString(i, lightLocation);

                    // Übertragene Zeichen mit in den aktuellen Durchlauf einrechnen
                    k += strlen(lightLocation);

                    // Und jetzt noch die Leseposition um fünf weiterziehen, wie oben
                    httpTable[i].readPos += 5;
                  }
                  else
                  {
                    // Nicht mehr genug Platz für String: while-Schleife abbrechen, und bis zum
                    // nächsten Durchlauf, bis der Sendebuffer leer ist, warten.
                    k = TCP_RXTX_BUFLEN;
                  }
                }
                else
                {
                  // Unbekannte Stringvariable -> ignorieren
                  httpTable[i].readPos += 5;
                }
              }
              else
              {
                // Prüfe, ob der Sendebuffer im aktuellen Durchlauf noch Platz für die Stringvariable hat
                if(((uint16_t)httpString[j-1].len + (uint16_t)k + 2UL) < ((uint16_t)TCP_RXTX_BUFLEN))
                {
                  // Falls ja, können wir die Stringvariable direkt übertragen
                  sendString(i, (char *)httpString[j-1].str);

                  // Übertragene Zeichen mit in den aktuellen Durchlauf einrechnen
                  k += httpString[j-1].len;

                  // Und jetzt noch die Leseposition um fünf weiterziehen, wie oben
                  httpTable[i].readPos += 5;
                }
                else
                {
                  // Nicht mehr genug Platz für String: while-Schleife abbrechen, und bis zum
                  // nächsten Durchlauf, bis der Sendebuffer leer ist, warten.
                  k = TCP_RXTX_BUFLEN;
                }
              }
            }
            else
            {
              k++;

              // Nächstes Zeichen übertragen
              tcpConnList[i].txbuf[tcpConnList[i].txwrit++] = pgm_read_byte(&httpTable[i].httpPage[httpTable[i].readPos++]);

              // Index-Überlauf behandeln
              if(tcpConnList[i].txwrit >= TCP_RXTX_BUFLEN)
                tcpConnList[i].txwrit = 0;
            }
          }
        }
        break;

      default:
        httpTable[i].status = HTTP_STAT_IDLE;
    }
  }
}


/*!
 * \ingroup http
 * \brief Stringvariablen aktualisieren
 *
 * Diese Funktion wird vor der Übertragung einer HTML-Seite aufgerufen und dient zur Initialisierung
 * der Stringvariablen, die in der Seite enthalten sein können. Damit wird sichergestellt, dass beim
 * Übertragen der Seite aktuelle Daten verwendet werden.
 *
 * Die Anzahl und der Inhalt der Stringvariablen muss vom Benutzer festgelegt werden, genauso wie die
 * Größe des Arrays httpString, das die Strings enthält.
 *
 * \sa httpHandler
 */

void httpInitStringVars(void)
{
  // 01: Softwareversion
  httpString[0].len = sprintf((char *)httpString[0].str, "%s", SW_VERSION);

  // 02: Status des Pause-Buttons ("" wenn aktiv oder pausiert, " disabled" wenn inaktiv)
  if(lightMode == LIGHT_MODE_OFF)
    httpString[1].len = sprintf((char *)httpString[2].str, " disabled");
  else
  {
    httpString[1].len = 0;
    httpString[1].str[0] = 0;
  }

  // 03: Beschriftung des Pause-Buttons ("Pause" oder "Fortsetzen")
  if(lightMode == LIGHT_MODE_PAUSE)
    httpString[2].len = sprintf((char *)httpString[2].str, "Fortsetzen");
  else
    httpString[2].len = sprintf((char *)httpString[2].str, "Pause");

  // 04: Temperatur
  httpString[3].len = sprintf((char *)httpString[3].str, "%s C", NTCtemp);

  // 05: Autostart deaktivieren
  if((lightAutoStart & 0xF0) == EEP_AUTOSTART_ON)
    httpString[4].len = sprintf((char *)httpString[4].str, "ausschalten");
  else
    httpString[4].len = sprintf((char *)httpString[4].str, "aktivieren");

  // 06: Betriebsspannung
  httpString[5].len = sprintf((char *)httpString[5].str, "%s V", uLed);
}


/*!
 * \ingroup http
 * \brief POST-Kommando ausführen
 *
 * Diese Funktion wertet einen per POST-Aktion erhaltenen Befehl aus und führt diesen aus.
 * Der Befehl wird vom Webserver erhalten und hat die Form "xxx*", wobei xxx der
 * auszuführende Befehl ist (mit "*" abgeschlossen).
 *
 * \sa httpHandler
 */

void httpPostCommand(volatile uint8_t * cmd)
{
  uint8_t progNr;

  // Autostart toggeln
  if((cmd[0] == 'a') && (cmd[1] == 't') && (cmd[2] == 'o'))
  {
    if((lightAutoStart & 0xF0) == EEP_AUTOSTART_OFF)
    {
      // Lichtprogrammnr. erhalten, nur oberes Nibble auf ON setzen
      lightAutoStart &= ~(0xF0);
      lightAutoStart |= EEP_AUTOSTART_ON;
      eeprom_write_byte(&eepLightAutoStart, lightAutoStart);
    }
    else
    {
      // Lichtprogrammnr. erhalten, nur oberes Nibble auf OFF setzen
      lightAutoStart &= ~(0xF0);
      lightAutoStart |= EEP_AUTOSTART_OFF;
      eeprom_write_byte(&eepLightAutoStart, lightAutoStart);
    }
  }

  // Programm stoppen und LEDs ausschalten
  if((cmd[0] == 'o') && (cmd[1] == 'f') && (cmd[2] == 'f'))
  {
    lightMode = LIGHT_MODE_OFF;
    lightProgAct = 0;
  }

  // Programm stoppen und LEDs ausschalten
  if((cmd[0] == 'b') && (cmd[1] == 'r') && (cmd[2] == 'k'))
  {
    // Pause beenden und Lichtprogramm fortsetzen
    if(lightMode == LIGHT_MODE_PAUSE)
    {
      lightMode = LIGHT_MODE_ACTIVE;
    }
    // Pause einleiten, Lichtprogramm anhalten
    else if(lightMode == LIGHT_MODE_ACTIVE)
    {
      lightMode = LIGHT_MODE_PAUSE;
    }
  }

  // Lichtprogramm starten
  if(cmd[0] == 'p')
  {
    progNr = ((cmd[1] - 0x30) * 10) + (cmd[2] - 0x30);

    if((progNr > 0) && (progNr <= 16))    // Programmnummer prüfen
    {
      if(IS_CHARACTER(lightPrograms[progNr - 1][0]))
      {
        // Zuletzt aktiviertes Programm merken
        lightAutoStart = (lightAutoStart & 0xF0) | (lightProgNr - 1);
        eeprom_write_byte(&eepLightAutoStart, lightAutoStart);

        lightProgStart(progNr);          // Lichtprogramm starten
      }
    }
  }
}


