// simple view

var opLvl = 0;
var maxLvl = 77; // unlimited = set smth to limit for every user

var timeLastUpdateUnitView = 0;
var needUpdateUnitView;
var timerUpdateUnitView;
var logDisplayedModule;

var w1aw; //
var w2aw; //
var w1b; //
var w2b; //
var w1c; //
var w2c; //
var w2d; //
var wm; // modal div
var fm; // full modal div
var mb; //  modal button ok
//var ts; // top string - 1-line window for full path to current unit
var log = []; // array of strings to display in log terminal window (w2b)
var logLog = []; // array of strings to display in logLog window (w2c)
var winLog;
var loggerStrings = [];
var preset;
var verbosityLevel = 0;

var deleting = 0;

function buttonFlash(but, flash) {
  if (flash) {
    but.style.animation = 'blinker 0.3s cubic-bezier(.5, 0, 1, 1) infinite alternate';
    but.style.color = 'Red';
  } else {
    but.style.animation = '';
    but.style.color = '';
  }
}

function modelChanged(reason, unit, details) {
  let ok = false;
  if (!details) details = {};
  switch (reason) {
    case 'oscilloscopeReady':
      displayOscilloscope(unit, details);
      break;
    case 'startReadAllCfg':
      {
        // let buttonId = '';
        // if (unit && unit._sut != '_System') buttonId = unit._sut;
        // if (!buttonId) buttonId = '';
        buttonFlash(document.getElementById('dictFromPpk'), true);
      }
      break;
    case 'doneReadAllCfg':
      {
        // let buttonId = '';
        // if (unit && unit._sut != '_System') buttonId = unit._sut;
        // if (!buttonId) buttonId = '';
        // buttonFlash(document.getElementById(buttonId + 'dictFromPpk'), false);
        buttonFlash(document.getElementById('dictFromPpk'), false);
      }
      break;
    case 'userSet':
      {
        let la = 'EN';
        if (lang) la = lang;
        let n = schema.dictionary.notLoggedIn[la];
        let lvl = 0;
        if (loggedInUser) {
          n = loggedInUser.config.name;
          if (!loggedInUser.config.lang) loggedInUser.config.lang = 0;
          if (!loggedInUser.config.level) loggedInUser.config.level = 0;
          la = schema.languages[loggedInUser.config.lang];
          lvl = loggedInUser.config.level;
        }
        if (loggedInUser) changeLang(loggedInUser.config.lang);

        if (n == '') n = 'user#' + loggedInUser._sun;
        let tit = 'v' + schema.version + ' ' + n;
        let newLvl = opLvl;
        if (opLvl == 0 && lvl) newLvl = 1;
        if (opLvl > lvl) newLvl = lvl;
        if (opLvl != newLvl) {
          opLvl = newLvl;
          document.getElementById('level').value = opLvl;
          setUnitViewSub(w1aw, w1aw.curUnit, w1aw.curSut);
        }
        let doch = document.title != tit;
        document.title = tit;
        document.getElementById('logIn').innerHTML = n;
        if (doch) changeLang(la);
      }
      break;
    case 'finalFullLog':
      fullLog = false;
      winLog.innerHTML = fullLogText;
      copyLog();
      fullLogText = '';
      alert('done, ' + logQtyDone + ' and copied to clipboard');
      //  document.getElementById("full").innerHTML = fullLogText;
      //    fullLogText="";
      break;
    case 'logAdded':
      if (typeof details == 'string') {
        putLog('Log: ' + details); //.substring(15));
        putLogLog(details, unit); //.substring(15));
      }
      break;
    case 'online':
      ok = 'true';
    case 'offline':
      markOnline(ok, details.box, details.mod);
      break;
    case 'beforeDelete':
      if (unit == w1a.curUnit) setUnitView(w1aw, unit._up);
      if (!deleting++) viewDelete(unit);
      break;
    case 'afterDelete':
      if (deleting) deleting--;
      if (!deleting) postDelete();
      break;
    case 'changed':
      updateUnitView(unit, details);
      break;
    case 'force':
      forceUpdateUnitView(unit);
      break;
    case 'boot':
      updateBootView(unit);
      break;
    case 'afterLoad':
      if (sys) {
        try {
          delete w1a.curUnit;
          delete w2a.curUnit;
          setUnitViewSub(w1aw, string2u(sys?.state?.curUnit), sys?.state?.curType);
        } catch {
          if (sys._subUnits && sys._subUnits._Box) {
            for (let b in sys._subUnits._Box)
              if (b[0] != '_') {
                if (w1aw) setUnitViewSub(w1aw, sys._subUnits._Box[b], 'Module');
                break;
              }
          } else if (w1aw) setUnitView(w1aw, sys);
        }
        setUnitView(w2aw, sys);
      }
      break;
    case 'changedType':
      refreshBoth();
      break;
    case 'showCfg':
      w2d.innerHTML = details; // corrected - parameter name
      setW2('x');
      break;
    case 'stopSendingUnit':
      setW2('t');
      buttonFlash(document.getElementById('dict2ppk'), false);
      buttonFlash(document.getElementById('dict2ppk1'), false);
      if (typeof details != 'string') details = '';
      putLog('stop sending to PPK: ' + details);
      break;
  }
}

function systemLog(critical, text, color) {
  if (critical) {
    text = text.replaceAll('<br/>', '\n\r');
    alert(text);
  } else putLog(text, color);
}

function markOnline(online, box, mod) {
  let onl = document.getElementById('online');
  if (online) onl.style.backgroundColor = 'SpringGreen';
  else onl.style.backgroundColor = 'FireBrick';
  if (box) {
    if (!mod) mod = 0;
    mod = ('0000' + mod).slice(-2);
    box = ('0000' + box).slice(-2);
    onl.textContent = 'Online ' + box + ':' + mod;
  }
}

function sendEmptyCommand() {
  let m = this.cmd;
  let cmd = {};
  let u = this.curunit;
  cmd[m] = {};
  sendCommand(u, cmd);
}

function execCommandS() {
  let m = this.cmd;
  let u = this.curunit;
  let spc = schema.unitTypes[u._typeName]?.[m].__?.specialCommand;
  if (!u || !spc) alert('error special command');
  else eval(spc + '(u,m);');
}

function execCommand() {
  let m = this.cmd;
  let u = this.curunit;
  if (!u[m]) u[m] = {};
  exec1Command(u, m);
}

function execCommandV() {
  let m = this.cmd;
  let u = this.curunit;
  if (!u[m]) u[m] = {};
  u[m][this.cmdPar] = this.cmdVal;
  exec1Command(u, m);
}

var fw2filename;

function setUpdateFirmwareView() {
  setW2('t');
  let file = this.files[0];
  fw2filename = file.name;
  let u = this.curunit;
  setUpdateFirmware(file, u);
}

var logRecords; // array of 100 log records or undefined on start
var logQty;

function showLog(time, direction) {
  midLogTime = 0;
  logQty = 0;
  logRecords = [];
  winLog.innerHTML = '';
  // now read all "last" messages to avoid requering them
  const dbTran = localDB.transaction('Log');
  const dbSt = dbTran.objectStore('Log');
  const dbIndex = dbSt.index('datePC');
  let dbCurReq;
  if (direction > 0) dbCurReq = dbIndex.openCursor(IDBKeyRange.lowerBound(time), 'next');
  else dbCurReq = dbIndex.openCursor(IDBKeyRange.upperBound(time), 'prev');
  dbCurReq.onsuccess = event => {
    const cursor = event.target.result;
    if (cursor && cursor.value && logQty++ < 100) {
      logRecords.push(cursor.value);
      let oneLine =
        '<span fullEvent="' +
        decodeRecordLong(cursor.value, '\n') +
        '" onclick="alert(this.attributes.getNamedItem(\'fullEvent\').value);">' +
        decodedRecordShort(cursor.value) +
        '</span> <br/>';
      if (direction < 0) winLog.innerHTML = winLog.innerHTML + oneLine;
      else winLog.innerHTML = oneLine + winLog.innerHTML;
      cursor.continue();
    } else if (direction > 0) logRecords.reverse();
  };
}

function click_printMessages() {
  printMessages = document.getElementById('printMessages').checked;
}

function click_pullLog() {
  noPullLog = document.getElementById('pullLog').checked;
}

function goTimeLog() {
  const event = new Date();
  event.setSeconds(0);
  event.setMonth(0);
  event.setFullYear(document.getElementById('logYear').value);
  event.setDate(document.getElementById('logDay').value);
  event.setMonth(document.getElementById('logMonth').value - 1);
  event.setHours(document.getElementById('logHour').value);
  event.setMinutes(document.getElementById('logMinute').value);
  const lt = event.getTime();

  showLog(lt, -1);
}

function prevLog() {
  if (logQty) {
    let mid = 0;
    if (logQty > 50) mid = logQty - 50;
    let midLogTime = logRecords[mid].datePC;
    showLog(midLogTime, -1);
  } else lastLog();
}

function nextLog() {
  if (logQty) {
    let mid = logQty - 1;
    if (logQty > 99) mid = 99;
    let midLogTime = logRecords[mid].datePC;
    showLog(midLogTime, 1);
  } else lastLog();
}

function lastLog() {
  // showLog(Date.now(), -1);
  logLog = [];
  // todo limit time range
  let time = Date.now();
  // now read all messages to avoid requering them
  const dbTran = localDB.transaction('Log');
  const dbSt = dbTran.objectStore('Log');
  const dbIndex = dbSt.index('datePC');
  let dbCurReq;
  let qty = 0;
  dbCurReq = dbIndex.openCursor(IDBKeyRange.upperBound(time), 'prev');
  dbCurReq.onsuccess = event => {
    const cursor = event.target.result;
    if (cursor && cursor.value && qty++ < logLinesQty) {
      let t = decodedRecordShort(cursor.value);
      let u = unitIdToUnit(cursor.value.message.id);
      putLogLog(t, u, true);
      cursor.continue();
    }
  };
}

function updateBootView(unit) {
  let strng = fw2update ? ' uploading firmware (' + fwPercent + '%)  ' : '';
  let b = document.getElementById('updateFirmwareDivStop');
  let viewitfw2update = document.getElementById('updateFirmwareDiv');
  if (fw2update && w1aw.curUnit == unit && !multipleFirmwares) {
    if (b) b.style.visibility = 'visible';
    if (viewitfw2update) viewitfw2update.textContent = strng;
  }
  if (b && (!fw2update || multipleFirmwares)) b.style.visibility = 'hidden';

  // + fw2filename + ' ';
  if (multipleFirmwares) {
    setFileMultyText();
    switch (multipleFirmwares.stage) {
      case 'askModule':
      case 'askBoot':
        strng = '';
        break;
    }
    wm.innerHTML += '<br><br>' + strng + '<br><br>' + showMultipleFirmwares;
  } else fm.style.display = 'none';
}

function forceUpdateUnitView(u) {
  if (u == w1a.curUnit && !w1a.curSut) setUnitViewSubDelayed(w1a, u, null, true);
  //        setUnitView(w1a, u);
}

