import { deepEqual } from 'fast-equals';

declare var PUBLIC_URL: string;
console.log("Url is " + PUBLIC_URL);

document.body.innerHTML = require('./main.html') as string;


import { TimeView } from "./timeview";
import { PlantViews } from "./plant";
import { NetworkConfigView } from "./network";
import { NightLampView } from "./nightlightview";
import { TankConfigView } from "./tankview";
import { SubmitView } from "./submitView";
import { ProgressView } from "./progress";
import { OTAView } from "./ota";
import { BatteryView } from "./batteryview";
import { FileView } from './fileview';
import { LogView } from './log';

export class Controller {
  loadTankInfo() : Promise<void> {
    return fetch(PUBLIC_URL + "/tank")
    .then(response => response.json())
    .then(json => json as TankInfo)
    .then(tankinfo => {
      controller.tankView.setTankInfo(tankinfo)
    })
    .catch(error => {
      console.log(error);
    });
  }

  loadLogLocaleConfig() {
    return fetch(PUBLIC_URL + "/log_localization")
    .then(response => response.json())
    .then(json => json as LogLocalisation)
    .then(loglocale => {
      controller.logView.setLogLocalisation(loglocale)
    })
    .catch(error => {
      console.log(error);
    });
  }
  loadLog() {
    return fetch(PUBLIC_URL + "/log")
    .then(response => response.json())
    .then(json => json as LogArray)
    .then(logs => {
      controller.logView.setLog(logs)
    })
    .catch(error => {
      console.log(error);
    });
  }
  getBackupInfo() : Promise<void> {
    return fetch(PUBLIC_URL + "/backup_info")
    .then(response => response.json())
    .then(json => json as BackupHeader)
    .then(header => {
      controller.submitView.setBackupInfo(header)
    })
    .catch(error => {
      console.log(error);
    });
  }

  populateTimezones(): Promise<void> {
    return fetch('/timezones')
        .then(response => response.json())
        .then(json => json as string[])
        .then(timezones => {
          controller.timeView.timezones(timezones)
        })
        .catch(error => console.error('Error fetching timezones:', error));
  }

  updateFileList() : Promise<void> {
    return fetch(PUBLIC_URL + "/files")
    .then(response => response.json())
    .then(json => json as FileList)
    .then(filelist => {
      controller.fileview.setFileList(filelist, PUBLIC_URL)
    })
    .catch(error => {
      console.log(error);
    });
  }
  uploadFile(file: File, name:string) {
    var current = 0;
    var max = 100;
    controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
    var ajax = new XMLHttpRequest();
    ajax.upload.addEventListener("progress", event => {
      current = event.loaded / 1000;
      max = event.total / 1000;
      controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
    }, false);
    ajax.addEventListener("load", () => {
      controller.progressview.removeProgress("file_upload")
      controller.updateFileList()
    }, false);
    ajax.addEventListener("error", () => {
      alert("Error upload")
      controller.progressview.removeProgress("file_upload")
      controller.updateFileList()
    }, false);
    ajax.addEventListener("abort", () => {
      alert("abort upload")
      controller.progressview.removeProgress("file_upload")
      controller.updateFileList()
    }, false);
    ajax.open("POST", PUBLIC_URL + "/file?filename="+name);
    ajax.send(file);
  }
  deleteFile(name:string) {
    controller.progressview.addIndeterminate("file_delete", "Deleting " + name);
    var ajax = new XMLHttpRequest();
    ajax.open("DELETE", PUBLIC_URL + "/file?filename="+name);
    ajax.send();
    ajax.addEventListener("error", () => {
      controller.progressview.removeProgress("file_delete")
      alert("Error delete")
      controller.updateFileList()
    }, false);
    ajax.addEventListener("abort", () => {
      controller.progressview.removeProgress("file_delete")
      alert("Error upload")
      controller.updateFileList()
    }, false);
    ajax.addEventListener("load", () => {
      controller.progressview.removeProgress("file_delete")
      controller.updateFileList()
    }, false);
    controller.updateFileList()
  }

