#!/usr/bin/env node
/**
 * Passive Bistatic Radar — Multi-Frequency Mesh Application
 *
 * Uses neighbor WiFi APs as illuminators of opportunity to build range-Doppler
 * maps for moving target detection. Each neighbor AP is an uncontrolled
 * transmitter whose signals pass through the room and are modulated by people
 * and objects. The ESP32 nodes capture CSI from these transmissions across
 * 6 channels.
 *
 * This is the same principle used by military passive radar (Kolchuga, VERA-NG)
 * but with WiFi APs instead of broadcast towers.
 *
 * Requires multi-frequency mesh scanning (ADR-073): 2 ESP32 nodes hopping
 * across channels 1, 3, 5, 6, 9, 11.
 *
 * Usage:
 *   node scripts/passive-radar.js
 *   node scripts/passive-radar.js --port 5006 --duration 60
 *   node scripts/passive-radar.js --replay data/recordings/overnight-1775217646.csi.jsonl
 *   node scripts/passive-radar.js --node-distance 3.0
 *
 * ADR: docs/adr/ADR-078-multifreq-mesh-applications.md
 */

'use strict';

const dgram = require('dgram');
const fs = require('fs');
const readline = require('readline');
const { parseArgs } = require('util');

// ---------------------------------------------------------------------------
// CLI
// ---------------------------------------------------------------------------
const { values: args } = parseArgs({
  options: {
    port:          { type: 'string', short: 'p', default: '5006' },
    duration:      { type: 'string', short: 'd' },
    replay:        { type: 'string', short: 'r' },
    interval:      { type: 'string', short: 'i', default: '3000' },
    json:          { type: 'boolean', default: false },
    'node-distance': { type: 'string', default: '3.0' },
    'doppler-bins': { type: 'string', default: '16' },
    'range-bins':   { type: 'string', default: '12' },
  },
  strict: true,
});

const PORT = parseInt(args.port, 10);
const DURATION_MS = args.duration ? parseInt(args.duration, 10) * 1000 : null;
const INTERVAL_MS = parseInt(args.interval, 10);
const JSON_OUTPUT = args.json;
const NODE_DISTANCE = parseFloat(args['node-distance']);
const DOPPLER_BINS = parseInt(args['doppler-bins'], 10);
const RANGE_BINS = parseInt(args['range-bins'], 10);

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const CSI_MAGIC = 0xC5110001;
const HEADER_SIZE = 20;
const SPEED_OF_LIGHT = 3e8; // m/s

const CHANNEL_FREQ = {};
for (let ch = 1; ch <= 13; ch++) CHANNEL_FREQ[ch] = 2412 + (ch - 1) * 5;

const NODE1_CHANNELS = [1, 6, 11];
const NODE2_CHANNELS = [3, 5, 9];

// Neighbor APs as illuminators with estimated positions
const ILLUMINATORS = [
  { ssid: 'ruv.net',       channel: 5,  signal: 100, pos: [1.5, 3.5], freq: 2432e6 },
  { ssid: 'Cohen-Guest',   channel: 5,  signal: 100, pos: [2.0, 3.8], freq: 2432e6 },
  { ssid: 'COGECO-21B20',  channel: 11, signal: 100, pos: [4.0, 2.0], freq: 2462e6 },
  { ssid: 'HP M255',       channel: 5,  signal: 94,  pos: [0.5, 1.5], freq: 2432e6 },
  { ssid: 'conclusion',    channel: 3,  signal: 44,  pos: [3.5, 3.0], freq: 2422e6 },
  { ssid: 'NETGEAR72',     channel: 9,  signal: 42,  pos: [4.5, 1.0], freq: 2452e6 },
  { ssid: 'COGECO-4321',   channel: 11, signal: 30,  pos: [4.0, 3.5], freq: 2462e6 },
  { ssid: 'Innanen',       channel: 6,  signal: 19,  pos: [1.0, 4.0], freq: 2437e6 },
];

const NODE_POS = {
  1: [0, 2.0],
  2: [NODE_DISTANCE, 2.0],
};

// Range-Doppler plot characters
const RD_CHARS = [' ', '\u2581', '\u2582', '\u2583', '\u2584', '\u2585', '\u2586', '\u2587', '\u2588'];