var maskToShowAU = 0;
function updateUnitView(unit, msg) {
  if (['Area', 'InputLink', 'OutputLink'].includes(unit._sut)) setAreaButtonColor(unit);

  if (!w1aw) return;

  const sel = document.getElementById('viewAUselector');
  if (sel) {
    maskToShowAU = 0;
    try {
      if (sel.options[9].selected) maskToShowAU = 0xffff;
      else for (let i = 0; i < 9; i++) if (sel.options[i].selected) maskToShowAU |= 1 << i;
    } catch {}
  }

  if (typeof msg == 'object') {
    if (!msg) msg = {};
    if (unit === w1aw.curUnit && !w1aw.curSut) {
      // not list of units
      if (msg && msg.justRead) {
        let d = msg.justRead.data;
        if (d) {
          if (ArrayBuffer.isView(d)) {
            let s = '';

            function b2cc(b) {
              function b2h(n) {
                return n.toString(16);
              }
              let cc = '';
              cc += b2h(b >> 4);
              cc += b2h(b & 15);
              return cc;
            }
            for (let i = 0; i < d.length; i++) s += b2cc(d[i]) + ' ';
            alert(s);
          }
        }
      }
      // if (!unit._configured) {
      //     let t = false;
      //     if (unit._configPPK)
      //         for (let p in unit._configPPK) {
      //             if (unit._configPPK[p] != undefined) {
      //                 unit.config[p] = unit._configPPK[p];
      //                 t = true;
      //             }
      //         }
      //     if (t) setUnitViewSubDelayed(w1aw, unit);
      //     delete unit._configPPK
      // }
      for (let mn in msg) {
        if (mn == 'config') {
          if (!unit._configPPK) unit._configPPK = {};
          for (let pn in unit.config) {
            // if (typeof(msg.config[pn]) != "undefined" || typeof(unit._configPPK[pn]) != "undefined") {
            let right = document.getElementById(unit._idString + '_parSpanRight_config_' + pn);
            if (right) {
              let pd = unit._desc.config[pn];
              let ppkval = right.lastChild; // button
              let mainInput = right.firstChild;
              if (mainInput.nodeName == 'INPUT' || mainInput.nodeName == 'SELECT') mainInput.value = unit.config[pn];
              if (ppkval && ppkval.ppkval) {
                // else not ppk button
                makeParPpk(ppkval);
              } else {
                if (pd.ro) right.textContent = getParVal(pd, unit.config[pn]);
                else right.textContent = getParVal(pd, unit._configPPK[pn]);
              }
            }
          }
        } else {
          if (typeof msg[mn] == 'number') {
            // todo - simple messages
          } else {
            for (let pn in msg[mn]) {
              let right = document.getElementById(unit._idString + '_parSpanRight_' + mn + '_' + pn);
              if (right && typeof (msg[mn][pn] == 'number')) {
                // fixme - more depth of structure
                let val = right.firstChild;
                if (!val) val = right;
                let pd = unit._desc[mn][pn];
                if (val.nodeName.toUpperCase() == 'INPUT') {
                  if (val.type == 'checkbox') val.checked = unit[mn][pn] ? true : false;
                  else val.value = unit[mn][pn];
                }
                // new value already in unit, old value
                else val.textContent = getParVal(pd, unit[mn][pn]);
                if (!val.tagName) val = right;
                if (right.useColor) {
                  let left = right.previousElementSibling;
                  if (unit[mn][pn]) {
                    val.style.color = 'OrangeRed';
                    left.style.color = 'OrangeRed';
                  } else {
                    val.style.color = 'LawnGreen';
                    left.style.color = 'LawnGreen';
                  }
                }
              }
            }
          }
        }
      }
    } else if (unit._up === w1aw.curUnit && unit._sut === w1aw.curSut) {
      // may be changed style of button in list
      let but = document.getElementById(unit._idString + '_button');
      if (but) {
        // if (msg.status)
        calcStatus(but, unit);
      } else setUnitViewSub(w1aw, w1aw.curUnit, w1aw.curSut); // right now
    }
  } else {
    // msg == sut - string - called if list of this sut changed
    if (unit == w1aw.curUnit && ((msg && msg == w1aw.curSut) || (typeof msg == 'object' && msg.keys().length)))
      setUnitViewSubDelayed(w1aw, w1aw.curUnit, w1aw.curSut);
  }
}

var wordsAU = ''; // todo - bad practice - global var as second return value
function realMark(su) {
  let rm;
  try {
    let usu = su;
    let offline = su.status && su.status.offline;
    while (usu && !usu._desc.__.hardware) usu = usu._up;
    if (usu) offline = (usu.status && usu.status.offline) || (usu._realType != usu._typeName && !usu.status);

    if (!su._desc.__.hardware) su._realType = su._typeName;

    wordsAU = '';

    // todo - use schema
    if (typeof su._fastStatus == 'number') {
      let stat4 = 0;

      // todo - if present fastStatus ... else look at status
      // todo - extend faststatus - fixme --

      switch (su._fastStatus & 3) {
        case 0:
          wordsAU = 'not in PPK';
          stat4 = 1; // notinppk
          break;
        case 3:
          wordsAU = 'offline';
          stat4 = 2; // comloss
          break;
        case 2:
          wordsAU = 'errors';
          stat4 = 4; // errors
          break;
        case 1:
          wordsAU = 'OK';
          break;
      }

      if (su._fastStatus & 4) {
        wordsAU += ' reStarted';
        stat4 |= 8; // restarted
      }

      if (su._fastStatus & 8) {
        wordsAU += ' MKZ!';
        stat4 |= 16; //
      }

      if (su._fastStatus & 16) {
        wordsAU += ' ON';
        stat4 |= 32; //
      }

      if (su._fastStatus & 32) {
        wordsAU += ' fault';
        stat4 |= 64; //
      }

      if (su._fastStatus & 64) {
        wordsAU += ' noSynch';
        stat4 |= 128; //
      }

      if (!stat4) stat4 = 256; // OK

      let stat4f = stat4;
      if (maskToShowAU) stat4f &= maskToShowAU;

      if (stat4 & 1)
        // not in PPK
        rm = 'notPPK';
      else if (su._deleted) rm = 'del';
      else if (!su._configured) rm = 'notAlwd';
      else if (!stat4f) rm = 'notSel';
      else if (stat4 & 2) rm = 'ofl';
      // offline
      else if (stat4 & 32) rm = 'ON';
      // ON
      else if (stat4 & 16) rm = 'MKZ';
      // mkz
      else if (stat4 & 64) rm = 'FLT';
      // fault
      else if (stat4 & 128) rm = 'noSYN';
      // no synch
      // if (!stat4)
      else rm = 'OK'; // ok

      // if (stat4 & 1) rm = '000'; // not conf
      // else if (stat4 ) rm = 'o00'; // some error
      // else  rm = '+++'; // all ok
      // // else {
      //   rm = stat4 & 4 ? 'e' : 's';
      // }

      // if (rm.length < 2) {
      //   if (stat4 & 8) {
      //     rm += 'r';
      //   } else rm += '1';
      //   if (stat4 & 16) {
      //     rm += 'Z';
      //   } else rm += '1';
      // }

      return rm;
    }
    // 000 o00
    // s11 s1Z sr1 srZ
    // e11 e1Z er1 erZ

    if (!su._configured) rm = '--c';
    else if ((su.config && su.config.ignore) || (su.status && su.status.disabled)) return 'RRR';
    else if (su._realType) {
      if (su._realType != su._typeName) rm = 'T';
      else if (!su._configOK) rm = 'C';
      else rm = '+';
      if (offline) rm += 'o-';
      else {
        rm += '+';
        if (su.status && su.status.notSynched) rm += 's';
        else rm += '+';
      }
    } else rm = '---';
  } catch {
    rm = '---';
  }
  if (su && su.status && rm == '+++') {
    if (su.status.on) rm += su.status.on > 1 ? 'Y' : 'o';
    else if (su.status.fault) rm += 'f';
    else if (su.status.faultDown) rm += 'd';
  }
  return rm;
}

function realMark2(rm) {
  let ra = '';
  if (rm[0] == 'T' || rm[0] == 'C') return 'NNN';
  if (rm == '+o-') return 'ooo';
  for (let i = 0; i < rm.length; i++) {
    let c = rm[i];
    if (c == '-') c = '0';
    else if (c == '+') c = '1';
    ra = ra + c;
  }
  return ra;
}

function showUnits2() {
  setW2('u');
}

function updateOnTimeout() {
  if (needUpdateUnitView) setUnitViewSub(w1a, w1a.curUnit, w1a.curSut);
  timerUpdateUnitView = needUpdateUnitView = undefined;
}

function setUnitViewSubDelayed(w1a, u, sutn, faster) {
  // ref to object
  if (w1a == w1aw && u == w1a.curUnit && sutn == w1a.curSut) {
    // just refresh
    needUpdateUnitView = 1;
    if (!timerUpdateUnitView) {
      if (timeLastUpdateUnitView + 500 > Date.now())
        timerUpdateUnitView = setTimeout(updateOnTimeout, faster ? 400 : 700);
      else updateOnTimeout();
    }
  }
}

var setUnitViewSubBusy = false;

function calcStatus(but, su) {
  let name = '';
  if (su._deleted) name = 'X ';
  else if (!su._configured) name = '- ';

  if (su.status && su.status.fault) name = '* ';
  if (su.status && su.status.on) name = '+ ';
  name += '#' + su._sun;
  name += ' (' + getTypeName(su._typeName) + ')';
  if (su.config && typeof su.config.areaNum == 'number') name += ' :' + su.config.areaNum;

  if (
    su.config &&
    su.config.unitID && // todo - here hardcoded unitID parameter
    su.config.unitID != '' &&
    typeof su.config.unitID == 'string'
  ) {
    name += ' = ' + getUnitName(unitIdToUnit2(su.config.unitID), verbosityLevel);
  }

  let rM = realMark(su);
  name += getHardwareID(su);
  /*            for( let id of schema.unitTypes[sutn].__.hardwareIDs)
                    if(su.config && su.config[id])
                        name+=" #"+su.config[id];     
    */
  name += '  ' + rM;
  if (wordsAU.length) name += '   --------   ' + wordsAU;
  if (su.config && typeof su.config.name == 'string' && su.config.name.length)
    name = '<b>' + su.config.name + '</b>' + '<br/>' + name;
  if (but.innerHTML != name) but.innerHTML = name;
  let ln = 'lnk' + realMark2(rM);
  if (ln != but.className) but.className = ln;
  if (su._typeName == '_Box') {
    let mod = su._subUnits.Module[1];
    let st = mod._subUnits.Boot[1]?.status;
    if (st && st.boardVersion) but.textContent += ' v.' + st.boardVersion.toString();
    if (st && st.bootVersion) but.textContent += '_' + st.bootVersion.toString();
    if (mod && mod.info && mod.info.appVersion) but.textContent += '_' + mod.info.appVersion.toString();
  }
}

var sortList = '_sun'; // default = '_sun'

