'use strict';
// WiThrottle-server voor ModelRailPro.
//
// Laat throttle-apps (Engine Driver, WiThrottle, enz.) je baan bedienen: ze verbinden via TCP
// en sturen WiThrottle-commando's; wij vertalen die naar ModelRailPro-commando's (handleCmd).
// Geen externe dependencies (net-module). Standaardpoort 12090 — in de app zet je IP:poort
// handmatig (mDNS-autodiscovery kan later). Uitzetten met BB_WITHROTTLE_PORT=0.
//
// Ondersteund: handshake + roster, snelheid (V), richting (R), functies (F, toggle bij druk),
// noodstop (X), railspanning (PPA), multi-throttle (M-commando's), heartbeat.

const net = require('net');

const SEP = '\n';
const RS = ']\\[';   // record-separator  ]\[
const US = '}|{';    // unit-separator    }|{

class WiThrottleServer {
  constructor(opts = {}) {
    this.port = opts.port != null ? opts.port : 12090;
    this.getRoster = opts.getRoster || (() => ({ locos: [] }));
    this.getPower = opts.getPower || (() => false);
    this.onCmd = opts.onCmd || (() => {});
    this.log = opts.log || (() => {});
    this.server = null;
    this.clients = new Set();
    this.locoState = {};   // addr -> {speed,dir}
  }

  start() {
    if (!this.port) return;
    this.server = net.createServer((sock) => this._client(sock));
    this.server.on('error', (e) => this.log('[withrottle] fout: ' + e.message));
    this.server.listen(this.port, () => this.log('[withrottle] luistert op TCP ' + this.port + ' (throttle-apps)'));
  }
  stop() { if (this.server) { try { this.server.close(); } catch (_) {} this.server = null; } for (const c of this.clients) { try { c.sock.destroy(); } catch (_) {} } this.clients.clear(); }

  _send(c, line) { try { c.sock.write(line + SEP); } catch (_) {} }

  _rosterLine() {
    const locos = (this.getRoster().locos || []);
    let s = 'RL' + locos.length;
    for (const l of locos) {
      const addr = parseInt(l.addr, 10) || 0;
      s += RS + (l.name || ('Loc ' + addr)) + US + addr + US + (addr > 127 ? 'L' : 'S');
    }
    return s;
  }
  _funcLabels(addr) {
    const l = (this.getRoster().locos || []).find((x) => (parseInt(x.addr, 10) || 0) === addr);
    const labels = new Array(29).fill('');
    if (l) for (const f of (l.functions || [])) { if (f.fn >= 0 && f.fn < 29) labels[f.fn] = f.label || ('F' + f.fn); }
    return labels;
  }

  _client(sock) {
    const c = { sock, buf: '', name: '', throttles: {} };   // throttles: tId -> Set(addr)
    this.clients.add(c);
    sock.setEncoding('utf8');
    // begroeting
    this._send(c, 'VN2.0');
    this._send(c, 'PPA' + (this.getPower() ? '1' : '0'));
    this._send(c, '*10');                       // heartbeat-interval (s)
    this._send(c, this._rosterLine());
    this._send(c, 'PTL');                        // (lege) wisselijst
    sock.on('data', (d) => {
      c.buf += d;
      let i;
      while ((i = c.buf.indexOf('\n')) >= 0) {
        const line = c.buf.slice(0, i).replace(/\r$/, '');
        c.buf = c.buf.slice(i + 1);
        if (line) this._line(c, line);
      }
    });
    sock.on('error', () => {});
    sock.on('close', () => this.clients.delete(c));
  }

  _broadcastPower(on) { for (const c of this.clients) this._send(c, 'PPA' + (on ? '1' : '0')); }

  _line(c, line) {
    const t = line[0];
    if (t === 'M') return this._multi(c, line);
    if (line.startsWith('PPA')) {
      const v = line[3];
      if (v === '1') { this.onCmd({ type: 'power', on: true }); this._broadcastPower(true); }
      else if (v === '0') { this.onCmd({ type: 'power', on: false }); this._broadcastPower(false); }
      return;
    }
    if (t === 'N') { c.name = line.slice(1); this._send(c, '*10'); return; }   // device name
    if (t === '*') return;                                                     // heartbeat tik
    if (t === 'Q') { try { c.sock.end(); } catch (_) {} return; }              // quit
    if (t === 'H') return;                                                     // HU hardware-id e.d.
  }

  _addr(key) { return parseInt(String(key).replace(/^[LS]/, ''), 10) || 0; }
  _st(addr) { return (this.locoState[addr] = this.locoState[addr] || { speed: 0, dir: 1 }); }
  _drive(addr) { const s = this._st(addr); this.onCmd({ type: 'throttle', addr, speed: s.speed, dir: s.dir }); }

  // Multi-throttle: M<tId><actie><key><;><waarde>
  _multi(c, line) {
    const tId = line[1];
    const action = line[2];
    const rest = line.slice(3);
    const parts = rest.split('<;>');
    const key = parts[0];
    const val = parts[1] != null ? parts[1] : '';
    c.throttles[tId] = c.throttles[tId] || new Set();

    if (action === '+') {
      const addr = this._addr(key);
      c.throttles[tId].add(addr);
      this._send(c, 'M' + tId + '+' + key + '<;>');                       // bevestig
      this._send(c, 'M' + tId + 'L' + key + '<;>' + RS + this._funcLabels(addr).join(RS)); // functielabels
      const s = this._st(addr);
      this._send(c, 'M' + tId + 'A' + key + '<;>' + 'V' + s.speed);
      this._send(c, 'M' + tId + 'A' + key + '<;>' + 'R' + s.dir);
      return;
    }
    if (action === '-') {
      if (key === '*') c.throttles[tId].clear(); else c.throttles[tId].delete(this._addr(key));
      this._send(c, 'M' + tId + '-' + key + '<;>');
      return;
    }
    if (action === 'A') {
      const targets = key === '*' ? Array.from(c.throttles[tId]) : [this._addr(key)];
      const cmd = val[0];
      for (const addr of targets) {
        const s = this._st(addr);
        if (cmd === 'V') { let v = parseInt(val.slice(1), 10); if (isNaN(v)) v = 0; if (v < 0) v = 0; s.speed = v; this._drive(addr); this._echo(c, tId, addr, 'V' + s.speed); }
        else if (cmd === 'R') { s.dir = val[1] === '0' ? 0 : 1; this._drive(addr); this._echo(c, tId, addr, 'R' + s.dir); }
        else if (cmd === 'F') { const press = val[1] === '1'; const fn = parseInt(val.slice(2), 10); if (press && fn >= 0) { s['f' + fn] = !s['f' + fn]; this.onCmd({ type: 'func', addr, fn, on: !!s['f' + fn] }); this._echo(c, tId, addr, 'F' + (s['f' + fn] ? '1' : '0') + fn); } }
        else if (cmd === 'X') { s.speed = 0; this._drive(addr); this._echo(c, tId, addr, 'V0'); }   // loc-noodstop
        else if (cmd === 'I') { s.speed = 0; this._drive(addr); this._echo(c, tId, addr, 'V0'); }   // idle/stop
        else if (cmd === 'q') { this._echo(c, tId, addr, (val[1] === 'V' ? 'V' + s.speed : 'R' + s.dir)); } // query
      }
      return;
    }
  }
  // echo de actie terug naar de andere knoppen van deze client (consistente UI)
  _echo(c, tId, addr, payload) { this._send(c, 'M' + tId + 'A' + (addr > 127 ? 'L' : 'S') + addr + '<;>' + payload); }
}

module.exports = { WiThrottleServer };