// ---------------------------------------------------------------------------
// Per-illuminator CSI history for Doppler processing
// ---------------------------------------------------------------------------
class IlluminatorTracker {
  constructor(illuminator, nodeId) {
    this.illuminator = illuminator;
    this.nodeId = nodeId;
    this.ssid = illuminator.ssid;
    this.channel = illuminator.channel;
    this.freqHz = illuminator.freq;
    this.wavelength = SPEED_OF_LIGHT / this.freqHz;

    // Phase history per subcarrier (ring buffer)
    this.maxHistory = 64;
    this.phaseHistory = []; // array of { timestamp, phases: Float64Array }
    this.amplitudeHistory = [];

    // Range-Doppler map
    this.rangeDoppler = null;
    this.lastUpdateMs = 0;
  }

  /** Ingest a new CSI frame */
  ingest(timestamp, amplitudes, phases) {
    this.phaseHistory.push({ timestamp, phases: new Float64Array(phases) });
    this.amplitudeHistory.push({ timestamp, amplitudes: new Float64Array(amplitudes) });

    if (this.phaseHistory.length > this.maxHistory) {
      this.phaseHistory.shift();
      this.amplitudeHistory.shift();
    }
  }

  /**
   * Compute range-Doppler map from CSI phase history.
   *
   * Doppler: phase change rate across consecutive frames for each subcarrier.
   *   fd = d(phase)/dt / (2*pi) -> velocity = fd * wavelength / 2
   *
   * Range: phase slope across subcarriers within each frame.
   *   tau = d(phase)/d(subcarrier_freq) / (2*pi) -> range = c * tau
   */
  computeRangeDoppler(dopplerBins, rangeBins) {
    const n = this.phaseHistory.length;
    if (n < 4) return null;

    const nSub = this.phaseHistory[0].phases.length;
    if (nSub < 4) return null;

    // Initialize range-Doppler map
    const rd = new Float64Array(rangeBins * dopplerBins);

    // Doppler processing: compute phase change rate per subcarrier
    const dopplerPerSub = new Float64Array(nSub);
    const rangePerFrame = new Float64Array(n);

    for (let sc = 0; sc < nSub; sc++) {
      // Linear regression of phase vs time for this subcarrier
      let sumT = 0, sumP = 0, sumTT = 0, sumTP = 0;
      let prevPhase = this.phaseHistory[0].phases[sc];

      for (let f = 0; f < n; f++) {
        const t = this.phaseHistory[f].timestamp;
        // Unwrap phase
        let phase = this.phaseHistory[f].phases[sc];
        while (phase - prevPhase > Math.PI) phase -= 2 * Math.PI;
        while (phase - prevPhase < -Math.PI) phase += 2 * Math.PI;
        prevPhase = phase;

        sumT += t;
        sumP += phase;
        sumTT += t * t;
        sumTP += t * phase;
      }

      const meanT = sumT / n;
      const denom = sumTT - sumT * meanT;
      if (Math.abs(denom) > 1e-10) {
        const slope = (sumTP - sumT * (sumP / n)) / denom;
        // Doppler frequency (Hz) = slope / (2*pi)
        dopplerPerSub[sc] = slope / (2 * Math.PI);
      }
    }

    // Range processing: phase slope across subcarriers per frame
    const subcarrierSpacing = 312.5e3; // OFDM subcarrier spacing: 312.5 kHz

    for (let f = 0; f < n; f++) {
      const phases = this.phaseHistory[f].phases;
      // Linear regression of phase vs subcarrier index
      let sumI = 0, sumP = 0, sumII = 0, sumIP = 0;
      let prevPhase = phases[0];

      for (let sc = 0; sc < nSub; sc++) {
        let phase = phases[sc];
        // Unwrap
        while (phase - prevPhase > Math.PI) phase -= 2 * Math.PI;
        while (phase - prevPhase < -Math.PI) phase += 2 * Math.PI;
        prevPhase = phase;

        sumI += sc;
        sumP += phase;
        sumII += sc * sc;
        sumIP += sc * phase;
      }

      const meanI = sumI / nSub;
      const denom = sumII - sumI * meanI;
      if (Math.abs(denom) > 1e-10) {
        const slope = (sumIP - sumI * (sumP / nSub)) / denom;
        // Time delay (seconds) = slope / (2*pi * subcarrier_spacing)
        const tau = Math.abs(slope) / (2 * Math.PI * subcarrierSpacing);
        rangePerFrame[f] = SPEED_OF_LIGHT * tau / 2; // bistatic range / 2
      }
    }

    // Map to bins
    const maxDoppler = 5.0;  // Hz (corresponds to ~0.3 m/s at 2.4 GHz)
    const maxRange = 10.0;   // meters

    for (let sc = 0; sc < nSub; sc++) {
      const doppler = dopplerPerSub[sc];
      const dBin = Math.floor(((doppler + maxDoppler) / (2 * maxDoppler)) * (dopplerBins - 1));
      if (dBin < 0 || dBin >= dopplerBins) continue;

      // Use mean amplitude as intensity
      let meanAmp = 0;
      for (let f = 0; f < n; f++) {
        meanAmp += this.amplitudeHistory[f].amplitudes[sc];
      }
      meanAmp /= n;

      // Average range across frames for this subcarrier's range bin
      let meanRange = 0;
      for (let f = 0; f < n; f++) meanRange += rangePerFrame[f];
      meanRange /= n;

      const rBin = Math.floor((meanRange / maxRange) * (rangeBins - 1));
      if (rBin < 0 || rBin >= rangeBins) continue;

      rd[rBin * dopplerBins + dBin] += meanAmp;
    }

    this.rangeDoppler = {
      map: rd,
      dopplerBins,
      rangeBins,
      maxDoppler,
      maxRange,
      nFrames: n,
    };

    return this.rangeDoppler;
  }