//todo = add "ID conflict" marks before display
function setUnitViewSub(w1a, u, sutn) {
  // ref to object
  needUpdateUnitView = 0;
  // window.timerUpdateUnitView;
  checkUnitDesc(u);
  timeLastUpdateUnitView = Date.now();

  if (setUnitViewSubBusy) return; // todo - maybe obsolete

  if (w1a == w2aw) showUnits2();

  let summary;
  let sum = 0;
  let sumNew = 0;

  if (!w1a) return;
  if (!u) {
    u = w1a.curUnit;
    sutn = w1a.curSut;
  }
  u = checkPresent(u);

  if (!u) {
    setUnitView(w1a, sys);
    return;
  }

  // if (!u) {
  //     try {
  //         for (let b in sys._subUnits._Box) u = sys._subUnits._Box[b];
  //         sutn = "Module";
  //         if (!u) {
  //             u = sys;
  //             sutn = "_Box";
  //         }
  //     } catch {
  //         u = sys;
  //         sutn = undefined;
  //     }
  // }

  if (!u._subUnits[sutn]) sutn = undefined;
  if (!sutn) {
    setUnitView(w1a, u);
    return;
  }

  let sud = u._subUnits[sutn];
  let uSubDesc = u._desc.__.subUnits[sutn];
  let adddelete = (uSubDesc.hardware || uSubDesc.software) && sutn !== 'Module'; // todo multimodule

  if (uSubDesc.qty === 1 && !adddelete) {
    // if fixed number of units - go directly to the unit
    setUnitView(w1a, sud[1]);
    return;
  }

  setUnitViewSubBusy = true;

  w1a.innerHTML = '';
  w1a.appendChild(setTopString(w1a, u, sutn));

  let sorting = false;

  if (adddelete && w1a == w1aw) {
    {
      const but = document.createElement('button');
      but.className = 'lnk';
      but.onclick = function () {
        if (w1a.curUnit && w1a.curSut) {
          if (addUnitToSut(w1a.curUnit, w1a.curSut)) refreshBoth();
        }
      };
      but.title = schema.dictionary.addNewSu[lang];
      but.textContent = '\u{271A}';
      w1a.appendChild(but);
    }
    {
      const but = document.createElement('button');
      but.className = 'lnk';
      but.title = schema.dictionary.delAllSu[lang];
      but.onclick = function () {
        if (w1a.curUnit && w1a.curSut)
          if (w1a.curUnit._subUnits) {
            // clearAll(w1a.curUnit, w1a.curSut);
            let sus = w1a.curUnit._subUnits[w1a.curSut];
            if (sus)
              for (let sn in sus)
                if (sn[0] !== '_') {
                  sus[sn]._deleted = true;
                  sus[sn]._configured = false;
                }
          }
        setUnitViewSub(w1a, w1a.curUnit, w1a.curSut);
      };

      but.textContent = '\u{274C}';
      w1a.appendChild(but);
    }
    {
      const but = document.createElement('button');
      but.className = 'lnk';
      but.title = schema.dictionary.confAllSu[lang];
      but.onclick = function () {
        if (w1a.curUnit && w1a.curSut) {
          markConfigured(w1a.curUnit, w1a.curSut);
          setUnitViewSub(w1a);
        }
      };
      but.textContent = '\u{2705}'; // 1F197  &#x2705
      w1a.appendChild(but);
    }
    {
      const but = document.createElement('button');
      but.className = 'lnk';
      but.title = schema.dictionary.unConfAllSu[lang];
      but.onclick = function () {
        if (w1a.curUnit && w1a.curSut)
          if (w1a.curUnit._subUnits) {
            // clearAll(w1a.curUnit, w1a.curSut);
            let sus = w1a.curUnit._subUnits[w1a.curSut];
            if (sus)
              for (let sn in sus)
                if (sn[0] !== '_') {
                  sus[sn]._configured = false;
                }
          }
        setUnitViewSub(w1a, w1a.curUnit, w1a.curSut);
      };

      but.textContent = '\u{237B}';
      w1a.appendChild(but);
    }
    {
      const but = document.createElement('button');
      but.title = schema.dictionary.crTSinput[lang];
      but.className = 'lnk';
      but.onclick = function () {
        if (w1a.curUnit && w1a.curSut) {
          let su = w1a.curUnit._subUnits[w1a.curSut];
          // let n = 0;
          let arnumstring = prompt('Please enter area number', '1');
          let arnum = parseInt(arnumstring);
          //  n +=
          for (let sun in su) createTS(su[sun], arnum);
          // buildInput(su[sun]);
          // alert(' ' + n + ' new inputs created');
        }
      };
      but.textContent = '\u{1F3EA}';
      w1a.appendChild(but);
    }

    try {
      if (schema.unitTypes[sutn].config.SN) {
        {
          const but = document.createElement('button');
          but.title = 'sort by SN';
          but.className = sortList == 'SN' ? 'lnkOK' : 'lnk';
          but.onclick = function () {
            sortList = 'SN';
            setUnitViewSub(w1a, u, sutn);
          };
          but.textContent = '\u{2193}SN';
          w1a.appendChild(but);
        }
        {
          const but = document.createElement('button');
          but.title = 'sort by typeSN';
          but.className = sortList == 'typeSN' ? 'lnkOK' : 'lnk';
          but.onclick = function () {
            sortList = 'typeSN';
            setUnitViewSub(w1a, u, sutn);
          };
          but.textContent = '\u{2193}typeSN';
          w1a.appendChild(but);
        }
      } else if (sortList == 'SN' || sortList == 'typeSN') sortList = '_sun';
      {
        const but = document.createElement('button');
        but.title = 'sort by number';
        but.className = sortList == '_sun' ? 'lnkOK' : 'lnk';
        but.onclick = function () {
          sortList = '_sun';
          setUnitViewSub(w1a, u, sutn);
        };
        but.textContent = '\u{2193}num';
        w1a.appendChild(but);
      }
      {
        const but = document.createElement('button');
        but.title = 'sort by number';
        but.className = sortList == 'name' ? 'lnkOK' : 'lnk';
        but.onclick = function () {
          sortList = 'name';
          setUnitViewSub(w1a, u, sutn);
        };
        but.textContent = '\u{2193}name';
        w1a.appendChild(but);
      }

      sorting = true;
    } catch {}

    {
      const but = document.createElement('button');
      but.onclick = function () {
        let u = w1a.curUnit;
        let s = w1a.curSut;
        for (let sun in u._subUnits[s]) u._subUnits[s][sun]._selected = false;
        setUnitViewSub(w1aw, u, s);
      };
      but.textContent = 'unsel';
      w1a.appendChild(but);
    }
    {
      const but = document.createElement('button');
      but.onclick = function () {
        let u = w1a.curUnit;
        let s = w1a.curSut;
        for (let sun in u._subUnits[s]) u._subUnits[s][sun]._selected = true;
        setUnitViewSub(w1aw, u, s);
      };
      but.textContent = 'selAll';
      w1a.appendChild(but);
    }

    {
      let summary = document.createElement('span');
      let qty = 0;
      for (let s in u._subUnits[sutn]) if (s[0] != '_') qty++;
      summary.innerHTML = schema.dictionary.totalQty[lang] + ' = ' + qty;
      w1a.appendChild(summary);
    }

    // todo - use schema
    if (sutn == 'AU') {
      {
        const but = document.createElement('button');
        but.onclick = function () {
          readSerialsFromFile(u);
        };
        but.textContent = 'loadSNs';
        w1a.appendChild(but);
      }

      const but = document.createElement('button');
      but.onclick = function () {
        sendBinaryRequestAllAU(u);
      };
      but.textContent = 'update';
      w1a.appendChild(but);

      const sel = document.createElement('select');
      for (let i = 0; i < 10; i++) {
        let opt = document.createElement('option');
        opt.value = i;
        opt.textContent = ['notInPPK', 'offline', 'errors', 'restart', 'MKZ', 'ON', 'fault', 'noSynch', 'OK', 'ALL'][i];
        if (i == 9) opt.selected = true; // ALL
        sel.appendChild(opt);
      }
      sel.id = 'viewAUselector';
      sel.multiple = true;
      w1a.appendChild(sel);

      sendBinaryRequestAllAU(u);
    }

    // todo - use schema
    if (sutn == 'Area') {
      {
        const but = document.createElement('button');
        but.onclick = function () {
          addSelectedToView(u);
        };
        but.textContent = '=>ОБЛАСТИ';
        w1a.appendChild(but);
      }
      {
        const but = document.createElement('button');
        but.onclick = function () {
          setSelectedToView(u);
        };
        but.textContent = '<=ОБЛАСТИ';
        w1a.appendChild(but);
      }
    }
  }

  const curfs = document.createElement('fieldset');
  let cursub = document.createElement('legend');
  cursub.textContent = getTypeName(sutn);
  curfs.appendChild(cursub);
  curfs.setAttribute('class', 'columnButtons');

  let suns = [];
  for (let sun in sud) suns.push(sun);

  if (sorting)
    switch (sortList) {
      case 'name':
        suns.sort(function (a, b) {
          if (!sud[a].config.name) sud[a].config.name = '';
          if (!sud[b].config.name) sud[b].config.name = '';
          return sud[a].config.name.localeCompare(sud[b].config.name);
        });
        break;
      case 'SN':
        suns.sort(function (a, b) {
          if (!sud[a].config.SN) sud[a].config.SN = 0;
          if (!sud[b].config.SN) sud[b].config.SN = 0;
          return sud[a].config.SN - sud[b].config.SN;
        });
        break;
      case 'typeSN':
        suns.sort(function (a, b) {
          if (!sud[a].config.SN) sud[a].config.SN = 0;
          if (!sud[b].config.SN) sud[b].config.SN = 0;
          let an = '' + sud[a]._typeName + ' ' + sud[a].config.SN;
          let bn = '' + sud[b]._typeName + ' ' + sud[b].config.SN;
          return an.localeCompare(bn);
        });
        break;
      case '_sun':
      default:
    }

  for (let sun of suns)
    if (sun[0] != '_') {
      initFaultsDown(sud[sun]);
      let su = sud[sun];
      if (su) {
        const div = document.createElement('div');
        div.style.width = '100%';
        div.style['flex-direction'] = 'row';
        div.style.display = 'flex';
        // div.style.display= 'block';
        //const chk = document.createElement('checkbox');
        const chk = document.createElement('input');
        chk.type = 'checkbox';
        chk.id = su._idString + '_check';
        // chk.style.float= 'left';
        // chk.style.width= '20px';
        // chk.style.hidden= false;
        // chk.style.height= '20px';
        chk.checked = su._selected;
        chk.onclick = function () {
          chckUnit(su._idString);
        };
        div.appendChild(chk);
        const but = document.createElement('button');
        sum++;
        if (!su._configured) sumNew++;
        but.cu = su;
        but.id = su._idString + '_button';
        but.w1a = w1a;
        but.onclick = showthis;
        but.style.width = '100%';

        calcStatus(but, su);
        // but.style.overflow= 'hidden';
        let conflict = checkConflicts(su);
        if (!conflict) div.appendChild(but);
        else if (conflict.length) {
          but.textContent += 'conflict or SN==0 !!!';
          but.setAttribute('class', 'errorSun');
          div.appendChild(but);
        } else {
          try {
            const lineInTable = document.createElement('span');
            // lineInTable.style.overflow= 'hidden';
            lineInTable.appendChild(but);
            for (let cun of conflict)
              if (cun != su._idString) {
                let but2 = document.createElement('button');
                but2.cun = cun;
                but2.textContent = '!! SN == ' + getUnitName(unitIdToUnit2(cun), 1);
                but2.onclick = function () {
                  setUnitView(w1a, unitIdToUnit2(this.cun));
                };
                lineInTable.appendChild(but2);
              }
            div.appendChild(lineInTable);
          } catch {}
        }
        curfs.appendChild(div);
      }
    }
  w1a.appendChild(curfs);

  if (summary) summary.innerText = '  total=' + sum + ', new=' + sumNew;

  checkSubscribe(w1a, u, sutn);

  if (u !== w1a.curUnit) {
    queryUnit(u);
    recalcFaultsDown(w1a.curUnit);
  }
  if (!sys.state) sys.state = {};
  if (w1a == w1aw) {
    if (w1a.curUnit != u || w1a.curSut != sutn)
      history.pushState({ unit: u._idString, sut: sutn }, '', '?unit=' + u._idString + '&sut=' + sutn);
    sys.state.curUnit = u2string(u);
    sys.state.curType = w1a.curSut = sutn;
  }
  w1a.curUnit = u;
  setUnitViewSubBusy = false;
}

