// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <protocomm.h>

#include "esp_event.h"
#include "wifi_provisioning/wifi_config.h"

#ifdef __cplusplus
extern "C" {
#endif

ESP_EVENT_DECLARE_BASE(WIFI_PROV_EVENT);

/**
 * @brief   Events generated by manager
 *
 * These events are generated in order of declaration and, for the
 * stretch of time between initialization and de-initialization of
 * the manager, each event is signaled only once
 */
typedef enum {
    /**
     * Emitted when the manager is initialized
     */
    WIFI_PROV_INIT,

    /**
     * Indicates that provisioning has started
     */
    WIFI_PROV_START,

    /**
     * Emitted when Wi-Fi AP credentials are received via `protocomm`
     * endpoint `wifi_config`. The event data in this case is a pointer
     * to the corresponding `wifi_sta_config_t` structure
     */
    WIFI_PROV_CRED_RECV,

    /**
     * Emitted when device fails to connect to the AP of which the
     * credentials were received earlier on event `WIFI_PROV_CRED_RECV`.
     * The event data in this case is a pointer to the disconnection
     * reason code with type `wifi_prov_sta_fail_reason_t`
     */
    WIFI_PROV_CRED_FAIL,

    /**
     * Emitted when device successfully connects to the AP of which the
     * credentials were received earlier on event `WIFI_PROV_CRED_RECV`
     */
    WIFI_PROV_CRED_SUCCESS,

    /**
     * Signals that provisioning service has stopped
     */
    WIFI_PROV_END,

    /**
     * Signals that manager has been de-initialized
     */
    WIFI_PROV_DEINIT,
} wifi_prov_cb_event_t;

typedef void (*wifi_prov_cb_func_t)(void *user_data, wifi_prov_cb_event_t event, void *event_data);

/**
 * @brief   Event handler that is used by the manager while
 *          provisioning service is active
 */
typedef struct {
    /**
     * Callback function to be executed on provisioning events
     */
    wifi_prov_cb_func_t event_cb;

    /**
     * User context data to pass as parameter to callback function
     */
    void *user_data;
} wifi_prov_event_handler_t;

/**
 * @brief Event handler can be set to none if not used
 */
#define WIFI_PROV_EVENT_HANDLER_NONE { \
    .event_cb  = NULL,                 \
    .user_data = NULL                  \
}

/**
 * @brief   Structure for specifying the provisioning scheme to be
 *          followed by the manager
 *
 * @note    Ready to use schemes are available:
 *              - wifi_prov_scheme_ble     : for provisioning over BLE transport + GATT server
 *              - wifi_prov_scheme_softap  : for provisioning over SoftAP transport + HTTP server
 *              - wifi_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging)
 */
typedef struct wifi_prov_scheme {
    /**
     * Function which is to be called by the manager when it is to
     * start the provisioning service associated with a protocomm instance
     * and a scheme specific configuration
     */
    esp_err_t (*prov_start) (protocomm_t *pc, void *config);

    /**
     * Function which is to be called by the manager to stop the
     * provisioning service previously associated with a protocomm instance
     */
    esp_err_t (*prov_stop) (protocomm_t *pc);

    /**
     * Function which is to be called by the manager to generate
     * a new configuration for the provisioning service, that is
     * to be passed to prov_start()
     */
    void *(*new_config) (void);

    /**
     * Function which is to be called by the manager to delete a
     * configuration generated using new_config()
     */
    void (*delete_config) (void *config);

    /**
     * Function which is to be called by the manager to set the
     * service name and key values in the configuration structure
     */
    esp_err_t (*set_config_service) (void *config, const char *service_name, const char *service_key);

    /**
     * Function which is to be called by the manager to set a protocomm endpoint
     * with an identifying name and UUID in the configuration structure
     */
    esp_err_t (*set_config_endpoint) (void *config, const char *endpoint_name, uint16_t uuid);

    /**
     * Sets mode of operation of Wi-Fi during provisioning
     * This is set to :
     * - WIFI_MODE_APSTA for SoftAP transport
     * - WIFI_MODE_STA for BLE transport
     */
    wifi_mode_t wifi_mode;
} wifi_prov_scheme_t;