  updateRTCData() : Promise<void> {
    return fetch(PUBLIC_URL + "/time")
      .then(response => response.json())
      .then(json => json as GetTime)
      .then(time => {
        controller.timeView.update(time.native, time.rtc)
      })
      .catch(error => {
        controller.timeView.update("n/a", "n/a")
        console.log(error);
      });
  }
  updateBatteryData(): Promise<void> {
    return fetch(PUBLIC_URL + "/battery")
      .then(response => response.json())
      .then(json => json as BatteryState)
      .then(battery => {
        controller.batteryView.update(battery)
      })
      .catch(error => {
        controller.batteryView.update(null)
        console.log(error);
      })
  }
  uploadNewFirmware(file: File) {
    var current = 0;
    var max = 100;
    controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
    var ajax = new XMLHttpRequest();
    ajax.upload.addEventListener("progress", event => {
      current = event.loaded / 1000;
      max = event.total / 1000;
      controller.progressview.addProgress("ota_upload", (current / max) * 100, "Uploading firmeware (" + current + "/" + max + ")")
    }, false);
    ajax.addEventListener("load", () => {
      controller.progressview.removeProgress("ota_upload")
      controller.reboot();
    }, false);
    ajax.addEventListener("error", () => {
      alert("Error ota")
      controller.progressview.removeProgress("ota_upload")
    }, false);
    ajax.addEventListener("abort", () => {
      alert("abort ota")
      controller.progressview.removeProgress("ota_upload")
    }, false);
    ajax.open("POST", PUBLIC_URL + "/ota");
    ajax.send(file);
  }
  version() : Promise<void> {
    controller.progressview.addIndeterminate("version", "Getting buildVersion")
    return fetch(PUBLIC_URL + "/version")
      .then(response => response.json())
      .then(json => json as VersionInfo)
      .then(versionInfo => {
        controller.progressview.removeProgress("version")
        controller.firmWareView.setVersion(versionInfo);
      })
  }

  getBackupConfig() {
    controller.progressview.addIndeterminate("get_backup_config", "Downloading Backup")
    fetch(PUBLIC_URL + "/get_backup_config")
      .then(response => response.text())
      .then(loaded => {
        controller.progressview.removeProgress("get_backup_config")
        controller.submitView.setBackupJson(loaded);
      })
  }

  async downloadConfig(): Promise<void> {
    controller.progressview.addIndeterminate("get_config", "Downloading Config")
    const response = await fetch(PUBLIC_URL + "/get_config");
    const loaded = await response.json();
    var currentConfig = loaded as PlantControllerConfig;
    controller.setInitialConfig(currentConfig);
    controller.setConfig(currentConfig);
    //sync json view initially
    controller.configChanged();
    controller.progressview.removeProgress("get_config");
  }
  setInitialConfig(currentConfig: PlantControllerConfig) {
    this.initialConfig = currentConfig
  }
  uploadConfig(json: string, statusCallback: (status: string) => void) {
    controller.progressview.addIndeterminate("set_config", "Uploading Config")
    fetch(PUBLIC_URL + "/set_config", {
      method: "POST",
      body: json,
    })
      .then(response => response.text())
      .then(text => statusCallback(text))
    controller.progressview.removeProgress("set_config")
    //load from remote to be clean
    controller.downloadConfig()
  }
  backupConfig(json: string, statusCallback: (status: string) => void) {
    controller.progressview.addIndeterminate("backup_config", "Backingup Config")
    fetch(PUBLIC_URL + "/backup_config", {
      method: "POST",
      body: json,
    })
      .then(response => response.text())
      .then(text => statusCallback(text))
    controller.progressview.removeProgress("backup_config")
  }
  syncRTCFromBrowser() {
    controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
    var value: SetTime = {
      time: new Date().toISOString()
    }
    var pretty = JSON.stringify(value, undefined, 1);
    fetch(PUBLIC_URL + "/time", {
      method: "POST",
      body: pretty
    }).then(
      _ => controller.progressview.removeProgress("write_rtc")
    )
  }

