/* flaneth - flash and ethernet
   Copyright (C) 2007-2012 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft: GNU public license V2 - http://www.gnu.org/copyleft/gpl.html
   a BlinkenArea project - http://www.blinkenarea.org/ */

#include <string.h>
#include <avr/pgmspace.h>

#include "debug.h"
#include "http.h"
#include "macros.h"
#include "tcp.h"

// receive states while parsing HTTP request
#define HTTP_RCV_CMD 0  // receiving HTTP command (e.g. GET)
#define HTTP_RCV_FILE 1 // receiving filename
#define HTTP_RCV_HDR_END 2      // receiving header until end
#define HTTP_RCV_DONE 3 // receiving completed

// knwon HTTP commands
#define HTTP_CMD_UNKNOWN 0      // unknown HTTP command
#define HTTP_CMD_GET 1  // GET command

// files available using HTTP
#define HTTP_FILE_NOT_FOUND 0   // file not found file
#define HTTP_FILE_INDEX 1       // index file
#define HTTP_FILE_ON_ALL 2      // turn on all link
#define HTTP_FILE_ON_SINGLE 3   // turn on single links (+0..+7)
#define HTTP_FILE_OFF_ALL 11    // turn off all link
#define HTTP_FILE_OFF_SINGLE 12 // turn off single links (+0..+7)
#define HTTP_FILE_IMG1 20       // image: on
#define HTTP_FILE_IMG0 21       // image: off

// content of webpages and strings to fill in for variables
#include "http_content.inc"

// table with HTTP connections
struct HttpConnection {
  unsigned char ConnNo; // number of the TCP connection, 0xFF is unused
  unsigned long RcvCnt; // number of received bytes on connection
  unsigned char RcvState;       // what is being received at the moment
  unsigned char RcvBuf[16];     // receive buffer
  unsigned char RcvBufLen;      // length of data in receive buffer
  unsigned char Command;        // the command the client issued
  char PreHttp1;        // flag if a HTTP version before 1.0 is used
  unsigned char File;   // the file the client requested
  uint16_t pSndData;    // the data to send (progmem pointer)
  unsigned short SndDataLen;    // the length of the data to send
  char Vars;    // flag if to interpret variables
} HttpConns[2];

// parse HTTP command
static void HttpParseCommand(struct HttpConnection *pConn)
{
  // GET command
  if (pConn->RcvBufLen == 3
      && strncasecmp((const char *)pConn->RcvBuf, "GET", 3) == 0)
    pConn->Command = HTTP_CMD_GET;
  // unknown command is the default
  else
    pConn->Command = HTTP_CMD_UNKNOWN;
}

// parse HTTP file
static void HttpParseFile(struct HttpConnection *pConn)
{
  // index file
  if ((pConn->RcvBufLen == 1
       && strncasecmp((const char *)pConn->RcvBuf, "/", 1) == 0)
      || (pConn->RcvBufLen == 2
          && strncasecmp((const char *)pConn->RcvBuf, "/i", 2) == 0))
    pConn->File = HTTP_FILE_INDEX;
  // turn on all link
  else if (pConn->RcvBufLen == 3
           && strncasecmp((const char *)pConn->RcvBuf, "/on", 3) == 0)
    pConn->File = HTTP_FILE_ON_ALL;
  // turn on single link
  else if (pConn->RcvBufLen == 4
           && strncasecmp((const char *)pConn->RcvBuf, "/on", 3) == 0
           && pConn->RcvBuf[3] >= '1' && pConn->RcvBuf[3] <= '8')
    pConn->File = HTTP_FILE_ON_SINGLE + pConn->RcvBuf[3] - '1';
  // turn off all link
  else if (pConn->RcvBufLen == 4
           && strncasecmp((const char *)pConn->RcvBuf, "/off", 4) == 0)
    pConn->File = HTTP_FILE_OFF_ALL;
  // turn off single link
  else if (pConn->RcvBufLen == 5
           && strncasecmp((const char *)pConn->RcvBuf, "/off", 4) == 0
           && pConn->RcvBuf[4] >= '1' && pConn->RcvBuf[4] <= '8')
    pConn->File = HTTP_FILE_OFF_SINGLE + pConn->RcvBuf[4] - '1';
  // image: on
  else if (pConn->RcvBufLen == 5
           && strncasecmp((const char *)pConn->RcvBuf, "/img1", 5) == 0)
    pConn->File = HTTP_FILE_IMG1;
  // image: off
  else if (pConn->RcvBufLen == 5
           && strncasecmp((const char *)pConn->RcvBuf, "/img0", 5) == 0)
    pConn->File = HTTP_FILE_IMG0;
  // error file is the default
  else
    pConn->File = HTTP_FILE_NOT_FOUND;
}

