#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "command/command.h"
#include "wm_include.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lwip/netif.h"
#include "app_common.h"
#include "nano_shell_interface.h"
#include "wm_gpio_afsel.h"

extern int wm_printf(const char *fmt,...);
extern u32 tls_mem_get_avail_heapsize(void);
extern bool disconnect_client(void);

extern int demo_bt_enable();
extern int demo_bt_destroy();
extern int demo_ble_server_on();
extern int demo_ble_server_off();

void tls_wifi_client_event_cb(u8 *mac, enum tls_wifi_client_event_type event)
{
  struct tls_sta_info_t *mac_addr = (struct tls_sta_info_t *)mac;

  shell_printf("Client event(%d), MAC : %M"NEW_LINE, event, mac_addr);
}

void wifi_scan_result_cb(void)
{
  u16 buffer_size = sizeof(struct tls_scan_bss_t) + sizeof(struct tls_bss_info_t) * 20;
  u8 *buf = tls_mem_alloc(buffer_size);
  if(buf == NULL)
  {
    shell_printf("Failed to allocate result buffer"NEW_LINE);
    return;
  }
  struct tls_scan_bss_t *scan_result = (struct tls_scan_bss_t *)buf;
  struct tls_bss_info_t *station_list = scan_result->bss;

  tls_wifi_get_scan_rslt(buf, buffer_size);
  shell_printf("Found %u nearby station(s) - info size(%u/%u)"NEW_LINE,
  scan_result->count,
  scan_result->length,
  buffer_size);

  for(u8 i = 0; i < scan_result->count; i++)
  {
    station_list[i].ssid[station_list[i].ssid_len] = '\0';
    shell_printf("station %u :"NEW_LINE"SSID : %s"NEW_LINE"BSSID : %02X:%02X:%02X:%02X:%02X:%02X"NEW_LINE"RSSI : %d dB"NEW_LINE"Channel : %u"NEW_LINE"Max DR : %u Mbps"NEW_LINE"Mode %u"NEW_LINE"Auth :%u"NEW_LINE"WPS supported : %u"NEW_LINE NEW_LINE,
    i,
    (char *)station_list[i].ssid,
    station_list[i].bssid[0], station_list[i].bssid[1], station_list[i].bssid[2], station_list[i].bssid[3], station_list[i].bssid[4], station_list[i].bssid[5],
    (s8)station_list[i].rssi,
    station_list[i].channel,
    station_list[i].max_data_rate,
    station_list[i].mode,
    station_list[i].privacy,
    station_list[i].wps_support);
  }

  tls_mem_free(buf);
}

void tls_wifi_data_ext_recv_cb(u8* data, u32 data_len, struct tls_wifi_ext_t *ext)
{
  shell_printf("recv packet :"NEW_LINE"rssi : %d\nrate : %u"NEW_LINE, (s8)ext->rssi, ext->rx_rate);
  for(u32 i = 0; i < data_len; i++)
  {
    shell_printf("%02X", data[i]);
    if(i % 30 == 0)
      shell_printf(NEW_LINE);
  }
  shell_printf(NEW_LINE);
}

void tls_rtc_irq_cb(void *arg)
{
  struct tm rtc_time;
  tls_get_rtc(&rtc_time);

  shell_printf("rtc isr called"NEW_LINE"time is :"NEW_LINE"%d:%d:%d %d/%d/%d"NEW_LINE,
  rtc_time.tm_hour,
  rtc_time.tm_min,
  rtc_time.tm_sec,
  rtc_time.tm_mday,
  rtc_time.tm_mon,
  rtc_time.tm_year);

  tls_rtc_timer_stop();
}

int _system(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "list_task") == 0)
    {
      char *buf = NULL;

      buf = tls_mem_alloc(1024);
      if(NULL == buf)
        return 0;
#if configUSE_TRACE_FACILITY
      vTaskList(buf);
#endif
      shell_printf(NEW_LINE"%s"NEW_LINE"buf_len : %d"NEW_LINE, buf, strlen(buf));
      tls_mem_free(buf);
      buf = NULL;
    }
    else if(strcmp(argv[1], "ram_usage") == 0)
    {
      shell_printf("Free OS heap : %u/%u byte(s)"NEW_LINE"tls heap size : %u"NEW_LINE, xPortGetFreeHeapSize(), configTOTAL_HEAP_SIZE, tls_mem_get_avail_heapsize());
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of system actions :"NEW_LINE"list_task"NEW_LINE"ram_usage"NEW_LINE);
  }
  return 0;
}

