/*
 * elsewhere must be defined
 *      processMessage(msg)
 * will be called with message like {"from":{"box":255,"mod":255:"unit":0},"to":{"box":255,"mod":255:"unit":6}},"id":[array of integers], "unitType":typename, "cmd":{"boot":{"status":{"version":127}}}}
 * !!! note !! message already decoded through schema
 *
 * to send smth call  function fillRequest(request)
 * request =
 * {
 *      id = [integers to put as path],           =   [ type 0 subid subid ... subid subid mod box ]
 *  unitType = name of unit type, e.g. "AxDPI"
 *      cmd = e.g. {"config":{"threshould":14}}
 *  from / to ignored even if present
 * }
 *
 */

/*
 * global variables
 */

var printMessages = false;
var emulationAnswerOn = false;

var lastGotReply; // absolute time of last packet, millisec, to check for offline

var nextFirmware = 0;
var firmwareTimeout = 30;
var doFw2update;
var onlineHTTP; // if sent request successfully (200)
var errorsHTTP = 0; // incremented on every failed sent
var postRequests = []; // queue to send

/*
 * internal vars
 */

var curRequest; // got from queue if any
var countRetrySend; // 3 retries, else drop request and mark OFFLINE

var pollTimeOutId; // periodic "long GET" request
var postTimeOutId; // send if present something in the queue
var xh; // for post
var xph; // for poll (GET)



const ConnectionType = Object.freeze({
  NONE:         Symbol("none"),
  HTTP_POLLING: Symbol("http_polling"),
  WEBSOCKET:    Symbol("websocket")
});

let connection_type = ConnectionType.NONE;

/**
* Provides WebSocket connection to the server, as well as sending and receiving data over the connection
*/
class WebSocketConnection {
  /**
   * Constructor automatically determines the WebSocket URL based on the current page's URL.
   */
  constructor() {
	const currentUrl = new URL(window.location.href);
    const ipAddress = currentUrl.hostname;
    const port = currentUrl.port;

    this.ws_connection = undefined;
    this.online = false;
    this.errors = 0;
    this.url = `ws://${ipAddress}:${port}/api/websocket`;
  }

  #initWebSocket() {
    console.log("Attempting to connect to a WebSocket");
    console.log("Path: " + this.url);

    this.ws_connection = new WebSocket(this.url);
    this.ws_connection.binaryType = "arraybuffer";

    this.ws_connection.onopen = () => {    
      console.log("WebSocket connection established");
      this.online = true;
    };

    this.ws_connection.onmessage = (event) => {
      try {
        let fullAnswer = new Uint8Array(event.data);
        while (fullAnswer.length) {
          let l = fullAnswer[0] + 9;
          if (l > fullAnswer.length) l = fullAnswer.length;
          let curAnswer = fullAnswer.slice(1, l);
          fullAnswer = fullAnswer.slice(l);
          parseAnswer(curAnswer);
        } 
      } catch {
        console.error("onmessage error");
      }
    };

    this.ws_connection.onerror = (error) => {
      console.error("WebSocket error:", error);
      this.online = false;
      this.errors++;
    };

    this.ws_connection.onclose = () => {
      console.log("WebSocket connection closed");
      this.online = false;
      console.log("Reconnect WebSocket..");
      setTimeout(() => this.#initWebSocket(), 5000);
    };
  }

  /**
   * Send data via WebSocket
   * @param {ArrayBuffer} data array buffer to send
   */
  send(data) {
    if (this.ws_connection !== undefined && this.ws_connection.readyState === WebSocket.OPEN) {
      this.ws_connection.send(data);
    } else {
      console.warn("sendMessage: WebSocket isn't open!");
    }
  }

  /**
   * Initialize WebSocket and connect 
   */
  init() {
    if (this.ws_connection === undefined) {
      this.#initWebSocket();
    } else if (this.ws_connection.readyState === WebSocket.OPEN) {
      console.log("WebSocket is already open.");
    }
  }
}


async function httpGet(url) {
  try {
      const response = await fetch(url);
      if (response.ok) {
          return true;
      } else {
          console.error('Response error:', response.statusText);
          return false;
      }
  } catch (error) {
      console.error('An error occurred while executing the request:', error);
      return false;
  }
}

