'use strict';  // all variables must be defined
/**
 * 
 */
const arGroups = ["container", "view"]
function webiqVar() {
  // private variables
  let _this = this        // refernce to me, requested for funcs called by events
  let _iUsed = 0          // counter: cross ref found
  let _iUnused = 0        // counter: no cross ref found
  let _arFiles = []       // script files names & content
  let _iRowMax = 0        // number of table rows
  let _iRow = 0           // loop var for table row
  let _iRowMaxPercent = 0 // for progress calc
  let _iRowPercent = 0    //
  let _jModel = {}        // json of webIq model
  let _jGroups = {}       // group guid + name
  let _jAlarm = {}        // json of alarms
  let _jRecipe = {}       // json of recipes
  let _jTrend = {}        // json of trends
  let _iTypeChanged = 0;  // counter
  let _iNotFound = 0;     // counter
  let _iFound = 0;        // counter
  let _iAll = 0;          // counter
  let _sOut = ""          // cross ref list
  let _jToken = null
  let _sHost = "https://127.0.0.1:8080/"
  let _sSymbols = ["iCounter", "iTest", "iTestCopy", "rTest", "rTestCopy"]
  let _bRead = false
  let _SpyGroup = []

  /*
  _this.writeFile = async function (sFile, sText) {
    function encodeBase64Unicode(sIn) {
      // 1. code string in UTF-8 bytes
      //    `encodeURIComponent` code special cahrs as `%xx`
      //    `escape` could not be used, because it is not working for everything
      const utf8Encoded = encodeURIComponent(sIn).replace(
        /%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
          return String.fromCharCode("0x" + p1);
        }
      );
      return btoa(utf8Encoded);
    }
    // open file. Problem file is created in projectdata not in workspace
    // file handling can't be used
    let jCmd = cmdFsOpen
    jCmd.data.path = sFile
    jCmd.data.open_mode = ["out", "trunc"]
    let jOpen
    try {
      // jOpen = await wsSendRecv(jCmd).catch((err) => { msgBox("error", err.message); return });
      jOpen = await wsSendRecv(jCmd)
    } catch (e) {
      msgBox("error", e.error.message); return;
    }

    // convert to Base64, which is requested by WebIQ fs.write
    // split string into max. length supported be fs.write
    let sBase64 = encodeBase64Unicode(sText)
    const iLenSeg = 10
    const iLen = sBase64.length
    for (let i = 0; i < iLen; i += iLenSeg) {
      const sSeg = sBase64.substring(i, i + iLenSeg)
      jCmd = cmdFsWrite
      jCmd.data.file_id = jOpen.data
      jCmd.data.data = sSeg
      await wsSendRecv(jCmd).catch((e) => { msgBox("error", e.error.message); return });
    }

    // close file
    jCmd = cmdFsClose
    jCmd.data = jOpen.data
    jRes = await wsSendRecv(jCmd).catch((err) => { msgBox("error", err.message); return });
  }
    */
  // Zur Überprüfung (Dekodieren):
  // function decodeBase64Unicode(str) {
  //   return decodeURIComponent(
  //     atob(str)
  //       .split("")
  //       .map(function (c) {
  //         return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
  //       })
  //       .join("")
  //   );
  // }
  // console.log(decodeBase64Unicode(base64EncodedUnicode)); // Ausgabe: Hallo, Welt! 👋 ÄÖÜ

  _this.readRcpTmpls = async function () {
    // read recipe templates definition
    let jCmd = cmdRecipeTemplateList
    jCmd.data.include_items = true
    _jRecipe = await wsSendRecv(jCmd).catch((err) => { msgBox("error", err.message); return });
    return _jRecipe
  }
  _this.readDataLayer = async function () {
    if (!_bRead) {
      _bRead = true
      _this.readDataLayerLoop()
    } else {
      _bRead = false
    }
  }
  _this.readDataLayerLoop = async function () {
    let sReq = ""
    let jRes = {}
    let sVal = ""
    if (_jToken === null) {
      for (let i = 0; i < 5; i++) {
        _sSymbols.push("iCounter" + i)
        _sSymbols.push("iCounter")
      }
      try {
        sReq = _sHost + 'identity-manager/api/v1.0/auth/token';
        jRes = await axios.post(sReq, { name: "boschrexroth", password: "sts" });
      }
      catch (err) {
        msgBox("error", "Axios unable to post request: " + err + '\n' + sReq)
        return err
      }
      _jToken = { headers: { authorization: 'Bearer ' + jRes.data.access_token } }
    }

    _sOut = ""
    for (let i = 0; i < _sSymbols.length; i++) {
      sReq = _sHost + 'automation/api/v2/nodes/plc/app/Application/sym/GVL/' + _sSymbols[i]
      try {
        jRes = await axios.get(sReq, _jToken)
        sVal = "" + jRes.data.value
      } catch (err) {
        sVal = "???"
      }
      _sOut += _sSymbols[i].padStart(10, " ") + ":" + sVal.padStart(10, " ") + "\n"
    }
    divTabDetail.innerHTML = "<pre>" + _sOut + "</pre>"
    if (_bRead) {
      setTimeout(_this.readDataLayerLoop, 400)
    }
  }

  _this.addVar2Group = async function (sWebIq, sOpcUa) {
    if (tableData.iRowsSelected === 0) {
      await msgBox("warning", eval(l.nothingSelected))
      return;
    }
    for (let i = tableData.iRowFirst; i <= tableData.iRowLast; i++) {
      if (tableRowSelected(table.rows[i])) {
        if (table.rows[i].cells[COL_SOURCE].outerText !== SRC_INTERNAL) {
          let sItemName = table.rows[i].cells[COL_ITEM].outerText
          add2Group(sItemName, "_" + sItemName)
          let sTmp = ""
          for (let j = 0; j < _SpyGroup.length; j++)
            sTmp += _SpyGroup[j].sWebIq + "\t"
          setLStorage("wiqao-spyGroup", sTmp)
        }
      }
    }
    console.log("***************")
    for (let i = 0; i < _SpyGroup.length; i++)
      console.log(_SpyGroup[i].sWebIq.padEnd(20, " ") + " : " + _SpyGroup[i].sOpcUa)
  }

  /**
  * If sWebIq is not contained in _SpyGroup, it is added
  * @param {string} sWebIq name of var in WebIQ
  * @param {string} sOpcUa name of var in OpcUa
  * @returns true: added false: not added
  */
  function add2Group(sWebIq, sOpcUa) {
    let bExists = _SpyGroup.find(e => e.sWebIq === sWebIq)
    if (!bExists) {
      let jTmp = { sWebIq: sWebIq, sOpcUa: sOpcUa }
      _SpyGroup.push(jTmp)
      return true
    }
    return false
  }
  /**
  * deletes selected variables
  */
  _this.delete = async function () {
    let jCmd = {}
    let sCmd = ""
    _iAll = 0
    const iROWS = table.rows.length
    if (tableData.iRowsSelected === 0) {
      await msgBox("warning", eval(l.nothingSelected))
      return
    } else {
      const resp = await msgBox("confirm", eval(l.confirmDeleteVars))
      if (resp === null)
        return
    }

    // set name of project to connect to
    sTarget = sWsGuid;
    // loop over all rows
    for (let iRow = ROW_DATA_FIRST; iRow < iROWS; iRow++) {
      if (tableRowSelected(table.rows[iRow])) {
        _iAll++;
        // setup delete command
        jCmd = cmdVariableDel;
        jCmd.data.name = table.rows[iRow].cells[COL_ITEM].outerText;
        await wsSendRecv(jCmd).catch((err) => { abortJS(err.message); });
      }
    }
    openWorkspaceProject(iProjectLoadId);
    // display user information
    await msgBox("info", eval(l.okDeleteVars));
  }

  /**
   * Checks if opcUa variables
   * - still exists in opc browse
   * - data type changed
   * - data range of (PLC>HMI)
   */
  _this.checkOpcUa = async function (bStart) {
    const sColorNotFound = "red"        // background color "variable not found"
    const sColorTypeChanged = "yellow"  // background color "data type changed"
    const sColorTypeRange = "orange"    // background color "range of var in PLC>HMI"
    let jCmd = {}                       // sent json command
    let jResp = {}                      // command response as json

    if (bStart) {
      // arFound_gb = []
      if (tableData.iRowsSelected === 0) {
        await msgBox("warning", eval(l.nothingSelected))
        return
      }

      sTarget = sWsGuid;  // set name of project to connect to
      _iRowMax = tableData.iRowLast + 1
      _iRow = tableData.iRowFirst
      _iRowMaxPercent = tableData.iRowLast - tableData.iRowFirst
      _iRowPercent = 0
      _iFound = 0
      _iNotFound = 0
      setTimeout(() => { _this.checkOpcUa(false) }, 0)
      await progress("on")
      return
    } else {
      if (tableRowSelected(table.rows[_iRow])) {
        let oItem = arList.find(x => x.name === table.rows[_iRow].cells[COL_ITEM].innerHTML);
        oItem.check = SPACE + eval(l.ok)
        // process only visible & selected items
        if (tableRowSelected(table.rows[_iRow]) & !table.rows[_iRow].hidden & (table.rows[_iRow].cells[COL_SOURCE].outerText !== "Internal")) {
          // get data of item
          jCmd = cmdVariableGet;
          jCmd.data.name = table.rows[_iRow].cells[COL_ITEM].outerText;
          jResp = await wsSendRecv(jCmd).catch((err) => {
            progress("off")
            msgBox("error", err.message)
            return
          });

          async function browseProps(sNode) {
            const sParentNode = sNode.substring(0, sNode.lastIndexOf('/'))
            const sChildNode = sNode.substring(sNode.lastIndexOf('/') + 1)
            jCmd = cmdioExtBrowse;
            jCmd.data.name = jResp.data.source;// io name
            jCmd.data.node_id.node = sParentNode;
            jCmd.data.node_id.namespace = 8;
            let jRespBrowse = null
            try {
              jRespBrowse = await wsSendRecv(jCmd, false)
              if (jRespBrowse.error) {
                if (jRespBrowse.error.errc === -2136080384) {
                  progress("off")
                  msgBox("error", eval(l.noOpcUaConnection))
                  return null
                }
                table.rows[_iRow].cells[COL_SOURCE].style.backgroundColor = sColorNotFound;
                oItem.check = eval(l.noPlcVar)
                _iNotFound++;
                return false
              }
            } catch (e) { }
            for (let i = 0; i < jRespBrowse.data.length; i++) {
              if (jRespBrowse.data[i].browse_name.node === sChildNode) {
                return jRespBrowse.data[i]
              }

            }
            return false
          }

          // browse item from OpcUa server. Failes if Server is not running
          if (jResp.data.source !== null) {
            let myProps = await browseProps(jResp.data.plc_name.value)
            if (myProps === null) {
              _iNotFound++;
              return
            } else if (myProps === false) {
              table.rows[_iRow].cells[COL_SOURCE].style.backgroundColor = sColorNotFound;
              oItem.check = eval(l.noPlcVar)
              _iNotFound++;
            } else {
              // browse only returns ok, but no data when item is used.
              let sTypeWebIQ = jResp.data.type.name
              let sTypePlc = myProps.data_type.type
              let bTypeChanged = false
              if (sTypeWebIQ === "int") {
                if (!((sTypePlc.startsWith("i")) || (sTypePlc.startsWith("u")))) {
                  bTypeChanged = true
                }
              } else if (sTypeWebIQ === "float") {
                if (!["float", "double"].includes(sTypePlc)) {
                  bTypeChanged = true
                }
              } else if (sTypeWebIQ !== sTypeWebIQ) {
                bTypeChanged = true
              }
              if (bTypeChanged) {
                //console.warn(jCmd.data.node_id.node + " plc:" + sTypePlc + " webIQ:" + sTypeWebIQ);
                table.rows[_iRow].cells[COL_SOURCE].style.backgroundColor = sColorTypeChanged
                oItem.check = eval(l.plcTypeChanged) + sTypePlc
                _iTypeChanged++

                // } else if (["i64", "u64", "u32", "double"].includes(sTypePlc)) {
              } else if (["i64", "u64"].includes(sTypePlc)) {
                oItem.check = eval(l.plcRange) + sTypePlc
              }

              table.rows[_iRow].cells[COL_ITEM].style.backgroundColor = ""
              _iFound++;
            }
          }
        }
        table.rows[_iRow].cells[COL_CHECK].innerHTML = oItem.check
        if (_iRow >= _iRowMax) {
          i = iMax
        }
      }
      // select next row
      _iRow++
      if (_iRow < _iRowMax) {
        _iRowPercent++
        progress("", 100 * (_iRowPercent) / _iRowMaxPercent)
        setTimeout(() => { _this.checkOpcUa(false) }, 0)
        return
      } else {
        _iAll = _iFound + _iNotFound;
        progress("off")
        if (_iAll === 0) {
          await msgBox("warning", eval(l.noOpcUaVarSelected));
        } else if ((_iNotFound + _iTypeChanged) > 0) {
          let sType = (_iNotFound > 0) ? "error" : "warning"
          await msgBox(sType, eval(l.errorOpcUaCheck));
        } else {
          await msgBox("info", eval(l.okOpcUaCheck));
        }
      }

    }
  }

  /**
   * create cross reference list. 
   * Fct. call itself to enable screen update until list is created
   * @param {bool} bStart true=first call false=2nd,3rd,... call
   * @returns nothing
   */
  _this.crossRef = async function (bStart = true) {
    let bDetails = (tableData.iRowsSelected === 1) | chkCrossRef.checked   // true: show detail list false: hide it
    // bDetails = false

    let jComposites = {} // json with composite name & guid

    // get data from files and via cmd's
    if (bStart) {
      _sOut = ""
      _iRowMax = tableData.iRowLast + 1
      _iRow = tableData.iRowFirst
      _iUsed = 0    // cross ref found
      _iUnused = 0  // no cross ref found
      _arFiles = []
      let sIn = ""

      // set name of project to connect to
      sTarget = sWsGuid;
      if (!chkCrossRef.checked) {
        if (tableData.iRowsSelected === 0) {
          await msgBox("warning", eval(l.nothingSelected))
          return
        }
      }

      await progress("on")

      // read groups (Composites) guid with name
      sIn = await fetchTextFile(FILE_GROUPS)
      let jGroups = JSON.parse(sIn)
      for (let sGuid in jGroups) {
        _jGroups[sGuid] = jGroups[sGuid].label
      }
      jGroups = {}

      // read in model
      sIn = await fetchTextFile(FILE_APP_MODEL)
      _jModel = JSON.parse(sIn)
      sIn = ""

      // read alarm definition
      _jAlarm = await wsSendRecv(cmdAlarmList).catch((err) => {
        msgBox("error", err.message)
        return
      });

      // read recipe templates definition
      _jRecipe = await wsSendRecv(cmdRecipeTemplateList).catch((err) => {
        msgBox("error", err.message)
        return
      });

      // read trend definition
      _jTrend = await wsSendRecv(cmdTrendItemList).catch((err) => {
        msgBox("error", err.message)
        return
      });

      // read ui actions & local scripts names& content
      let arFileNames = [FILE_UIACTIONS, FILE_LOCALSCRIPTS]
      for (let i = 0; i < arFileNames.length; i++) {
        sIn = await fetchTextFile(arFileNames[i])
        let jFiles = JSON.parse(sIn)
        for (let sKey in jFiles) {
          let jData = {
            url: `${DIR_ROOT}${jFiles[sKey].url}`,
          }
          jData.content = await fetchTextFile(jData.url)
          _arFiles.push(jData)
        }
      }
      setTimeout(() => { _this.crossRef(false) }, 0); // call me again, to execute next step
    } else {
      let sVar = ""
      if (chkCrossRef.checked) {
        sVar = txtCrossRef.value
      } else {
        sVar = table.rows[tableData.iRowFirst].cells[COL_ITEM].innerHTML
      }

      let arGroups = []

      // debug info
      if (false) {
        // collect node guid's (panel,tab,swipe screens)
        for (let x in _jModel.templates) {
          if (x.startsWith("node-")) {
            arGroups.push(x)
          }
        }
        // search where screens are used
        arFound_gb = []
        // loop over alls found panels
        for (let i = 0; i < arGroups.length; i++) {
          for (let j in _jModel.templates) {
            // loop over all template children
            for (let k = 0; k < _jModel.templates[j].length; k++) {
              getPanel(_jModel.templates[j][k], 0, "", arGroups[i])
            }
          }
        }

        if (bDetails) showCrossRefArray("PANEL", arFound_gb)
        for (let i = 0; i < arFound_gb.length; i++) {
          let arW = arFound_gb[i].split(" : ")
          jComposites[arW[1]] = arW[0]
        }
        arFound_gb = []

        _sOut += "<y>------------------ TEMPLATES ------------------------------</y>\n"
        for (let x in _jModel.templates) {
          // 
          let r = _jModel.templates[x]

          if (x.startsWith("group-")) {
            _sOut += `${_jGroups[x]} ${x}\n`
          } else if (x.startsWith("node-")) {
            for (let i = 0; i < r.length; i++) {
              _sOut += `${jComposites[x]}${FS_PATH}${r[i].controlConfig.name} ${x}\n`
            }
            _sOut += "\n"
          }
          for (let y = 0; y < r.length; y++) {
            if (bDetails) getVarModel(r[y], 0, "", sVar);
          }
          if (bDetails) showCrossRefArray("Model", arFound_gb)
          arFound_gb = []
        }
      }
      arFound_gb = []
      for (let x in _jModel.templates) {
        if (x.startsWith("group-")) {
          arGroups.push(x)
          getVarModel2(_jModel.templates[x][0], 0, _jGroups[x] + FS_PATH, sVar, _jModel, chkCrossRef.checked);
        }
      }
      showCrossRefArray("Composites", arFound_gb)

      let iMax = 20 // execute multiple rows in one cylce
      for (let i = 0; i < iMax; i++) {
        let iVarInFile = 0

        if (chkCrossRef.checked) {
          sVar = txtCrossRef.value
        } else {
          sVar = table.rows[_iRow].cells[COL_ITEM].innerHTML
        }
        let oItem = {}
        if (!chkCrossRef.checked) {
          oItem = arList.find(x => x.name === sVar);
        }
        oItem.check = ""

        if (bDetails) {
          arFound_gb = []
          getVarModel2(_jModel.templates["root"][0], 0, "", sVar, _jModel, chkCrossRef.checked);
          showCrossRefArray("Model " + sVar, arFound_gb)
        }

        let bOk = false
        if (chkCrossRef.checked) {
          bOk = true
        } else {
          // process only visible & selected items
          bOk = tableRowSelected(table.rows[_iRow]) & !table.rows[_iRow].hidden
        }
        if (bOk) {
          // check model
          let keysModel = getKeys(_jModel, sVar, "")
          // oItem.check = "M" + keysModel.length
          oItem.check += (keysModel.length) ? "M" : "-"

          // check alarms
          let keysAlarm = getKeys(_jAlarm, sVar, "")
          oItem.check += (keysAlarm.length) ? "A" : "-"
          if (bDetails)
            showCrossRefArray("Alarms " + sVar, getVarAlarm(_jAlarm, sVar))

          // check recipes
          let keysRecipe = getKeys(_jRecipe, sVar, " ")
          oItem.check += (keysRecipe.length) ? "R" : "-"
          if (bDetails)
            showCrossRefArray("Recipes " + sVar, getVarRecipe(_jRecipe, sVar))

          // check trends
          let keysTrend = getKeys(_jTrend, sVar, " ")
          oItem.check += (keysTrend.length) ? "T" : "-"
          // oItem.check += " T" + keysTrend.length
          if (bDetails)
            showCrossRefArray("Trend " + sVar, getVarTrend(_jTrend, sVar))

          // check files
          let regEx = null
          if (chkCrossRef.checked) {
            regEx = new RegExp(`(${sVar})`, "g")
          } else {
            regEx = new RegExp(`(["'\`"]${sVar}["'\`])`, "g")
          }
          let arResult = []
          for (let i = 0; i < _arFiles.length; i++) {
            if (_arFiles[i].content.match(regEx)) {
              iVarInFile++
              if (bDetails) {
                getVarFile(_arFiles[i].url, _arFiles[i].content, regEx, arResult)
              }
            }
          }
          if (bDetails)
            showCrossRefArray("File  " + sVar, arResult)

          // oItem.check += "F" + iVarInFile
          oItem.check += (iVarInFile) ? "F" : "-"

          let iCount = keysModel.length + keysAlarm.length + keysRecipe.length + keysTrend.length + iVarInFile
          oItem.check = iCount + "x:" + oItem.check
          if (iCount === 0) {
            _iUnused++
          } else {
            _iUsed++
          }
        }
        if (!chkCrossRef.checked) {
          table.rows[_iRow].cells[COL_CHECK].innerHTML = oItem.check
        }
        _iRow++
        if (_iRow >= _iRowMax) {
          i = iMax
        }
      }
      // highlight item name
      let regex = new RegExp(sVar, 'g');
      divTabDetail.innerHTML = "<pre>" + _sOut.replace(regex, `<z>${sVar}</z>`) + "</pre>"

      if (_iRow < _iRowMax) {
        progress("", 100 * (_iRow + 1) / _iRowMax)
        setTimeout(() => { _this.crossRef(false) }, 0);
      } else {
        progress("", 100 * (_iRow + 1) / _iRowMax)
        progress("off")
        if (tableData.iRowsSelected > 1) await msgBox("info", eval(l.crossRefInfo));
      }
    }
  }
  /**
  * appends strings from arResult to _sOut
  * @param {string} sTitle description of data
  * @param {string[]} arResult data to display
  */
  function showCrossRefArray(sTitle, arResult) {
    if (arResult.length > 0) {
      let iLen = (80 - sTitle.length) / 2
      iLen = (iLen < 0) ? 0 : iLen
      _sOut += `<y>${"".padEnd(iLen, "*")} ${sTitle} ${"".padEnd(iLen, "*")}</y>\n`
      for (let i = 0; i < arResult.length; i++) {
        _sOut += arResult[i] + "\n"
      }
    }
  }
  /**
   * Returns array with matches (Recipe-name)
   * @param {*} jData json object with alarms
   * @param {*} sVar variable name to search 
   */
  function getVarRecipe(jData, sVar) {
    let arResult = []
    let iMax = jData.data.total_count
    for (let i = 0; i < iMax; i++) {
      let jRcp = jData.data.templates[i].items
      for (let k in jRcp) {
        if (jRcp[k].item_alias.match(sVar)) {
          arResult.push(jData.data.templates[i].template_name)
        }
      }
    }
    return arResult
  }
  /**
  * Returns array with matches (Trend-name)
  * @param {*} jData json object with alarms
  * @param {*} sVar variable name to search
  */
  function getVarTrend(jData, sVar) {
    let arResult = []
    let iMax = jData.data.total_count
    for (let i = 0; i < iMax; i++) {
      let jItem = jData.data.items[i]
      if (jItem.item_alias.match(sVar)) {
        arResult.push((jItem.recorder_name))
      }
    }
    return arResult
  }
  /**
   * Returns array with matches (Alarm-id: item or context_items)
   * @param {*} jData json object with alarms
   * @param {*} sVar  variable name to search 
   */
  function getVarAlarm(jData, sVar) {
    let arResult = []
    let iMax = jData.data.total_count
    for (let i = 0; i < iMax; i++) {
      let jA = jData.data.alarms[i]
      let sId = "" + jA.index
      sId = sId.padStart(4, "0")
      // check item
      if (jA.item.match(sVar)) {
        arResult.push(sId + ": item")
      }
      // check context items
      if (jA.context_items.includes(sVar)) {
        arResult.push(sId + ": context_items")
      }
    }
    return arResult
  }
  /**
   * Returns array with matches (file-name line-number: line-content)
   * @param {string} sFileName name of JS file
   * @param {stromg} sFileContent content of JS file
   * @param {regExpr} regExp regular expr. for search
   * @param {string[]} result array
   * @returns nothing
   */
  function getVarFile(sFileName, sFileContent, regExp, arResult) {
    let arRow = sFileContent.split("\n")
    for (let i = 0; i < arRow.length; i++) {
      if (arRow[i].match(regExp)) {
        let sRow = ((i + 1) + "").padStart(4, "0")
        let sResult = sFileName.substring(18).padEnd(30, " ") + " " + sRow + ":" + arRow[i].replace(regExp, '<z>$1</z>')
        arResult.push(sResult)
      }
    }
  }
}
/**
 * Recursive list of WebIQ model tree
 */
