function toggleVisibility(idImg, idDiv) {
  var div = document.getElementById(idDiv);
  var img = document.getElementById(idImg);
  if (div.style.display === "none") {
    div.style.display = "flex";
    img.src = "res/dark/hide.png";
  } else {
    div.style.display = "none";
    img.src = "res/dark/show.png";
  }
}

class APIScanner
{
	static api;
	static settings;
	static scanned;
	
	constructor(parent) {
		APIScanner.api = parent;
		this.qr = null;
		this.cameras = [];
	}
	
	Start(id, subroute, parser) {
		APIScanner.settings = {
			subroute: subroute,
			parser: parser,
			id: id
		};
		APIScanner.scanned = {primary: {}, secondary: {}};
		for (let i in parser) {
			if (i!=subroute) {
				APIScanner.scanned.secondary[parser[i]] = null;
		} }
		
		// Find already parsed IDs
		const subData = APIScanner.api.activeRoute.resourcesIndex[id].data.SUB[parser[subroute]];
		for (let i in subData) {
			APIScanner.scanned.primary[subData[i].ID] = true;
		}
		
		this.qr = new QrScanner(
    	document.getElementById("qrscanner_video"),
    	APIScanner.parse,
    	{returnDetailedScanResult: true, highlightScanRegion: true, highlightCodeOutline: true}
		);
		if (QrScanner.hasCamera()) {
			let _this = this;
  		QrScanner.listCameras(true)
  			.then(result => _this.cameras = result)
  			.catch(error => alert(error || 'Camera detection failed.'));
		}
	  this.cameraIndex = 0;
		document.getElementById("qrscanner_text").innerHTML = "";
		
		this.qr.start();
	}
	Stop() {
		this.qr.stop();
		this.qr.destroy();
		this.qr = null;
	}
	SwitchCamera() {
		if (this.qr!==null && QrScanner.hasCamera()) {
		  ++this.cameraIndex;
		  if (this.cameraIndex>=this.cameras.length) {
		    this.cameraIndex = 0;
		  }
		  this.qr.setCamera(this.cameras[this.cameraIndex].id);
		}
	}
	static parse(result) {
		let scan = result.data.split("/");
		if (scan.length==3) {
			let route = scan[1];
			let subid = scan[2];

			// Is it a route we should parse?
			if (typeof(APIScanner.settings.parser[route])!="undefined") {
				const marker = APIScanner.settings.parser[route];

				// Is it the primary route and if yes, has this ID been parsed before?			
				if (route==APIScanner.settings.subroute && typeof(APIScanner.scanned.primary[subid])=="undefined") {
					APIScanner.scanned.primary[subid] = true;
					
					let index = -1;
					
					const options = APIScanner.api.activeRoute.options[marker];
					for (let i in options) {
						if (options[i].ID==subid) {
							index = i;
							break;
					} }
					// Has the ID been found?
					if (index>=0) {
						document.getElementById("qrscanner_text").innerHTML += options[index].NAME + "<br />";
						APIScanner.api.Request("POST", APIScanner.scanned.secondary, APIScanner.settings.id, APIScanner.settings.subroute, subid);
					}
				} else if(route!=APIScanner.settings.subroute) { // Is it a secondary field?
					APIScanner.scanned.secondary[marker] = subid;
	}	}	}	}
}

class APIResource  // This holds ONE table record
{
  constructor(route, group, data) {
    this.route = route;
    this.group = group;
    this.ID = data.ID;
    group.resourcesIndex[this.ID] = this;
    route.resourcesIndex[this.ID] = this;

    this.data = data;
    this.show = false;
    
    // TODO: This SHOULD be determined by the client through comparing of data with privileges and not be preprocessed by server. Should it?
    this.localAdmin = data.admin;
  }
  
  Render(includingBox = false) {
    var data = this.data;
    data.SHOWBUTTON = this.show;
    data.SHOWCONTENT = this.show;
    data.SELECTBOX = "<input type=checkbox onChange='SelectChange("+this.ID+", "+(this.route.selected.includes(this.ID) ? "false);' checked" : "true);'")+" />";
    
    var ret = "";
    return (includingBox ? "<div id='LIST/" + this.ID + "' class='list_entry'>" : "")
         + this.route.tpl.Render(data, "LIST", (this.route.admin || this.localAdmin ? ":ADMIN" : ""))
         + (includingBox ? "</div>" : "");
  }
}

