//_____________________________________________________________________________
//
// Copyright (C) 2018                   Mobsya                   CH-1020 Renens
//_____________________________________________________________________________
//
// PROJECT   XXX
//_____________________________________________________________________________
//
//! \file    main.c
//! \brief   This module provides the useful functions to use the XXX
//!
//! \author  Vincent Gonet
//_____________________________________________________________________________

//-----------------------------------------------------------------------------
// Include Section
//-----------------------------------------------------------------------------

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sys.h"

#include "driver/timer.h"

#include "gpio.h"

//-----------------------------------------------------------------------------
// Constants/Macros Definitions
//-----------------------------------------------------------------------------

#define WIFI_SSID                                "XXX"  // TODO Enter your own SSID
#define WIFI_PASS                                "XXX"  // TODO Enter your own password

#define TIMER_DIVIDER                               16  // Hardware timer clock divider
#define TIMER_SCALE    (TIMER_BASE_CLK / TIMER_DIVIDER) // Convert counter value to seconds

#define GROUND_IR_PIN_NUM                           2u  // Number of ground IR sensors

// IR ground pins
#define IR_PULSE_GROUND_LEFT_PIN                   33u  // Pin of the left ground IR sensor
#define IR_PULSE_GROUND_RIGHT_PIN                  32u  // Pin of the right ground IR sensor

//-----------------------------------------------------------------------------
// Types Definitions
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Exported Global Data
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Private Data
//-----------------------------------------------------------------------------

// FreeRTOS event group to signal when we are connected
static EventGroupHandle_t EventGroup;

static const char *Tag = "main";

SemaphoreHandle_t Timer125usSemaphore = NULL;

static bool WifiIsConnected = false;

static const T_GpioPinConfig PinConfig[GROUND_IR_PIN_NUM] =
{
  // PinNumber                Mode               Resistor             Level            Interrupt
  {IR_PULSE_GROUND_LEFT_PIN,  E_GpioMode_Output, E_GpioResistor_None, E_GpioLevel_Low, E_GpioInterrupt_Disable},
  {IR_PULSE_GROUND_RIGHT_PIN, E_GpioMode_Output, E_GpioResistor_None, E_GpioLevel_Low, E_GpioInterrupt_Disable}
};

TaskHandle_t task;

//-----------------------------------------------------------------------------
// Private Functions Prototypes
//-----------------------------------------------------------------------------

static void Timer_Init(int timer_idx, bool auto_reload, double timer_interval_sec);

static void WIFI_InitNVS(void);

static void WIFI_Init(void);

static bool WIFI_IsConnected(void);

static esp_err_t EventHandler(void *ctx, system_event_t *event);

static void GroundIR_Init(void);

//-----------------------------------------------------------------------------
// Inline Code Definition
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Functions Implementation
//-----------------------------------------------------------------------------

void SensorTask(void* pvParameter)
{
  ESP_LOGI(Tag, "Start Sensor Task");

  // Attempt to create a semaphore
  Timer125usSemaphore = xSemaphoreCreateBinary();

  timer_start(TIMER_GROUP_0, 0);

  while (1)
  {
    if (Timer125usSemaphore != NULL)
    {
      if (xSemaphoreTake(Timer125usSemaphore, portMAX_DELAY) == pdTRUE)  // portMAX_DELAY
      {
        Gpio_TogglePinLevel(IR_PULSE_GROUND_RIGHT_PIN);
      }
      else
      {
    	Gpio_TogglePinLevel(IR_PULSE_GROUND_LEFT_PIN);
      }
    }
  }
}

//_____________________________________________________________________________

void app_main()
{
  static int appCore1 = 0;
  //static int appCore2 = 1;  // FIXME Unused

  WIFI_InitNVS();

  GroundIR_Init();

  WIFI_Init();

  while (!WIFI_IsConnected())
  {}

  Timer_Init(0, true, 0.000125);

  xTaskCreatePinnedToCore(
    SensorTask,  // Function to implement the task
    "sensor",    // Name of the task
    2048,        // Stack size in words
    NULL,        // Task input parameter
    9,           // Priority of the task
    &task,       // Task handle
    appCore1);   // Core where the task should run
}

//_____________________________________________________________________________

void IRAM_ATTR timer_group0_isr(void* para)
{
  BaseType_t higherPriorityTaskWoken = pdFALSE;

  int timer_idx = (int) para;

  // Retrieve the interrupt status and the counter value
  // from the timer that reported the interrupt
  uint32_t intr_status = TIMERG0.int_st_timers.val;
  TIMERG0.hw_timer[timer_idx].update = 1;

  // After the alarm has been triggered, we need enable it again, so it is triggered the next time
  TIMERG0.hw_timer[timer_idx].config.alarm_en = TIMER_ALARM_EN;

  // Clear the interrupt and update the alarm time for the timer with without reload
  if ((intr_status & BIT(timer_idx)) && timer_idx == TIMER_0)
  {
    TIMERG0.int_clr_timers.t0 = 1;

    xSemaphoreGiveFromISR(Timer125usSemaphore, &higherPriorityTaskWoken);

    portYIELD_FROM_ISR();
  }
}

//_____________________________________________________________________________