/**
 * @brief   Structure for specifying the manager configuration
 */
typedef struct {
    /**
     * Provisioning scheme to use. Following schemes are already available:
     *     - wifi_prov_scheme_ble     : for provisioning over BLE transport + GATT server
     *     - wifi_prov_scheme_softap  : for provisioning over SoftAP transport + HTTP server + mDNS (optional)
     *     - wifi_prov_scheme_console : for provisioning over Serial UART transport + Console (for debugging)
     */
    wifi_prov_scheme_t scheme;

    /**
     * Event handler required by the scheme for incorporating scheme specific
     * behavior while provisioning manager is running. Various options may be
     * provided by the scheme for setting this field. Use WIFI_PROV_EVENT_HANDLER_NONE
     * when not used. When using scheme wifi_prov_scheme_ble, the following
     * options are available:
     *     - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM
     *     - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE
     *     - WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT
     */
    wifi_prov_event_handler_t scheme_event_handler;

    /**
     * Event handler that can be set for the purpose of incorporating application
     * specific behavior. Use WIFI_PROV_EVENT_HANDLER_NONE when not used.
     */
    wifi_prov_event_handler_t app_event_handler;
} wifi_prov_mgr_config_t;

/**
 * @brief   Security modes supported by the Provisioning Manager.
 *
 * These are same as the security modes provided by protocomm
 */
typedef enum wifi_prov_security {
    /**
     * No security (plain-text communication)
     */
    WIFI_PROV_SECURITY_0 = 0,

    /**
     * This secure communication mode consists of
     *   X25519 key exchange
     * + proof of possession (pop) based authentication
     * + AES-CTR encryption
     */
    WIFI_PROV_SECURITY_1
} wifi_prov_security_t;

/**
 * @brief   Initialize provisioning manager instance
 *
 * Configures the manager and allocates internal resources
 *
 * Configuration specifies the provisioning scheme (transport)
 * and event handlers
 *
 * Event WIFI_PROV_INIT is emitted right after initialization
 * is complete
 *
 * @param[in] config Configuration structure
 *
 * @return
 *  - ESP_OK      : Success
 *  - ESP_FAIL    : Fail
 */
esp_err_t wifi_prov_mgr_init(wifi_prov_mgr_config_t config);

/**
 * @brief   Stop provisioning (if running) and release
 *          resource used by the manager
 *
 * Event WIFI_PROV_DEINIT is emitted right after de-initialization
 * is finished
 *
 * If provisioning service is  still active when this API is called,
 * it first stops the service, hence emitting WIFI_PROV_END, and
 * then performs the de-initialization
 */
void wifi_prov_mgr_deinit(void);

/**
 * @brief   Checks if device is provisioned
 *
 * This checks if Wi-Fi credentials are present on the NVS
 *
 * The Wi-Fi credentials are assumed to be kept in the same
 * NVS namespace as used by esp_wifi component
 *
 * If one were to call esp_wifi_set_config() directly instead
 * of going through the provisioning process, this function will
 * still yield true (i.e. device will be found to be provisioned)
 *
 * @note    Calling wifi_prov_mgr_start_provisioning() automatically
 *          resets the provision state, irrespective of what the
 *          state was prior to making the call.
 *
 * @param[out] provisioned  True if provisioned, else false
 *
 * @return
 *  - ESP_OK      : Retrieved provision state successfully
 *  - ESP_FAIL    : Wi-Fi not initialized
 *  - ESP_ERR_INVALID_ARG   : Null argument supplied
 *  - ESP_ERR_INVALID_STATE : Manager not initialized
 */
esp_err_t wifi_prov_mgr_is_provisioned(bool *provisioned);

