// September 2019 @liv-in-sky durch viel vorarbeit von @thewhobox (api verbindung)
// Definition Login
const unifi_username = "x";
const unifi_password = "xxxxxx!";
const unifi_controller = "https://192.xxx.xxx.xxx:8443";
// DEFINITION der zu anzeigenden Netzwerke am besten bis auf id und smart alle gleich setzen: beispiel:
const wifis = {
  "WLAN_Dragon1": { name: "WLAN_Dragon1", id: "yyyyyyyyyyyyyyyyyyyyy", desc: "WLAN_Dragon1", smart: "WLAN_Dragon1" } ,
  "WLAN_DragonGuest": { name: "WLAN_DragonGuest", id: "yyyyyyyyyyyyyyyyyyyy", desc: "WLAN_DragonGuest", smart: "WLAN_DragonGuest" }
}
let wifiDPs = [];
const request = require('request-promise-native').defaults({ rejectUnauthorized: false });
var fs = require('fs')
const datei = "/opt/iobroker/iobroker-data/files/iqontrol/htmlvoucher.html";
const datei2 = "/opt/iobroker/iobroker-data/files/iqontrol/htmlclients.html";
let writeFile = true;
let  writeFileVar = 0;
let writeClientTable = true;
let writeAnwesenheit = true;
let listeDatenpunkte = [];
let listeDatenpunkteNew = [];
let listeDatenpunkteAlt = [];
// Datenpunkte Hauptpfad wählen - WICHTIG muss mit der javascript instanz, inder das script läuft zusammenpassen
const dpPrefix = "javascript.0.";
// Abfragezyklus definieren
const abfragezyklus =20000;
//HIER Einstellungen : EIN-AUSSCHALTEN Vouchers, iqontrol-Datei erstellen, anwesenheitskontrolle-clientpflege
let  iqontrol = true;
let  anwesenheit = true; // beim setzen von true auf false die verzeichnisstruktur unter iobroker-objects "von hand" löschen
let vouchers = true;
// Hier Definition iqontrol-Style für Popup
const format = "<!DOCTYPE html><html><head><title>Voucher</title></head><body><table style=\"color:black;text-align:center; font-family:Helvetica;background-image: linear-gradient(42deg,transparent, lightblue);\">";
const format2 = "<!DOCTYPE html><html><head><title>Clients</title></head><body><table style=\"color:black; font-family:Helvetica;background-image: linear-gradient(42deg,transparent, lightblue);\">";
//-----------------AB HIER NICHTS MEHR ÄNDERN------------------------
let cookies = [];
let loggedIn = false;
let debug = false;
if ( !anwesenheit) fs.writeFileSync(datei2,("variable anwesenheit und/oder iqontrol ist nicht im unifiscript aktiviert - auf true setzen")); 
if ( !vouchers) fs.writeFileSync(datei,("variable vouchers und/oder iqontrol ist nicht im unifiscript aktiviert - auf true setzen")); 
//Erstelle Datenpunkte für die WLANs automatisch
for(let wifi_name in wifis) {
    wifiDPs.push(dpPrefix + "WLANUnifi." + wifis[wifi_name].name);
   
   createState(dpPrefix + "WLANUnifi."+ wifi_name, {
      name: wifis[wifi_name].desc,
      role: 'state',
      read: true,
      write: true,
      type: "boolean",
      smartName: {
          de: wifis[wifi_name].smart,
          smartType: "SWITCH"
      } });}
if (true) {
createState(dpPrefix + "WLANUnifi.Wifi_Clients", {
   name: 'Unifi Wifi Clients Table', 
   role: 'string',
   read:  true, 
   write: true,
});}
if (vouchers) {
createState(dpPrefix + "WLANUnifi.Wifi_Vouchers", {
   name: 'Unifi Wifi Vouchers_Table', 
   role: 'string',
   read:  true, 
   write: true,
});
for (var i = 1; i < 21; i++) { 
   var x=i.toString();
   if (i<10) x="0"+x;
  createState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+x, {
   name: 'Unifi Voucher_Code'+x, 
   role: 'string',
   read:  true, 
   write: true,
   });
createState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+x+".code"+x, {
   name: 'Unifi Voucher_Code_code'+x, 
   role: 'string',
   read:  true, 
   write: true,
   });    
