//import SerialPort from './serialport.js'
const {SerialPort, InterByteTimeoutParser} = require('serialport')
const electron = require('electron')
const fs = require('fs');
const path_m = require('path');
const {encode, decode,formats_list} = require('./converters.js')

const default_version = '2'

function load_config (version=null,subversion=null){
  var path = path_m.join(__dirname,'data/versions/')
  var version_list = {}
  fs.readdirSync(path).forEach(file => {
    var fn = file.split('.')
    if (fn.length>1){
      if (typeof version_list[fn[0]]=='undefined'){
        version_list[fn[0]]=[]
      }
      if (fn.length>2){
        version_list[fn[0]].push(fn[1])
      }
    }
  })
  for (var i in version_list){
    version_list[i] = version_list[i].sort((a,b)=>{return parseInt(a)-parseInt(b)})
  }
  var message = ''
  var version1 = (version==null || version=='' || typeof version=='undefined')?default_version:version
  var fname = String(version1)
  var found = false

  if (subversion!=null && subversion != '' && typeof subversion != 'undefined'){
    for(var i in version_list[version1]){
      var sv = version_list[version1][version_list[version1].length-1-i]
      if (parseInt(subversion)>=parseInt(sv)){
        fname += '.'+String(sv)
        found = true
        if (parseInt(subversion)==parseInt(sv)){
          message = 'Версия прошивки '+String(version)+'.'+String(subversion)
        }else{
          message = 'Нет данных для подверсии прошивки '+String(version1)+'.'+String(subversion)+', загружен шаблон для версии '+fname
        }
        break
      }
    }
  }
  if (!found){
    message = 'Нет данных для версии прошивки ' + String(version)+'.'+String(subversion)+', загружен шаблон по умолчанию (версия '+fname+')'
  }
  var out = {}
  out = fs.readFileSync(path+fname+'.txt', 'utf8');
  out = JSON.parse(out)
  out['message'] = message
  return out;
}

function lead_z(data,len=2){//добавляет ведущие нули до длины len (по умолчанию 2)
  if (String(data).length<len){
    return '0'.repeat((len-String(data).length))+String(data)
  }
  return String(data)
}

function crc16(data){
    crc_table=[0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,0xC601,
    0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,0xCC01,0x0CC0,0x0D80,0xCD41,
    0x0F00,0xCFC1,0xCE81,0x0E40,0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,
    0xC841,0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,0x1E00,0xDEC1,
    0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,0x1400,0xD4C1,0xD581,0x1540,0xD701,
    0x17C0,0x1680,0xD641,0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,
    0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,0x3600,0xF6C1,0xF781,
    0x3740,0xF501,0x35C0,0x3480,0xF441,0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,
    0x3E80,0xFE41,0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,0x2800,
    0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,0xEE01,0x2EC0,0x2F80,0xEF41,
    0x2D00,0xEDC1,0xEC81,0x2C40,0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,
    0x2640,0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,0xA001,0x60C0,
    0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,0x6600,0xA6C1,0xA781,0x6740,0xA501,
    0x65C0,0x6480,0xA441,0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,
    0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,0x7800,0xB8C1,0xB981,
    0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,
    0xBC81,0x7C40,0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,0x7200,
    0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,0x5000,0x90C1,0x9181,0x5140,
    0x9301,0x53C0,0x5280,0x9241,0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,
    0x5440,0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,0x5A00,0x9AC1,
    0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,0x8801,0x48C0,0x4980,0x8941,0x4B00,
    0x8BC1,0x8A81,0x4A40,0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,
    0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,0x8201,0x42C0,0x4380,
    0x8341,0x4100,0x81C1,0x8081,0x4040]

    var crc_hi=0xFF
    var crc_lo=0xFF

    for (var i=0;i<data.length;i+=1){
      var w = data[i]
      index=crc_lo ^ w
      crc_val=crc_table[index]
      crc_temp=Math.floor(crc_val/256)
      crc_val_low=crc_val-(crc_temp*256)
      crc_lo=crc_val_low ^ crc_hi
      crc_hi=crc_temp
    }

    var crc=(crc_hi<<8 | crc_lo)
    var crc_o = lead_z((crc % 0x100).toString(16))+lead_z(Math.floor(crc/0x100).toString(16))
    return crc_o
}

