shawkinsl/mtga-tracker

View on GitHub
electron/main.js

Summary

Maintainability
F
1 wk
Test Coverage
const console = require('console');
const jwt = require('jsonwebtoken');
const { inspectorRouter } = require("./inspectorApi")

global.updateReady = false
global.updateDownloading = false
global.checkInProgress = false
const { handleStartupEvent, updater } = require("./updates")

if (handleStartupEvent()) {
  return;
}

const { app, ipcMain, BrowserWindow, Tray, Menu, nativeImage } = require('electron')
const fs = require('fs');
const path = require('path')
const keytar = require('keytar')
const uuidv4 = require('uuid/v4');
const request = require('request')
const crypto = require("crypto")

// Check if our instance is the primary instance
if(app.makeSingleInstance(focusMTGATracker)) {
  app.quit();
  return;
}

let checksum = (str, algorithm, encoding) => {
    return crypto
        .createHash(algorithm || 'md5')
        .update(str, 'utf8')
        .digest(encoding || 'hex')
}

const API_URL = "https://now.mtgatracker.com";

// check if we have saved a UUID for this tracker. If not, generate one
keytar.getPassword("mtgatracker", "tracker-id").then(savedTrackerID => {
  let uuidIsNew = false;
  if (!savedTrackerID) {
    // we need to make one
    let trackerID = uuidv4() + "_" + crypto.randomBytes(10).toString('hex');
    keytar.setPassword("mtgatracker", "tracker-id", trackerID)
    uuidIsNew = true;
    global.trackerID = trackerID;
  } else {
    global.trackerID = savedTrackerID;
  }

  // now we check if we have a token with that uuid
  keytar.getPassword("mtgatracker", "tracker-id-token").then(token => {
    var decodedTrackerIdToken
    if (token !== null) {
      decodedTrackerIdToken = jwt.decode(token)
    }
    if (!token || uuidIsNew || decodedTrackerIdToken.trackerID !== global.trackerID) {
      // we need to get a token and save it
      request.post({
        url: `${API_URL}/public-api/tracker-token/`,
        json: true,
        body: {trackerID: global.trackerID},
        headers: {'User-Agent': 'MTGATracker-App'}
      }, (err, res, data) => {
        if (err) console.log(err)
        if(res && res.statusCode == 200) {
          keytar.setPassword("mtgatracker", "tracker-id-token", data.token)
        } else {
          console.log(`unknown status code while getting tracker-token: ${res.statusCode}`)
        }
      })
    }
  })
})

const firstRun = process.argv[1] == '--squirrel-firstrun';
global.firstRun = firstRun
const runFromSource = !process.execPath.endsWith("MTGATracker.exe")

function updateCheck() {
  if (!global.updateDownloading && !global.checkInProgress) {
    global.checkInProgress = true
    updater.check((err, status) => {
      if (!err && status) {
        // Download the update
        global.updateDownloading = true;
        updater.download()
      }
      // the check is complete, we can run the check again now
      global.checkInProgress = false
    })
  }
}

if (!firstRun && fs.existsSync(path.resolve(path.dirname(process.execPath), '..', 'update.exe'))) {
  updateCheck() // check once; setInterval fires the first time AFTER the timeout
  setInterval(updateCheck,    1000     * 60       * 60     * 2)
                          //  1 second * 1 minute * 1 hour * 2 = 2 hours
}

const findProcess = require('find-process');
const settings = require('electron-settings');
updater.autoUpdater.on('update-downloaded', (e) => {
  global.updateReady = true
  mainWindow.webContents.send('updateReadyToInstall', {
    text: "A new version has been downloaded. Restart to update!"
  })
})

