#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
#include <WebSocketsServer.h>

const char *ssid = "Kkrbt_mobile";
const char *password = "Kkrbt2024";

typedef String str;

Adafruit_MPU6050 mpu;
ESP8266WebServer server(80);
WebSocketsServer webSocket(81);

const char *dataFilePath = "/data.csv";
const int maxDataPoints = 30;
const int maxStoredDataPoints = 5000;

const int rangeDataY = 200;

float roll, pitch, yaw;
float rollData[maxDataPoints];
float pitchData[maxDataPoints];
float yawData[maxDataPoints];
unsigned long timestamps[maxDataPoints];

unsigned long previousMillis = 0, lastSaveTime = 0;
const long interval = 100;

bool paused = false;

// ... (function declarations)

void setup() {
  Serial.begin(115200);
  Wire.begin();

  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount file system");
    return;
  } 

  while (!mpu.begin()) {
    Serial.println("Failed to find MPU6050 chip");
    delay(500);
  }

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }

  Serial.println("Connected to WiFi");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  Serial.println("Clearing data...");
  File dataFile = SPIFFS.open(dataFilePath, "w");  // Open the file in write mode
  if (dataFile) {
    dataFile.close();
    Serial.println("Data cleared successfully!");
  } else {
    Serial.println("Error: Unable to open file for clearing data!");
  }

  // Load existing data from file
  loadStoredData();

  // Setup route untuk mengambil data dan menampilkan grafik dan tabel
  server.on("/download", HTTP_GET, handleDownload);

  server.on("/", HTTP_GET, []() {
    // Halaman HTML dan JavaScript untuk menampilkan grafik garis
    String page = "<html><head>";
    page += "<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>";
    page += "<style> button { margin-top: 10px; }</style>"; // Menambahkan gaya untuk memindahkan tombol ke atas
    page += "<div id='chart'></div>";
  
    page += "<button onclick='togglePause()'>Pause</button>"; // Tombol Pause
    
    page += "<div id='timestamp'></div>"; // Div untuk menampilkan timestamp
    page += "<table border='1'>";
    page += "  <tr><th>Timestamp</th><th>Roll</th><th>Pitch</th><th>Yaw</th></tr>";
    page += "  <tbody id='dataTable'></tbody>";
    page += "</table>";
  
    page += "<button onclick='downloadData()'>Download Data</button>";

    page += "<script>";
    page += "  var paused = false;";
    page += "  var chartDiv = document.getElementById('chart');";
    page += "  var timestampDiv = document.getElementById('timestamp');";
    page += "  var tableBody = document.getElementById('dataTable');";
    
    // page += "  var traceRoll = { x: [], y: [], type: 'line', name: 'Roll', line: {color: 'red'} };";
    // page += "  var tracePitch = { x: [], y: [], type: 'line', name: 'Pitch', line: {color: 'green'} };";
    // page += "  var traceYaw = { x: [], y: [], type: 'line', name: 'Yaw', line: {color: 'blue'} };";

    page += "  var traceRoll = { x: [], y: [], type: 'scatter', mode: 'lines+markers', name: 'Roll', line: {color: 'red', shape: 'spline'} };";
    page += "  var tracePitch = { x: [], y: [], type: 'scatter', mode: 'lines+markers', name: 'Pitch', line: {color: 'green', shape: 'spline'} };";
    page += "  var traceYaw = { x: [], y: [], type: 'scatter', mode: 'lines+markers', name: 'Yaw', line: {color: 'blue', shape: 'spline'} };";
     
    page += "  var layout = { title: 'Roll, Pitch, Yaw Over Time', xaxis: {title: 'Time'}, yaxis: {title: 'Angle (degrees)', range: ["+ str(-rangeDataY) + "," + str(rangeDataY) +"], tick0: 0, dtick: 20} };";
      
    page += "  Plotly.plot(chartDiv, [traceRoll, tracePitch, traceYaw], layout);";

    page += "  function updateChart(timestamps, roll, pitch, yaw) {";
    page += "    if (!paused) {";
    page += "      traceRoll.x = timestamps.map(formatTimestamp);";
    page += "      traceRoll.y = roll;";
    page += "      tracePitch.x = timestamps.map(formatTimestamp);";
    page += "      tracePitch.y = pitch;";
    page += "      traceYaw.x = timestamps.map(formatTimestamp);";
    page += "      traceYaw.y = yaw;";
    page += "      Plotly.update(chartDiv, [traceRoll, tracePitch, traceYaw], layout);";
    page += "      timestampDiv.innerHTML = 'Last Update: ' + formatTimestamp(timestamps[timestamps.length - 1]);";
    page += "    }";
    page += "  }"; 


    page += "  function updateTable(timestamps, roll, pitch, yaw) {";
    page += "    if (!paused) {";
    page += "      if (tableBody.rows.length >= " + String(maxDataPoints) + ") {";
    page += "        tableBody.deleteRow(0);";
    page += "      }";
    page += "      var newRow = tableBody.insertRow(-1);";
    page += "      var cell1 = newRow.insertCell(0);";
    page += "      var cell2 = newRow.insertCell(1);";
    page += "      var cell3 = newRow.insertCell(2);";
    page += "      var cell4 = newRow.insertCell(3);";
    page += "      cell1.innerHTML = formatTimestamp(timestamps[timestamps.length - 1]);";
    page += "      cell2.innerHTML = roll[roll.length - 1];";
    page += "      cell3.innerHTML = pitch[pitch.length - 1];";
    page += "      cell4.innerHTML = yaw[yaw.length - 1];";
    page += "    }";
    page += "  }";
    page += "  function formatTimestamp(timestamp) {";
    page += "    var date = new Date(timestamp);";
    page += "    var minutes = date.getMinutes();";
    page += "    var seconds = date.getSeconds();";
    page += "    var milliseconds = date.getMilliseconds();";
    page += "    return minutes + ':' + (seconds < 10 ? '0' : '') + seconds + ':' + (milliseconds < 10 ? '00' : (milliseconds < 100 ? '0' : '')) + milliseconds;";
    page += "  }";
    page += "  function togglePause() {";
    page += "    paused = !paused;";
    page += "  }";
    page += "  var ws = new WebSocket('ws://' + location.hostname + ':81/');";
    page += "  ws.onmessage = function(event) { var jsonData = JSON.parse(event.data); updateChart(jsonData.timestamps, jsonData.roll, jsonData.pitch, jsonData.yaw); updateTable(jsonData.timestamps, jsonData.roll, jsonData.pitch, jsonData.yaw); };";
    
    page += "  function downloadData() {";
    page += "    var xhr = new XMLHttpRequest();";
    page += "    xhr.open('GET', '/download', true);";
    page += "    xhr.responseType = 'blob';";
    page += "    xhr.onload = function() {";
    page += "      var a = document.createElement('a');";
    page += "      a.href = window.URL.createObjectURL(xhr.response);";
    page += "      a.download = 'data.csv';";
    page += "      a.style.display = 'none';";
    page += "      document.body.appendChild(a);";
    page += "      a.click();";
    page += "      document.body.removeChild(a);";
    page += "    };";
    page += "    xhr.send();";
    page += "  }";
    
    page += "</script></body></html>";

    server.send(200, "text/html", page);
  });

  server.begin();
  webSocket.begin();
  webSocket.onEvent(handleWebSocket);
 
}

