/*
 * reads schema and builds permanent schema model.
 * 
 * A.Omelianchuk, 01-dec-2018
 *
 * 
 * 
 * 
 * global vars:
 * 
 *  var lang="EN"; // used now
 * 
 *  var schema; // read from schema.json and expanded for easy processing
 * 
 * 
 * 
 * 
  schema.json  has:
 
 
 version, boards 
    - for future use
    
    
 languages
    array of EN RU IT ES etc.... available in this file - for language selector. New languages may be added on request
    
    
    
dictionary
    necessary phrases -translated into different languages.
    may be for specific user interfaces it would be better to keep dictionary in separate file.
    may be I will also separate this section from schema.json
 
 
    
 unitTypes    ==========  main section
    all types of units
 
 each unit has:
    "__" - information
    other subelements are names of possible messages
 
  in "__":
    "inherits": name of type whose data are inherited as though listed here
                == note !    when schema is read into browser, all inherited messages/parameters are copied (duplicated), so no need to go up the inheritance tree
    "number": = integer = for protobuf encoding = present and nonzero only if unit type is not abstract, may be in reality
    "titleEN": = short name = may be for each language. EN is default. if no Title - unit/messageparameter name is used as TitleEN
    "descriptionEN": = long description  may be absent
            note!   === after loaded, all language-specific attributes are filled with default if not set explicitely (Description will be set from Title, all languages from default = EN)
    
    "subUnits" = list of descriptors of subunits present in each unit of this type 
    
    in "subUnits":
        <typeName of subunit> :  {}  == may be abstract name, in reality may be any compatible (inherited) type used
        if abstract - sure one can define manually while building configuration
            abstract are marked with
                        "hardware":true,        =     may be can be "found" by some means, can be "offline", usually has serialNumber for identification
                    or
                        "software":true,        =       may be freely created by operator with any possible subnumber
            
        also inside every subunit descriptor:   
            "qty": quantity of subunits of this type
            TitleEN - specific for this subUnit == if omitted, type name used
            DescriptionEN  == and other languages possible. if omitted, type Description used
            

    in message descriptors there are inside:

    "__" with
        
        "number" - used for protobuf encoding
        "rw" - if may be used for commands
        "rwAll":true,  -  if all parameters sure can be used in command
               "TitleRU":"xxxxxxx"  - and Description and other languages

        
    others subelements are parameters of message
    in each parameter descriptor there are:
    
        "number": - for protobuf encoding
        "enumEN": - and other languages - (optional) - if values are to be selected from list.
                    also to be used for display if from list. combined with defined in parent
                    e.g.: 
                    {
                        "0": "off",
                        "1": "warning",
                        "2": "on",
                        "3": "double"
                    },
                    
                    NOTE! == after loaded all "enum" for all languages are present (defaulted to EN if not set explicitely)
                    
        "repeated": true/false =  protobuf arrays - currently "repeated" used only in status.faults
        "rw" - may be used in command (some messages are both commands and asynch messages - e.g. main "status" message)
        "ro" - may NOT be used in command (some messages are both commands and asynch messages - e.g. main "status" message)
 
        "maxValue": 
        "minValue":   =   limits when setting in command
        
        "format": -- for protobuf encoding. NOW all parameters are UNSIGNED ! 
            "fixed32" 
            "varInt" (default)
            "fixed64" (in javascript will be arrayBuffer 8 bytes)
            also may by any name of message as submessage (only messages for this unit type and parents)
        "format": "bytes", with "length": 128, == now only in firmware update command
 
 after loaded added:
 
 schema.unitTypesNumbers =   list of unitTypes (names) keyed by unit's number.
  
 in every unit added quasyarray of names 
    __.msgNumbers with message names indexed by their numbers
    __.heirs typename js array of all children and grandchildren - so when You need type conpatible with this - it is sure in "heirs"
    
 in every message adds array of parameter names __.parametersNumbers  indexed by appropriate numbers - to decode protobuf into JSON
 
 * unitTypeNames are usually CapitalizedCamel
 * messageNames and message parameter names are usually lowercaseCamel
 * messages MAY be not message/with/parameters (default), but also simple value parameter == now not used
 * parameters MAY be not simple value (varInt uint32) parameters (default), but also submessage/with/parameters == now only when reading records from Log
 *
 
 
 
 
 helper functions:
 
 
 
 function initializeSchema(callBack)
        to be called for initialize
        it reads schema.json by XHR and then asynchronously processes it and after successful processing calls "callback"
        after done will call "callback" 
        - it is a hint to init evrything in right sequence and avoid long time of nonresponsive screen
        currently in htm there is
            <body onload="initializeView1();"   >
        
        which mainy calls 
            initializeSchema(initializeView2);

        which in turn calls
            initializeModel(initializeView);
            
        so after all will be called
            initializeView
            
   

   
 
function unitTypeCompatible(fact, required)    // * unitTypeName unitTypeNameRequired
  returns true if fact (typename) is a descendent of "required" typename
  so "fact" can be used where "required" is required
  
  
  
  
function getTypeName(type) {  // mainly as stated here plus some safety checks
    return schema.unitTypes[type].__["Title"+lang];

    
    
   
function getParVal(pd,val) {  // mainly as stated here plus some safety checks
    return string, which is number or "Enum" with current "language"
   
    
    
   
function getDict(name){ // mainly as stated here plus some safety checks
    return schema.dictionary[name];

     
   
 functions used by "model.js" :  
            
            

 * input object similar to {"status":{"state":"on"}} may be several with different names
 * unittype = name of type
 * 
 * result smth like
 * [{"number":6,"format":2,"value":[{"number":1,"format":2,"value":[{"number":3,"format":0,"value":1}]}]}]
 * for universal JSON-protobuf convertion that,s array of objects

 function encodeCommandBySchemaForProtobuf(unitType,messages){


 
 
 
 
 * input smth like
 * [{"number":1,"format":2,"value":[{"number":3,"format":0,"value":17}]}] output
 * like {"status":{"snType":6548679,"version":121}}
function decodeAnswerBySchemaFromProtobuf (utn,a) =========  reverse function

 
 
 
 
 
 
 
 
*/