// adapted from the excellent medium post:
// https://medium.com/@hql287/persisting-windows-state-in-electron-using-javascript-closure-17fc0821d37
function windowStateKeeper(windowName) {
  let window, windowState;
  function setBounds() {
    // Restore from settings
    if (settings.has(`windowState.${windowName}`)) {

      windowState = settings.get(`windowState.${windowName}`);
      // we also need to check if the screen x and y would end up on is available, if not fix it
      // app is ready at this point, we can use screen
      const { screen } = require("electron")

      let positionIsValid = false;
      for (let display of screen.getAllDisplays()) {
        let lowestX = display.bounds.x;
        let highestX = lowestX + display.bounds.width;

        let lowestY = display.bounds.y;
        let highestY = lowestY + display.bounds.height;
        if (lowestX < windowState.x && windowState.x < highestX && lowestY < windowState.y && windowState.y < highestY) {
          positionIsValid = true;
        }
      }
      if (!positionIsValid) {
        console.log(`windowState ${windowState.x} / ${windowState.y} is not valid, resetting to (10, 10)`)
        windowState.x = 10
        windowState.y = 10
      }
      return;
    }
    // Default
    windowState = {
      x: undefined,
      y: undefined,
      width: 1000,
      height: 800,
    };
  }
  function saveState() {
    if(window) {
      if (!windowState.isMaximized) {
        windowState = window.getBounds();
      }
      windowState.isMaximized = window.isMaximized();
      settings.set(`windowState.${windowName}`, windowState);
    }
  }
  function track(win) {
    window = win;
    win.on('close', saveState);
  }
  setBounds();
  return({
    x: windowState.x,
    y: windowState.y,
    width: windowState.width,
    height: windowState.height,
    isMaximized: windowState.isMaximized,
    track,
  });
}


// check if we need to show the ToS
let tosAcks = settings.get("tosAcks", [])
let tosPath = 'resources/app.asar/legal/tos.md';
if (runFromSource) {
  tosPath = 'legal/tos.md';
}

let currentTOSChecksum = checksum(fs.readFileSync(tosPath))

let tosAcked = false;

if (tosAcks.includes(currentTOSChecksum)) {
   tosAcked = true;
   console.log("TOS already acked, ok to launch")
} else {
   console.log("must ack TOS before launch")
}

/*************************************************************
 * py process
 *************************************************************/

const PY_DIST_FOLDER = 'appdist'
const PY_FOLDER = 'app'
const PY_MODULE = 'mtgatracker_backend' // without .py suffix

let pyProc = null
let pyPort = null

let appDataRoaming = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + 'Library/Preferences' : '/var/local')
let logPath = path.join(appDataRoaming, "..", "LocalLow", "Wizards Of The Coast", "MTGA", "Player.log");


let getBooleanArg = (short, long) => {
  let shortIdx = process.argv.indexOf(short)
  let longIdx = process.argv.indexOf(long)
  return shortIdx != -1 || longIdx != -1;
}

let debugFileCmdOpt = getBooleanArg('-df', '--debug_file')
let debugCmdOpt = getBooleanArg('-d', '--debug')
let frameCmdOpt = getBooleanArg('-uf', '--use_framed')
let fullFileCmdOpt = getBooleanArg('-f', '--full_file')

if (debugCmdOpt) {
  settings.set('debug', true)
}

if (frameCmdOpt) {
  settings.set('useFrame', true)
}

// Hack to update to new structure
if (!settings.has('winLossCounter.alltime.total') && settings.has('winLossCounter.win') && settings.has('winLossCounter.loss')) {
  settings.set('winLossCounter.alltime.total', settings.get('winLossCounter'));
}

