'use strict';
// DCC-EX adapter. Praat het DCC-EX native command-protocol (<...>) en houdt
// de live-toestand bij. Drie verbindingsmodi:
//   - sim : geen hardware, alles wordt intern bijgehouden (ontwikkelen/testen)
//   - tcp : DCC-EX met WiFi (ESP32), default poort 2560
//   - serial : USB (Arduino) — vereist later de 'serialport' npm-module (TODO)
//
// DCC-EX commando's die we gebruiken:
//   <1> / <0>            track power aan/uit
//   <t CAB SPEED DIR>    rijden: SPEED 0..126 (−1 = noodstop), DIR 1=vooruit 0=achteruit
//   <F CAB FUNC STATE>   functie aan/uit (licht, geluid, ...), STATE 1/0
//   <T ID STATE>         wissel (in DCC-EX gedefinieerd via id), STATE 1/0
//   <Q>                  sensoren (bezetmelders) opvragen  [fase 2]
const net = require('net');
const EventEmitter = require('events');

class DccEx extends EventEmitter {
  constructor(opts = {}) {
    super();
    this.mode = opts.mode || 'sim';
    this.host = opts.host || '192.168.1.110';
    this.port = opts.port || 2560;
    this.connected = false;
    this.sock = null;
    this._rx = '';
    // live-toestand
    this.state = {
      power: false,
      locos: {},     // addr -> { speed:0, dir:1, fns:{} }
      turnouts: {},  // id   -> thrown(bool)
      sensors: {},   // blok-id -> bezet(bool)  (s88/terugmelding; in sim gesimuleerd)
    };
    this._simBlocks = opts.blocks || [];   // voor de bezetmelding-simulatie
    this._simPos = 0;
  }

  // Demo-bezetmelding in simulator: laat een "trein" door de blokken lopen zolang
  // er power is en minstens één loc rijdt. Geeft de baan-weergave leven zonder hardware.
  startSim(blocks) {
    if (this.mode !== 'sim') return;
    this._simBlocks = blocks || this._simBlocks;
    if (!this._simBlocks.length) return;
    clearInterval(this._sim);
    this._sim = setInterval(() => {
      if (this._suspendSim) return;   // automatiserings-motor stuurt de sensoren zelf
      const moving = this.state.power && Object.values(this.state.locos).some(l => l.speed > 0);
      const fast = Object.values(this.state.locos).reduce((m, l) => Math.max(m, l.speed), 0);
      if (!moving) return;
      this._simPos = (this._simPos + 1) % this._simBlocks.length;
      const s = {};
      for (let i = 0; i < this._simBlocks.length; i++) s[this._simBlocks[i].id] = (i === this._simPos);
      this.state.sensors = s;
      this.emit('state');
    }, Math.max(500, 2200 - 12 * 0)).unref?.();
    // (interval vast; snelheid-afhankelijk maken kan later)
  }

  // ---- verbinding -------------------------------------------------------
  connect() {
    if (this.mode === 'sim') {
      this.connected = true;
      this.emit('open');
      this.emit('log', '[sim] simulator actief — geen hardware');
      return;
    }
    if (this.mode === 'tcp') {
      this._connectTcp();
      return;
    }
    this.emit('log', '[fout] modus "' + this.mode + '" nog niet ondersteund (serial volgt)');
  }

  _connectTcp() {
    const sock = new net.Socket();
    this.sock = sock;
    sock.setEncoding('utf8');
    sock.on('connect', () => {
      this.connected = true;
      this.emit('open');
      this.emit('log', '[tcp] verbonden met ' + this.host + ':' + this.port);
      this.send('<s>'); // status opvragen
    });
    sock.on('data', (d) => this._onData(d));
    sock.on('error', (e) => this.emit('log', '[tcp] fout: ' + e.message));
    sock.on('close', () => {
      this.connected = false;
      this.emit('close');
      this.emit('log', '[tcp] verbinding gesloten — opnieuw proberen over 3s');
      setTimeout(() => this._connectTcp(), 3000);
    });
    sock.connect(this.port, this.host);
  }

