/* eslint-disable new-cap */
/* eslint-disable prettier/prettier */
/* eslint-disable prefer-destructuring */
/* eslint-disable no-bitwise */
/* eslint-disable compat/compat */
// Below is to change the name of websocket import (import {client} as Client)
import loginLocal, {
  printTicket,
  onlineTicketById,
  postBadge,
  runScenario,
  openPassage,
  controlPassage,
  onlineTicketByIdChangeState
} from "./Modules/Acs01Local.mjs";
import loginRemote, {
  getSites,
  getCounters,
  getCounterById,
  setCounterById,
  logoutRemote,
} from "./Modules/Acs01Remote.mjs";

// const { client: Client } = require("websocket");
const { ipcRenderer } = require("electron");

// Debug
const debug = true;

// Global vars for fetching & websocket
let mainWindowId;

let localUrl;
let cookie;
let username;
let password;
let defaultReaderId;

// Not yet used
let defaultPrinterId;
let defaultPrinterBackupId;
let defaultPrinterButtonId;
let defaultScenarioConfigured;
let defaultScenarioId;
let applicationMode;

// Websocket:
const createHex = 0x01;
const readHex = 0x02;
const updateHex = 0x03;
const deleteHex = 0x04;
const resultRequest = 0x0ff;
const createHandlers = [];
const readHandlers = [];
const deleteHandlers = [];
const updateHandlers = [];

let latestConnect;
let wsConnection;
let websocketIsSetAtLeastOnce = false;
let wsConnectionNumberSend = 0;
let wsConnectionNumberReceived = -1;
let wsSetupFromInit = false;
let pingPongStatus = false;
let pingPongFails = 0;

// Fetching process
const printTicketProcess = async () => {
  if (defaultScenarioConfigured) {
    try {
      const checkScenario = await runScenario(localUrl, defaultScenarioId);
      ipcRenderer.postMessage(
        "interWindowRelay",
        [
          "workerWindowNetwork->mainWindow-result-runFetchRunScenario",
          checkScenario,
        ],
        []
      );

      if (checkScenario?.result === true) {
        const printTicketResult = await printTicket(localUrl, defaultPrinterId);
        ipcRenderer.postMessage(
          "interWindowRelay",
          [
            "workerWindowNetwork->mainWindow-result-runFetchPrintTicket",
            printTicketResult,
          ],
          []
        );
        if (debug)
          console.log(
            "<FETCH OK - Local PrintTicket && RunScenario> ",
            printTicketResult
          );
      }
      if (debug)
        console.log(
          "<FETCH OK - Local PrintTicket && RunScenario> ",
          checkScenario
        );
    } catch (error) {
      if (debug)
        console.log(
          "!! <FETCH FAIL - Local PrintTicket && RunScenario> ",
          error
        );
    }
  } else {
    try {
      const printTicketResult = await printTicket(localUrl, defaultPrinterId);
      ipcRenderer.postMessage(
        "interWindowRelay",
        [
          "workerWindowNetwork->mainWindow-result-runFetchPrintTicket",
          printTicketResult,
        ],
        []
      );
      if (debug)
        console.log("<FETCH OK - Local PrintTicket> ", printTicketResult);
    } catch (error) {
      if (debug) console.log("!! <FETCH FAIL - Local PrintTicket> ", error);
    }
  }
};

// Websocket
// First we have some helper functions
const registerCreateHandler = (topic, status, callback) => {
  createHandlers.push({ method: createHex, topic, status, callback });
};

const registerReadHandler = (topic, status, callback) => {
  readHandlers.push({ method: readHex, topic, status, callback });
};

const registerUpdateHandler = (topic, status, callback) => {
  updateHandlers.push({ method: updateHex, topic, status, callback });
};

const registerDeleteHandler = (topic, status, callback) => {
  deleteHandlers.push({ method: deleteHex, topic, status, callback });
};

const ioLoopChanged = async (e) => {
  // We cannot send to mainWindow directly so we need to use the relay from node.js main
  ipcRenderer.postMessage(
    "interWindowRelay",
    ["workerWindowNetwork->mainWindow-ws-ioLoopChanged", e],
    []
  );
};