let debug = settings.get('debug', false);
let mtgaOverlayOnly = settings.get('mtgaOverlayOnly', true);
let showErrors = settings.get('showErrors', false);
let sendAnonymousUsageInfo = settings.get('sendAnonymousUsageInfo', false);
let showInspector = settings.get('showInspector', true);
let useFrame = settings.get('useFrame', false);
let staticMode = settings.get('staticMode', false);
let useTheme = settings.get('useTheme', false);
let themeFile = settings.get('themeFile', "");
let showIIDs = settings.get('showIIDs', false);
let no_server = settings.get('no_server', false);
let mouseEvents = settings.get('mouseEvents', true);
let leftMouseEvents = settings.get('leftMouseEvents', true);
let showGameTimer = settings.get('showGameTimer', true);
let showChessTimers = settings.get('showChessTimers', true);
let hideDelay = settings.get('hideDelay', 10);
let invertHideMode = settings.get('invertHideMode', false);
let rollupMode = settings.get('rollupMode', true);
let winLossCounter = settings.get('winLossCounter', {alltime:{total: {win: 0, loss: 0}}});
winLossCounter.daily = {total: {win: 0, loss: 0}};
let showTotalWinLossCounter = settings.get('showTotalWinLossCounter', true);
let showDeckWinLossCounter = settings.get('showDeckWinLossCounter', true);
let showDailyTotalWinLossCounter = settings.get('showDailyTotalWinLossCounter', true);
let showDailyDeckWinLossCounter = settings.get('showDailyDeckWinLossCounter', true);
let showVaultProgress = settings.get('showVaultProgress', true);
let lastCollection = settings.get('lastCollection', {});
let lastVaultProgress = settings.get('lastVaultProgress', 0);
let minVaultProgress = settings.get('minVaultProgress', 0);
let sortMethod = settings.get('sortMethod', 'draw');
let useFlat = settings.get('useFlat', true);
let useMinimal = settings.get('useMinimal', true);
let zoom = settings.get('zoom', 0.8);
let recentCards = settings.get('recentCards', []);
let recentCardsQuantityToShow = settings.get('recentCardsQuantityToShow', 10);
let minToTray = settings.get('minToTray', false);
logPath = settings.get("logPath", logPath)
let showUIButtons = settings.get('showUIButtons',true)
let showHideButton = settings.get('showHideButton',true)
let showMenu = settings.get('showMenu',true)
let blankInventory = {
                        wcCommon: 0,
                        wcUncommon: 0,
                        wcRare: 0,
                        wcMythic: 0,
                        boosters: [],
                        draftTokens: 0,
                        gems: 0,
                        gold: 0,
                        vaultProgress: 0,
                        wcTrackPosition: 0,
                        boosters: [
                          {collationId: 100005, count: 0},
                          {collationId: 100006, count: 0},
                          {collationId: 100007, count: 0},
                          {collationId: 100008, count: 0},
                          {collationId: 100009, count: 0},
                          {collationId: 100010, count: 0},
                        ]
                      }
let inventory = settings.get('inventory',blankInventory)

let inventorySpent = JSON.parse(JSON.stringify(blankInventory))
let inventoryGained = JSON.parse(JSON.stringify(blankInventory))
let blankBoosters = {100005: 0,100006: 0,100007: 0,100008: 0,100009: 0,100010: 0}
inventorySpent.boosters = JSON.parse(JSON.stringify(blankBoosters))
inventoryGained.boosters = JSON.parse(JSON.stringify(blankBoosters))

global.historyEvents = []

let debugFile = false;
if (debugFileCmdOpt) {
    debugFile = true;
    logPath = debugFileCmdOpt;
    console.log("Using debug file")
}

let kill_server = true;
let noFollow = false;
let server_killed = false;
let readFullFile = false;
if (fullFileCmdOpt) {
  readFullFile = true;
}

ipcMain.on('updateWinLossCounters', (e,arg) => {
  if (arg.key == 'all'){
    global['winLossCounter'] = arg.value;
    settings.set('winLossCounter', arg.value);
  } else {
    global['winLossCounter']['alltime'][arg.key] = arg.value.alltime;
    global['winLossCounter']['daily'][arg.key] = arg.value.daily;
    settings.set('winLossCounter.alltime.' + arg.key, arg.value.alltime);
  }

  try {
    mainWindow.webContents.send('counterChanged',global['winLossCounter'],arg);
    if ( settingsWindow != null){
      settingsWindow.webContents.send('counterChanged',global['winLossCounter']);
    }
  } catch (e) {
    console.log("could not send counterChanged message");
    console.log(e);
  }
})

ipcMain.on('lastVaultProgressChanged', (e,new_progress) => {
  global.lastVaultProgress = new_progress
  settings.set('lastVaultProgress',new_progress)
  try {
    if ( collectionWindow != null){
      collectionWindow.webContents.send('lastVaultProgressChanged',global['lastVaultProgress']);
    }
  } catch (e) {
    console.log("could not send lastVaultProgressChanged message");
    console.log(e);
  }
})