// process HTTP request
static void HttpProcessRequest(struct HttpConnection *pConn)
{
  // different commands
  switch (pConn->Command) {

  case HTTP_CMD_GET:
    // different actions
    switch (pConn->File) {

    case HTTP_FILE_ON_ALL:
      pConn->File = HTTP_FILE_INDEX;    // "symlink" to index file
      break;

    case HTTP_FILE_ON_SINGLE + 0:
    case HTTP_FILE_ON_SINGLE + 1:
    case HTTP_FILE_ON_SINGLE + 2:
    case HTTP_FILE_ON_SINGLE + 3:
    case HTTP_FILE_ON_SINGLE + 4:
    case HTTP_FILE_ON_SINGLE + 5:
    case HTTP_FILE_ON_SINGLE + 6:
    case HTTP_FILE_ON_SINGLE + 7:
      pConn->File = HTTP_FILE_INDEX;    // "symlink" to index file
      break;

    case HTTP_FILE_OFF_ALL:
      pConn->File = HTTP_FILE_INDEX;    // "symlink" to index file
      break;

    case HTTP_FILE_OFF_SINGLE + 0:
    case HTTP_FILE_OFF_SINGLE + 1:
    case HTTP_FILE_OFF_SINGLE + 2:
    case HTTP_FILE_OFF_SINGLE + 3:
    case HTTP_FILE_OFF_SINGLE + 4:
    case HTTP_FILE_OFF_SINGLE + 5:
    case HTTP_FILE_OFF_SINGLE + 6:
    case HTTP_FILE_OFF_SINGLE + 7:
      pConn->File = HTTP_FILE_INDEX;    // "symlink" to index file
      break;

    }   // switch( pConn->File )

    // different files
    switch (pConn->File) {

    case HTTP_FILE_INDEX:
      if (pConn->PreHttp1) {
        pConn->pSndData = (uint16_t) HttpIndex + HttpIndexHeaderSize;
        pConn->SndDataLen = sizeof(HttpIndex) - 1 - HttpIndexHeaderSize;
      } else {
        pConn->pSndData = (uint16_t) HttpIndex;
        pConn->SndDataLen = sizeof(HttpIndex) - 1;
      }
      pConn->Vars = 1;
      break;

    case HTTP_FILE_IMG1:
      if (pConn->PreHttp1) {
        pConn->pSndData = (uint16_t) HttpImg1 + HttpImg1HeaderSize;
        pConn->SndDataLen = sizeof(HttpImg1) - 1 - HttpImg1HeaderSize;
      } else {
        pConn->pSndData = (uint16_t) HttpImg1;
        pConn->SndDataLen = sizeof(HttpImg1) - 1;
      }
      pConn->Vars = 0;
      break;

    case HTTP_FILE_IMG0:
      if (pConn->PreHttp1) {
        pConn->pSndData = (uint16_t) HttpImg0 + HttpImg0HeaderSize;
        pConn->SndDataLen = sizeof(HttpImg0) - 1 - HttpImg0HeaderSize;
      } else {
        pConn->pSndData = (uint16_t) HttpImg0;
        pConn->SndDataLen = sizeof(HttpImg0) - 1;
      }
      pConn->Vars = 0;
      break;

    case HTTP_FILE_NOT_FOUND:
    default:
      if (pConn->PreHttp1) {
        pConn->pSndData = (uint16_t) HttpNotFound + HttpNotFoundHeaderSize;
        pConn->SndDataLen = sizeof(HttpNotFound) - 1 - HttpNotFoundHeaderSize;
      } else {
        pConn->pSndData = (uint16_t) HttpNotFound;
        pConn->SndDataLen = sizeof(HttpNotFound) - 1;
      }
      pConn->Vars = 0;

    }   // switch( pConn->File )
    break;

  case HTTP_CMD_UNKNOWN:
  default:
    if (pConn->PreHttp1) {
      pConn->pSndData = (uint16_t) HttpBadRequest + HttpBadRequestHeaderSize;
      pConn->SndDataLen =
          sizeof(HttpBadRequest) - 1 - HttpBadRequestHeaderSize;
    } else {
      pConn->pSndData = (uint16_t) HttpBadRequest;
      pConn->SndDataLen = sizeof(HttpBadRequest) - 1;
    }
    pConn->Vars = 0;

  }     // switch( pConn->Command )
}