////////22
var lang = 'RU'; // used now
var schema;
var schema2; // todo - remove - only for tech needs
var maxNumberUsed = 0; // for Krutikov - to assign numbers to vitual pure-abstract types
var schLang;

//////////21
function processUnitTitles(un) {
  let ud = schema.unitTypes[un];
  let ud_ = ud.__;
  if (ud_.doneProcessUnitTitles) return;
  ud_.doneProcessUnitTitles = true;
  let prnt = schema.unitTypes[ud_.inherits]; // parent descriptor

  if (ud_.TitleEN == '---' || !ud_.TitleEN) ud_.TitleEN = un;
  if (ud_.DescriptionEN == '---' || !ud_.DescriptionEN) ud_.DescriptionEN = ud_.TitleEN;

  for (let lang of schema.languages) //
    if (lang !== 'EN') {
      if (ud_['Title' + lang] == '---' || !ud_['Title' + lang]) ud_['Title' + lang] = ud_.TitleEN;
      if (ud_['Description' + lang] == '---' || !ud_['Description' + lang])
        ud_['Description' + lang] = ud_.DescriptionEN;
    }

  if (prnt) {
    processUnitTitles(ud_.inherits); // first recurcively process parent to pull from top-level
    for (let atr in prnt.__)
      if (!['heirs', 'name', 'inherits', 'number', 'vNumber'].includes(atr)) {
        // attention! add if more names used ! or use somth like atr[0]=="_"
        if (typeof ud_[atr] == 'undefined') ud_[atr] = prnt.__[atr];
        else {
          if (typeof ud_[atr] === 'object' && typeof prnt.__[atr] === 'object')
            for (let aa in prnt.__[atr])
              if (typeof ud_[atr][aa] == 'undefined') ud_[atr][aa] = prnt.__[atr][aa];
              else if (typeof ud_[atr][aa] === 'object' && typeof prnt.__[atr][aa] === 'object')
                for (let aa2 in prnt.__[atr][aa])
                  if (typeof ud_[atr][aa][aa2] == 'undefined') ud_[atr][aa][aa2] = prnt.__[atr][aa][aa2];
        }
      } // if for
    for (let mn in prnt)
      if (!ud[mn]) {
        ud[mn] = {};
        if (mn !== '__') ud[mn].__ = {};
      }
  } // if prnt

  for (let mn in ud) {
    if (mn !== '__') {
      let prntmd;
      if (prnt) prntmd = prnt[mn];
      let prntmd_;
      if (prntmd) prntmd_ = prntmd.__;

      let md = ud[mn];
      if (!md) md = ud[mn] = {};
      if (!md.__) md.__ = {};
      let md_ = md.__;

      mkTitDesc(md_, prntmd_, mn);

      if (prntmd_) {
        for (let atr in prntmd_) if (!md_[atr]) md_[atr] = prntmd_[atr];
        for (let pn in prntmd_.fieldNumbers) if (!md_.fieldNumbers[pn]) md_.fieldNumbers[pn] = prntmd_.fieldNumbers[pn];
      }

      if (prntmd)
        for (let pn in prntmd) // for parameters present in parent message
          if (pn !== '__') {
            let pd = md[pn];
            if (!pd) pd = md[pn] = {};
            let prpd = prntmd[pn];
            mkTitDesc(pd, prpd, pn);
            pd._level = prpd._level + 1;
            if (prpd.enumEN) {
              //en always present..  copy parent to child if child has no enum for each language but if not EN changed
              if (!pd.enumEN) pd.enumEN = {};
              for (let n in prpd.enumEN) if (!pd.enumEN[n]) pd.enumEN[n] = prpd.enumEN[n];
              for (let lang of schema.languages)
                if (lang != 'EN') {
                  if (!pd['enum' + lang]) pd['enum' + lang] = {};
                  for (let n in prpd['enum' + lang])
                    if (pd.enumEN[n] == prpd.enumEN[n] && !pd['enum' + lang][n])
                      pd['enum' + lang][n] = prpd['enum' + lang][n];
                }
            }
            if (prpd.categories) {
              if (!pd.categories) pd.categories = prpd.categories;
              else for (let n in prpd.categories) if (!pd.categories[n]) pd.categories[n] = prpd.categories[n];
            }
          }

      for (let pn in md)
        if (!prntmd || !prntmd[pn]) {
          let pd = md[pn];
          pd._level = 0;

          if (!pd.enumEN)
            // may be only other lang declared - never should be
            for (let lang of schema.languages)
              if (pd['enum' + lang]) {
                pd.enumEN = pd['enum' + lang];
                break;
              }

          if (pd.enumEN) {
            for (let lang of schema.languages) {
              if (!pd['enum' + lang]) pd['enum' + lang] = {};
              if (lang != 'EN') {
                if (!pd['enum' + lang]) pd['enum' + lang] = {};
                for (ennum in pd.enumEN) ////// set other langs as in EN (and maybe EN as other if missed)
                  if (pd.enumEN[ennum] && !pd['enum' + lang][ennum]) pd['enum' + lang][ennum] = pd.enumEN[ennum];
                for (ennum in pd['enum' + lang]) ////// set maybe EN as other if missed)
                  if (pd['enum' + lang][ennum] && !pd.enumEN[ennum]) pd.enumEN[ennum] = pd['enum' + lang][ennum];
              }
            }
          }
        }

      for (let pn in md) if (!prntmd || !prntmd[pn]) mkTitDesc(md[pn], undefined, pn);

      if (prntmd)
        for (let pn in prntmd)
          if (pn !== '__') {
            if (!md[pn]) md[pn] = prntmd[pn];
            else if (typeof md[pn] == 'object' && typeof prntmd[pn] == 'object')
              for (let atr in prntmd[pn]) if (md[pn][atr] === undefined) md[pn][atr] = prntmd[pn][atr];
          }
    }
  } // for (let mn in ud) {

  //   let md_ = md.__;

  //   //        if (typeof(ud[mn]) == "object" && typeof(prnt[mn]) == "object")
  //   for (let pn in prnt[mn])
  //     if (pn !== "__") {
  //       if (!ud[mn][pn]) ud[mn][pn] = prnt[mn][pn];
  //       else if (typeof(ud[mn][pn]) == "object" && typeof(prnt[mn][pn]) == "object")
  //         for (let atr in prnt[mn][pn])
  //           if (ud[mn][pn][atr] === undefined)
  //             ud[mn][pn][atr] = prnt[mn][pn][atr];
  //     }
  // } //      for (let mn in prnt) {

  if (prnt && prnt.__ && prnt.__.subUnits)
    // for (let mn in prnt)
    //   if (mn !== "__" && !ud[mn]) ud[mn] = prnt[mn];
    for (let sutn in prnt.__.subUnits)
      if (!ud_.subUnits[sutn]) ud_.subUnits[sutn] = prnt.__.subUnits[sutn];
      else {
        for (let atr in prnt.__.subUnits[sutn])
          if (
            (atr != 'qty' && !ud_.subUnits[sutn][atr]) ||
            (atr == 'qty' && typeof ud_.subUnits[sutn][atr] != 'number')
          )
            ud_.subUnits[sutn][atr] = prnt.__.subUnits[sutn][atr];
      }

  // for (let sutn in ud_.subUnits) {
  //   mkTitDesc(ud_.subUnits[sutn], null, sutn);
  //   if (typeof(ud_.subUnits[sutn].qty) != "number")
  //     ud_.subUnits[sutn].qty = 1;
  // }
  for (let sutn in ud_.subUnits) {
    let d = ud_.subUnits[sutn];
    for (let p in d) if (d[p] === '---') delete d[p];
    if (typeof d.qty != 'number') d.qty = 1;
  }

  //                    for (let hn of ud_.heirs)
  //                      processUnitTitles(hn);
} // function processUnitTitles