ipcMain.on('inventoryChanged', (e,new_inventory) => {
  let fields = ['gold','gems','wcCommon','wcUncommon','wcRare','wcMythic']
  for (let field of fields) {
    let changed = new_inventory[field] - global.inventory[field]
    if (changed > 0){
      global.inventoryGained[field] += changed
    } else {
      global.inventorySpent[field] -= changed
    }
  }

  for (let new_set of new_inventory.boosters){
    let old_set = global.inventory.boosters.find(x => x.collationId == new_set.collationId) || null
    let changed = 0
    if (old_set == null){
      changed = new_set.count
      global.inventoryGained.boosters[new_set.collationId] = 0
      global.inventorySpent.boosters[new_set.collationId] = 0
    } else {
      changed = new_set.count - old_set.count
    }
    if (changed > 0){
      global.inventoryGained.boosters[new_set.collationId] += changed
    } else {
      global.inventorySpent.boosters[new_set.collationId] -= changed
    }
  }

  global.inventory = new_inventory
  settings.set('inventory',new_inventory)

  try {
    if ( collectionWindow != null){
      collectionWindow.webContents.send('inventoryChanged',global.inventory,global.inventorySpent,global.inventoryGained);
    }
  } catch (e) {
    console.log("could not send inventoryChanged message");
    console.log(e);
  }
})

ipcMain.on('recentCardsChanged', (e,new_recent) => {
  global.recentCards.unshift(new_recent)
  settings.set('recentCards',global.recentCards)
  try {
    if ( collectionWindow != null){
      collectionWindow.webContents.send('recentCardsChanged',new_recent);
    }
  } catch (e) {
    console.log("could not send recentCardsChanged message");
    console.log(e);
  }
})

ipcMain.on('messageAcknowledged', (event, arg) => {
  let acked = settings.get("messagesAcknowledged", [])
  acked.push(arg)
  settings.set("messagesAcknowledged", acked)
  global["messagesAcknowledged"] = acked;
})

ipcMain.on('settingsChanged', (event, arg) => {
  global[arg.key] = arg.value;
  settings.set(arg.key, arg.value)
  mainWindow.webContents.send('settingsChanged')
})

ipcMain.on('hideRequest', (event, arg) => {
  if (historyWindow) {
    try {
      historyWindow.webContents.send('hideRequest', arg)
    } catch (error) {
      console.log("couldn't send stdout message to history window, likely already destroyed")
    }
  }
})

ipcMain.on('clearGameHistory', event => {
  global.historyEvents = []
  if (historyWindow) {
    try {
      historyWindow.webContents.send("clearGameHistory")
    } catch (error) {
      console.log("couldn't send stdout message to history window, likely already destroyed")
    }
  }
})

ipcMain.on('gameHistoryEvent', (event, arg) => {
  global.historyEvents.push(arg)
  if (historyWindow) {
    try {
      historyWindow.webContents.send('gameHistoryEventSend', arg)
    } catch (error) {
      console.log("couldn't send stdout message to main window, likely already destroyed")
    }
  }
})

ipcMain.on('tosAgreed', (event, arg) => {
  let tosAcks = settings.get("tosAcks", [])
  let currentTOSChecksum = checksum(fs.readFileSync(tosPath))
  if (!tosAcks.includes(currentTOSChecksum)) {
    tosAcks.push(currentTOSChecksum)
  }
  settings.set('tosAcks', tosAcks)
  createMainWindow()
  tosWindow.close()
})

let openSettingsWindow = () => {
  if(settingsWindow == null) {
    let settingsWidth = debug ? 1400 : 1025;

    const settingsWindowStateMgr = windowStateKeeper('settings')
    settingsWindow = new BrowserWindow({width: settingsWidth,
                                        height: 800,
                                        toolbar: false,
                                        titlebar: false,
                                        title: false,
                                        maximizable: false,
                                        show: false,
                                        icon: "img/icon_small.ico",
                                        x: settingsWindowStateMgr.x,
                                        y: settingsWindowStateMgr.y})
    settingsWindowStateMgr.track(settingsWindow)
    settingsWindow.setMenu(null)
    settingsWindow.loadURL(require('url').format({
      pathname: path.join(__dirname, 'settings.html'),
      protocol: 'file:',
      slashes: true
    }))
    if (debug) {
      settingsWindow.webContents.openDevTools()
    }
    settingsWindow.on('closed', function () {
      settingsWindow = null;
    })
  }
  settingsWindow.once('ready-to-show', () => {
    settingsWindow.show()
  })
  settingsWindow.on('close', () => {global.settingsPaneIndex = 'general'})
}

