/*RAW TCP
 * Attempt One at custom ADF audio stream raw G711 to from TCP
 */

#include <string.h>
#include "freertos/portmacro.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"

#include "board.h"

#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_common.h"
#include "audio_mem.h"
#include "audio_idf_version.h"

#include "i2s_stream.h"
#include "esp_peripherals.h"
#include "input_key_service.h"
#include "filter_resample.h"

#include "tcp_client_stream.h"
#include "wifi_service.h"
#include "periph_wifi.h"


#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
#include "esp_netif.h"
#else
#include "tcpip_adapter.h"
#endif

static const char *TAG = "RAW_TCP_TEST";

#define I2S_SAMPLE_RATE     16000
#define I2S_CHANNELS        1
#define I2S_BITS            16

#define PLAYBACK_RATE       16000
#define PLAYBACK_CHANNEL    1
#define PLAYBACK_BITS       16

audio_element_handle_t i2s_stream_reader, i2s_stream_writer;
static audio_element_handle_t tcp_stream_writer, tcp_stream_reader;
static audio_pipeline_handle_t recorder, player;

esp_err_t _tcp_write_stream_event_handle(tcp_stream_event_msg_t *msg, tcp_stream_status_t state, void *event_ctx)
{
	int *RCD_ST = (int *) event_ctx;

    if (state == TCP_STREAM_STATE_CONNECTED) {
    	ESP_LOGI(TAG, "TCP_WR Connected");
    	ESP_LOGI(TAG, "RCD_ST_CB, Status: %d", *RCD_ST);
    	*RCD_ST = 1;
}
    if (state == TCP_STREAM_STATE_NONE) {ESP_LOGI(TAG, "TCP_WR None occured");}

	ESP_LOGI(TAG, "TCP_WR Received callback");

    return ESP_OK;
}

esp_err_t _tcp_read_stream_event_handle(tcp_stream_event_msg_t *msg, tcp_stream_status_t state, void *event_ctx)
{

	int *PLY_ST = (int *) event_ctx;

    if (state == TCP_STREAM_STATE_CONNECTED) {
    	ESP_LOGI(TAG, "TCP_RD Connected");
    	ESP_LOGI(TAG, "PLY_ST_CB, Status: %d", *PLY_ST);
    	*PLY_ST = 1;
}
    if (state == TCP_STREAM_STATE_NONE) {ESP_LOGI(TAG, "TCP_RD None occured");}

	ESP_LOGI(TAG, "TCP_RD Received callback");

    return ESP_OK;
}


