class TemplateEngine
{
  constructor(tpl, adminData) {
    this.tpl = tpl;
    this.adminData = adminData;
    this.modeFallback = {
      ":EDIT": ":ADMIN",
      ":ADMIN": "",
      "": null
    };
  }
  
  Render(content, marker, mode = "") {
    return this.render(marker, content, this.extractSection(this.tpl, marker, mode), mode);
  }
  
  render(sections, content, tpl, mode = "") {
    // content has to be an object of `marker` => `data`
    // data can be a primitive, an array, or an object
    // Boolean corresponds to a conditional section, call replaceCondition
    // String and number correspond to a simple marker, call replaceMarker for it
    // Array and object always correspond to a section
    // Object can be handed recursively to a new instance of render. Be sure to extract the correct section of tpl though
    // If data is an array, iterate over it and hand every entry to a new instance like with object. Concatenate the results
    if (typeof(content)!="object" || tpl=="")
      return "";
    var ret = tpl;
    for (var marker in content) {
      var sectionMarker = (sections!="" ? sections+"."+marker : marker);
      var type = typeof(content[marker]);
      
      if (mode==":EDIT" && sections.indexOf("MAIN")!=-1 && typeof(this.adminData[marker])!="undefined") {
        var data = this.adminData[marker];
        var admin_tpl = this.extractSection(ret, sectionMarker, mode);
        var admin_ret = "";
        for (var i in data) {
          if (content[marker]==null) {
            data[i].SELECTED = data[i].ID=="__NULL__";
          } else if (Array.isArray(content[marker])) {
            var selected = false;
            for (var j in content[marker]) {
              if (data[i].ID==content[marker][j].ID) {
                selected = true;
              }
            }
            data[i].SELECTED = selected;
          } else {
            data[i].SELECTED = data[i].ID==content[marker].ID;
          }
          
          admin_ret+= this.render(sectionMarker, data[i], admin_tpl, "");
        }
        ret = this.replaceSection(ret, sectionMarker, admin_ret, "");
      } else {
        if (content[marker]==null) {
          ret = this.replaceSection(ret, sectionMarker, "", mode);
          ret = this.replaceMarker(ret, sectionMarker, "__NULL__", mode);
        } if (type=="boolean") {
          ret = this.replaceCondition(ret, sectionMarker, content[marker], mode);
        } else if (type=="string" && "__CASE__:"==content[marker].substr(0, 9)) {
          ret = this.replaceSwitch(ret, sectionMarker, content[marker].split(":"), mode);
        } else if (type=="string" || type=="number") {
          ret = this.replaceMarker(ret, sectionMarker, content[marker], mode);
        } else if (type=="object" && Array.isArray(content[marker])) {
          var arr_tpl = this.extractSection(ret, sectionMarker, mode);
          var arr_ret = "";
          for (var ind in content[marker]) {
            arr_ret+= this.render(sectionMarker, content[marker][ind], arr_tpl, mode);
          }
          ret = this.replaceSection(ret, sectionMarker, arr_ret, mode);
        } else if (type=="object") { // Hopefully a JSON
          ret = this.replaceSection(ret, sectionMarker, this.render(sectionMarker, content[marker], this.extractSection(ret, sectionMarker, mode), mode), mode);
        }
      }
    }
    return ret;
  }
  
  makeMarker(marker) {
    return "###" + marker + "###";
  }
  
  makeSectionMarker(marker, pos) { //creates START or END marker of section
    return "<!-- " + marker + " " + pos + " -->";
  }
  