  configChanged() {
    const current = controller.getConfig();
    var pretty = JSON.stringify(current, undefined, 0);
    controller.submitView.setJson(pretty);


    if (deepEqual(current, controller.initialConfig)) {
      document.title = "PlantCtrl"
    } else {
      document.title = "*PlantCtrl"
    }
  }

  testNightLamp(active: boolean){
    var body: NightLampCommand = {
      active: active
    }
    var pretty = JSON.stringify(body, undefined, 1);
    fetch(PUBLIC_URL + "/lamptest", {
      method: "POST",
      body: pretty
    })
  }

  testPlant(plantId: number) {
    let counter = 0
    let limit = 30
    controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")

    let timerId: string | number | NodeJS.Timeout | undefined
    function updateProgress() {
      counter++;
      controller.progressview.addProgress("test_pump", counter / limit * 100, "Testing pump " + (plantId + 1) + " for " + (limit - counter) + "s")
      timerId = setTimeout(updateProgress, 1000);

    }
    timerId = setTimeout(updateProgress, 1000);

    var body: TestPump = {
      pump: plantId
    }
    var pretty = JSON.stringify(body, undefined, 1);

    fetch(PUBLIC_URL + "/pumptest", {
      method: "POST",
      body: pretty
    })
      .then(response => response.text())
      .then(
        _ => {
          clearTimeout(timerId);
          controller.progressview.removeProgress("test_pump");
        }
      )
  }

  getConfig(): PlantControllerConfig {
    return {
      network: controller.networkView.getConfig(),
      tank: controller.tankView.getConfig(),
      night_lamp: controller.nightLampView.getConfig(),
      plants: controller.plantViews.getConfig(),
      timezone: controller.timeView.getTimeZone()
    }
  }

  scanWifi() {
    let counter = 0
    let limit = 5
    controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")

    let timerId: string | number | NodeJS.Timeout | undefined
    function updateProgress() {
      counter++;
      controller.progressview.addProgress("scan_ssid", counter / limit * 100, "Scanning for SSIDs for " + (limit - counter) + "s")
      timerId = setTimeout(updateProgress, 1000);

    }
    timerId = setTimeout(updateProgress, 1000);


    var ajax = new XMLHttpRequest();
    ajax.responseType = 'json';
    ajax.onreadystatechange = () => {
      if (ajax.readyState === 4) {
        clearTimeout(timerId);
        controller.progressview.removeProgress("scan_ssid");
        this.networkView.setScanResult(ajax.response as SSIDList)
      }
    };
    ajax.onerror = (evt) => {
      clearTimeout(timerId);
      controller.progressview.removeProgress("scan_ssid");
      alert("Failed to start see console")
    }
    ajax.open("POST", PUBLIC_URL + "/wifiscan");
    ajax.send();
  }

  setConfig(current: PlantControllerConfig) {
    this.tankView.setConfig(current.tank);
    this.networkView.setConfig(current.network);
    this.nightLampView.setConfig(current.night_lamp);
    this.plantViews.setConfig(current.plants);
    this.timeView.setTimeZone(current.timezone);
  }

  measure_moisture() {
    let counter = 0
    let limit = 2
    controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")

    let timerId: string | number | NodeJS.Timeout | undefined
    function updateProgress() {
      counter++;
      controller.progressview.addProgress("measure_moisture", counter / limit * 100, "Measure Moisture " + (limit - counter) + "s")
      timerId = setTimeout(updateProgress, 1000);

    }
    timerId = setTimeout(updateProgress, 1000);


    fetch(PUBLIC_URL + "/moisture")
      .then(response => response.json())
      .then(json => json as Moistures)
      .then(time => {
        controller.plantViews.update(time.moisture_a, time.moisture_b)
        clearTimeout(timerId);
        controller.progressview.removeProgress("measure_moisture");
      })
      .catch(error => {
        clearTimeout(timerId);
        controller.progressview.removeProgress("measure_moisture");
        console.log(error);
      });
  }

  exit() {
    fetch(PUBLIC_URL + "/exit", {
      method: "POST",
    })
    controller.progressview.addIndeterminate("rebooting", "Returned to normal mode, you can close this site now")

  }

