/*jshint esversion: 6 */

const electron = require('electron');
const updater = require('electron-updater');
const fs = require('fs');
const os = require('os');
const path = require('path');
const process = require('process');
const url = require('url');

class Application {

    constructor() {
        this._views = new ViewCollection();
        this._configuration = new ConfigurationService();
        this._menu = new MenuService();
        this._openFileQueue = [];

        electron.app.setAppUserModelId('com.lutzroeder.netron');

        if (this.makeSingleInstance()) {
            electron.app.quit();
        }

        electron.ipcMain.on('open-file-dialog', (e, data) => {
            this.openFileDialog();
        });

        electron.ipcMain.on('drop-files', (e, data) => {
            this.dropFiles(e.sender, data.files);
        });

        electron.app.on('will-finish-launching', () => {
            electron.app.on('open-file', (e, path) => {
                this.openFile(path);
            });
        });

        electron.app.on('ready', () => {
            this.ready();
        });

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

        electron.app.on('will-quit', () => {
            this._configuration.save();
        });

        this.parseCommandLine(process.argv);
        this.checkForUpdates();
    }

    makeSingleInstance() {
        return electron.app.makeSingleInstance((argv, workingDirectory) => { 
            var currentDirectory = process.cwd();
            process.chdir(workingDirectory);
            var open = this.parseCommandLine(argv);
            process.chdir(currentDirectory);
            if (!open) {
                if (this._views.count > 0) {
                    var view = this._views.item(0);
                    if (view) {
                        view.restore();
                    }
                }
            }
        });
    }

    parseCommandLine(argv) {
        var open = false;
        if (process.platform == 'win32' && argv.length > 1) {
            argv.slice(1).forEach((arg) => {
                if (!arg.startsWith('-')) {
                    var extension = arg.split('.').pop();
                    if (extension != '' && extension != 'js' && fs.existsSync(arg) && fs.statSync(arg).isFile()) {
                        this.openFile(arg);
                        open = true;
                    }
                }
            });
        }
        return open;
    }

    ready() {
        this._configuration.load();
        if (this._openFileQueue) {
            var openFileQueue = this._openFileQueue;
            this._openFileQueue = null;
            while (openFileQueue.length > 0) {
                var file = openFileQueue.shift();
                this.openFile(file);
            }
        }
        if (this._views.count == 0) {
            this._views.openView();
        }
        this.resetMenu();
        this._views.on('active-view-changed', (e) => {
            this.updateMenu();
        });
        this._views.on('active-view-updated', (e) => {
            this.updateMenu();
        });
    }

    openFileDialog() {
        var showOpenDialogOptions = { 
            properties: [ 'openFile' ], 
            filters: [
                { name: 'ONNX Model', extension: [ 'onnx', 'pb' ] },
                { name: 'Keras Model', extension: [ 'json', 'keras', 'h5' ] },
                { name: 'CoreML Model', extension: [ 'mlmodel' ] },
                { name: 'Caffe Model', extension: [ 'caffemodel' ] },
                { name: 'Caffe2 Model', extension: [ 'pb' ] },
                { name: 'MXNet Model', extension: [ 'json' ] },
                { name: 'TensorFlow Graph', extension: [ 'pb', 'meta' ] },
                { name: 'TensorFlow Saved Model', extension: [ 'saved_model.pb' ] },
                { name: 'TensorFlow Lite Model', extension: [ 'tflite' ] }
            ]
        };
        electron.dialog.showOpenDialog(showOpenDialogOptions, (selectedFiles) => {
            if (selectedFiles) {
                selectedFiles.forEach((selectedFile) => {
                    this.openFile(selectedFile);
                });
            }
        });
    }

    openFile(file) {
        if (this._openFileQueue) {
            this._openFileQueue.push(file);
            return;
        }
        if (file && file.length > 0 && fs.existsSync(file))
        {
            // find existing view for this file
            var view = this._views.find(file);
            // find empty welcome window
            if (view == null) {
                view = this._views.find(null);
            }
            // create new window
            if (view == null) {
                view = this._views.openView();
            }
            this.loadFile(file, view);
        }
    }

    loadFile(file, view) {
        var recents = this._configuration.get('recents');
        recents = recents.filter(recent => file != recent.path);
        view.open(file);
        recents.unshift({ path: file });
        if (recents.length > 9) {
            recents.splice(9);
        }
        this._configuration.set('recents', recents);
        this.resetMenu();
    }

