/*
 
  Code to test wiznet WIZ810MJ module

  See:

     <http://code.rancidbacon.com/LearningAboutArduinoWIZ810MJ>     

  Current features:
  
      * Initial W5100 driver port:
     
          + new-style network configuration
          
          + socket creation/listening/closing
          
          + Sending/Receiving okay
          
          + example "echo" server (no longer)
          
          + example "web server" with LED flash.

      * Terrible hacked-together code

  Author:

     follower@rancidbacon.com
      
  License:
    
     LGPL
     
  Version:
  
     20071106-0005+
     
*/

#include <WIZ810MJ.h>


/* --------- SPI --------- */

// Define SPI-related pins
#define PIN_DATA_OUT 11 // MOSI (Master Out / Slave In)
#define PIN_DATA_IN  12 // MISO (Master In / Slave Out)
#define PIN_SPI_CLOCK  13 // SCK (Serial Clock) 
#define PIN_SLAVE_SELECT 10 // SS (Slave Select)

class SpiConfiguration {
  
  public:
    static void begin(void);
};

void SpiConfiguration::begin(void) {
  /*

     Configure pins and registers required for SPI communication.
  
  */
  
  //   Configure I/O pins
  pinMode(PIN_DATA_OUT, OUTPUT);
  pinMode(PIN_DATA_IN, INPUT);
  pinMode(PIN_SPI_CLOCK, OUTPUT);
  pinMode(PIN_SLAVE_SELECT, OUTPUT);
  
  digitalWrite(PIN_SLAVE_SELECT, HIGH); // Disable slave
  
  /*
     Configure SPI Control Register (SPCR) (All values initially 0)
      Bit  Description
        7  SPI Interrupt Enable    -- disable  (SPIE --> 0)
        6  SPI Enable              -- enable   (SPE  --> 1)
        5  Data Order              -- MSB 1st  (DORD --> 0) (Slave specific)
        4  Master/Slave Select     -- master   (MSTR --> 1)
        3  Clock Polarity          --          (CPOL --> 0) (Slave specific) ("Mode")
        2  Clock Phase             --          (CPHA --> 0) (Slave specific)
        1  SPI Clock Rate Select 1 -- }        (SPR1 --> 0) 
        0  SPI Clock Rate Select 0 -- } fOSC/4 (SPR0 --> 0) ("Fastest" but see SPI2X in SPSR)
  */
  SPCR = (1<<SPE)| (1<<MSTR);

  // Clear previous data and status (TODO: Determine if necessary/better way.)
  // (Based on Playground SPI example.)
  byte dummy;
  dummy = SPSR;
  dummy = SPDR;
  delay(10);
  
}

SpiConfiguration SPI = SpiConfiguration();

/* ----------------------- */


/* ------ WIZ810MJ ------ */

class Wiz810MjDevice {
  
  public:
    Wiz810MjDevice(int resetPin);
    
    void setIp(byte b0, byte b1, byte b2, byte b3);
    void setMask(byte b0, byte b1, byte b2, byte b3);
    void setGateway(byte b0, byte b1, byte b2, byte b3);
    void setMac(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5);
  
  private:
    void _init(void);
    byte * _packBuffer(byte b0, byte b1, byte b2, byte b3);
    byte * _packBuffer(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5);
    
    int _resetPin;

    byte _scratchBuffer[6];   // Can we make this static?
};


Wiz810MjDevice::Wiz810MjDevice(int resetPin) {
  // TODO: We should really allow the chip-select pin to be set here?
  //       Or require that it's defined. (Currently in the library file 'types.h'.)
  _resetPin = resetPin;
  
  _init();
}


void Wiz810MjDevice::_init(void) {
  /*
  
     Initialise the WIZ810MJ module and driver.
     
   */

  /*
     Initialise the W5100 chip
     
     (Originally I thought it was possible for the chip
      to function without a hardware reset but it
      seems not to be the case.)
   */   
  pinMode(_resetPin, OUTPUT);
  
  // We rely on the time between function calls to
  // be long enough for the chip to recognise the
  // reset.
  digitalWrite(_resetPin, HIGH);
  digitalWrite(_resetPin, LOW); // reset
  digitalWrite(_resetPin, HIGH);
  
  // Chip initialisation by driver
  // Might be redundant following the above reset,
  // as this performs a software reset.
  iinchip_init();
  
  // Initialise driver
  // (This is required to configure some variables used
  // internally by the driver--even if the default chip
  // configuration is used.)
  sysinit(0x55, 0x55);  
}