class APIGroup
{
  constructor(route, id, resources) {
    this.route = route;
    this.ID = id;
    this.resources = [];      // List of APIResources
    this.resourcesIndex = {};  // resources indexed by ID
    
    this.show = true;
    
    this.route.groupsIndex[id] = this;
    for (var i in resources) {
      this.Add(resources[i]);
    }
  }
  Add(resource) {
    this.resources.push(new APIResource(this.route, this, resource));
  }
  Delete(id) {
    var index = this.route.findIndexInEntries(id, this.resources);
    delete this.resources[index];
    this.resources.splice(index, 1);
  }
  Clear() {
    for (var i in this.resources) {
      delete this.resources[i];
    }
    this.resources = [];
    this.resourcesIndex = {};
  }
  Render() {
    var ret = "<article id='GROUP/" + this.ID + "'>" + this.route.tpl.Render({"GROUP": this.ID}, "GROUP_BEGIN", "");
    for (var k in this.resources) {
      ret+= this.resources[k].Render(true);
    }
    ret+= this.route.tpl.Render({"GROUP": this.ID}, "GROUP_END", "") + "</article>";
    return ret;
  }
}

class APIFilter // This holds ONE filter element
{
  constructor(data, type) {
    this.Set(data, type);
  }
  Get() {
    return {and: this.and, field: this.field, op: this.op, value: this.value};
  }
  Set(data, type) {
    this.and = typeof(data.and)!="undefined" ? data.and : "and";
    this.field = typeof(data.field)!="undefined" ? data.field : "none";
    this.op = typeof(data.op)!="undefined" ? data.op : "";
    this.value = typeof(data.value)!="undefined" ? data.value : ("Multiple"==type ? [] : "");
  }
}

class APIRoute  // This holds a list of resources
{
  constructor(parent, route, info) {
    this.parent = parent;
    this.route = route;
    this.isPage = info.isPage;
    this.mainFields = info.mainFields;
    this.filter = [];
    this.tplFile = info.template;
    this.tpl = null;
    this.image = typeof(info.image)!="undefined" ? info.image : null;
    this.name = info.name;
    this.admin = false;
    this.options = [];
    this.selected = [];
    this.dropzones = {};

    this.groups = [];       // List of APIResourceGroups
    this.groupsIndex = {};
    this.resourcesIndex = {};    // Index of APIResources by ID linking to entries inside their respective group
  }
  
