#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "esp_timer.h"
#include "baidu_chat_agents_engine.h"

#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0))
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#else
#include "board.h"
#include "periph_wifi.h"
#include "sys/socket.h"
#include "netdb.h"
#include "audio_sys.h"
#include "audio_mem.h"
#define BRTC_DEMO_STATS
// #define BRTC_DEMO_VIDEO
#define BRTC_DEMO_AUDIO
#endif

#ifdef BRTC_DEMO_AUDIO
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_common.h"
#include "i2s_stream.h"
#include "aec_stream.h"
#include "raw_stream.h"
#define BRTC_AUDIO_FRAME_SIZE 640
#define BRTC_AUDIO_RATE 16000
#define BRTC_AUDIO_BITS 16
#define BRTC_AUDIO_CHANNEL 1

static QueueHandle_t audio_queue = NULL;
static int is_first_audio = 1;
static audio_element_handle_t i2s_stream_writer = NULL;
typedef struct {
    uint8_t* data;
    size_t len;
} audio_data_t;
#endif

//==============================================================================
// General Definitions and Global Variables
//==============================================================================
#define BRTC_LOG printf
static bool brtcDemoRunning = false;
static bool brtcDemoCapture = false;
static bool g_brtc_license_abnormal_exit = false;    //true:表示鉴权失败退出
static bool g_brtc_agent_login_failed_exit = false;    //true:表示智能体登陆失败退出
static bool g_brtc_agent_disconnect_exit = false;    //true:表示网络断开退出
/* 使用全局引擎变量，方便各任务访问 */
static BaiduChatAgentEngine *g_engine = NULL;
static uint64_t time_last_asr = 0;

/******************************************************************************
 ******************************************************************************
 **
 ** Wi-Fi Section
 **
 ******************************************************************************
 ******************************************************************************/
#define BRTC_WIFI_SSID      "BD"
#define BRTC_WIFI_PASS      "11111111"

#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0))
static EventGroupHandle_t wifi_event_group = NULL;
const static int CONNECTED_BIT = BIT0;

static esp_err_t brtc_demo_event_handler(void *ctx, system_event_t *event)
{
    switch(event->event_id) {
        case SYSTEM_EVENT_STA_START:
            esp_wifi_connect();
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            esp_wifi_connect();
            xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
            break;
        default:
            break;
    }
    return ESP_OK;
}

void brtc_demo_init_wifi(void) {
    tcpip_adapter_init();
    /* 创建事件组 */
    wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_init(brtc_demo_event_handler, NULL));
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = BRTC_WIFI_SSID,
            .password = BRTC_WIFI_PASS,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
}
#else
void brtc_demo_init_wifi(void)
{
    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);

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0))
    ESP_ERROR_CHECK(esp_netif_init());
#else
    tcpip_adapter_init();
#endif

    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 = {
        .wifi_config.sta.ssid = BRTC_WIFI_SSID,
        .wifi_config.sta.password = BRTC_WIFI_PASS,
    };
    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);
}
#endif

#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0))
void brtc_demo_link_lwip() {
    char addr[64] = "";
    char serv[6] = "3";
    struct addrinfo hints, *res = NULL;
    int af = AF_INET;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = af;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    getaddrinfo(addr, serv, &hints, &res);
}
#endif
/******************************************************************************
 ******************************************************************************
 **
 ** Baidu RTC ChatAgent Section
 **
 ******************************************************************************
 ******************************************************************************/
// Callback implementations
void onErrorCallback(int errCode, const char* errMsg) {
    BRTC_LOG("Error occurred. Code: %d, Message: %s\n", errCode, errMsg);
}

void onCallStateChangeCallback(AGentCallState state) {
    BRTC_LOG("Call state changed: %d\n", state);
    switch (state) {
        case AGENT_LOGIN_SUCCESS:
            BRTC_LOG("AGENT_LOGIN_SUCCESS: %d\n", state);
            break;
        case AGENT_LOGIN_FAIL:
            BRTC_LOG("AGENT_LOGIN_FAIL: %d, please exit and re-login\n", state);
            g_brtc_agent_login_failed_exit = true;
            break;
        default:
            BRTC_LOG("Unknown call state: %d\n", state);
            break;
    }
}