createState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+x+".erstellt", {
   name: 'Unifi Voucher_Code_erstellt'+x, 
   role: 'string',
   read:  true, 
   write: true,
   });
createState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+x+".dauer", {
   name: 'Unifi Voucher_Code_duration'+x, 
   role: 'string',
   read:  true, 
   write: true,
   });
createState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+x+".abgelaufen", {
   name: 'Unifi Voucher_Code_expires'+x, 
   role: 'string',
   read:  true, 
   write: true,
   });
}}
createState(dpPrefix + "WLANUnifi.Wifi_Clients_Anzahl",  { name: 'Wifi_Clients_Anzahl', desc: 'Wifi_Clients_Anzahl', type: 'number', unit: '', min: '0', max: '100', role: '',read: true, write: true });
if (vouchers) createState(dpPrefix + "WLANUnifi.Wifi_Vouchers_Anzahl", { name: 'Wifi_Vouchers_Anzahl', desc: 'Wifi_Vouchers_Anzahl', type: 'number', unit: '', min: '0', max: '100', role: '',read: true, write: true }); 
function dlog(message) {
  if(debug)
      console.log(message);
}
//-----------------------------------------LOGIN---------------------------------------------------------------
async function login() {
  return new Promise(async (resolve, reject) => {
      let resp = await request.post({
          resolveWithFullResponse: true,
          url: unifi_controller + "/api/login",
          body: JSON.stringify({ username: unifi_username, password: unifi_password }),
          headers: { 'Content-Type': 'application/json' }
      }).catch((e) => { dlog("login: reject"), reject(e) });
      
      if(resp != null) {
          dlog("login: login war erfolgreich! " + ((resp.headers && resp.headers.hasOwnProperty("set-cookie")) ? "Mit Cookies":"Ohne Cookies"));
          if(resp.headers && resp.headers.hasOwnProperty("set-cookie")) {
              let set_cookies = resp.headers["set-cookie"];
              for(i = 0; i < set_cookies.length; i++) {
                  let cookie = set_cookies[i];
                  cookie = cookie.split(";")[0];
                  cookies.push(cookie);
              }
          } else {
              dlog("login: no cookies to set!")
          }
          loggedIn = true;
          resolve();
      } else {
          dlog("login: rejected")
          reject("resp = null");
      }
  });
}
async function dlogout() {
  return new Promise(async (resolve, reject) => {
      let resp = await request.get({
          url: unifi_controller + "/dlogout",
          headers: { Cookie: cookies.join("; ") }
      }).catch((e) => reject(e));
      if(resp != null) {
          dlog("Du bist nun ausgedloggt.");
          dlog(resp);
          resolve();
      } else {
          reject("resp = null");
      }
  });
}
//-----------------------------------------STATUS   WIFI  ---------------------------------------------------------
//Updatet status vom Wifi
//wifi: wifi object aus der konstanten wifis
async function getStatus(wifi) {
     dlog("BIN IN STATUS");
  return new Promise(async (resolve, reject) => {
      dlog("nur mal so");
      if (!loggedIn) await login().catch((e) => reject(e));
      let resp = await request.get({
          url: unifi_controller + "/api/s/default/rest/wlanconf/" + wifi.id,
          headers: { Cookie: cookies.join("; ") }
      }).catch((e) => { dlog("getStatus reject " + e); reject(e) });
      dlog("got response " + JSON.stringify(resp));
      resp = JSON.parse(resp);
      let wlanOn = resp.data[0].enabled;
      dlog("WLAN " + wifi.name + " ist: " + (wlanOn ? "an" : "aus"));
      if (resp != null && resp.meta && resp.meta.rc == "ok") {
          dlog("Status erfolgreich geholt!");
          dlog(resp);
          let wlanOn = resp.data[0].enabled;
          dlog("WLAN ist: " + (wlanOn ? "an" : "aus"));
          setStateDelayed(dpPrefix + "WLANUnifi." + wifi.name, wlanOn, 200);
       
          resolve(wlanOn);
      } else {
          dlog("nicht ok...")
          reject(JSON.stringify(resp));
      }dlog("BIN aus STATUS raus");
  });
   
}
//-----------------------------------------GETCLIENTS---------------------------------------------------------------
 async function getClients() {
    dlog("BIN IN CLIENTS");
   
   return new Promise(async (resolve, reject) => {
       dlog("nur mal so");
       if(!loggedIn) await login().catch((e) => reject(e));
       let resp = await request.get({
           url: unifi_controller + "/api/s/default/stat/sta/",
           headers: { Cookie: cookies.join("; ") }
       }).catch((e) => { dlog("getStatus reject " + e); reject(e) });  
  
dlog("got response " + JSON.stringify(resp));
dlog(typeof resp);
dlog("--------------------- " + resp);
//resp = JSON.parse(resp);
//Sortierung Daten und Verwandlung Json
var versuch = [];
versuch = JSON.parse(resp).data;
dlog(versuch[2].hostname+versuch[2].mac);
versuch.sort(function(alpha, beta) {
   if ( alpha.hostname < beta.hostname )
       return -1;
   if ( alpha.hostname > beta.hostname )
       return 1;
   return 0;
});
dlog(versuch.length.toString());
var anzahlClients = versuch.length;
setStateDelayed(dpPrefix + "WLANUnifi.Wifi_Clients_Anzahl",anzahlClients,100);
var clientListe = "";
await getExistingClients();
//erstelle aktuelles array
listeDatenpunkteNew=[];
for (var i = 0; i < versuch.length; i++) { 
   listeDatenpunkteNew[i] = versuch[i].hostname;}
  dlog (listeDatenpunkteNew[3]);
// gibt es unterschiedliche Daten - bezug listeDatenpunkte anwesenheit?  
dlog("ClientTable "+listeDatenpunkteNew.length + " - " + listeDatenpunkteAlt.length); 
if  (listeDatenpunkteNew.length == listeDatenpunkteAlt.length) {writeClientTable = false;} else {writeClientTable=true;listeDatenpunkteAlt=[];listeDatenpunkteAlt=listeDatenpunkteNew.concat();}   
dlog("ClientTable "+writeClientTable.toString());
dlog("ClientTable "+listeDatenpunkteNew.length + " - " + listeDatenpunkteAlt.length);
// gibt es unterschiedliche Daten - bezug listeDatenpunkte anwesenheit?
if (anwesenheit && listeDatenpunkteNew.length==listeDatenpunkte.length) {writeAnwesenheit = false;} else {writeAnwesenheit=true}
dlog("writeAnwesenheit "+writeAnwesenheit.toString());
dlog("writeAnwesenheit "+listeDatenpunkteNew.length + " - " + listeDatenpunkte.length);
// erstelle htmlclientliste wenn listenDaten verändert  
    if ( writeClientTable ) {
     for (var i = 0; i < versuch.length; i++)  {
       dlog(versuch[i].hostname + " --- " + versuch[i].essid + " --- " + versuch[i].ip);
       clientListe = clientListe.concat("<tr><td>"+versuch[i].hostname+" </td><td>"+versuch[i].essid+"    </td><td>"+versuch[i].ip+"</td></tr>");
       dlog(clientListe);
       var ipWandler= versuch[i].ip;
       if (anwesenheit && writeAnwesenheit) {
          createState(dpPrefix + "WLANUnifi.Wifi_Client_States."+versuch[i].hostname, {
          name: ipWandler, 
          role: 'boolean',
          read:  true, 
          write: true,
          });
       }
     
     if (anwesenheit && listeDatenpunkteNew.indexOf(versuch[i].hostname)>-1) { //ist hostname in neuer liste und verzeichnisstruktur  -> true (anwesend)
       dlog("gefunden"+versuch[i].hostname);
       setStateDelayed(dpPrefix + "WLANUnifi.Wifi_Client_States."+versuch[i].hostname, true, 500);}
 } }
     
 
if (anwesenheit) {
for (var b = 0; b < listeDatenpunkte.length-1; b++) { //  ist hostname in verzeichnis struktur und nicht in neuer liste -> false (abwesend)
if (listeDatenpunkteNew.indexOf(listeDatenpunkte[b])==-1) {
    dlog("nicht gefunden"+listeDatenpunkte[b]);
    setStateDelayed(dpPrefix + "WLANUnifi.Wifi_Client_States."+listeDatenpunkte[b], false, 500);}}}
if (iqontrol && anwesenheit && writeClientTable) fs.writeFileSync(datei2, format2+clientListe.concat("</table><p style=\"color:blue; font-family:Helvetica;\">Geamtanzahl angemeldeter Geräte:"+versuch.length+"</p>"));
dlog("ClientFile schreibt! "+iqontrol.toString()+writeClientTable.toString());
if (writeClientTable) setState(dpPrefix + "WLANUnifi.Wifi_Clients", "<table>"+clientListe.concat("</table>")); //schreibe client table
dlog("bin raus aus  clients");
});
}
async function getExistingClients() {
   dlog("BIN IN EXISTING CLIENTS");
listeDatenpunkte = [];
var cacheSelectorState = $("state[state.id=" + dpPrefix + "WLANUnifi.Wifi_Client_States.*]");
  cacheSelectorState.each(function (id, c) {
    if (!id.includes("undefined")) {
     listeDatenpunkte[c] = id.replace(dpPrefix + "WLANUnifi.Wifi_Client_States.", "");}
});
//for (i = 0; i<listeDatenpunkte.length;i++) {log(listeDatenpunkte[i]);}
//log(listeDatenpunkte.length.toString());
dlog("bin raus a existing clients");
}
//-----------------------------------------GET--VOUCHERS---------------------------------------------------------------
async function getVouchers() {
     dlog("BIN IN VOUCHERS");
   return new Promise(async (resolve, reject) => {
       dlog("nur mal so");
       if(!loggedIn) await login().catch((e) => reject(e));
       let resp = await request.get({
           url: unifi_controller + "/api/s/default/stat/voucher",
           headers: { Cookie: cookies.join("; ") }
       }).catch((e) => { dlog("getStatus reject " + e); reject(e) });  
  
dlog("got response " + JSON.stringify(resp));
dlog(typeof resp);
dlog("--------------------- " + resp);
resp = JSON.parse(resp);
dlog(resp.meta);
dlog(resp.meta.rc);
dlog(resp.data[1].code);
dlog(resp.data.length);
dlog(JSON.stringify(resp).length.toString());
var laengeMessage=JSON.stringify(resp).length;
if (laengeMessage==writeFileVar) {writeFile = false;} else {writeFile=true}
writeFileVar=JSON.stringify(resp).length;
if (writeFile) {
var clientListe = "<tr><td>DAUER </td><td>STATUS-ABGELAUFEN    </td><td>CODE</td>  </td><td>ERSTELLT</td></tr> ";
for (var i = 1; i < 21; i++) { 
   var x=i.toString();
   if ( i < 10) { var yyy="0"+x;} else {yyy=x}; setStateDelayed(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yyy, "", 100);}
   
for (var i = 0; i < resp.data.length; i++) { 
  var zeit= resp.data[i].create_time*1000
  let zeit1 =  formatDate(getDateObject(zeit), "TT.MM.JJJJ SS:mm").toString();
  clientListe = clientListe.concat("<tr><td>"+resp.data[i].duration+" </td><td>"+resp.data[i].status_expires+"    </td><td>"+resp.data[i].code+"  </td><td>" +zeit1 + "</td></tr>");
  var y=i+1; 
  var yy=y.toString();
  if (y<10 )  yy="0"+yy;
 
  if (i<20  )  {
      dlog(zeit1);
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".code"+yy, resp.data[i].code );
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".erstellt", zeit1 );
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".dauer", resp.data[i].duration );
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".abgelaufen", resp.data[i].status_expires );
      
  }}
