/**
 * Copyright (c) 2024 小明IO
 *
 * 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.
 *
 * Commercial use of this software requires prior written authorization from the Licensor.
 * 请注意：将 ESP-AI 代码用于商业用途需要事先获得许可方的授权。
 * 删除与修改版权属于侵权行为，请尊重作者版权，避免产生不必要的纠纷。
 *
 * @author 小明IO
 * @email  1746809408@qq.com
 * @github https://github.com/wangzongming/esp-ai
 * @websit https://espai.fun
 */

#include "main.h"

bool start_scan_ssids_ed = false;

void ESP_AI::set_config()
{
    String loc_wifi_name = get_local_data("wifi_name");
    String loc_wifi_pwd = get_local_data("wifi_pwd");

    String wifi_name = esp_ai_server.arg("wifi_name");
    String wifi_pwd = esp_ai_server.arg("wifi_pwd");
    String api_key = esp_ai_server.arg("api_key");
    String ext1 = esp_ai_server.arg("ext1");
    String ext2 = esp_ai_server.arg("ext2");
    String ext3 = esp_ai_server.arg("ext3");
    String ext4 = esp_ai_server.arg("ext4");
    String ext5 = esp_ai_server.arg("ext5");
    String ext6 = esp_ai_server.arg("ext6");
    String ext7 = esp_ai_server.arg("ext7");

    DEBUG_PRINTLN(debug, ("================== Set Config ===================="));
    DEBUG_PRINTLN(debug, "设置 wifi_name：" + wifi_name);
    DEBUG_PRINTLN(debug, "设置 wifi_pwd：" + wifi_pwd);
    DEBUG_PRINTLN(debug, "设置 api_key：" + api_key);
    DEBUG_PRINTLN(debug, "设置 ext1：" + ext1);
    DEBUG_PRINTLN(debug, "设置 ext2：" + ext2);
    DEBUG_PRINTLN(debug, "设置 ext3：" + ext3);
    DEBUG_PRINTLN(debug, "设置 ext4：" + ext4);
    DEBUG_PRINTLN(debug, "设置 ext5：" + ext5);
    DEBUG_PRINTLN(debug, "设置 ext6：" + ext6);
    DEBUG_PRINTLN(debug, "设置 ext7：" + ext7);
    DEBUG_PRINTLN(debug, ("==================================================="));
    // return;

    if (loc_wifi_name != wifi_name || loc_wifi_pwd != wifi_pwd)
    { 
        // 为保证可以返回wifi连接状态。开启 Access Point 模式
        WiFi.mode(WIFI_AP_STA);
        WiFi.begin(wifi_name, wifi_pwd);
        DEBUG_PRINT(debug, F("connect wifi ing..."));
    }else{
        DEBUG_PRINT(debug, F("wifi信息并未发生变化，不重新连接wifi。仅进行重新绑定设备。"));
    }

    ap_connect_err = "0";
    int connect_count = 0;
    // 10s 连不上Wifi的话就判定失败
    int try_count = 15;
    while (WiFi.status() != WL_CONNECTED && connect_count <= try_count)
    {
        connect_count++;
        // 内置状态处理
        status_change("0_ing");
        // 设备状态回调
        if (onNetStatusCb != nullptr)
        {
            esp_ai_net_status = "0_ing";
            onNetStatusCb("0_ing");
        }
        delay(250);
        DEBUG_PRINT(debug, ".");
        // 内置状态处理
        status_change("0_ing_after");
        if (onNetStatusCb != nullptr)
        {
            esp_ai_net_status = "0_ing";
            onNetStatusCb("0_ing_after");
        }
        delay(250);
    }

    // 这里连接失败后是无法给用户提示，因为wifi断开了，ip地址也没了，所以只能重启板子
    // 如果失败，连接时间将会 >= 7500ms, 其他情况都只能说明 wifi 连接成功了
    if (WiFi.status() != WL_CONNECTED)
    {
        esp_ai_net_status = "0";
        ap_connect_err = "1";
        DEBUG_PRINTLN(debug, ("配网页面设置 WIFI 连接失败"));
        web_server_setCrossOrigin();
        String json_response = "{\"success\":false,\"message\":\"wifi连接失败，请检查账号密码，将会自动重启设备。\"}";
        esp_ai_server.send(200, "application/json", json_response);
        delay(1000);
        ESP.restart();
        return;
    }

    // 执行绑定设备的方法
    bool is_bind_ok = true;
    if (onBindDeviceCb != nullptr)
    {
        String loc_device_id = get_local_data("device_id");
        String res_str = onBindDeviceCb(loc_device_id, wifi_name, wifi_pwd, ext1, ext2, ext3, ext4, ext5, ext6, ext7);

        JSONVar parse_res = JSON.parse(res_str);
        if (JSON.typeof(parse_res) == "undefined")
        {
            is_bind_ok = false;
            Serial.println("\n[Error] onBindDeviceCb 回调函数返回数据格式有误，请检查 onBindDeviceCb 回调函数是否正常返回数据。");
        }
        is_bind_ok = (bool)parse_res["success"];

        // 配网和绑定设备回调都完毕了
        web_server_setCrossOrigin();
        esp_ai_server.send(200, "application/json", res_str);
    }
    else
    {
        // 到这里 wifi 连接已经成功了，如果是热点下，热点将会没了，这时候无法返回下面这个成功状态 ...
        web_server_setCrossOrigin();
        String json_response = "{\"success\":true,\"message\":\"wifi 连接成功，设备激活成功, 即将重启设备。\"}";
        esp_ai_server.send(200, "application/json", json_response);
    }

    if (is_bind_ok)
    {
        set_local_data("wifi_name", wifi_name);
        set_local_data("wifi_pwd", wifi_pwd);
        set_local_data("api_key", api_key);
        set_local_data("ext1", ext1);
        set_local_data("ext2", ext2);
        set_local_data("ext3", ext3);
        set_local_data("ext4", ext4);
        set_local_data("ext5", ext5);
        set_local_data("ext6", ext6);
        set_local_data("ext7", ext7);
        // 重启板子
        delay(2000);
        ESP.restart();
    }
}