////////////20
function mkTitDesc(obj, parent, defEN) {
  // parameter descriptor, parent parameter descriptor, parameter name. also same for message.__ ; when called sure parent already  processed from his parent
  if ('---' == obj.TitleEN || !obj.TitleEN) {
    if (parent) obj.TitleEN = parent.TitleEN;
    else obj.TitleEN = defEN;
  }
  if ('---' == obj.DescriptionEN || !obj.DescriptionEN) {
    if (parent) obj.DescriptionEN = parent.DescriptionEN;
    else obj.DescriptionEN = obj.TitleEN;
  }
  for (let lang of schema.languages)
    if (lang !== 'EN') {
      if (parent) {
        if ('---' == obj['Title' + lang] || !obj['Title' + lang]) obj['Title' + lang] = parent['Title' + lang];
        if ('---' == obj['Description' + lang] || !obj['Description' + lang])
          obj['Description' + lang] = parent['Description' + lang];
      } else {
        if ('---' == obj['Title' + lang] || !obj['Title' + lang]) obj['Title' + lang] = obj.TitleEN;
        if ('---' == obj['Description' + lang] || !obj['Description' + lang])
          obj['Description' + lang] = obj.DescriptionEN;
      }
    }
} // function mkTitDesc

////////////////19
function extendSchema() {
  for (let t in schema.unitTypes) {
    let ty = schema.unitTypes[t];
    for (let m in ty)
      if (m[0] != '_') {
        let msg = ty[m];
        for (let p in msg) {
          let pa = msg[p];
          let pc = pa.common;
          if (pc) {
            let cc = schema.common[pc];
            for (let pp in cc)
              if (pa[pp] === '---' || typeof pa[pp] === 'undefined') pa[pp] = cc[pp];
              else if (pp.substring(0, 4) === 'enum' || pp == 'categories')
                // is it correct for categories ?? not tested/ common not tested at all ??
                for (let en in cc[pp])
                  if (pa[pp][en] === '---' || typeof pa[pp][en] === 'undefined') pa[pp][en] = cc[pp][en];
          }
        }
      }
  }

  for (let t in schema.unitTypes) {
    let ty = schema.unitTypes[t];
    for (let m in ty)
      if (m[0] != '_') {
        let msg = ty[m];
        for (let p in msg) {
          let par = msg[p];
          for (let pp in par)
            if (pp != 'enumEN' && pp.substring(0, 4) === 'enum' && par.enumEN) {
              let enEN = par.enumEN;
              let enN = par[pp];
              for (let env in enEN) if (!enN[env]) enN[env] = enEN[env];
            }
        }
      }
  }

  schema.unitTypesNumbers = [];

  for (let un in schema.unitTypes) {
    // unit name
    let ud = schema.unitTypes[un]; // unit descriptor
    if (!ud.__) ud.__ = {};
    let ud_ = ud.__; // "__"
    if (ud_.vNumber && ud_.vNumber > maxNumberUsed) maxNumberUsed = ud_.vNumber;
    if (ud_.number && ud_.number > maxNumberUsed) maxNumberUsed = ud_.number;
    ud_.name = un; // to get name if have pointer on ud

    if (ud_.number) {
      if (schema.unitTypesNumbers[ud_.number]) systemLog(true, 'duplicate unit type number ' + ud_.number);
      schema.unitTypesNumbers[ud_.number] = un;
    }

    if (!ud_.heirs) ud_.heirs = [];
    if (!ud_.subUnits) ud_.subUnits = {};
    if (!ud_.subUnitNumbers) ud_.subUnitNumbers = {};

    // todo may be use refs instead of names in heirs, numbers etc...
    if (ud_.inherits) {
      // only once - no recursion
      if (schema.unitTypes[ud_.inherits]) {
        if (!schema.unitTypes[ud_.inherits].__) schema.unitTypes[ud_.inherits].__ = {};
        if (!schema.unitTypes[ud_.inherits].__.heirs) schema.unitTypes[ud_.inherits].__.heirs = [];
        schema.unitTypes[ud_.inherits].__.heirs.push(un);
      } else alert('not found parent type ' + ud_.inherits + ' in ' + un);
    }

    if (!ud_.msgs) ud_.msgNumbers = {};

    for (let mn in ud) {
      if (mn !== '__') {
        let md = ud[mn];
        if (!md.__) md.__ = {};
        let md_ = md.__;
        md_.name = mn;
        if (md_.number) {
          if (ud_.msgNumbers[md_.number]) alert('duplicate message number ' + md_.number + ' in unit ' + un);
          ud_.msgNumbers[md_.number] = mn;
        }
        if (!md_.fieldNumbers) md_.fieldNumbers = {};
        for (let pn in md)
          if (pn !== '__') {
            let pd = md[pn];
            if (mn === 'config') {
              if (pd.hardwareID) {
                if (!md_.hardwareID) md_.hardwareID = {};
                md_.hardwareID[pn] = true;
              }
            }
            if (pd.number) {
              if (md_.fieldNumbers[pd.number] && md_.fieldNumbers[pd.number] != pn)
                systemLog(true, 'duplicate parameter number ' + pd.number + ' in message ' + mn + ' in unit ' + un);
              md_.fieldNumbers[pd.number] = pn;
            }
          }
      }
    }
  } // for (let un in schema.unitTypes)

  // now again, for all root elements, and pass params and titles from inherited

  // if (! schema.unitTypes[un].__.inherits)
  for (let un in schema.unitTypes) processUnitTitles(un);

  function allHeirs(cun) {
    let cud = schema.unitTypes[cun];
    let cud_ = cud.__;
    if (!cud_.allRealHeirs) cud_.allRealHeirs = []; // allRealHeirs - only not-virtual, to be used as real type for virtual subUnit type (e.g. all real AU for "AU")
    if (!cud_.vNumber) if (!cud_.allRealHeirs.includes(cun)) cud_.allRealHeirs.push(cun);
    for (let hn of cud_.heirs) {
      let hd = schema.unitTypes[hn];
      let hd_ = hd.__;
      allHeirs(hn); // fill heir allRealHeirs
      if (hd_.number)
        if (!cud_.allRealHeirs.includes(hn))
          // real unit
          cud_.allRealHeirs.push(hn);
      for (let ash of hd_.allRealHeirs) // and now add all real heirs of this heir
        if (!cud_.allRealHeirs.includes(ash)) cud_.allRealHeirs.push(ash);
      cud_.allRealHeirs.sort();
    }
  } // function allHeirs

  // if (! schema.unitTypes[un].__.inherits)
  for (let un in schema.unitTypes) allHeirs(un);

  function titlesSubTypes(un) {
    // for upper level types process them recursively
    let ud = schema.unitTypes[un];
    let ud_ = ud.__;
    let ud_s = ud_.subUnits;
    let pn = ud_.inherits; // parent name
    let pd;
    let pd_;
    let pd_s;
    if (pn) pd = schema.unitTypes[pn];
    if (pd) pd_ = pd.__;
    if (pd_) pd_s = pd_.subUnits;
    if (pd_s)
      for (let sutn in pd_s) {
        // subunit-type-name
        if (!ud_s[sutn]) ud_s[sutn] = pd_s[sutn];
        else
          for (let xxx in pd_s[sutn]) {
            if ((xxx != 'qty' && !ud_s[sutn][xxx]) || (xxx == 'qty' && typeof ud_s[sutn][xxx] != 'number'))
              ud_s[sutn][xxx] = pd_s[sutn][xxx];
          }
        let sud = ud_s[sutn];
        let psud = pd_s[sutn];
        let sutnd = schema.unitTypes[sutn];
        let sutnd_;
        if (sutnd) sutnd_ = sutnd.__;
        for (let lang of schema.languages)
          for (let name of ['Title', 'Description'])
            if (sud[name + lang] == '---' || !sud[name + lang]) {
              if (psud) sud[name + lang] = psud[name + lang];
              else sud[name + lang] = sutnd_[name + lang];
            }
      }
    for (let h of ud_.heirs) titlesSubTypes(h);
  }

  for (let un in schema.unitTypes) if (!schema.unitTypes[un].__.inherits) titlesSubTypes(un); // for upper level types process them recursively

  function mkDesc(o) {
    let titleBetter;
    if (o.DescriptionEN == '---' || !o.DescriptionEN) {
      o.DescriptionEN = o.TitleEN;
      titleBetter = true;
    }
    for (let lang of schema.languages)
      if (o['Description' + lang] == '---' || !o['Description' + lang]) {
        if (titleBetter) o['Description' + lang] = o['Title' + lang];
        else o['Description' + lang] = o.DescriptionEN;
      }
  }

  for (let un in schema.unitTypes) {
    let ud = schema.unitTypes[un];
    mkDesc(ud.__);
    for (let msg in ud)
      if (msg[0] !== '_') {
        mkDesc(ud[msg].__);
        for (let par in ud[msg]) {
          if (ud[msg].__.rwAll) ud[msg][par].rw = true;
          mkDesc(ud[msg][par]);
        }
      }
  }

  for (let un in schema.unitTypes) {
    let ud_ = schema.unitTypes[un].__;
    for (let sutn in ud_.subUnits) {
      let sutnum = ud_.subUnits[sutn].number;
      if (!sutnum) sutnum = 0;
      ud_.subUnits[sutn].number = sutnum;
      ud_.subUnitNumbers[sutnum] = sutn;
      if (ud_.subUnits[sutn].aliasNumber) ud_.subUnitNumbers[ud_.subUnits[sutn].aliasNumber] = sutn;
    }
  }

  for (let un in schema.unitTypes) {
    let d = schema.unitTypes[un];
    let d_ = d.__;
    for (let m in d)
      if (m[0] == '_') {
        if (!d[m].RWoperatorLevel) d[m].RWoperatorLevel = 0;
      } else for (let p in d[m]) if (!d[m][p].RWoperatorLevel) d[m][p].RWoperatorLevel = 0;

    d_.hardwareIDs = []; // in every type
    let c = d.config;
    for (let p in c) if (p[0] !== '_') if (c[p].hardwareID) d_.hardwareIDs.push(p);
  }

  let di = schema.dictionary;
  for (let de in di) {
    if (di[de].EN == '---' || !di[de].EN) di[de].EN = de;
    for (let ln of schema.languages) if (ln != 'EN') if (di[de][ln] == '---' || !di[de][ln]) di[de][ln] = di[de].EN;
  }

  for (let un in schema.unitTypes) {
    let d = schema.unitTypes[un];
    let d_ = d.__;
    if (!d_.subUnits || !Object.keys(d_.subUnits).length)
      if (d.status.faultDown) {
        d.status.faultDown = undefined;
        delete d.status.faultDown;
      }
  }

  for (let un in schema.unitTypes) {
    let d = schema.unitTypes[un];
    for (let mn in d)
      if (mn[0] != '_') {
        let md = d[mn];
        for (let pn in md)
          if (pn[0] != '_') {
            let pd = md[pn];
            if (typeof pd.sameAs == 'string') {
              let sas = pd.sameAs.split('.');
              try {
                let sun = sas[0];
                let smn = sas[1];
                let spn = sas[2];
                if (sun == 'this') sun = un;
                if (smn == 'this') smn = mn;
                if (spn == 'this') spn = pn;
                let src = schema.unitTypes[sun][smn][spn];
                for (let pdn in src) if (typeof pd[pdn] == 'undefined') pd[pdn] = src[pdn];
              } catch {}
            }
          }
      }
  }
}