function showthis() {
  setUnitViewSub(this.w1a, this.cu, this.sut);
}

function setTopString(w1a, cu, sutn) {
  const topstr = document.createElement('fieldset');
  topstr.className = 'topstring';
  let curelement = document.createElement('legend');
  curelement.textContent = 'full path to unit';
  topstr.appendChild(curelement);

  while (cu) {
    if (sutn) {
      const but = document.createElement('button');
      but.className = 'lnk';
      but.w1a = w1a;
      but.cu = cu;
      but.sut = sutn;
      but.onclick = showthis;
      but.textContent = '(' + getTypeName(sutn) + ')';
      topstr.insertBefore(but, curelement);
      curelement = but;
    }

    sutn = cu._sut;

    let tname = getTypeName(cu._typeName);
    let name = '';
    if (sutn && sutn != '_System') {
      if (cu._sun) name = ' #' + cu._sun + ' '; // " "+getTypeName(cu._suType)+
      if (cu._typeName) if (!schema.unitTypes[sutn].__.number) if (cu._typeName[0] != '_') name += '( ' + tname + ' )';
    } else name = tname;

    const but = document.createElement('button');
    but.className = 'lnk';
    but.onclick = showthis;
    but.w1a = w1a;
    but.cu = cu;
    but.textContent = name;
    topstr.insertBefore(but, curelement);
    curelement = but;

    cu = cu._up;
  }

  return topstr;
}

function saveValue(e) {
  saveValueForElement(this);
  e.stopPropagation();
}

function saveValueForElement(elt) {
  let cu = elt.curunit;
  if (cu) {
    let mn = elt.msgname;
    if (mn) {
      let pn = elt.parname;
      let v;
      if (elt.type === 'text') v = elt.value;
      else if (elt.type === 'checkbox') {
        if (elt.checked) v = 1;
        else v = 0;
        let c;
        if (v) c = 'orangeRed';
        else c = 'lawnGreen';
        elt.parentElement.previousElementSibling.style.color = c;
      } else v = Number.parseInt(elt.value);

      let nv = setValueInMsg(cu, mn, pn, v);
      if (nv != v) elt.value = nv;
    }
  }
}

var setUnitViewBusy = false;

function makeParPpk(ppkval) {
  // button, unit, parName - only for "config"
  let u = ppkval.unit;
  let pn = ppkval.parName;
  let difppk = false;
  let doClick = false;
  let parT = '';
  if (u._configPPK && typeof u._configPPK[pn] !== 'undefined') {
    if (
      (typeof u.config[pn] == 'undefined' || u.config[pn] === '' || u.config[pn] === u._configPPK[pn]) &&
      pn != 'unitID' // this used when unitID already used elsewhere
    ) {
      doClick = true;
      // u.config[pn] = u._configPPK[pn];
      // u._configPPK[pn] = undefined;
      // delete u._configPPK[pn];
    } else {
      if (typeof u.config[pn] == 'number') difppk = u._configPPK[pn] !== u.config[pn];
      if (typeof u.config[pn] == 'string') difppk = u._configPPK[pn] != u.config[pn];
    }
    parT = getParVal(u._desc.config[pn], u._configPPK[pn]);
  }
  ppkval.textContent = '\u{00A0} ' + schema.dictionary.inPPK[lang] + ' = ' + parT;
  ppkval.parText = parT;
  ppkval.hidden = !difppk;
  if (!difppk)
    if (doClick) {
      Window.autoClickFromPPK = true;
      ppkval.click();
      Window.autoClickFromPPK = false;
      delete u._configPPK[pn];
    }
}

function makeParSpan(pn, u, msgn, curfs) {
  if (msgn == 'config') {
    if (!u._configPPK) u._configPPK = {};
    // if (!u._configured)
    //     if (typeof(u._configPPK[pn]) != "undefined") {
    //         u.config[pn] = u._configPPK[pn];
    //         delete u._configPPK[pn];
    //     }
  }

  let val = u[msgn][pn];
  let md = u._desc[msgn];
  let editAll = md.__.rwAll && md.__.RWoperatorLevel <= opLvl && w1a === w1aw;

  // if (msgn === "config" && pn === "typeSN") return; // todo - set/change module type
  if (md[pn].operatorLevel && md[pn].operatorLevel > opLvl) return;

  if (val !== '' && !val) val = 0;
  let pd = md[pn];
  if (pd && !pd.unused && !pd.notForOperator) {
    // hide subunits fault if no subunits
    let cur = document.createElement('tr');

    let enums = pd['enum' + lang];

    curfs.appendChild(cur);
    cur.title = paramDescription(pd);
    let curleft = document.createElement('td');
    let curright = document.createElement('td');
    curleft.align = 'right';
    curleft.width = '50%';
    curright.align = 'left';
    cur.appendChild(curleft);
    cur.appendChild(curright);
    if (w1a == w1aw) {
      // only left
      curright.id = u._idString + '_parSpanRight_' + msgn + '_' + pn;
    }

    // if (msgn == "status" && u._typeName == "Boot" && pn == "snType")
    //     curright.textContent = "" + (val >> 24) + ":" + (val & 0xffffff);
    // // special - fixed32 boot SNtype
    // else
    curright.textContent = '' + getParVal(pd, val);

    if (msgn == 'config') {
      const chk = document.createElement('input');
      chk.type = 'checkbox';
      chk.id = pn + '_config_check';
      chk.onclick = function () {
        addToPreset(pn);
      };
      chk.hidden = true;
      curleft.appendChild(chk);
      const nam = document.createElement('span');
      nam.textContent = pd['Title' + lang] + ' = ';
      curleft.appendChild(nam);
    } else {
      curleft.textContent = pd['Title' + lang] + ' = ';
      // if(pd.hex) curright.textContent+=" = 0x"+val.toString(16);
      if (pd.maxValue < 2 && pd.format != 'string') {
        curright.useColor = true;
        if (val) {
          curleft.style.fontWeight = 900;
          curright.style.color = 'OrangeRed';
          curleft.style.color = 'OrangeRed';
        } else {
          curright.style.color = 'LawnGreen';
          curleft.style.color = 'LawnGreen';
        }
      }
    }

    if ((pd.ro || md[pn].RWoperatorLevel > opLvl) && pd.link) {
      let butFind = document.createElement('button');
      butFind.textContent = '\u{1F50D}';
      curright.appendChild(butFind);
      butFind.sid = curright.textContent;
      butFind.onclick = function () {
        setUnitView(w2aw, unitIdToUnit2(this.sid));
        showUnits2();
      };
    }

    if (
      editAll &&
      !pd.ro &&
      md[pn].RWoperatorLevel <= opLvl &&
      md.__.RWoperatorLevel <= opLvl &&
      !(pd.hardwareID && u._sut == 'Module' && u._sun < 4)
    ) {
      // todo - create motherboard
      // all status are RW, e.g. config - just edit them in-place
      let tit = 'w1a' + md.__._name + pn;
      let inp;
      let setunitID = false;
      curright.textContent = '';
      if (!enums) {
        inp = document.createElement('input');
        if (pd.maxValue == 1) {
          inp.type = 'checkbox';
          if (val) inp.checked = true;
        } else if (pd.format != 'bytes') {
          if (pd.format != 'string') {
            // bytes ==> plain input == todo = get unit ID from another window
            inp.type = 'number';
            if (pd.maxValue) inp.max = pd.maxValue;
            if (pd.minValue) inp.min = pd.minValue;
            else inp.min = 0;
          } else {
            // string
            inp.type = 'text';
            if (pd.pattern) inp.pattern = pd.pattern;
            if (pd.maxValue) inp.maxlength = pd.maxValue.toString();
          }
        } else {
          // bytes == maybe Link ???
          if (pd.link) {
            setunitID = inp;
          }
        }
      } else {
        inp = document.createElement('select');
        for (en in enums) {
          let opt = document.createElement('option');
          opt.value = Number(en);
          opt.textContent = enums[en].split(':', 1)[0];
          inp.appendChild(opt);
        }
      }

      inp.parameterDescriptor = pd;
      inp.value = val;
      inp.id = tit;
      //                inp.addEventListener('focusout',saveValue);
      inp.addEventListener('change', saveValue);
      inp.msgname = msgn;
      inp.parname = pn;
      inp.curunit = u;
      curright.appendChild(inp);

      if (pd.callOnAreaChange) {
        let butFind = document.createElement('button');
        butFind.textContent = '\u{1F50D}';
        curright.appendChild(butFind);
        let m = u;
        while (m && m._sut != 'Module') m = m._up;
        butFind.onclick = function () {
          setUnitView(w2aw, m._subUnits.Area[this.parentElement.firstChild.value]);
          showUnits2();
        };
      }

      if (setunitID) {
        {
          let butSet = document.createElement('button');
          butSet.textContent = '\u{2B05}';
          curright.appendChild(butSet);
          let butFind = document.createElement('button');
          butFind.textContent = '\u{1F50D}';
          curright.appendChild(butFind);
          butSet.sid = setunitID;
          butFind.sid = setunitID;
          // left window = left set input value
          butSet.onclick = function () {
            this.sid.value = this.w2.curUnitId;
            let nv = setValueInMsg(this.sid.curunit, this.sid.msgname, this.sid.parname, this.sid.value);
            this.sid.value = nv;
          };
          butFind.onclick = function () {
            setUnitView(this.w2, unitIdToUnit2(this.sid.value));
            showUnits2();
          };
          if (w1a === w1aw) butSet.w2 = butFind.w2 = w2aw;
          else butSet.w2 = butFind.w2 = w1aw;
        }
      }

      if (pd.measureUnits) {
        let mu = document.createElement('span');
        mu.textContent = ' ' + pd.measureUnits;
        curright.appendChild(mu);
      }

      if (msgn == 'config') {
        let ppkval = document.createElement('button');
        ppkval.onclick = usePpkValue;
        ppkval.parName = pn;
        ppkval.unit = u;
        ppkval.ppkval = true;
        makeParPpk(ppkval);
        curright.appendChild(ppkval);
      }
    } else if (
      pd.rw &&
      w1a === w1aw &&
      pd.RWoperatorLevel <= opLvl &&
      md.__.RWoperatorLevel <= opLvl &&
      msgn != 'config' &&
      !md.__.rwAll &&
      !pd.ro
    ) {
      let but = document.createElement('button');
      if (pd.maxValue < 2 && pd.format != 'string') {
        but.textContent = schema.dictionary.toggle[lang];
      } else {
        but.textContent = schema.dictionary.change[lang];
      }
      but.onclick = changeValue;
      but.msgname = msgn;
      but.parname = pn;
      if (pd.format != 'string') but.maxParValue = pd.maxValue;
      but.pd = pd;
      but.curValue = val;
      but.curunit = u;
      curright.appendChild(but);
    }
  }
} // function makeparspan