const websocket = new WebSocketConnection();

/*
 * initialize at least once
 */

async function startHardwarePolling() {
  // const storageAppKey = 'WebConfiguratorIsOpen';
  // const pageAlreadyOpen = localStorage.getItem(storageAppKey);
  // if(pageAlreadyOpen) {
  //   alert('Конфигуратор уже запущен! Закройте эту страницу и вернитесь к ранее отрытому экземпляру');
  //   connection_type = ConnectionType.NONE; 
  //   return;
  // } else {
  //   localStorage.setItem(storageAppKey, 'true');
  //   window.addEventListener('beforeunload', (event) => {
  //     localStorage.removeItem(storageAppKey);
  //   });
  // }

  console.log('Check if server supports WebSockets..')
  let getPath = '/api/server-info';
  if (window.getPathApi) getPath = window.getPathApi + getPath;
  if(await httpGet(getPath)) {
    console.log('init websocket');
    websocket.init();
    connection_type = ConnectionType.WEBSOCKET;
    return;
  }

  connection_type = ConnectionType.HTTP_POLLING;
  if (postTimeOutId) window.clearTimeout(postTimeOutId); // clear if already set
  if (pollTimeOutId) window.clearTimeout(pollTimeOutId);
  if (xh) xh.abort();
  if (xph) xph.abort();
  postTimeOutId = pollTimeOutId = 0;
  // init XHR variables
  xh = new XMLHttpRequest();
  xh.timeout = 2000; // POST should be fast normally
  xh.onreadystatechange = function () {
    if (postTimeOutId) window.clearTimeout(postTimeOutId);
    postTimeOutId = 0;
    if (this.readyState == 4) {
      if (this.status == 200) {
        curRequest = null;
        onlineHTTP = true;
        if (fw2update) {
          setPostHardware(200);
          // postTimeOutId = window.setTimeout(postHardware, 200); // sent ok - fast - 50 ms - retry to send next
          return;
        }
        setPostHardware(postRequests.length > 1 ? 47 : 111);
        // postTimeOutId = window.setTimeout(postHardware, postRequests.length>1 ? 47 : 111); // sent ok - fast - 50 ms - retry to send next
        //                if(postRequests.length) postHardware();
        //              else postTimeOutId = window.setTimeout(postHardware, 15); // sent ok - fast - 50 ms - retry to send next
      } else {
        errorsHTTP++;
        // postTimeOutId = window.setTimeout(postHardware, 300); // errors - 3*300 msec delay
        console.log('POST error');
        if (5 < ++countRetrySend) {
          onlineHTTP = false;
          curRequest = null;
        }
        setPostHardware(postRequests.length > 1 ? 47 : 111);
        // postTimeOutId = window.setTimeout(postHardware, postRequests.length>1 ? 47 : 111); // sent ok - fast - 50 ms - retry to send next
      }
    }
    setInterval(postHardware, 1000);
  };

  xph = new XMLHttpRequest();
  xph.timeout = 2000; // 10 sec max for "long GET poll"
  /*    xph.ontimeout = function(){
            errorsHTTP++;
            onlineHTTP=false;
            pollTimeOutId = window.setTimeout(pollHardware, 1000); // wait 1 more sec before retry poll
        }; */
  xph.onreadystatechange = function () {
    if (this.readyState == 4) {
      //let e = new Date();
      // console.log("poll-got: "+e.getTime());
      //          let to=1000; // if error - slow poll - 1 sec
      if (this.status == 502) {
        // BAD GATEWAY = simply no new packets to GET
        //            to=100;
      }
      if (this.status == 200)
        try {
          // got something - fast retry to get more
          let fullAnswer = new Uint8Array(this.response);
          while (fullAnswer.length) {
            let l = fullAnswer[0] + 9;
            if (l > fullAnswer.length) l = fullAnswer.length;
            let curAnswer = fullAnswer.slice(1, l);
            fullAnswer = fullAnswer.slice(l);
            parseAnswer(curAnswer); // below in this file
          }
          buildPollingRequest(); // moved here from processMessage to avoid multiple send on single get
          //         let f = new Date();
          //       console.log("parse: "+(f.getTime()-e.getTime()));
        } catch {}
      pollHardware(); //to=2; // ok - fast - 10 msec
      //else            pollTimeOutId = window.setTimeout(pollHardware, to);
    }
  };
  xph.responseType = 'arraybuffer';

  setPostHardware(postRequests.length > 1 ? 66 : 200);
  // postTimeOutId = window.setTimeout(postHardware, postRequests.length>1 ? 66 : 200); // set first timeouts to start XHR   was 4:100
  pollTimeOutId = window.setTimeout(pollHardware, 40);
}