///////////////18
function combine2schemas() {
  if (schLang.common)
    for (let com in schLang.common) {
      if (schema.common[com]) {
        for (let cc in schLang.common[com]) {
          let ccc = schLang.common[com][cc];
          if (typeof ccc == 'string' && ccc !== '---') schema.common[com][cc] = ccc;
        }
      } else schema.common[com] = schLang.common[com];
    }

  for (let la of schLang.languages) if (!schema.languages.includes(la)) schema.languages.push(la);

  for (let word in schLang.dictionary)
    if (!schema.dictionary[word]) schema.dictionary[word] = schLang.dictionary[word];
    else
      for (let la in schLang.dictionary[word])
        if (schLang.dictionary[word][la] != '---') schema.dictionary[word][la] = schLang.dictionary[word][la];

  function combineDefines(s, l) {
    if (l && typeof l == 'object' && s && typeof s == 'object') {
      if (Array.isArray(l)) for (let i = l.length; i--; ) combineDefines(s[i], l[i]);
      else
        for (let p in l) {
          if (p.startsWith('Title') || p.startsWith('Description')) {
            if (l[p] != '---') s[p] = l[p];
          } else if (p.startsWith('enum')) {
            if (!s[p]) s[p] = {};
            for (let n in l[p]) if (s[p][n] !== null && l[p][n] != '---') s[p][n] = l[p][n];
          } else combineDefines(s[p], l[p]);
        }
    }
  } // function combineDefines(s,l) {

  combineDefines(schema, schLang);
} // function combine2schemas(){

