/*
 * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */
/* Modem console example: Custom DCE

   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 <cstring>
#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_dce_module.hpp"
//#include "generate/esp_modem_command_declare.inc"
#include "my_module_dce.hpp"

using namespace esp_modem;
//
// Define preprocessor's forwarding to dce_commands definitions
//
//
// Repeat all declarations and forward to the AT commands defined in esp_modem::dce_commands:: namespace
//
//DECLARE_ALL_COMMAND_APIS(return_type name(...) )
/**
 * @brief Sends the initial AT sequence to sync up with the device
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::sync()
{
    return esp_modem::dce_commands::sync(this);
}
/**
 * @brief Reads the operator name
 * @param[out] name operator name
 * @param[out] act access technology
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_operator_name(std::string &name, int &act)
{
    return esp_modem::dce_commands::get_operator_name(this, name, act);
}
/**
 * @brief Stores current user profile
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::store_profile()
{
    return esp_modem::dce_commands::store_profile(this);
}
/**
 * @brief Sets the supplied PIN code
 * @param[in] pin Pin
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_pin(const std::string &pin)
{
    return esp_modem::dce_commands::set_pin(this, pin);
}
/**
 * @brief Execute the supplied AT command in raw mode (doesn't append '\r' to command, returns everything)
 * @param[in] cmd String command that's send to DTE
 * @param[out] out Raw output from DTE
 * @param[in] pass Pattern in response for the API to return OK
 * @param[in] fail Pattern in response for the API to return FAIL
 * @param[in] timeout AT command timeout in milliseconds
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::at_raw(const std::string &cmd, std::string &out, const std::string &pass, const std::string &fail, int timeout)
{
    return esp_modem::dce_commands::at_raw(this, cmd, out, pass, fail, timeout);
}
/**
 * @brief Execute the supplied AT command
 * @param[in] cmd AT command
 * @param[out] out Command output string
 * @param[in] timeout AT command timeout in milliseconds
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::at(const std::string &cmd, std::string &out, int timeout)
{
    return esp_modem::dce_commands::at(this, cmd, out, timeout);
}
/**
 * @brief Checks if the SIM needs a PIN
 * @param[out] pin_ok true if the SIM card doesn't need a PIN to unlock
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::read_pin(bool &pin_ok)
{
    return esp_modem::dce_commands::read_pin(this, pin_ok);
}
/**
 * @brief Sets echo mode
 * @param[in] echo_on true if echo mode on (repeats the commands)
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_echo(const bool echo_on)
{
    return esp_modem::dce_commands::set_echo(this, echo_on);
}
/**
 * @brief Sets the Txt or Pdu mode for SMS (only txt is supported)
 * @param[in] txt true if txt mode
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::sms_txt_mode(const bool txt)
{
    return esp_modem::dce_commands::sms_txt_mode(this, txt);
}
/**
 * @brief Sets the default (GSM) character set
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::sms_character_set()
{
    return esp_modem::dce_commands::sms_character_set(this);
}
/**
 * @brief Sends SMS message in txt mode
 * @param[in] number Phone number to send the message to
 * @param[in] message Text message to be sent
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::send_sms(const std::string &number, const std::string &message)
{
    return esp_modem::dce_commands::send_sms(this, number, message);
}
/**
 * @brief Resumes data mode (Switches back to the data mode, which was temporarily suspended)
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::resume_data_mode()
{
    return esp_modem::dce_commands::resume_data_mode(this);
}
/**
 * @brief Sets php context
 * @param[in] p1 PdP context struct to setup modem cellular connection
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_pdp_context(PdpContext &pdp)
{
    return esp_modem::dce_commands::set_pdp_context(this, pdp);
}
/**
 * @brief Switches to the command mode
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_command_mode()
{
    return esp_modem::dce_commands::set_command_mode(this);
}
/**
 * @brief Switches to the CMUX mode
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_cmux()
{
    return esp_modem::dce_commands::set_cmux(this);
}
/**
 * @brief Reads the IMSI number
 * @param[out] imsi Module's IMSI number
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_imsi(std::string &imsi)
{
    return esp_modem::dce_commands::get_imsi(this, imsi);
}
/**
 * @brief Reads the IMEI number
 * @param[out] imei Module's IMEI number
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_imei(std::string &imei)
{
    return esp_modem::dce_commands::get_imei(this, imei);
}
/**
 * @brief Reads the module name
 * @param[out] name module name
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_module_name(std::string &name)
{
    return esp_modem::dce_commands::get_module_name(this, name);
}
/**
 * @brief Sets the modem to data mode
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_data_mode()
{
    return esp_modem::dce_commands::set_data_mode(this);
}
/**
 * @brief Get Signal quality
 * @param[out] rssi signal strength indication
 * @param[out] ber channel bit error rate
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_signal_quality(int &rssi, int &ber)
{
    return esp_modem::dce_commands::get_signal_quality(this, rssi, ber);
}
/**
 * @brief Sets HW control flow
 * @param[in] dce_flow 0=none, 2=RTS hw flow control of DCE
 * @param[in] dte_flow 0=none, 2=CTS hw flow control of DTE
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_flow_control(int dce_flow, int dte_flow)
{
    return esp_modem::dce_commands::set_flow_control(this, dce_flow, dte_flow);
}
/**
 * @brief Hangs up current data call
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::hang_up()
{
    return esp_modem::dce_commands::hang_up(this);
}
/**
 * @brief Get voltage levels of modem power up circuitry
 * @param[out] voltage Current status in mV
 * @param[out] bcs charge status (-1-Not available, 0-Not charging, 1-Charging, 2-Charging done)
 * @param[out] bcl 1-100% battery capacity, -1-Not available
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_battery_status(int &voltage, int &bcs, int &bcl)
{
    return esp_modem::dce_commands::get_battery_status(this, voltage, bcs, bcl);
}
/**
 * @brief Power down the module
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::power_down()
{
    return esp_modem::dce_commands::power_down(this);
}
/**
 * @brief Reset the module
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::reset()
{
    return esp_modem::dce_commands::reset(this);
}
/**
 * @brief Configures the baudrate
 * @param[in] baud Desired baud rate of the DTE
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_baud(int baud)
{
    return esp_modem::dce_commands::set_baud(this, baud);
}
/**
 * @brief Force an attempt to connect to a specific operator
 * @param[in] mode mode of attempt
 * mode=0 - automatic
 * mode=1 - manual
 * mode=2 - deregister
 * mode=3 - set format for read operation
 * mode=4 - manual with fallback to automatic
 * @param[in] format what format the operator is given in
 * format=0 - long format
 * format=1 - short format
 * format=2 - numeric
 * @param[in] oper the operator to connect to
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_operator(int mode, int format, const std::string &oper)
{
    return esp_modem::dce_commands::set_operator(this, mode, format, oper);
}
/**
 * @brief Attach or detach from the GPRS service
 * @param[in] state 1-attach 0-detach
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_network_attachment_state(int state)
{
    return esp_modem::dce_commands::set_network_attachment_state(this, state);
}
/**
 * @brief Get network attachment state
 * @param[out] state 1-attached 0-detached
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_network_attachment_state(int &state)
{
    return esp_modem::dce_commands::get_network_attachment_state(this, state);
}
/**
 * @brief What mode the radio should be set to
 * @param[in] state state 1-full 0-minimum ...
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_radio_state(int state)
{
    return esp_modem::dce_commands::set_radio_state(this, state);
}
/**
 * @brief Get current radio state
 * @param[out] state 1-full 0-minimum ...
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_radio_state(int &state)
{
    return esp_modem::dce_commands::get_radio_state(this, state);
}
/**
 * @brief Set network mode
 * @param[in] mode preferred mode
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_network_mode(int mode)
{
    return esp_modem::dce_commands::set_network_mode(this, mode);
}
/**
 * @brief Preferred network mode (CAT-M and/or NB-IoT)
 * @param[in] mode preferred selection
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_preferred_mode(int mode)
{
    return esp_modem::dce_commands::set_preferred_mode(this, mode);
}
/**
 * @brief Set network bands for CAT-M or NB-IoT
 * @param[in] mode CAT-M or NB-IoT
 * @param[in] bands bitmap in hex representing bands
 * @param[in] size size of teh bands bitmap
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_network_bands(const std::string &mode, const int *bands, int size)
{
    return esp_modem::dce_commands::set_network_bands(this, mode, bands, size);
}
/**
 * @brief Show network system mode
 * @param[out] mode current network mode
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_network_system_mode(int &mode)
{
    return esp_modem::dce_commands::get_network_system_mode(this, mode);
}
/**
 * @brief GNSS power control
 * @param[out] mode power mode (0 - off, 1 - on)
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::set_gnss_power_mode(int mode)
{
    return esp_modem::dce_commands::set_gnss_power_mode(this, mode);
}
/**
 * @brief GNSS power control
 * @param[out] mode power mode (0 - off, 1 - on)
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::get_gnss_power_mode(int &mode)
{
    return esp_modem::dce_commands::get_gnss_power_mode(this, mode);
}
/**
 * @brief Configure PSM
 * @param[in] mode psm mode (0 - off, 1 - on, 2 - off & discard stored params)
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::config_psm(int mode, const std::string &tau, const std::string &active_time)
{
    return esp_modem::dce_commands::config_psm(this, mode, tau, active_time);
}
/**
 * @brief Configure CEREG urc
 * @param[in] value
 * value = 0 - Disable network URC
 * value = 1 - Enable network URC
 * value = 2 - Enable network URC with location information
 * value = 3 - Enable network URC with location information and EMM cause
 * value = 4 - Enable network URC with location information and PSM value
 * value = 5 - Enable network URC with location information and PSM value, EMM cause
 */