void loop() {
  server.handleClient();
  webSocket.loop();

  unsigned long currentMillis = millis();

  // Read data from MPU6050
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);

    roll = atan2(a.acceleration.y, a.acceleration.z) * 180 / PI;
    pitch = atan2(-a.acceleration.x, sqrt(a.acceleration.y * a.acceleration.y + a.acceleration.z * a.acceleration.z)) * 180 / PI;
    yaw = g.gyro.z * 180 / PI;

    sendWebSocketData();

    // Save data to file every 1 second
    if (currentMillis - lastSaveTime >= 1000) {
      updateDataHistory(currentMillis, roll, pitch, yaw);
      saveDataToFile();
      lastSaveTime = currentMillis;
    }
  }
}
   
void saveDataToFile() {
  Serial.println("Saving data to file...");
  File dataFile = SPIFFS.open(dataFilePath, "a");  // Open the file in append mode
  if (dataFile) {
    if (dataFile.size() > 0) {
      dataFile.println();  // Add a newline before appending new data, if the file is not empty
    }

    for (int i = 0; i < maxDataPoints; i++) {
      if (timestamps[i] != 0) {  // Only save valid data
        dataFile.print(String(timestamps[i]) + "," + String(rollData[i]) + "," + String(pitchData[i]) + "," + String(yawData[i]));
        if (i < maxDataPoints - 1 && timestamps[i + 1] != 0) {
          dataFile.println();  // Add a newline if there is more data
        }
      }
    }
    dataFile.close();

    Serial.println("Data saved successfully!");
  } else {
    Serial.println("Error: Unable to open file for writing!");
  }
}