var extSchema;

///////////17
function initializeSchema(callBack) {
  var xh = new XMLHttpRequest();
  var langSchema = 0;

  function doFinal() {
    extendSchema();

    for (let t in schema.unitTypes) {
      let ud = schema.unitTypes[t];
      for (let m in ud)
        if (m !== '__') {
          let md = ud[m];
          for (let p in md)
            if (p !== '__') {
              pd = md[p];
              if (!pd.format) pd.format = 'varInt';
              else
                switch (pd.format) {
                  case 'operatorLevels':
                    delete pd.format;
                    for (let lan = schema.languages.length; lan--; ) {
                      let la = schema.languages[lan];
                      let en = {};
                      for (let opl in schema.operatorLevels) {
                        let opld = schema.operatorLevels[opl];
                        let enla = opld[la];
                        if (!enla) enla = opld.EN;
                        en[opld.number] = enla;
                      }
                      pd['enum' + la] = en;
                    }
                    break;
                  case 'languages':
                    delete pd.format;
                    let en = {};
                    for (let lan = schema.languages.length; lan--; ) {
                      let la = schema.languages[lan];
                      en[lan] = la;
                      pd['enum' + la] = en;
                    }
                    break;
                }
            }
        }
      for (let p in ud.__) {
        if (typeof ud.__[p] == 'string' && !ud.__[p].length) {
          delete ud.__[p];
        }
      }
    }

    if (callBack) callBack();
  } //                              function doFinal(){

  xh.onreadystatechange = function () {
    if (this.readyState == 4) {
      if (this.status == 200 || this.status == 0) {
        try {
          if (langSchema) {
            schLang = JSON.parse(this.responseText);
            if (schLang) combine2schemas(schLang);
            if (convertorPresent) extSchema = JSON.stringify(schema);
            doFinal();
          } else {
            langSchema = 1;
            if (this.responseText.length) schema = JSON.parse(this.responseText);
            else schema = JSON.parse(builtInSchema);
            if (convertorPresent) schema2 = JSON.parse(this.responseText);
            // todo - may be, make simpler file structure for deployment ======== done-s path to file for grey configurator must start with '/src/services/
            let getPath = 'services/schema.json'; //#'+Date.now();
            if (window.srcPath) getPath = window.srcPath + getPath;
            xh.open('GET', getPath, true); // done-ao   /src
            xh.send(null);
          }
        } catch {
          if (!langSchema) systemLog(true, 'schema read error');
          else doFinal();
        } // and use what was read
      } else {
        if (!langSchema) systemLog(true, 'schema read error');
        else doFinal();
      }
    }
  };
  // done-s path to file for grey configurator must start with '/src/services/
  let getPath = 'services/schema.json'; //#'+Date.now();
  if (window.srcPath) getPath = window.srcPath + getPath;
  xh.open('GET', getPath, true); // todo - may be not hardcodeit == and simpler file structure for deployment?
  xh.send(null);
}

