////////////////////////////////////////////////////////////////////////////////
//ライブラリをインストールします
////////////////////////////////////////////////////////////////////////////////
#include <M5Core2.h>          //M5Core2のライブラリをインクルード
#include <BluetoothSerial.h>  //ブルートゥースのライブラリをインクルード
#include <FS.h>               //内部データーベースのライブラリをインクルード
#include <SPIFFS.h>           //SPIFFSライブラリをインクルード
#include <WiFi.h>             // WiFiライブラリをインクルード
#include "logo.h"             //画像ファイルです
#include <HTTPClient.h>       //HTTP通信用ライブラリ
#include <Wire.h>             //I2C用のライブラリ

////////////////////////////////////////////////////////////////////////////////
//RTC関係の部分
////////////////////////////////////////////////////////////////////////////////
#define JST 3600 * 9                                                        // グリニッジ標準時と日本標準時の差
const char* week[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };  // 曜日を定義
RTC_DateTypeDef RTC_DateStruct;                                             //RTC関連の設定
RTC_TimeTypeDef RTC_TimeStruct;                                             //RTC関連の設定
struct tm timeinfo;                                                         //RTC関連の設定

////////////////////////////////////////////////////////////////////////////////
//グローバル変数置き場
////////////////////////////////////////////////////////////////////////////////
int system_status = 1;  //プログラムステータスを1に設定します
//プログラムステータス説明
//0:通常稼働中です
//1:初期設定のための最初の通信を行います
//2:WiFiを設定します
//3:サーバーからUIDを取得
//10:リセット

int system_status_temp = 0;  //プログラムステータスをの控え
int resetbutton = 0;         //リセットボタンの変数
BluetoothSerial SerialBT;    //ブルートゥースに使う変数を定義
String Version = "0.1";      //バージョンを定義

////////////////////////////////////////////////////////////////////////////////
//最初の処理
////////////////////////////////////////////////////////////////////////////////
void setup() {
  M5.begin(true, true, true, true);  //m5を使うためのもの
  Wire.begin();
  SPIFFS.begin();                                        //内部データを利用するための処理
  M5.Axp.ScreenBreath(100);                              // 画面の明るさを100
  M5.Lcd.setRotation(1);                                 // 画面の向きをちょうせい
  M5.Lcd.fillScreen(WHITE);                              // 背景色
  M5.Lcd.setSwapBytes(false);                            // 色がおかしい場合に変更する
  M5.Lcd.startWrite();                                   //画面表示開始
  M5.Lcd.pushImage(0, 0, imgWidth, imgHeight, logo);     //起動ロゴを表示
  M5.Lcd.setTextSize(2);                                 // テキストサイズ倍率(整数で指定)
  M5.Lcd.println("V:" + Version);                        //バージョンを表示
  Serial.println("起動しました");                        //起動時にシリアルに送信(開発用)
  delay(1000);                                           //少し待機
  M5.Lcd.endWrite();                                     //描画を一度終了します
  Serial.println("WiFiコンフィグ探知");                  //内部メモリを確認しシリアルに送信(開発用)
  WiFi.mode(WIFI_STA);                                   //WiFiの動作モードを設定
  if (SPIFFS.exists("/wifi_credentials.txt")) {          //WiFIの設定が済まされているかどうか
    Serial.println("wifi_credentials.txtが存在します");  //内部メモリを確認しシリアルに送信(開発用)
    system_status = 3;                                   //WiFiの設定が存在する場合、settingを0に設定
    connectToWiFi();
  }
  if (SPIFFS.exists("/uuid.txt")) {                            //WiFIの設定が済まされているかどうか
    Serial.println("uuid.txtが存在します");                    //内部メモリを確認しシリアルに送信(開発用)
    system_status = 0;                                         //WiFiの設定が存在する場合、settingを0に設定
    configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");  // NTPサーバとローカルのタイムゾーンを設定
    setNTP2RTC();                                              // RTCに現在時刻を上書きする
  }                                                            //if文終了
  Serial.println("コンフィグ探知終了");                        //内部メモリを確認しシリアルに送信(開発用)
  if (system_status == 1) {                                    //初回起動に動作させるもの
    M5.Lcd.pushImage(0, 0, imgWidth, imgHeight, QR);           //設定用のQRコードを表示
    Serial.println("ロゴ表示完了");                            //ロゴ表示完了をシリアルに送信(開発用)
    M5.Lcd.endWrite();                                         //描画を一度終了します
    SerialBT.begin("BambooCompost");                           //ブルートゥースのデバイス名を定義しています
    Serial.println("ブルートゥースを開始しました");            //ブルートゥース接続開始をシリアルに送信(開発用)
  }                                                            //if文終了
}  //void setup終了