function chngtp() {
  try {
    if (w1a.curUnit) {
      let sd = w1a.curUnit._up._desc.__.subUnits[w1a.curUnit._sut];
      if (!(sd.hardware || sd.software)) return;
      fm.style.display = 'block';

      let curtype = w1a.curUnit._typeName;
      if (!curtype) curtype = w1a.curUnit._sut;

      wm.textContent =
        '\n\n\nYou are about to change subUnit TYPE for this unit\n all settings will be reset to default!\n\n';
      wm.setAttribute('style', 'white-space: pre-wrap;');

      const sel = document.createElement('select');

      let list = schema.unitTypes[w1a.curUnit._sut].__.allRealHeirs;

      function makeopt(tn) {
        let opt = document.createElement('option');
        opt.value = tn;
        opt.textContent = getTypeName(tn);
        sel.appendChild(opt);
      }

      //           if(!list.includes(w1a.curUnit._sut))
      //             makeopt(w1a.curUnit._sut);
      for (let t of list) makeopt(t);
      wm.appendChild(sel);
      sel.value = curtype;

      mb.onclick = function () {
        if (changeUnitType(w1a.curUnit, sel.value)) refreshBoth();
        fm.style.display = 'none';
      };
    }
  } catch {}
}

function oneCommand(w1a, msgn, u) {
  let md = u._desc[msgn];
  if (!md || !md.__ || md.__.notForOperator) return;
  if (md.__.operatorLevel > opLvl) return;
  if (!u[msgn]) u[msgn] = {};
  if (!u._typeName) u._typeName = u._sut;
  let editAll = md.__.rwAll && md.__.RWoperatorLevel <= opLvl && w1a === w1aw;
  if (md && !md.__.unused) {
    const curf = document.createElement('fieldset');
    curf.title = md.__['Description' + lang];
    let cursub = document.createElement('legend');
    cursub.textContent = md.__['Title' + lang];
    curf.appendChild(cursub);
    w1a.appendChild(curf);
    const curfs = document.createElement('table');
    curfs.width = '100%';
    curf.appendChild(curfs);
    let separateButtons;
    for (let p in md)
      if (p[0] !== '_' && md[p] && (!md[p].operatorLevel || md[p].operatorLevel <= opLvl)) {
        if (md[p].separateButtons) {
          separateButtons = p;
          continue; // this "selectable" parameter will be expanded as separate buttons
        }
        if (md[p].format == 'string') {
          if (typeof u[msgn][p] == 'string' && md[p].maxValue < u[msgn][p].length)
            u[msgn][p] = u[msgn][p].substring(0, md[p].maxValue);
        } else if (md[p].format == 'bytes' && ArrayBuffer.isView(u[msgn][p])) {
          // u[msgn][p] = Uint8toString(u[msgn][p]);
        } else {
          checkValue(u[msgn], md[p], p);
        }
        if (!u[msgn]) u[msgn] = {};
        makeParSpan(p, u, msgn, curfs); // todo - order by level of inheritance
      }
    if (w1a === w1aw) {
      if (separateButtons) {
        let pd = md[separateButtons];
        let valueList = [];
        if (pd.maxValue == 1 && !pd.minValue) {
          // on/off
          valueList = [
            { name: getDict('stateON'), value: '1' },
            { name: getDict('stateOFF'), value: '0' },
          ];
        } else {
          let vl = paramShortEnums(pd);
          if (pd.enumOperatorLevel) for (let en in vl) if (pd.enumOperatorLevel[en] > opLvl) vl[en] = ''; // hide
          if (vl) for (let env in vl) if (vl[env] != '') valueList.push({ value: env, name: vl[env] });
        }
        for (vd of valueList) {
          const but = document.createElement('button');
          but.onclick = execCommandV;
          but.cmd = msgn;
          but.cmdVal = vd.value;
          but.cmdPar = separateButtons;
          but.curunit = u;
          but.w1aa = w1a;
          but.textContent = vd.name;
          curfs.appendChild(but);
        }
      } else {
        const but = document.createElement('button');
        let doRefresh = msgn == 'config' || !md.__.rwAll;
        if (opLvl < md.__.RWoperatorLevel) doExec = false;
        if (doRefresh) but.onclick = sendEmptyCommand;
        else if (md.__.specialCommand) but.onclick = execCommandS;
        else but.onclick = execCommand;
        but.cmd = msgn;
        but.curunit = u;
        but.w1aa = w1a;
        if (doRefresh) but.textContent = schema.dictionary.refresh[lang];
        else but.textContent = schema.dictionary.execute[lang];
        // if (md.__.rw)
        curfs.appendChild(but);
      }
    }
  }
} // function onecommand

function setUnitView(w1a, u) {
  // ref to object
  if (!u) u = w1a.curUnit;
  u = checkPresent(u);
  if (!u) u = sys;
  checkUnitDesc(u);
  if (setUnitViewBusy) return;
  timeLastUpdateUnitView = Date.now();

  /*
    if (u._typeName=="_Box"){
        setUnitViewSub(w1a,u,"Module");
        return;    
    }*/
  // if (u._typeName == "_System") {
  //     setUnitViewSub(w1a, u, "_Box");
  //     return;
  // }

  prepareDefaultConfig(u);

  setUnitViewBusy = true;
  try {
    initFaultsDown(u);

    w1a.innerHTML = '';

    let subs;
    // todo - check - save - curUnit. ???
    w1a.appendChild(setTopString(w1a, u));

    let sud = {};
    let hardsoft = false; // can change type and subnumber

    if (u._up) {
      let tname = getTypeName(u._typeName);
      let name = getTypeName(u._sut);
      if (name && tname) name += ' #' + u._sun + ' ( ' + tname + ' )';
      else name = 'unit';

      const curfs = document.createElement('fieldset');
      let cursub = document.createElement('legend');
      cursub.textContent = name;
      curfs.appendChild(cursub);
      const just_ = document.createElement('span');
      just_.textContent = ' ';
      curfs.appendChild(just_);

      sud = u._up._desc.__.subUnits[u._sut];

      hardsoft = (sud.hardware || sud.software) && u._sut != 'Module'; // todo multimodule

      let one = sud.qty === 1 && !hardsoft;

      if (hardsoft && w1a === w1aw) {
        u._hardsoft = true;

        {
          // configured checkbox
          //  const sp = document.createElement("div");
          const lab = document.createElement('label');
          lab.innerText = schema.dictionary.configured[lang] + '       ';
          const inp = document.createElement('input');
          inp.type = 'checkbox';
          inp.id = 'configuredCheckBox';
          lab.htmlFor = 'configuredCheckBox';
          if (u._configured) inp.checked = true;
          inp.onclick = function (elt) {
            if (w1a.curUnit && !w1a.curSut) {
              if (elt.currentTarget.checked) markConfigured(w1a.curUnit);
              else w1a.curUnit._configured = false;
              setUnitView(w1a);
            }
          };

          inp.title = schema.dictionary.configuredDetailed[lang];

          const d = document.createElement('div');

          d.appendChild(inp);
          d.appendChild(lab);
          curfs.appendChild(d);
        }

        if (u._sut == '_Box' || sud.software) {
          // configured checkbox
          //  const sp = document.createElement("div");
          const lab = document.createElement('label');
          lab.innerText = schema.dictionary.filter[lang] + '       ';
          const inp = document.createElement('input');
          inp.type = 'checkbox';
          inp.id = 'filterCheckBox';
          lab.htmlFor = 'filterCheckBox';
          if (u._filter) inp.checked = true;
          inp.onclick = function (elt) {
            if (w1a.curUnit && !w1a.curSut) {
              w1a.curUnit._filter = elt.currentTarget.checked;
              setUnitView(w1a);
            }
          };

          const d = document.createElement('div');

          d.appendChild(inp);
          d.appendChild(lab);
          curfs.appendChild(d);
        }

        if (u._sut != 'Module') {
          const but = document.createElement('button');
          but.onclick = function () {
            if (w1a.curUnit && !w1a.curSut) {
              w1a.curUnit._deleted = true;
              w1a.curUnit._configured = false;
              setUnitView(w1a);
            }
          };

          but.textContent = '\u{274C}';
          but.title = schema.dictionary['delete'][lang];
          curfs.appendChild(but);
        }
        if (u._sut != 'Module') {
          const but = document.createElement('button');
          but.onclick = function () {
            if (w1a.curUnit && !w1a.curSut) {
              let u = w1a.curUnit;
              u._deleted = true;
              u._configured = false;
              setUnitViewSub(w1a, u._up, u._sut);
              deleteUnit(u);
            }
          };

          but.textContent = '\u{274C}\u{274C}';
          but.title = schema.dictionary['delete'][lang] + ' NOW!';
          curfs.appendChild(but);
        }

        {
          // hardwareIDs
          let hid = getHardwareID(u);
          if (hid != '') {
            const sp = document.createElement('span');
            sp.innerText = ' ' + hid + ' ';
            curfs.appendChild(sp);
          }
        }

        {
          const but = document.createElement('button');
          if (u._sut != 'Module')
            // todo - module address
            but.onclick = function () {
              try {
                let sd = w1a.curUnit._up._desc.__.subUnits[w1a.curUnit._sut];
                let maxQ = sd.qty;
                if (!(sd.hardware || sd.software)) return;
                fm.style.display = 'block';
                wm.textContent = '\n\n\nYou are about to change subUnit number for this unit\n\n';
                wm.setAttribute('style', 'white-space: pre-wrap;');
                const but = document.createElement('input');
                but.type = 'number';
                but.min = '1';
                but.max = '' + maxQ;
                but.value = '' + w1a.curUnit._sun;
                wm.appendChild(but);

                mb.onclick = function () {
                  let newsun = but.value;
                  if (changeUnitSun(w1a.curUnit, newsun)) refreshBoth();
                  else but.value = w1a.curUnit._sun;
                  fm.style.display = 'none';
                };
              } catch {}
            };
          but.textContent = '# ' + u._sun;
          curfs.appendChild(but);
        }

        //// was only for demonstration and debugging
        // if (u._sut == 'AU') {
        //   const but = document.createElement('button');
        //   but.textContent = 'toAnother';
        //   but.onclick = function () {
        //     try {
        //       let u = w1a.curUnit;
        //       let alsun = u._up._sun;
        //       if (alsun == 1) alsun = 2;
        //       else alsun = 1;
        //       let newAL = u._up._up._subUnits.AL[alsun];
        //       moveUnit(u, newAL);
        //       setUnitView(w1a,u);
        //     } catch {}
        //   };
        //   curfs.appendChild(but);
        // }

        {
          const but = document.createElement('button');
          if (u._sut != 'Module')
            // todo - module address
            but.onclick = chngtp;
          but.textContent = 'type "' + tname + '"';
          curfs.appendChild(but);
        }
      } // if hardsoft

      if (!hardsoft) u._configured = true;

      if (!['_Box', 'Boot', '_System'].includes(u._typeName)) {
        const but = document.createElement('span');
        let sId = shortID(u);
        but.textContent = 'ID= ' + sId;
        w1a.curUnitId = sId; // for set in another window
        curfs.appendChild(but);
      }

      {
        let rm = realMark(u);
        if (rm.length) {
          let p = document.createElement('span');
          p.setAttribute('class', 'onlinemark');
          p.textContent = rm;
          curfs.appendChild(p);
        }
      }

      {
        if (u._hardsoft && u._deleted) {
          let p = document.createElement('span');
          p.setAttribute('class', 'onlinemark');
          p.textContent = ' _ ' + schema.dictionary.willBeDeleted[lang];
          curfs.appendChild(p);
        } else if (u._hardsoft && !u._configured) {
          let p = document.createElement('span');
          p.setAttribute('class', 'onlinemark');
          p.textContent = ' _ ' + schema.dictionary.willBeNotSaved[lang];
          curfs.appendChild(p);
        } else if (u._hardsoft && u._offline && online) {
          let p = document.createElement('span');
          p.setAttribute('class', 'onlinemark');
          p.textContent = ' _ ' + schema.dictionary.notFoundInPPK[lang];
          curfs.appendChild(p);
        }

        if (u._sunPPK && u._sunPPK != u._sun) {
          let p = document.createElement('span');
          p.setAttribute('class', 'onlinemark');
          p.textContent = ' _ ' + schema.dictionary.serialInPPKisAsSetIn[lang] + ' #' + u._sunPPK;
          curfs.appendChild(p);
        }

        if (u._sunPPKback && u._sunPPKback != u._sun) {
          let p = document.createElement('span');
          p.setAttribute('class', 'onlinemark');
          p.textContent = ' _ ' + schema.dictionary.thisSNisAsInPPKinUnit[lang] + ' #' + u._sunPPKback;
          curfs.appendChild(p);
        }
      }

      if (u._sut == '_Box') {
        {
          const but = document.createElement('button');
          but.title = 'export CFG';
          but.className = 'lnk';
          but.onclick = function () {
            exportCfgCsv(u);
          };
          but.textContent = 'exp';
          curfs.appendChild(but);
        }
        {
          const but = document.createElement('button');
          but.title = 'IMport CFG';
          but.className = 'lnk';
          but.onclick = function () {
            importCfgCsv(u);
          };
          but.textContent = 'imp';
          curfs.appendChild(but);
        }
        {
          const but = document.createElement('button');
          but.title = 'IMport CFG win-1251';
          but.className = 'lnk';
          but.onclick = function () {
            importCfgCsv(u, true);
          };
          but.textContent = 'imp1251';
          curfs.appendChild(but);
        }
      }

      w1a.appendChild(curfs);
    } // if u._up

    {
      // subunits if any
      let curfs;

      for (let sutn in u._subUnits)
        if (
          sutn !== '__' &&
          u._desc.__.subUnits &&
          u._desc.__.subUnits[sutn] &&
          !u._desc.__.subUnits[sutn].unused &&
          (u._desc.__.subUnits[sutn].operatorLevel <= opLvl || !u._desc.__.subUnits[sutn].operatorLevel)
        ) {
          let fd = 0;
          let sus = u._subUnits[sutn];
          for (let n in sus)
            if (n[0] != '_') {
              let sn = sus[n];
              if (
                sn &&
                sn.status &&
                (sn.status.fault || sn.status.faultDown) &&
                !sn.status.disabled &&
                !(sn.config && sn.config.ignored)
              ) {
                fd = 1;
                break;
              }
            }
          if (!curfs) {
            subs = true;
            curfs = document.createElement('fieldset');
            let cursub = document.createElement('legend');
            curfs.title = cursub.textContent = schema.dictionary['subunits'][lang];
            curfs.appendChild(cursub);
          }
          let desc = getSutDesc(sutn, u);
          const but = document.createElement('button');
          but.sut = sutn;
          if (fd) but.className = 'lnk111d';
          but.w1a = w1a;
          but.title = desc;
          but.onclick = function () {
            setUnitViewSub(this.w1a, this.w1a.curUnit, this.sut);
          };
          but.textContent = getSutName(sutn, u);
          curfs.appendChild(but);
        }

      if (curfs) w1a.appendChild(curfs);
    } // subunits

    //if(!u._deleted) {
    const cp = ['config', 'status', 'info'];

    lastClickedField = undefined;

    for (let msgn of cp) oneCommand(w1a, msgn, u);

    if (w1a === w1aw) {
      for (let m in u._desc)
        if (m[0] !== '_' && !cp.includes(m)) {
          let md = u._desc[m];
          //if (md.__.rw) {
          if (Object.keys(md).length > 1) oneCommand(w1a, m, u);
          else if (md.__.rw && md.__.operatorLevel <= opLvl && md.__.RWoperatorLevel <= opLvl) {
            const but = document.createElement('button');
            but.onclick = execCommand;
            but.cmd = m;
            but.curunit = u;
            but.textContent = md.__['Title' + lang];
            but.title = md.__['Description' + lang];
            w1a.appendChild(but);
          }
          //}
        }

      if (u._desc.updateFirmware && u._desc.updateFirmware.__ && !u._desc.updateFirmware.__.unused) {
        //  && u._online ) {           todo - remove
        let curfs = document.createElement('fieldset');
        let cursub = document.createElement('legend');
        cursub.textContent = 'update firmware';
        curfs.appendChild(cursub);
        w1a.appendChild(curfs);
        const but = document.createElement('input');
        but.type = 'file';
        but.onchange = setUpdateFirmwareView;
        but.curunit = u;
        curfs.appendChild(but);
        //if(fw2update) {
        const butsf = document.createElement('span');
        butsf.id = 'updateFirmwareDiv';
        butsf.textContent = ''; // uploading firmware ("+fwPercent+"%) : "+fw2filename+" ";
        curfs.appendChild(butsf);
        const buts = document.createElement('button');
        buts.textContent = 'Stop';
        buts.style.visibility = 'hidden';
        buts.id = 'updateFirmwareDivStop';
        buts.onclick = function () {
          updateFirmware(null, null);
          updateBootView(u);
          // setUnitView(w1aw);
        };
        curfs.appendChild(buts);
        // }
      }
      //}

      checkSubscribe(w1a, u, undefined);
      if (u !== w1a.curUnit) queryUnit(u);
    }
    if (w1a.curUnit != u) recalcFaultsDown(w1a.curUnit);

    if (!sys.state) sys.state = {};
    if (w1a == w1aw) {
      if (w1a.curUnit != u || w1a.curSut) history.pushState({ unit: u._idString }, '', '?unit=' + u._idString);
      sys.state.curUnit = u2string(u);
      sys.state.curType = null;
    }
    w1a.curUnit = u;
    w1a.curSut = null;
  } catch {}

  setUnitViewBusy = false;

  /// if(u._mod!==oldMod || u._box!==oldBox) confirmSubscribe(); ====todo
}