/*
 * low level poll/post routines
 * */

function pollHardware() {
  if(connection_type == ConnectionType.HTTP_POLLING) {
    // usually 10 millisec after previous finish
    //  let e = new Date();
    //  console.log("poll: "+e.getTime());
    // done-s replace "/s" to "/api/s" for dev and back for prod ============ if necessary, set global variable Window.getPathApi to "/api"
    let getPath = '/s';
    if (window.getPathApi) getPath = window.getPathApi + getPath;
    xph.open('GET', getPath, true);
    xph.timeout = 10000; // POST should be fast normally
    xph.send(null); // just poll by GET
  }
}

function setPostHardware(ddt) {
  // if (postTimeOutId) return;
  if (ddt < 50) {
    ddt = 50;
  }
  if (postTimeOutId) window.clearTimeout(postTimeOutId);
  postTimeOutId = 0;
  postTimeOutId = window.setTimeout(postHardware, ddt);
}

function postHardware() {
  //  e = new Date();
  //  console.log("post attempt ="+postRequests.length+"="+e.getTime());

  if ( ((connection_type == ConnectionType.HTTP_POLLING) && (!xh)) || ((connection_type == ConnectionType.WEBSOCKET) && (websocket.online == false)) ) {
    setPostHardware(500);
    return;
  }
  if (fw2update || multipleFirmwares) {
    if (fwUpdateTimeout + 60000 > Date.now()) {
      let dn = Date.now();
      if (dn > nextFirmware) nextFirmware = firmwareTimeout + dn;
      else return;
      if (!curRequest) curRequest = buildUpdate(); // if updating - only updating
      // if (curRequest) {
      //   if (dn+10 < nextFirmware) {
      //     // wait
      //     setPostHardware(nextFirmware - dn);
      //     return;
      //   }
      // }
      setPostHardware(nextFirmware - dn);
    } else {
      // timeout
      let infoString = 'reflashing ' + getUnitName(fw2updateUnit) + ' aborted: timeout';
      systemLog(false, infoString);
      multiUpdateErrors += infoString + '\r\n';
      fw2updateUnit = fw2update = doFw2update = null;
      stepMultipleFirmwares(infoString);
      //cmd={"reBoot":{}}; // done update
      modelChanged('boot');
    }
  }

  checkPeriodicRequest();

  if (!curRequest) countRetrySend = 0;
  if (!curRequest) curRequest = postRequests.shift();

  if (emulationAnswerOn) {
    curRequest = null;
  }

  if (!curRequest) {
    setPostHardware(200);
    return;
  } // was 200
  else if (((connection_type == ConnectionType.HTTP_POLLING) && ( xh.readyState == 4 || xh.readyState == 0)) || websocket.online == true) {
    //   let q=postRequests.length+1;
    let len = curRequest.byteLength + 1;
    for (let i = postRequests.length; i--; ) len += 1 + postRequests[i].byteLength;
    //     console.log("post "+q+"/"+len);
    let ab = new ArrayBuffer(len);
    let sn = new Uint8Array(ab);
    let n = 0;
    while (curRequest && len) {
      let len1 = curRequest.byteLength + 1;
      sn[n++] = len1 - 1;
      let uu = new Uint8Array(curRequest);
      for (let i = 0; i < curRequest.byteLength; i++) sn[n++] = uu[i];
      if (len1 > len) len = 0;
      else len -= len1;
      if (postRequests.length && len && postRequests[0].byteLength <= len) curRequest = postRequests.shift();
      else break;
    }
    ab = ab.slice(0, ab.byteLength - len);
    // done-s replace "/s" to "/api/s" for dev and back for prod ============ if necessary, set global variable Window.getPathApi to "/api"
    let getPath = '/s';
    if (window.getPathApi) getPath = window.getPathApi + getPath;

    if(connection_type == ConnectionType.HTTP_POLLING) {
      xh.open('POST', getPath, true);
      xh.timeout = 2000; // POST should be fast normally
      xh.send(ab);
    } else if (connection_type == ConnectionType.WEBSOCKET){
      websocket.send(ab);
    }

  } else {
    setPostHardware(500);
    return;
  }
}