  Add() {
    // Get JSON
    var send = {};
    for (var i in this.mainFields) {
      if (this.mainFields[i].add) {
        var e = document.getElementById("ADD/"+i);
        if (e!=null) {
          if (this.mainFields[i].type=="Multiple") {
            // Multiple values can be selected
            send[i] = Array.from(e.selectedOptions).map(el=>el.value);
          } else if (this.mainFields[i].type=="Checkbox") {
            send[i] = e.checked;
          } else if (e.value=="__NULL__") {
            // Null is selected
            send[i] = null;
          } else {
            // Only one value can be selected/input
            send[i] = e.value;
          }
        } else if (this.mainFields[i].type=="DateTime") {
          var eDate = document.getElementById("ADD/"+i+"/DATE");
          var eTime = document.getElementById("ADD/"+i+"/TIME");
          if (null!=eDate && null!=eTime) {
            send[i] = eDate.value + " " + eTime.value;
    } } } }
    this.parent.Request("POST", send);
  }
  Save(id) {
    // Get JSON
    
    var json = this.findEntry(id).data.MAIN;
    var send = {};
    var element = document.getElementById("LIST/"+id);
    element.className = "list_entry";
    
    for (var i in this.mainFields) {
      if (this.mainFields[i].edit) {
        var e = document.getElementById("LIST/"+id+"/"+i);
        
        if (e!=null) {
          if (this.mainFields[i].type=="Multiple") {
            // Multiple values can be selected
            send[i] = Array.from(e.selectedOptions).map(el=>el.value);
          } else if (this.mainFields[i].type=="Checkbox") {
            send[i] = e.checked;
          } else if (e.value=="__NULL__") {
            // Null is selected
            send[i] = null;
          } else {
            // Only one value can be selected/input
            send[i] = e.value;
          }
        } else if (this.mainFields[i].type=="DateTime") {
          var eDate = document.getElementById("LIST/"+id+"/"+i+"/DATE");
          var eTime = document.getElementById("LIST/"+id+"/"+i+"/TIME");
          if (null!=eDate && null!=eTime) {
            send[i] = eDate.value + " " + eTime.value;
    } } } }
    this.parent.Request("PATCH", send, id);
  }
  Edit(id) {
    var _this = this;
    var entry = this.findEntry(id);
    var element = document.getElementById("LIST/"+id);
    element.innerHTML = this.tpl.Render(entry.data, "LIST", ":EDIT");
    element.className = "list_entry list_edit";
    this.renderHelper(id);
  }
  Cancel(id) {
    var entry = this.findEntry(id);
    var element = document.getElementById("LIST/"+id);
    element.innerHTML = this.tpl.Render(entry.data, "LIST", ":ADMIN");
    element.className = "list_entry";
  }
  SubShowAdd(id, markers) {
    var entry = this.findEntry(id);
    
    let marker;
    var data = {"ID": id};
    if (Array.isArray(markers)) {
    	marker = markers[0];
    	for (let i=1; i<markers.length; ++i) {
    		data[markers[i]] = this.options[markers[i]];
    	}
    } else {
    	marker = markers;
    }
    data[marker] = [];    
    
    for (var i in this.options[marker]) {
      if (this.findIndexInEntries(this.options[marker][i].ID, entry.data.SUB[marker])==null) {
        data[marker].push(this.options[marker][i]);
    } }
    
    var element = document.getElementById("LIST/"+id+"/SUB/"+marker+"/ADD");
    element.innerHTML = this.tpl.Render(data, "ADD.SUB."+marker, "");
  }
  SubShowScan(id, subroute, parsers) {
    document.getElementById("qrscanner").style.display = "flex";
    this.parent.scanner.Start(id, subroute, parsers);
  }
  SubAdd(id, markers, route) {
  	let marker;
  	let json = {};
  	if (Array.isArray(markers)) {
  		marker = markers[0];
  		for (let i=1; i<markers.length; ++i) {
    		json[markers[i]] = document.getElementById("ADD/"+id+"/"+markers[i]).value;
    		if (json[markers[i]]=="__NULL__") {
    			json[markers[i]] = null;
    		}
    	}
  	} else {
  		marker = markers;
  	}
    const subid = document.getElementById("ADD/"+id+"/"+marker).value;
    this.parent.Request("POST", json, id, route, subid);
  }
  ToggleEntryView(id) {
    var entry = this.findEntry(id);
    entry.show = !entry.show;
    var element = document.getElementById("LIST/"+id);
    element.innerHTML = this.renderEntry(entry);
  }
  FilterAdd() {
    var length = this.filter.push(new APIFilter({}, "none"));
    this.parent.Request("GET");
  }
  FilterDelete(id) {
    delete this.filter[id];
    this.filter.splice(id, 1);
    this.parent.Request("GET");
  }
  FilterChange(id) {
    var data = {
      and: document.getElementById("FILTER/"+id+"/AND").value,
      field: document.getElementById("FILTER/"+id+"/FIELD").value
    };
    
    
    if ("none"!=data.field) {
      var eValue = document.getElementById("FILTER/"+id+"/VALUE");
      var eOp = document.getElementById("FILTER/"+id+"/OP");
      if (null!=eOp) {
        data.op = eOp.value;
      }
      if (null==eValue) {
        if (this.mainFields[data.field].type=="DateTime") {
          var eDate = document.getElementById("FILTER/"+id+"/VALUE/DATE");
          var eTime = document.getElementById("FILTER/"+id+"/VALUE/TIME");
          if (null!=eDate && null!=eTime) {
            data.value = eDate.value + " " + eTime.value;
        } }
      } else {
        if (this.mainFields[data.field].type=="Multiple") {
          // Multiple values can be selected
          data.value = Array.from(eValue.selectedOptions).map(el=>el.value);
        } else if (this.mainFields[data.field].type=="Checkbox") {
          data.value = eValue.checked;
        } else if (eValue.value=="__NULL__") {
          // Null is selected
          data.value = null;
        } else {
          // Only one value can be selected/input
          data.value = eValue.value;
    } } }
    
    if ("undefined"!=typeof(this.filter[id])) {
      this.filter[id].Set(data, "none"!=data.field ? this.mainFields[data.field].type : "none");
    }
    this.parent.Request("GET");
  }
  
  SelectChange(id, state) {
    var selectedIndex = this.selected.indexOf(id);
    if (state && 0>selectedIndex) {
      this.selected.push(id);
    } else if (!state && -1<selectedIndex) {
      this.selected.splice(selectedIndex, 1);
    }
    var html = this.renderEntry(this.findEntry(id));
    document.getElementById("LIST/"+id).innerHTML = html;
    if (state) {
      document.getElementById("LIST/"+id).classList.add("list_selected");
    } else {
      document.getElementById("LIST/"+id).classList.remove("list_selected");
    }
  }
  
  /* "private" functions */
  