async function listSerialPorts(obj) {
  await SerialPort.list().then((ports, err) => {
    if(err) {
      console.log('err', err.message)
      return
    }
    obj['com_ports'] = ports
  })
}

function crc_check(buf){
  if (Buffer.isBuffer(buf)){
    var s = ''
    for (var i = 0;i<buf.length;i+=1){
      s+=lead_z(buf[i].toString(16),2)
    }
  }else{
    var s = str(buf)
  }
  if (s.length>=4){
    var crc = s.slice(s.length-4,s.length)
    var data = s.slice(0,s.length-4)  
  }else{
    var crc = ''
    var data = ''
  }
  return crc16(Buffer.from(data,'hex'))==crc
}

var obj = {}

listSerialPorts(obj).then(()=>{
    electron.contextBridge.exposeInMainWorld(
        'com_ports',obj['com_ports']
    )
})

const baudRate = 115200
const slave = '01'

var device,parser

function connect_device(port,on_error=(data)=>{}){
    device = new SerialPort({
      path:port['path'],
      baudRate: baudRate,
      bufferSize: 64,
    },on_error,true);
    parser = device.pipe(new InterByteTimeoutParser({interval:500}))
    //device.on('data',(data)=>{console.log('data',data)})
    //parser.on('data',(data)=>{console.log('data',data)})
    device.on('close',(data)=>{console.log('closing port',data)})
    var reg_addr = 2108
    var reg_num = 1
    console.log('dv',device,device.port)
}

function disconnect_device(){
  try{
    device.close()
  }catch{

  }
}

const MAX_REG_NUM = 128

