/*
 * tcpio.c
 *
 *  Created on: Sep 12, 2018
 *      Author: robert.applebee
 */

#include "lwip/tcp.h"
#include "tcpio.h"
#include "common/protocol.h"
#include "poe.h"
#include "string.h"
#include <stdio.h>


enum command_states
{
  CS_NONE = 0,
  CS_ACCEPTED,
  CS_RECEIVED,
  CS_CLOSING
};

struct command_state
{
  u8_t state;
  u8_t retries;
  struct tcp_pcb *pcb;
  /* pbuf (chain) to recycle */
  struct pbuf *p;
};

err_t TCPaccept(void *arg, struct tcp_pcb *newpcb, err_t err);
err_t TCPrecv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
void TCPerror(void *arg, err_t err);
err_t TCPpoll(void *arg, struct tcp_pcb *tpcb);
err_t TCPsent(void *arg, struct tcp_pcb *tpcb, u16_t len);
void TCPsend(struct tcp_pcb *tpcb, struct command_state *es);
void TCPsendResponse(struct tcp_pcb *tpcb, const void *buf, u16_t len, struct command_state *cs);
void TCPclose(struct tcp_pcb *tpcb, struct command_state *es);

struct ip_addr clientIpAddr;

struct tcp_pcb *TCPinit(void)
{
    struct tcp_pcb *pcb;

    pcb = tcp_new();
    if (pcb == NULL) {
        LWIP_DEBUGF(UDP_DEBUG, ("tcp_new failed!\n"));
        return 0;
    }

    /* bind to any IP address on TCP port */
    if (tcp_bind(pcb, IP_ADDR_ANY, TCP_PORT) != ERR_OK) {
        LWIP_DEBUGF(UDP_DEBUG, ("tcp_bind failed!\n"));
        return 0;
    }

    pcb = tcp_listen(pcb);
    if (!pcb) {
        LWIP_DEBUGF(UDP_DEBUG, ("tcp_listen failed!\n"));
        return 0;
    }

    tcp_accept(pcb, TCPaccept);

    return pcb;
}


err_t TCPaccept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  err_t ret_err;
  struct command_state *cs;

  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(err);

  /* commonly observed practive to call tcp_setprio(), why? */
  tcp_setprio(newpcb, TCP_PRIO_NORMAL);

  clientIpAddr.addr = newpcb->remote_ip.addr;

  cs = (struct command_state *)mem_malloc(sizeof(struct command_state));
  if (cs != NULL)
  {
    cs->state = CS_ACCEPTED;
    cs->pcb = newpcb;
    cs->retries = 0;
    cs->p = NULL;
    /* pass newly allocated es to our callbacks */
    tcp_arg(newpcb, cs);
    tcp_recv(newpcb, TCPrecv);
    tcp_err(newpcb, TCPerror);
    tcp_poll(newpcb, TCPpoll, 0);
    ret_err = ERR_OK;
  }
  else
  {
    ret_err = ERR_MEM;
  }
  return ret_err;
}

err_t TCPrecv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  struct command_state *cs;
  err_t ret_err;
  u16_t len;

  LWIP_ASSERT("arg != NULL",arg != NULL);
  cs = (struct command_state *)arg;
  if (p == NULL)  {
      /* remote host closed connection */
      cs->state = CS_CLOSING;
      if(cs->p == NULL) {
          /* we're done sending, close it */
          TCPclose(tpcb, cs);
      } else {
          p = cs->p;
          if (ProcessData(p->payload, p->len, tcpOut)) {
              len = strlen(tcpOut);

              /* we're not done yet */
              tcp_sent(tpcb, TCPsent);
              TCPsendResponse(tpcb, tcpOut, len,  cs);
          }
          tcp_recved(tpcb, p->len);
          pbuf_free(p);
      }
      ret_err = ERR_OK;
  }  else if(err != ERR_OK)  {
      /* cleanup, for unkown reason */
      if (p != NULL) {
          cs->p = NULL;
          tcp_recved(tpcb, p->len);
          pbuf_free(p);
      }
      ret_err = err;
  } else if(cs->state == CS_ACCEPTED) {
      /* first data chunk in p->payload */
      cs->state = CS_RECEIVED;
      /* store reference to incoming pbuf (chain) */
      cs->p = p;
      /* install send completion notifier */
      if (ProcessData(p->payload, p->len, tcpOut)) {
          len = strlen(tcpIn);

          /* we're not done yet */
          tcp_sent(tpcb, TCPsent);
          TCPsendResponse(tpcb, tcpOut, len,  cs);
      }

      /* cleanup */
      if (p != NULL) {
          tcp_recved(tpcb, p->len);
          cs->p = NULL;
          pbuf_free(p);
      }
      ret_err = ERR_OK;
  } else if (cs->state == CS_RECEIVED) {
      /* read some more data */
      if(cs->p == NULL) {
          cs->p = p;
          if (ProcessData(p->payload, p->len, tcpOut)) {
              len = strlen(tcpOut);

              /* we're not done yet */
              tcp_sent(tpcb, TCPsent);
              TCPsendResponse(tpcb, tcpOut, len,  cs);
          }

          /* cleanup, for unkown reason */
          if (p != NULL) {
              tcp_recved(tpcb, p->len);
              cs->p = NULL;
              pbuf_free(p);
          }
    } else  {
        struct pbuf *ptr;

        /* chain pbufs to the end of what we recv'ed previously  */
        ptr = cs->p;
        pbuf_chain(ptr,p);
    }
    ret_err = ERR_OK;
  } else if (cs->state == CS_CLOSING) {
      /* odd case, remote side closing twice, trash data */
      if (p) {
          tcp_recved(tpcb, p->tot_len);
          cs->p = NULL;
          pbuf_free(p);
      }
      ret_err = ERR_OK;
  } else {
       /* unkown cs->state, trash data  */
      if (p) {
        tcp_recved(tpcb, p->tot_len);
        cs->p = NULL;
        pbuf_free(p);
      }
      ret_err = ERR_OK;
  }
  return ret_err;
}