/*
 *
 * routines to convert to/from binary message
 *
 */

/*
 * converts binary to {id+cmd}
 *
 * answer =
 * {
 *      id = [integers to put as path],           =   [ type 0 subid subid ... subid subid mod box ]
 *      cmd = [{format:0 value:17 ... etc}{}{}{}]
 * }
 *
 * bin = Uint8Array starting from "from" (box mod idlsb idmsb)
 */

function decodeBinAnswer(bin) {
  // {id:arrayofintegers,cmd:{Command:{}}}
  let id = [];
  let cmd = [];
  let len = bin.length;
  let pos = 8; // start of subid or message

  if (len < 10) return null;

  id.push(bin[6]); // box from
  id.push(bin[7]); // mod
  if (bin[5] == 1) {
    // normal unit
    while (pos < len) {
      function getVarInt() {
        let v = 0;
        let shift = 0;
        while (pos < len) {
          let t = bin[pos++];
          v += (t & 127) << shift;
          if (!(t & 128)) break;
          shift += 7;
        }
        return v;
      }
      let suid = getVarInt();
      id.push(suid);
      if (suid) continue;
      id.push(getVarInt()); // type
      break;
    }
  }
  id.reverse();

  ids = '';
  let p = 6;
  if (id.length > 2) {
    do ids += byte2string(bin[p]);
    while (bin[p++]);
    do ids += byte2string(bin[p]);
    while (bin[p++] & 0x80);
  } else for (; p < 8; p++) ids += byte2string(bin[p]);

  if (pos < len) cmd = protobuf2JSON(bin.subarray(pos));

  return {
    id: id,
    cmd: cmd,
    idString: ids,
  };
}

/*
 * converts {id+cmd} to binary
 *
 * request =
 * {
 *      id = [integers to put as path],           =   [ type 0 subid subid ... subid subid mod box ]
 *      cmd = [{format:0 value:17 ... etc}{}{}{}]
 * }
 */

function makeBinRequest(request, category, tag) {
  // {id:uint8binary,cmd:{Command:{}}}
  if (!request.id) return; // cmd may be empty
  let ab = new ArrayBuffer(300);
  let data = new Uint8Array(ab);
  let d0 = makeBinAdr(request.id);
  if (d0.length < 4) return;
  try {
    let pos = 0;
    let len = d0.length;
    for (let i = 0; i < len; i++) data[pos++] = d0[i];

    let sm = JSON2protobuf(request.cmd);
    len = sm.length;
    for (let i = 0; i < len; i++) data[pos++] = sm[i];

    if (sm.length)
      // save command to tag - really used only for subscribe to detect asynch messages
      data[0] = sm[0];

    if (typeof category == 'number') data[1] = category;
    if (typeof tag == 'number') data[0] = tag;
    return data.buffer.slice(0, pos);
  } catch {
    return null;
  }
}

/*
 * request =
 * {
 *      id = [integers to put as path],           =   [ type 0 subid subid ... subid subid mod box ] == only mod/box for Boot
 *      unitType = AxDPI or smth
 *      cmd = {"config":{"threshould":14}}   or smth similar
 * }
 * justReturn = true => do not push
 * returns request built
 */
function fillRequest(request, category, tag, justReturn) {
  if (!online && request.unitType != 'Boot') return;
  if (!doFw2update) {
    let curRequest;
    try {
      if (typeof tag == 'number') request.tag = tag;
      if (typeof category == 'number') request.cat = category;
      if (printMessages) systemLog(0, '<' + JSON.stringify(request), 'yellow');

      let r = {};
      r.id = request.id;
      // if(request.cmd.records)
      //    console.log(33);
      r.cmd = encodeCommandBySchemaForProtobuf(request.unitType, request.cmd); ////      cmd = [{format:0 value:17 ... etc}{}{}{}]   ===  to put as protobuf

      curRequest = makeBinRequest(r, category, tag);
    } catch {}
    if (curRequest) {
      // check not to put undefined - was once found while deleting multiple TSinputs in grey cfg with Area of this TSinputs unfolded
      if (!justReturn) postRequests.push(curRequest);
    }
    if (!justReturn) postHardware();
    else return curRequest;
  }
  // if(postTimeOutId) window.clearTimeout(postTimeOutId);
  // postTimeOutId = window.setTimeout(postHardware, 3); // if 3 sec failed - wait 3 sec more
}

