/*
	  Ethernet shield test program
    Copyright (C) 2018  Andreas Mieke
    Copyright (C) 2010  Arduino LLC

    This program 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.

    This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "socket.h"

// Check if address is a net addres 0.0.0.0
#define is_net_addr(addr)  			 ((addr[0] == 0x00) &&	\
																	(addr[1] == 0x00) &&	\
																	(addr[2] == 0x00) &&	\
																	(addr[3] == 0x00))

// Check if address is broadcast address 255.255.255.255
#define is_broadcast_addr(addr)  ((addr[0] == 0xFF) &&	\
																	(addr[1] == 0xFF) &&	\
																	(addr[2] == 0xFF) &&	\
																	(addr[3] == 0xFF))

// Check if port is valid (not 0)
#define is_valid_port(port) 		 (port != 0x00)

static uint16_t local_port;

uint8_t ETH_socket(SOCKET s, uint8_t protocol, uint16_t port, uint8_t flag)
{
  // Check if protocol is supported
	if ((protocol == ETH_SMR_TCP) || (protocol == ETH_SMR_UDP) || (protocol == ETH_SMR_IPRAW) || (protocol == ETH_SMR_MACRAW) || (protocol == ETH_SMR_PPPoE))
  {
    // Close socket, if open
    ETH_close(s);
    // Set the mode register to the protocol, plus eventual flags
    ETH_writeSnMR(s, protocol | flag);
    if (port != 0) {
      // If port is not 0 (0 on client mode) write port to the port register
      ETH_writeSnPORT(s, port);
    } 
    else {
      // Write a random port to the port register (for client mode)
      local_port++;
      ETH_writeSnPORT(s, local_port);
    }

    // Execute the open socket command
    ETH_exec_socket_cmd(s, ETH_SCR_OPEN);
    return 1;
  }
  return 0;
}

uint8_t ETH_socket_status(SOCKET s)
{
  // Read the status register and return it
	uint8_t tmp = ETH_readSnSR(s);
  return tmp;
}

void ETH_close(SOCKET s)
{
  // Execute close socket and disable interrupts
	ETH_exec_socket_cmd(s, ETH_SCR_CLOSE);
  ETH_writeSnIR(s, 0xFF);
}

uint8_t ETH_connect(SOCKET s, uint8_t * addr, uint16_t port)
{
  // Check if port and address is valid
	if (is_net_addr(addr) || is_broadcast_addr(addr) || !is_valid_port(port)) 
    return 0;

  // Write IP to destination register
  ETH_writeSnDIPR(s, addr);
  // Write port to destination register
  ETH_writeSnDPORT(s, port);
  // Execute connect
  ETH_exec_socket_cmd(s, ETH_SCR_CONNECT);
  return 1;
}

void ETH_disconnect(SOCKET s)
{
  // Execute disconnect
	ETH_exec_socket_cmd(s, ETH_SCR_DISCON);
}

uint8_t ETH_listen(SOCKET s)
{
  // If state is not initzialized, return
  if (ETH_readSnSR(s) != ETH_SSR_INIT) {
    return 0;
  }
  // Execute the listen command
  ETH_exec_socket_cmd(s, ETH_SCR_LISTEN);
  return 1;
}

uint16_t ETH_send(SOCKET s, const uint8_t * buf, uint16_t len)
{
	uint8_t status=0;
  uint16_t ret=0;
  uint16_t freesize=0;

  // If data is bigger then Tx memory, split it
  if (len > ETH_SSIZE) 
    ret = ETH_SSIZE;
  else 
    ret = len;

  do 
  {
    // Write data to Tx memory
    freesize = ETH_get_TX_free_size(s);
    status = ETH_readSnSR(s);
    if ((status != ETH_SSR_ESTABLISHED) && (status != ETH_SSR_CLOSE_WAIT))
    {
      ret = 0; 
      break;
    }
  } 
  while (freesize < ret);

  // Start data processing and execute send command
  ETH_send_data_processing(s, (uint8_t *)buf, ret);
  ETH_exec_socket_cmd(s, ETH_SCR_SEND);

  // Check interrupt if everything is okay
  while ((ETH_readSnIR(s) & ETH_SIR_SEND_OK) != ETH_SIR_SEND_OK ) 
  {
    // If socket is closed set the state locally right
    if (ETH_readSnSR(s) == ETH_SSR_CLOSED )
    {
      ETH_close(s);
      return 0;
    }
  }
  // Mark interrupt as handled
  ETH_writeSnIR(s, ETH_SIR_SEND_OK);
  return ret;
}

int16_t ETH_recv(SOCKET s, uint8_t * buf, int16_t len)
{
  // Check remaining size
  int16_t ret = ETH_get_RX_received_size(s);
  if (ret == 0)
  {
    // Read status
    uint8_t status = ETH_readSnSR(s);
    // If status is wrong, return 0 read bytes
    if (status == ETH_SSR_LISTEN || status == ETH_SSR_CLOSED || status == ETH_SSR_CLOSE_WAIT)
    {
      ret = 0;
    }
    else
    {
      // Return -1 if there is nothing to read
      ret = -1;
    }
  }
  // If there is more to read then we have space, just read to the supplied limit
  else if (ret > len)
  {
    ret = len;
  }

  // If there is data to read and we have space, beginn processing and reading
  if (ret > 0)
  {
    ETH_recv_data_processing(s, buf, ret, 0x00);
    ETH_exec_socket_cmd(s, ETH_SCR_RECV);
  }
  return ret;
}

int16_t ETH_recv_available(SOCKET s)
{
  // Check if data is available to read
	return ETH_get_RX_received_size(s);
}

uint16_t ETH_peek(SOCKET s, uint8_t *buf)
{
  // Receive one byte
	ETH_recv_data_processing(s, buf, 1, 1);
	return 1;
}

uint16_t ETH_sendto(SOCKET s, const uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t port)
{
	uint16_t ret=0;

  // If write size is bigger then memory, limit to memory
  if (len > ETH_SSIZE) ret = ETH_SSIZE;
  else ret = len;

  // Check if address and port is right
  if (is_net_addr(addr) || !is_valid_port(port) || ret == 0) {
    ret = 0;
  }
  else
  {
    // Set address and port to send to
    ETH_writeSnDIPR(s, addr);
    ETH_writeSnDPORT(s, port);

    // Process data and send it
    ETH_send_data_processing(s, (uint8_t *)buf, ret);
    ETH_exec_socket_cmd(s, ETH_SCR_SEND);

    // Wait for the data to be sent
    while ((ETH_readSnIR(s) & ETH_SIR_SEND_OK) != ETH_SIR_SEND_OK) 
    {
      // If time out reset send and timout interrupts
      if (ETH_readSnIR(s) & ETH_SIR_TIMEOUT)
      {
        ETH_writeSnIR(s, (ETH_SIR_SEND_OK | ETH_SIR_TIMEOUT));
        return 0;
      }
    }

    // Reset send ok interrupt
    ETH_writeSnIR(s, ETH_SIR_SEND_OK);
  }
  return ret;
}

uint16_t ETH_recvfrom(SOCKET s, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t *port)
{
	uint8_t head[8];
  uint16_t data_len=0;
  uint16_t ptr=0;

  // Check if there is something to receive
  if (len > 0)
  {
    // Get the receive pointer
    ptr = ETH_readSnRX_RD(s);
    // Handle receiving according to socket type
    switch (ETH_readSnMR(s) & 0x07)
    {
    case ETH_SMR_UDP :
      ETH_read_data(s, ptr, head, 0x08);
      ptr += 8;
      addr[0] = head[0];
      addr[1] = head[1];
      addr[2] = head[2];
      addr[3] = head[3];
      *port = head[4];
      *port = (*port << 8) + head[5];
      data_len = head[6];
      data_len = (data_len << 8) + head[7];

      ETH_read_data(s, ptr, buf, data_len);
      ptr += data_len;

      ETH_writeSnRX_RD(s, ptr);
      break;

    case ETH_SMR_IPRAW :
      ETH_read_data(s, ptr, head, 0x06);
      ptr += 6;

      addr[0] = head[0];
      addr[1] = head[1];
      addr[2] = head[2];
      addr[3] = head[3];
      data_len = head[4];
      data_len = (data_len << 8) + head[5];

      ETH_read_data(s, ptr, buf, data_len);
      ptr += data_len;

      ETH_writeSnRX_RD(s, ptr);
      break;

    case ETH_SMR_MACRAW:
      ETH_read_data(s, ptr, head, 2);
      ptr+=2;
      data_len = head[0];
      data_len = (data_len<<8) + head[1] - 2;

      ETH_read_data(s, ptr, buf, data_len);
      ptr += data_len;
      ETH_writeSnRX_RD(s, ptr);
      break;

    default :
      break;
    }
    ETH_exec_socket_cmd(s, ETH_SCR_RECV);
  }
  return data_len;
}

void ETH_flush(SOCKET s)
{
	// TODO
}

uint16_t ETH_igmpsend(SOCKET s, const uint8_t * buf, uint16_t len)
{
	uint16_t ret=0;

  if (len > ETH_SSIZE) 
    ret = ETH_SSIZE;
  else 
    ret = len;

  if (ret == 0)
    return 0;

  // Process and send IGMP data
  ETH_send_data_processing(s, (uint8_t *)buf, ret);
  ETH_exec_socket_cmd(s, ETH_SCR_SEND);

  // Wairt for data to be sent or timeout
  while ((ETH_readSnIR(s) & ETH_SIR_SEND_OK) != ETH_SIR_SEND_OK) 
  {
    if (ETH_readSnIR(s) & ETH_SIR_TIMEOUT)
    {
      ETH_close(s);
      return 0;
    }
  }

  // Reset interrupt
  ETH_writeSnIR(s, ETH_SIR_SEND_OK);
  return ret;
}

int ETH_start_UDP(SOCKET s, uint8_t* addr, uint16_t port)
{
  // Check if address and port are valid
	if (is_net_addr(addr) || !is_valid_port(port))
  {
    return 0;
  }
  else
  {
    // Write IP and port
    ETH_writeSnDIPR(s, addr);
    ETH_writeSnDPORT(s, port);
    return 1;
  }
}

uint16_t ETH_buffer_data(SOCKET s, uint16_t offset, const uint8_t* buf, uint16_t len)
{
	uint16_t ret = 0;
  if (len > ETH_get_TX_free_size(s))
  {
    ret = ETH_get_TX_free_size(s);
  }
  else
  {
    ret = len;
  }
  ETH_send_data_processing_offset(s, offset, buf, ret);
  return ret;
}

int ETH_send_UDP(SOCKET s)
{
  ETH_exec_socket_cmd(s, ETH_SCR_SEND);

  while ((ETH_readSnIR(s) & ETH_SIR_SEND_OK) != ETH_SIR_SEND_OK) 
  {
    if (ETH_readSnIR(s) & ETH_SIR_TIMEOUT)
    {
      ETH_writeSnIR(s, (ETH_SIR_SEND_OK | ETH_SIR_TIMEOUT));
      return 0;
    }
  }

  ETH_writeSnIR(s, ETH_SIR_SEND_OK);
  return 1;
}