void TCPerror(void *arg, err_t err)
{
  struct command_state *cs;

  LWIP_UNUSED_ARG(err);

  cs = (struct command_state *)arg;
  if (cs != NULL)
  {
    mem_free(cs);
  }
  TCPinit();
}

err_t TCPpoll(void *arg, struct tcp_pcb *tpcb)
{
  err_t ret_err;
  struct command_state *cs;

  cs = (struct command_state *)arg;
  if (cs != NULL)
  {
      /* no remaining pbuf (chain)  */
      if(cs->state == CS_CLOSING)
      {
          TCPclose(tpcb, cs);
      }
    ret_err = ERR_OK;
  }
  else
  {
    /* nothing to be done */
    tcp_abort(tpcb);
    ret_err = ERR_ABRT;
  }
  return ret_err;
}

err_t TCPsent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
  struct command_state *cs;

  LWIP_UNUSED_ARG(len);

  cs = (struct command_state *)arg;
  cs->retries = 0;

  /* lost connection */
  if(cs->state == CS_CLOSING)
  {
      TCPclose(tpcb, cs);
  }
  return ERR_OK;
}

void TCPsendResponse(struct tcp_pcb *tpcb, const void *buf, u16_t len, struct command_state *cs)
{
  err_t wr_err = ERR_OK;

  /* enqueue data for transmission */
  wr_err = tcp_write(tpcb, buf, len, 1);
  if (wr_err == ERR_OK)
      wr_err = tcp_output(tpcb);
  if (wr_err == ERR_MEM) {
      /* we are low on memory, try later / harder, defer to poll */
  } else {
     /* other problem ?? */
  }
}

void TCPsend(struct tcp_pcb *tpcb, struct command_state *cs)
{
  struct pbuf *ptr;
  err_t wr_err = ERR_OK;

  while ((wr_err == ERR_OK) &&
         (cs->p != NULL) &&
         (cs->p->len <= tcp_sndbuf(tpcb)))
  {
  ptr = cs->p;

  /* enqueue data for transmission */
  wr_err = tcp_write(tpcb, ptr->payload, ptr->len, 1);
  if (wr_err == ERR_OK)
  {
     u16_t plen;
      u8_t freed;

     plen = ptr->len;
     /* continue with next pbuf in chain (if any) */
     cs->p = ptr->next;
     if(cs->p != NULL)
     {
       /* new reference! */
       pbuf_ref(cs->p);
     }
     /* chop first pbuf from chain */
      do
      {
        /* try hard to free pbuf */
        freed = pbuf_free(ptr);
      }
      while(freed == 0);
     /* we can read more data now */
     tcp_recved(tpcb, plen);
   }
   else if(wr_err == ERR_MEM)
   {
      /* we are low on memory, try later / harder, defer to poll */
     cs->p = ptr;
   }
   else
   {
     /* other problem ?? */
   }
  }
}
void TCPclose(struct tcp_pcb *tpcb, struct command_state *es)
{
  tcp_arg(tpcb, NULL);
  tcp_sent(tpcb, NULL);
  tcp_recv(tpcb, NULL);
  tcp_err(tpcb, NULL);
  tcp_poll(tpcb, NULL, 0);

  if (es != NULL)
  {
    mem_free(es);
  }

  // reconnect if connection closes
  tpcb = tcp_listen(tpcb);
//  TCPinit();
}





