#include "sntp.h"
#include "rtc.h"
#include "settings_api.h"

#include <string.h>
#include <time.h>

#ifdef PRINTF_STDLIB
#include <stdio.h>
#endif
#ifdef PRINTF_CUSTOM
#include "tinystdio.h"
#endif


#define SENDFAIL_TIMEOUT 5000 /* 5 seconds */
#define SENT_TIMEOUT 60000 /* 1 minute */
#define BADREPLY_TIMEOUT 60000 /* 1 minute */
#define VALID_TIMEOUT (8 * 3600000) /* 8 hours */

/* number of seconds between 1900 and 1970 */
#define DIFF_SEC_1900_1970         (2208988800UL)

struct sntp_packet
{
  uint8_t status;
  uint8_t stratum;
  uint8_t ppoll;
  uint8_t precision;
  uint32_t distance;
  uint32_t dispersion;
  uint32_t refid;
  uint64_t reftime;
  uint64_t org;
  uint64_t rec;
  uint64_t xmt;
};

static unsigned int timeout;
static struct udp_pcb* upcb;
static struct ip_addr server;
static int port = 123;

/**
  * @brief  Общая структура настроек
  */
extern SETTINGS_t sSettings;

/**
  * @brief  Разовая синхронизация времени при старте контроллера
  */
extern TaskHandle_t xHandleSntpOnceSinhro;

/**
  * @brief  Синхронизация времени единоразово при включении контроллера
  * @retval 
  */
void vTaskOnceSynchro(void *arg)
{
  for (;;) 
  {
	if (sSettings.sSNTP.sntpEnable)
	{  
      vTaskDelay(7000);
  	  SNTP_Poll();
	  //printf("Once time sinhro\n\r");
	  vTaskDelete(xHandleSntpOnceSinhro);
	}
	else
	  vTaskDelete(xHandleSntpOnceSinhro);
  }
}

/**
  * @brief  Периодическая синхронизация времени.
  *         Выполняется раз в сутки с 0 часов.
  * @retval 
  */
void vTaskPeriodicSynchro(void *arg)
{
  TM_RTC_t data;
  
  static uint8_t fSinhro = 0;
  
  for (;;) 
  {
	vTaskDelay(10000);
	if (sSettings.sSNTP.sntpEnable)
	{
	  TM_RTC_GetDateTime(&data, TM_RTC_Format_BIN);
	  
	  /* Если пришло время синхронизации */
	  if ((data.hours == 0) && (fSinhro == 0))
	  {
		SNTP_Poll();
	    fSinhro = 1;
		//printf("Periodic time sinhro\n\r");
	  }
	  
	  if (data.hours > 1)
	    fSinhro = 0;
	}
   	
  }
}

/**
  * @brief  Отладочный таск. Выводим время в консоль.
  * @retval 
  */
void vTaskSntp(void *arg)
{
  TM_RTC_t data;
  
  for (;;)
  {
    vTaskDelay(1000);
	TM_RTC_GetDateTime(&data, TM_RTC_Format_BIN);
	printf("%d.%d.%d %d:%d:%d \n\r", data.date,data.month,data.year,data.hours,data.minutes,data.seconds);
  }
}

/**
  * @brief  Инициализация SNTP.
  * @retval 
  */
void SNTP_Init(void)
{
  
  
  SNTP_SetServerAddr(sSettings.sSNTP.ip);
  SNTP_Enable(sSettings.sSNTP.sntpEnable);
}

static void recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, 
				 struct ip_addr *addr, u16_t port)
{
  time_t t;
  int utcSec = 0;
  TM_RTC_t data;
  
  if (p->len == sizeof(struct sntp_packet))
  {
    int i;
    struct sntp_packet aligned;
    //myassert(p->len == p->tot_len); /* don't accept chained pbuf */
    memcpy(&aligned, p->payload, sizeof(aligned));
    i = (aligned.status >> 3) & 7;
    
	if ((i < 1) || (i > 4)) /* SNTP version 1..4 */
      goto out;

    i = aligned.status & 7;

	if ((i != 4) && (i != 5)) /* mode 4 or 5: server or broadcast */
      goto out;

	if (aligned.xmt == 0)
      goto out;
	
	utcSec = (int)(3600.0*sSettings.sSNTP.timeZone);
	t = (ntohl(aligned.xmt) - 2208988800 + utcSec );
	
	TM_RTC_SetDataTimeUnix((uint32_t)t);
	
	/* Сохраним время последней синхронизации */
	TM_RTC_GetDateTime(&data, TM_RTC_Format_BIN);
		
	memset(sSettings.sSNTP.data, 0, sizeof(sSettings.sSNTP.data));
	
	sprintf(sSettings.sSNTP.data, "%02d.%02d.%02d %02d:%02d:%02d", 
			data.date, data.month, data.year,
			data.hours, data.minutes, data.seconds);
	
    timeout = VALID_TIMEOUT;
    
  }
  out:
    pbuf_free(p);
}

void SNTP_Enable(bool enable)
{
  if (enable)
  {
    if (upcb == 0)
    {
      err_t ret;
      upcb = udp_new();
      if (upcb != 0)
	  {
		ret = udp_bind(upcb, IP_ADDR_ANY, port);

	    if (ret != ERR_OK)
        {
          udp_remove(upcb);
          upcb = 0;
        }
        else
        {
          udp_recv(upcb, recv, 0);
        }
        timeout = 0;
	  }
    }
  }
  else if (upcb != 0)
  {
    udp_remove(upcb);
    upcb = 0;
  }
}

bool SNTP_IsEnabled(void)
{
  return upcb != 0;
}

void SNTP_SetServerAddr(char *addr)
{
  server.addr = ipaddr_addr(addr);
}

int sntp_getserverport(void)
{
  return port;
}

static void send_request(void)
{
  struct sntp_packet packet;
  struct pbuf* psend;
  memset(&packet, 0, sizeof(packet));
  packet.status = (3 << 3) /* SNTP vesion 3 */ | (3 << 0); /* Mode: client */
  psend = pbuf_alloc(PBUF_RAW, sizeof(packet), PBUF_REF);
  
  if (psend != 0)
  {
    psend->payload = &packet;
    timeout = (udp_sendto(upcb, psend, &server, port) == ERR_OK) ? SENT_TIMEOUT : SENDFAIL_TIMEOUT;
    pbuf_free(psend);
  }
}

void SNTP_Poll(void)
{
  if (upcb)
    send_request();
}