/**
 * @brief   Start provisioning service
 *
 * This starts the provisioning service according to the scheme
 * configured at the time of initialization. For scheme :
 * - wifi_prov_scheme_ble : This starts protocomm_ble, which internally initializes
 *                          BLE transport and starts GATT server for handling
 *                          provisioning requests
 * - wifi_prov_scheme_softap : This activates SoftAP mode of Wi-Fi and starts
 *                          protocomm_httpd, which internally starts an HTTP
 *                          server for handling provisioning requests (If mDNS is
 *                          active it also starts advertising service with type
 *                          _esp_wifi_prov._tcp)
 *
 * Event WIFI_PROV_START is emitted right after provisioning starts without failure
 *
 * @note   This API will start provisioning service even if device is found to be
 *         already provisioned, i.e. wifi_prov_mgr_is_provisioned() yields true
 *
 * @param[in] security      Specify which protocomm security scheme to use :
 *                              - WIFI_PROV_SECURITY_0 : For no security
 *                              - WIFI_PROV_SECURITY_1 : x25519 secure handshake for session
 *                                establishment followed by AES-CTR encryption of provisioning messages
 * @param[in] pop           Pointer to proof of possession string (NULL if not needed). This
 *                          is relevant only for protocomm security 1, in which case it is used
 *                          for authenticating secure session
 * @param[in] service_name  Unique name of the service. This translates to:
 *                              - Wi-Fi SSID when provisioning mode is softAP
 *                              - Device name when provisioning mode is BLE
 * @param[in] service_key   Key required by client to access the service (NULL if not needed).
 *                          This translates to:
 *                              - Wi-Fi password when provisioning mode is softAP
 *                              - ignored when provisioning mode is BLE
 *
 * @return
 *  - ESP_OK      : Provisioning started successfully
 *  - ESP_FAIL    : Failed to start provisioning service
 *  - ESP_ERR_INVALID_STATE : Provisioning manager not initialized or already started
 */
esp_err_t wifi_prov_mgr_start_provisioning(wifi_prov_security_t security, const char *pop,
                                           const char *service_name, const char *service_key);

/**
 * @brief   Stop provisioning service
 *
 * If provisioning service is active, this API will initiate a process to stop
 * the service and return. Once the service actually stops, the event WIFI_PROV_END
 * will be emitted.
 *
 * If wifi_prov_mgr_deinit() is called without calling this API first, it will
 * automatically stop the provisioning service and emit the WIFI_PROV_END, followed
 * by WIFI_PROV_DEINIT, before returning.
 *
 * This API will generally be used along with wifi_prov_mgr_disable_auto_stop()
 * in the scenario when the main application has registered its own endpoints,
 * and wishes that the provisioning service is stopped only when some protocomm
 * command from the client side application is received.
 *
 * Calling this API inside an endpoint handler, with sufficient cleanup_delay,
 * will allow the response / acknowledgment to be sent successfully before the
 * underlying protocomm service is stopped.
 *
 * Cleaup_delay is set when calling wifi_prov_mgr_disable_auto_stop().
 * If not specified, it defaults to 1000ms.
 *
 * For straightforward cases, using this API is usually not necessary as
 * provisioning is stopped automatically once WIFI_PROV_CRED_SUCCESS is emitted.
 * Stopping is delayed (maximum 30 seconds) thus allowing the client side
 * application to query for Wi-Fi state, i.e. after receiving the first query
 * and sending `Wi-Fi state connected` response the service is stopped immediately.
 */
void wifi_prov_mgr_stop_provisioning(void);

/**
 * @brief   Wait for provisioning service to finish
 *
 * Calling this API will block until provisioning service is stopped
 * i.e. till event WIFI_PROV_END is emitted.
 *
 * This will not block if provisioning is not started or not initialized.
 */
void wifi_prov_mgr_wait(void);