    dropFiles(sender, files) {
        var view = this._views.from(sender);
        files.forEach((file) => {
            if (view) {
                this.loadFile(file, view);
                view = null;
            }
            else {
                this.openFile(file);
            }
        });
    }


    export() {
        var view = this._views.activeView;
        if (view && view.path) {
            var defaultPath = 'Untitled';
            var file = view.path;
            var lastIndex = file.lastIndexOf('.');
            if (lastIndex != -1) {
                defaultPath = file.substring(0, lastIndex);
            }
            var owner = electron.BrowserWindow.getFocusedWindow();
            var showSaveDialogOptions = {
                title: 'Export',
                defaultPath: defaultPath,
                buttonLabel: 'Export',
                filters: [
                    { name: 'PNG', extensions: [ 'png' ] },
                    { name: 'SVG', extensions: [ 'svg' ] }
                ]
            };
            electron.dialog.showSaveDialog(owner, showSaveDialogOptions, (filename) => {
                if (filename) {
                    view.execute('export', { 'file': filename });
                }
            });
        }
    }

    execute(command, data) {
        var view = this._views.activeView;
        if (view) {
            view.execute(command, data || {});
        }
        this.updateMenu();
    }

    reload() {
        var view = this._views.activeView;
        if (view && view.path) {
            this.loadFile(view.path, view);
        }
    }

    toggleDevTools() {
        if (this.isDev()) {
            var window = electron.BrowserWindow.getFocusedWindow();
            if (window) {
                window.toggleDevTools();
            }
        }
    }

    checkForUpdates() {
        if (this.isDev()) {
            return;
        }
        var autoUpdater = updater.autoUpdater;
        autoUpdater.autoDownload = false;
        autoUpdater.on('update-available', (info) => {
            var owner = electron.BrowserWindow.getFocusedWindow();
            var messageBoxOptions = {
                icon: path.join(__dirname, 'icon.png'),
                title: ' ',
                message: 'A new version of ' + electron.app.getName() + ' is available.',
                detail: 'Click \'Download and Install\' to download the update and automatically install it on exit.',
                buttons: ['Download and Install', 'Remind Me Later'],
                defaultId: 0,
                cancelId: 1
            };
            var result = electron.dialog.showMessageBox(owner, messageBoxOptions);
            if (result == 0) {
                autoUpdater.autoDownload = true;
                autoUpdater.checkForUpdatesAndNotify();
            }
        });
        autoUpdater.checkForUpdates();
    }

    get package() { 
        if (!this._package) {
            var appPath = electron.app.getAppPath();
            var file = appPath + '/package.json'; 
            var data = fs.readFileSync(file);
            this._package = JSON.parse(data);
            this._package.date = new Date(fs.statSync(file).mtime);
        }
        return this._package;
    }

    about() {
        var owner = electron.BrowserWindow.getFocusedWindow();
        var author = this.package.author;
        var date = this.package.date;
        var details = [];
        details.push('Version ' + electron.app.getVersion());
        if (author && author.name && date) {
            details.push('');
            details.push('Copyright \u00A9 ' + date.getFullYear().toString() + ' ' + author.name);
        }
        var aboutDialogOptions = {
            icon: path.join(__dirname, 'icon.png'),
            title: ' ',
            message: electron.app.getName(),
            detail: details.join('\n')
        };
        electron.dialog.showMessageBox(owner, aboutDialogOptions);
    }