static esp_err_t player_pipeline_open(int *PLY_ST)
{
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    player = audio_pipeline_init(&pipeline_cfg);
    mem_assert(player);
    AUDIO_NULL_CHECK(TAG, player, return ESP_FAIL);

    ESP_LOGI(TAG, "Create tcp client stream to read data");
    tcp_stream_cfg_t tcp_cfg = TCP_STREAM_CFG_DEFAULT();
    tcp_cfg.type = AUDIO_STREAM_READER;
    tcp_cfg.port = CONFIG_PLAYBACK_TCP_PORT;
    tcp_cfg.host = CONFIG_PLAYBACK_TCP_URL;
    tcp_cfg.event_handler = _tcp_read_stream_event_handle;
    tcp_cfg.event_ctx = (void *) PLY_ST;
    tcp_stream_reader = tcp_stream_init(&tcp_cfg);
    AUDIO_NULL_CHECK(TAG, tcp_stream_reader, return 0);

    ESP_LOGI(TAG, "Create i2s stream to write data to codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_cfg.i2s_config.sample_rate = 16000;
    i2s_stream_writer = i2s_stream_init(&i2s_cfg);
    AUDIO_NULL_CHECK(TAG, i2s_stream_writer, return 0);


    ESP_LOGI(TAG, "Register all elements to audio player pipeline");
    audio_pipeline_register(player, tcp_stream_reader, "TCP_RD");
    audio_pipeline_register(player, i2s_stream_writer, "i2s");

    ESP_LOGI(TAG, "Link it together tcp-->filter_downsample-->i2s");
    const char *link_tag[3] = {"TCP_RD", "i2s"};
    audio_pipeline_link(player, &link_tag[0], 2);

    ESP_LOGI(TAG, "VOIP player has been created");
    return ESP_OK;
}

static esp_err_t recorder_pipeline_open(int *RCD_ST)
{
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    recorder = audio_pipeline_init(&pipeline_cfg);
    mem_assert(recorder);
    AUDIO_NULL_CHECK(TAG, recorder, return ESP_FAIL);

    ESP_LOGI(TAG, "Create i2s stream to read audio data from codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_READER;
    i2s_cfg.uninstall_drv = false;
    i2s_cfg.out_rb_size = 16 * 1024; // Increase buffer to avoid missing data in bad network conditions
    i2s_cfg.task_core = 1;
    i2s_cfg.task_prio = 24;
    i2s_cfg.i2s_config.sample_rate = I2S_SAMPLE_RATE;
    i2s_stream_reader = i2s_stream_init(&i2s_cfg);
    i2s_stream_set_clk(i2s_stream_reader,
    		I2S_SAMPLE_RATE,
			I2S_BITS,
			I2S_CHANNELS);

    ESP_LOGI(TAG, "Create tcp client stream to write data");
    tcp_stream_cfg_t tcp_cfg = TCP_STREAM_CFG_DEFAULT();
    tcp_cfg.type = AUDIO_STREAM_WRITER;
    tcp_cfg.port = CONFIG_RECORD_TCP_PORT;
    tcp_cfg.host = CONFIG_RECORD_TCP_URL;
    tcp_cfg.event_handler = _tcp_write_stream_event_handle;
    tcp_cfg.event_ctx = (void *) RCD_ST;
    tcp_stream_writer = tcp_stream_init(&tcp_cfg);
    AUDIO_NULL_CHECK(TAG, tcp_stream_writer, return 0);

    ESP_LOGI(TAG, "Register all elements to audio recorder pipeline");
    audio_pipeline_register(recorder, i2s_stream_reader, "i2s");
    audio_pipeline_register(recorder, tcp_stream_writer, "TCP_WR");

    ESP_LOGI(TAG, "Link it together I2s_RD-->TCP_WR");
    const char *link_tag[2] = {"i2s","TCP_WR"};
    audio_pipeline_link(recorder, &link_tag[0], 2);

    ESP_LOGI(TAG, "VOIP recorder has been created");
    return ESP_OK;
}

void app_main(void)
{
	int PLY_ST = -1, RCD_ST = -1;
	esp_err_t err = nvs_flash_init();
	if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
		// NVS partition was truncated and needs to be erased
		// Retry nvs_flash_init
		ESP_ERROR_CHECK(nvs_flash_erase());
		err = nvs_flash_init();
	}
	ESP_ERROR_CHECK(esp_netif_init());

	esp_log_level_set("*", ESP_LOG_WARN);
	esp_log_level_set(TAG, ESP_LOG_DEBUG);

	ESP_LOGI(TAG, "Start codec chip");
	audio_board_handle_t board_handle = audio_board_init();
	audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);

	ESP_LOGI(TAG, "RCD_ST, Status: %d", RCD_ST);
	ESP_LOGI(TAG, "PLY_ST, Status: %d", PLY_ST);

	ESP_LOGI(TAG, "Start and wait for Wi-Fi network");
	esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
	esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
	periph_wifi_cfg_t wifi_cfg = {
		.ssid = CONFIG_WIFI_SSID,
		.password = CONFIG_WIFI_PASSWORD,
	};
	esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
	esp_periph_start(set, wifi_handle);
	periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);

	ESP_LOGI(TAG, "Start audio_pipeline");
	player_pipeline_open(&PLY_ST);
	recorder_pipeline_open(&RCD_ST);

	ESP_LOGI(TAG, "Set up  event listener");
	audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
	audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

	ESP_LOGI(TAG, "Listening event from record pipeline");
	audio_pipeline_set_listener(recorder, evt);

	ESP_LOGI(TAG, "Listening event from play pipeline");
	audio_pipeline_set_listener(player, evt);

	ESP_LOGI(TAG, "Listening event from  peripherals");
	audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

	audio_pipeline_run(player);
	audio_pipeline_run(recorder);

	while (1) {
		audio_event_iface_msg_t msg;
		esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);

		if (ret != ESP_OK) {
			ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
			i2s_stream_set_clk(i2s_stream_writer,
									PLAYBACK_RATE,
									PLAYBACK_BITS,
									PLAYBACK_CHANNEL);
			continue;
		}