  parseContent(responseJson, method, id, sub) {
    this.meta = responseJson.meta;
    this.prints = responseJson.prints;
    if (id!=null) {
      // Find id and …
      var element = document.getElementById("LIST/"+id);
      if (id in this.resourcesIndex) {
        if (method=="DELETE" && sub==null) {
          // … delete it
          var group = this.resourcesIndex[id].group;
          group.Delete(id);
          element.parentNode.removeChild(element);
        } else {
          // … replace it
          this.resourcesIndex[id].data = responseJson.content;
          element.innerHTML = this.renderEntry(this.resourcesIndex[id]);
      } }
    } else if (method=="POST") {
      // Insert the newly created into entries
      if (typeof(this.groupsIndex[responseJson.content.GROUP])=="undefined") {
        this.groups.push(new APIGroup(this, responseJson.content.GROUP, [responseJson.content]));
      } else {
        this.groupsIndex[responseJson.content.GROUP].Add(responseJson.content);
      }
      var entryHtml = "<div id='LIST/" + responseJson.content.ID + "' class='list_entry'>" + this.renderEntry(this.resourcesIndex[responseJson.content.ID]) + "</div>";
      var elList = document.getElementById("GROUP/" + responseJson.content.GROUP);
      elList.insertAdjacentHTML("beforeend", entryHtml);
    } else if (method=="GET") {
      // Update everything
      this.admin = responseJson.admin;
      this.options = responseJson.options;
      for (var k in this.filter) {
        delete this.filter[k];
      }
      this.selected = [];
      if ("undefined"!=typeof(responseJson.selected)) {
        this.selected = responseJson.selected;
      }
      this.filter = [];
      for (var l in responseJson.filter) {
        this.filter.push(new APIFilter(responseJson.filter[l], "none"!=responseJson.filter[l].field ? this.mainFields[responseJson.filter[l].field].type : "none"));
      }
      
      for (var iG in this.groups) {
        this.groups[iG].Clear();
        delete this.groups[iG];
      }
      this.groups = [];
      this.groupsIndex = {};
      
      for (var groupID in responseJson.content) {
        this.groups.push(new APIGroup(this, responseJson.content[groupID].ID, responseJson.content[groupID].ENTRIES));
      }
      
      if (this.tplFile!=null) {
        this.reloadTemplate(this.tplFile);
  } } }
  
  findIndexInEntries(id, data) {
    for (var i in data) {
      if (data[i].ID==id) {
        return i;
    } }
    return null;
  }
  
  findEntry(id) {
    if (id in this.resourcesIndex) {
      return this.resourcesIndex[id];
    }
    return null;
  }
  
  reloadTemplate() {
    var _this = this;
    var request = new XMLHttpRequest();
    request.open("GET", this.tplFile, true);
    request.setRequestHeader("Accept", "text/html");
    request.onreadystatechange = function() {
      if (request.readyState != 4 || request.status != 200) return;
      _this.tpl = new TemplateEngine(request.responseText, _this.options);
      _this.render();
    };
    request.send();
  }
  
  renderEntry(entry) {
    var data = entry.data;
    data.SHOWBUTTON = entry.show;
    data.SHOWCONTENT = entry.show;
    data.SELECTBOX = "<input type=checkbox onChange='SelectChange("+entry.ID+", "+(this.selected.includes(entry.ID) ? "false);' checked" : "true);'")+" />";
    return this.tpl.Render(entry.data, "LIST", (this.admin || entry.localAdmin ? ":ADMIN" : ""));
  }
  
  getFilter() {
    var filter = [];
    for (var i in this.filter) {
      filter.push(this.filter[i].Get());
    }
    return filter;
  }
  