let openCollectionWindow = () => {
  if(collectionWindow == null) {
    let collectionWidth = debug ? 1400 : 1025;

    const collectionWindowStateMgr = windowStateKeeper('collection')
    collectionWindow = new BrowserWindow({width: collectionWidth,
                                        height: 850,
                                        toolbar: false,
                                        titlebar: false,
                                        title: false,
                                        maximizable: false,
                                        show: false,
                                        icon: "img/icon_small.ico",
                                        x: collectionWindowStateMgr.x,
                                        y: collectionWindowStateMgr.y})
    collectionWindowStateMgr.track(collectionWindow)
    collectionWindow.setMenu(null)
    collectionWindow.loadURL(require('url').format({
      pathname: path.join(__dirname, 'collection.html'),
      protocol: 'file:',
      slashes: true
    }))
    if (debug) {
      collectionWindow.webContents.openDevTools()
    }
    collectionWindow.on('closed', function () {
      collectionWindow = null;
    })
  }
  collectionWindow.once('ready-to-show', () => {
    collectionWindow.show()
  })
}

let openInspectorWindow = () => {
  if(inspectorWindow == null) {
    let settingsWidth = debug ? 1400 : 1100;

    const inspectorWindowStateMgr = windowStateKeeper('settings')
    inspectorWindow = new BrowserWindow({width: settingsWidth,
                                        height: 800,
                                        toolbar: false,
                                        titlebar: false,
                                        title: false,
                                        show: false,
                                        icon: "img/icon_small.ico",
                                        x: inspectorWindowStateMgr.x,
                                        y: inspectorWindowStateMgr.y})
    inspectorWindowStateMgr.track(inspectorWindow)
    inspectorWindow.setMenu(null)
    inspectorWindow.loadURL(require('url').format({
      pathname: path.join(__dirname, 'inspector/index.html'),
      protocol: 'file:',
      slashes: true
    }))
    if (debug) {
      inspectorWindow.webContents.openDevTools()
    }
    inspectorWindow.on('closed', function () {
      inspectorWindow = null;
    })
  }
  inspectorWindow.once('ready-to-show', () => {
    inspectorWindow.show()
  })
  inspectorWindow.on('close', () => {global.settingsPaneIndex = 'general'})
}



let openHistoryWindow = () => {
  if(historyWindow == null) {
    let historyWidth = debug ? 1200 : 400;

    const historyWindowStateMgr = windowStateKeeper('history')
    historyWindow = new BrowserWindow({width: historyWidth,
                                        height: 800,
                                        toolbar: false,
                                        titlebar: false,
                                        title: false,
                                        maximizable: false,
                                        show: false,
                                        transparent: !(debug || useFrame),
                                        frame: (debug || useFrame),
                                        alwaysOnTop: true,
                                        icon: "img/icon_small.ico",
                                        x: historyWindowStateMgr.x,
                                        y: historyWindowStateMgr.y})
    historyWindowStateMgr.track(historyWindow)
    historyWindow.setMenu(null)
    historyWindow.loadURL(require('url').format({
      pathname: path.join(__dirname, 'game_history.html'),
      protocol: 'file:',
      slashes: true
    }))
    if (debug) {
      historyWindow.webContents.openDevTools()
    }
    historyWindow.on('closed', function () {
      historyWindow = null;
    })
  }
  historyWindow.once('ready-to-show', () => {
    historyWindow.show()
  })
}


let openTOSWindow = () => {
  if(tosWindow == null) {
    tosWindow  = new BrowserWindow({width: 800,
                                        height: 560,
                                        toolbar: false,
                                        titlebar: false,
                                        title: false,
                                        maximizable: false,
                                        show: false,
                                        icon: "img/icon_small.ico"})
    tosWindow.setMenu(null)
    tosWindow.loadURL(require('url').format({
      pathname: path.join(__dirname, 'tos.html'),
      protocol: 'file:',
      slashes: true
    }))
    if (debug) {
      tosWindow.webContents.openDevTools()
    }
    tosWindow.on('closed', function () {
      tosWindow = null;
    })
  }
  tosWindow.once('ready-to-show', () => {
    tosWindow.show()
  })
}