// unitTypeName obj, where add subobject descriptor
///////////////////////////16
function decodeOneParameterInJSON(unitType, where, statusDesc, p) {
  // where
  // descr
  // data
  for (pn in statusDesc)
    if (statusDesc[pn].type == p.number) {
      if (statusDesc[pn].unitstatus) {
        where[pn] = processUnitstatus(unitType, p.value);
        return where[pn];
      } else {
        where[pn] = p.value;
        return null;
      }
    }
  return null;
}

/*
 * input object similar to {"status":{"state":"on"}} may be several with different names
 * unittype = name of type
 *
 * result smth like
 * [{"number":6,"format":2,"value":[{"number":1,"format":2,"value":[{"number":3,"format":0,"value":1}]}]}]
 * for universal JSON-protobuf convertion that,s array of objects
 */
////////////15
function encodeCommandBySchemaForProtobuf(unitType, messages) {
  let msgs = []; // sure fixed one element "message"
  try {
    // unitname
    let ud = schema.unitTypes[unitType];
    for (let msgn in messages) {
      // may be several - in general

      function string2u8i(s, l) {
        let ab = new ArrayBuffer(100);
        let u8v = new Uint8Array(ab);
        for (let i = 100; i--; ) u8v[i] = 0;

        for (let i = 0; i < s.length; i++) {
          let j = i & 1;
          let b = 0;
          switch (s[i]) {
            case '1':
              b = 1;
              break;
            case '2':
              b = 2;
              break;
            case '3':
              b = 3;
              break;
            case '4':
              b = 4;
              break;
            case '5':
              b = 5;
              break;
            case '6':
              b = 6;
              break;
            case '7':
              b = 7;
              break;
            case '8':
              b = 8;
              break;
            case '9':
              b = 9;
              break;
            case 'a':
            case 'A':
              b = 10;
              break;
            case 'b':
            case 'B':
              b = 11;
              break;
            case 'c':
            case 'C':
              b = 12;
              break;
            case 'd':
            case 'D':
              b = 13;
              break;
            case 'e':
            case 'E':
              b = 14;
              break;
            case 'f':
            case 'F':
              b = 15;
              break;
          }
          if (!j) b *= 16;
          u8v[i >> 1] += b;
        }

        let len = (s.length + 1) >> 1;
        if (l) if (len !== l) len = l;
        return u8v.slice(0, len);
      }

      function encodeParameterBySchemaForProtobuf(outObj, parVal) {
        outObj.value = parVal;
        switch (outObj.format) {
          case undefined:
          case 'varInt':
            outObj.format = 0;
            break;
          case 'fixed32':
            outObj.format = 5;
            break;
          case 'fixed64':
            if (typeof parVal == 'string') {
              outObj.format = 1;
              outObj.value = string2u8i(parVal, 8);
            } else delete outObj.value;
            break;
          case 'bytes':
            {
              if (ArrayBuffer.isView(parVal)) outObj.format = 2;
              else if (typeof parVal == 'string') {
                outObj.value = string2u8i(parVal, 0);
              } else delete outObj.value;
            }
            break;
          case 'string':
            {
              if (typeof parVal == 'string') {
                outObj.format = 2;
                let ab = new ArrayBuffer(parVal.length);
                let u8v = new Uint8Array(ab);

                let a = new Array();
                for (let i = 0; i < parVal.length; i++) {
                  let c = parVal.charCodeAt(i);
                  if (c > 127) {
                    // psewdo utf8
                    if (c >= 0x410 && c <= 0x43f) c = c - 0x410 + 0x90;
                    else if (c >= 0x440 && c <= 0x44f) c = c - 0x440 + 0x80;
                  }
                  u8v[i] = c;
                }
                outObj.value = u8v;
              }
            }
            break;
          default:
            // probably submessage
            {
              let submsg = [];
              outObj.value = submsg;
              encodeMessageBySchemaForProtobuf(submsg, outObj.format, parVal);
              outObj.format = 2;
            }
            break;
        }
      }

      function encodeMessageBySchemaForProtobuf(outArray, messageName, messageObj) {
        let msg = {};
        let md = ud[messageName];
        if (md) {
          let md_ = md.__;
          msg.number = md_.number;
          msg.format = md_.format;
          if (msg.number) {
            // else unknown message for this unitType - ignore this message
            switch (msg.format) {
              case 'varInt':
              case 'string':
              case 'fixed32':
              case 'fixed64':
              case 'bytes':
                // scalar message == todo - array (repeated)
                // messages
                // msg contains format, will put value there
                encodeParameterBySchemaForProtobuf(msg, messageObj);
                break;
              default:
                // submessage
                msg.format = 2;
                msg.value = [];
                for (let pn in messageObj) {
                  let par = {};
                  let pmd = md[pn];
                  if (pmd && !pmd.notForPPK) {
                    if (pmd.number) par.number = pmd.number;
                    if (pmd.format) par.format = pmd.format;
                    encodeParameterBySchemaForProtobuf(par, messageObj[pn]);
                  }
                  if (par.number) msg.value.push(par);
                }
                break;
            }
          }
        }
        if (msg.number) outArray.push(msg);
      }

      encodeMessageBySchemaForProtobuf(msgs, msgn, messages[msgn]);
    } // for command in unit
  } catch {}
  return msgs;
}