void ESP_AI::get_config()
{
    String loc_device_id = get_local_data("device_id");
    String loc_wifi_name = get_local_data("wifi_name");
    String loc_wifi_pwd = get_local_data("wifi_pwd");
    String loc_api_key = get_local_data("api_key");
    String loc_ext1 = get_local_data("ext1");
    String loc_ext2 = get_local_data("ext2");
    String loc_ext3 = get_local_data("ext3");
    String loc_ext4 = get_local_data("ext4");
    String loc_ext5 = get_local_data("ext5");
    String loc_ext6 = get_local_data("ext6");
    String loc_ext7 = get_local_data("ext7");

    JSONVar json_response;
    JSONVar json_response_data;
    json_response["success"] = true;
    json_response_data["device_id"] = loc_device_id;
    json_response_data["wifi_name"] = loc_wifi_name;
    json_response_data["wifi_pwd"] = loc_wifi_pwd;
    json_response_data["api_key"] = loc_api_key;
    json_response_data["ext1"] = loc_ext1;
    json_response_data["ext2"] = loc_ext2;
    json_response_data["ext3"] = loc_ext3;
    json_response_data["ext4"] = loc_ext4;
    json_response_data["ext5"] = loc_ext5;
    json_response_data["ext6"] = loc_ext6;
    json_response_data["ext7"] = loc_ext7;
    json_response["data"] = json_response_data;
    String send_data = JSON.stringify(json_response);

    web_server_setCrossOrigin();
    esp_ai_server.send(200, "application/json", send_data);
}

// 开始扫描 wifi
void ESP_AI::start_scan_ssids()
{
    JSONVar json_response;
    json_response["success"] = true;
    web_server_setCrossOrigin();
    String send_data = JSON.stringify(json_response);
    esp_ai_server.send(200, "application/json", send_data);

    if (start_scan_ssids_ed)
    {
        Serial.println("已扫描过网络，直接请求即可拿到网络信息");
        return;
    }
    else
    {
        start_scan_ssids_ed = true;
        Serial.println("开始扫描网络");
        // 启动异步扫描，不会阻塞主线程
        WiFi.scanNetworks(true);
    }
}

bool is_scan_over = false;
JSONVar esp_ai_wifi_scan_json_response_data;