  findMarkerModes(tpl, section) {
    var ret = [];
    var sectionMarker = this.makeMarker(section+":");
    var markerLength = 5 + sectionMarker.length - 3; // 5 = "<!-- ", 3 = "###"
    var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, markerLength);
    for (var startIndex = 0; startIndex < tpl.length;) {
      var foundIndex = tpl.indexOf(anyMarker, startIndex);
      if (foundIndex==-1) {
        startIndex = tpl.length;
      } else {
        var markerEnd = tpl.indexOf("###", foundIndex + markerLength);
        var mode = tpl.substr(foundIndex + markerLength - 1, markerEnd - foundIndex - markerLength + 1);  // Should be e.g. :EDIT
        var endMarker = this.makeSectionMarker(this.makeMarker(section + mode), "END");
        var endPos = tpl.indexOf(endMarker, markerEnd);
        if (endPos==-1) {
          startIndex = tpl.length;
        } else {
          startIndex = endPos + markerLength;
          ret.push(mode);
    } } }
    return ret;
  }
  
  deleteSection(tpl, section) {
    var sectionMarker = this.makeMarker(section);
    var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, 5 + sectionMarker.length); // 5 = "<!-- "
    var endMarker = this.makeSectionMarker(sectionMarker, "END");
    var startPos = tpl.indexOf(anyMarker);
    var endPos = tpl.lastIndexOf(endMarker) + endMarker.length;
    if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
      return tpl;
    }
    var ret = (startPos>0 ? tpl.substr(0, startPos) : "");
    if (endPos<tpl.length) ret+= tpl.substr(endPos);
    return ret;
  }
  
  findAndDeleteUnneededModes(tpl, section, mode = "") {
    var ret = tpl;
    // Find and destroy unneeded mode siblings
    var modes = this.findMarkerModes(tpl, section);
    var chosenMode = mode;
    while (!modes.includes(chosenMode) && this.modeFallback[chosenMode]!=null) {
      chosenMode = this.modeFallback[chosenMode];
    }
    for (var i in modes) {
      if (modes[i]!=chosenMode) {
        ret = this.deleteSection(ret, section + modes[i]);
      }
    }
    if (chosenMode!="") {
      ret = this.deleteSection(ret, section);
    }
    return {"tpl": ret, "mode": chosenMode};
  }
  
  extractSection(tpl, section, mode = "") { //Extracts a section from a template
    var sectionMarker = this.makeMarker(section + mode);
    var startMarker = this.makeSectionMarker(sectionMarker, "START");
    var endMarker = this.makeSectionMarker(sectionMarker, "END");
    var startPos = tpl.indexOf(startMarker) + startMarker.length;
    var endPos = tpl.lastIndexOf(endMarker);
    if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
      return (this.modeFallback[mode]!=null ? this.extractSection(tpl, section, this.modeFallback[mode]) : "");
    }
    return tpl.substr(startPos,endPos-startPos);
  }
  
  replaceSection(tpl, section, value, mode = "") { //replaces a section in a template with a value
    var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
    
    var sectionMarker = this.makeMarker(section + modeTpl.mode);
    var startMarker = this.makeSectionMarker(sectionMarker, "START");
    var endMarker = this.makeSectionMarker(sectionMarker, "END");
    var startPos = modeTpl.tpl.indexOf(startMarker);
    var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
    if ((startPos==-1) || (endPos==-1) || (endPos<=startPos)) {return modeTpl.tpl;}
    var ret = (startPos>0 ? modeTpl.tpl.substr(0, startPos) : "");
    ret+= value;
    if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
    return ret;
  }
  
  replaceCondition(tpl, section, bool, mode = "") { // selects one of two sections in a template
    // Template looks like this:
    // <!-- ###SECTION### IF -->
    //   True text
    // <!-- ###SECTION### ELSE -->
    //   False text
    // <!-- ###SECTION### END -->
    var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
    
    var sectionMarker = this.makeMarker(section + modeTpl.mode);
    var ifMarker = this.makeSectionMarker(sectionMarker, "IF");
    var elseMarker = this.makeSectionMarker(sectionMarker, "ELSE");
    var endMarker = this.makeSectionMarker(sectionMarker, "END");
    
    var startPos = modeTpl.tpl.indexOf(ifMarker);
    var else_startPos = modeTpl.tpl.indexOf(elseMarker);
    var else_endPos = else_startPos + elseMarker.length;
    var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
    
    if ((startPos==-1) || (else_startPos==-1) || (endPos==-1) || (endPos<=startPos)) {
      if (bool) {
        return modeTpl.tpl;
      } else {
        return this.replaceSection(modeTpl.tpl, section, "", modeTpl.mode);
      }
    }
    
    var value = "";
    if (bool) {
      // Show if-part
      value = modeTpl.tpl.substr(startPos, else_startPos - startPos);
    } else {
      // Show else-part
      value = modeTpl.tpl.substr(else_endPos, endPos - else_endPos - endMarker.length);
    }
    
    var ret = (startPos>0 ? modeTpl.tpl.substr(0, startPos) : "");
    ret+= value;
    if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos);
    return ret;
  }
  
  replaceSwitch(tpl, section, keys, mode = "") { // selects one or more of a variable number of cases from a section in a template
    // Template looks like this:
    // <!-- ###SECTION### SWITCH -->
    //   Default text
    // <!-- ###SECTION### CASE_A -->
    //   Text A
    // <!-- ###SECTION### CASE_B -->
    //   Text B
    // <!-- ###SECTION### END -->
    var modeTpl = this.findAndDeleteUnneededModes(tpl, section, mode);
    var sectionMarker = this.makeMarker(section + modeTpl.mode);
    
    var switchMarker = this.makeSectionMarker(sectionMarker, "SWITCH");
    var endMarker = this.makeSectionMarker(sectionMarker, "END");
    var anyMarker = this.makeSectionMarker(sectionMarker, "").substr(0, 5 + sectionMarker.length); // 5 = "<!-- "
    
    var switchPos = modeTpl.tpl.indexOf(switchMarker);
    var endPos = modeTpl.tpl.lastIndexOf(endMarker) + endMarker.length;
    if (switchPos==-1 || endPos==-1) {return modeTpl.tpl;}
    
    var value = "";
    for (var i = 1; i<keys.length; i++) {
      var keyMarker = this.makeSectionMarker(sectionMarker, keys[i]);
      var keyPos = modeTpl.tpl.indexOf(keyMarker);
      
      if (keyPos==-1) {
        keyPos = switchPos + switchMarker.length; // Case `key` wasn't found, use default case
      } else {
        keyPos+= keyMarker.length;
      }
      // Find the start of the next marker.
      var nextPos = modeTpl.tpl.indexOf(anyMarker, keyPos);
      if (nextPos==-1) {
        // Shouldn't happen. If it does, endMarker is before keyMarker
        nextPos = endPos;
      }
      
      value+= modeTpl.tpl.substr(keyPos, nextPos - keyPos);
    }
    
    
    var ret = (switchPos>0 ? modeTpl.tpl.substr(0, switchPos) : "");
    ret+= value;
    if (endPos<modeTpl.tpl.length) ret+= modeTpl.tpl.substr(endPos); 
    return ret;
  }
  
  replaceMarker(tpl, marker, value)   { //replaces a marker in a template with a value
    return tpl.replaceAll(this.makeMarker(marker), value);
  }
  
/*  replaceMarkerArray(tpl, array) { //replaces all markers in a template with values from an array (index defines marker)
    for (var ind in array)
    {
      tpl = tpl.replaceAll(this.makeMarker(ind), array[ind]);
    }
    return tpl;
  }*/
}