  waitForReboot() {
    console.log("Check if controller online again")
    fetch(PUBLIC_URL + "/version", {
      method: "GET",
      signal: AbortSignal.timeout(5000)
    }).then(response => {
      if (response.status != 200){
        console.log("Not reached yet, retrying")
        setTimeout(controller.waitForReboot, 1000)
      } else {
        console.log("Reached controller, reloading")
        controller.progressview.addIndeterminate("rebooting", "Reached Controller, reloading")
        setTimeout(function(){
          window.location.reload()
        }, 2000);
      }
    })
      .catch(err => {
        console.log("Not reached yet, retrying")
        setTimeout(controller.waitForReboot, 1000)
      })
  }

  reboot() {
    fetch(PUBLIC_URL + "/reboot", {
      method: "POST",
    })
    controller.progressview.addIndeterminate("rebooting", "Rebooting")
    setTimeout(this.waitForReboot, 1000)
  }

  initialConfig: PlantControllerConfig | null = null
  readonly rebootBtn: HTMLButtonElement
  readonly exitBtn: HTMLButtonElement
  readonly timeView: TimeView;
  readonly plantViews: PlantViews;
  readonly networkView: NetworkConfigView;
  readonly tankView: TankConfigView;
  readonly nightLampView: NightLampView;
  readonly submitView: SubmitView;
  readonly firmWareView: OTAView;
  readonly progressview: ProgressView;
  readonly batteryView: BatteryView;
  readonly fileview: FileView;
  readonly logView: LogView
  constructor() {
    this.timeView = new TimeView(this)
    this.plantViews = new PlantViews(this)
    this.networkView = new NetworkConfigView(this, PUBLIC_URL)
    this.tankView = new TankConfigView(this)
    this.batteryView = new BatteryView(this)
    this.nightLampView = new NightLampView(this)
    this.submitView = new SubmitView(this)
    this.firmWareView = new OTAView(this)
    this.progressview = new ProgressView(this)
    this.fileview = new FileView(this)
    this.logView = new LogView(this)
    this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
    this.rebootBtn.onclick = () => {
      controller.reboot();
    }
    this.exitBtn = document.getElementById("exit") as HTMLButtonElement
    this.exitBtn.onclick = () => {
      controller.exit();
    }
  }
}
const controller = new Controller();
controller.progressview.removeProgress("rebooting");




const tasks = [
  { task: controller.populateTimezones, displayString: "Populating Timezones" },
  { task: controller.updateRTCData, displayString: "Updating RTC Data" },
  { task: controller.updateBatteryData, displayString: "Updating Battery Data" },
  { task: controller.downloadConfig, displayString: "Downloading Configuration" },
  { task: controller.version, displayString: "Fetching Version Information" },
  { task: controller.updateFileList, displayString: "Updating File List" },
  { task: controller.getBackupInfo, displayString: "Fetching Backup Information" },
  { task: controller.loadLogLocaleConfig, displayString: "Loading Log Localization Config" },
  { task: controller.loadTankInfo, displayString: "Loading Tank Information" },
];

async function executeTasksSequentially() {
  let current = 0;
  for (const { task, displayString } of tasks) {
    current++;
    let ratio = current / tasks.length;
    controller.progressview.addProgress("initial", ratio * 100, displayString);
    try {
      await task();
    } catch (error) {
      console.error(`Error executing task '${displayString}':`, error);
      // Optionally, you can decide whether to continue or break on errors
      break;
    }
  }
}

executeTasksSequentially().then(r => {
  controller.progressview.removeProgress("initial")
});




controller.progressview.removeProgress("rebooting");

window.addEventListener("beforeunload", (event) => {
  const currentConfig = controller.getConfig();
  
  // Check if the current state differs from the initial configuration
  if (!deepEqual(currentConfig, controller.initialConfig)) {
    const confirmationMessage = "You have unsaved changes. Are you sure you want to leave this page?";
    
    // Standard behavior for displaying the confirmation dialog
    event.preventDefault();
    event.returnValue = confirmationMessage; // This will trigger the browser's default dialog
    return confirmationMessage;
  }
});