/*
 *
 * routines to update firmware
 *
 *
 */

var fw2updateUnit;
var bootUpdateId; //
var fw2update;
var adr2update;
var lastAdr2update;
var fwPercent = 0;
var repeat200 = 0; // try send from 200 after 0 - old BOOT writes at 0 very very long time, may be no direct answer
var fwUpdateTimeout; // time to stop if no progress (2 minutes after next address change)

/*
 * start / restart
 */

function updateFirmware(bootID, fw) {
  // ID as usual (array of int), fw = unit8array <new firmware>
  if (!fw) {
    fw2updateUnit = bootUpdateId = fw2update = undefined;
    fwPercent = 0;
    specialTagWhileFwOrCfg = false;
    return;
  }

  lastPollAll = 0;

  fwPercent = 0;
  repeat200 = 0;
  if (fw && bootID) {
    fw2update = undefined;
    let l = bootID.length;
    let box = bootID[--l];
    let mod = bootID[--l];
    if (box != thisBox && box != 255) firmwareTimeout = 500;
    else firmwareTimeout = 150;
  }
  if (!fw) fw2updateUnit = undefined;
  else {
    let lenPos = 0x34; // bis
    if (bootID.length <= 2) lenPos = 0x28; // ppkr
    let ba = new Uint8Array(fw);
    let len = 0;
    for (let i = 3; i--; ) len = len * 256 + ba[lenPos + i];
    if (len < fw.byteLength) fw = fw.slice(0, len);
  }
  bootUpdateId = bootID;
  adr2update = 0;
  fwUpdateTimeout = Date.now();
  doFw2update = false;
  fw2update = fw;
  specialTagWhileFwOrCfg = true;
  postHardware();
}

/*
 *  prepare next write
 */


function buildUpdate() {
  let cr = buildMultiUpdate();
  if (cr) return cr;
  if (!fw2update) return null;
  doFw2update = true; // only when successfully started fw upload
  const ab = new ArrayBuffer(300);
  var data = new Uint8Array(ab);
  try {
    let ml = 128;
    let ad2write = adr2update;
    if (fw2updateUnit._sut == 'Boot') {
      if (!ad2write) {
        if (!repeat200++) {
          ml = 200;
        } else {
          ad2write = 200;
          ml = 56;
          if (repeat200 > 3) repeat200 = 0;
        }
      } else if (ad2write === 200) ml = 56; // first 256 need to be split into 200 and 56, then use 128-byte blocks
    } else {
      if (!adr2update) ml = 192;
      // 2 times 00 , then 2 times c0
      else if (ad2write === 192) ml = 64; // first 256 need to be split into 200 and 56, then use 128-byte blocks
    }
    let cmd;
    let u8v = new Uint8Array(fw2update.slice(ad2write, ad2write + ml));

    fwPercent = Math.round((adr2update * 100) / fw2update.byteLength);

    if (emulationAnswerOn) {
      adr2update += ml;
    }

    modelChanged('boot');
    let r = {};
    r.id = bootUpdateId;
    if (fw2updateUnit._sut == 'Boot') {
      if (adr2update >= fw2update.byteLength) {
        fw2updateUnit = fw2update = doFw2update = null;
        specialTagWhileFwOrCfg = false;
        stepMultipleFirmwares('done OK');
        cmd = {
          reBoot: {},
        }; // done update
        modelChanged('boot');
      } else
        cmd = {
          updateFirmware: {
            address: ad2write,
            data: u8v,
          },
        };
      r.cmd = encodeCommandBySchemaForProtobuf('Boot', cmd); ////      cmd = [{format:0 value:17 ... etc}{}{}{}]   ===  to put as protobuf
    } else {
      // bism
      const ab2 = new ArrayBuffer(300);
      var data2 = new Uint8Array(ab2);
      // file_write_request_t, then data
      let pp = 0;
      data2[pp++] = 0; // ID
      data2[pp++] = 0; // ID
      let eof;
      if (ad2write + ml >= fw2update.byteLength) {
        eof = 1;
        if (fw2update.byteLength > ad2write) ml = fw2update.byteLength - ad2write;
        else ml = 0;
      } else eof = 0;
      data2[pp++] = eof;
      data2[pp++] = ml;
      let ad = ad2write;
      for (let i = 4; i--; ) {
        data2[pp++] = ad & 0xff;
        ad = ad >> 8;
      }
      for (let i = 0; i < ml; i++) data2[pp++] = u8v[i];

      r.cmd = [
        {
          format: 2,
          number: 10,
          value: data2.slice(0, pp),
        },
      ];
    }
    return makeBinRequest(r, undefined, 0xff);
  } catch {}
  setPostHardware(200);
  // if(postTimeOutId) window.clearTimeout(postTimeOutId); // clear if already set
  // postTimeOutId = window.setTimeout(postHardware, 200); // sent ok - fast - 50 ms - retry to send next
  return null;
}