void onConnectionStateChangeCallback(AGentConnectState state) {
    BRTC_LOG("Connection state changed: %d\n", state);
        switch (state) {
        case AGENT_CONNECTION_STATE_CONNECTING:
            BRTC_LOG("AGENT_CONNECTION_STATE_CONNECTING: %d\n", state);
            break;
        case AGENT_CONNECTION_STATE_CONNECTED:
            BRTC_LOG("AGENT_CONNECTION_STATE_CONNECTED: %d\n", state);
            g_brtc_agent_login_failed_exit = true;
            break;
        case AGENT_CONNECTION_STATE_DISCONNECTED:
            BRTC_LOG("AGENT_CONNECTION_STATE_DISCONNECTED: %d, please attempt to reconnect\n", state);
            g_brtc_agent_disconnect_exit = true; 
            // Call to clean up resources before reconnecting by calling baidu_chat_agent_engine_destroy
            // Make sure to call baidu_chat_agent_engine_destroy before brtc_demo_start
            break;
        default:
            BRTC_LOG("Unknown call state: %d\n", state);
            break;
    }
}

void onUserAsrSubtitleCallback(const char* text, bool final) {
    BRTC_LOG("User ASR subtitle: %s (final: %d)\n", text, final); 
    time_last_asr = esp_timer_get_time();
}

void onFunctionCall(const char* id, const char* params) {
    BRTC_LOG("onFunctionCall, id: %s, params: %s\n", id, params);
//调节音量的打印信息：onFunctionCall, id: 1749438356763, params: {"session_id":"1749438356763","content":{"function_name":"adjust_volume","parameter_list":[{"mode":"down"}]}}
//onFunctionCall回调的params是json格式，请基于业务需要进行解析所需要的字段。
}

void onMediaSetup(void) {
    BRTC_LOG("Media setup completed.\n");
    baidu_chat_agent_engine_send_text_to_TTS(g_engine, "我来了");
}

void onAIAgentSubtitle(const char* text, bool final) {
    BRTC_LOG("onAIAgentSubtitle.%s, final:%d\n", text, final);
    time_last_asr = esp_timer_get_time();
}

void onAIAgentSpeaking(bool speeking) {
    BRTC_LOG("onAIAgentSpeaking.\n");
    if (speeking)
        is_first_audio = 1;
}

void onAudioPlayerOp(const char* path, bool start) {
    BRTC_LOG("onAudioPlayerOp. path:%s, start:%d\n", path, start);
}

void onAudioData(const uint8_t* data, size_t len) {
    //BRTC_LOG("Received audio data of length: %zu\n", len);
#ifdef BRTC_DEMO_AUDIO
    if (i2s_stream_writer && len > 0) {
       audio_data_t audio_data;
       audio_data.data = malloc(len);
       memcpy(audio_data.data, data, len);
       audio_data.len = len;
       xQueueSend(audio_queue, &audio_data, portMAX_DELAY);
    }
#endif
}

void onVideoData(const uint8_t* data, size_t len, int width, int height) {
    BRTC_LOG("Received video data of length: %zu, width: %d, height: %d\n", len, width, height);
}

void onLicenseResult (bool result) {
    printf("onLicenseResult: %d\n", result);
    if (!result) {
        BRTC_LOG("onLicenseResult: failed\n");
        g_brtc_license_abnormal_exit = true;
    }
}