/**
 * @brief   Disable auto stopping of provisioning service upon completion
 *
 * By default, once provisioning is complete, the provisioning service is automatically
 * stopped, and all endpoints (along with those registered by main application) are
 * deactivated.
 *
 * This API is useful in the case when main application wishes to close provisioning service
 * only after it receives some protocomm command from the client side app. For example, after
 * connecting to Wi-Fi, the device may want to connect to the cloud, and only once that is
 * successfully, the device is said to be fully configured. But, then it is upto the main
 * application to explicitly call wifi_prov_mgr_stop_provisioning() later when the device is
 * fully configured and the provisioning service is no longer required.
 *
 * @note    This must be called before executing wifi_prov_mgr_start_provisioning()
 *
 * @param[in] cleanup_delay Sets the delay after which the actual cleanup of transport related
 *                          resources is done after a call to wifi_prov_mgr_stop_provisioning()
 *                          returns. Minimum allowed value is 100ms. If not specified, this will
 *                          default to 1000ms.
 *
 * @return
 *  - ESP_OK : Success
 *  - ESP_ERR_INVALID_STATE : Manager not initialized or
 *                            provisioning service already started
 */
esp_err_t wifi_prov_mgr_disable_auto_stop(uint32_t cleanup_delay);

/**
 * @brief   Set application version and capabilities in the JSON data returned by
 *          proto-ver endpoint
 *
 * This function can be called multiple times, to specify information about the various
 * application specific services running on the device, identified by unique labels.
 *
 * The provisioning service itself registers an entry in the JSON data, by the label "prov",
 * containing only provisioning service version and capabilities. Application services should
 * use a label other than "prov" so as not to overwrite this.
 *
 * @note    This must be called before executing wifi_prov_mgr_start_provisioning()
 *
 * @param[in] label   String indicating the application name.
 *
 * @param[in] version String indicating the application version.
 *                    There is no constraint on format.
 *
 * @param[in] capabilities  Array of strings with capabilities.
 *                          These could be used by the client side app to know
 *                          the application registered endpoint capabilities
 *
 * @param[in] total_capabilities  Size of capabilities array
 *
 * @return
 *  - ESP_OK : Success
 *  - ESP_ERR_INVALID_STATE : Manager not initialized or
 *                            provisioning service already started
 *  - ESP_ERR_NO_MEM : Failed to allocate memory for version string
 *  - ESP_ERR_INVALID_ARG : Null argument
 */
esp_err_t wifi_prov_mgr_set_app_info(const char *label, const char *version,
                                     const char**capabilities, size_t total_capabilities);

/**
 * @brief   Create an additional endpoint and allocate internal resources for it
 *
 * This API is to be called by the application if it wants to create an additional
 * endpoint. All additional endpoints will be assigned UUIDs starting from 0xFF54
 * and so on in the order of execution.
 *
 * protocomm handler for the created endpoint is to be registered later using
 * wifi_prov_mgr_endpoint_register() after provisioning has started.
 *
 * @note    This API can only be called BEFORE provisioning is started
 *
 * @note    Additional endpoints can be used for configuring client provided
 *          parameters other than Wi-Fi credentials, that are necessary for the
 *          main application and hence must be set prior to starting the application
 *
 * @note    After session establishment, the additional endpoints must be targeted
 *          first by the client side application before sending Wi-Fi configuration,
 *          because once Wi-Fi configuration finishes the provisioning service is
 *          stopped and hence all endpoints are unregistered
 *
 * @param[in] ep_name  unique name of the endpoint
 *
 * @return
 *  - ESP_OK      : Success
 *  - ESP_FAIL    : Failure
 */
esp_err_t wifi_prov_mgr_endpoint_create(const char *ep_name);

/**
 * @brief   Register a handler for the previously created endpoint
 *
 * This API can be called by the application to register a protocomm handler
 * to any endpoint that was created using wifi_prov_mgr_endpoint_create().
 *
 * @note    This API can only be called AFTER provisioning has started
 *
 * @note    Additional endpoints can be used for configuring client provided
 *          parameters other than Wi-Fi credentials, that are necessary for the
 *          main application and hence must be set prior to starting the application
 *
 * @note    After session establishment, the additional endpoints must be targeted
 *          first by the client side application before sending Wi-Fi configuration,
 *          because once Wi-Fi configuration finishes the provisioning service is
 *          stopped and hence all endpoints are unregistered
 *
 * @param[in] ep_name   Name of the endpoint
 * @param[in] handler   Endpoint handler function
 * @param[in] user_ctx  User data
 *
 * @return
 *  - ESP_OK      : Success
 *  - ESP_FAIL    : Failure
 */