// get a variable
// returns progmem pointer to variable and the length of the variable
static void HttpGetVariable(unsigned char VarNo, uint16_t * ppVar,
                            unsigned char *pVarLen)
{
  switch (VarNo) {

    // current state of output 1..8 ("ON" or "off")
  case 0x81:
  case 0x82:
  case 0x83:
  case 0x84:
  case 0x85:
  case 0x86:
  case 0x87:
  case 0x88:
    *ppVar = (uint16_t) HttpVarOff;
    *pVarLen = sizeof(HttpVarOff);
    break;

    // current state of output 1..8 ("1" or "0")
  case 0x91:
  case 0x92:
  case 0x93:
  case 0x94:
  case 0x95:
  case 0x96:
  case 0x97:
  case 0x98:
    *ppVar = (uint16_t) HttpVar0;
    *pVarLen = sizeof(HttpVar0);
    break;

    // unknown variable
  default:
    *ppVar = (uint16_t) HttpVarUnknown;
    *pVarLen = sizeof(HttpVarUnknown);
    break;

  }     // switch( VarNo );
}

// called when connection is established
void HttpConnect(unsigned char ConnNo)
{
  unsigned char i;
  struct HttpConnection *pConn;

  // find connection in table (in case TCP calls us twice, this should never
  // happen)
  for (i = 0; i < count(HttpConns); i++)
    if (HttpConns[i].ConnNo == ConnNo)
      break;
  // connection not found
  if (i >= count(HttpConns)) {
    // find a free entry
    for (i = 0; i < count(HttpConns); i++)
      if (HttpConns[i].ConnNo == 0xFF)
        break;
    if (i >= count(HttpConns))  // no free entry found
      return;   // ignore this connection (will be closed in first call of
                // HttpSend)
  }
  // get pointer to connection
  pConn = &HttpConns[i];

  debug_http_printf("accept no=%u", ConnNo);

  // put new connection into table
  pConn->ConnNo = ConnNo;
  pConn->RcvCnt = 0;
  pConn->RcvState = HTTP_RCV_CMD;
  pConn->RcvBufLen = 0;
}

// called when connection is closed / reset
// (after this, the connection number may not be used any more)
void HttpClose(unsigned char ConnNo)
{
  unsigned char i;
  struct HttpConnection *pConn;

  // find connection in table
  for (i = 0; i < count(HttpConns); i++)
    if (HttpConns[i].ConnNo == ConnNo)
      break;
  if (i >= count(HttpConns))    // connection not found
    return;     // ignore this (now already closed) connection
  // get pointer to connection
  pConn = &HttpConns[i];

  debug_http_printf("close no=%u", ConnNo);

  // drop connection from table
  pConn->ConnNo = 0xFF;
}