/*
 * process GET - may be answer on update command
 *
 * true if not processed as update
 */

function checkUpdate(a) {
  if (checkMultiUpdate(a)) return false; // this is an answer for multiboot request
  if (!fw2update) return true;
  //if(a.from.unit!=0) return true;
  //if(a.unitType!=="Boot") return true;
  if (!compareID(a.id, bootUpdateId)) return true;
  for (let u in a.cmd) {
    if (u !== 'updateFirmware') continue;
    let c = a.cmd[u];
    if (a.unitType == 'Boot') {
      if (a.from.unit != 0) return false;
      if ((adr2update || c.address <= 256) && (c.address > adr2update || !c.address)) {
        // required 0 due to reboot ??? may be still answer from previous command
        adr2update = c.address;
        if (c.address) fwUpdateTimeout = Date.now();
        //if(postTimeOutId) {window.clearTimeout(postTimeOutId); postTimeOutId=0;}
        //            postStep=0; // will send fw ?????????
        if (adr2update) nextFirmware = 0; // no delay before next portion
        // curRequest = 0;
        postHardware();
      }
    } else {
      // BIS
      if (a.from.unit != 1) return false;
      if (fw2updateUnit._sut == 'SK') {
        if (c[8] != fw2updateUnit._sun || c[9] != 0) return false; // ignore
      } else {
        if (c[9] != fw2updateUnit._sun || c[8] != fw2updateUnit._up._sun) return false; // ignore
      }
      fwUpdateTimeout = Date.now();
      adr2update = c[4] + c[5] * 256 + c[6] * 256 * 256 + c[7] * 256 * 256 * 256;
      if (c[2]) {
        systemLog(false, 'reflashing ' + getUnitName(fw2updateUnit) + ' done: OK');
        fw2updateUnit = fw2update = doFw2update = null;
        specialTagWhileFwOrCfg = false;
      } else postHardware();
    }
    modelChanged('boot');
    return false;
  }
  return true;
}

/*
 * procedures to convert unit id as array to/from (uint8) as in PPK packets;
 * arr = [mod, box] if to boot
 * arr = [type...SuIDpath...,mod,box]  if to oter units
 */

function makeBinAdr(arr) {
  // gets array returns Uint8array
  let pp = arr.length;
  const ab = new ArrayBuffer(300);
  var data = new Uint8Array(ab);
  let pos = 4;
  if (pp) data[2] = arr[--pp]; // box (only one value in array)
  if (pp) data[3] = arr[--pp]; // mod if not _Box
  if (pp) data[1] = 1;
  // units = always even sys
  else data[1] = 0; // boot = always even sys (empty array)
  data[0] = 0;

  while (pp) pos = putVarIntBuf(arr[--pp], data, pos);

  return data.slice(0, pos);
}

/*
 * procedures to convert unit id as array to/from (uint8) as in link IDs;
 */