/*
 * input smth like
 * [{"number":1,"format":2,"value":[{"number":3,"format":0,"value":17}]}] output
 * like {"status":{"snType":6548679,"version":121}}
 */
///////////14
function decodeAnswerBySchemaFromProtobuf(utn, a) {
  // NUMBER of unit type
  let ans = {};
  try {
    let un = schema.unitTypesNumbers[utn];

    function decodeParameterBySchemaFromProtobuf(format, obj) {
      switch (obj.format) {
        case 0:
          if (format === 'varInt' || format === 'timePPK') return obj.value;
          else return undefined;
        case 5:
          if (format === 'fixed32') return obj.value;
          else return undefined;
        case 2:
          if (format === 'bytes') return obj.bytes;
          else return undefined;
        case 1:
          if (format === 'fixed64') {
            let s = '';
            for (let i = 8; i--; ) {
              let b = obj.bytes[i];
              for (let j = 2; j--; ) {
                let bb = b;
                if (j) bb >>= 4;
                bb &= 0x0f;
                if (bb > 9) c = String.fromCharCode(55 + bb);
                else c = String.fromCharCode(48 + bb);
                s += c;
              }
            }
            return s;
          }
        default:
          return undefined;
      }
    }

    function decodeMessageBySchemaFromProtobuf(ansObj, msg) {
      let mn;
      let mv;
      let ud = schema.unitTypes[un];
      mn = ud.__.msgNumbers[msg.number];

      if (mn) {
        let mf;
        let md = ud[mn];
        let md_;
        if (md) md_ = md.__;
        if (md_) mf = md_.format;
        switch (mf) {
          case 'timePPK':
          case 'varInt':
          case 'fixed32':
          case 'fixed64':
          case 'bytes':
            // scalar message == todo - array (repeated)
            // messages
            // msg contains format, will put value there
            mv = decodeParameterBySchemaFromProtobuf(mf, msg);
            break;
          default:
            // real message
            mv = {};
            for (let pn in md) if (pn[0] != '_') if (md[pn].repeated) mv[pn] = [];
            if (md_ && Array.isArray(msg.value))
              for (let par of msg.value) {
                let pn = md_.fieldNumbers[par.number];
                if (pn) {
                  let pd = md[pn];
                  if (pd) {
                    let pf = pd.format;
                    if (!pf) pf = 'varInt';
                    switch (pf) {
                      case 'string':
                        mv[pn] = '';
                        if (par.format == 2) {
                          let ccc = 0;
                          for (let i = 0; i < par.length; i++) {
                            let cc = par.bytes[i];
                            if (ccc) {
                              cc += ccc;
                              ccc = 0;
                            } else if (cc == 0xd0) {
                              ccc = 0x410 - 0x90;
                              continue;
                            } else if (cc == 0xd1) {
                              ccc = 0x440 - 0x80;
                              continue;
                            }
                            if (!cc) break;
                            mv[pn] += String.fromCharCode(cc);
                          }
                        }
                        break;
                      case 'timePPK':
                      case 'varInt':
                      case 'fixed32':
                      case 'fixed64':
                      case 'bytes':
                        {
                          let pv = decodeParameterBySchemaFromProtobuf(pf, par);
                          if (pv !== undefined) {
                            if (pd && pd.repeated) {
                              if (mv[pn]) mv[pn].push(pv);
                              else mv[pn] = [pv];
                            } else mv[pn] = pv;
                          }
                        }
                        break;
                      default:
                        // submessage
                        if (par.format === 2) {
                          mv[pn] = {};
                          decodeMessageBySchemaFromProtobuf(mv[pn], par.value);
                        }
                    }
                  }
                }
              }
        }
        if (mv !== undefined) {
          if (md_ && md_.repeated) {
            if (ansObj[mn]) ansObj[mn].push(mv);
            else ansObj[mn] = [mv];
          } else ansObj[mn] = mv;
        }
      }
    }

    for (let m of a) decodeMessageBySchemaFromProtobuf(ans, m);
  } catch {
    return {};
  }

  return ans;
}

/*
 * unitTypeName unitTypeNameRequired
 */
/////////////13
function unitTypeCompatible(fact, required) {
  try {
    do {
      if (fact === required) return true;
      fact = schema.unitTypes[fact].__.inherits;
    } while (fact);
  } catch {}
  return false;
}

////////////12
function getTypeName(type) {
  if (type) if (schema.unitTypes[type]) return schema.unitTypes[type].__['Title' + lang];
  return type;
}

//////////////11
function getSutSmth(sut, unit, smth) {
  try {
    let sutDesc = unit._desc.__.subUnits[sut];
    let name = sutDesc[smth + lang];
    if (name) return name;
    let typDesc = schema.unitTypes[sut].__;
    name = typDesc[smth + lang];
    if (name) return name;
    name = sutDesc[smth + 'EN'];
    if (name) return name;
    name = typDesc[smth + 'EN'];
    if (name) return name;
  } catch {}
  return sut;
}