//Datenpunkteäalt löschen
var w = resp.data.length;
for (i = w; i < 20; i++) { 
      var y=i+1; 
  var yy=y.toString();
  if (y<10 )  yy="0"+yy;
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".code"+yy, " na " );
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".erstellt", " na " );
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".dauer", " na " );
      setState(dpPrefix + "WLANUnifi.Wifi_Vouchers-CODES.CODE"+yy+".abgelaufen", " na " );
}
}
if (iqontrol && writeFile) {fs.writeFileSync(datei, format + clientListe.concat("</table></style></body></html><p style=\"color:blue; font-family:Helvetica;\">Geamtanzahl Vouchers:"+resp.data.length+"</p>"));}
if (writeFile) setState(dpPrefix + "WLANUnifi.Wifi_Vouchers", "<table>"+clientListe.concat("</table></style></body></html>"));
if (writeFile) setState(dpPrefix + "WLANUnifi.Wifi_Vouchers_Anzahl", resp.data.length);
dlog("bin raus a existing vouchers");
});
}
//-----------------------------------------SET WIFIS - WIFI EIN-AUSSCHALTEN----------------------------------------------
//Wifi an-/ausschalten
//enabled: true = anschalten; false = ausschalten
//wifi: wifi object aus der konstanten wifis
async function setWifi(enabled, wifi) {
  return new Promise(async (resolve, reject) => {
      dlog("setWifi: start set Wifi_haupt");
      if (!loggedIn) { dlog("need to login"); await login().catch((e) => reject(e)); }
      dlog("setWifi: now setting Wifi_haupt");
      let resp = request.post({
          url: unifi_controller + "/api/s/default/upd/wlanconf/" + wifi.id,
          body: JSON.stringify({ enabled }),
          headers: { 'Content-Type': 'application/json', Cookie: cookies.join("; ") }
      }).catch((e) => { dlog("setWifi: rejected: " + e); reject(e) });
      dlog("setWifi: got response")
      if (resp != null) {
          dlog("setWifi: Wifi wurde erfolgreich " + (enabled ? "eingeschaltet" : "ausgeschaltet"));
          dlog(resp);
          setState(dpPrefix + "WLANUnifi." + wifi.name, enabled, enabled);
          resolve();
      } else {
          dlog("setWifi: rejetced")
          dlog("resp: " + JSON.stringify(resp));
          reject("msg: " + JSON.parse(resp.body).meta.msg);
      }
  });
}
//-----------------------------------------------SCHALTER------------------------------------------------
on({id: wifiDPs, ack: false, change: "ne"}, function (obj) { 
 var value = obj.state.val;
 var dp2 = obj.name
 setWifi(value, wifis[dp2]);
 dlog(wifis[dp2])
});
//-----------------------------------------------MAIN LOOP------------------------------------------------
setInterval(async () => {
   for(let wifi_name in wifis) {
   await getStatus(wifis[wifi_name]);}
 
   if (vouchers)  getVouchers();
   await getClients();
 
   }, abfragezyklus); // wird oben definiert
// Beispiel für
//setWifi(true, wifis.WLAN_DragoRootGuest);