function getVarModel2(r, iLevel, sPath, sValue, jModel, bIncludes) {
  sPath += r.controlConfig.name + FS_PATH;
  if (["group "].includes(r.ui)) {
    for (let k = 0; k < jModel.templates[r.groupId].length; k++) {
      let x = jModel.templates[r.groupId][k]
      getVarModel2(jModel.templates[r.groupId][k], iLevel + 1, sPath, sValue, jModel, bIncludes);
    }
  } else if (["dialog-box", "screen", "tab-panel", "panel", "slide-in", "popup-menu"].includes(r.ui)) {
    for (let k = 0; k < jModel.templates[r.id].length; k++) {
      getVarModel2(jModel.templates[r.id][k], iLevel + 1, sPath, sValue, jModel, bIncludes);
    }
  }

  if (!["container", "view"].includes(r.ui)) {
    let arFound = getKeys(r, sValue, sPath, bIncludes)
    if (arFound.length)
      arFound

    arFound.forEach(el => {
      arFound_gb.push(sPath + el)
    });
  }
  for (let i = 0; i < r.children.length; i++) {
    getVarModel2(r.children[i], iLevel + 1, sPath, sValue, jModel, bIncludes);
  }
}
/**
 * Recursive list of WebIQ model tree
 * @param {*} r json object to parse
 * @param {*} iLevel level of current json object
 * @param {*} sPath path of current json object
 * @param {*} sValue value to search
 * @returns sets global array arFound_gb
 */