  renderFilterEntry(id) {
    var html = "";
    var data = this.filter[id].Get();
    
    // Render and/or combiner
    if (id>0) {
      html+= "<select id='FILTER/"+id+"/AND' onChange='FilterChange("+id+");'><option value='and'"+("and"==data.and ? " selected" : "")+">Und</option>"
            +"<option value='or'"+("or"==data.and ? " selected" : "")+">Oder</option></select>";
    } else {
      html+= "<input type=hidden id='FILTER/"+id+"/AND' value='and' />";
    }
    
    // Render Field selector
    html+= "<select id='FILTER/"+id+"/FIELD' onChange='FilterChange("+id+");'><option value='none'"+("none"==data.field ? " selected" : "")+">Ungefiltert</option>";
    for (var i in this.mainFields) {
      if (this.mainFields[i].filter) {
        html+= "<option value='"+i+"'"+(i==data.field ? " selected" : "")+">"+this.mainFields[i].label+"</option>";
    } }
    html+= "</select>";
    if ("undefined"!=typeof(this.mainFields[data.field])) {
      // Render operator
      // Render value input
      switch (this.mainFields[data.field].type) {
        case "Checkbox":
          html+= "ist <input type=hidden id='FILTER/"+id+"/OP' value='=' />";
          html+= "<input type='checkbox' id='FILTER/"+id+"/VALUE' "+(data.value ? "checked " : "")+"onChange='FilterChange("+id+");' />";
          break;
        case "Multiple":
          html+= "<select id='FILTER/"+id+"/OP' onChange='FilterChange("+id+");'>"
                +"<option value='like'"+("like"==data.op ? " selected" : "")+">enthält</option>"
                +"<option value='not like'"+("not like"==data.op ? " selected" : "")+">enthält nicht</option></select>"
                +"<select id='FILTER/"+id+"/VALUE' onChange='FilterChange("+id+");' multiple>";
          if ("undefined"!=typeof(this.options[data.field])) {
            for (var j in this.options[data.field]) {
              html+= "<option value='"+this.options[data.field][j].ID+"'"+(data.value.includes(this.options[data.field][j].ID) ? " selected" : "")+">"
                    +this.options[data.field][j].NAME+"</option>";
          } }
          html+= "</select>";
          break;
        case "Select":
          html+= "<select id='FILTER/"+id+"/OP' onChange='FilterChange("+id+");'>"
                +"<option value='='"+("="==data.op ? " selected" : "")+">=</option>"
                +"<option value='<>'"+("<>"==data.op ? " selected" : "")+">!=</option></select>"
                +"<select id='FILTER/"+id+"/VALUE' onChange='FilterChange("+id+");'>";
          if ("undefined"!=typeof(this.options[data.field])) {
            for (var k in this.options[data.field]) {
              html+= "<option value='"+this.options[data.field][k].ID+"'"+(data.value==this.options[data.field][k].ID ? " selected" : "")+">"
                    +this.options[data.field][k].NAME+"</option>";
          } }
          html+= "</select>";
          break;
        case "String":
          html+= "<select id='FILTER/"+id+"/OP' onChange='FilterChange("+id+");'><option value='like'"+("like"==data.op ? " selected" : "")+">enthält</option>"
                +"<option value='not like'"+("not like"==data.op ? " selected" : "")+">enthält nicht</option></select>"
                +"<input id='FILTER/"+id+"/VALUE' onChange='FilterChange("+id+");' value='"+data.value+"' style='width: 4em;'/>";
          break;
        case "Integer":
          html+= "<select id='FILTER/"+id+"/OP' onChange='FilterChange("+id+");'><option value='='"+("="==data.op ? " selected" : "")+">=</option>"
                +"<option value='<>'"+("<>"==data.op ? " selected" : "")+">!=</option>"
                +"<option value='<='"+("<="==data.op ? " selected" : "")+"><=</option>"
                +"<option value='>='"+(">="==data.op ? " selected" : "")+">>=</option></select>"
                +"<input type='number' id='FILTER/"+id+"/VALUE' onChange='FilterChange("+id+");' value='"+data.value+"' />";
          break;
        case "DateTime":
          html+= "<select id='FILTER/"+id+"/OP' onChange='FilterChange("+id+");'>"
                +"<option value='<='"+("<="==data.op ? " selected" : "")+"><=</option>"
                +"<option value='>='"+(">="==data.op ? " selected" : "")+">>=</option></select>";
          var datetimesplit = data.value.split(" ");
          html+= "<input type='date' id='FILTER/"+id+"/VALUE/DATE' onChange='FilterChange("+id+");' value='"+datetimesplit[0]+"' />";
          html+= "<input type='time' id='FILTER/"+id+"/VALUE/TIME' onChange='FilterChange("+id+");' value='"+datetimesplit[1]+"' />";
          break;
    } }
    // Render deletor
    html+= "<a href='javascript:FilterDelete("+id+");'><img src='/res/dark/minus.png' /></a>";
    
    return html;
  }
  
  renderFilter() {
    var ret = "<ul><li>Filter:</li>";
    for (var i in this.filter) {
      ret+= "<li id='FILTER/"+i+"'>"+this.renderFilterEntry(i)+"</li>";
    }
    ret+= "<a href='javascript:FilterAdd();'><li><img src='/res/dark/add.png' /></li></a>";
    if (this.filter.length>1) {
      ret+= "<a href='javascript:FilterDelete('ALL');'><li><img src='/res/dark/delete.png' /></li></a>";
    }
    return ret+"</ul>";
  }
  