int _reset(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  tls_sys_reset();
  return 0;
}

int _bus(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  char spi_recv_buff[32] = "";
  u32 fclk = SPI_DEFAULT_SPEED;
  if(argc > 1)
  {
    if(strcmp(argv[1], "spi_init") == 0)
    {
      if(argc == 3)
      {
        fclk = strtoul(argv[2], NULL, 10);
        if(!fclk) fclk = SPI_DEFAULT_SPEED;
      }
      shell_printf("SPI init : %d, clk -> %u Hz"NEW_LINE, tls_spi_setup(SPI_DEFAULT_MODE, SPI_CS_ACTIVE_MODE, fclk), fclk);
    }
    else if(strcmp(argv[1], "spi_w") == 0)
    {
      shell_printf("Writing [%s](len : %d) to SPI"NEW_LINE, argv[2], strlen(argv[2]));
      if(tls_spi_write((u8*)argv[2], strlen(argv[2])) != TLS_SPI_STATUS_OK)
      {
        shell_printf("Failed to write to SPI"NEW_LINE);
        return 0;
      }
    }
    else if(strcmp(argv[1], "spi_r") == 0)
    {
      if(tls_spi_read((u8*)spi_recv_buff, sizeof(spi_recv_buff) - 1) != TLS_SPI_STATUS_OK)
      {
        shell_printf("Failed to read from SPI"NEW_LINE);
        return 0;
      }
      shell_printf("Received [%s](len : %d) from SPI"NEW_LINE, spi_recv_buff, strlen(spi_recv_buff));
    }
    else if(strcmp(argv[1], "spi_wr") == 0)
    {
      if(tls_spi_read_with_cmd((u8 *) argv[2], strlen(argv[2]), (u8 *) spi_recv_buff, sizeof(spi_recv_buff) - 1) != TLS_SPI_STATUS_OK)
      {
        shell_printf("Failed to write & read combo using SPI"NEW_LINE);
        return 0;
      }
      shell_printf("Writing [%s](len : %d) to SPI"NEW_LINE, argv[2], strlen(argv[2]));
      shell_printf("Received [%s](len : %d) from SPI"NEW_LINE, spi_recv_buff, strlen(spi_recv_buff));
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of %s actions :"NEW_LINE"spi_init"NEW_LINE"spi_w"NEW_LINE"spi_r"NEW_LINE"spi_wr"NEW_LINE, argv[0]);
  }
  return 0;
}

int _soft_ap(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "state") == 0)
    {
      shell_printf("SOFT AP state : %u"NEW_LINE, tls_wifi_softap_get_state());
    }
    else if(strcmp(argv[1], "create") == 0)
    {
      struct tls_softap_info_t ap_info;
      struct tls_ip_info_t ip_info;

      tls_wifi_set_oneshot_flag(0);
      tls_wifi_softap_destroy();

      shell_printf("Registering client event callback"NEW_LINE);
      tls_wifi_softap_client_event_register(&(tls_wifi_client_event_cb));

      strncpy((char *)ap_info.ssid, argv[2], 32);
      ap_info.ssid[32] = '\0';
      ap_info.encrypt = IEEE80211_ENCRYT_TKIP_WPA2;
      ap_info.channel = 5;

      ap_info.keyinfo.format = 1;
      ap_info.keyinfo.index = 1;
      ap_info.keyinfo.key_len = strlen(argv[3]);
      strncpy((char *)ap_info.keyinfo.key, argv[3], 63);

      ip_info.ip_addr[0] = 192;
      ip_info.ip_addr[1] = 168;
      ip_info.ip_addr[2] = 1;
      ip_info.ip_addr[3] = 1;
      ip_info.netmask[0] = 255;
      ip_info.netmask[1] = 255;
      ip_info.netmask[2] = 255;
      ip_info.netmask[3] = 0;
      ip_info.dnsname[0] = '\0';
      
      int result = tls_wifi_softap_create(&ap_info, &ip_info);
      shell_printf("Create AP with SSID : %s, key(%d) : %s -> %d"NEW_LINE, ap_info.ssid, ap_info.keyinfo.key_len, ap_info.keyinfo.key, result);
    }
    else if(strcmp(argv[1], "destroy") == 0)
    {
      tls_wifi_softap_client_event_register(NULL);
      tls_wifi_set_oneshot_flag(0);
      tls_wifi_softap_destroy();
      shell_printf("Stopping SOFT AP"NEW_LINE);
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of soft_ap actions :"NEW_LINE"state"NEW_LINE"create <SSID> <PWD>"NEW_LINE"destroy"NEW_LINE);
  }
  return 0;
}