  _onData(chunk) {
    this._rx += chunk;
    let i;
    while ((i = this._rx.indexOf('>')) !== -1) {
      const start = this._rx.indexOf('<');
      if (start === -1 || start > i) { this._rx = this._rx.slice(i + 1); continue; }
      const msg = this._rx.slice(start, i + 1);
      this._rx = this._rx.slice(i + 1);
      this.emit('log', '↩ ' + msg);
      this._parse(msg);
    }
  }

  _parse(msg) {
    // Eenvoudige parser voor terugkoppeling van de centrale.
    if (msg === '<p1>') { this.state.power = true; this.emit('state'); return; }
    if (msg === '<p0>') { this.state.power = false; this.emit('state'); return; }
    // Bezetmelding: <Q id> = sensor actief, <q id> = sensor vrij.
    let m;
    if ((m = /^<Q\s+(\d+)/.exec(msg))) { this.state.sensors[m[1]] = true; this.emit('state'); return; }
    if ((m = /^<q\s+(\d+)/.exec(msg))) { this.state.sensors[m[1]] = false; this.emit('state'); return; }
  }

  // ---- laag-niveau verzenden -------------------------------------------
  send(cmd) {
    this.emit('log', '→ ' + cmd);
    if (this.mode === 'sim') return;            // sim: niets te sturen
    if (this.sock && this.connected) {
      try { this.sock.write(cmd); } catch (e) { this.emit('log', '[tx-fout] ' + e.message); }
    }
  }

  // ---- hoog-niveau commando's ------------------------------------------
  _loco(addr) {
    if (!this.state.locos[addr]) this.state.locos[addr] = { speed: 0, dir: 1, fns: {} };
    return this.state.locos[addr];
  }

  power(on) {
    this.state.power = !!on;
    this.send(on ? '<1>' : '<0>');
    this.emit('state');
  }

  throttle(addr, speed, dir) {
    addr = parseInt(addr, 10);
    speed = Math.max(-1, Math.min(126, parseInt(speed, 10)));
    dir = dir ? 1 : 0;
    const l = this._loco(addr);
    l.speed = speed < 0 ? 0 : speed;
    l.dir = dir;
    this.send('<t ' + addr + ' ' + speed + ' ' + dir + '>');
    this.emit('state');
  }

  func(addr, fn, on) {
    addr = parseInt(addr, 10); fn = parseInt(fn, 10); on = on ? 1 : 0;
    const l = this._loco(addr);
    l.fns[fn] = !!on;
    this.send('<F ' + addr + ' ' + fn + ' ' + on + '>');
    this.emit('state');
  }

  estop() {
    // Noodstop: alle locs op −1 (snelle stop), power blijft aan zodat je weer kunt rijden.
    for (const addr of Object.keys(this.state.locos)) {
      this.state.locos[addr].speed = 0;
      this.send('<t ' + addr + ' -1 ' + this.state.locos[addr].dir + '>');
    }
    this.emit('log', '⛔ NOODSTOP');
    this.emit('state');
  }

  turnout(id, thrown) {
    id = parseInt(id, 10); thrown = thrown ? 1 : 0;
    this.state.turnouts[id] = !!thrown;
    this.send('<T ' + id + ' ' + thrown + '>');
    this.emit('state');
  }

  // CV-programmeren op het hoofdspoor (PoM): DCC-EX <w CAB CV VALUE>
  writeCV(addr, cv, value) {
    addr = parseInt(addr, 10); cv = parseInt(cv, 10); value = parseInt(value, 10);
    if (!addr || !cv || isNaN(value)) return;
    this.send('<w ' + addr + ' ' + cv + ' ' + value + '>');
    this.emit('log', '✎ CV' + cv + ' = ' + value + ' (loc ' + addr + ', PoM)');
  }
}

module.exports = { DccEx };