  render() {
    var metaHtml = "<article id='meta'>" + this.tpl.Render(this.meta, "META", "") + "</article>";
    var entriesHtml = "<article id='LIST/HEADER'><h1>" + this.name + "<span id='loadImage'></span></h1><ul class='inline'>";
    for (var i in this.prints) {
      entriesHtml+= "<a href='javascript:Print(\"" + this.prints[i].ID + "\", \"" + this.prints[i].Bezeichnung + "\");'><li>" 
                  + this.prints[i].Bezeichnung + "<img src='/res/dark/print.png' /></li></a>";
    }
    entriesHtml+= "</ul>";
    entriesHtml+= "<div id='FILTER' class='list_entry_title'>" + this.renderFilter() + "</div></article>";
    
    for (var j in this.groups) {
      entriesHtml+= this.groups[j].Render();
    }
    var addHtml = (this.admin ? "<article id='LIST/ADD'>" + this.tpl.Render(this.options, "ADD.MAIN", "") + "</article>" : "");
    document.getElementById("content").innerHTML = metaHtml + addHtml + entriesHtml;
    //this.renderHelper();
  }
  
  renderHelper(id = null) {
    var _this = this;
    if (null!==id && !!document.getElementById("dropzone_"+id)) {
      this.dropzones[id] = new Dropzone("form#dropzone_"+id, {
      	url: document.getElementById("dropzone_"+id).action,
        //url: this.route + "/" + id + "/Bilder",
        createImageThumbnails: false,
        headers: {
          "Authorization": "Bearer " + _this.parent.jwt,
          "Accept": "application/json"
        },
        dictDefaultMessage: "Datei Drop "
      }).on("sending", function(file, xhr, formData) {
        _this.Cancel(id);
        _this.parent.showMessages(["Datei wird hochgeladen! Dies kann je nach Größe eine Weile dauern."]);
        formData.append("secToken", _this.parent.secToken);
      }).on("complete", function(file) {
        if (file.xhr.status==201) {
          _this.parent.showMessages(["Datei erfolgreich hochgeladen!"]);
        }
        var json = _this.parent.parseResponse(file.xhr.responseText);
        _this.parent.parseJson(json, file.xhr.status);
        _this.parseContent(json, "GET", id);
      });
  } }
}

class APIManager
{
  constructor() {
    this.jwt = this.getCookie("jwt");
    this.emulate = null;
    this.secToken = this.getCookie("secToken");
    this.loggedIn = null;
    this.root = "https://app.fw-innenstadt.de";
    
    this.scanner = new APIScanner(this);
    
    this.routes = {"/index.php/": new APIRoute(this, "/index.php/", {
      link: "/index.php/", name: "Start", image: "/wappen48.png",
      template: "/res/main.html", isPage: true, marker: "START",
      mainFields: []})};
    this.path = this.getCookie("path");
    
    if (this.path=="") {
      this.path = "/index.php/";
      this.activeRoute = this.routes[this.path];
    }
    this.Request("GET");
  }
  
  Request(method, json = {}, id = null, sub = null, subid = null) {
    var _this = this;
    var request = new XMLHttpRequest();
    var path = this.path + (id!=null ? "/" + id + (sub!=null ? "/" + sub + (subid!=null ? "/"+subid : "") : "") : "");
    var accept = "application/json";
    var loadElement = document.getElementById("loadImage");
    if (null!=loadElement) {
      loadElement.innerHTML = "<img src='/res/loading.gif' />";
    }
    
    json.secToken = typeof(this.secToken)!="undefined" ? this.secToken : "";
    
    if ("undefined"!=typeof(this.activeRoute)) {
      var hasParam = false;
      // Append filter if exists
      if (this.activeRoute.filter.length>0) {
        path += "?filter="+encodeURI(JSON.stringify(this.activeRoute.getFilter()));
        hasParam = true;
      }
    
      // Append selection
      if (this.activeRoute.selected.length>0) {
        path += (hasParam ? "&" : "?")+"selected="+encodeURI(JSON.stringify(this.activeRoute.selected));
        hasParam = true;
      }

      // Append print request
      if (typeof(json.Print)!="undefined") {
        path += (hasParam ? "&" : "?")+"print="+json.Print;
        accept = "text/html";
        hasParam = true;
      }
      
      // Append user emulation
      if (this.emulate!=null) {
        path += (hasParam ? "&" : "?")+"emulate="+this.emulate;
        hasParam = true;
      }
    }    
    request.open(method, path, true);
    if (this.jwt!="") {
      request.setRequestHeader("Authorization", "Bearer " + this.jwt);
    }
    if (method!="GET") {
      request.setRequestHeader("Content-Type", "application/json");
    }
    request.setRequestHeader("Accept", accept);
    
    request.onreadystatechange = function() {
      if (request.readyState != 4) return;
      
      var loadElement = document.getElementById("loadImage");
      if (null!=loadElement) {
        loadElement.innerHTML = "";
      }
      
      if ("text/html"==accept) {
        if (200==request.status) {
          var windowUrl = 'about:blank';
          var uniqueName = new Date();
          var windowName = 'Print' + uniqueName.getTime();
          var printWindow = window.open(windowUrl, windowName, 'left=50000,top=50000,width=0,height=0');
          printWindow.document.write("<html><body style='margin: 0; font-family: Calibri, Carlito, Arial Narrow, Liberation Sans Narrow, Arial, Liberation Sans; font-size: 12px;'>");
          printWindow.document.write(request.responseText);
          printWindow.document.write("</body></html>");
          printWindow.document.close();
          printWindow.focus();
        } else {
          _this.showMessages(["Es konnte kein Ausdruck abgerufen werden!"]);
        }
      } else if ("application/json"==accept) {
        var responseJson = _this.parseResponse(request.responseText);
      
        _this.parseJson(responseJson, request.status);
        if (request.status>=200 && request.status<300) {
          if (typeof(_this.activeRoute)!="undefined") {
            _this.activeRoute.parseContent(responseJson, method, id, sub);
    } } } };
    
    request.send(JSON.stringify(json));
  }
  