function getVarModel(r, iLevel, sPath, sValue) {
  let arResult = []
  let sType = r.ui + "*"
  sType = sType.padEnd(25, " ")
  // console.log(`${"".padStart(2 * iLevel, " ")}path=${sPath}${r.controlConfig.name} ui=${r.ui}`);
  // console.log(sType + sText)
  if (!arGroups.includes(r.ui)) {
    let arFound = getKeys(r, sValue, sPath)
    arFound.forEach(el => {
      arFound_gb.push(sPath + r.controlConfig.name + FS_VAR + el)
    });
  }
  sPath += r.controlConfig.name + FS_PATH;
  for (let i = 0; i < r.children.length; i++) {
    getVarModel(r.children[i], iLevel + 1, sPath, sValue);
  }
  return arResult
}
/**
 * Recursive list of WebIQ model tree
 */
function getPanel(r, iLevel, sPath, sValue) {
  if (!arGroups.includes(r.ui)) {
    let arFound = getKeys2(r, sValue, sPath)
    arFound.forEach(el => {
      if (el === "id") {
        arFound_gb.push(sPath + r.controlConfig.name + FS_VAR + sValue)
      }
    });
  }
  sPath += r.controlConfig.name + FS_PATH;
  for (let i = 0; i < r.children.length; i++) {
    getPanel(r.children[i], iLevel + 1, sPath, sValue);
  }
}