#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 "filter_resample.h"
#include "algorithm_stream.h"
#include "raw_stream.h"
#include "board.h"
#include "es8388.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

/************************************************************************************
* Definition Declaration -Start-
*************************************************************************************/
#define I2S_SAMPLE_RATE     16000
#define I2S_CHANNELS        1
#define I2S_BITS            16

#define DEFAULT_REF_DELAY_MS    5
#define ESP_RING_BUFFER_SIZE    256

#define AudioRecordTCP
#define AudioTCPPlay

/************************************************************************************
* Definition Declaration -End-
*************************************************************************************/
/************************************************************************************
* Variable Declaration -Start-
*************************************************************************************/
static const char *TAG = "RAW_TCP_TEST";

/** Mn audio element*/
static audio_element_handle_t element_algo, filter_w;
static audio_element_handle_t tcp_stream_writer, tcp_stream_reader;
static audio_pipeline_handle_t recorder, player;
static ringbuf_handle_t ringbuf_ref;
audio_board_handle_t board_handle;

audio_event_iface_handle_t evt;
audio_event_iface_msg_t msg;

typedef struct {
	int TCP_WR_ST;
	int TCP_RD_ST;
	int I2s_PLY_ST;
	int I2s_RCD_ST;
}Audio_St;

Audio_St AdsT;

/************************************************************************************
* Variable Declaration -End-
*************************************************************************************/
/***********************************************************************************
* Function Declaration -Start-
*************************************************************************************/

/** i2s driver initialize */
static esp_err_t i2s_driver_init(i2s_port_t port, i2s_channel_fmt_t channels, i2s_bits_per_sample_t bits);

/** i2s write callback */
static int i2s_write_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx);

/** i2s read callback */
static int i2s_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx);

/** TCP Write event Handler */
esp_err_t tcp_write_stream_event_handle(tcp_stream_event_msg_t *msg, tcp_stream_status_t state, void *event_ctx);

/** TCP Read event Handler */
esp_err_t tcp_read_stream_event_handle(tcp_stream_event_msg_t *msg, tcp_stream_status_t state, void *event_ctx);

/** I2s Read init */
static esp_err_t player_pipeline_open(Audio_St *PLY_ST);

/** I2s Write init */
static esp_err_t recorder_pipeline_open(Audio_St *RCD_ST);

/** I2s Main init */
void audio_init(void);

/** I2s codec software init */
void audio_codec_sw_init(void);

/** I2s codec hardware init */
void audio_codec_hw_init(void);

/************************************************************************************
* Function Declaration -End-
*************************************************************************************/
/************************************************************************************
* Audio -Start-
*************************************************************************************/
// --------------------------------------------------------------------------------------------
/** i2s driver initialize */
// --------------------------------------------------------------------------------------------
static esp_err_t i2s_driver_init(i2s_port_t port, i2s_channel_fmt_t channels, i2s_bits_per_sample_t bits)
{
    i2s_config_t i2s_cfg = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX,
        .sample_rate = I2S_SAMPLE_RATE,
        .bits_per_sample = bits,
        .channel_format = channels,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2 | ESP_INTR_FLAG_IRAM,
        .dma_buf_count = 8,
        .dma_buf_len = 300,
		.use_apll = false,
        .tx_desc_auto_clear = true,
        .fixed_mclk = 0
    };

    i2s_driver_install(port, &i2s_cfg, 0, NULL);
    board_i2s_pin_t board_i2s_pin = {0};
    i2s_pin_config_t i2s_pin_cfg;
    get_i2s_pins(port, &board_i2s_pin);
    i2s_pin_cfg.bck_io_num = board_i2s_pin.bck_io_num;
    i2s_pin_cfg.ws_io_num = board_i2s_pin.ws_io_num;
    i2s_pin_cfg.data_out_num = board_i2s_pin.data_out_num;
    i2s_pin_cfg.data_in_num = board_i2s_pin.data_in_num;
    i2s_pin_cfg.mck_io_num = board_i2s_pin.mck_io_num;
    i2s_set_pin(port, &i2s_pin_cfg);

    return ESP_OK;
}

// --------------------------------------------------------------------------------------------
/** i2s read callback */
// --------------------------------------------------------------------------------------------
static int i2s_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
    size_t bytes_read = 0;

    int ret = i2s_read(CODEC_ADC_I2S_PORT, buf, len, &bytes_read, wait_time);
    if (ret == ESP_OK) {
    } else {
        ESP_LOGE(TAG, "i2s read failed");
    }

    return bytes_read;
}