// called when sending data is possible
// (return length of available data, 0xFFFF to close connection)
unsigned short HttpSend(unsigned char ConnNo, unsigned long Pos,
                        unsigned char *pBuffer, unsigned short MaxLen)
{
  unsigned char i, var, VarLen, VarPos;
  struct HttpConnection *pConn;
  unsigned short len, j;
  uint16_t src, srcTmp, pVar;   // progmem pointer
  char chr, *dest;

  // find connection in table
  for (i = 0; i < count(HttpConns); i++)
    if (HttpConns[i].ConnNo == ConnNo)
      break;
  if (i >= count(HttpConns))    // connection not found
    return 0xFFFF;      // close connection
  // get pointer to connection
  pConn = &HttpConns[i];

  // not done with receiving
  if (pConn->RcvState != HTTP_RCV_DONE)
    return 0;   // do not send anything yet

  // at or behind end of data
  if (Pos >= (unsigned long)pConn->SndDataLen)
    return 0xFFFF;      // request to close connection

  // get number of bytes to send
  len = min(pConn->SndDataLen - (unsigned short)Pos, MaxLen);
  if (len == 0) // nothing to send
    return 0;

  // no variable in variable buffer
  var = 0;
  VarLen = 0;
  VarPos = 0;
  pVar = 0; // keep compiler happy - initialize
  // if part that should be sent starts with variable
  src = pConn->pSndData + (uint16_t) Pos;
  chr = (char)pgm_read_byte_near(src);  // read first character
  if (pConn->Vars && (unsigned char)chr >= 0x80) {
    // get variable
    var = chr;
    HttpGetVariable(var, &pVar, &VarLen);
    // get position in variable
    for (VarPos = 0, srcTmp = src - 1; srcTmp > pConn->pSndData;
         VarPos++, srcTmp--)
      if ((char)pgm_read_byte_near(srcTmp) != var)      // if normal
                                                        // character or other 
                                                        // variable, we found 
                                                        // the begin of the
                                                        // variable
        break;
  }
  // copy data to buffer
  dest = (char *)pBuffer;
  for (j = 0; j < len; j++) {
    // read current character
    chr = (char)pgm_read_byte_near(src);
    // variable
    if (pConn->Vars && (unsigned char)chr >= 0x80) {
      // new variable
      if (var != chr) {
        // get variable
        var = chr;
        HttpGetVariable(var, &pVar, &VarLen);
        VarPos = 0;
      }
      // copy next character of variable
      if (VarPos < VarLen)      // get next character of variable
        *dest = (char)pgm_read_byte_near(pVar + VarPos++);
      else
        *dest = ' ';    // fill rest of variable with spaces
    }
    // normal character
    else {
      var = 0;  // not a variable
      *dest = chr;      // copy character
    }
    // next character
    src++;
    dest++;
  }     // for( j ...

  // return length of data in buffer
  debug_http_printf("send no=%u pos=%lu len=%u", ConnNo, Pos, len);
  return len;
}

// called when data was sent and ACKed
void HttpSent(unsigned char ConnNo, unsigned long Pos)
{
  // nothing needs to be done here
  (void)ConnNo;
  (void)Pos;
}

