import { constants, setPriority } from "os";

import * as Nimiq from "@nimiq/core";
import { battery, cpuTemperature, graphics } from "systeminformation";

import { app, BrowserWindow, ipcMain, dialog } from "electron";

import SushiPoolCpuMiner from "./CpuMiner/SushiPoolCpuMiner.js";
import { humanHashes } from "./CpuMiner/Utils";

import NativeMiner from "./GpuMiner/NativeMiner";
import DumbPoolMiner from "./GpuMiner/DumbPoolMiner";
import { getDeviceOptions } from "./GpuMiner/Utils";

import checkPoolOnline, { getGlobalHashrate } from "./api";

import store from "../renderer/store";

// Disable security warnings
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";

/**
 * Set `__static` path to static files in production
 * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
 */
if (process.env.NODE_ENV !== "development") {
  global.__static = require("path")
    .join(__dirname, "/static")
    .replace(/\\/g, "\\\\");
}

let mainWindow;
const winURL =
  process.env.NODE_ENV === "development"
    ? `http://localhost:9080`
    : `file://${__dirname}/index.html`;

function createWindow() {
  /**
   * Initial window options
   */
  mainWindow = new BrowserWindow({
    height: 560,
    width: process.env.NODE_ENV === "development" ? 1090 : 660,
    center: true,
    resizable: false,
    fullscreenable: false,
    frame: false,
    transparent: true,
    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true,
      experimentalFeatures: true,
    },
  });
  if (process.env.NODE_ENV !== "development") mainWindow.removeMenu();

  log("Detecting UV_THREADPOOL_SIZE: " + process.env.UV_THREADPOOL_SIZE);

  if (!process.env.UV_THREADPOOL_SIZE) {
    process.env.UV_THREADPOOL_SIZE = 128;
    if (process.platform === "win32") {
      const Shell = require("node-powershell");
      let ps = new Shell({
        executionPolicy: "Bypass",
        noProfile: true,
      });
      const command =
        "[Environment]::SetEnvironmentVariable('UV_THREADPOOL_SIZE', 128, 'User')";
      ps.addCommand(command);
      ps.invoke()
        .then((output) => {
          dialog.showMessageBox({
            type: "info",
            message:
              "First time setup completed. NIM Pools Hub Miner will now restart.",
          });
          app.relaunch();
          app.quit();
        })
        .catch((err) => {
          console.log(err);
          ps.dispose();
        });
    }
  } else {
    log(`Detected ${process.env.UV_THREADPOOL_SIZE} threadpool size`);
  }

  log("Nimiq initialization");
  Nimiq.GenesisConfig.main();

  store.dispatch("setAppVersion", app.getVersion());
  store.dispatch("setCpuHashrate", null);
  store.dispatch("setGpuHashrate", null);
  store.dispatch("setMiningCPU", false);
  store.dispatch("setMiningGPU", false);

  mainWindow.loadURL(winURL);

  mainWindow.on("closed", () => {
    mainWindow = null;
  });
}

app.on("ready", createWindow);

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (mainWindow === null) {
    createWindow();
  }
});

// Disabled until Temperatures are reliable, getting 16ÂșC on my PC, thanks to wmic reporting bad temperatures
/* ipcMain.on("cpu-temp", async (event, arg) => {
  const temp = await cpuTemperature();
  console.log("CPU Temp: ", temp);
  event.reply("cpu-temp-reply", temp.main);
}); */

/**
 * Auto Updater
 *
 * Uncomment the following code below and install `electron-updater` to
 * support auto updating. Code Signing with a valid certificate is required.
 * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
 */

const { autoUpdater } = require("electron-updater");

autoUpdater.on("update-downloaded", () => {
  mainWindow.webContents.send("update-downloaded");
  setTimeout(() => autoUpdater.quitAndInstall(), 5000);
});

app.on("ready", () => {
  if (process.env.NODE_ENV === "production")
    autoUpdater.checkForUpdatesAndNotify();
  autoUpdater.logger = require("electron-log");
  autoUpdater.logger.transports.file.level = "info";
});

// Miner

const TAG = "Miner";
const $ = {};

Nimiq.Log.instance.level = "info";