// --------------------------------------------------------------------------------------------
/** i2s write callback */
// --------------------------------------------------------------------------------------------
static int i2s_write_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
    size_t bytes_write = 0;

    if (rb_write(ringbuf_ref, buf, len, wait_time) <= 0) {
        ESP_LOGW(TAG, "ringbuf write timeout, wait_time: %d, len: %d", wait_time, len);
    }

    int ret = i2s_write_expand(I2S_NUM_0, buf, len, 16, I2S_BITS, &bytes_write, wait_time);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "i2s write failed");
    }

    return bytes_write;
}

// --------------------------------------------------------------------------------------------
/** TCP Write event Handler */
// --------------------------------------------------------------------------------------------
esp_err_t tcp_write_stream_event_handle(tcp_stream_event_msg_t *msg, tcp_stream_status_t state, void *event_ctx)
{
	Audio_St *RCD_ST = (Audio_St *) event_ctx;

    if (state == TCP_STREAM_STATE_CONNECTED) {
    	ESP_LOGI(TAG, "TCP_WR Connected");
	   	ESP_LOGI(TAG, "RCD_ST_CB, Status: %d", RCD_ST->TCP_WR_ST);
    	RCD_ST->TCP_WR_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;
}

// --------------------------------------------------------------------------------------------
/** TCP Read event Handler */
// --------------------------------------------------------------------------------------------
esp_err_t tcp_read_stream_event_handle(tcp_stream_event_msg_t *msg, tcp_stream_status_t state, void *event_ctx)
{

	Audio_St *PLY_ST = (Audio_St *) event_ctx;

    if (state == TCP_STREAM_STATE_CONNECTED) {
    	ESP_LOGI(TAG, "TCP_RD Connected");
    	ESP_LOGI(TAG, "PLY_ST_CB, Status: %d", PLY_ST->TCP_RD_ST);
    	PLY_ST->TCP_RD_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;
}

// --------------------------------------------------------------------------------------------
/** I2s Read init */
// --------------------------------------------------------------------------------------------
static esp_err_t player_pipeline_open(Audio_St *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, "----> Player_pipeline_open Start <----");
    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.timeout_ms = 3600 * 1000;
	tcp_cfg.task_stack = (16 * 1024);
	tcp_cfg.ext_stack = true;
	tcp_cfg.task_core = 0;
    tcp_cfg.event_handler = tcp_read_stream_event_handle;
    tcp_cfg.event_ctx = (void *) &AdsT;
    tcp_stream_reader = tcp_stream_init(&tcp_cfg);
    AUDIO_NULL_CHECK(TAG, tcp_stream_reader, return 0);

    rsp_filter_cfg_t rsp_cfg_w = DEFAULT_RESAMPLE_FILTER_CONFIG();
    rsp_cfg_w.src_rate = I2S_SAMPLE_RATE;
    rsp_cfg_w.src_ch = 1;
    rsp_cfg_w.dest_rate = I2S_SAMPLE_RATE;
    rsp_cfg_w.dest_ch = 1;
	rsp_cfg_w.task_stack = (16 * 1024);
	rsp_cfg_w.out_rb_size = (4 * 1024);
	rsp_cfg_w.stack_in_ext = true;
	rsp_cfg_w.task_core = 0;
    rsp_cfg_w.complexity = 5;
    filter_w = rsp_filter_init(&rsp_cfg_w);
    AUDIO_NULL_CHECK(TAG, filter_w, return 0);
    audio_element_set_write_cb(filter_w, i2s_write_cb, NULL);
    audio_element_set_output_timeout(filter_w, portMAX_DELAY);


    ESP_LOGI(TAG, "Create i2s stream to write data to codec chip");
    ESP_LOGI(TAG, "Register all elements to audio player pipeline");

    audio_pipeline_register(player, tcp_stream_reader, "TCP_RD");
	audio_pipeline_register(player, filter_w, "filter_w");


    ESP_LOGI(TAG, "Link it together TCP_WR-->filter-->[codec_chip]");

    const char *link_tag[2] = {"TCP_RD", "filter_w"};
    audio_pipeline_link(player, &link_tag[0], 2);

    ESP_LOGI(TAG, "VOIP player has been created");
    ESP_LOGI(TAG, "----> Player_pipeline_open End <----");

    return ESP_OK;
}

// --------------------------------------------------------------------------------------------
/** I2s Write init */
// --------------------------------------------------------------------------------------------
static esp_err_t recorder_pipeline_open(Audio_St *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, " ----> Recorder_pipeline_open Start <----");
    ESP_LOGI(TAG, "Create algorithm stream for aec");

    algorithm_stream_cfg_t algo_config = ALGORITHM_STREAM_CFG_DEFAULT();
	algo_config.input_type = ALGORITHM_STREAM_INPUT_TYPE2;
    algo_config.sample_rate = I2S_SAMPLE_RATE;
	algo_config.task_core = 1;
	algo_config.stack_in_ext = 1;
	algo_config.task_prio = 22;
	algo_config.algo_mask = (ALGORITHM_STREAM_USE_AEC|ALGORITHM_STREAM_USE_AGC|ALGORITHM_STREAM_USE_NS);
	algo_config.rec_linear_factor = 1;
	algo_config.ref_linear_factor = 1;
	algo_config.agc_gain = 20;
	algo_config.task_stack = (16 * 1024);
	algo_config.out_rb_size = (8 * 1024);
    element_algo = algo_stream_init(&algo_config);
    AUDIO_NULL_CHECK(TAG, element_algo, return 0);
    audio_element_set_music_info(element_algo, I2S_SAMPLE_RATE, I2S_CHANNELS, I2S_BITS);
    audio_element_set_read_cb(element_algo, i2s_read_cb, NULL);
    audio_element_set_input_timeout(element_algo, portMAX_DELAY);

    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_PLAYBACK_TCP_PORT;
    tcp_cfg.host = CONFIG_RECORD_TCP_URL;
	tcp_cfg.task_core = 1;
	tcp_cfg.task_prio = 15;
	tcp_cfg.timeout_ms = 3600 * 1000;
	tcp_cfg.task_stack = (16 * 1024);
    tcp_cfg.event_handler = tcp_write_stream_event_handle;
    tcp_cfg.event_ctx = (void *) &AdsT;
    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, element_algo, "algo");
    audio_pipeline_register(recorder, tcp_stream_writer, "TCP_WR");
    ESP_LOGI(TAG, "Link it together Algo-->TCP_WR");

	const char *link_tag[2] = {"algo","TCP_WR"};
    audio_pipeline_link(recorder, &link_tag[0], 2);
    ESP_LOGI(TAG, "VOIP recorder has been created");
    ESP_LOGI(TAG, " ----> Recorder_pipeline_open End <----");

    //Please reference the way of ALGORITHM_STREAM_INPUT_TYPE2 in "algorithm_stream.h"
    ringbuf_ref = rb_create(ESP_RING_BUFFER_SIZE, 1);
    audio_element_set_multi_input_ringbuf(element_algo, ringbuf_ref, 0);

    /* When the playback signal far ahead of the recording signal,
        the playback signal needs to be delayed */
    algo_stream_set_delay(element_algo, ringbuf_ref, DEFAULT_REF_DELAY_MS);

    return ESP_OK;
}