byte * Wiz810MjDevice::_packBuffer(byte b0, byte b1, byte b2, byte b3) {
  /*
  
   */
  return _packBuffer(b0, b1, b2, b3, 0, 0); // Adds two bytes of padding
}

byte * Wiz810MjDevice::_packBuffer(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5) {
  /*
  
   */
  _scratchBuffer[0] = b0;
  _scratchBuffer[1] = b1;
  _scratchBuffer[2] = b2;
  _scratchBuffer[3] = b3;
  _scratchBuffer[4] = b4;
  _scratchBuffer[5] = b5;

  return _scratchBuffer;
}


void Wiz810MjDevice::setIp(byte b0, byte b1, byte b2, byte b3) {
  /*
  
   */
  setSIPR(_packBuffer(b0, b1, b2, b3));  
}

void Wiz810MjDevice::setMask(byte b0, byte b1, byte b2, byte b3) {
  /*
  
   */
  setSUBR(_packBuffer(b0, b1, b2, b3));  
}

void Wiz810MjDevice::setGateway(byte b0, byte b1, byte b2, byte b3) {
  /*
  
   */
  setGAR(_packBuffer(b0, b1, b2, b3));  
}

void Wiz810MjDevice::setMac(byte b0, byte b1, byte b2, byte b3, byte b4, byte b5) {
  /*
  
   */
  setSHAR(_packBuffer(b0, b1, b2, b3, b4, b5));  
}

/* ----------------------- */

/* --  NetworkInterface -- */

class NetworkInterface {
  
  public:
    NetworkInterface(Wiz810MjDevice& networkDevice);

    Wiz810MjDevice& device; // TODO: Make this a generic "network device" interface
};

NetworkInterface::NetworkInterface(Wiz810MjDevice& networkDevice) : device (networkDevice) {
  /*

    Note: The "weirdness" in this function declaration is a "initalization list",
          i.e. this bit:
    
         ) : device (networkDevice) {
  
          it is needed to give the 'device' Reference a value supplied to the method
          rather than having a new 'Wiz810MjDevice' instance created. 
   */
};

// NetworkInterface Network = NetworkInterface(...); // TODO: Make this a global

/* ----------------------- */


// #define PIN_RESET 9 // WIZnet module /RESET

#define PIN_RESET 8 // WIZnet module /RESET

SOCKET testSocket;
byte ip[6];


#define PIN_LED 2

void setup () {
  Serial.begin(9600);
  Serial.println("Setup enter...");

  SPI.begin();    
  
  Wiz810MjDevice WIZ810MJ = Wiz810MjDevice(PIN_RESET);

  Serial.println("Test W5100 configuration...");        
  
  WIZ810MJ.setMac(0x02,0xDE,0xAD,0xBE,0xEF,0x00);
   
  // WIZ810MJ.setIp(192,168,2,105);
  WIZ810MJ.setMask(255,255,255,0);
  WIZ810MJ.setGateway(192,168,2,101);
   
  NetworkInterface Network = NetworkInterface(WIZ810MJ); 
  
  Network.device.setIp(192,168,2,105);
   
  Serial.println("End test W5100 configuration...");        
    
        
  Serial.println("Test W5100 driver code...");
  
  getGAR(ip);
  
  Serial.print("Gateway IP read (first digit): ");
  Serial.println(ip[0], DEC);

  Serial.println("End test W5100 driver code...");        

  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, HIGH);
  
  Serial.println("Setup exit...");
  
}


void sendBanner(uint8_t *buffer, int ledState) { // {Socket targetSocket, ) {

  //strcpy((char *) buffer, "Content-Type: text/plain\n\nfoo!\n");
  strcpy((char *) buffer, "HTTP/1.1 200 OK\nContent-Type: text/html\n\n<html><body bgcolor='#000000'>foo!</body></html>\n");
  if (ledState) {
   buffer[63] = 'F'; 
  } else {
   buffer[65] = 'F';     
  }

  Serial.print("send result: "); 
  Serial.println(send(testSocket, buffer, strlen((char *)buffer)), DEC);
}


uint8_t readByte() {
  uint8_t theByte;
  recv(testSocket, &theByte, 1);
  return theByte;
}