void handleDownload() {
  File dataFile = SPIFFS.open(dataFilePath, "r");
  if (dataFile) {
    Serial.println("Sending CSV data for download...");
    server.sendHeader("Content-Disposition", "attachment;filename=data.csv");
    server.streamFile(dataFile, "text/csv");
    dataFile.close();
  } else {
    Serial.println("Error: File not found!");
    server.send(404, "text/plain", "File not found");
  }
}
  
void loadStoredData() {
  File dataFile = SPIFFS.open(dataFilePath, "r");
  if (dataFile) {
    int count = 0;
    while (dataFile.available() && count < maxStoredDataPoints) {
      String line = dataFile.readStringUntil('\n');
      if (line.length() > 0) {
        // Split the line into parts using tabs
        int tab1 = line.indexOf(',');
        int tab2 = line.indexOf(',', tab1 + 1);
        int tab3 = line.indexOf(',', tab2 + 1);

        if (tab1 != -1 && tab2 != -1 && tab3 != -1) {
          timestamps[count] = line.substring(0, tab1).toInt();
          rollData[count] = line.substring(tab1 + 1, tab2).toFloat();
          pitchData[count] = line.substring(tab2 + 1, tab3).toFloat();
          yawData[count] = line.substring(tab3 + 1).toFloat();

          count++;
        }
      }
    }
    dataFile.close();
  }
}

 
void updateDataHistory(unsigned long timestamp, float newRoll, float newPitch, float newYaw) {
  // Shift data to make space for new data
  for (int i = 0; i < maxDataPoints - 1; i++) {
    rollData[i] = rollData[i + 1];
    pitchData[i] = pitchData[i + 1];
    yawData[i] = yawData[i + 1];
    timestamps[i] = timestamps[i + 1];
  }

  // Add new data at the end
  rollData[maxDataPoints - 1] = newRoll;
  pitchData[maxDataPoints - 1] = newPitch;
  yawData[maxDataPoints - 1] = newYaw;
  timestamps[maxDataPoints - 1] = timestamp;

  // Check if the maximum limit is reached and remove the oldest data
  if (timestamps[maxDataPoints - 1] - timestamps[0] > maxStoredDataPoints * interval) {
    for (int i = 0; i < maxDataPoints - 1; i++) {
      rollData[i] = rollData[i + 1];
      pitchData[i] = pitchData[i + 1];
      yawData[i] = yawData[i + 1];
      timestamps[i] = timestamps[i + 1];
    }
    rollData[maxDataPoints - 1] = 0;  // Reset the last element
    pitchData[maxDataPoints - 1] = 0;
    yawData[maxDataPoints - 1] = 0;
    timestamps[maxDataPoints - 1] = 0;
  }
}


void handleWebSocket(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
  // ... (WebSocket event handling code remains unchanged)
}

void sendWebSocketData() {
  unsigned long timestamp = millis();
  String payload = "{\"timestamps\": [";
  String rollPayload = "\"roll\": [";
  String pitchPayload = "\"pitch\": [";
  String yawPayload = "\"yaw\": [";

  for (int i = 0; i < maxDataPoints; i++) {
    if (i > 0) {
      payload += ",";
      rollPayload += ",";
      pitchPayload += ",";
      yawPayload += ",";
    }
    payload += timestamps[i];
    rollPayload += rollData[i];
    pitchPayload += pitchData[i];
    yawPayload += yawData[i];
  }

  payload += "," + String(timestamp) + "]";
  rollPayload += "," + String(roll) + "]";
  pitchPayload += "," + String(pitch) + "]";
  yawPayload += "," + String(yaw) + "]";

  payload += "," + rollPayload + "," + pitchPayload + "," + yawPayload + "}";
  webSocket.broadcastTXT(payload);

  updateDataHistory(timestamp, roll, pitch, yaw);
} 

void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }

  server.send(404, "text/plain", message);
}