ipcMain.on('openSettings', openSettingsWindow)
ipcMain.on('openCollection', openCollectionWindow)
ipcMain.on('openInspector', openInspectorWindow)
ipcMain.on('openHistory', openHistoryWindow)

app.disableHardwareAcceleration()

const guessPackaged = () => {
  const fullPath = path.join(__dirname, "..", PY_DIST_FOLDER)
  return fs.existsSync(fullPath)
}

const getScriptPath = () => {
  if (!guessPackaged()) {
    return path.join(__dirname, "..", PY_FOLDER, PY_MODULE + '.py')
  }
  if (process.platform === 'win32') {
    return path.join(__dirname, "..", PY_DIST_FOLDER, PY_MODULE, PY_MODULE + '.exe') // TODO: verify this
  }
  return path.join(__dirname, "..", PY_DIST_FOLDER, PY_MODULE, PY_MODULE)
}

const getPyBinPath = () => {
  if (process.platform === 'win32') {
    venv_path_win = path.join(__dirname, "..", "venv", "Scripts", "python.exe")
    venv_path_x = path.join(__dirname, "..", "venv", "Scripts", "python")
    fallback_path = "python"
    if (fs.existsSync(venv_path_win)) {
        return venv_path_win
    } else if (fs.existsSync(venv_path_x)) {
        return venv_path_x
    } else {
        return fallback_path // ? shrug
    }
  }
}

const selectPort = () => {
  pyPort = 5678
  return pyPort
}

port = selectPort()
global.port = port;

const generateArgs = () => {
    var args = ["-p", port]
    args.push("-i")
    args.push(logPath)
    if (noFollow) {
        args.push('-nf')
    }
    if (readFullFile) {
        args.push('-f')
    }
    if (mouseEvents) {
      args.push('-m')
    }
    return args
}

const cleanupPyProc = (cb)  => {
    finishedCount = 0;
    p1 = findProcess('name', "mtgatracker_backend.exe")
    p2 = findProcess('port', 5678)
    Promise.all([p1, p2]).then(function(vals) {
        nameList = vals[0]
        portList = vals[1]
        killedList = []
        nameList.forEach(function(proc) {
            if (proc.pid != 0 && !killedList.includes(proc.pid)) {
                console.log("leftover python process (name) @ " + proc.pid + ", killing...")
                process.kill(proc.pid)
                killedList.push(proc.pid)
            }
        })
        portList.forEach(function(proc) {
            if (proc.pid != 0 && !killedList.includes(proc.pid)) {
                console.log("leftover python process (port) @ " + proc.pid + ", killing...")
                process.kill(proc.pid)
                killedList.push(proc.pid)
            }
        })
      cb()
    })

}

const createPyProc = () => {
  let script = getScriptPath()
  let pbPath = getPyBinPath()

  let args = generateArgs()
  if (guessPackaged()) {
    try {
      mainWindow.webContents.send('stdout', {text: `calling: spawn(${script}, ${args}}`})
    } catch (error) {
      console.log("couldn't send stdout message to main window, likely already destroyed")
    }
    pyProc = require('child_process').spawn(script, args)
  } else {
    let pbArgs = ['-u', script].concat(args)  // -u for unbuffered python
    try {
      mainWindow.webContents.send('stdout', {text: `calling: spawn(${pbPath}, ${pbArgs})`})
    } catch (error) {
      console.log("couldn't send stdout message to main window, likely already destroyed")
    }
    pyProc = require('child_process').spawn(pbPath, pbArgs)
  }

  if (pyProc != null) {
    console.log('child process success on port ' + port)
    pyProc.stderr.on('data', function(data) {
      console.log("py stderr: " + data.toString());
      if (mainWindow) {
        try {
          mainWindow.webContents.send('stdout', {text: "py stderr:" + data.toString()})
        } catch (error) {
          console.log("couldn't send stdout message to main window, likely already destroyed")
        }
      }
    });
    pyProc.stdout.on('data', function(data) {
      console.log("py stdout:" + data.toString());
      if (mainWindow) {
        try {
          mainWindow.webContents.send('stdout', {text: "py stdout:" + data.toString()})
        } catch (error) {
          console.log("couldn't send stdout message to main window, likely already destroyed")
        }
      }
    });
    pyProc.on('exit', function(code) {
      console.log(`python exited with code ${code}`);
      server_killed = true;
    });
  }
}