int _station(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "scan") == 0)
    {
      tls_wifi_scan_result_cb_register(&(wifi_scan_result_cb));

      if(tls_wifi_scan() == WM_SUCCESS)
      {
        shell_printf("Scanning nearby stations..."NEW_LINE);
      }
      else
      {
        shell_printf("Failed to start wifi scan"NEW_LINE);
      }
    }
    else if(strcmp(argv[1], "state") == 0)
    {
      shell_printf("Station state : %u"NEW_LINE, tls_wifi_get_state());
    }
    else if(strcmp(argv[1], "connect") == 0)
    {
      shell_printf("Connecting to %s with pwd : %s"NEW_LINE, argv[2], argv[3]);
      if(tls_wifi_connect((u8 *)argv[2], strlen(argv[2]), (u8 *)argv[3], strlen(argv[3])) == WM_SUCCESS)
      {
        shell_printf("Connecting..."NEW_LINE);
      }
      else
      {
        shell_printf("Failed to connect !"NEW_LINE);
      }
    }
    else if(strcmp(argv[1], "disconnect") == 0)
    {
      shell_printf("Disconnecting from current station"NEW_LINE);
      tls_wifi_set_oneshot_flag(0);
      tls_wifi_disconnect();
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of station actions :"NEW_LINE"scan"NEW_LINE"state"NEW_LINE"connect <SSID> <PWD>"NEW_LINE"disconnect"NEW_LINE);
  }
  return 0;
}

int _cpu_temp(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  int temperature = adc_temp();

  shell_printf("CPU temp is %d.%03d"NEW_LINE, temperature/1000, temperature%1000);
  return 0;
}

int _wifi(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "off") == 0)
    {
      tls_wifi_softap_destroy();
      tls_wifi_disconnect();
      shell_printf("set one shot flg : %d"NEW_LINE,tls_wifi_set_oneshot_flag(0));
      shell_printf("Stopping WIFI interface"NEW_LINE);
    }
    else if(strcmp(argv[1], "error") == 0)
    {
      shell_printf("Error : %s"NEW_LINE, tls_wifi_get_errinfo(tls_wifi_get_errno()));
    }
    else if(strcmp(argv[1], "promiscuous_on") == 0)
    {
      shell_printf("WiFi promiscuous on"NEW_LINE);
      tls_wifi_data_ext_recv_cb_register(&(tls_wifi_data_ext_recv_cb));
    }
    else if(strcmp(argv[1], "promiscuous_off") == 0)
    {
      shell_printf("WiFi promiscuous off"NEW_LINE);
      tls_wifi_data_ext_recv_cb_register(NULL);
    }
    else if(strcmp(argv[1], "mode") == 0)
    {
      shell_printf("Mode is : %d"NEW_LINE, tls_wifi_get_oneshot_flag());
    }
    else if(strcmp(argv[1], "get_ip") == 0)
    {
      struct netif *netif = tls_get_netif();

      if(netif)
      {
        shell_printf("netif 1"NEW_LINE"ip addr : %v"NEW_LINE"netmask : %v"NEW_LINE"gateway : %v"NEW_LINE, netif->ip_addr.addr,
			  netif->netmask.addr,
			  netif->gw.addr);

        if(netif->next)
        {
          shell_printf("netif 2"NEW_LINE"ip addr : %v"NEW_LINE"netmask : %v"NEW_LINE"gateway : %v"NEW_LINE, netif->next->ip_addr.addr,
			    netif->next->netmask.addr,
			    netif->next->gw.addr);
        }
      }
      else
      {
        shell_printf("No netif yet, connect to sta or create soft_ap !"NEW_LINE);
      }
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of wifi actions :"NEW_LINE"off"NEW_LINE"error"NEW_LINE"promiscuous_on"NEW_LINE"promiscuous_off"NEW_LINE"mode"NEW_LINE"get_ip"NEW_LINE);
  }
  return 0;
}

