/*
import Libinput from './libinput.js';
Libinput.instance.connect('device-init', () => {
  let pointers = [];
  Libinput.devices.forEach(dev => {
    if (dev.Capabilities.includes('pointer')) {
      pointers.push(dev);
    }
  })
  Libinput.addDebugInstance('pointers', pointers)
    .connect('new-line', e => print(e.lastLine))
});
*/

const { Service, Utils } = '../imports.js';
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';

class DebugInstance extends GObject.Object {
  static {
    GObject.registerClass({
      Signals: {
        'changed': {},
        'closed': {},
        'new-line': {},
      },
    }, this);
  }
  devices = [];
  name = '';
  lastLine = '';

  readOutput(stdout, stdin) {
    stdout.read_line_async(GLib.PRIORITY_LOW, null, (stream, result) => {
      try {
        const [line] = stream.read_line_finish_utf8(result);

        if (line !== null) {
          this.lastLine = line;
          this.emit('new-line');
          this.readOutput(stdout, stdin);
        }
      } catch (e) {
        logError(e);
      }
    });
  }

  getOutput(devs) {
    try {
      let args = [];
      devs.forEach(dev => {
        if (dev.Kernel) {
          args.push('--device');
          args.push(dev.Kernel);
        }
      });

      const proc = Gio.Subprocess.new(['libinput', 'debug-events', ...args],
        Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE);

      // Get the `stdin`and `stdout` pipes, wrapping `stdout` to make it easier to
      // read lines of text
      const stdinStream = proc.get_stdin_pipe();
      const stdoutStream = new Gio.DataInputStream({
        base_stream: proc.get_stdout_pipe(),
        close_base_stream: true,
      });

      // Start the loop
     this.readOutput(stdoutStream, stdinStream);
    } catch (e) {
      logError(e);
    }
  }

  constructor(name, devs) {
    super();

    this.devices = devs;
    this.name = name;

    this.getOutput(devs);
  }
}

class LibinputService extends Service {
  static {
    Service.register(this, {
      'device-init': ['boolean'],
      'instance-closed': ['string'],
      'instance-added': ['string'],
    });
  }

  debugInstances = new Map();
  devices = new Map();

  get devices() { return this._devices; }

  parseOutput(output) {
    let lines = output.split('\n');
    let device = null;

    lines.forEach(line => {
      let parts = line.split(':');

      if (parts[0] === 'Device') {
        device = {};
        this.devices.set(parts[1].trim(), device);
      }
      else if (device && parts[1]) {
        let key = parts[0].trim();
        let value = parts[1].trim();
        device[key] = value;
      }
    });
    this.emit('device-init', true);
  }

  constructor() {
    super();
    this.debugInstances = new Map();
    Utils.execAsync(['libinput', 'list-devices'])
      .then(out => this.parseOutput(out))
      .catch(console.error);
  }

  addDebugInstance(name, devs) {
    if (this.debugInstances.get(name))
      return;

    devs = Array(devs);
    if (devs.some(dev => dev.Capabilities && dev.Capabilities.includes('pointer'))) {
    }
    const debugInst = new DebugInstance(name, devs);

    debugInst.connect('closed', () => {
      this.debugInstances.delete(name);
      this.emit('instance-closed', name);
      this.emit('changed');
    });

    this.debugInstances.set(name, debugInst);
    this.emit('instance-added', name);
    return debugInst;
  }
}

export default class Libinput {
  static { Service.Libinput = this; }
  static instance = new LibinputService();

  static get devices() { return Libinput.instance.devices; }
  static get debugInstances() { return Libinput.instance.debugInstances; }

  static addDebugInstance(name, dev) {
    return Libinput.instance.addDebugInstance(name, dev);
  }
}