//ReadRegisterM из modbus_util.officer_commands переписанная на js
function read_register(data,on_success=(data)=>{},on_error=(data)=>{},on_progress=(data)=>{}){
  var data_proc = []
  for (var i in data){
    var item = [parseInt(data[i][0]),parseInt(data[i][1])]
    var split = i.split('$')
    if (split.length>1){
      var split_1=split[1].split('#')
      split_1 = parseInt(split_1[0])
      if (split_1>=2){
        item[0]+=Math.floor(split_1/2)
      }
    }
    data_proc.push(item)
  }
  data_proc.sort((a,b)=>{
    if (a[0]==b[0]){
      return a[1]-b[1]
    }else{
      return a[0]-b[0]
    }
  })
  var i = 0;
  while (i<data_proc.length-1){
    var cur = data_proc[i]
    var next = data_proc[i+1]
    if (cur[0]==next[0] && cur[1]==next[1]){
      data_proc.splice(i+1,1)
    }
    else if ((cur[0]+cur[1])>=next[0]){
      cur[1]=Math.max(cur[0]+cur[1],next[0]+next[1])-cur[0]
      data_proc.splice(i+1,1)
    }else{
      i+=1
    }
  }
  device.on('error',(error)=>{on_error(error);})
  read_rec(data_proc,0,on_error,on_progress).then(()=>{
    var out = {}
    if (check_compl(data_proc)>-1){
      on_error('Не удалось прочитать значения')
      throw('Не удалось прочитать значения')
      return;
    }
    for (var i in data){
      var item = data[i];
      var addr = parseInt(item[0]);
      var len = parseInt(item[1])
      var split = i.split('$');
      if (split.length>1){
        var split_1 = split[1].split('#')
        addr+=Math.floor(parseInt(split_1[0])/2)
      }
      for (var j in data_proc){
        var p_item = data_proc[j]
        if (addr>=p_item[0]){
          if (p_item[0]*2+p_item[2].length-5>=(addr+len)*2){
            var o_item = {}
            if (split.length<=1){
              var i_bytes = p_item[2].slice(3+(addr-p_item[0])*2,3+(addr-p_item[0]+len)*2)
              var val = encode(item[2],i_bytes,len)
              o_item['val']=val
            }else{
              split_1[0]=parseInt(split_1[0])%2
              var s_b = split_1[0]
              var e_b = s_b+1
              var split_2 = split_1[1].split(':')
              split_2[0]=parseInt(split_2[0])
              if (split_2.length<=1){
                var i_bytes = p_item[2].slice(3+(addr-p_item[0])*2,p_item[2].length-2)
                var val = encode('bl',i_bytes.slice(split_1[0],split_1[0]+1)).replace(',','')
                val = val[split_2[0]]
                var s_b = split_1[0]
                var e_b = s_b+1
                o_item['val']=val
              }else{
                split_2[1]=parseInt(split_2[1])
                var s_b = split_1[0]
                var e_b = s_b+Math.ceil(split_2[1]/8)
                var i_bytes = p_item[2].slice(3+(addr-p_item[0])*2,p_item[2].length-2)
                var val = encode('bl',i_bytes.slice(s_b,e_b)).replace(',','').slice(split_2[0],split_2[1]+1)
                val = decode('bl',val)
                val = encode(item[2],val)
                o_item['val']=val
              }
              if (s_b%2>0){
                var p_bytes = i_bytes.slice(s_b-1,s_b) 
                o_item['m_byte']= encode('bl',p_bytes,1).replace(',','')
              }
              if (e_b%2>0){
                var p_bytes = i_bytes.slice(e_b,e_b+1) 
                o_item['c_byte']= encode('bl',p_bytes,1).replace(',','')
              }
              var if_bytes = i_bytes.slice(s_b,e_b)
              o_item['cur_byte']= encode('bl',if_bytes,1).replace(',','')
            }
            out[i]=o_item
            break;
          }
        }
      }
    }
    on_success(out);
    return out;
  }).catch((error)=>{
      on_error(error)
      throw(error)
  })
}

async function read_rec (data,ind,on_error,on_progress){
  if (ind>=data.length){
    return
  }
  var item = data[ind]
  var addr = item[0]
  var len = item[1]
  var reg_addr = lead_z(parseInt(addr).toString(16),4)
  var reg_num = lead_z(parseInt(len).toString(16),4)
  var arb = Buffer.from((slave+'03'+reg_addr+reg_num),'hex')
  var crc = String(crc16(arb))
  arb = Buffer.from((slave+'03'+reg_addr+reg_num+crc),'hex')
  device.write(arb,'hex',()=>{
    parser.once('data',async(res)=>{
      if (!crc_check(res)){
        on_error('Ошибка проверки CRC')
        throw('Ошибка проверки CRC')
      }else{
        data[ind].push(res);
        on_progress(Math.floor(ind/data.length*100));
        await sleep(500);
        read_rec(data,ind+1,on_error,on_progress)
      }
    })
  })
  var iter_num = 0
  var chk = check_compl(data)
  while (chk>-1){
    await sleep(1000);
    iter_num+=1;
    if (iter_num>(5*(chk-ind+1))){
      on_error('Превышено время ожидания считывания')
      throw('Превышено время ожидания считывания')
    }
    chk = check_compl(data)
  }
}

function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms)
  })
}

function check_compl(data){
  for (var i in data){
    if (data[i].length<3){
      return i;
    }
  }
  return -1;
}