const printerChanged = async (e) => {
  ipcRenderer.postMessage(
    "interWindowRelay",
    ["workerWindowNetwork->mainWindow-ws-printerChanged", e],
    []
  );
};

const passageChanged = async (e) => {
  ipcRenderer.postMessage(
    "interWindowRelay",
    ["workerWindowNetwork->mainWindow-ws-passageChanged", e],
    []
  );
};

const counterChanged = async (e) => {
  ipcRenderer.postMessage(
    "interWindowRelay",
    ["workerWindowNetwork->mainWindow-ws-counterChanged", e],
    []
  );
};

const ioOutputAction = async (e) => {
  ipcRenderer.postMessage(
    "interWindowRelay",
    ["workerWindowNetwork->mainWindow-ws-ioOutputAction", e],
    []
  );
};

registerUpdateHandler("/v1/io_connection/state", resultRequest, ioLoopChanged);

registerCreateHandler("/v1/printer/event", resultRequest, printerChanged);

const ticketScanned = async (e) => {
  ipcRenderer.postMessage(
    'interWindowRelay',
    ['workerWindowNetwork->mainWindow-ws-ticketScanned', e],
    []
  );
};

registerUpdateHandler('/v1/ticket/scan', resultRequest, ticketScanned);

registerUpdateHandler("/v1/passage/action", resultRequest, passageChanged);

registerUpdateHandler("/v1/counter", resultRequest, counterChanged);

registerUpdateHandler(
  "/v1/io_connection/action",
  resultRequest,
  ioOutputAction
);

const parse = (payload) => {
  try {
    return JSON.parse(payload);
  } catch {
    return payload;
  }
};

const shift = (buffer, pos) => {
  let length = buffer[pos];
  length <<= 8;
  length |= buffer[pos + 1];

  return length;
};

const decodeData = (data) => {
  let position = 0;
  const buffer = new Uint8Array(data);

  // we know by heart that the start position of the topic start position is the 4th spot in the array since the first one is
  // defined for the method, the second one for the responsecode and third and fourth spot in the array are ment for the topic length
  const getTopic = (start = 4) => {
    const topicLength = shift(buffer, 2);
    position = start + topicLength;
    return String.fromCharCode(...buffer.slice(start, position));
  };

  const getPayload = () => {
    const dataBuf = buffer.slice(position, buffer.length).buffer;
    const result = new TextDecoder("utf-8").decode(dataBuf);
    return result;
  };

  // the first byte is the method
  const [resultMethod, resultStatus] = buffer;
  const resultTopic = getTopic();
  const resultPayload = getPayload();

  return {
    resultMethod,
    resultStatus,
    resultTopic,
    resultPayload,
  };
};

// Websocket setup function
const setupWs = () => {
  if (applicationMode === 0) {
    return;
  }
  const wsClient = new WebSocket(`ws://${localUrl}/wsapi`);
  wsClient.bufferType = "arraybuffer";
  wsClient.binaryType = "arraybuffer";
  websocketIsSetAtLeastOnce = true;
  if (debug) console.log("5. WebSocket setup called");

  wsClient.onopen = (ev) => {
    latestConnect = Date.now();
    console.log("Websocket opened");
  };

  wsClient.onclose = (ev) => {
    console.log("Websocket close");
    initConnectionsFromCachedData();
  };

  wsClient.onerror = function (event) {
    console.log("Websocket error");
  };

  wsClient.onmessage = (ev) => {
    const { resultMethod, resultStatus, resultTopic, resultPayload } =
      decodeData(ev.data);

    const execute = (arr) => {
      const match = arr.find((handler) => {
        return handler.status === resultStatus && handler.topic == resultTopic;
      });

      if (match !== undefined) {
        match.callback(parse(resultPayload));
      }
    };
    if (debug)
      console.log(
        `<RAW Websocket INFO> resultMethod: ${resultMethod} resultStatus: ${resultStatus} resultTopic: ${resultTopic} resultPayload: ${resultPayload}.`
      );
    switch (resultMethod) {
      case readHex:
        return execute(readHandlers);
      case updateHex:
        return execute(updateHandlers);
      case createHex:
        return execute(createHandlers);
      case deleteHex:
        return execute(deleteHandlers);
      default:
        return console.log("unknown result method", resultMethod);
    }
  };
};

