'use strict';
// XpressNet (Lenz, Roco multiMaus via interface, e.a.) — serieel/USB (LI101F/USB-interface).
// XpressNet gebruikt dezelfde "X-bus"-commando's als binnen het Z21 LAN-protocol; het verschil
// is de transportlaag (serieel i.p.v. UDP). Elk commando eindigt met een XOR-checksum.
//
// Veelgebruikte commando's:
//   Track power on  : 0x21 0x81 [xor]        (resume normal operations)
//   Track power off : 0x21 0x80 [xor]
//   Emergency stop  : 0x80 [xor]
//   Loco drive(128) : 0xE4 0x13 AH AL  (D + speed)  [xor]
//   Loco func grp1  : 0xE4 0x20 AH AL  (000 F0 F4..F1) [xor]
//   Accessory       : 0x52 AH (1000 ABBP) [xor]
//
// ⚠️ SCAFFOLD: X-bus-commando's volgen de Lenz/Z21-spec; seriële framing + adres-encoding op
//    een echte XpressNet-interface verifiëren. Vereist `npm i serialport`.
const EventEmitter = require('events');

class XpressNet extends EventEmitter {
  constructor(opts = {}) {
    super();
    this.mode = 'xpressnet';
    this.path = opts.path || '/dev/ttyUSB0';
    this.baud = opts.baud || 57600;              // LI-USB: vaak 57600 (verifieer)
    this.connected = false;
    this.port = null;
    this.state = { power: false, locos: {}, turnouts: {}, sensors: {} };
  }

  connect() {
    let SerialPort;
    try { ({ SerialPort } = require('serialport')); }
    catch (_) { this.emit('log', '[xpressnet] serialport ontbreekt — `npm i serialport`. Nu: log-only.'); this.connected = false; this.emit('close'); return; }
    try {
      this.port = new SerialPort({ path: this.path, baudRate: this.baud });
      this.port.on('open', () => { this.connected = true; this.emit('open'); this.emit('log', '[xpressnet] poort open: ' + this.path + ' @' + this.baud); });
      this.port.on('data', (d) => this.emit('log', '↩ ' + d.toString('hex')));
      this.port.on('error', (e) => this.emit('log', '[xpressnet] fout: ' + e.message));
      this.port.on('close', () => { this.connected = false; this.emit('close'); });
    } catch (e) { this.emit('log', '[xpressnet] kon poort niet openen: ' + e.message); }
  }

  _x(xbytes, label) {
    let xor = 0; for (const x of xbytes) xor ^= x;
    const buf = Buffer.from([...xbytes, xor]);
    this.emit('log', '→ ' + (label || '') + ' [' + buf.toString('hex') + ']');
    if (this.connected && this.port) { try { this.port.write(buf); } catch (e) { this.emit('log', '[tx-fout] ' + e.message); } }
  }
  _loco(addr) { if (!this.state.locos[addr]) this.state.locos[addr] = { speed: 0, dir: 1, fns: {} }; return this.state.locos[addr]; }
  _adr(addr) { const aH = addr >= 100 ? (0xC0 | ((addr >> 8) & 0x3F)) : ((addr >> 8) & 0x3F); return [aH, addr & 0xFF]; }

  power(on) { this.state.power = !!on; this._x([0x21, on ? 0x81 : 0x80], on ? 'power on' : 'power off'); this.emit('state'); }
  throttle(addr, speed, dir) {
    addr = parseInt(addr, 10); let ui = Math.max(-1, Math.min(126, parseInt(speed, 10)));
    const l = this._loco(addr); l.dir = dir ? 1 : 0; l.speed = ui < 0 ? 0 : ui;
    let dcc = ui < 0 ? 1 : (ui === 0 ? 0 : Math.min(127, ui + 1));
    const sb = (dir ? 0x80 : 0x00) | (dcc & 0x7F);
    const [aH, aL] = this._adr(addr);
    this._x([0xE4, 0x13, aH, aL, sb], 'drive ' + addr + '=' + ui);
    this.emit('state');
  }
  func(addr, fn, on) {
    addr = parseInt(addr, 10); fn = parseInt(fn, 10); const l = this._loco(addr); l.fns[fn] = !!on;
    const [aH, aL] = this._adr(addr);
    if (fn <= 4) { // groep 1: 000 F0 F4 F3 F2 F1
      let g = 0; if (l.fns[0]) g |= 0x10; if (l.fns[1]) g |= 0x01; if (l.fns[2]) g |= 0x02; if (l.fns[3]) g |= 0x04; if (l.fns[4]) g |= 0x08;
      this._x([0xE4, 0x20, aH, aL, g], 'func1');
    } else if (fn <= 8) {
      let g = 0; for (let i = 5; i <= 8; i++) if (l.fns[i]) g |= (1 << (i - 5));
      this._x([0xE4, 0x21, aH, aL, g], 'func2');
    } else {
      let g = 0; for (let i = 9; i <= 12; i++) if (l.fns[i]) g |= (1 << (i - 9));
      this._x([0xE4, 0x22, aH, aL, g], 'func3');
    }
    this.emit('state');
  }
  estop() { for (const a of Object.keys(this.state.locos)) this.state.locos[a].speed = 0; this._x([0x80], 'NOODSTOP'); this.emit('log', '⛔ NOODSTOP'); this.emit('state'); }
  turnout(id, thrown) {
    id = parseInt(id, 10); this.state.turnouts[id] = !!thrown; const a = id - 1;
    const aH = (a >> 2) & 0xFF; const nibble = 0x80 | ((a & 0x03) << 1) | (thrown ? 1 : 0) | 0x08;
    this._x([0x52, aH, nibble], 'wissel ' + id); this.emit('state');
  }
}

module.exports = { XpressNet };