command_result Shiny::DCE::config_network_registration_urc(int value)
{
    return esp_modem::dce_commands::config_network_registration_urc(this, value);
}
/**
 *  @brief Gets the current network registration state
 *  @param[out] state The current network registration state
 *  state = 0 - Not registered, MT is not currently searching an operator to register to
 *  state = 1 - Registered, home network
 *  state = 2 - Not registered, but MT is currently trying to attach or searching an operator to register to
 *  state = 3 - Registration denied
 *  state = 4 - Unknown
 *  state = 5 - Registered, Roaming
 *  state = 6 - Registered, for SMS only, home network (NB-IoT only)
 *  state = 7 - Registered, for SMS only, roaming (NB-IoT only)
 *  state = 8 - Attached for emergency bearer services only
 *  state = 9 - Registered for CSFB not preferred, home network
 *  state = 10 - Registered for CSFB not preferred, roaming
 */
command_result Shiny::DCE::get_network_registration_state(int &state)
{
    return esp_modem::dce_commands::get_network_registration_state(this, state);
}
/**
 *  @brief Configures the mobile termination error (+CME ERROR)
 *  @param[in] mode The form of the final result code
 *  mode = 0 - Disable, use and send ERROR instead
 *  mode = 1 - Enable, use numeric error values
 *  mode = 2 - Enable, result code and use verbose error values
 */