let fetchState = false;
// Init all the connections, by dooing first fetch then catch cookie and then setup WS
const initConnectionsFromCachedData = async () => {
  if (applicationMode === 0 || typeof applicationMode === "undefined") {
    return;
  }
  try {
    const fetchedData = await loginLocal(localUrl, username, password);
    ipcRenderer.postMessage(
      "interWindowRelay",
      [
        "mainWindow->workerWindowNetwork-result-runFetchLoginLocal",
        fetchedData,
      ],
      []
    );
    if (debug) console.log("3. <FETCH OK - loginLocal> ", fetchedData);
    if (fetchedData?.role === 100) {
      fetchState = true;
    } else {
      fetchState = false;
    }
    console.log("state fetch ", fetchedData?.role);
  } catch (error) {
    if (debug) console.log("!! <FETCH FAIL - loginLocal> ", error);
    fetchState = false;
    return;
  }
  if (fetchState === false) {
    setTimeout(() => {
      initConnectionsFromCachedData();
    }, 2500);
  }
  if (
    typeof localUrl !== "undefined" &&
    localUrl !== "" &&
    localUrl !== "error" &&
    fetchState !== false
  ) {
    setupWs();
  }
};

// IPC
(async () => {
  const initData = await ipcRenderer.invoke(
    "workerWindowNetwork->main-keystoneSync-getInitData",
    "arg1"
  );
  if (typeof initData !== "undefined" && initData !== "") {
    if (debug) console.log("1. InitData received from IIFE : ", initData);
    localUrl = initData[0];
    cookie = initData[1];
    username = initData[2];
    password = initData[3];
    defaultReaderId = initData[4];
    defaultPrinterId = initData[5];
    defaultScenarioConfigured = initData[6];
    defaultScenarioId = initData[7];
    applicationMode = initData[8];
    defaultPrinterBackupId = initData[9];
    defaultPrinterButtonId = initData[10];
    if (wsSetupFromInit === false) {
      initConnectionsFromCachedData();
      wsSetupFromInit = true;
    }
  }
})();

ipcRenderer.on("mainWindowId", (event, result) => {
  if (debug) console.log("2. Received mainWindowId from main", result);
  mainWindowId = result;
});

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-openPassage",
  (event, defaultPassageId) => {
    openPassage(localUrl, defaultPassageId);
    if (debug)
      console.log("<IPC> mainWindow->main->workerWindowNetwork-openPassage");
  }
);
ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-controlPassage",
  (event, args) => {
    controlPassage(localUrl, args[0], args[1]);
    if (debug)
      console.log("<IPC> mainWindow->main->workerWindowNetwork-controlPassage");
  }
);
ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-changeOnlineTicketState",
  (event, onlineTicketId) => {
    onlineTicketByIdChangeState(localUrl, onlineTicketId);
    if (debug)
      console.log(
        "<IPC> mainWindow->main->workerWindowNetwork-changeOnlineTicketState"
      );
  }
);

ipcRenderer.on(
  "main->workerWindowNetwork-keystoneSync-updateInitData",
  (event, initData) => {
    if (debug)
      console.log(
        "<IPC> main->workerWindowNetwork-keystoneSync-updateInitData ",
        initData
      );
    localUrl = initData[0];
    cookie = initData[1];
    username = initData[2];
    password = initData[3];
    defaultReaderId = initData[4];
    defaultPrinterId = initData[5];
    defaultScenarioConfigured = initData[6];
    defaultScenarioId = initData[7];
    applicationMode = initData[8];
    defaultPrinterBackupId = initData[9];
    defaultPrinterButtonId = initData[10];
  }
);