void setUserParameters(AgentEngineParams *params) {
    snprintf(params->agent_platform_url, sizeof(params->agent_platform_url), "%s", SERVER_HOST_ONLINE);
    snprintf(params->appid, sizeof(params->appid), "%s", BDCloudDefaultRTCAppID);
    snprintf(params->userId, sizeof(params->userId), "%s", "12345678");
    snprintf(params->cer, sizeof(params->cer), "%s", "./a.cer");
    snprintf(params->workflow, sizeof(params->workflow), "%s", "VoiceChat");
    snprintf(params->license_key, sizeof(params->license_key), "%s", "67a21d44928babcdfb03a8dcbd");

    printf("params->config:%s\n", params->config);
    params->instance_id = 10373;
    params->verbose = false;
    params->enable_local_agent =true;
    params->enable_voice_interrupt = true;
    params->level_voice_interrupt = 80;
    params->AudioInChannel = 1;
    params->AudioInFrequency = 16000;
}

void brtc_demo_init(void) {
    BaiduChatAgentEvent events = {
        .onError = onErrorCallback,
        .onCallStateChange = onCallStateChangeCallback,
        .onConnectionStateChange = onConnectionStateChangeCallback,
        .onUserAsrSubtitle = onUserAsrSubtitleCallback,
        .onAIAgentSubtitle = onAIAgentSubtitle,
        .onAIAgentSpeaking = onAIAgentSpeaking,
        .onFunctionCall = onFunctionCall,
        .onAudioPlayerOp = onAudioPlayerOp,
        .onMediaSetup = onMediaSetup,
        .onAudioData = onAudioData,
        .onVideoData = onVideoData,
        .onLicenseResult = onLicenseResult,
    };

    BaiduChatAgentEngine* engine = baidu_create_chat_agent_engine(&events);
    if (!engine) {
        BRTC_LOG("Engine initialization failed.\n");
        return;
    }
    g_engine = engine; // 保存全局变量供后续使用

    AgentEngineParams agentParams;
    memset(&agentParams, 0, sizeof(agentParams));
    setUserParameters(&agentParams);

    BRTC_LOG("Logging in...\n");
    int result = baidu_chat_agent_engine_init(engine, &agentParams);
    if (result != 200) {
        BRTC_LOG("Failed to log in. Error code: %d\n", result);
        baidu_chat_agent_engine_destroy(engine);
        g_engine = NULL;
        return;
    }

    baidu_chat_agent_engine_call(engine);
}

void brtc_demo_start(void* arg) {
    brtc_demo_init();
    vTaskDelete(NULL);
}

#ifdef BRTC_DEMO_STATS
static bool brtc_stats_looping = false;
static int looping_c = 0;

static void brtc_demo_stats(void *pv) {
    if (time_last_asr == 0) {
        time_last_asr = esp_timer_get_time();
    }

    BRTC_LOG("ASR time_task: %lld %lld %lld  \n", esp_timer_get_time(), time_last_asr, (esp_timer_get_time() - time_last_asr));
    while (1) {
        if (g_brtc_license_abnormal_exit || g_brtc_agent_login_failed_exit ||
            ((time_last_asr != 0) && ((esp_timer_get_time() - time_last_asr) > 300 * 1000000))) {
            if (g_brtc_license_abnormal_exit) {
                BRTC_LOG("brtc license confirm failed ...\n");
            } else if (g_brtc_agent_login_failed_exit) {
                BRTC_LOG("brtc lagent login failed ...\n");
            } else {
                BRTC_LOG("ASR no ai response over 5 min ...\n");
            }
            BaiduChatAgentEngine *agentEngine = g_engine;
            g_engine = NULL;
            baidu_chat_agent_engine_send_text_to_TTS(agentEngine, "ByeBye,我退下了");
            vTaskDelay(10000 / portTICK_PERIOD_MS);
            time_last_asr = 0;
            BRTC_LOG("ASR baidu_chat_agent_engine_destroy ...\n");
            if (agentEngine) {
                BRTC_LOG("baidu_chat_agent_engine_destroy__1\n");
                baidu_chat_agent_engine_destroy(agentEngine);
                agentEngine = NULL;
                BRTC_LOG("baidu_chat_agent_engine_destroy__2\n");
            }
            break;
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0))
    brtc_demo_link_lwip();
#endif

    vTaskDelete(NULL);
}
#endif
/******************************************************************************
 ******************************************************************************
 **
 ** Audio Section
 **
 ******************************************************************************
 ******************************************************************************/