const startMining = async (gpu = false) => {
  const priority = Object.entries(constants.priority)[
    store.state.settings.cpuPriority
  ];
  try {
    setPriority(priority[1]);
  } catch (error) {
    console.error("Set CPU Priority Failed: ", error);
  }

  const deviceName = store.state.settings.deviceName;
  const maxThreads = store.state.settings.cpuThreads;
  const userAddress = store.state.settings.address;
  const poolHost = store.state.settings.host;
  const poolPort = store.state.settings.port;

  console.log("GPU: " + gpu);
  Nimiq.Log.i(TAG, `- network          = main`);
  Nimiq.Log.i(TAG, `- no. of threads   = ${maxThreads}`);
  Nimiq.Log.i(TAG, `- pool server      = ${poolHost}:${poolPort}`);
  Nimiq.Log.i(TAG, `- address          = ${userAddress}`);
  Nimiq.Log.i(TAG, `- device name      = ${deviceName}`);
  if (Nimiq.Log.instance.level === 3) {
    Nimiq.Log.w(TAG, `Debug mode has been enabled.`);
  }

  const hashrate = gpu ? 200 : 50; // 100 kH/s by default
  const desiredSps = 5;
  const startDifficulty = (1e3 * hashrate * desiredSps) / (1 << 16);
  const minerVersion = `NPH Miner ${gpu ? "GPU" : "CPU"} ${app.getVersion()}`;
  const userAgent = `${minerVersion} (${Nimiq.PlatformUtils.userAgentString})`;
  const deviceData = {
    deviceName,
    startDifficulty,
    minerVersion,
    userAgent,
  };

  if (!gpu) {
    $.miner = new SushiPoolCpuMiner(userAddress, deviceData, maxThreads);
    $.miner.on("hashrate-changed", async (hashrates) => {
      const totalHashrate = hashrates.reduce((a, b) => a + b, 0);
      const temp = await cpuTemperature();
      Nimiq.Log.i(TAG, `Hashrate: ${humanHashes(totalHashrate)}`);
      Nimiq.Log.i(TAG, `CPU Temp: ${temp.main}`);
      try {
        store.dispatch("setCpuHashrate", humanHashes(totalHashrate));
      } catch (e) {
        console.log(e);
      }
    });
    $.miner.connect(poolHost, poolPort);

    $.miner.on("share", (nonce) => {
      Nimiq.Log.i(TAG, `Found share. Nonce: ${nonce}`);
    });

    $.miner.on("pool-disconnected", function () {
      Nimiq.Log.w(TAG, `Lost connection with ${poolHost}.`);
    });

    $.miner.on("pool-balance", (balances) => {
      store.dispatch("setPoolBalance", balances);
    });
  } else {
    const vendor = (await graphics()).controllers[0].vendor;
    if (!vendor.includes("Advanced Micro Devices") && !vendor.includes("NVIDIA")) {
      mainWindow.webContents.send("no-gpu-alert");
    }
    const type = vendor.includes("NVIDIA") ? "cuda" : "opencl";
    console.log(`GPU Type: ${type}`);

    const argv = {
      memory: [store.state.settings.gpuMemory],
      threads: [store.state.settings.gpuThreads],
      cache: [store.state.settings.gpuCache],
      memoryTradeoff: [store.state.settings.gpuMemoryTradeoff],
      jobs: [store.state.settings.gpuJobs],
    };

    const deviceOptions = getDeviceOptions(argv);
    $.nativeMiner = new NativeMiner(type, deviceOptions);
    $.nativeMiner.on("hashrate-changed", (hashrates) => {
      const totalHashrate = hashrates.reduce((a, v) => a + (v || 0), 0);
      Nimiq.Log.i(
        TAG,
        `Hashrate: ${humanHashes(totalHashrate)} | ${hashrates
          .map((hr, idx) => `GPU${idx}: ${humanHashes(hr)}`)
          .filter((hr) => hr)
          .join(" | ")}`
      );
      try {
        /* mainWindow.webContents.send(
          "hashrate-update",
          humanHashes(totalHashrate)
        ); */
        store.dispatch("setGpuHashrate", humanHashes(totalHashrate));
      } catch (e) { }
    });
    const deviceId = DumbPoolMiner.generateDeviceId();
    Nimiq.Log.i(TAG, `- device id        = ${deviceId}`);

    $.minerGPU = new DumbPoolMiner(
      $.nativeMiner,
      Nimiq.Address.fromUserFriendlyAddress(userAddress),
      deviceId,
      deviceData
    );

    $.minerGPU.connect(poolHost, poolPort);

    $.minerGPU.on("share", (nonce) => {
      Nimiq.Log.i(TAG, `Found share. Nonce: ${nonce}`);
    });

    $.minerGPU.on("pool-disconnected", function () {
      Nimiq.Log.w(TAG, `Lost connection with ${poolHost}.`);
    });

    $.minerGPU.on("pool-balance", (balances) => {
      store.dispatch("setPoolBalance", balances);
    });
  }
};

// Messages from render process

ipcMain.on("startMining", async (event, arg) => {
  startMining(arg.gpu);
});

ipcMain.on("stopMining", async (event, arg) => {
  store.dispatch("setPoolBalance", null);
  if (arg === "cpu") {
    if ($.miner) {
      $.miner.disconnect();
      delete $.miner;
      store.dispatch("setCpuHashrate", "0 kH/s");
    }
  } else {
    if ($.minerGPU) {
      $.minerGPU.disconnect();
      delete $.minerGPU;
    }
    if ($.nativeMiner) {
      $.nativeMiner.stop();
      delete $.nativeMiner;
      store.dispatch("setGpuHashrate", "0 kH/s");
    }
  }
});

ipcMain.on("poolOnline", async (event, arg) => {
  let pool = arg.toLowerCase();
  pool = pool.charAt(0).toUpperCase() + pool.slice(1);
  const online = await checkPoolOnline(pool);
  event.reply(`poolOnlineReply${arg}`, online);
});

ipcMain.on("getGlobalHashrate", async (event) => {
  const globalHashrate = await getGlobalHashrate();
  event.reply("getGlobalHashrateReply", globalHashrate);
});

const log = (message) => {
  console.log(message);
  /* try {
    mainWindow.webContents.send("log", message);
  } catch (e) {} */
};

process.on("uncaughtException", (err, origin) => {
  console.log("Uncaught Exception:");
  console.log(err);
  console.log(`On: ${origin}`);
});