#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <WebSocketsClient.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <HTTPUpdate.h>
#include "esp_camera.h"
String version = "1.0";
DynamicJsonDocument jsonBuffer(100);
JsonObject jsonArr;

WebSocketsClient webSocket;

WiFiClient wificlient;
String localServerIP = "192.168.0.254";
String upBinUrl = "http://" + localServerIP + "/tool/ota/englishgolangcpp/dkcam.bin";
// 图像缓冲区大小
const int bufferSize = 1024 * 10;
uint8_t buffer[bufferSize];
String ssid = "webserver";                                             //wifi账号
String passwd = "www.nobiggie.cn";                                           //wifi密码

void hexdump(const void* mem, uint32_t len, uint8_t cols = 16) {
  const uint8_t* src = (const uint8_t*)mem;
  Serial.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
  for (uint32_t i = 0; i < len; i++) {
    if (i % cols == 0) {
      Serial.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
    }
    Serial.printf("%02X ", *src);
    src++;
  }
  Serial.printf("\n");
}

void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {

  switch (type) {
    case WStype_DISCONNECTED:
      break;
    case WStype_CONNECTED:
      Serial.printf("[WSc] Connected to url: %s\n", payload);
      break;
    case WStype_TEXT:
      Serial.printf("[WSc] get text: %s\n", payload);
      deserializeJson(jsonBuffer, payload);
      jsonArr = jsonBuffer.as<JsonObject>();
      if (jsonArr["message"] != "") {
        String user = jsonArr["user"];
        String date = jsonArr["date"];
        String message = jsonArr["message"];
        if (message == "picture") {
          // 拍照并发送图片
          camera_fb_t* fb = esp_camera_fb_get();
          if (!fb) {
            Serial.println("Failed to take picture");
            return;
          }
          size_t jpegSize = fb->len;
          memcpy(buffer, fb->buf, jpegSize);
          esp_camera_fb_return(fb);

          WiFiClient client;
          if (!client.connect(localServerIP.c_str(),80)) {
            Serial.println("Connection failed");
            return;
          }

          // 构造 HTTP 请求头
          client.printf("POST /dkimage HTTP/1.1\r\n");
          client.printf("Host: %s\r\n", localServerIP);
          client.printf("Content-Type: image/jpeg\r\n");
          client.printf("Content-Length: %d\r\n", jpegSize);
          client.printf("\r\n");

          // 发送图片数据
          client.write(buffer, jpegSize);
          client.flush();

          Serial.println("Image sent");
        }
        if (message == "updateBin") {
          while (1) {
            httpUpdate.onStart(update_started);
            httpUpdate.onEnd(update_finished);
            httpUpdate.onProgress(update_progress);
            httpUpdate.onError(update_error);
            t_httpUpdate_return ret = httpUpdate.update(wificlient, upBinUrl);
            //t_httpUpdate_return ret = httpUpdate.update(wificlient, "server", 80, "/file.bin");
            switch (ret) {
              case HTTP_UPDATE_FAILED:
                Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
                break;
              case HTTP_UPDATE_NO_UPDATES:
                Serial.println("HTTP_UPDATE_NO_UPDATES");
                break;
              case HTTP_UPDATE_OK:
                Serial.println("HTTP_UPDATE_OK");
                break;
            }
          }
          espReboot();
        }
      }
      // send message to server
      // webSocket.sendTXT("message here");
      break;
    case WStype_BIN:
      //语音是以 []byte 传输
      Serial.printf("[WSc] get binary length: %u\n", length);
      break;
    case WStype_ERROR:
    case WStype_FRAGMENT_TEXT_START:
    case WStype_FRAGMENT_BIN_START:
    case WStype_FRAGMENT:
    case WStype_FRAGMENT_FIN:
      break;
  }
}

void setup() {
  Serial.begin(115200);
  // 设置相机
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = 5;
  config.pin_d1 = 18;
  config.pin_d2 = 19;
  config.pin_d3 = 21;
  config.pin_d4 = 36;
  config.pin_d5 = 39;
  config.pin_d6 = 34;
  config.pin_d7 = 35;
  config.pin_xclk = 0;
  config.pin_pclk = 22;
  config.pin_vsync = 25;
  config.pin_href = 23;
  config.pin_sscb_sda = 26;
  config.pin_sscb_scl = 27;
  config.pin_pwdn = 32;
  config.pin_reset = -1;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // 初始化相机
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  delay(300);
  // 连接 Wi-Fi
  WiFi.begin("webserver", "www.nobiggie.cn");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
  //通知服务器 我正在重启,验证一下广告牌开关状态 10秒后我启动完成发送广告牌开关状态给我
  webSocket.begin(localServerIP, 80, "/websocket");
  webSocket.onEvent(webSocketEvent);
  webSocket.setAuthorization("user=esp32camkd&passwd=efghijk&room=admin");
  webSocket.setReconnectInterval(5000);
}
void loop() {
  webSocket.loop();
}

void espReboot() {
  ESP.restart();
}

String esp32post(String url) {
  HTTPClient http;
  http.begin(wificlient, url);
  http.setTimeout(3000);
  String postStr = "version=esp32test10";
  char dataBuff[postStr.length()];
  strcpy(dataBuff, postStr.c_str());
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  int httpResponseCode = http.POST(dataBuff);
  if (httpResponseCode > 0) {
    String response = http.getString();
    return response;
  }
  return "";
}



//当升级开始时，打印日志
void update_started() {
  Serial.println("CALLBACK: HTTP update process started");
}
//当升级结束时，打印日志
void update_finished() {
  Serial.println("CALLBACK: HTTP update process finished");
}
//当升级中，打印日志
void update_progress(int cur, int total) {
  Serial.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total);
}
//当升级失败时，打印日志
void update_error(int err) {
  Serial.printf("CALLBACK: HTTP update fatal error code %d\n", err);
}