//		if(RCD_ST == 0){
//			recorder_pipeline_open(&RCD_ST);
//			audio_pipeline_run(recorder);
//		}
//		if(PLY_ST == 0){
//			recorder_pipeline_open(&PLY_ST);
//			audio_pipeline_run(player);
//		}

		/* Resets Recorder pipeline if tcp_stream_writer fails to open connection      */
		/* Note AEL_STATUS_ERROR_PROCESS triggers 1st then only AEL_STATUS_ERROR_OPEN, */
		/* On Next pipeline reset. Code will loop until connection is reestablished    */
		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
			msg.source == (void *)tcp_stream_writer &&
			msg.cmd == AEL_MSG_CMD_REPORT_STATUS &&
			((int)msg.data == AEL_STATUS_ERROR_OPEN ||
			 (int)msg.data == AEL_STATUS_ERROR_PROCESS)){

				ESP_LOGI(TAG, "TCP_WR, Status: %d", (int)msg.data);
				ESP_LOGI(TAG, "RCD_ST, Status: %d", RCD_ST);

				audio_pipeline_stop(recorder);
				audio_pipeline_wait_for_stop(recorder);
				audio_pipeline_reset_elements(recorder);
				audio_pipeline_reset_ringbuffer(recorder);
				audio_pipeline_reset_items_state(recorder);
				audio_pipeline_run(recorder);
		}

		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
			msg.source == (void *)i2s_stream_reader &&
			msg.cmd == AEL_MSG_CMD_REPORT_STATUS){

			ESP_LOGI(TAG, "I2s_RD, Status: %d", (int)msg.data);
		}

		/* Resets Player pipeline if tcp_stream_reader fails to open connection        */
		/* Note AEL_STATUS_ERROR_PROCESS triggers 1st then only AEL_STATUS_ERROR_OPEN, */
		/* On Next pipeline reset. Code will loop until connection is reestablished    */
		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
			msg.source == (void *)tcp_stream_reader	&&
			msg.cmd == AEL_MSG_CMD_REPORT_STATUS &&
			((int)msg.data == AEL_STATUS_ERROR_OPEN ||
			 (int)msg.data == AEL_STATUS_ERROR_PROCESS)) {

				ESP_LOGI(TAG, "TCP_RD, Status: %d", (int)msg.data);
				ESP_LOGI(TAG, "PLY_ST, Status: %d", PLY_ST);

				audio_pipeline_stop(player);
				audio_pipeline_wait_for_stop(player);
				audio_pipeline_reset_elements(player);
				audio_pipeline_reset_ringbuffer(player);
				audio_pipeline_reset_items_state(player);
				audio_pipeline_run(player);
		}

		/* Stop when the last pipeline element (i2s_stream_writer in this case) receives stop event */
		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
			msg.source == (void *)i2s_stream_writer &&
			msg.cmd == AEL_MSG_CMD_REPORT_STATUS) {

			ESP_LOGI(TAG, "I2s_WR, Status: %d", (int)msg.data);
		}
	}

}