function checkSubscribe(w1a, u, sut) {
  if (w1a !== w1aw) return;
  if (w1a.curUnit === u && w1a.curSut == sut) return;
  setSubscribe(u, false, sut);
}

function dosendParameter() {
  let inp = this.previousElementSibling;
  let newv = Number(this.previousElementSibling.value);
  if (inp.type == 'checkbox') newv = inp.checked ? 1 : 0;
  dosend1Parameter(this.curunit, this.msgname, this.parname, newv);
  let p = this.parentElement;
  p.removeChild(p.lastChild);
  p.removeChild(p.lastChild);
  let ch = p.lastChild;
  ch.textContent = ch.lastText;
  ch.lastText = '';
  let cmd = {};
  cmd[this.msgname] = {};
  cmd[this.msgname][this.parname] = this.curunit[this.msgname][this.parname];
  this.curunit[this.msgname][this.parname] = ch.curValue = newv;
  updateUnitView(this.curunit, cmd);
}

function usePpkValue() {
  if (!this) return;
  if (!this.parName) return;
  if (!this.unit) return;
  let newVal;
  if (this.unit._configPPK) {
    newVal = this.unit._configPPK[this.parName];
  }
  let ps = this.parentElement?.firstChild;
  if (ps && newVal !== undefined) {
    let nv = setValueInMsg(this.unit, ps.msgname, ps.parname, newVal);
    ps.value = nv;
    if (nv == newVal) {
      delete this.unit._configPPK[this.parName];
      this.hidden = true;
    } else this.hidden = false;

    if (ps.type == 'checkbox') {
      ps.checked = !!nv;
      ps.value = ps.checked ? 1 : 0;
    }
  }
  // = this.parText;
}

function changeValue() {
  // create elements to query new value
  if (!this.lastText) {
    this.lastText = this.textContent;
    this.textContent = '\u{274C}';

    let enums = paramShortEnums(this.pd);
    let ask;
    if (!enums) {
      ask = document.createElement('input');
      if (this.pd.maxValue == 1 && pd.format != 'string') {
        ask.type = 'checkbox';
        ask.value = 1 ^ this.curValue;
        if (Number(ask.value)) ask.checked = true;
      } else if (this.pd.format != 'bytes') {
        // bytes ==> plain input == todo = get unit ID from another window
        ask.value = this.curValue;
        ask.type = 'number';
        if (this.pd.maxValue) ask.max = '' + this.pd.maxValue;
        if (this.pd.minValue) ask.min = '' + this.pd.minValue;
        else ask.min = '0';
      }
    } else {
      ask = document.createElement('select');
      for (en in enums) {
        let opt = document.createElement('option');
        opt.value = Number(en);
        opt.textContent = enums[en];
        ask.appendChild(opt);
      }
      ask.value = this.curValue;
    }

    this.parentElement.appendChild(ask);
    let snd = document.createElement('button');
    snd.textContent = schema.dictionary['2ppk'][lang]; //  //  2705
    snd.onclick = dosendParameter;
    snd.msgname = this.msgname;
    snd.parname = this.parname;
    snd.curunit = this.curunit;
    this.parentElement.appendChild(snd);
  } else {
    this.textContent = this.lastText;
    this.parentElement.removeChild(this.parentElement.lastChild);
    this.parentElement.removeChild(this.parentElement.lastChild);
    this.lastText = '';
  }
}

function refreshBoth() {
  refresh(w1aw);
  refresh(w2aw);
}

function refresh(w1a) {
  if (w1a && w1a.curUnit && (w1a == w1aw || !w1a.hidden)) {
    if (w1a.curSut) setUnitViewSub(w1a, w1a.curUnit, w1a.curSut);
    else setUnitView(w1a, w1a.curUnit);
  }
}

function debabort() {
  alert('debabort');
}

function putLog(str, color) {
  // setW2('t');
  let ssss = '';
  if (color) ssss = ssss + '<font color="' + color + '">' + new Date().toLocaleTimeString(lang) + ' ' + str + '</font>';
  else ssss = ssss + new Date().toLocaleTimeString(lang) + ' ' + str;
  if (log.length > 1500) log.shift();
  log.push(ssss);
  let s = '';
  for (let ss of log) s = ss + '<br>' + s;
  if (w2b) w2b.innerHTML = s;
}

var logFilter = false;
var logFilterUnits = false;

function filterLog() {
  logFilter = !logFilter;
  document.getElementById('filterLog').style.color = logFilter ? 'Red' : '';
}

function filterLogUnits() {
  logFilterUnits = !logFilterUnits;
  document.getElementById('filterLogUnits').style.color = logFilterUnits ? 'Red' : '';
}

function putLogLog(str, u, reverse) {
  if (logFilter && u) {
    if (logFilterUnits) {
      if (!u._filter) return;
    } else {
      while (u && u._up && u._sut != '_Box') u = u._up;
      if (u && !u._filter) return;
    }
  }
  // setW2('t');
  if (logLog.length > logLinesQty) logLog.shift();
  logLog.push(str);
  let s = '';

  if (reverse) {
    for (let ss of logLog) s += '<br><span onclick="showLogDetailes(this);">' + ss + '</span>';
  } else {
    for (let ss of logLog) s = '<br><span onclick="showLogDetailes(this);">' + ss + '</span><br>' + s;
  }
  if (winLog) winLog.innerHTML = s;
}

