'use strict';
// Märklin Central Station 2/3 (en Trix) — CAN-bus over UDP.
// Verbinding: UDP, verzenden naar poort 15731, ontvangen op 15730 (broadcast in het LAN).
//
// CAN-frame in 13 bytes UDP:
//   [0..1] = (prio<<9) | (command<<1) | responseBit   (big-endian 16-bit)
//   [2..3] = hash (mag 0x0300 zijn voor een PC)
//   [4]    = DLC (aantal data-bytes, 0..8)
//   [5..12]= data (8 bytes, rest 0)
// Commando's: 0x00 systeem, 0x04 loc-snelheid, 0x05 loc-richting, 0x06 loc-functie, 0x0B accessory.
// Loc-adres (UID) in data[0..3]; DCC-loc = 0x4000 + adres, MM2-loc = 0x0000 + adres.
//
// ⚠️ SCAFFOLD: byte-structuur volgt de CS2-CAN-documentatie; op een echte CS2/CS3 verifiëren.
const dgram = require('dgram');
const EventEmitter = require('events');

class Marklin extends EventEmitter {
  constructor(opts = {}) {
    super();
    this.mode = 'marklin';
    this.host = opts.host || '192.168.0.10';     // IP van de Central Station
    this.txPort = opts.port || 15731;
    this.rxPort = 15730;
    this.dcc = opts.dcc !== false;               // true: DCC-loc (0x4000), false: MM2
    this.connected = false;
    this.sock = null;
    this.state = { power: false, locos: {}, turnouts: {}, sensors: {} };
  }

  connect() {
    this.sock = dgram.createSocket({ type: 'udp4', reuseAddr: true });
    this.sock.on('message', (m) => this._onData(m));
    this.sock.on('error', (e) => this.emit('log', '[marklin] fout: ' + e.message));
    this.sock.bind(this.rxPort, () => {
      this.connected = true; this.emit('open');
      this.emit('log', '[marklin] UDP → ' + this.host + ':' + this.txPort + ' (luistert op ' + this.rxPort + ')');
    });
  }

  _frame(command, data) {
    const b = Buffer.alloc(13);
    const id = ((command & 0xFF) << 1);          // prio 0, responseBit 0
    b.writeUInt16BE(id, 0);
    b.writeUInt16BE(0x0300, 2);                   // hash voor een PC-zender
    b[4] = data.length & 0x0F;
    Buffer.from(data).copy(b, 5);
    return b;
  }
  _send(command, data, label) {
    const f = this._frame(command, data);
    this.emit('log', '→ ' + (label || '') + ' [' + f.toString('hex') + ']');
    if (this.sock && this.connected) { try { this.sock.send(f, this.txPort, this.host); } catch (e) { this.emit('log', '[tx-fout] ' + e.message); } }
  }
  _onData(m) {
    this.emit('log', '↩ ' + m.toString('hex'));
    if (m.length < 13) return;
    const cmd = (m.readUInt16BE(0) >> 1) & 0xFF;
    const dlc = m[4] & 0x0F;
    const d = m.slice(5, 5 + dlc);
    if (cmd === 0x11 && d.length >= 6) {                 // S88-event: bezetmelding
      const contact = d.readUInt16BE(2);                 // d0..1 = apparaat, d2..3 = contact
      this.state.sensors[contact] = d[5] === 1;          // d5: 0=vrij, 1=bezet
      this.emit('state');
    } else if (cmd === 0x00 && d.length >= 5) {          // systeem: go/stop
      if (d[4] === 0x01) { this.state.power = true; this.emit('state'); }
      else if (d[4] === 0x00) { this.state.power = false; this.emit('state'); }
    }
  }

  _loco(addr) { if (!this.state.locos[addr]) this.state.locos[addr] = { speed: 0, dir: 1, fns: {} }; return this.state.locos[addr]; }
  _uid(addr) { const u = (this.dcc ? 0x4000 : 0x0000) + (addr & 0x3FFF); return [(u >> 24) & 0xFF, (u >> 16) & 0xFF, (u >> 8) & 0xFF, u & 0xFF]; }

  power(on) {
    this.state.power = !!on;
    // systeem-commando: data = [0,0,0,0, subcmd]  (0x01=go, 0x00=stop)
    this._send(0x00, [0, 0, 0, 0, on ? 0x01 : 0x00], on ? 'GO' : 'STOP');
    this.emit('state');
  }
  throttle(addr, speed, dir) {
    addr = parseInt(addr, 10); const ui = Math.max(0, Math.min(126, parseInt(speed, 10)));
    const l = this._loco(addr); l.speed = ui; l.dir = dir ? 1 : 0;
    const sp = Math.round((ui / 126) * 1000);     // CS2: snelheid 0..1000
    const u = this._uid(addr);
    this._send(0x04, [...u, (sp >> 8) & 0xFF, sp & 0xFF], 'speed ' + ui);
    this._send(0x05, [...u, dir ? 1 : 2], 'dir');  // 1=vooruit, 2=achteruit
    this.emit('state');
  }
  func(addr, fn, on) {
    addr = parseInt(addr, 10); fn = parseInt(fn, 10); this._loco(addr).fns[fn] = !!on;
    const u = this._uid(addr);
    this._send(0x06, [...u, fn & 0xFF, on ? 1 : 0], 'f' + fn);
    this.emit('state');
  }
  estop() {
    for (const a of Object.keys(this.state.locos)) this.state.locos[a].speed = 0;
    this._send(0x00, [0, 0, 0, 0, 0x00], 'NOODSTOP'); this.emit('log', '⛔ NOODSTOP'); this.emit('state');
  }
  turnout(id, thrown) {
    id = parseInt(id, 10); this.state.turnouts[id] = !!thrown;
    const u = this._uid(0x3000 + id);             // accessory-UID-bereik
    this._send(0x0B, [...u, thrown ? 1 : 0, 1], 'wissel ' + id);  // positie, stroom aan
    this.emit('state');
  }
}

module.exports = { Marklin };