// --------------------------------------------------------------------------------------------
/** I2s Main init */
// --------------------------------------------------------------------------------------------
void audio_init(void){
	audio_codec_hw_init();

	audio_hal_set_volume(board_handle->audio_hal, 95);
	es8388_set_mic_gain(24);

	// POWER_AMP_SET;
	es8388_pa_power(true);
}

// --------------------------------------------------------------------------------------------
/** I2s codec hardware init */
// --------------------------------------------------------------------------------------------
void audio_codec_hw_init(void)
{
    esp_log_level_set("*", ESP_LOG_WARN);
	esp_log_level_set(TAG, ESP_LOG_DEBUG);
	ESP_LOGI(TAG, "Start codec chip");

	i2s_driver_init(I2S_NUM_0, I2S_CHANNEL_FMT_ONLY_LEFT, I2S_BITS);
	board_handle = audio_board_init();
	audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_BOTH, AUDIO_HAL_CTRL_START);
}

// --------------------------------------------------------------------------------------------
/** I2s codec software init */
// --------------------------------------------------------------------------------------------
void audio_codec_sw_init(void)
{
#ifdef AudioTCPPlay
	ESP_LOGI(TAG, "TCP_read, Status: %d; I2s_play, Status %d",\
				AdsT.TCP_RD_ST, AdsT.I2s_PLY_ST);
#endif
#ifdef AudioRecordTCP
	ESP_LOGI(TAG, "I2s_rec, Status: %d; TCP_write, Status %d",\
	 			AdsT.I2s_RCD_ST , AdsT.TCP_WR_ST);
#endif
	ESP_LOGI(TAG, "Start audio_pipeline");
#ifdef AudioTCPPlay
	player_pipeline_open(&AdsT);
#endif
#ifdef AudioRecordTCP
	recorder_pipeline_open(&AdsT);
#endif
	ESP_LOGI(TAG, "Set up  event listener");

	audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
	evt = audio_event_iface_init(&evt_cfg);

#ifdef AudioTCPPlay
	ESP_LOGI(TAG, "Listening event from play pipeline");
	audio_pipeline_set_listener(player, evt);
#endif
#ifdef AudioRecordTCP
	ESP_LOGI(TAG, "Listening event from record pipeline");
	audio_pipeline_set_listener(recorder, evt);
#endif
}