int _wifi_sleep(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "query") == 0)
    {
      shell_printf("power saving : 0x%X, psm chip sleep : 0x%X"NEW_LINE,
      tls_wifi_get_psflag(),
      tls_wifi_get_psm_chipsleep_flag());
    }
    else if(strcmp(argv[1], "set") == 0)
    {
      
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
    
  }
  else
  {
    shell_printf("List of wifi_sleep actions :"NEW_LINE"query"NEW_LINE"set"NEW_LINE);
  }
  return 0;
}

int _pmu(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "sleep") == 0)
    {
      u32 duration = strtoul(argv[2], NULL, 10);
      shell_printf("Going to sleep mode for %u s"NEW_LINE, duration);
      tls_pmu_timer0_start(duration);
      tls_pmu_sleep_start();
      shell_printf("Waking up out of sleep mode"NEW_LINE);
      tls_pmu_timer0_stop();
    }
    else if(strcmp(argv[1], "standby") == 0)
    {
      u32 duration = strtoul(argv[2], NULL, 10);
      shell_printf("Going to standby mode for %u s"NEW_LINE, duration);
      tls_pmu_timer0_start(duration);
      tls_pmu_standby_start();
      shell_printf("Waking up out of standby mode"NEW_LINE);
      tls_pmu_timer0_stop();
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of pmu actions :"NEW_LINE"sleep <duration(s)>"NEW_LINE"standby <duration(s)>"NEW_LINE);
  }
  return 0;
}

int _rtc(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "get") == 0)
    {
      struct tm rtc_time;
      tls_get_rtc(&rtc_time);

      shell_printf("rtc time is :"NEW_LINE"%d:%d:%d %d/%d/%d"NEW_LINE,
      rtc_time.tm_hour,
      rtc_time.tm_min,
      rtc_time.tm_sec,
      rtc_time.tm_mday,
      rtc_time.tm_mon,
      rtc_time.tm_year);
    }
    else if(strcmp(argv[1], "set") == 0)
    {
      struct tm rtc_time;

      rtc_time.tm_hour = strtoul(argv[2], NULL, 10);
      rtc_time.tm_min  = strtoul(argv[3], NULL, 10);
      rtc_time.tm_sec  = strtoul(argv[4], NULL, 10);
      rtc_time.tm_mday  = strtoul(argv[5], NULL, 10);
      rtc_time.tm_mon  = strtoul(argv[6], NULL, 10);
      rtc_time.tm_year  = strtoul(argv[7], NULL, 10);

      shell_printf("Setting rtc to :"NEW_LINE"%d:%d:%d %d/%d/%d"NEW_LINE"isr callback registered !"NEW_LINE,
      rtc_time.tm_hour,
      rtc_time.tm_min,
      rtc_time.tm_sec,
      rtc_time.tm_mday,
      rtc_time.tm_mon,
      rtc_time.tm_year);

      tls_set_rtc(&rtc_time);
      tls_rtc_isr_register(&(tls_rtc_irq_cb), NULL);
    }
    else if(strcmp(argv[1], "alarm") == 0)
    {
      struct tm rtc_time;

      rtc_time.tm_hour = strtoul(argv[2], NULL, 10);
      rtc_time.tm_min  = strtoul(argv[3], NULL, 10);
      rtc_time.tm_sec  = strtoul(argv[4], NULL, 10);
      rtc_time.tm_mday  = strtoul(argv[5], NULL, 10);
      rtc_time.tm_mon  = strtoul(argv[6], NULL, 10);
      rtc_time.tm_year  = strtoul(argv[7], NULL, 10);

      shell_printf("Setting rtc alarm to :"NEW_LINE"%d:%d:%d %d/%d/%d"NEW_LINE,
      rtc_time.tm_hour,
      rtc_time.tm_min,
      rtc_time.tm_sec,
      rtc_time.tm_mday,
      rtc_time.tm_mon,
      rtc_time.tm_year);

      tls_rtc_timer_start(&(rtc_time));
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of rtc actions :"NEW_LINE"get"NEW_LINE"set <h> <m> <s> <d> <m> <y>"NEW_LINE"alarm <h> <m> <s> <d> <m> <y>"NEW_LINE);
  }
  return 0; 
}