if (!no_server) {
    cleanupPyProc(createPyProc)
}

global.API_URL = API_URL;
global.debug = debug;
global.mtgaOverlayOnly = mtgaOverlayOnly;
global.showErrors = showErrors;
global.sendAnonymousUsageInfo = sendAnonymousUsageInfo;
global.showInspector = showInspector;
global.useFrame = useFrame;
global.staticMode = staticMode;
global.useTheme = useTheme;
global.themeFile = themeFile;
global.showIIDs = showIIDs;
global.leftMouseEvents = leftMouseEvents;
global.showGameTimer = showGameTimer;
global.showChessTimers = showChessTimers;
global.invertHideMode = invertHideMode;
global.rollupMode = rollupMode;
global.hideDelay = hideDelay;
global.mouseEvents = mouseEvents;
global.winLossCounter = winLossCounter;
global.showTotalWinLossCounter = showTotalWinLossCounter;
global.showDeckWinLossCounter = showDeckWinLossCounter;
global.showDailyTotalWinLossCounter = showDailyTotalWinLossCounter;
global.showDailyDeckWinLossCounter = showDailyDeckWinLossCounter;
global.showVaultProgress = showVaultProgress;
global.lastVaultProgress = lastVaultProgress;
global.lastCollection = lastCollection;
global.minVaultProgress = minVaultProgress;
global.version = app.getVersion()
global.messagesAcknowledged = settings.get("messagesAcknowledged", [])
global.runFromSource = runFromSource
global.sortMethod = sortMethod
global.useFlat = useFlat
global.useMinimal = useMinimal
global.zoom = zoom
global.recentCards = recentCards
global.recentCardsQuantityToShow = recentCardsQuantityToShow
global.logPath = logPath
global.minToTray = minToTray
global.historyZoom = settings.get("history-zoom", 1.0)
global.settingsPaneIndex = "general"
global.showUIButtons = showUIButtons
global.showHideButton = showHideButton
global.showMenu = showMenu
global.inventory = inventory
global.inventorySpent = inventorySpent
global.inventoryGained = inventoryGained

/*************************************************************
 * window management
 *************************************************************/

let mainWindow = null
let settingsWindow = null
let inspectorWindow = null
let historyWindow = null
let tosWindow = null
let collectionWindow = null

let window_width = 354;
let window_height = 200;
if (debug) {
    window_width = 1220;
    window_height = 700;
}

const openDeckTrackerHandler = (menuItem, browserWindow, event) => {
    focusMTGATracker();
}

const openSettingsHandler = (menuItem, browserWindow, event) => {
  focusMTGATrackerSettings();
}

const openHistoryHandler = (menuItem, browserWindow, event) => {
  focusMTGAHistory();
}

const openInspectorHandler = (menuItem, browserWindow, event) => {
  focusInspector();
}

const openCollectionHandler = (menuItem, browserWindow, event) => {
  focusCollection();
}

const closeTrackerHandler = (menuItem, browserWindow, event) => {
  mainWindow.close();
}

let tray = null;

const createTray = () => {
  if(tray==null) {
    let iconFile = 'icon_tray.png'
    let iconPath = path.join(__dirname,'img', iconFile);
    console.log(fs.existsSync(iconPath))
    let nativeIcon = nativeImage.createFromPath(iconPath)
    tray = new Tray(nativeIcon)
    const contextMenu = Menu.buildFromTemplate([
      {label: "DeckTracker", type: "normal", click: openDeckTrackerHandler},
      {label: "Settings", type: "normal", click: openSettingsHandler},
      {label: "Inspector", type: "normal", click: openInspectorHandler},
      {label: "Collection", type: "normal", click: openCollectionHandler},
      {label: "History", type: "normal", click: openHistoryHandler},
      {label: "Quit", type: "normal", click: closeTrackerHandler }
    ])
    tray.setToolTip('MTGA Tracker')
    tray.setContextMenu(contextMenu)
    tray.on("double-click", (event, bounds) => openDeckTrackerHandler())
  }
}