const char* TAG = "main";
#include "audio_sys.h"
static void brtc_task_monitor(void * arg) {
    while (1) {
        vTaskDelay(5000 / portTICK_PERIOD_MS);
       audio_sys_get_real_time_stats();
       AUDIO_MEM_SHOW(TAG);
    }
}

static void audio_render_task(void * arg) {
    while (1) {
        audio_data_t audio_data;
        if (is_first_audio == 1) {
            int n = uxQueueMessagesWaiting(audio_queue);
            if (n < 4) {
                vTaskDelay(pdMS_TO_TICKS(15));
                continue;
            }
            is_first_audio = 0;
        }
        if (xQueueReceive(audio_queue, &audio_data, portMAX_DELAY) == pdPASS) {
            audio_element_output(i2s_stream_writer, (char*)audio_data.data, audio_data.len);
            free(audio_data.data);
        }
    }
}

#ifdef BRTC_DEMO_AUDIO
typedef struct {
    audio_pipeline_handle_t rec_pipe;
    audio_element_handle_t  i2s_read;
    audio_element_handle_t  aec_stream;
    audio_element_handle_t  raw_stream;
    bool                    running;
} rec_res_t;

static rec_res_t rec_res;

static void destroy_record_pipe(void)
{
    if (rec_res.rec_pipe) {
        if (rec_res.running) {
            rec_res.running = false;
            audio_pipeline_stop(rec_res.rec_pipe);
            audio_pipeline_wait_for_stop(rec_res.rec_pipe);
        } else {
            if (rec_res.i2s_read) {
                audio_element_deinit(rec_res.i2s_read);
            }
            if (rec_res.aec_stream) {
                audio_element_deinit(rec_res.aec_stream);
            }
            if (rec_res.raw_stream) {
                audio_element_deinit(rec_res.raw_stream);
            }
        }
        audio_pipeline_terminate(rec_res.rec_pipe);
        audio_pipeline_deinit(rec_res.rec_pipe);
    }
    memset(&rec_res, 0, sizeof(rec_res));
}

#define BREAD_ON_NULL_HD(hd) if (hd == NULL) {break;}

static int create_rec_pipeline(void)
{
    do {
        audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
        rec_res.rec_pipe = audio_pipeline_init(&pipeline_cfg);
        BREAD_ON_NULL_HD(rec_res.rec_pipe);

        i2s_stream_cfg_t i2w_cfg = I2S_STREAM_CFG_DEFAULT();
        i2w_cfg.type = AUDIO_STREAM_READER;
        rec_res.i2s_read = i2s_stream_init(&i2w_cfg);
        BREAD_ON_NULL_HD(rec_res.i2s_read);
        i2s_stream_set_clk(rec_res.i2s_read, BRTC_AUDIO_RATE, 32, 1);

        aec_stream_cfg_t aec_cfg = AEC_STREAM_CFG_DEFAULT();
        aec_cfg.input_format = "RMNM";
        rec_res.aec_stream = aec_stream_init(&aec_cfg);
        BREAD_ON_NULL_HD(rec_res.aec_stream);

        raw_stream_cfg_t raw_cfg = RAW_STREAM_CFG_DEFAULT();
        raw_cfg.type = AUDIO_STREAM_WRITER;
        raw_cfg.out_rb_size = 2 * 1024;
        rec_res.raw_stream = raw_stream_init(&raw_cfg);
        BREAD_ON_NULL_HD(rec_res.raw_stream);
        audio_element_set_output_timeout(rec_res.raw_stream, portMAX_DELAY);

        const char *link_tag[] = {"i2s", "aec", "raw"};
        audio_pipeline_register(rec_res.rec_pipe, rec_res.i2s_read , link_tag[0]);
        audio_pipeline_register(rec_res.rec_pipe, rec_res.aec_stream , link_tag[1]);
        audio_pipeline_register(rec_res.rec_pipe, rec_res.raw_stream , link_tag[2]);
        audio_pipeline_link(rec_res.rec_pipe, &link_tag[0], sizeof(link_tag)/sizeof(link_tag[0]));
        audio_pipeline_run(rec_res.rec_pipe);
        rec_res.running = true;
        return 0;
    } while (0);
    destroy_record_pipe();
    return -1;
}