function makeShorterAdr(arr) {
  // gets array returns Uint8array
  let pp = arr.length;
  const ab = new ArrayBuffer(300);
  var data = new Uint8Array(ab);
  let pos = 0;
  if (pp) data[pos++] = arr[--pp]; // box (only one value in array)
  if (pp) data[pos++] = arr[--pp]; // mod if not _Box
  while (pp--) pos = putVarIntBuf(arr[pp], data, pos);
  return data.slice(0, pos);
}

// function makeAdrFromBin(data) {
//   // gets array returns Uint8array
//   let arr = [];
//   let pp = data.length;
//   if (pp <= 2) return arr; // sys
//   arr.push(data[2]); // box
//   if (pp === 3) return arr; // _Box
//   arr.push(data[3]); // mod
//   if (data[1] === 0) return arr; // boot
//   let pos = 4; // now listof varInt
//   while (pos < pp) {
//     let ans = getVarIntBuf(data, pos);
//     pos = ans.p;
//     arr.push(ans.v);
//   }
//   arr.reverse();
//   return arr;
// }
// done-ao -- bug was if unit == box or boot
function makeAdrFromBin(data) {
  // gets array returns Uint8array
  let arr = [];
  let pp = data.length;
  if (pp > 2) {
    // else sys
    arr.push(data[2]); //  box
    if (pp != 3 && data[3]) {
      // else _Box
      arr.push(data[3]); // mod
      if (data[1] != 0) {
        // else boot
        let pos = 4; // now listof varInt
        while (pos < pp) {
          let ans = getVarIntBuf(data, pos);
          pos = ans.p;
          arr.push(ans.v);
        }
      }
    }
  }
  arr.reverse();
  return arr;
}

// data = reverse array [ type 0 suid...suid mod box ]
function makeAdrFromShorterBin(data) {
  // gets array returns Uint8array
  let arr = [];
  let pp = data.length;
  let pos = 0;
  while (pos < pp) {
    let ans;
    if (pos < 2) arr.push(data[pos++]);
    else {
      let ans = getVarIntBuf(data, pos);
      arr.push(ans.v);
      pos = ans.p;
    }
  }
  arr.reverse();
  return arr;
}

function compareID(id1, id2) {
  if (id1.length !== id2.length) return false;
  if (id1[id1.length - 1] == 255) id1[id1.length - 1] = thisBox;
  if (id2[id2.length - 1] == 255) id2[id2.length - 1] = thisBox;
  if (id1[id1.length - 2] == 255) id1[id1.length - 2] = thisModule;
  if (id2[id2.length - 2] == 255) id2[id2.length - 2] = thisModule;

  for (let n = id1.length; n--; ) {
    if (id1[n] != id2[n]) return false;
  }
  return true;
}

/*
 * main routine to process got by GET packet
 */

var currentCommand; // used by model.js to confirm
function parseAnswer(answer) {
  lastGotReply = Date.now();

  if(answer[5]==0x26) { // binary type answer - from.category
    if (answer.length < 8) return;
    let box = answer[6];
    let mod = answer[7];
    let pack = answer.slice(8,answer.length)
    processBinaryAnswer (box, mod, pack);
  }

  currentCommand = answer;

  function adr(u8ar) {
    // uint8array
    var ad = {};
    ad.box = u8ar[2];
    ad.mod = u8ar[3];
    ad.unit = u8ar[1];
    ad.mark = u8ar[0];
    return ad;
  }
  if (answer.length < 8) return;

  var a = {};
  a.to = adr(answer.subarray(0, 4));
  a.from = adr(answer.subarray(4, 8));
  let idcmd = decodeBinAnswer(answer);
  if (idcmd) {
    a.id = idcmd.id;
    a.idString = idcmd.idString;
    let q = idcmd.id[0]; // [0] must be unit type if not boot
    if (idcmd.id.length == 2) {
      q = -1; // boot
      if (idcmd.id[1] == 255 && thisBox) idcmd.id[1] = thisBox; // !!!!!!!!!!!! to compare
      if (idcmd.id[0] == 255 && thisModule) idcmd.id[0] = thisModule;
    }

    a.unitType = schema.unitTypesNumbers[q];

    a.cmd = decodeAnswerBySchemaFromProtobuf(q, idcmd.cmd);

    if (checkUpdate(a)) {
      if (printMessages) systemLog(0, '>' + JSON.stringify(a), 'yellow');
      processMessage(a);
    }
  }
}