  /** Get dominant Doppler (strongest moving target) */
  getDominantDoppler() {
    if (!this.rangeDoppler) return null;
    const { map, dopplerBins, rangeBins, maxDoppler } = this.rangeDoppler;

    let maxVal = 0, maxD = 0, maxR = 0;
    for (let r = 0; r < rangeBins; r++) {
      for (let d = 0; d < dopplerBins; d++) {
        const val = map[r * dopplerBins + d];
        if (val > maxVal) {
          maxVal = val;
          maxD = d;
          maxR = r;
        }
      }
    }

    if (maxVal < 0.01) return null;

    const doppler = (maxD / (dopplerBins - 1)) * 2 * maxDoppler - maxDoppler;
    const velocity = doppler * this.wavelength / 2;
    const range = (maxR / (rangeBins - 1)) * this.rangeDoppler.maxRange;

    return { doppler: doppler.toFixed(2), velocity: velocity.toFixed(3), range: range.toFixed(1), intensity: maxVal.toFixed(1) };
  }
}

// ---------------------------------------------------------------------------
// Multi-static fusion
// ---------------------------------------------------------------------------
class MultiStaticFusion {
  constructor() {
    this.trackers = new Map(); // key: `${ssid}-node${nodeId}` -> IlluminatorTracker
  }

  getOrCreateTracker(illuminator, nodeId) {
    const key = `${illuminator.ssid}-node${nodeId}`;
    if (!this.trackers.has(key)) {
      this.trackers.set(key, new IlluminatorTracker(illuminator, nodeId));
    }
    return this.trackers.get(key);
  }

  ingestFrame(nodeId, channel, timestamp, amplitudes, phases) {
    // Find illuminators on this channel
    for (const il of ILLUMINATORS) {
      if (il.channel === channel) {
        const tracker = this.getOrCreateTracker(il, nodeId);
        tracker.ingest(timestamp, amplitudes, phases);
      }
    }
  }

  /** Compute all range-Doppler maps */
  computeAll(dopplerBins, rangeBins) {
    const results = [];
    for (const [key, tracker] of this.trackers) {
      const rd = tracker.computeRangeDoppler(dopplerBins, rangeBins);
      if (rd) {
        results.push({ key, tracker, rd });
      }
    }
    return results;
  }