function initializeView2() {
  winLog = document.getElementById('winLog');
  // document.getElementById('pullLog').checked = noPullLog;

  node1 = 0;

  /*
   * two main windows
   */
  w1aw = document.getElementById('w1a');
  w2aw = document.getElementById('w2a');
  w1b = document.getElementById('w1b');
  w2b = document.getElementById('w2b');
  w1c = document.getElementById('w1c');
  w2c = document.getElementById('w2c');
  w2d = document.getElementById('w2d');
  w2e = document.getElementById('w2e');

  setW2('t');

  // ts = document.getElementById('topString');

  putLog('Starting');

  /*
   *  z-index: 1; display: none;  height: 100%;   width: 100%;    flex-flow: column;
   */
  fm = document.getElementById('fullmodal');

  /* in it inside
   * width: 100%; overflow=auto ; flexgrow=1
   */
  wm = document.getElementById('wm');
  mb = document.getElementById('modButtonOK');

  let cncl = document.getElementById('modButtonCancel');

  cncl.onclick = function (event) {
    fm.style.display = 'none';
    abortAllFirmwareUpdates();
  }; // hide modal on click outside central part

  initializeModel(initializeView);
}

function initializeView1() {
  initializeSchema(initializeView2);
}

//function displayString(msg) {
//    document.getElementById("topString").textContent = msg;
//}

function changeLang(newl) {
  //if (newl == lang) return;
  if (!schema.languages.includes(newl)) newl = 'RU';
  lang = newl;
  document.getElementById('lang').value = lang;
  refreshBoth();
  for (let mid of ['menu0', 'menu1', 'menu2']) {
    let m = document.getElementById(mid);

    function chngLng(elt) {
      let d = elt.id;
      if (d)
        if (d.substring(0, 4) === 'dict') {
          let di = d.substring(4);
          let newText;
          if (schema.dictionary[di]) newText = schema.dictionary[di][lang];
          if (!newText) newText = '';
          if (newText.length && (elt.textContent.charCodeAt(0) < 0x500 || !elt.textContent.length))
            // not special icon
            elt.textContent = newText;
          elt.title = newText;
          if (schema.dictionary[di + 'Desc']) newText = schema.dictionary[di + 'Desc'][lang];
          if (newText) elt.title = newText;
        }
    }
    if (m) m.childNodes.forEach(chngLng);
  }
  let lvl = document.getElementById('level');
  if (!opLvl) opLvl = 0;
  let oldv = lvl.value;
  let selstr = '';
  for (let l in schema.operatorLevels) {
    let optn = schema.operatorLevels[l][newl];
    if (!optn) optn = schema.operatorLevels[l].EN;
    if (!optn) optn = l;
    selstr += '<option value="' + schema.operatorLevels[l].number + '">' + optn + '</option>';
  }
  lvl.innerHTML = selstr;
  lvl.value = opLvl;
  lvl.onchange = function () {
    if (this.value > maxLvl) this.value = maxLvl;
    opLvl = this.value;
    modelChanged('userSet');
    refreshBoth();
  }; // todo - refresh other windows also
}

function initializeView() {
  let lng = document.getElementById('lang');
  let selstr = '';
  for (let l of schema.languages) selstr += '<option value="' + l + '">' + l + '</option>';
  lng.innerHTML = selstr;
  lng.onchange = function () {
    changeLang(this.value);
  }; // todo - refresh other windows also
  // let svdlng = lang;
  //    lang = null;
  // changeLang(svdlng); // implictly called after setUser

  try {
    setUnitViewSub(w1aw, string2u(sys.state.curUnit), sys.state.curType);
  } catch {
    setUnitViewSub(w1aw);
  }
  // setUnitViewSub(w2aw);

  // askUser();
  let pass = localStorage.getItem('forcedUserPass');
  if (pass)
    try {
      setUser(pass);
      // if (pass && pass.length) opLvl = loggedInUser.config.level;
      // setUser(pass);
    } catch {}

  try {
    document.getElementById('doemulateAnswer').checked = false;
  } catch {}

  lastLog();

  window.addEventListener('popstate', event => {
    if (event.state.unit) {
      // if(event.state.sut)
      let u = unitIdToUnit2(event.state.unit);
      w1aw.curUnit = u;
      w1aw.curSut = event.state.sut; // to prevent new pushState
      setUnitViewSub(w1aw, u, event.state.sut);
      // else setUnitView(unitIdToUnit2(event.state.unit));
      event.preventDefault();
    }
  });

  buildAREAS();
}

function setW2(n) {
  let a = 'a';
  switch (n) {
    case 't':
      a = 'b';
      break;
    case 'x':
      a = 'd';
      break;
    case 'l':
      a = 'c';
      break;
    case 'd':
      a = 'd';
      break;
    case 'e':
      a = 'e';
      break;
  }
  let s = document.getElementById('selectw2');
  s.value = a;
  selectW2(s);
}

function sendConfigToPPK(w1a, only1) {
  if (sendingUnit) {
    stopSendingUnit('You stopped uploading');
  } else {
    setW2('t');
    justSendConfigToPPK(w1a.curUnit, w1a.curSut, only1);
    buttonFlash(document.getElementById(only1 ? 'dict2ppk1' : 'dict2ppk'), true);
  }
}

function sendConfigToPPK1(w1a) {
  sendConfigToPPK(w1a, true);
}

var fullLogText;
var fullLog = false;
var logQtyDone = 0;

function copyLog() {
  window.getSelection().selectAllChildren(winLog);
  //  var textArea = document.createElement("input");
  //  textArea.type = "text";
  // // textArea.style.position = 'fixed';
  // //   textArea.style.top = 0;
  // //   textArea.style.left = 0;

  // //   // Ensure it has a small width and height. Setting to 1px / 1em
  // //   // doesn't work as this gives a negative w/h on some browsers.
  // //   textArea.style.width = '2em';
  // //   textArea.style.height = '2em';

  // //   // We don't need padding, reducing the size if it does flash render.
  // //   textArea.style.padding = 0;

  // //   // Clean up any borders.
  // //   textArea.style.border = 'none';
  // //   textArea.style.outline = 'none';
  // //   textArea.style.boxShadow = 'none';

  // //   // Avoid flash of the white box if rendered for any reason.
  // //   textArea.style.background = 'transparent';

  //   textArea.value = winLog.innerText;
  //   winLog.innerText = "";

  //   winLog.appendChild(textArea);
  //   textArea.focus();
  //   textArea.select();
  //   try {
  //  winLog.setSelectionRange(0, 99999); /* For mobile devices */
  // } catch {}

  //    window.getSelection().selectAllChildren(winLog);

  let successful = 0;
  try {
    successful = document.execCommand('copy');
    var msg = successful ? 'successful' : 'unsuccessful';
    console.log('Copying text command was ' + msg);
  } catch (err) {
    console.log('Oops, unable to copy');
  }
  try {
    if (!successful) {
      navigator.clipboard.writeText(winLog.innerText);
    }
  } catch {}
  // let s=textArea.value;
  //   textArea.value="";
  //   winLog.removeChild(textArea);
  //   winLog.innerText = s;
}

function displayLog(rec) {
  let str = logText(rec);
  if (fullLog) {
    let c = '<br/>' + ++logQtyDone + str + '<br/>';
    fullLogText += c;
    winLog.innerHTML = c;
  } else {
    loggerStrings.push(str);
    if (loggerStrings.length > 60) loggerStrings.shift();
    str = '';
    for (let i = loggerStrings.length; i--; ) str += loggerStrings[i] + '<br/>';
    winLog.innerHTML = str;
  }
}

function viewDelete(u) {
  if (u == sys) {
    w1a.curUnit = undefined;
    w2a.curUnit = undefined;
    return;
  }
  let cu = w1a.curUnit;
  while (cu) {
    if (cu == u) {
      setUnitViewSub(w1a, u._up, u._sut);
      break;
    }
    cu = cu._up;
  }
  cu = w2a.curUnit;
  while (cu) {
    if (cu == u) {
      setUnitViewSub(w2a, u._up, u._sut);
      break;
    }
    cu = cu._up;
  }
}

function postDelete() {
  //if (w1a.curUnit)
  setUnitViewSub(w1a, w1a.curUnit, w1a.curSut);
  if (w2a.curUnit && !w2a.hidden) setUnitViewSub(w2a, w2a.curUnit, w2a.curSut);
}

function selectW2(t) {
  showAreas = t.value == 'e';
  w2a.hidden = true;
  w2b.hidden = true;
  w2d.hidden = true;
  w2e.hidden = true;
  w2c.hidden = true;
  window['w2' + t.value].hidden = false;
}

function debugCommand(e) {
  let file = e.files[0];
  let u = w1a.curUnit;
  // while (u && u._sut != "Module")
  //     u = u._up;
  if (u) {
    let reader = new FileReader();
    reader.onload = function () {
      let cmd = JSON.parse(reader.result);
      sendCommand(u, cmd, cmd.category, cmd.tag);
    };
    reader.onerror = function () {
      systemLog(true, reader.error);
    };

    reader.readAsText(file);
  }
}

function setAskedUser() {
  const passin = document.getElementById('password');
  const butSet = document.getElementById('logInSet');
  const butLog = document.getElementById('logIn');
  if (!passin.hidden) {
    passin.hidden = true;
    butLog.hidden = false;
    butSet.hidden = true;
    setUser(passin.value);
  }
}

function askUser() {
  const passin = document.getElementById('password');
  const butSet = document.getElementById('logInSet');
  const butLog = document.getElementById('logIn');
  if (passin.hidden) {
    passin.hidden = false;
    passin.value = '';
    butLog.hidden = true;
    butSet.hidden = false;
  }

  // if (sys.config.defaultUser) setUser(sys.config.defaultUser);
  // if (!loggedInUser) {
  //   let pro = getDict('logIn');
  //   if (lang && lang != 'EN') pro += ' (enter PIN:)';
  //   setUser(prompt(pro));
  // }
}

// expected struct with smth like
// {"rebooter":{"6":{"1":{bin:binImage,name:filename,version:9999}, "2":{bin:binfile...}...}, "7":{}}
// {"application":...}               === same as rebooter

function setFileMultyText() {
  wm.innerHTML = '<br>Mass firmware update<br><br>';
  for (let bv = 6; bv < 8; bv++) {
    for (let bt = 1; bt < 4; bt++) {
      wm.innerHTML +=
        'bootloader  firmware for board version ' +
        bv +
        ', module ' +
        bt +
        ' = ' +
        wm.mf.rebooter[bv][bt].name +
        '<br>';
      wm.innerHTML +=
        'application firmware for board version ' +
        bv +
        ', module ' +
        bt +
        ' = ' +
        wm.mf.application[bv][bt].name +
        '<br>';
    }
  }
  wm.innerHTML += '<br><br>';
}

function setFileMulty() {
  let reader = [];
  for (let i = 0; i < this.files.length; i++) {
    let file = this.files[i];
    let nam = file.name;
    let namSplitted = nam.split('_');
    let version = 0;
    reader[i] = new FileReader();

    reader[i].onload = function (e) {
      // todo check unit type
      let fw = new Uint8Array(this.result);
      let typ = fw[0x1b];
      let boardver = fw[0x16];
      let mfw;
      if (nam[0] == 'r') {
        mfw = wm.mf.rebooter;
        version = namSplitted[5];
      } else {
        mfw = wm.mf.application;
        version = namSplitted[4];
      }
      version = parseInt(version);
      if (!mfw[boardver]) mfw[boardver] = {};
      mfw[boardver][typ] = { bin: fw, name: nam, version: version };
      fillMultiAskText();
    };
    reader[i].readAsArrayBuffer(file);
  }
}