void ESP_AI::scan_ssids()
{
    JSONVar json_response;
    json_response["success"] = true;

    // 扫描一次就够了
    if (is_scan_over == false)
    {
        int scanStatus = WiFi.scanComplete();
        if (scanStatus == WIFI_SCAN_RUNNING)
        {
            Serial.println("扫描进行中...");
        }
        else if (scanStatus >= 0)
        {
            is_scan_over = true;
            Serial.printf("\n\n扫描完成，共找到 %d 个网络\n", scanStatus);
            for (int i = 0; i < scanStatus; i++)
            {
                // 国内 2.4ghz 信道 1-14
                JSONVar json_item;
                json_item["ssid"] = WiFi.SSID(i).c_str();
                json_item["rssi"] = WiFi.RSSI(i);
                json_item["channel"] = WiFi.channel(i);
                esp_ai_wifi_scan_json_response_data[i] = json_item;

                Serial.printf("网络 %d: %s, 信号强度: %d dBm\n", i, WiFi.SSID(i).c_str(), WiFi.RSSI(i));
            }
            WiFi.scanDelete();       // 清理扫描结果，准备下次扫描
            WiFi.scanNetworks(true); // 重新开始扫描
        }
        else
        {
            Serial.println("扫描失败");
        }
    }

    Serial.println("获取网络");
    json_response["data"] = esp_ai_wifi_scan_json_response_data;
    web_server_setCrossOrigin();
    String send_data = JSON.stringify(json_response);
    esp_ai_server.send(200, "application/json", send_data);
}

/***
 * 清除配网信息
 * 包括配网页面可配置的所有字段信息
 */
void ESP_AI::clear_config()
{

    set_local_data("wifi_name", "");
    set_local_data("wifi_pwd", "");
    set_local_data("api_key", "");
    set_local_data("ext1", "");
    set_local_data("ext2", "");
    set_local_data("ext3", "");
    set_local_data("ext4", "");
    set_local_data("ext5", "");
    set_local_data("ext6", "");
    set_local_data("ext7", "");

    web_server_setCrossOrigin();
    esp_ai_server.send(200, "application/json", "{\"success\":true,\"message\":\"清除配网信息成功, 即将重启设备。\"}");

    // 重启板子
    delay(2000);
    ESP.restart();
}

void ESP_AI::web_server_setCrossOrigin()
{
    esp_ai_server.sendHeader(F("Access-Control-Allow-Origin"), F("*"));
    esp_ai_server.sendHeader(F("Access-Control-Max-Age"), F("600"));
    esp_ai_server.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
    esp_ai_server.sendHeader(F("Access-Control-Allow-Headers"), F("*"));
};

void ESP_AI::web_server_init()
{
    esp_ai_server.on("/", [this]()
                     { this->web_server_page_index(); });
    esp_ai_server.on("/set_config", [this]()
                     { this->set_config(); });
    esp_ai_server.on("/get_config", [this]()
                     { this->get_config(); });
    esp_ai_server.on("/start_scan_ssids", [this]()
                     { this->start_scan_ssids(); });
    esp_ai_server.on("/scan_ssids", [this]()
                     { this->scan_ssids(); });
    esp_ai_server.on("/clear_config", [this]()
                     { this->clear_config(); });

    esp_ai_server.begin();
}