// --------------------------------------------------------------------------------------------
/** App_main */
// --------------------------------------------------------------------------------------------
void app_main(void)
{
	audio_init();

    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);

	audio_codec_sw_init();

	audio_pipeline_run(player);
	audio_pipeline_run(recorder);

	while(1){
		esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);

		if (ret != ESP_OK) {
			ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
			break;
		}

	#ifdef AudioRecordTCP
		/* 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){
			switch((int)msg.data){
				case AEL_STATUS_ERROR_PROCESS:
				case AEL_STATUS_ERROR_OPEN:
				case AEL_STATUS_ERROR_INPUT:
				case AEL_STATUS_ERROR_OUTPUT:
					break;
				case AEL_STATUS_ERROR_UNKNOWN:
				case AEL_STATUS_ERROR_TIMEOUT:
					ESP_LOGI(TAG, "TCP_WR, Status: %d", (int)msg.data);
					break;

				case AEL_STATUS_STATE_PAUSED:
				case AEL_STATUS_STATE_STOPPED:
				case AEL_STATUS_STATE_FINISHED:
				case AEL_STATUS_MOUNTED:
				case AEL_STATUS_UNMOUNTED:
					ESP_LOGI(TAG, "TCP_WR, Status: %d", (int)msg.data);
					break;			
			}
		}

		if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
			msg.source == (void *)element_algo &&
			msg.cmd == AEL_MSG_CMD_REPORT_STATUS){

			ESP_LOGI(TAG, "I2s_RD, Status: %d", (int)msg.data);
			switch((int)msg.data){
				case AEL_STATUS_ERROR_UNKNOWN:
				case AEL_STATUS_ERROR_TIMEOUT:
					ESP_LOGI(TAG, "Algo, Status: %d", (int)msg.data);
					break;			
			}
			ESP_LOGI(TAG, "Algo, Status: %d\r\n", (int)msg.data);
		}

	#endif
	#ifdef AudioTCPPlay
		/* 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){
			switch((int)msg.data){
				case AEL_STATUS_ERROR_PROCESS:
				case AEL_STATUS_ERROR_OPEN:
				case AEL_STATUS_ERROR_INPUT:
				case AEL_STATUS_ERROR_OUTPUT:
					break;
				case AEL_STATUS_ERROR_UNKNOWN:
				case AEL_STATUS_ERROR_TIMEOUT:
					ESP_LOGI(TAG, "TCP_RD, Status: %d", (int)msg.data);
					break;

				case AEL_STATUS_STATE_PAUSED:
				case AEL_STATUS_STATE_STOPPED:
				case AEL_STATUS_STATE_FINISHED:
				case AEL_STATUS_MOUNTED:
				case AEL_STATUS_UNMOUNTED:
					ESP_LOGI(TAG, "TCP_RD, Status: %d", (int)msg.data);
					break;				
			}
		}
		
		/* 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 *)filter_w &&
			msg.cmd == AEL_MSG_CMD_REPORT_STATUS) {

			ESP_LOGI(TAG, "I2s_WR, Status: %d", (int)msg.data);
			switch((int)msg.data){
				case AEL_STATUS_ERROR_UNKNOWN:
				case AEL_STATUS_ERROR_TIMEOUT:
					ESP_LOGI(TAG, "Fltr_w, Status: %d", (int)msg.data);

					break;			
			}
			ESP_LOGI(TAG, "Fltr_w, Status: %d", (int)msg.data);
		}
	#endif
	}
}
/************************************************************************************
* Audio -End-
*************************************************************************************/