function fillMultiAskText() {
  const fib = document.createElement('input');
  fib.type = 'file';
  fib.multiple = 'multiple';
  fib.onchange = setFileMulty;
  setFileMultyText();
  wm.innerHTML += '<br><br>select files<br><br>';
  fm.style.display = 'block';
  wm.appendChild(fib);
}

function multyFWask() {
  if (!online) {
    alert('NOT CONNECTED !');
    return;
  }
  wm.mf = {
    rebooter: {},
    application: {},
  };

  // 6: {1:{ bin: 0, name: 'none', version: 0 },2:{ bin: 0, name: 'none', version: 0 },3:{ bin: 0, name: 'none', version: 0 }}, 7: { bin: 0, name: 'none', version: 0 } },
  for (let bv = 6; bv < 8; bv++) {
    wm.mf.rebooter[bv] = {};
    wm.mf.application[bv] = {};
    for (let bt = 1; bt < 4; bt++) {
      wm.mf.rebooter[bv][bt] = { bin: undefined, name: 'none', version: 0 };
      wm.mf.application[bv][bt] = { bin: undefined, name: 'none', version: 0 };
    }
  }
  // OK button
  mb.onclick = function () {
    setW2('t'); // terminal
    startMultipleFirmwares(wm.mf);
    updateBootView();
  };
  fillMultiAskText();
}

function fastReadAllCfgView() {
  fastReadAllCfg(w1aw.curUnit);
}

function saveForcedPass(th) {
  localStorage.setItem('forcedUserPass', th.value);
}

var logLinesQty = 1000;
function changeLogLines() {
  logLinesQty = document.getElementById('logLines').value;
  lastLog();
}

function displayOscilloscope(module, oscilloscopeResult) {
  let oscilloscopeData = oscilloscopeResult.data;
  let outText = '';
  fm.style.display = 'block';
  mb.innerHTML = 'save';
  let qty = 0;
  let count0 = 0;
  mb.onclick = function (event) {
    let text = '';
    for (let pnt = count0; pnt--; ) {
      for (let row = qty; row--; ) {
        text += oscilloscopeData[pnt * qty + row] + '; ';
      }
      text += '\n';
    }
    text +=
      '\n request:\n' +
      JSON.stringify(module.oscilloscope) +
      '\n' +
      'result:\n' +
      JSON.stringify(oscilloscopeResult) +
      '\n';
    saveTextToFile(text, '' + Date.now() + '.csv');
    fm.style.display = 'none';
    wm.innerHTML = '';
    mb.innerHTML = 'OK';
  }; // OK button
  let chanNums = [];
  wm.innerHTML =
    oscilloscopeResult.address +
    ';  err = ' +
    oscilloscopeResult.errors +
    ';  biterr = ' +
    oscilloscopeResult.bitErrors +
    '<br></br>';

  let chnames = [];
  for (let i = 1; i < 4; i++) {
    let nam = 'channel' + i;
    let chNum = module.oscilloscope[nam];
    if (chNum < 12) {
      qty++;
      chanNums.push(i - 1);
    }
    let chname = schema.common.oscillChannels.enumEN[chNum];
    chnames.push(chname);
  }
  if (qty < 1) {
    qty = 1;
    chanNums.push(0);
  }

  count0 = 1500 / qty;
  if (module.oscilloscope.fastPulse) {
    if (qty == 3) count0 = 497;
    //1491;
    else if (qty == 2) count0 = 749;
    //1498;
    else count0 = 1498;
  }
  let maxv = [0, 0, 0];
  let minv = [0xffff, 0xffff, 0xffff];
  for (let n = qty; n--; ) {
    // 2,1,0
    let count = count0;
    while (count--) {
      let v = oscilloscopeData[count * qty + n];
      if (v > maxv[n]) maxv[n] = v;
      if (v < minv[n]) minv[n] = v;
    }
  }

  for (let i = 0; i < qty; i++) {
    wm.innerHTML +=
      ['white', 'red', 'green'][i] + '=' + chnames[i] + ' ( max= ' + maxv[i] + ', min= ' + minv[i] + ');  ';
  }

  wm.innerHTML += '<br><canvas id="chartWin" width="' + (20 + count0 * 2) + '" height="540" ></canvas>'; // style="border:1px solid #000000;
  const chartWin = document.getElementById('chartWin');
  const ctx = chartWin.getContext('2d');

  ctx.fillStyle = 'rgb(0 0 200)';
  ctx.fillRect(5, 5, count0 * 2 + 15, 535);

  for (let n = qty; n--; ) {
    // 2,1,0
    let coeff = maxv[n] - minv[n];
    if (coeff) coeff = 500 / coeff;
    else coeff = 500;
    let count = count0;
    ctx.strokeStyle = ['white', 'red', 'green'][n];
    ctx.beginPath();
    while (count--) {
      let v = oscilloscopeData[count * qty + n];
      let newV = v - minv[n];
      newV = newV * coeff;
      ctx.lineTo(10 + count * 2, n * 10 + 510 - Math.round(newV));
    }
    ctx.stroke();
  }
}

function alertFullMessage(msg) {
  systemLog(true, decodedRecord2text(decodeRecordLang(msg)));
}

function showLogDetailes(sp) {
  let line = sp.innerText;
  let numInd = line.split(' ')[2];
  if (numInd[numInd.length - 1] == "'") numInd = numInd.substring(0, numInd.length - 1);
  let num = numInd.split('.')[0];
  let b = sys._subUnits._Box[num];
  let SN = b.config.SN;
  let snadrind = '' + SN + '.' + numInd;
  findLogMessage(snadrind, alertFullMessage);
}

function saveTerminal() {
  let s = '';
  for (let ss of log) s = ss + '\n' + s;
  saveTextToFile(s, 'term' + Date.now() / 1000);
}

var showAreas = false;
var curAreas = []; // here will be stack of areas selected, when clicked in
var topAreas = []; // list of areas (and TSs ? - now only areas) to be displayed at top level, IDs

function addSelectedToView(u) {
  // unit - area - add to monitoring in AREAS
  const allAreas = u._subUnits.Area;
  if (allAreas && Object.keys(allAreas).length) {
    for (let arn in allAreas) {
      let ar = allAreas[arn];
      ar._selectedArea = ar._selected;
    }
  }
  buildAREAS();
}

function setSelectedToView(u) {
  for (arn in u._subUnits?.Area) u._subUnits.Area[arn]._selected = u._subUnits.Area[arn]._selectedArea;
  setUnitViewSub(w1aw, u, 'Area');
}

function buildAREAS() {
  // initialize
  curAreas = [];
  topAreas = [];
  const ppks = sys._subUnits._Box;
  let done = false;
  if (ppks) {
    for (let ppkn in ppks) {
      const ppk = ppks[ppkn];
      const m = ppk._subUnits?.Module[1];
      if (m) {
        const areas = m?._subUnits?.Area;
        if (areas && Object.keys(areas).length) {
          for (let arn in areas) {
            const ar = areas[arn];
            if (ar._selectedArea) topAreas.push(areas[arn]._idString);
          }
        }
      }
    }
    if (!topAreas.length) {
      // now build automatically from all top-areas
      for (let ppkn in ppks) {
        const areas = ppks[ppkn]._subUnits?.Module[1]?._subUnits?.Area;
        for (let arn in areas) {
          let ar = areas[arn];
          if (!ar?.config?.areaNum) {
            ar._selectedArea = true;
            topAreas.push(areas[arn]._idString);
          }
        }
      }
    }
    displayAREAS();
  }
}

function setAreasButton(but, ar) {
  // called when area changed or
  let newstate = '_';
  if (but) {
    if (ar?.status?.on) {
      if (ar.status.on > 1) {
        but.style.backgroundColor = 'Red';
        if (ar._sut == 'Area' || unitTypeCompatible(ar._typeName, 'FireSensorLink')) newstate = 'ПОЖАР';
        else newstate = 'ВКЛ-2';
      } else {
        but.style.backgroundColor = 'Orange';
        if (ar._sut == 'Area') newstate = 'ВНИМАНИЕ';
        else if (unitTypeCompatible(ar._typeName, 'FireSensorLink')) newstate = 'ПОЖАР';
        else newstate = 'ВКЛ';
      }
    } else if (ar?.status?.fault) {
      but.style.backgroundColor = 'Yellow';
      newstate = 'НЕИСПРАВ';
    } else {
      but.style.backgroundColor = 'Green';
    }
    but.innerHTML = but.___maintext + newstate;
  }
}

function setAreaButtonColor(ar) {
  // called when area changed
  setAreasButton(document.getElementById('view' + ar?._idString), ar);
}

function clickAREAS(id) {
  let ar = findUnitFromStringID(id);
  if (ar && ar._sut == 'Area') {
    curAreas.push(id);
    displayAREAS();
  }
  setUnitView(w1aw, ar);
}

function displayAREAS() {
  //   var win = window,
  //   doc = document,
  //   docElem = doc.documentElement,
  //   body = doc.getElementsByTagName('body')[0],
  //   ww = win.innerWidth || docElem.clientWidth || body.clientWidth;

  // // let ww = Screen.width-20;//w2e.innerWidth-10;//getBoundingClientRect();
  // // ww = ww?.width - 10;
  // ww = Math.floor((ww -20)/ 8);
  // ww = ww.toString + 'px';

  w2e.innerHTML = '';

  function addArea(ar) {
    if (!ar) return;
    if (ar == sys) return;
    let but = document.createElement('Button');
    but.onclick = function () {
      clickAREAS(ar._idString);
    };

    but.style['min-width'] = '18%';
    but.id = 'view' + ar._idString;
    but.style['text-align'] = 'center';
    let tex = '_';
    if (ar.config?.name) tex = ar.config.name;
    but.___maintext = getUnitName(ar, 1) + '<br/>' + tex + '<br/>';
    but.innerHTML = but.___maintext + '_';
    setAreasButton(but, ar); // set bg color
    w2e.appendChild(but);
  }
  if (curAreas.length) {
    let but = document.createElement('Button');
    but.onclick = function () {
      if (curAreas.length) {
        curAreas.pop();
        displayAREAS();
      }
    };
    but.textContent = '<-';
    w2e.appendChild(but);
    const ar0 = findUnitFromStringID(curAreas[curAreas.length - 1]);
    if (ar0?._sut == 'Area')
      for (let sutt in ar0._subUnits) {
        let sa = ar0._subUnits[sutt];
        for (let sunn in sa) addArea(sa[sunn]);
      }
  } else if (topAreas.length) for (const id of topAreas) addArea(findUnitFromStringID(id));
}

function chckUnit(ids) {
  let u = unitIdToUnit2(ids);
  const chk = document.getElementById(ids + '_check');
  u._selected = chk?.checked;
}

function viewSelectorsCopy() {
  preset = { __type: w1aw.curUnit._typeName };
  let u = w1aw.curUnit;
  const cd = schema.unitTypes[u._typeName]?.config;
  for (let pn in cd) {
    let chk = document.getElementById(pn + '_config_check');
    if (chk) chk.hidden = false;
  }
}

function addToPreset(pn) {
  let chk = document.getElementById(pn + '_config_check');
  if (chk?.checked) preset[pn] = w1aw.curUnit.config[pn];
  else delete preset[pn];
}