int readMatch(char *stringToMatch) {
  /*

    Routine to read and match bytes received.
    
    (Essentially strcmp replacement without requiring a large receive buffer.)
    
    NOTE: Failed matches drop all bytes read so far. (i.e. you can't check for
          a bunch of possible matches from the same starting position).
          TODO: Fix this.
          
    Note: This blocks if there isn't enough data in the rx buffer.
    
  */
      
  while (getSn_RX_RSR(testSocket) < strlen(stringToMatch)) {
     // block
     // TODO: Return error or wait or "too short"?
  }

  // TODO: Do fancy string-matching techniques to avoid reading the whole string
  //       on non-matches. :-)
  for (int currCharIdx = 0; currCharIdx < strlen(stringToMatch); currCharIdx++) {
    if (readByte() != stringToMatch[currCharIdx]) {
       return 0;
    }
  }
  
  return 1;
}


void loop() {
  Serial.println("Test W5100 socket...");
  
  Serial.print("Create socket result: "); 
  Serial.println(socket(testSocket, Sn_MR_TCP, 80, 0), DEC);

  Serial.print("Socket status: ");   
  Serial.println(IINCHIP_READ(Sn_SR(testSocket)), HEX);
  
  if (IINCHIP_READ(Sn_SR(testSocket)) == SOCK_CLOSED) {
    Serial.println("Socket still closed, waiting...");
    while (IINCHIP_READ(Sn_SR(testSocket)) == SOCK_CLOSED) {
      //pass
    }
  }
  
  Serial.print("Listen on socket result: "); 
  Serial.println(listen(testSocket), DEC);

  Serial.println("Waiting for connection..."); 

  while (getSn_SR(testSocket) == SOCK_LISTEN) {
    delay(500);
  }

  Serial.println(getSn_SR(testSocket),HEX);

  getSn_DIPR(testSocket, ip);
  
  Serial.print("Destination IP read (last digit): ");
  Serial.println(ip[3], DEC);

  
  // TODO: Avoid buffer overflows...
#define MAX_TX_BUFFER_SIZE 200
  uint8_t bytesToSend[MAX_TX_BUFFER_SIZE];

  //sendBanner(bytesReceived);
  
  int dataLength = 0;
  
#define STATE_GET 0
#define STATE_READ 5

#define STATE_END -2

#define STATE_ERR -1
  int state = STATE_GET;
  int ledState = 1;
  unsigned char theByte;
  
  while (getSn_SR(testSocket) == SOCK_ESTABLISHED) {
   while (getSn_RX_RSR(testSocket) > 0) {

    if (state == STATE_GET) {
      if (readMatch("GET /")) {
        state = STATE_READ;
      } else {
        state = STATE_ERR;
      }
    }
        
    if (state == STATE_READ) {
     
      theByte = readByte();
      
      if (theByte == '0') {        
       digitalWrite(PIN_LED, LOW);
       ledState = 0;
       delay(100);
     } else if (theByte == '1') {
       digitalWrite(PIN_LED, HIGH);
       ledState = 1;
       delay(100);    
     } else {
       // It's not a valid byte.
       state = STATE_END;       
     }

    } else {
      state = STATE_ERR;
    }
      
     if ((state == STATE_ERR) || (state == STATE_END)) {
       Serial.println("");
       break;
     }
       
     /*
    dataLength = getSn_RX_RSR(testSocket);
    
    if (dataLength >= MAX_RX_BUFFER_SIZE) { // TODO: blah, blah...
      dataLength = MAX_RX_BUFFER_SIZE-1;
    }
    // Serial.print("dataLength: "); Serial.println(dataLength, HEX);
    //Serial.print("recv result: "); 
    //Serial.println(recv(testSocket, bytesReceived, dataLength), DEC); // NOTE: Throws away unread portion? No?
    recv(testSocket, bytesReceived, dataLength); // TODO: Return length?
    bytesReceived[dataLength]=0x00;
    Serial.print((char *)bytesReceived);
    
    //Serial.print("send result: "); 
    //Serial.println(send(testSocket, bytesReceived, dataLength), DEC);
    
    */
    
   }
   sendBanner(bytesToSend, ledState);
   break;
 }

  close(testSocket);
  disconnect(testSocket);
                    
  Serial.println("End test W5100 socket...");        

  
  
}