////////////////////////////////////////////////////////////////////////////////
//メインのループです
////////////////////////////////////////////////////////////////////////////////
void loop() {
  M5.update();
  //初回設定用のコード
  if (system_status == 1) {  //プログラムステータス1の時に実行(void BTSettingの部分を実行します)
    BTSetting();             //ブルートゥースで初回コネクト
  }                          //if文終了
  if (system_status == 2) {  //プログラムステータス2の時に実行
    receiveWiFiSettings();   //WiFiのせってい(void receiveWiFiSettingsの部分を実行します)
  }                          //if文終了
  if (system_status == 3) {  //プログラムステータス3の時に実行
    getUUID();               //UUIDをサーバーから取得し、時刻を調整します
  }                          //if文終了

  ResetButton();
}  //void loop終了

////////////////////////////////////////////////////////////////////////////////
//WiFi関係
////////////////////////////////////////////////////////////////////////////////
//WiFiに接続
void connectToWiFi() {
  String ssid;      //ローカル変数を定義
  String password;  //ローカル変数を定義
  // WiFi認証情報を読み取る
  readWiFiCredentials(ssid, password);                              //内部データからWiFiの情報を取りに行きます
  Serial.println("SSID: " + ssid);                                  //シリアルでSSIDを送信(開発用)
  Serial.println("Password: " + password);                          //シリアルでパスワードを送信(開発用)
  Serial.println("SSID Length: " + String(ssid.length()));          //シリアルでSSIDの長さを送信(開発用)
  Serial.println("Password Length: " + String(password.length()));  //シリアルでパスワードの長さを送信(開発用)
  // WiFiに接続する
  WiFi.begin(ssid, password);                               //WiFiをスタート
  Serial.println("Connecting to WiFi...");                  //シリアルで接続開始をつうち(開発用)
  int attempts = 0;                                         //ローカル変数を定義
  while (WiFi.status() != WL_CONNECTED && attempts < 30) {  //WiFi再接続の処理
    delay(1000);                                            //1秒待機
    Serial.println("Connecting to WiFi...");                //シリアルで接続開始をつうち(開発用)
    attempts++;                                             //WiFi再接続の処理
  }                                                         //while文終了
  if (WiFi.status() == WL_CONNECTED) {                      //WiFiがつながっているときのしょり
    Serial.println("Connected to WiFi");                    //シリアルで接続をつうち(開発用)
    Serial.println(WiFi.localIP());                         //シリアルでIPをつうち(開発用)
    Serial.println(WiFi.dnsIP());                           //シリアルでDNSサーバーのIPをつうち(開発用)
  } else {                                                  //if文「または」
    Serial.println("Failed to connect to WiFi");            //シリアルで接続失敗をつうち(開発用)
  }                                                         //if文終了
  delay(10);                                                //0.01秒待つ
}  //void connectToWiFi終了

//WiFiの情報を内部データベースから取得
void readWiFiCredentials(String& ssid, String& password) {
  File file = SPIFFS.open("/wifi_credentials.txt", "r");           //WiFiの設定ファイルを開く
  if (file) {                                                      // ファイルチェック
    while (file.available()) {                                     // ファイルチェック
      String line = file.readStringUntil('\n');                    // 行から前後の余分な空白を取り除く
      line.trim();                                                 // 行から前後の余分な空白を取り除く
      int separatorIndex = line.indexOf('=');                      // 変数定義
      if (separatorIndex != -1) {                                  //WiFi設定取得関係の処理
        String key = line.substring(0, separatorIndex);            //WiFi設定取得関係の処理
        String value = line.substring(separatorIndex + 1);         //WiFi設定取得関係の処理
        if (key.equals("SSID")) {                                  //WiFi設定取得関係の処理
          ssid = value;                                            //WiFi設定取得関係の処理
        } else if (key.equals("Password")) {                       //WiFi設定取得関係の処理
          password = value;                                        //WiFi設定取得関係の処理
        }                                                          //if文終了
      }                                                            //if文終了
    }                                                              //while文終了
    file.close();                                                  //ファイルを閉じる
    Serial.println("WiFi credentials read successfully.");         //シリアルで正しく設定されたことを通知
  } else {                                                         //if文「または」
    Serial.println("Error reading WiFi credentials from SPIFFS");  //設定でエラーが起きたことをシリアルで通知
  }                                                                //if文終了
}  //void readWiFiCredentials文終了