command_result Shiny::DCE::config_mobile_termination_error(int mode)
{
    return esp_modem::dce_commands::config_mobile_termination_error(this, mode);
}
/**
 * @brief Configure eDRX
 * @param[in] mode
 * mode = 0 - Disable
 * mode = 1 - Enable
 * mode = 2 - Enable + URC
 * mode = 3 - Disable + Reset parameter.
 * @param[in] access_technology
 * act = 0 - ACT is not using eDRX (used in URC)
 * act = 1 - EC-GSM-IoT (A/Gb mode)
 * act = 2 - GSM (A/Gb mode)
 * act = 3 - UTRAN (Iu mode)
 * act = 4 - E-UTRAN (WB-S1 mode)
 * act = 5 - E-UTRAN (NB-S1 mode)
 * @param[in] edrx_value nible string containing encoded eDRX time
 * @param[in] ptw_value nible string containing encoded Paging Time Window
 */
command_result Shiny::DCE::config_edrx(int mode, int access_technology, const std::string &edrx_value)
{
    return esp_modem::dce_commands::config_edrx(this, mode, access_technology, edrx_value);
}
std::unique_ptr<Shiny::DCE> create_shiny_dce(const esp_modem::dce_config *config,
                                             std::shared_ptr<esp_modem::DTE> dte,
                                             esp_netif_t *netif)
{
    return Shiny::Factory::create(config, std::move(dte), netif);
}
/**
 * @brief Definition of the command API, which makes the Shiny::DCE "command-able class"
 * @param cmd Command to send
 * @param got_line Recv line callback
 * @param time_ms timeout in ms
 * @param separator line break separator
 * @return OK, FAIL or TIMEOUT
 */
command_result Shiny::DCE::command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms, const char separator)
{
    if (!handling_urc) {
        return dte->command(cmd, got_line, time_ms, separator);
    }
    handle_cmd = got_line;
    signal.clear((1) | (2));
    esp_modem::DTE_Command command{cmd};
    dte->write(command);
    signal.wait_any((1) | (2), time_ms);
    handle_cmd = nullptr;
    if (signal.is_any((1))) {
        return esp_modem::command_result::OK;
    }
    if (signal.is_any((2))) {
        return esp_modem::command_result::FAIL;
    }
    return esp_modem::command_result::TIMEOUT;
}
/**
 * @brief Handle received data
 *
 * @param data Data received from the device
 * @param len Length of the data
 * @return standard command return code (OK|FAIL|TIMEOUT)
 */
command_result Shiny::DCE::handle_data(uint8_t *data, size_t len)
{
    if (std::memchr(data, '\n', len)) {
        if (handle_urc) {
            handle_urc(data, len);
        }
        if (handle_cmd) {
            auto ret = handle_cmd(data, len);
            if (ret == esp_modem::command_result::TIMEOUT) {
                return command_result::TIMEOUT;
            }
            if (ret == esp_modem::command_result::OK) {
                signal.set((1));
            }
            if (ret == esp_modem::command_result::FAIL) {
                signal.set((2));
            }
        }
    }
    return command_result::TIMEOUT;
}