  /**
   * Fuse multi-static detections.
   * Each illuminator provides a range measurement to the target.
   * The target lies on an ellipse with foci at TX (illuminator) and RX (ESP32 node).
   * Intersection of multiple ellipses gives position.
   */
  fuseDetections() {
    const detections = [];
    for (const [key, tracker] of this.trackers) {
      const dom = tracker.getDominantDoppler();
      if (dom && parseFloat(dom.intensity) > 1.0) {
        detections.push({
          key,
          ssid: tracker.ssid,
          channel: tracker.channel,
          nodeId: tracker.nodeId,
          txPos: tracker.illuminator.pos,
          rxPos: NODE_POS[tracker.nodeId],
          bistaticRange: parseFloat(dom.range),
          velocity: parseFloat(dom.velocity),
          intensity: parseFloat(dom.intensity),
        });
      }
    }

    if (detections.length < 2) {
      return { detections, fusedPosition: null };
    }

    // Simple centroid-based fusion:
    // For each detection, compute the midpoint of the TX-RX baseline
    // weighted by intensity. This is a rough approximation.
    // (Full ellipse intersection requires nonlinear optimization.)
    let sumX = 0, sumY = 0, sumW = 0;
    for (const det of detections) {
      // Midpoint between TX and RX, offset by bistatic range
      const mx = (det.txPos[0] + det.rxPos[0]) / 2;
      const my = (det.txPos[1] + det.rxPos[1]) / 2;
      const w = det.intensity;
      sumX += mx * w;
      sumY += my * w;
      sumW += w;
    }

    const fusedPosition = sumW > 0
      ? { x: (sumX / sumW).toFixed(2), y: (sumY / sumW).toFixed(2), confidence: Math.min(1, detections.length / 4).toFixed(2) }
      : null;

    return { detections, fusedPosition };
  }
}

// ---------------------------------------------------------------------------
// CSI parsing
// ---------------------------------------------------------------------------
function parseIqHex(iqHex, nSubcarriers) {
  const bytes = Buffer.from(iqHex, 'hex');
  const amplitudes = new Float64Array(nSubcarriers);
  const phases = new Float64Array(nSubcarriers);

  for (let sc = 0; sc < nSubcarriers; sc++) {
    const offset = 2 + sc * 2;
    if (offset + 1 >= bytes.length) break;
    let I = bytes[offset];
    let Q = bytes[offset + 1];
    if (I > 127) I -= 256;
    if (Q > 127) Q -= 256;
    amplitudes[sc] = Math.sqrt(I * I + Q * Q);
    phases[sc] = Math.atan2(Q, I);
  }

  return { amplitudes, phases };
}

function parseCSIFrame(buf) {
  if (buf.length < HEADER_SIZE) return null;
  const magic = buf.readUInt32LE(0);
  if (magic !== CSI_MAGIC) return null;

  const nodeId = buf.readUInt8(4);
  const nSubcarriers = buf.readUInt16LE(6);
  const freqMhz = buf.readUInt32LE(8);
  const rssi = buf.readInt8(16);

  const amplitudes = new Float64Array(nSubcarriers);
  const phases = new Float64Array(nSubcarriers);

  for (let sc = 0; sc < nSubcarriers; sc++) {
    const offset = HEADER_SIZE + sc * 2;
    if (offset + 1 >= buf.length) break;
    const I = buf.readInt8(offset);
    const Q = buf.readInt8(offset + 1);
    amplitudes[sc] = Math.sqrt(I * I + Q * Q);
    phases[sc] = Math.atan2(Q, I);
  }

  let channel = 0;
  if (freqMhz >= 2412 && freqMhz <= 2484) {
    channel = freqMhz === 2484 ? 14 : Math.round((freqMhz - 2412) / 5) + 1;
  }

  return { nodeId, nSubcarriers, freqMhz, rssi, amplitudes, phases, channel };
}

// Channel assignment for legacy JSONL
const nodeChannelIdx = { 1: 0, 2: 0 };
function assignChannel(nodeId) {
  const channels = nodeId === 1 ? NODE1_CHANNELS : NODE2_CHANNELS;
  const ch = channels[nodeChannelIdx[nodeId] % channels.length];
  nodeChannelIdx[nodeId]++;
  return ch;
}

