const { v4: uuid } = require('uuid');
const osc = require('osc');
const net = require('net');
const udp = require('dgram');
const { debounce } = require('lodash/function');

const PLUGINS = require('./plugins.js');
const VIEW = require('./view.js');
const SAVESLOTS = require('./saveSlots.js');

const devices = {};
module.exports.all = devices;

registerDevice = function (newDevice) {
  console.log(PLUGINS.all);

  if (PLUGINS.all[newDevice.type] == undefined) {
    console.error(`Plugin for device ${newDevice.type} does not exist.`);
    return true;
  }

  const initElements = document.getElementsByClassName('init');
  for (let i = 0; i < initElements.length; i++) {
    initElements[i].style.display = 'none';
  }

  // only register device if it hasn't already been added
  if (newDevice.addresses.length > 0) {
    for (let i in devices) {
      if (
        devices[i].type == newDevice.type &&
        JSON.stringify(devices[i].addresses) ==
          JSON.stringify(newDevice.addresses)
      ) {
        // This device has already been added
        infoUpdate(devices[i], 'status', 'ok');
        return false;
      }
    }
  }

  // console.log("Registered new "+newDevice.type)

  const id = newDevice.id || uuid();
  devices[id] = {
    id,
    status: 'new',
    type: newDevice.type,
    displayName: newDevice.displayName,
    defaultName: newDevice.defaultName,
    port: newDevice.port,
    addresses: newDevice.addresses,
    data: {},
    pinIndex: false,
    lastDrawn: 0,
    lastHeartbeat: 0,
    heartbeatInterval: PLUGINS.all[newDevice.type].heartbeatInterval,
    draw: debounce(
      function () {
        VIEW.draw(this);
      },
      30,
      { leading: true, trailing: true }
    ),
  };

  VIEW.addDeviceToList(devices[id]);
  initDeviceConnection(id);
};
module.exports.registerDevice = registerDevice;

initDeviceConnection = function (id) {
  const device = devices[id];

  infoUpdate(device, 'status', 'new');

  if (device.port == undefined || device.addresses.length == 0) {
    return true;
  }
  try {
    // mostly only useful for UDP
    device.connection.close();
  } catch (err) {}

  const { type } = devices[id];
  const plugins = PLUGINS.all;

  if (plugins[type].connectionType == 'osc') {
    device.connection = new osc.TCPSocketPort({
      address: device.addresses[0],
      port: device.port,
    });
    device.connection.open();
    device.connection.on('error', (error) => {
      // console.error(error)
      device.connection.close();
    });
    device.connection.on('ready', () => {
      plugins[type].ready(device);
      if (Object.keys(devices).length == 1) {
        VIEW.switchDevice(device.id);
      }
    });
    device.connection.on('message', (message) => {
      // log("OSC IN", message.address);
      plugins[type].data(device, message);
      device.lastMessage = Date.now();
    });
    device.send = function (address, args) {
      device.connection.send({ address: address, args: args });
    };
    device.plugin = plugins[type];
  } else if (plugins[type].connectionType == 'TCPsocket') {
    device.connection = new net.Socket();

    device.connection.connect(
      { port: device.port, host: device.addresses[0] },
      () => {}
    );

    device.connection.on('error', (error) => {
      // console.error(error)
    });
    device.connection.on('ready', () => {
      plugins[type].ready(device);
      if (Object.keys(devices).length == 1) {
        VIEW.switchDevice(device.id);
      }
    });
    device.connection.on('data', (message) => {
      // log("SOCK IN", message);
      plugins[type].data(device, message);
      device.lastMessage = Date.now();
      infoUpdate(device, 'status', 'ok');
    });
    device.send = function (data) {
      // log("SOCK OUT", data);
      device.connection.write(data);
    };
  } else if (plugins[type].connectionType == 'UDPsocket') {
    device.connection = udp.createSocket('udp4');

    device.connection.bind(() => {
      plugins[type].ready(device);

      device.connection.on('message', (msg, info) => {
        plugins[type].data(device, msg);
        infoUpdate(device, 'status', 'ok');
      });
    });

    device.send = function (data) {
      device.connection.send(data, device.port, device.addresses[0], (err) => {
        // console.log(err);
      });
    };
  }
};
module.exports.initDeviceConnection = initDeviceConnection;

module.exports.deleteActive = function () {
  const device = VIEW.getActiveDevice();
  const choice = confirm(
    `Are you sure you want to delete ${device.type} device "${
      device.displayName || device.defaultName
    }"?`
  );

  if (choice) {
    VIEW.removeDeviceFromList(device);
    delete devices[device.id];
    SAVESLOTS.deleteFromSlots(device);
    SAVESLOTS.saveAll();
    SAVESLOTS.reloadActiveSlot();
  }
};

module.exports.changeActiveType = function (newType) {
  const device = VIEW.getActiveDevice();
  device.type = newType;

  initDeviceConnection(device.id);
  VIEW.draw(device);
  // SAVESLOTS.saveAll();
};

module.exports.changeActiveIP = function (newIP) {
  const device = VIEW.getActiveDevice();
  device.addresses[0] = newIP;
  initDeviceConnection(device.id);
  VIEW.draw(device);
  SAVESLOTS.saveAll();
};

module.exports.changeActivePort = function (newPort) {
  const device = VIEW.getActiveDevice();
  device.port = newPort;

  initDeviceConnection(device.id);
  VIEW.draw(device);
  SAVESLOTS.saveAll();
};

module.exports.changeActiveName = function (newName) {
  const device = VIEW.getActiveDevice();
  device.displayName = newName;
  infoUpdate(device, 'displayName', newName);
  VIEW.draw(device);
  SAVESLOTS.saveAll();
};
module.exports.changeActivePinIndex = function (newPin) {
  const device = VIEW.getActiveDevice();
  device.pinIndex = newPin;
  VIEW.draw(device);
  SAVESLOTS.saveAll();
};
module.exports.changePinIndex = function (device, newPin) {
  device.pinIndex = newPin;
  // SAVESLOTS.saveAll();
};
module.exports.refreshActive = function () {
  const device = VIEW.getActiveDevice();
  if (device == undefined) {
    return true;
  }
  initDeviceConnection(device.id);
  VIEW.draw(device);
};

infoUpdate = function (device, param, value) {
  if (param == 'addresses') {
    device.addresses = value.replace(/\s+/g, '').split(',');
  } else {
    device[param] = value;
  }
  VIEW.addDeviceToList(device);
};
module.exports.infoUpdate = infoUpdate;

function heartbeat() {
  for (let i in devices) {
    const device = devices[i];
    if (Date.now() >= device.lastHeartbeat + device.heartbeatInterval) {
      if (device.status == 'broken') {
        initDeviceConnection(i);
      } else if (Date.now() - device.lastMessage > 10000) {
        infoUpdate(device, 'status', 'broken');
      } else {
        if (device.port != undefined && device.addresses.length > 0) {
          PLUGINS.all[device.type].heartbeat(device);
        } else {
          // console.error("Invalid IP/Port on device "+device.name)
        }
      }
      device.lastHeartbeat = Date.now();
    }
  }
}
setInterval(heartbeat, 100);