void ESP_AI::web_server_page_index()
{
    String content = "<!DOCTYPE html><html lang='en'><head> <meta charset='UTF-8'> <meta name='viewport' content='viewport-fit=cover,width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no' /> <title>ESP-AI 配网</title> <style> #ssid { border-radius: 3px; font-size: 14px; padding: 12px 12px; box-sizing: border-box; border: 1px solid #cfcfcf; outline: none; width: 100%; } #loading { position: fixed; z-index: 2; top: 0px; bottom: 0px; left: 0px; right: 0px; background-color: rgba(51, 51, 51, 0.8); text-align: center; color: #fff; font-size: 22px; transition: 0.3s; } #loading .icon { border: 16px solid #f3f3f3; border-radius: 50%; border-top: 16px solid #09aba2; border-bottom: 16px solid #40cea5; width: 120px; height: 120px; margin: auto; position: absolute; bottom: 0px; top: 0px; left: 0px; right: 0px; -webkit-animation: spin 2s linear infinite; animation: spin 2s linear infinite; } @-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style></head><body style='margin: 0;font-size: 16px; color: #333;height: 80vh;position: relative;font-family: 苹方,黑体;overflow: hidden;'> <div style='height: 100%;display: flex;flex-direction: column; justify-content: center;align-items: center;' id='wifi_setting_panel'> <div style='padding-bottom: 18px;text-align: center;'> <img style='width:60px;' src='data:image/png;base64, ' /> <div style='font-size: 22px;color: #757575;'> 为 ESP-AI 设备配网 </div> </div> <div style='width: 100%;padding: 8px 24px;box-sizing: border-box;'> <select id='ssid' style='background: #fff;padding-right: 20px;' placeholder='请选择 wifi'> <option value='' id='scan-ing'>网络扫描中...</option> </select> </div> <div style='width: 100%;padding: 8px 24px;box-sizing: border-box;'> <input id='password' name='password' type='text' placeholder='请输入WIFI密码' style='border-radius: 3px;font-size:14px;padding: 12px 12px;box-sizing: border-box;border: 1px solid #cfcfcf;outline: none;width:100%;'> </div> <div style='width: 100%;padding: 8px 24px;box-sizing: border-box;'> <input id='api_key' name='api_key' type='text' placeholder='请输入产品密钥, 请到开发者平台获取' style='border-radius: 3px;font-size:14px;padding: 12px 12px;box-sizing: border-box;border: 1px solid #cfcfcf;outline: none;width:100%;'> </div> <div style='width: 100%;text-align: right; padding: 6px 24px; padding-bottom: 32px; box-sizing: border-box;'> <button id='submit-btn' style='width:100% ; box-sizing: border-box; border-radius: 3px; padding: 16px 0px;border: none;color: #fff;background-color: transparent; color:#fff; background: linear-gradient(45deg, #40cea5, #09aba2); box-shadow: 0px 0px 3px #ccc;letter-spacing: 2px;'>连接WIFI</button> </div> <div style='font-size: 13px;position: fixed;bottom: 12px;color:#929292'> 设备编码：<span id='device_id'> - </span> </div> </div> <div id='loading'> <div class='icon'></div> </div></body></html><script> var domain = ''; var scan_time = null; var loading = false; function myFetch(apiName, params, cb) { fetch(domain + apiName, { mode: 'cors' }) .then(function (res) { return res.json() }) .then(function (data) { cb && cb(data); }) }; function get_config() { myFetch('/get_config', {}, function (res) { console.log('wifi信息：', res); document.querySelector('#loading').style.display = 'none'; if (res.success) { var data = res.data; document.querySelector('#ssid').value = data.wifi_name; document.querySelector('#password').value = data.wifi_pwd; document.querySelector('#api_key').value = data.api_key; document.querySelector('#device_id').innerHTML = data.device_id; } else { alert('获取配置失败, 请刷新页面重试'); } }); } function start_scan_ssids() { myFetch('/start_scan_ssids', {}, function (res) { if (res.success) { setTimeout(function () { scan_time = setInterval(function () { scan_ssids(); }, 1000); }, 4000); } else { alert('启动网络扫描失败，请刷新页面重试'); } }); } function scan_ssids() { console.log('获取 wifi'); myFetch('/scan_ssids', {}, function (res) { if (res.success) { var data = res.data || []; if (data.length > 0) { document.querySelector('#scan-ing').innerHTML = '请选择wifi...'; }; var selectDom = document.getElementById('ssid'); var options = selectDom.getElementsByTagName('option') || []; var optionsName = []; for (var i = 0; i < options.length; i++) { optionsName.push(options[i].getAttribute('value')); }; data.forEach(function (item) { if (item.ssid && !optionsName.includes(item.ssid)) { var option = document.createElement('option'); option.innerText = item.ssid + '     ('+ (item.channel <= 14 ? '2.4GHz' : '5GHz') +' 信道：'+item.channel+')'; option.setAttribute('value', item.ssid);selectDom.appendChild(option); } }); if (data.length) { clearInterval(scan_time); get_config(); }; } else { alert('启动网络扫描失败，请刷新页面重试'); } }); } function setWifiInfo() { if (loading) { return; }; var wifi_name = document.querySelector('#ssid').value; var wifi_pwd = document.querySelector('#password').value; var api_key = document.querySelector('#api_key').value; if (!wifi_name) { alert('请输入 WIFI 账号哦~'); return; } if (!wifi_pwd) { alert('请输入 WIFI 密码哦~'); return; } loading = true; document.querySelector('#submit-btn').innerHTML = '配网中...'; clearTimeout(window.reloadTimer); window.reloadTimer = setTimeout(function () { alert('未知配网状态，即将重启设备'); setTimeout(function () { window.location.reload(); }, 2000); }, 20000); myFetch( '/set_config?wifi_name=' + encodeURIComponent(wifi_name) + '&wifi_pwd=' + encodeURIComponent(wifi_pwd) + '&api_key=' + api_key, {}, function (res) { clearTimeout(window.reloadTimer); loading = false; document.querySelector('#submit-btn').innerHTML = '连接WIFI'; if (res.success) { alert(res.message); window.close(); } else { alert(res.message); } } ); }; window.onload = function () { start_scan_ssids(); get_config(); document.querySelector('#submit-btn').addEventListener('click', setWifiInfo); }</script>";

    if (String(wifi_config.html_str) != "")
    {
        content = String(wifi_config.html_str);
    }

    web_server_setCrossOrigin(); // 避免跨域
    esp_ai_server.send(200, "text/html", content);
}