static void brtc_demo_audio(void * arg) {
    size_t rs = 0;
    char abuf[BRTC_AUDIO_FRAME_SIZE] = {0};

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

    i2s_stream_cfg_t i2w_cfg = I2S_STREAM_CFG_DEFAULT();
    i2w_cfg.type = AUDIO_STREAM_WRITER;
    i2s_std_slot_config_t slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(32, 1);
    slot_cfg.slot_mask = (i2s_std_slot_mask_t)1;
    i2w_cfg.task_stack = -1;
    slot_cfg.data_bit_width = 16;
    slot_cfg.slot_bit_width = 32;
    i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(BRTC_AUDIO_RATE);
    i2w_cfg.std_cfg.clk_cfg = clk_cfg;
    i2w_cfg.std_cfg.slot_cfg = slot_cfg;
    // i2w_cfg.buffer_len = 1024 * 16;
    i2s_stream_writer = i2s_stream_init(&i2w_cfg);

    create_rec_pipeline();

    BRTC_LOG("brtc_demo_audio:%d\n", __LINE__);

    audio_element_run(i2s_stream_writer);
    xTaskCreate(brtc_task_monitor, "brtc_demo_audio", 1024 * 4, NULL, 1, NULL);
    xTaskCreate(audio_render_task, "audio_render_task", 1024 * 4, NULL, 1, NULL);

    if (rec_res.running) {
        while (brtcDemoRunning) {
            rs = raw_stream_read(rec_res.raw_stream, (char *)abuf, BRTC_AUDIO_FRAME_SIZE);
            if (rs > 0 && g_engine) {
                baidu_chat_agent_engine_send_audio(g_engine, (const uint8_t*)abuf, rs);
            }
        }
    }
    destroy_record_pipe();
    audio_element_handle_t writer = i2s_stream_writer;
    i2s_stream_writer = NULL;
    audio_element_deinit(writer);
    BRTC_LOG("brtc_demo_audio:%d\n", __LINE__);
    vTaskDelete(NULL);
    
}
#endif // BRTC_DEMO_AUDIO

/******************************************************************************
 ******************************************************************************
 **
 ** app_main Section: Entry Point and Task Orchestration
 **
 ******************************************************************************
 ******************************************************************************/
void brtc_demo_create_task(TaskFunction_t func, const char* task_name, uint32_t stack_size, UBaseType_t uxPriority) {
    StackType_t *stack_buf = heap_caps_calloc(1, stack_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    if (!stack_buf) {
        BRTC_LOG("Failed to allocate stack for %s\n", task_name);
        return;
    }
    TaskParameters_t params = {
        .pvTaskCode = func,
        .pcName = task_name,
        .usStackDepth = stack_size / sizeof(portSTACK_TYPE),
        .pvParameters = NULL,
        .uxPriority = uxPriority,
        .puxStackBuffer = stack_buf,
        .xRegions = { {0} }
    };
    xTaskCreateRestrictedPinnedToCore(&params, NULL, 1);
}

void app_main(void)
{
    brtcDemoRunning = true;
    brtcDemoCapture = false;

    audio_queue = xQueueCreate(30, sizeof(audio_data_t));

    brtc_demo_init_wifi();
    brtc_demo_create_task(brtc_demo_start, "brtc_demo_start", 1024 * 16, 17);

#ifdef BRTC_DEMO_AUDIO
    xTaskCreatePinnedToCore(brtc_demo_audio, "brtc_demo_audio", 1024 * 4, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(brtc_demo_stats, "brtc_demo_stats", 1024 * 8, NULL, 3, NULL, 0);
#endif
}