    isDev() {
        return ('ELECTRON_IS_DEV' in process.env) ?
            (parseInt(process.env.ELECTRON_IS_DEV, 10) === 1) :
            (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath));
    }

    updateMenu() {
        var context = {};
        context.window = electron.BrowserWindow.getFocusedWindow();
        context.webContents = context.window ? context.window.webContents : null; 
        context.view = this._views.activeView;
        this._menu.update(context);
    }

    resetMenu() {

        var menuRecentsTemplate = [];
        if (this._configuration.has('recents')) {
            var recents = this._configuration.get('recents');
            recents = recents.filter(recent => fs.existsSync(recent.path) && fs.statSync(recent.path).isFile());
            if (recents.length > 9) {
                recents.splice(9);
            }
            this._configuration.set('recents', recents);
            recents.forEach((recent, index) => {
                var file = recent.path;
                menuRecentsTemplate.push({
                    label: Application.minimizePath(recent.path),
                    accelerator: ((process.platform === 'darwin') ? 'Cmd+' : 'Ctrl+') + (index + 1).toString(),
                    click: () => { this.openFile(file); }
                });
            });
        }

        var menuTemplate = [];
        
        if (process.platform === 'darwin') {
            menuTemplate.unshift({
                label: electron.app.getName(),
                submenu: [
                    { role: "about" },
                    { type: 'separator' },
                    { role: 'hide' },
                    { role: 'hideothers' },
                    { role: 'unhide' },
                    { type: 'separator' },
                    { role: "quit" }
                ]
            });
        }
        
        menuTemplate.push({
            label: '&File',
            submenu: [
                {
                    label: '&Open...',
                    accelerator: 'CmdOrCtrl+O',
                    click: () => { this.openFileDialog(); }
                },
                {
                    label: 'Open &Recent',
                    submenu: menuRecentsTemplate
                },
                { type: 'separator' },
                { 
                    id: 'file.export',
                    label: '&Export...',
                    accelerator: 'CmdOrCtrl+Shift+E',
                    click: () => this.export(),
                },
                { type: 'separator' },
                { role: 'close' },
            ]
        });
        
        if (process.platform !== 'darwin') {
            menuTemplate.slice(-1)[0].submenu.push(
                { type: 'separator' },
                { role: 'quit' }
            );
        }

        if (process.platform == 'darwin') {
            electron.systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true);
            electron.systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', true);
        }

        menuTemplate.push({
            label: '&Edit',
            submenu: [
                {
                    id: 'edit.cut',
                    label: 'Cu&t',
                    accelerator: 'CmdOrCtrl+X',
                    click: () => this.execute('cut', null),
                },
                {
                    id: 'edit.copy',
                    label: '&Copy',
                    accelerator: 'CmdOrCtrl+C',
                    click: () => this.execute('copy', null),
                },
                {
                    id: 'edit.paste',
                    label: '&Paste',
                    accelerator: 'CmdOrCtrl+V',
                    click: () => this.execute('paste', null),
                },
                {
                    id: 'edit.select-all',
                    label: 'Select &All',
                    accelerator: 'CmdOrCtrl+A',
                    click: () => this.execute('selectall', null),
                },
                { type: 'separator' },
                {
                    id: 'edit.find',
                    label: '&Find...',
                    accelerator: 'CmdOrCtrl+F',
                    click: () => this.execute('find', null),
                }
            ]
        });
    
        var viewTemplate = {
            label: '&View',
            submenu: [
                {
                    id: 'view.show-details',
                    accelerator: 'CmdOrCtrl+D',
                    click: () => this.execute('toggle-details', null),
                },
                {
                    id: 'view.show-names',
                    accelerator: 'CmdOrCtrl+U',
                    click: () => this.execute('toggle-names', null),
                },
                { type: 'separator' },
                {
                    id: 'view.reload',
                    label: '&Reload',
                    accelerator: (process.platform === 'darwin') ? 'Cmd+R' : 'F5',
                    click: () => this.reload(),
                },
                { type: 'separator' },
                {
                    id: 'view.reset-zoom',
                    label: 'Actual &Size',
                    accelerator: 'CmdOrCtrl+Backspace',
                    click: () => this.execute('reset-zoom', null),
                },
                {
                    id: 'view.zoom-in',
                    label: 'Zoom &In',
                    accelerator: 'CmdOrCtrl+Up',
                    click: () => this.execute('zoom-in', null),
                },
                {
                    id: 'view.zoom-out',
                    label: 'Zoom &Out',
                    accelerator: 'CmdOrCtrl+Down',
                    click: () => this.execute('zoom-out', null),
                },
                { type: 'separator' },
                {
                    id: 'view.show-properties',
                    label: '&Properties...',
                    accelerator: 'CmdOrCtrl+Enter',
                    click: () => this.execute('show-properties', null),
                }        
            ]
        };
        if (this.isDev()) {
            viewTemplate.submenu.push({ type: 'separator' });
            viewTemplate.submenu.push({ role: 'toggledevtools' });
        }
        menuTemplate.push(viewTemplate);

        if (process.platform === 'darwin') {
            menuTemplate.push({
                role: 'window',
                submenu: [
                    { role: 'minimize' },
                    { role: 'zoom' },
                    { type: 'separator' },
                    { role: 'front'}
                ]
            });
        }    

        var helpSubmenu = [
            {
                label: '&Search Feature Requests',
                click: () => { electron.shell.openExternal('https://www.github.com/' + this.package.repository + '/issues'); }
            },
            {
                label: 'Report &Issues',
                click: () => { electron.shell.openExternal('https://www.github.com/' + this.package.repository + '/issues/new'); }
            }
        ];

        if (process.platform != 'darwin') {
            helpSubmenu.push({ type: 'separator' });
            helpSubmenu.push({
                role: 'about',
                click: () => this.about()
            });
        }

        menuTemplate.push({
            role: 'help',
            submenu: helpSubmenu
        });

        var commandTable = {};
        commandTable['file.export'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['edit.cut'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['edit.copy'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['edit.paste'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['edit.select-all'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['edit.find'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['view.show-details'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; },
            label: (context) => { return !context.view || !context.view.get('show-details') ? 'Show &Details' : 'Hide &Details'; }
        };
        commandTable['view.show-names'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; },
            label: (context) => { return !context.view || !context.view.get('show-names') ? 'Show &Names' : 'Hide &Names'; }
        };
        commandTable['view.reload'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['view.reset-zoom'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['view.zoom-in'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['view.zoom-out'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };
        commandTable['view.show-properties'] = {
            enabled: (context) => { return context.view && context.view.path ? true : false; }
        };

        this._menu.build(menuTemplate, commandTable);
        this.updateMenu();
    }

    static minimizePath(file) {
        if (process.platform != 'win32') {
            var home = os.homedir();
            if (file.startsWith(home))
            {
                return '~' + file.substring(home.length);
            }
        }
        return file;
    }

}

class View {

    constructor(owner) {
        this._owner = owner;
        this._ready = false;
        this._path = null;
        this._properties = {};

        const size = electron.screen.getPrimaryDisplay().workAreaSize;
        var options = {};
        options.title = electron.app.getName(); 
        options.backgroundColor = '#eeeeee';
        options.icon = electron.nativeImage.createFromPath(path.join(__dirname, 'icon.png'));
        options.minWidth = 600;
        options.minHeight = 400;
        options.width = size.width;
        options.height = size.height;
        if (options.width > 1024) {
            options.width = 1024;
        }
        if (options.height > 768) {
            options.height = 768;
        }
        if (this._owner.count > 0 && View._position && View._position.length == 2) {
            options.x = View._position[0] + 30;
            options.y = View._position[1] + 30;
            if (options.x + options.width > size.width) {
                options.x = 0;
            }
            if (options.y + options.height > size.height) {
                options.y = 0;
            }
        }
        this._window = new electron.BrowserWindow(options);
        View._position = this._window.getPosition();
        this._updateCallback = (e, data) => { 
            if (e.sender == this._window.webContents) {
                this.update(data.name, data.value); 
                this.raise('updated');
            }
        };
        electron.ipcMain.on('update', this._updateCallback);
        this._window.on('closed', () => {
            electron.ipcMain.removeListener('update', this._updateCallback);
            this._owner.closeView(this);
        });
        this._window.on('focus', (e) => {
            this.raise('activated');
        });
        this._window.on('blur', (e) => {
            this.raise('deactivated');
        });
        this._window.webContents.on('dom-ready', () => {
            this._ready = true;
        });
        var location = url.format({
            pathname: path.join(__dirname, 'view-electron.html'),
            protocol: 'file:',
            slashes: true
        });
        this._window.loadURL(location);
    }

    get window() {
        return this._window;
    }

    get path() {
        return this._path;
    }

    open(file) {
        this._openPath = file;
        if (this._ready) {
            this._window.webContents.send("open", { file: file });
        }
        else {
            this._window.webContents.on('dom-ready', () => {
                this._window.webContents.send("open", { file: file });
            });
            var location = url.format({
                pathname: path.join(__dirname, 'view-electron.html'),
                protocol: 'file:',
                slashes: true
            });
            this._window.loadURL(location);
        }
    }

    restore() {
        if (this._window) { 
            if (this._window.isMinimized()) {
                this._window.restore();
            }
            this._window.show();
        }
    }

    match(path) {
        if (this._openPath) {
            if (path == null) {
                return false;
            }
            if (path == this._openPath) {
                return true;
            }
        }
        return (this._path == path);
    }

    execute(command, data) {
        if (this._window && this._window.webContents) {
            this._window.webContents.send(command, data);
        }
    }

    update(name, value) {
        switch (name) {
            case 'path':
                if (value) {
                    this._path = value;
                    var title = Application.minimizePath(this._path);
                    if (process.platform !== 'darwin') {
                        title = title + ' - ' + electron.app.getName();
                    }
                    this._window.setTitle(title);
                    this._window.focus();
                }
                this._openPath = null;
                break;
            default:
                this._properties[name] = value;
                break;
        }
    }

    get(name) {
        return this._properties[name];
    }

    on(event, callback) {
        this._events = this._events || {};
        this._events[event] = this._events[event] || [];
        this._events[event].push(callback);
    }

    raise(event, data) {
        if (this._events && this._events[event]) {
            this._events[event].forEach((callback) => {
                callback(this, data);
            });
        }
    }
}

class ViewCollection {
    constructor() {
        this._views = [];
    }

    get count() {
        return this._views.length;
    }

    item(index) {
        return this._views[index];
    }

    openView() {
        var view = new View(this);
        view.on('activated', (sender) => {
            this._activeView = sender;
            this.raise('active-view-changed', { activeView: this._activeView });
        });
        view.on('updated', (sender) => {
            this.raise('active-view-updated', { activeView: this._activeView });            
        });
        view.on('deactivated', (sender) => {
            this._activeView = null;
            this.raise('active-view-changed', { activeView: this._activeView });
        });
        this._views.push(view);
        this._updateActiveView();
        return view;
    }

    closeView(view) {
        for (var i = this._views.length - 1; i >= 0; i--) {
            if (this._views[i] == view) {
                this._views.splice(i, 1);
            }
        }
        this._updateActiveView();
    }

    find(path) {
        return this._views.find(view => view.match(path));
    }

    from(contents) {
        return this._views.find(view => view && view.window && view.window.webContents && view.window.webContents == contents);
    }

    get activeView() {
        return this._activeView;
    }

    on(event, callback) {
        this._events = this._events || {};
        this._events[event] = this._events[event] || [];
        this._events[event].push(callback);
    }

    raise(event, data) {
        if (this._events && this._events[event]) {
            this._events[event].forEach((callback) => {
                callback(this, data);
            });
        }
    }

    _updateActiveView() {
        var window = electron.BrowserWindow.getFocusedWindow();
        var view = this._views.find(view => view.window == window) || null;
        if (view != this._activeView) {
            this._activeView = view;
            this.raise('active-view-changed', { activeView: this._activeView });        
        }
    }
}

class ConfigurationService {

    load() {
        var dir = electron.app.getPath('userData');
        if (dir && dir.length > 0) {
            var file = path.join(dir, 'configuration.json'); 
            if (fs.existsSync(file)) {
                var data = fs.readFileSync(file);
                if (data) {
                    this._data = JSON.parse(data);
                }
            }
        }
        if (!this._data) {
            this._data = {
                'recents': []
            };
        }
    }

    save() {
        if (this._data) {
            var data = JSON.stringify(this._data);
            if (data) {
                var dir = electron.app.getPath('userData');
                if (dir && dir.length > 0) {
                    var file = path.join(dir, 'configuration.json'); 
                    fs.writeFileSync(file, data);
                }
            }
        }
    }

    has(name) {
        return this._data && this._data.hasOwnProperty(name);
    }

    set(name, value) {
        this._data[name] = value;
    }

    get(name) {
        return this._data[name];
    }

}

class MenuService {

    build(menuTemplate, commandTable) {
        this._menuTemplate = menuTemplate;
        this._commandTable = commandTable;
        this._itemTable = {};
        menuTemplate.forEach((menuTemplateMenu) => {
            menuTemplateMenu.submenu.forEach((menuTemplateItem) => {
                if (menuTemplateItem.id) {
                    this._itemTable[menuTemplateItem.id] = menuTemplateItem;
                }
            });
        });
        this._rebuild();
    }

    update(context) {
        if (!this._menu && !this._commandTable) {
            return;
        }
        var rebuild = false;
        Object.keys(this._commandTable).forEach((id) => {
            var menuItem = this._menu.getMenuItemById(id);
            var command = this._commandTable[id];
            if (command && command.label) {
                var label = command.label(context);
                if (label != menuItem.label) {
                    var menuTemplateItem = this._itemTable[id];
                    if (menuTemplateItem) {
                        menuTemplateItem.label = label;
                        rebuild = true;
                    }
                }
            }
        });
        if (rebuild) {
            this._rebuild();
        }
        Object.keys(this._commandTable).forEach((id) => {
            var menuItem = this._menu.getMenuItemById(id);
            var command = this._commandTable[id];
            if (command) {
                if (command.enabled) {
                    menuItem.enabled = command.enabled(context);
                }
                if (command.label) {
                    if (menuItem.label != command.label(context)) {

                    }
                }
            }
        });
    }

    _rebuild() {
        this._menu = electron.Menu.buildFromTemplate(this._menuTemplate);
        electron.Menu.setApplicationMenu(this._menu);
    }
}

var application = new Application();