const createMainWindow = () => {
  const mainWindowStateMgr = windowStateKeeper('main')
  mainWindow = new BrowserWindow({width: window_width,
                                  height: window_height,
                                  show: false,
                                  resizable: (debug || useFrame),
                                  transparent: !(debug || useFrame),
                                  frame: (debug || useFrame),
                                  alwaysOnTop: true,
                                  toolbar: false,
                                  titlebar: false,
                                  title: false,
                                  maximizable: false,
                                  icon: "img/icon_small.ico",
                                  x: mainWindowStateMgr.x,
                                  y: mainWindowStateMgr.y})
  createTray();
  mainWindowStateMgr.track(mainWindow)
  mainWindow.loadURL(require('url').format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))
  mainWindow.on('closed', () => {
    console.log("main window closed")
    killServer()
  })
  mainWindow.on('minimize', () => {
    minToTray = settings.get('minToTray', false);
    createTray();
    mainWindow.setSkipTaskbar(minToTray)
  })

  if (debug) {
    mainWindow.webContents.openDevTools()
  }

  mainWindow.once('ready-to-show', () => {
    mainWindow.webContents.send('settingsChanged')
    mainWindow.show()
    console.timeEnd('init')
    mainWindow.webContents.setZoomFactor(zoom)
  })

  let versionsAcknowledged = settings.get('versionsAcknowledged', [])

  // show release notes on first launch of new version
  if (!versionsAcknowledged.includes(app.getVersion())) {
    versionsAcknowledged.push(app.getVersion())
    settings.set("versionsAcknowledged", versionsAcknowledged)
    global.settingsPaneIndex = 'about'
    openSettingsWindow()
  }
}

const openFirstWindow = () => {
  if (tosAcked) {
    createMainWindow()
  } else {
    openTOSWindow()
  }
}

function focusMTGATracker() {
  if(mainWindow) {
    mainWindow.setSkipTaskbar(false);
    mainWindow.show();
    if(mainWindow.isMinimized()) {
      mainWindow.restore();
    }
    mainWindow.focus();
  } else {
    openFirstWindow();
  }
}

function focusMTGATrackerSettings() {
  if(settingsWindow) {
    settingsWindow.show();
    if(settingsWindow.isMinimized()) {
      settingsWindow.restore();
    }
    settingsWindow.focus();
  } else {
    openSettingsWindow();
  }
}

function focusCollection() {
  if(collectionWindow) {
    collectionWindow.show();
    if(collectionWindow.isMinimized()) {
      collectionWindow.restore();
    }
    collectionWindow.focus();
  } else {
    openCollectionWindow();
  }
}

function focusInspector() {
  if(inspectorWindow) {
    inspectorWindow.show();
    if(inspectorWindow.isMinimized()) {
      inspectorWindow.restore();
    }
    inspectorWindow.focus();
  } else {
    openInspectorWindow();
  }

}

function focusMTGAHistory() {
  if(historyWindow) {
    historyWindow.show();
    if(historyWindow.isMinimized()) {
      historyWindow.restore();
    }
    historyWindow.focus();
  } else {
    openHistoryWindow();
  }
}

function freeze(time) {
    const stop = new Date().getTime() + time;
    while(new Date().getTime() < stop);
}

const killServer = () => {
    console.log("killServer called")
    if (!server_killed && kill_server) {
        server_killed = true;
        if (!no_server) {
            console.log("cleaning up")
            freeze(2000)
            cleanupPyProc(() => {})
        }
        pyProc = null
        pyPort = null
    }
    if (global.updateReady) {
      console.log("doing quitAndInstall")
      updater.install()
    } else {
      console.log("app.quit()")
      app.quit()
    }
}

app.on('ready', openFirstWindow)

app.on('will-quit', function() {
  console.log("will quit")
  killServer()
})