ipcRenderer.on(
  "mainWindow->workerWindowNetwork-ticketById",
  async (event, result) => {
    if (debug)
      console.log(
        "<IPC> Received mainWindow->workerWindowNetwork-ticketById ",
        result
      );
    if (result[0] === "" || typeof result[0] === "undefined") return;
    const ticketId = result[0];
    const fetchedData = await onlineTicketById(localUrl, ticketId);
    ipcRenderer.postMessage(
      "interWindowRelay",
      [
        "mainWindow->workerWindowNetwork-ticketById-reply",
        fetchedData,
      ],
      []
    );
    console.log("now sending response: ", fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->workerWindowNetwork-printTicket",
  (event, result) => {
    if (debug)
      console.log(
        "<IPC> Received mainWindow->workerWindowNetwork-printTicket ",
        result
      );
    if (result[2] === "" || typeof result[2] === "undefined") return;
    defaultScenarioConfigured = result[0];
    defaultScenarioId = result[1];
    defaultPrinterId = result[2];
    printTicketProcess();
  }
);

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-fetchRemoteLogin",
  (event, result) => {
    if (result[0] === "" || typeof result[0] === "undefined") return;
    if (result[1] === "" || typeof result[1] === "undefined") return;
    username = result[0];
    password = result[1];
    const fetchedData = loginRemote(username, password);
    event.reply(fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->workerWindowNetwork-authenticateRemote",
  async (event, result) => {
    if (debug)
      console.log("mainWindow->workerWindowNetwork-authenticateRemote", result);
    if (result[0] === "" || typeof result[0] === "undefined") return;
    if (result[1] === "" || typeof result[1] === "undefined") return;
    username = result[0];
    password = result[1];
    const fetchedData = await loginRemote(username, password);
    ipcRenderer.postMessage(
      "interWindowRelay",
      [
        "mainWindow->workerWindowNetwork-result-runFetchLoginRemote",
        fetchedData,
      ],
      []
    );
  }
);

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-fetchRemoteGetSites",
  (event, result) => {
    const fetchedData = getSites();
    event.reply(fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-fetchRemoteGetCounters",
  (event, result) => {
    const siteId = result[0];
    const fetchedData = getCounters(siteId);
    event.reply(fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-fetchRemoteGetCounterById",
  (event, result) => {
    const siteId = result[0];
    const counterId = result[1];
    const fetchedData = getCounterById(siteId, counterId);
    event.reply(fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-fetchRemoteSetCounterById",
  (event, result) => {
    const siteId = result[0];
    const counterId = result[1];
    const value = result[2];
    const localName = result[3];
    const fetchedData = setCounterById(siteId, counterId, value, localName);
    event.reply(fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->main->workerWindowNetwork-fetchRemoteLogoutRemote",
  (event, result) => {
    const fetchedData = logoutRemote();
    event.reply(fetchedData);
  }
);

ipcRenderer.on(
  "mainWindow->workerWindowNetwork-checkScenario",
  async (event, result) => {
    if (debug)
      console.log("mainWindow->workerWindowNetwork-checkScenario", result);
    if (result[0] === "" || typeof result[0] === "undefined") return;
    if (result[1] === "" || typeof result[1] === "undefined") return;
    defaultScenarioConfigured = result[0];
    defaultScenarioId = result[1];
    if (defaultScenarioConfigured) {
      const checkScenario = await runScenario(localUrl, defaultScenarioId);
      ipcRenderer.postMessage(
        "interWindowRelay",
        [
          "workerWindowNetwork->mainWindow-result-runFetchRunScenario",
          checkScenario,
        ],
        []
      );
    }
  }
);

ipcRenderer.on(
  "main->workerWindowNetwork-newBadgeCode",
  async (event, result) => {
    if (debug) console.log("main->workerWindowNetwork-newBadgeCode", result);
    if (result[0] === "" || typeof result[0] === "undefined") return;
    if (result[1] === "") return;
    const code = result[0];
    defaultReaderId = result[1];
    const sendCode = await postBadge(localUrl, code, defaultReaderId);
    ipcRenderer.postMessage(
      "interWindowRelay",
      ["workerWindowNetwork->mainWindow-result-newBadgeCode", sendCode],
      []
    );
    // Response not yet implemented in ACS01
  }
);