//WriteRegister из modbus_util.officer_commands переписанная на js
function write_register(data_1,on_success=(data)=>{},on_error=(data)=>{},on_progress=(data)=>{}){
  try{
    var data = data_1.sort((a,b)=>{return (a['byte']||0)-(b['byte']||0)})
    for (var i=0;i<data.length;i+=1){
      var reg = data[i]
      reg['reg']['pos'] = parseInt(reg['reg']['pos'])
      reg['reg']['len'] = parseInt(reg['reg']['len'])
      reg['byte'] = parseInt(reg['byte']||0)
      reg['b_len'] = parseInt(reg['b_len']||1)
    }
    var addr = data[0]['reg']['pos']+Math.floor(data[0]['byte']/2)
  }catch{
    on_error('Неполные параметры')
    return
  }
  for (var i in data){
    try{
        if (data[i]['byte']%2>0){
            if (i==0){
              throw Error()
            }
            if (data[i-1]['byte']!=data[i]['byte']-1){
              throw Error()
            }
        }
    }catch{
        on_error("Нет значения старшего байта.")
        return
    }
  }
  for (var i in data){
    var reg = data[i]
      if (formats_list().indexOf(reg['reg']['type'])==-1){
          on_error("Неизвестный формат данных.")
          return
      }
  }
  for (var i in data){
    var reg = data[i]
    try{
        reg['val'] = String(reg['val'])
    }catch{
        on_error("Не допускаются русские буквы.")
        return 
    }
  }
  var buf = Buffer.from('','hex')
  for (var i in data){
    reg = data[i]
    try{
        var flag = false
        while (reg['val'].split('~%').length>1){
            flag = true
            var a = decode(reg['reg']['type'],reg['val'].split('~%')[1].split('%')[1].split('~')[0])
            var a_b = encode('bl',a).replace(',','')
            a_b = a_b.slice(a_b.length-parseInt(reg['val'].split('~%')[1].split('%')[0]),a_b.length)
            reg['val'] = reg['val'].split('~%')[0]+a_b+reg['val'].split('~%')[1].split('~')[1]
        }
        if (flag){
          var ar = []
          for (var i=0;i<=reg['val'].length;i+=8){
            ar.push(reg['val'].slice(i,i+8))
          }
          reg['val'] = ar.join(',')
          var a = decode('bl',reg['val'])
        }else{
          var a = decode(reg['reg']['type'], reg['val'], flen=reg['reg']['len']*2)
        }
        if (data.length>1){
          var b_len = reg['b_len']||1
          var b_len = parseInt(b_len)
          var a_n = [a.slice(a.length-b_len,a.length)]
          for (var j=0;j<a_n.length;j+=1){
            var b = a_n[j]
            buf=Buffer.concat([buf,b])
          }
        }else{
          buf=Buffer.concat([buf,a])
        }
    }catch (err){
        on_error("Некорректные данные для формата "+reg['reg']['type']+' '+String(err))
        return
    }
  }
  var vals_s = ''
  for (var i=0;i<buf.length;i+=1){
    var a = lead_z(buf[i].toString(16),2);
    vals_s=vals_s+a
  }
  if (data.length>1){
    var len = Math.ceil(vals_s.length/4)
    var b_len = Math.ceil(vals_s.length/2)
  }else{
    var len = data[0]['reg']['len']
    var b_len = data[0]['reg']['len']*2
    vals_s = vals_s.slice(vals_s.length-b_len*2,vals_s.length)
  }
  var reg_addr = lead_z(parseInt(addr).toString(16),4)
  var reg_num = lead_z(parseInt(len).toString(16),4)
  var reg_bnum = lead_z(parseInt(b_len).toString(16),2)
  var arb = Buffer.from((slave+'10'+reg_addr+reg_num+reg_bnum+vals_s),'hex')
  var crc = String(crc16(arb))
  arb = Buffer.from((slave+'10'+reg_addr+reg_num+reg_bnum+vals_s+crc),'hex')
  device.write(arb,'hex',()=>{
    parser.once('data',async(res)=>{
      if (!crc_check(res)){
        on_error('Ошибка проверки CRC')
        return
      }else{
        on_success();
      }
    })
  })
}

electron.contextBridge.exposeInMainWorld(
    'modbus',{
        'connect_device':connect_device,
        'disconnect_device':disconnect_device,
        'read_register':read_register,
        'write_register':write_register,
        'load_config':load_config,
    }
)