static void Timer_Init(int timer_idx, bool auto_reload, double timer_interval_sec)
{
  // Select and initialize basic parameters of the timer
  timer_config_t config;

  config.divider     = TIMER_DIVIDER;
  config.counter_dir = TIMER_COUNT_UP;
  config.counter_en  = TIMER_PAUSE;
  config.alarm_en    = TIMER_ALARM_EN;
  config.intr_type   = TIMER_INTR_LEVEL;
  config.auto_reload = auto_reload;

  timer_init(TIMER_GROUP_0, timer_idx, &config);

  // Timer's counter will initially start from value below
  // Also, if auto_reload is set, this value will be automatically reload on alarm
  timer_set_counter_value(TIMER_GROUP_0, timer_idx, 0);

  // Configure the alarm value and the interrupt on alarm
  timer_set_alarm_value(TIMER_GROUP_0, timer_idx, timer_interval_sec * TIMER_SCALE);
  timer_enable_intr(TIMER_GROUP_0, timer_idx);
  timer_isr_register(TIMER_GROUP_0, timer_idx, timer_group0_isr,
                     (void*) timer_idx, ESP_INTR_FLAG_IRAM, NULL);

  ESP_LOGI(Tag, "Timer %d is initialized", timer_idx);
}

//_____________________________________________________________________________

static void WIFI_InitNVS(void)
{
  //Initialize NVS
  esp_err_t ret = nvs_flash_init();

  if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  {
    ESP_ERROR_CHECK(nvs_flash_erase());
	ret = nvs_flash_init();
  }

  ESP_ERROR_CHECK(ret);
}

//_____________________________________________________________________________

static void WIFI_Init(void)
{
  EventGroup = xEventGroupCreate();

  // Initialize the TCP Stack
  tcpip_adapter_init();

  // Initialize the system event handler
  ESP_ERROR_CHECK(esp_event_loop_init(EventHandler, NULL));

  // Configure the WIFI
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_wifi_init(&cfg));

  wifi_config_t wifi_config =
  {
    .sta =
	{
	  .ssid = WIFI_SSID,
	  .password = WIFI_PASS,
	},
  };

  // WIFI as Station Mode (connect to another wifi)
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

  // Configure RAM as the WIFI parameters storage
  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));

  ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
  ESP_ERROR_CHECK(esp_wifi_start());
}

//_____________________________________________________________________________

static bool WIFI_IsConnected(void)
{
  return WifiIsConnected;
}

//_____________________________________________________________________________

static esp_err_t EventHandler(void *ctx, system_event_t *event)
{
  uint16_t apCount = 0;

  switch (event->event_id)
  {
    case SYSTEM_EVENT_STA_START:
	  ESP_LOGI(Tag, "SYSTEM_EVENT_STA_START");
	  ESP_ERROR_CHECK(esp_wifi_connect());
	  break;

	case SYSTEM_EVENT_SCAN_DONE:
	  esp_wifi_scan_get_ap_num(&apCount);

	  printf("Number of access points found: %d\n", event->event_info.scan_done.number);

	  if (apCount > 0u)
	  {
	    wifi_ap_record_t* list = (wifi_ap_record_t*)malloc(sizeof(wifi_ap_record_t) * apCount);
	    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, list));

	    printf("======================================================================\n");
	    printf("             SSID             |    RSSI    |           AUTH           \n");
	    printf("======================================================================\n");

	    for (uint16_t i = 0u; i < apCount; i++)
	    {
	      char* authmode;

	      switch (list[i].authmode)
	      {
	        case WIFI_AUTH_OPEN:
	          authmode = "WIFI_AUTH_OPEN";
	          break;

	        case WIFI_AUTH_WEP:
	          authmode = "WIFI_AUTH_WEP";
	          break;

	        case WIFI_AUTH_WPA_PSK:
	          authmode = "WIFI_AUTH_WPA_PSK";
	          break;

	        case WIFI_AUTH_WPA2_PSK:
	          authmode = "WIFI_AUTH_WPA2_PSK";
	          break;

	        case WIFI_AUTH_WPA_WPA2_PSK:
	          authmode = "WIFI_AUTH_WPA_WPA2_PSK";
	          break;

	        default:
	          authmode = "Unknown";
	          break;
	      }

	      printf("%26.26s    |    % 4d    |    %22.22s\n", list[i].ssid, list[i].rssi, authmode);
	    }
	    free(list);
	    printf("\n\n");
	  }
	  break;

	case SYSTEM_EVENT_STA_GOT_IP:
	  ESP_LOGI(Tag, "SYSTEM_EVENT_STA_GOT_IP");
	  const char* ip = ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip);

	  ESP_LOGI(Tag, "got ip:%s", ip);

	  xEventGroupSetBits(EventGroup, BIT0);
	  WifiIsConnected = true;
	  break;

	case SYSTEM_EVENT_AP_STACONNECTED:
	  ESP_LOGI(Tag, "station:"MACSTR" join, AID=%d",
	           MAC2STR(event->event_info.sta_connected.mac),
	           event->event_info.sta_connected.aid);
	  break;

	case SYSTEM_EVENT_AP_STADISCONNECTED:
	  ESP_LOGI(Tag, "station:"MACSTR"leave, AID=%d",
	           MAC2STR(event->event_info.sta_disconnected.mac),
	           event->event_info.sta_disconnected.aid);
	  break;

	case SYSTEM_EVENT_STA_DISCONNECTED:
	  ESP_LOGI(Tag, "SYSTEM_EVENT_STA_DISCONNECTED");
	  ESP_ERROR_CHECK(esp_wifi_connect());
	  xEventGroupClearBits(EventGroup, BIT0);
	  break;

	default:
	  break;
  }

  return ESP_OK;
}

//_____________________________________________________________________________

static void GroundIR_Init(void)
{
  for (uint16_t index = 0u; index < GROUND_IR_PIN_NUM; index++)
  {
    Gpio_ConfigurePin(&PinConfig[index]);
  }

  ESP_LOGI(Tag, "Ground IR sensors are initialized");
}