// ---------------------------------------------------------------------------
// Visualization
// ---------------------------------------------------------------------------
function renderRangeDoppler(tracker) {
  const rd = tracker.rangeDoppler;
  if (!rd) return `  ${tracker.ssid} (ch${tracker.channel}): insufficient data`;

  const { map, dopplerBins, rangeBins, maxDoppler, maxRange, nFrames } = rd;
  const lines = [];

  lines.push(`  ${tracker.ssid} (ch${tracker.channel}, node${tracker.nodeId}) | ${nFrames} frames`);

  // Find max for normalization
  let maxVal = 0;
  for (let i = 0; i < map.length; i++) {
    if (map[i] > maxVal) maxVal = map[i];
  }
  if (maxVal === 0) maxVal = 1;

  // Render range (y-axis) vs Doppler (x-axis)
  for (let r = rangeBins - 1; r >= 0; r--) {
    const range = (r / (rangeBins - 1)) * maxRange;
    let row = `  ${range.toFixed(1).padStart(5)}m |`;
    for (let d = 0; d < dopplerBins; d++) {
      const val = map[r * dopplerBins + d] / maxVal;
      const level = Math.floor(val * 8.99);
      row += RD_CHARS[Math.max(0, Math.min(8, level))];
    }
    row += '|';
    lines.push(row);
  }

  // X-axis (Doppler)
  lines.push('  ' + ' '.repeat(7) + '+' + '-'.repeat(dopplerBins) + '+');
  const dLabel = `  ${' '.repeat(7)}-${maxDoppler}Hz${' '.repeat(Math.max(0, dopplerBins - 10))}+${maxDoppler}Hz`;
  lines.push(dLabel);

  // Dominant detection
  const dom = tracker.getDominantDoppler();
  if (dom) {
    lines.push(`  Peak: range=${dom.range}m  doppler=${dom.doppler}Hz  vel=${dom.velocity}m/s`);
  }

  return lines.join('\n');
}

function renderFusion(fusion) {
  const { detections, fusedPosition } = fusion;
  const lines = [];

  lines.push('');
  lines.push('  MULTI-STATIC FUSION');
  lines.push('  ' + '='.repeat(50));

  if (detections.length === 0) {
    lines.push('  No detections above threshold');
    return lines.join('\n');
  }

  lines.push(`  Active bistatic pairs: ${detections.length}`);
  for (const det of detections) {
    lines.push(`    ${det.ssid.padEnd(16)} ch${det.channel} -> node${det.nodeId} | ` +
      `range=${det.bistaticRange.toFixed(1)}m vel=${det.velocity.toFixed(3)}m/s`);
  }

  if (fusedPosition) {
    lines.push(`  Fused position: (${fusedPosition.x}, ${fusedPosition.y}) m  confidence=${fusedPosition.confidence}`);
  } else {
    lines.push('  Insufficient detections for position fusion (need 2+)');
  }

  return lines.join('\n');
}

// ---------------------------------------------------------------------------
// Global state
// ---------------------------------------------------------------------------
const multiStatic = new MultiStaticFusion();
let lastDisplayMs = 0;

function processFrame(nodeId, channel, timestamp, amplitudes, phases) {
  multiStatic.ingestFrame(nodeId, channel, timestamp, amplitudes, phases);
}

function displayUpdate() {
  const results = multiStatic.computeAll(DOPPLER_BINS, RANGE_BINS);
  const fusion = multiStatic.fuseDetections();

  if (JSON_OUTPUT) {
    const output = {
      timestamp: Date.now() / 1000,
      bistaticPairs: results.length,
      detections: fusion.detections.map(d => ({
        ssid: d.ssid, channel: d.channel, nodeId: d.nodeId,
        bistaticRange: d.bistaticRange, velocity: d.velocity,
      })),
      fusedPosition: fusion.fusedPosition,
    };
    console.log(JSON.stringify(output));
  } else {
    process.stdout.write('\x1B[2J\x1B[H');
    console.log('  PASSIVE BISTATIC RADAR');
    console.log('  Using neighbor WiFi APs as illuminators of opportunity');
    console.log('  ' + '-'.repeat(55));
    console.log('');

    // Show top 3 trackers by signal strength
    const sorted = results.sort((a, b) => b.tracker.illuminator.signal - a.tracker.illuminator.signal);
    for (const r of sorted.slice(0, 3)) {
      console.log(renderRangeDoppler(r.tracker));
      console.log('');
    }

    console.log(renderFusion(fusion));
    console.log('');
    console.log(`  Total bistatic pairs: ${multiStatic.trackers.size}`);
    console.log('  Press Ctrl+C to exit');
  }
}