// called when data was received
// must return new window size (not smaller than curWnd)
unsigned short HttpReceived(unsigned char ConnNo, unsigned long Pos,
                            unsigned char *pBuffer, unsigned short Len,
                            unsigned short curWnd)
{
  unsigned char i;
  struct HttpConnection *pConn;

  // find connection in table
  for (i = 0; i < count(HttpConns); i++)
    if (HttpConns[i].ConnNo == ConnNo)
      break;
  if (i >= count(HttpConns))    // connection not found
    return max(curWnd, 256);    // ignore this connection (will be closed in
                                // first call of HttpSend)
  // get pointer to connection
  pConn = &HttpConns[i];

  // received duplicate data or missed some data
  // (just to be on the safe side, this should never happen)
  if (pConn->RcvCnt != Pos) {
    // close connection
    TcpClose(pConn->ConnNo);
    pConn->ConnNo = 0xFF;
    return max(curWnd, 256);
  }
  // process received data
  for (; Len > 0; pBuffer++, Len--, pConn->RcvCnt++) {
    // store character in receive buffer (if it fits into it)
    if (pConn->RcvBufLen < sizeof(pConn->RcvBuf)) {
      pConn->RcvBuf[pConn->RcvBufLen] = *pBuffer;
      pConn->RcvBufLen++;
    }
    // actions according to state
    switch (pConn->RcvState) {

      // receiving HTTP command (e.g GET)
    case HTTP_RCV_CMD:
      if (*pBuffer == '\r' || *pBuffer == '\n') // newline
      {
        pConn->RcvBufLen--;     // remove newline from buffer
        HttpParseCommand(pConn);        // parse command
        pConn->PreHttp1 = 1;    // older than HTTP 1.0
        pConn->File = HTTP_FILE_INDEX;  // send index file
        pConn->RcvState = HTTP_RCV_DONE;        // receiving completed
        debug_http_printf("get+end no=%u", pConn->ConnNo);
        pConn->RcvBufLen = 0;   // empty receive buffer
        HttpProcessRequest(pConn);      // now process request
      } else if (*pBuffer == ' ' || *pBuffer == '\t')   // whitespace
      {
        pConn->RcvBufLen--;     // remove whitespace from buffer
        HttpParseCommand(pConn);        // parse command
        pConn->RcvState = HTTP_RCV_FILE;        // now receive filename
        debug_http_printf("get no=%u", pConn->ConnNo);
        pConn->RcvBufLen = 0;   // empty receive buffer
      }
      break;

      // receiving filename
    case HTTP_RCV_FILE:
      if (*pBuffer == '\r' || *pBuffer == '\n') // newline
      {
        pConn->RcvBufLen--;     // remove newline from buffer
        HttpParseFile(pConn);   // parse file
        pConn->PreHttp1 = 1;    // older than HTTP 1.0
        pConn->RcvState = HTTP_RCV_DONE;        // receiving completed
        debug_http_printf("file+end no=%u file=%u", pConn->ConnNo,
                          pConn->File);
        pConn->RcvBufLen = 0;   // empty receive buffer
        HttpProcessRequest(pConn);      // now process request
      } else if (*pBuffer == ' ' || *pBuffer == '\t')   // whitespace
      {
        pConn->RcvBufLen--;     // remove whitespace from buffer
        HttpParseFile(pConn);   // parse file
        pConn->PreHttp1 = 0;    // HTTP 1.0 or newer
        pConn->RcvState = HTTP_RCV_HDR_END;     // now receive header until
                                                // end
        debug_http_printf("file no=%u file=%u", pConn->ConnNo, pConn->File);
        pConn->RcvBufLen = 0;   // empty receive buffer
      }
      break;

      // receiving header until end
    case HTTP_RCV_HDR_END:
      if (*pBuffer != '\r' && *pBuffer != '\n') // not a newline
      {
        pConn->RcvBufLen = 0;   // empty receive buffer
        break;
      }
      if ((pConn->RcvBufLen == 2 && memcmp(pConn->RcvBuf, "\r\r", 2) == 0) ||   // CR 
                                                                                // CR
          (pConn->RcvBufLen == 2 && memcmp(pConn->RcvBuf, "\n\n", 2) == 0) ||   // LF 
                                                                                // LF
          (pConn->RcvBufLen == 3 && memcmp(pConn->RcvBuf, "\r\n\n", 3) == 0) || // CR 
                                                                                // LF 
                                                                                // LF
          (pConn->RcvBufLen == 3 && memcmp(pConn->RcvBuf, "\n\r\n", 3) == 0) || // LF 
                                                                                // CR 
                                                                                // LF
          pConn->RcvBufLen == 4)        // CR LF CR LF (or some other
                                        // combination of 4 times CR or LF)
      {
        pConn->RcvState = HTTP_RCV_DONE;        // receiving completed
        debug_http_printf("end no=%u", pConn->ConnNo);
        pConn->RcvBufLen = 0;   // empty receive buffer
        HttpProcessRequest(pConn);      // now process request
      }
      break;

      // receiving completed
    case HTTP_RCV_DONE:
      pConn->RcvBufLen = 0;     // ignore any additional data
      break;

    }   // switch( pConn->RcvState )
  }     // for( ; Len > 0; pBuffer++, Len--, pConn->RcvCnt++ )

  // return at least 256 bytes window size
  // - we are always able to receive data
  return max(curWnd, 256);
}

// http notification functions
struct TcpNotify HttpNotify =   // (extern)
{
  .Connect = HttpConnect,
  .Close = HttpClose,
  .Send = HttpSend,
  .Sent = HttpSent,
  .Received = HttpReceived,
};

// initialize
void HttpInit(void)     // (extern)
{
  unsigned char i;

  // no HTTP connections yet
  for (i = 0; i < count(HttpConns); i++)
    HttpConns[i].ConnNo = 0xFF;
}