//////////////10
function getSutName(sut, unit) {
  return getSutSmth(sut, unit, 'Title');
}

//////////////9
function getSutDesc(sut, unit) {
  return getSutSmth(sut, unit, 'Description');
}

//////////////8
function getTypeDesc(type) {
  if (type) if (schema.unitTypes[type]) return schema.unitTypes[type].__['Description' + lang];
  return type;
}

////////////////7
function getMsgDesc(ut, m) {
  let ud = schema.unitTypes[ut];
  while (ud) {
    let md = ud[m];
    if (md) {
      let md_ = md.__;
      if (md_)
        return {
          t: md_['Title' + lang],
          d: md_['Description' + lang],
          m: md,
        };
    }
    ud = schema.unitTypes[ud.__.inherits];
  }
  return {
    t: m,
  };
}

/////////////6
function getParVal(pd, val) {
  // returns object (repeated are arrays)
  let v = '';
  if (pd) {
    function makeVal(va) {
      let tv = 0;
      if (va) tv = va;

      if (pd.link && pd.format == 'bytes' && !va) va = '';

      if (pd.format == 'string') {
        // if (typeof va == "string") {
        v += '"' + va + '"';
        return;
      }

      if (pd.coeff) tv *= pd.coeff;
      if (pd.shift) tv -= pd.shift;

      if (pd['enum' + lang]) {
        if (pd['enum' + lang][tv]) tv = pd['enum' + lang][tv].split(':', 1)[0];
      } else {
        let tvo = tv;
        if (pd.signed) {
          tvo >>= 1;
          tvo &= 0x7fffffff;
          let v2 = tvo;
          if (tv & 1) {
            v2 = -v2;
            tvo = v2 & 0x7fffffff;
            tvo += 0x80000000; // positive hexstarted by FFFF
          }
          tv = v2;
        }
        let d = 0;
        let pc = pd.coeff;
        while (pc && pc < 1) {
          d++;
          pc *= 10;
        }
        if (pd.format != 'bytes') {
          if (!tv) tv = 0;
          tv = tv.toFixed(d);
          if (pd.hex) tv += '( 0x' + tvo.toString(16) + ' )';
        }
      }
      if (pd.measureUnits) tv += ' ' + pd.measureUnits;
      if (v.length) v += ', ';
      v += tv;
    }

    if (pd.repeated) {
      if (Array.isArray(val)) for (let i = val.length; i--; ) makeVal(val[i]);
      else v = '--';
    } else makeVal(val);
  } else v += val;
  return v;
}

///////////5
function getDict(name) {
  let d = schema.dictionary[name];
  if (d) d = d[lang];
  if (d) return d;
  else return name;
}

///////////////4 ====== verbose undefined or 0 - full; 1 - short, 2 - supershort (digits)
function getShortUnitName(u, verbose) {
  let curname;
  let sub = schema.unitTypes[u._up._typeName].__.subUnits;
  switch (verbose) {
    case 2:
      curname = schema.unitTypes[u._up._typeName].__.subUnits;
      if (Object.keys(sub).length > 1) {
        curname = curname[u._sut].number + '#';
        if (sub[u._sut].qty > 1) curname += u._sun;
      } else curname = '' + u._sun;
      break;
    default:
      if (sub[u._sut].qty > 1) {
        curname = getTypeName(u._sut, verbose) + '#' + u._sun;
        if (u._typeName !== u._sut) curname += '(' + getTypeName(u._typeName) + ')';
      } else curname = getTypeName(u._typeName);
      break;
    case 1:
      if (Object.keys(sub).length > 1) {
        curname = sub[u._sut]['TitleShort' + lang];
        if (!curname) curname = sub[u._sut]['Title' + lang];
        if (!curname) curname = getTypeName(u._sut);
        if (sub[u._sut].qty > 1) curname += '#';
      } else curname = '';
      if (sub[u._sut].qty > 1) curname += u._sun;
      break;
    case 3:
      if (schema.unitTypes[u._sut].__.treeView || schema.unitTypes[u._sut].__.treeViewList) {
        curname = sub[u._sut]['TitleShort' + lang];
        if (!curname) curname = sub[u._sut]['Title' + lang];
        if (!curname) curname = getTypeName(u._sut);
        if (sub[u._sut].qty > 1) curname += '#' + u._sun;
      } else curname = '';
      break;
  }
  return curname;
}

//////////////3 ----verbose - see above
function getUnitName(u, verbose) {
  if (!u) return;
  try {
    let name = '';
    while (u._typeName !== '_System') {
      let curname = getShortUnitName(u, verbose);
      if (name.length && curname.length) curname += '.';
      name = curname + name;
      u = u._up;
    }
    return name;
  } catch {}
  return 'unknown unit';
}
////////////////////////////////2
function paramDescription(pd) {
  let d = pd['Description' + lang];
  let enums = pd['enum' + lang];
  try {
    if (enums) {
      d += '\n=============\n' + schema.dictionary.enum[lang] + ':';
      for (let en in enums) d += '\n=======================\n' + enums[en];
    }
  } catch {}
  return d;
}
///////////////////////////////1
function paramShortEnums(pd) {
  let d = null;
  let enums = pd['enum' + lang];
  try {
    if (enums) {
      d = {};
      for (let en in enums) d[en] = enums[en].split(':', 1)[0];
    }
  } catch {}
  return d;
}

function findCfgParameterOrigin(parameter, unitTypeName) {
  let desc = schema.unitType[unitTypeName];
  while (
    desc &&
    desc.__.inherits &&
    schema.unitType[desc.__.inherits].config &&
    schema.unitType[desc.__.inherits].config[parameter]
  ) {
    unitTypeName = desc.__.inherits;
    desc = schema.unitType[unitTypeName];
  }
  return unitTypeName;
}
