/*
 * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */
/* PPPoS Client Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "mqtt_client.h"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "sock_dce.hpp"
#include "esp_log.h"
#include "tcp_transport_mbedtls.h"
#include "tcp_transport_at.h"
#include "sdkconfig.h"

#define USE_TLS CONFIG_EXAMPLE_USE_TLS
#define BROKER_HOST CONFIG_EXAMPLE_BROKER_HOST
#if USE_TLS
#define BROKER_SCHEME "mqtts"
#define BROKER_PORT 8883
#else
#define BROKER_SCHEME "mqtt"
#define BROKER_PORT 1883
#endif
#define BROKER_URL BROKER_SCHEME "://" BROKER_HOST


static const char *TAG = "modem_client";
static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0;
static const int GOT_DATA_BIT = BIT2;
static const int DCE0_DONE = BIT3;
static const int DCE1_DONE = BIT4;

static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRId32, base, event_id);
    esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
        msg_id = esp_mqtt_client_subscribe(client, "/topic/esp-pppos", 0);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        msg_id = esp_mqtt_client_publish(client, "/topic/esp-pppos", "esp32-pppos", 0, 0, 0);
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);
        xEventGroupSetBits(event_group, GOT_DATA_BIT);
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        break;
    default:
        ESP_LOGI(TAG, "MQTT other event id: %d", event->event_id);
        break;
    }
}

static void perform(void* ctx);

extern "C" void app_main(void)
{
    /* Init and register system/core components */
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    // Default to INFO; individual modules use DEBUG for verbose tracing
    esp_log_level_set("*", ESP_LOG_INFO);

    event_group = xEventGroupCreate();

    /* Configure and create the UART DTE */
    esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
    /* setup UART specific configuration based on kconfig options */
    dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN;
    dte_config.uart_config.rx_io_num = CONFIG_EXAMPLE_MODEM_UART_RX_PIN;
    dte_config.uart_config.rts_io_num = CONFIG_EXAMPLE_MODEM_UART_RTS_PIN;
    dte_config.uart_config.cts_io_num = CONFIG_EXAMPLE_MODEM_UART_CTS_PIN;
    dte_config.uart_config.rx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE;
    dte_config.uart_config.tx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_TX_BUFFER_SIZE;
    dte_config.uart_config.event_queue_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE;
    dte_config.task_stack_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE * 2;
    dte_config.task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY;
    dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2;

    auto dte = esp_modem::create_uart_dte(&dte_config);
    assert(dte);

    /* Configure the DCE */
    esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN);

    /* create the DCE and initialize network manually (using AT commands) */
    auto dce = sock_dce::create(&dce_config, dte);
    if (!dce->init()) {
        ESP_LOGE(TAG,  "Failed to setup network");
        return;
    }

    xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr);

    /* create another DCE to serve a new connection */
    auto dce1 = sock_dce::create(&dce_config, dte);
    if (!dce1->init()) {
        ESP_LOGE(TAG,  "Failed to setup network");
        return;
    }
    xTaskCreate(perform, "perform", 4096, dce1.get(), 4, nullptr);

    xEventGroupWaitBits(event_group, DCE0_DONE | DCE1_DONE, pdFALSE, pdTRUE, portMAX_DELAY);
#ifdef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
    // this example does never keeps both DCEs running and in tcp-transport option
    // we don't need a task to run so we exit main and keep DCE's "running"
    vTaskDelay(portMAX_DELAY);
#endif
}

static void perform(void* ctx)
{
    auto dce = static_cast<sock_dce::DCE*>(ctx);
    char mqtt_client_id[] = "MQTT_CLIENT_0";
    static int counter = 0;
    const int id = counter++;
    mqtt_client_id[sizeof(mqtt_client_id) - 2] += id;    // assumes a different client id per each thread
    esp_mqtt_client_config_t mqtt_config = {};
    mqtt_config.session.message_retransmit_timeout = 10000;
    mqtt_config.credentials.client_id = mqtt_client_id;
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
    mqtt_config.broker.address.port = BROKER_PORT + id;
    mqtt_config.broker.address.uri = BROKER_SCHEME "://127.0.0.1";
    dce->start_listening(BROKER_PORT + id);
#else
    mqtt_config.broker.address.port = BROKER_PORT;
    mqtt_config.broker.address.uri = BROKER_URL;
    esp_transport_handle_t at = esp_transport_at_init(dce);
#if USE_TLS
    esp_transport_handle_t ssl = esp_transport_tls_init(at);
    mqtt_config.network.transport = ssl;
#else
    mqtt_config.network.transport = at;
#endif // USE_TLS
#endif // CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
    esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
    esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, nullptr);
    esp_mqtt_client_start(mqtt_client);
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
    if (!dce->connect(BROKER_HOST, BROKER_PORT)) {
        ESP_LOGE(TAG, "Failed to start DCE");
        return;
    }
    while (true) {
        while (dce->perform_sock()) {
            ESP_LOGV(TAG, "...performing");
        }
        ESP_LOGD(TAG, "Loop exit.. retrying");
        // handle disconnections errors
        if (!dce->init()) {
            ESP_LOGE(TAG, "Failed to reinit network");
            return;
        }
        if (!dce->connect(BROKER_HOST, BROKER_PORT)) {
            ESP_LOGI(TAG, "Network reinitialized, retrying");
        }
    }
#endif
    xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE);
    vTaskDelete(nullptr);
}