int _bluetooth(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "enable") == 0)
    {
      shell_printf("Enabling bluetooth : %d"NEW_LINE, demo_bt_enable());
      //shell_printf("Enabling bluetooth test"NEW_LINE);
    }
    else if(strcmp(argv[1], "disable") == 0)
    {
      shell_printf("Disabling bluetooth : %d"NEW_LINE, demo_bt_destroy());
      //shell_printf("Disabling bluetooth test"NEW_LINE);
    }
    else if(strcmp(argv[1], "start_demo") == 0)
    {
      shell_printf("Starting demo : %d"NEW_LINE"Use a BLE app to find the device"NEW_LINE, demo_ble_server_on());
    }
    else if(strcmp(argv[1], "stop_demo") == 0)
    {
      shell_printf("Stopping demo : %d"NEW_LINE, demo_ble_server_off());
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of bluetooth actions :"NEW_LINE"enable"NEW_LINE"disable"NEW_LINE"start_demo"NEW_LINE"stop_demo"NEW_LINE);
  }
  return 0; 
}

int _telnet(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(argc > 1)
  {
    if(strcmp(argv[1], "dont_echo") == 0)
    {
      shell_printf("Disabling client echo"NEW_LINE"%c%c%c"NEW_LINE, 0xFF, 0xFB, 0x01);
    }
    else
    {
      shell_printf("Unknown %s action"NEW_LINE, argv[0]);
    }
  }
  else
  {
    shell_printf("List of %s actions :"NEW_LINE"dont_echo"NEW_LINE, argv[0]);
  }
  return 0; 
}


int _exit_remote_access(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
  if(disconnect_client())
  {
    shell_printf("Disconnected !"NEW_LINE);
  }
  else
  {
    shell_printf("Not using remote access !"NEW_LINE);
  }
  return 0; 
}

NANO_SHELL_ADD_CMD(bus,
                   _bus,
                   "Command to interact with the SPI bus",
                   "  Use this command to send/receive data from the SPI bus"NEW_LINE);
NANO_SHELL_ADD_CMD(system,
                   _system,
                   "Query system information",
                   "  Use this command to get system information"NEW_LINE);
NANO_SHELL_ADD_CMD(reset,
                   _reset,
                   "Reset the system",
                   "  Use this command reset the system"NEW_LINE);
NANO_SHELL_ADD_CMD(soft_ap,
                   _soft_ap,
                   "Command to control SOFT AP",
                   "  Use this command to control the SOFT AP subsystem"NEW_LINE);
NANO_SHELL_ADD_CMD(station,
                   _station,
                   "Command to control STATION mode",
                   "  Use this command to connect to a WiFi access point"NEW_LINE);
NANO_SHELL_ADD_CMD(wifi,
                   _wifi,
                   "Command to control WIFI interface",
                   "  Use this command to control the WIFI interface"NEW_LINE);
NANO_SHELL_ADD_CMD(cpu_temp,
                   _cpu_temp,
                   "Command to read the CPU temperature",
                   "  Use this command to read the CPU temperature"NEW_LINE);
NANO_SHELL_ADD_CMD(wifi_sleep,
                   _wifi_sleep,
                   "Command to control WiFi sleep",
                   "  Use this command to control WiFi sleep feature"NEW_LINE);
NANO_SHELL_ADD_CMD(pmu,
                   _pmu,
                   "Command to control the power management unit",
                   "  Use this command to control power management unit feature"NEW_LINE);
NANO_SHELL_ADD_CMD(rtc,
                   _rtc,
                   "Command to query and set up the rtc",
                   "  Use this command to interact with the rtc module"NEW_LINE);
NANO_SHELL_ADD_CMD(bluetooth,
                   _bluetooth,
                   "Command to control bluetooth functionality",
                   "  Use this command to interact use bluetooth"NEW_LINE);
NANO_SHELL_ADD_CMD(telnet,
                   _telnet,
                   "Command to set the telnet session up",
                   "  Use this command to set telnet session parameters"NEW_LINE);
NANO_SHELL_ADD_CMD(exit,
                   _exit_remote_access,
                   "Disconnect from Nano-Shell remote access",
                   "  Use this command to disconnect from Nano-Shell remote access"NEW_LINE);