// ---------------------------------------------------------------------------
// Live mode
// ---------------------------------------------------------------------------
function startLive() {
  const sock = dgram.createSocket('udp4');

  sock.on('message', (buf, rinfo) => {
    if (buf.length < 4) return;
    const magic = buf.readUInt32LE(0);
    if (magic !== CSI_MAGIC) return;

    const frame = parseCSIFrame(buf);
    if (!frame) return;

    processFrame(frame.nodeId, frame.channel, Date.now() / 1000, frame.amplitudes, frame.phases);

    const now = Date.now();
    if (now - lastDisplayMs >= INTERVAL_MS) {
      displayUpdate();
      lastDisplayMs = now;
    }
  });

  sock.bind(PORT, () => {
    if (!JSON_OUTPUT) {
      console.log(`Passive Bistatic Radar listening on UDP port ${PORT}`);
      console.log(`Illuminators: ${ILLUMINATORS.length} neighbor APs`);
      console.log(`Node distance: ${NODE_DISTANCE} m`);
      console.log('Waiting for CSI frames...');
    }
  });

  if (DURATION_MS) {
    setTimeout(() => {
      displayUpdate();
      sock.close();
      process.exit(0);
    }, DURATION_MS);
  }
}

// ---------------------------------------------------------------------------
// Replay mode
// ---------------------------------------------------------------------------
async function startReplay(filePath) {
  if (!fs.existsSync(filePath)) {
    console.error(`File not found: ${filePath}`);
    process.exit(1);
  }

  const rl = readline.createInterface({
    input: fs.createReadStream(filePath),
    crlfDelay: Infinity,
  });

  let frameCount = 0;
  let lastAnalysisTs = 0;
  let windowCount = 0;

  for await (const line of rl) {
    if (!line.trim()) continue;

    let record;
    try { record = JSON.parse(line); } catch { continue; }
    if (record.type !== 'raw_csi' || !record.iq_hex) continue;

    const { amplitudes, phases } = parseIqHex(record.iq_hex, record.subcarriers || 64);
    const channel = record.channel || assignChannel(record.node_id);

    processFrame(record.node_id, channel, record.timestamp, amplitudes, phases);
    frameCount++;

    const tsMs = record.timestamp * 1000;
    if (lastAnalysisTs === 0) lastAnalysisTs = tsMs;

    if (tsMs - lastAnalysisTs >= INTERVAL_MS) {
      windowCount++;
      const results = multiStatic.computeAll(DOPPLER_BINS, RANGE_BINS);
      const fusion = multiStatic.fuseDetections();

      if (JSON_OUTPUT) {
        console.log(JSON.stringify({
          window: windowCount,
          timestamp: record.timestamp,
          frames: frameCount,
          detections: fusion.detections.length,
          fusedPosition: fusion.fusedPosition,
        }));
      } else {
        console.log(`\n${'='.repeat(60)}`);
        console.log(`Window ${windowCount} | t=${record.timestamp.toFixed(1)}s | frames=${frameCount}`);
        console.log('='.repeat(60));

        const sorted = results.sort((a, b) => b.tracker.illuminator.signal - a.tracker.illuminator.signal);
        for (const r of sorted.slice(0, 3)) {
          console.log(renderRangeDoppler(r.tracker));
          console.log('');
        }

        console.log(renderFusion(fusion));
      }
      lastAnalysisTs = tsMs;
    }
  }

  // Final
  if (!JSON_OUTPUT) {
    const results = multiStatic.computeAll(DOPPLER_BINS, RANGE_BINS);
    const fusion = multiStatic.fuseDetections();

    console.log(`\n${'='.repeat(60)}`);
    console.log('FINAL PASSIVE RADAR SUMMARY');
    console.log('='.repeat(60));

    for (const [key, tracker] of multiStatic.trackers) {
      const dom = tracker.getDominantDoppler();
      const domStr = dom ? `range=${dom.range}m vel=${dom.velocity}m/s` : 'no detection';
      console.log(`  ${key.padEnd(30)} ${domStr}`);
    }

    console.log(renderFusion(fusion));
    console.log(`\nProcessed ${frameCount} frames in ${windowCount} windows`);
    console.log(`Bistatic pairs tracked: ${multiStatic.trackers.size}`);
  }
}

// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------
if (args.replay) {
  startReplay(args.replay);
} else {
  startLive();
}