esp_err_t wifi_prov_mgr_endpoint_register(const char *ep_name,
                                          protocomm_req_handler_t handler,
                                          void *user_ctx);

/**
 * @brief   Unregister the handler for an endpoint
 *
 * This API can be called if the application wants to selectively
 * unregister the handler of an endpoint while the provisioning
 * is still in progress.
 *
 * All the endpoint handlers are unregistered automatically when
 * the provisioning stops.
 *
 * @param[in] ep_name  Name of the endpoint
 */
void wifi_prov_mgr_endpoint_unregister(const char *ep_name);

/**
 * @brief   Event handler for provisioning manager
 *
 * This is called from the main event handler and controls the
 * provisioning manager's internal state machine depending on
 * incoming Wi-Fi events
 *
 * @note : This function is DEPRECATED, because events are now
 * handled internally using the event loop library, esp_event.
 * Calling this will do nothing and simply return ESP_OK.
 *
 * @param[in] ctx   Event context data
 * @param[in] event Event info
 *
 * @return
 *  - ESP_OK : Event handled successfully
 */
esp_err_t wifi_prov_mgr_event_handler(void *ctx, system_event_t *event) __attribute__ ((deprecated));

/**
 * @brief   Get state of Wi-Fi Station during provisioning
 *
 * @param[out] state    Pointer to wifi_prov_sta_state_t
 *                      variable to be filled
 *
 * @return
 *  - ESP_OK    : Successfully retrieved Wi-Fi state
 *  - ESP_FAIL  : Provisioning app not running
 */
esp_err_t wifi_prov_mgr_get_wifi_state(wifi_prov_sta_state_t *state);

/**
 * @brief   Get reason code in case of Wi-Fi station
 *          disconnection during provisioning
 *
* @param[out] reason    Pointer to wifi_prov_sta_fail_reason_t
*                       variable to be filled
 *
 * @return
 *  - ESP_OK    : Successfully retrieved Wi-Fi disconnect reason
 *  - ESP_FAIL  : Provisioning app not running
 */
esp_err_t wifi_prov_mgr_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t *reason);

/**
 * @brief   Runs Wi-Fi as Station with the supplied configuration
 *
 * Configures the Wi-Fi station mode to connect to the AP with
 * SSID and password specified in config structure and sets
 * Wi-Fi to run as station.
 *
 * This is automatically called by provisioning service upon
 * receiving new credentials.
 *
 * If credentials are to be supplied to the manager via a
 * different mode other than through protocomm, then this
 * API needs to be called.
 *
 * Event WIFI_PROV_CRED_RECV is emitted after credentials have
 * been applied and Wi-Fi station started
 *
 * @param[in] wifi_cfg  Pointer to Wi-Fi configuration structure
 *
 * @return
 *  - ESP_OK      : Wi-Fi configured and started successfully
 *  - ESP_FAIL    : Failed to set configuration
 */
esp_err_t wifi_prov_mgr_configure_sta(wifi_config_t *wifi_cfg);

/**
 * @brief   Reset Wi-Fi provisioning config
 *
 * Calling this API will restore WiFi stack persistent settings to default values.
 *
 * @return
 *  - ESP_OK      : Reset provisioning config successfully
 *  - ESP_FAIL    : Failed to reset provisioning config
 */
esp_err_t wifi_prov_mgr_reset_provisioning(void);

/**
 * @brief   Reset internal state machine and clear provisioned credentials.
 *
 * This API can be used to restart provisioning in case invalid credentials are entered.
 *
 * @return
 *  - ESP_OK      : Reset provisioning state machine successfully
 *  - ESP_FAIL    : Failed to reset provisioning state machine
 *  - ESP_ERR_INVALID_STATE : Manager not initialized
 */
esp_err_t wifi_prov_mgr_reset_sm_state_on_failure(void);

#ifdef __cplusplus
}
#endif