//リセットの処理
void ResetButton() {
  if (M5.BtnC.isPressed()) {                             //ボタンの感知
    delay(10);                                           //ウェイト
    resetbutton += 1;                                    //ボタンが押されている場合は値が増加します
  } else {                                               //if文の「または~」
    if (resetbutton > 0) {                               //値が0を下回らないように
      resetbutton -= 1;                                  //ボタンが押されていない場合は値が減少します
    }                                                    //if文終了
  }                                                      //if文終了
  if (resetbutton == 300) {                              //リセットボタンの値が一定数増えたら実行
    system_status_temp = system_status;                  //システムステータスを控える
    M5.Lcd.pushImage(0, 0, imgWidth, imgHeight, reset);  //警告画面を表示
    M5.Lcd.endWrite();                                   //描画を一度終了します
    system_status = 10;                                  //システムステータスを10に
  }                                                      //if文終了
  if (system_status == 10) {                             //システムステータスが10の時に実行
    if (resetbutton > 600) {                             //リセットボタンの値が一定数増えたら実行
      M5.Lcd.setTextSize(2);                             // 文字サイズを設定
      M5.Lcd.fillScreen(TFT_BLACK);                      //黒で塗りつぶす
      M5.Lcd.println("Resetting");                       //文字列を表示
      M5.Lcd.println("It may take some time");           //文字列を表示
      M5.Lcd.println("The system will restart");         //文字列を表示
      SPIFFS.format();                                   //内部データを削除
      ESP.restart();                                     //再起動
    }                                                    //if文終了
    if (resetbutton < 290) {                             //リセットボタンの値が一定数増えたら実行
      system_status == system_status_temp;               //控えたステータスを復元
    }                                                    //if文終了
  }                                                      //if文終了
}  //void ResetButton終了
////////////////////////////////////////////////////////////////////////////////
// 初期設定関連
////////////////////////////////////////////////////////////////////////////////
//ブルートゥースで最初のコネクトを行います
void BTSetting() {
  if (SerialBT.available()) {                              //初回ブルートゥースコネクトの処理
    String receivedData = SerialBT.readStringUntil('\n');  //受け取ったデータの処理
    receivedData.trim();                                   //受け取ったデータの処理
    if (receivedData.equals("connect")) {                  //接続先から送信された文字列が「connect」な場合に処理を実行します
      receivedData.trim();                                 //受け取ったデータの処理
      char* macAddress = getMAC();                         //WiFiのMacアドレスを取得します(char* getMACを実行します)
      SerialBT.println(macAddress);                        //接続先の危機に自身のMacアドレスを送信します
      system_status = 2;                                   //プログラムステータスを2に移行
      Serial.println("コネクト");                          //ブルートゥース接続をシリアルに送信(開発用)
    }                                                      //if文終了
  }                                                        //if文終了
}  //void BTSetting終了
// WiFiの設定情報を受信
void receiveWiFiSettings() {
  String wifiSettings = SerialBT.readStringUntil('\n');  //受け取ったデータの処理
  wifiSettings.trim();                                   //受け取ったデータの処理

  // カンマで分割
  int separatorIndex = wifiSettings.indexOf(',');                  //WiFiのパスワードとSSID(WiFiの名前)を受信
  if (separatorIndex != -1) {                                      //チェック
    String ssid = wifiSettings.substring(0, separatorIndex);       //パスワードとSSIDを分割
    String password = wifiSettings.substring(separatorIndex + 1);  //パスワードとSSIDを分割

    // SPIFFSにWiFiの設定情報を保存
    saveWiFiCredentials(ssid, password);  //内部データーベースに設定内容を保存します(void saveWiFiCredentialsの部分を実行)
    system_status = 3;                    //プログラムステータスを3に移行
  }                                       //if文終了
}  //void receiveWiFiSettings文終了
//内部データにWiFiのデータを書き込む
void saveWiFiCredentials(const String& ssid, const String& password) {  // SPIFFSにファイルを書き込み
  SPIFFS.remove("/wifi_credentials.txt");
  File file = SPIFFS.open("/wifi_credentials.txt", "w");        //wifi_credentials.txtというテキストに書き込みます
  if (file) {                                                   // ファイルチェック
    file.println("SSID=" + ssid);                               // SPIFFSにファイルを書き込み
    file.println("Password=" + password);                       // SPIFFSにファイルを書き込み
    file.close();                                               //ファイルを閉じる
    Serial.println("WiFi credentials saved successfully.");     //書き込めたことをシリアルに送信(開発用)
    ESP.restart();                                              //再起動
  } else {                                                      //if文の「または~」
    Serial.println("Error saving WiFi credentials to SPIFFS");  //エラーが出たときにシリアルに送信(開発用)
  }                                                             //if文終了
}  //void saveWiFiCredentials終了
//WiFiのMacアドレスを取得します
char* getMAC() {
  uint8_t baseMac[6];                                                                                                            //WiFiのMacアドレスを取得します
  esp_read_mac(baseMac, ESP_MAC_WIFI_STA);                                                                                       //WiFiのMacアドレスを取得します
  static char baseMacChr[18] = { 0 };                                                                                            // 静的な変数として宣言
  sprintf(baseMacChr, "%02X:%02X:%02X:%02X:%02X:%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]);  //WiFiのMacアドレスを取得します
  Serial.println(baseMacChr);                                                                                                    //WiFiのMacアドレスをシリアルに送信(開発用)
  return baseMacChr;                                                                                                             // baseMacChr のアドレスを返す
}  //char* getMAC終了
//UUIDを取得
void getUUID() {
  // HTTP通信の設定
  HTTPClient http;                                                 //httpクライアントを定義
  http.begin("https://ecohero.reach-world.net/M5API/getuid.php");  //サーバーのアドレス
  int httpCode = http.GET();                                       //サーバーに接続

  // HTTP応答の処理
  if (httpCode > 0) {                       //内容があるか確認
    String payload = http.getString();      //変数にGETの内容代入
    Serial.println("HTTP GET successful");  //シリアルで通信成功を通知(開発用)

    // SPIFFSに書き込み
    File file = SPIFFS.open("/uuid.txt", "w");
    if (file) {                                  //ファイルチェック
      file.print(payload);                       //ファイルに書き込み
      file.close();                              //ファイルを閉じる
      Serial.println("Data written to SPIFFS");  //シリアルで書き込み成功を通知(開発用)
      system_status = 4;                         //システムステータスを4に移行
    } else {                                     //if文「または」
      Serial.println("Failed to open file for writing");
    }                                                      //if文終了
  } else {                                                 //if文「または」
    Serial.print("HTTP GET failed with error code: ");     //シリアルでエラーを通知(開発用)
    Serial.println("HTTP GET failed");                     //シリアルでエラーを通知(開発用)
    Serial.println(http.errorToString(httpCode).c_str());  //シリアルでエラーを通知(開発用)
  }                                                        //if文終了
  http.end();                                              //httpを終了する
}
////////////////////////////////////////////////////////////////////////////////
// 取得した時刻をRTCに設定
////////////////////////////////////////////////////////////////////////////////
//NTPに接続
void getTimeFromNTP() {
  configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");  //NTPサーバーの設定
  while (!getLocalTime(&timeinfo)) {                         //繰り返し
    delay(1000);                                             //一秒待つ
  }
}
//RTCに書き込み
void setNTP2RTC() {
  getTimeFromNTP();         //RTC関係
  getLocalTime(&timeinfo);  //RTC関係
  // RTCを読み込む
  M5.Rtc.GetTime(&RTC_TimeStruct);  //RTC関係
  M5.Rtc.GetDate(&RTC_DateStruct);  //RTC関係
  // 時刻を更新する
  RTC_DateStruct.Year = timeinfo.tm_year + 1900;  //RTC関係
  RTC_DateStruct.Month = timeinfo.tm_mon + 1;     //RTC関係
  RTC_DateStruct.Date = timeinfo.tm_mday;         //RTC関係
  RTC_DateStruct.WeekDay = timeinfo.tm_wday;      //RTC関係
  M5.Rtc.SetDate(&RTC_DateStruct);                //RTC関係
  RTC_TimeStruct.Hours = timeinfo.tm_hour;        //RTC関係
  RTC_TimeStruct.Minutes = timeinfo.tm_min;       //RTC関係
  RTC_TimeStruct.Seconds = timeinfo.tm_sec;       //RTC関係
  M5.Rtc.SetTime(&RTC_TimeStruct);                //RTC関係
  Serial.println("RTCを設定しました");            //シリアルでRTC設定完了を通知(開発用)
}