  Open(path) {
    if (typeof(this.routes[path])!="undefined") {
      this.path = path;
      this.setCookie("path", path);
      this.activeRoute = this.routes[path];
      this.Request("GET");
    } else if ("/index.php/Logout"==path) {
      this.path = path;
      this.setCookie("path", "/index.php/");
      this.activeRoute = this.routes["/index.php/"];
      this.Request("GET");
    }
  }
  
  Login() {
    var button = document.getElementById("loginButton");
    button.disabled = true;
    button.className = "login disabled";
    var json = {
      "login": document.getElementById("loginUser").value,
      "password": document.getElementById("loginPassword").value
    };
    
    this.path = "/index.php/";
    this.activeRoute = this.routes[this.path];
    this.setCookie("path", this.path);
    this.Request("POST", json);
  }
  
  /* "Private" functions */
  
  registerRoutes(routes) {
    for (var i in routes) {
      if (typeof(this.routes[routes[i].link])=="undefined") {
        this.routes[routes[i].link] = new APIRoute(this, routes[i].link, routes[i]);
    } }
    if (typeof(this.activeRoute)=="undefined") {
      this.activeRoute = this.routes[this.path];
    }
    this.renderNavigation();
  }
  
  renderNavigation() {
    let nav = document.getElementById("navigation");
    let selectnav = document.getElementById("navigation_select");
    nav.innerHTML = "";
    selectnav.innerHTML = "";
    for (let i in this.routes) {
      if (this.routes[i].isPage) {
      	let selected = this.routes[i]==this.activeRoute ? " selected" : "";
      	selectnav.innerHTML+= "<option value='" + this.routes[i].route + "'" + selected + ">☰ " + this.routes[i].name + "</option>";

        let cssClass = this.routes[i]==this.activeRoute ? " class='current'" : "";
        if (this.routes[i].image!=null) {
          nav.innerHTML+= "<a href=\"javascript:Open('" + this.routes[i].route + "');\"" + cssClass + "><li><img src='" 
                       + this.routes[i].image + "' title='" + this.routes[i].name + "' /></li></a>";
        } else {
          nav.innerHTML+= "<a href=\"javascript:Open('" + this.routes[i].route + "');\"" + cssClass + "><li>" + this.routes[i].name + "</li></a>";
    } } }
    nav.innerHTML+= "<a href='javascript:Logout();'><li><img src='/res/dark/logout.png' title='Ausloggen' /></li></a>";
    selectnav.innerHTML+= "<option value='_LOGOUT_'>Logout</option>";
  }
  
  parseResponse(responseText) {
    var json = {};
    try {
      json = JSON.parse(responseText);
    } catch (SyntaxError) {
      this.showMessages(["Parsen der Antwort gescheitert. Es folgt die Antwort!", responseText]);
    }
    return json;
  }
  
