/* flaneth - flash and ethernet - dartboard mod
 * version 0.1 date 2008-11-09
 * Copyright (C) 2007-2008 Stefan Schuermans <stefan@schuermans.info>
 * 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 "dart.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_LEN 2 // receiving content length
#define HTTP_RCV_HDR_END 3 // receiving header until end
#define HTTP_RCV_DATA 4 // receiving post data
#define HTTP_RCV_DONE 5 // receiving completed

// knwon HTTP commands
#define HTTP_CMD_UNKNOWN 0 // unknown HTTP command
#define HTTP_CMD_GET 1 // GET command
#define HTTP_CMD_POST 2 // POST 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_DART 10 // dartboard response, still waiting
#define HTTP_FILE_DART_NO 11 // dartboard response, no hit
#define HTTP_FILE_DART_1D 12 // dartboard response, 1 digit hit
#define HTTP_FILE_DART_2D 13 // dartboard response, 2 digit hit

// maximum HTTP/SOAP timeout (must be less than TCP timeout)
#define HttpMaxTimeout 45

// 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[32]; // 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
  unsigned long DataLen; // length of the data the client posts
  unsigned char DataTag; // flag if currently in a tag in HTTP request body
  unsigned long DataValue; // numeric value posted in HTTP request body
  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
  unsigned char DartFactor; // factor hit on dart board to transmit
  unsigned char DartValue; // value hit on dart board to transmit
} HttpConns[4];

// 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;
  // POST command
  else if( pConn->RcvBufLen == 4 && strncasecmp( (const char *)pConn->RcvBuf, "POST", 4 ) == 0 )
  {
    pConn->Command = HTTP_CMD_POST;
    pConn->DataLen = 0;
  }
  // 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;
  // dartboard response
  else if( pConn->RcvBufLen == 5 && strncasecmp( (const char *)pConn->RcvBuf, "/dart", 5 ) == 0 )
    pConn->File = HTTP_FILE_DART;
  // 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:
    case HTTP_CMD_POST:

      // 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 = 0;
          break;

        case HTTP_FILE_DART_NO:
          if( pConn->PreHttp1 )
          {
            pConn->pSndData = (uint16_t)HttpDartNo + HttpDartNoHeaderSize;
            pConn->SndDataLen = sizeof( HttpDartNo ) - 1 - HttpDartNoHeaderSize;
          }
          else
          {
            pConn->pSndData = (uint16_t)HttpDartNo;
            pConn->SndDataLen = sizeof( HttpDartNo ) - 1;
          }
          pConn->Vars = 1;
          break;

        case HTTP_FILE_DART_1D:
          if( pConn->PreHttp1 )
          {
            pConn->pSndData = (uint16_t)HttpDart1d + HttpDart1dHeaderSize;
            pConn->SndDataLen = sizeof( HttpDart1d ) - 1 - HttpDart1dHeaderSize;
          }
          else
          {
            pConn->pSndData = (uint16_t)HttpDart1d;
            pConn->SndDataLen = sizeof( HttpDart1d ) - 1;
          }
          pConn->Vars = 1;
          break;

        case HTTP_FILE_DART_2D:
          if( pConn->PreHttp1 )
          {
            pConn->pSndData = (uint16_t)HttpDart2d + HttpDart2dHeaderSize;
            pConn->SndDataLen = sizeof( HttpDart2d ) - 1 - HttpDart2dHeaderSize;
          }
          else
          {
            pConn->pSndData = (uint16_t)HttpDart2d;
            pConn->SndDataLen = sizeof( HttpDart2d ) - 1;
          }
          pConn->Vars = 1;
          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 )
}

// 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;
  struct HttpConnection * pConn;
  unsigned short len, j;
  uint16_t src; // 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

  // still waiting for dartboard
  if( pConn->File == HTTP_FILE_DART )
    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;

  // copy data to buffer
  src = pConn->pSndData + (uint16_t)Pos;
  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 )
    {
      switch( chr ) {
        case 0x80: chr = pConn->DartFactor + '0'; break;
        case 0x90: chr = pConn->DartValue % 10 + '0'; break;
        case 0x91: chr = pConn->DartValue / 10 + '0'; break;
        default: chr = ' '; break;
      }
    }
    *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
}

// 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( "pre1+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( "cmd no=%u cmd=%u", pConn->ConnNo, pConn->Command );
          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
          if( pConn->Command == HTTP_CMD_POST )
            pConn->RcvState = HTTP_RCV_LEN; // now receive content length
          else
            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 content length
      case HTTP_RCV_LEN:
        if( *pBuffer == '\n' || *pBuffer == '\r' ) // newline
        {
          pConn->RcvBuf[pConn->RcvBufLen-1] = 0;
          // content length line
          if( pConn->RcvBufLen >= 15 && memcmp( pConn->RcvBuf, "content-length:", 15 ) == 0 )
          {
            for( i = 15; i < pConn->RcvBufLen; i++ )
              if( pConn->RcvBuf[i] >= '0' && pConn->RcvBuf[i] <= '9' )
                pConn->DataLen = pConn->DataLen * 10 + pConn->RcvBuf[i] - '0';
            debug_http_printf( "data len %lu", pConn->DataLen );
            pConn->RcvState = HTTP_RCV_HDR_END; // now receive header until end
          }
          // empty receive buffer
          pConn->RcvBufLen = 0;
          // re-insert newline if now receiving header until end
          if( pConn->RcvState == HTTP_RCV_HDR_END )
            pConn->RcvBuf[pConn->RcvBufLen++] = *pBuffer;
        }
        else if( *pBuffer >= 'A' && *pBuffer <= 'Z' ) // capital letter
        {
          pConn->RcvBuf[pConn->RcvBufLen - 1] |= 0x20; // convert to lower case
        }
        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)
        {
          if( pConn->Command == HTTP_CMD_POST && pConn->DataLen > 0 )
          {
            pConn->RcvState = HTTP_RCV_DATA; // now receive post data
            pConn->DataTag = 0; // not in a tag
            pConn->DataValue = 0; // no value yet
          }
          else
            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 post data
      case HTTP_RCV_DATA:
        pConn->RcvBufLen = 0; // ignore any data in receive buffer
        // process data byte for byte
        if( pConn->DataTag ) { // in a tag
          if( *pBuffer == '>' ) // end of tag
            pConn->DataTag = 0;
        } else { // not in a tag
          if( *pBuffer == '<' ) // start of tag
            pConn->DataTag = 1;
          if( *pBuffer >= '0' && *pBuffer <= '9' ) // a digit
            pConn->DataValue = pConn->DataValue * 10 + *pBuffer - '0'; // collect digits
        }
        // count down number of bytes to receive
        if( pConn->DataLen > 0 )
          pConn->DataLen--;
        if( pConn->DataLen <= 0 ) {
          debug_http_printf( "data value %lu", pConn->DataValue );
          if( pConn->DataValue > HttpMaxTimeout ) // limit timeout (must be less than TCP timeout)
            pConn->DataValue = HttpMaxTimeout;
          pConn->DataValue *= 5; // convert from seconds to ticks
          pConn->RcvState = HTTP_RCV_DONE; // receiving completed
        }
        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;
}

// http tick procedure - call every 200ms
void HttpTick200( void ) // (extern)
{
  unsigned char i, factor, value;

  // process all HTTP connections
  for( i = 0; i < count( HttpConns ); i++ ) {
    struct HttpConnection * pConn = &HttpConns[i];

    // connection in use and waiting for dart
    if( pConn->ConnNo != 0xFF &&
        pConn->RcvState == HTTP_RCV_DONE &&
        pConn->File == HTTP_FILE_DART ) {

        // poll dart
        DartPoll( &factor, &value );

        // 2 digit field hit
        if( factor > 0 && value >= 10 ) {
          pConn->DartFactor = factor;
          pConn->DartValue = value;
          pConn->File = HTTP_FILE_DART_2D;
          HttpProcessRequest( pConn ); // re-process request
          TcpSend( pConn->ConnNo ); // data to send is available now
        }

        // 1 digit field hit
        else if( factor > 0 && value > 0 ) {
          pConn->DartFactor = factor;
          pConn->DartValue = value;
          pConn->File = HTTP_FILE_DART_1D;
          HttpProcessRequest( pConn ); // re-process request
          TcpSend( pConn->ConnNo ); // data to send is available now
        }

        // no field hit
        else {
          // no timeout yet ---> count down time
          if( pConn->DataValue > 0 ) {
            pConn->DataValue--;
          }
          // timeout
          else {
            pConn->DartFactor = 0;
            pConn->DartValue = 0;
            pConn->File = HTTP_FILE_DART_NO;
            HttpProcessRequest( pConn ); // re-process request
            TcpSend( pConn->ConnNo ); // data to send is available now
          }
        }

    } // in use
  } // for( i ...
}