  parseJson(json, status) {
    if (typeof(json.status.newjwt)!="undefined" && json.status.newjwt!=null) {
      this.setCookie("jwt", json.status.newjwt);
      this.jwt = json.status.newjwt;
    }
    if (typeof(json.status.secToken)!="undefined" && json.status.secToken!=null) {
      this.setCookie("secToken", json.status.secToken);
      this.secToken = json.status.secToken;
    }
    if (typeof(json.status.loggedIn)!="undefined" && json.status.loggedIn) {
      if (json.status.loggedIn!=this.loggedIn) {
        this.changeLoginScreen(true);
      }
      if (status>=300) {
        this.showMessages(["Die Anfrage wurde mit Code " + status + " abgelehnt.<br />(" + this.translateHttpStatus(status) + ")"]);
      }
    } else {
      this.changeLoginScreen(false);
    }
    if (typeof(json.messages)!="undefined") {
      this.showMessages(json.messages);
    }
    if (typeof(json.pages)!="undefined") {
      this.registerRoutes(json.pages);
    }
  }
  
  translateHttpStatus(status) {
    switch (status) {
      case 200: return "OK";
      case 201: return "Erstellt";
      case 400: return "Fehlerhafte Anfrage";
      case 401: return "Anmeldung erforderlich";
      case 403: return "Keine Berechtigung";
      case 404: return "Nicht gefunden";
      case 500: return "Fehler auf Server";
      default: return "" + status + " = Unbekannt";
    }
  }
  
  changeLoginScreen(newStatus) {
    var s = document.getElementById('header').style;
    if (this.loggedIn==null) {
      if (!newStatus) {
        s.display = "block";
        s.opacity = 1;
      }
    } else if (newStatus) {
      (function fadeOut(){(s.opacity-=0.1)<0?s.display="none":setTimeout(fadeOut,40)})();
    } else {
      var button = document.getElementById("loginButton");
      button.disabled = false;
      button.className = "login";
      
      s.display = "block";
      (function fadeIn(){(s.opacity-=-0.1)>1?s.opacity=1:setTimeout(fadeIn,40)})();
    }
    this.loggedIn = newStatus;
    
    document.getElementById("navigation").className = (this.loggedIn ? "" : "hidden");
    document.getElementById("content").className = (this.loggedIn ? "" : "hidden");
  }
  
  showMessages(messages) {
    for (var i in messages) {
      Toastify({
        text: messages[i],
        duration: 3000,
        close: true,
        gravity: "top", // `top` or `bottom`
        position: "right", // `left`, `center` or `right`
        backgroundColor: "linear-gradient(to right, #00b09b, #96c93d)",
        stopOnFocus: true, // Prevents dismissing of toast on hover
      }).showToast();
  } }
  
  setCookie(cname, cvalue, exdays) {
    var d = new Date();
    d.setTime(d.getTime() + (exdays*24*60*60*1000));
    var expires = "expires="+ d.toUTCString();
    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;SameSite=Strict;Secure";
  }
  
  getCookie(cname){
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for(var i = 0; i <ca.length; i++) {
      var c = ca[i];
      while (c.charAt(0) == ' ') {
          c = c.substring(1);
      }
      
      if (c.indexOf(name) == 0) {
          return c.substring(name.length, c.length);
    } }
    return "";
  }
}
var api = new APIManager();

function ToggleEntryView(id) {api.activeRoute.ToggleEntryView(id);}
function Open(path) {api.Open(path);}
function OpenSelected() {
	let s = document.getElementById("navigation_select").value;
	if (s=="_LOGOUT_") {Logout();} else {api.Open(s);}
}
function Logout() {api.Open("/index.php/Logout");}
function FilterAdd() {api.activeRoute.FilterAdd();}
function FilterDelete(id) {api.activeRoute.FilterDelete(id);}
function FilterChange(id) {api.activeRoute.FilterChange(id);}
function Add() {api.activeRoute.Add();}
function Delete(id, data = {}) {api.Request("DELETE", data, id);}
function Edit(id) {api.activeRoute.Edit(id);}
function Cancel(id) {api.activeRoute.Cancel(id);}
function Save(id) {api.activeRoute.Save(id);}
function SubShowAdd(id, markers) {api.activeRoute.SubShowAdd(id, markers);}
function SubShowScan(id, subroute, parsers) {api.activeRoute.SubShowScan(id, subroute, parsers);}
function HideScanner() {api.scanner.Stop(); document.getElementById("qrscanner").style.display = "none";}
function SwitchCamera() {api.scanner.SwitchCamera();}
function SubAdd(id, markers, subroute) {api.activeRoute.SubAdd(id, markers, subroute);}
function SubDelete(id, subroute, subid = null) {api.Request("DELETE", {}, id, subroute, subid);}
function Request(method, data, id = null, subroute = null, subid = null) {api.Request(method, data, id, subroute, subid);}
function Print(printID, printName) {api.Request("GET", {Print: printID, PrintName: printName});}
function SelectChange(id, state) {api.activeRoute.SelectChange(id, state);}
