const { app, ipcMain, protocol, session, BrowserWindow, BrowserView, Menu, nativeImage, clipboard, dialog, Notification } = require('electron');
const path = require('path');
const { parse, format } = require('url');
const os = require('os');

const pkg = require(`${app.getAppPath()}/package.json`);
const protocolStr = 'flast';
const fileProtocolStr = `${protocolStr}-file`;

const platform = require('electron-platform');
const localShortcut = require('electron-localshortcut');

const Config = require('electron-store');
const config = new Config({
    defaults: {
        design: {
            homeButton: false,
            darkTheme: false,
            theme: 'default'
        },
        homePage: {
            defaultPage: `${protocolStr}://home`,
            defaultEngine: 'Google',
            searchEngines: [
                {
                    name: 'Google',
                    url: 'https://www.google.com/search?q=%s'
                },
                {
                    name: 'Bing',
                    url: 'https://www.bing.com/search?q=%s'
                },
                {
                    name: 'Yahoo! Japan',
                    url: 'https://search.yahoo.co.jp/search?p=%s'
                },
                {
                    name: 'goo',
                    url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
                },
                {
                    name: 'Baidu',
                    url: 'https://www.baidu.com/s?wd=%s'
                },
                {
                    name: 'Google Translate',
                    url: 'https://translate.google.com/?text=%s'
                },
                {
                    name: 'Youtube',
                    url: 'https://www.youtube.com/results?search_query=%s'
                },
                {
                    name: 'Twitter',
                    url: 'https://www.twitter.com/search?q=%s'
                },
                {
                    name: 'GitHub',
                    url: 'https://github.com/search?q=%s'
                }
            ]
        },
        adBlocker: true,
        window: {
            isCustomTitlebar: true,
            isMaximized: false,
            bounds: {
                width: 1100,
                height: 680
            }
        }
    },
});

const Datastore = require('nedb');
let db = {};
db.pageSettings = new Datastore({
    filename: path.join(app.getPath('userData'), 'Files', 'PageSettings.db'),
    autoload: true,
    timestampData: true
});

db.historys = new Datastore({
    filename: path.join(app.getPath('userData'), 'Files', 'History.db'),
    autoload: true,
    timestampData: true
});
db.downloads = new Datastore({
    filename: path.join(app.getPath('userData'), 'Files', 'Download.db'),
    autoload: true,
    timestampData: true
});
db.bookmarks = new Datastore({
    filename: path.join(app.getPath('userData'), 'Files', 'Bookmarks.db'),
    autoload: true,
    timestampData: true
});

db.apps = new Datastore({
    filename: path.join(app.getPath('userData'), 'Files', 'Apps.db'),
    autoload: true,
    timestampData: true
});

const { loadFilters, updateFilters, runAdblockService, removeAds } = require('./AdBlocker');

let floatingWindows = [];
let views = [];

getBaseWindow = (width = 1100, height = 680, minWidth = 320, minHeight = 200, x, y, frame = false) => {
    return new BrowserWindow({
        width, height, minWidth, minHeight, x, y, 'titleBarStyle': 'hidden', frame, fullscreenable: true,
        show: false,
        icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
        webPreferences: {
            nodeIntegration: true,
            webviewTag: true,
            plugins: true,
            experimentalFeatures: true,
            contextIsolation: false,
        }
    });
}

loadSessionAndProtocol = () => {
    const ses = session.defaultSession;

    setPermissionRequestHandler(ses, false);

    protocol.isProtocolHandled(protocolStr, (handled) => {
        if (!handled) {
            protocol.registerFileProtocol(protocolStr, (request, callback) => {
                const parsed = parse(request.url);

                return callback({
                    path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
                });
            }, (error) => {
                if (error) console.error('Failed to register protocol: ' + error);
            });
        }
    });

    protocol.isProtocolHandled(fileProtocolStr, (handled) => {
        if (!handled) {
            protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
                const parsed = parse(request.url);

                return callback({
                    path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
                });
            }, (error) => {
                if (error) console.error('Failed to register protocol: ' + error);
            });
        }
    });
}

loadSessionAndProtocolWithPrivateMode = (windowId) => {
    const ses = session.fromPartition(windowId);
    ses.setUserAgent(ses.getUserAgent().replace(/ Electron\/[0-9\.]*/g, '') + ' PrivMode');

    setPermissionRequestHandler(ses, true);

    ses.protocol.registerFileProtocol(protocolStr, (request, callback) => {
        const parsed = parse(request.url);

        return callback({
            path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
        });
    }, (error) => {
        if (error) console.error('Failed to register protocol: ' + error);
    });

    ses.protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
        const parsed = parse(request.url);

        return callback({
            path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
        });
    }, (error) => {
        if (error) console.error('Failed to register protocol: ' + error);
    });
}

setPermissionRequestHandler = (ses, isPrivate = false) => {
    if (!isPrivate) {
        ses.setPermissionRequestHandler((webContents, permission, callback) => {
            db.pageSettings.findOne({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, (err, doc) => {
                if (doc != undefined) {
                    if (permission == 'media' && doc.media != undefined && doc.media > -1)
                        return callback(doc.media === 0);
                    if (permission == 'geolocation' && doc.geolocation != undefined && doc.geolocation > -1)
                        return callback(doc.geolocation === 0);
                    if (permission == 'notifications' && doc.notifications != undefined && doc.notifications > -1)
                        return callback(doc.notifications === 0);
                    if (permission == 'midiSysex' && doc.midiSysex != undefined && doc.midiSysex > -1)
                        return callback(doc.midiSysex === 0);
                    if (permission == 'pointerLock' && doc.pointerLock != undefined && doc.pointerLock > -1)
                        return callback(doc.pointerLock === 0);
                    if (permission == 'fullscreen' && doc.fullscreen != undefined && doc.fullscreen > -1)
                        return callback(doc.fullscreen === 0);
                    if (permission == 'openExternal' && doc.openExternal != undefined && doc.openExternal > -1)
                        return callback(doc.openExternal === 0);
                } else {
                    dialog.showMessageBox({
                        type: 'info',
                        title: '権限の要求',
                        message: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname} が権限を要求しています。`,
                        detail: `要求内容: ${permission}`,
                        checkboxLabel: 'このサイトでは今後も同じ処理をする',
                        checkboxChecked: false,
                        noLink: true,
                        buttons: ['Yes', 'No'],
                        defaultId: 0,
                        cancelId: 1
                    }, (res, checked) => {
                        console.log(res, checked);
                        if (checked) {
                            if (permission == 'media')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, media: res }, { upsert: true });
                            if (permission == 'geolocation')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, geolocation: res }, { upsert: true });
                            if (permission == 'notifications')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, notifications: res }, { upsert: true });
                            if (permission == 'midiSysex')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, midiSysex: res }, { upsert: true });
                            if (permission == 'pointerLock')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, pointerLock: res }, { upsert: true });
                            if (permission == 'fullscreen')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, fullscreen: res }, { upsert: true });
                            if (permission == 'openExternal')
                                db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, openExternal: res }, { upsert: true });
                        }
                        return callback(res === 0);
                    });
                }
            });
        });
    } else {
        ses.setPermissionRequestHandler((webContents, permission, callback) => {
            dialog.showMessageBox({
                type: 'info',
                title: '権限の要求',
                message: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname} が権限を要求しています。`,
                detail: `要求内容: ${permission}`,
                noLink: true,
                buttons: ['Yes', 'No'],
                defaultId: 0,
                cancelId: 1
            }, (res) => {
                return callback(res === 0);
            });
        });
    }
}

module.exports = class WindowManager {
    constructor() {
        this.windows = new Map();

        ipcMain.on('window-add', (e, args) => {
            this.addWindow(args.isPrivate);
        });

        ipcMain.on('update-filters', (e, args) => {
            updateFilters();
        });

        ipcMain.on('data-history-get', (e, args) => {
            db.historys.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
                e.sender.send('data-history-get', { historys: docs });
            });
        });

        ipcMain.on('data-history-clear', (e, args) => {
            db.historys.remove({}, { multi: true });
        });

        ipcMain.on('data-downloads-get', (e, args) => {
            db.downloads.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
                e.sender.send('data-downloads-get', { downloads: docs });
            });
        });

        ipcMain.on('data-downloads-clear', (e, args) => {
            db.downloads.remove({}, { multi: true });
        });

        ipcMain.on('data-bookmarks-get', (e, args) => {
            db.bookmarks.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
                e.sender.send('data-bookmarks-get', { bookmarks: docs });
            });
        });

        ipcMain.on('data-bookmarks-clear', (e, args) => {
            db.bookmarks.remove({}, { multi: true });
        });

        ipcMain.on('data-apps-add', (e, args) => {
            db.apps.update({ id: args.id }, { id: args.id, name: args.name, description: args.description, url: args.url }, { upsert: true });

            db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
                console.log(docs)
            });
        });

        ipcMain.on('data-apps-remove', (e, args) => {
            db.apps.remove({ id: args.id }, {});
        });

        ipcMain.on('data-apps-get', (e, args) => {
            db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
                e.sender.send('data-apps-get', { apps: docs });
            });
        });

        ipcMain.on('data-apps-is', (e, args) => {
            db.apps.find({ id: args.id }).exec((err, docs) => {
                e.sender.send('data-apps-is', { id: args.id, isInstalled: (docs.length > 0 ? true : false) });
            });
        });

        ipcMain.on('data-apps-clear', (e, args) => {
            db.apps.remove({}, { multi: true });
        });

        ipcMain.on('clear-browsing-data', () => {
            const ses = session.defaultSession;
            ses.clearCache((err) => {
                if (err) log.error(err);
            });

            ses.clearStorageData({
                storages: [
                    'appcache',
                    'cookies',
                    'filesystem',
                    'indexdb',
                    'localstorage',
                    'shadercache',
                    'websql',
                    'serviceworkers',
                    'cachestorage',
                ],
            });

            config.clear();
            db.historys.remove({}, { multi: true });
            db.downloads.remove({}, { multi: true });
            db.bookmarks.remove({}, { multi: true });
            db.apps.remove({}, { multi: true });
        });
    }

    addWindow = (isPrivate = false) => {
        loadSessionAndProtocol();
        loadFilters();

        const { width, height, x, y } = config.get('window.bounds');
        const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 320, 200, x, y, !config.get('window.isCustomTitlebar'));

        const id = (!isPrivate ? `window-${window.id}` : `private-${window.id}`);

        config.get('window.isMaximized') && window.maximize();

        const startUrl = process.env.ELECTRON_START_URL || format({
            pathname: path.join(__dirname, '/../build/index.html'), // 警告：このファイルを移動する場合ここの相対パスの指定に注意してください
            protocol: 'file:',
            slashes: true,
            hash: `/window/${id}`,
        });

        window.loadURL(startUrl);

        window.once('ready-to-show', () => {
            window.show();
        });

        window.on('closed', () => {
            this.windows.delete(id);
        });

        window.on('close', (e) => {
            delete views[id];

            config.set('window.isMaximized', window.isMaximized());
            config.set('window.bounds', window.getBounds());
        });

        window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
        window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
        window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
        window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
        window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
        window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));

        // registerProtocols();
        this.registerListeners(id);

        localShortcut.register(window, 'CmdOrCtrl+Shift+I', () => {
            if (window.getBrowserView() == undefined) return;
            const view = window.getBrowserView();

            if (view.webContents.isDevToolsOpened()) {
                view.webContents.devToolsWebContents.focus();
            } else {
                view.webContents.openDevTools();
            }
        });

        localShortcut.register(window, 'CmdOrCtrl+R', () => {
            if (window.getBrowserView() == undefined) return;
            const view = window.getBrowserView();

            view.webContents.reload();
        });

        localShortcut.register(window, 'CmdOrCtrl+Shift+R', () => {
            if (window.getBrowserView() == undefined) return;
            const view = window.getBrowserView();

            view.webContents.reloadIgnoringCache();
        });

        this.windows.set(id, window);

        if (process.argv != undefined) {
            window.webContents.send(`tab-add-${id}`, { url: process.argv[process.argv.length - 1] });
        }
    }

    registerListeners = (id) => {
        ipcMain.on(`browserview-add-${id}`, (e, args) => {
            this.addView(id, args.url, args.isActive);
        });

        ipcMain.on(`browserview-remove-${id}`, (e, args) => {
            this.removeView(id, args.id);
        });

        ipcMain.on(`browserview-select-${id}`, (e, args) => {
            this.selectView(id, args.id);
        });

        ipcMain.on(`browserview-get-${id}`, (e, args) => {
            let datas = [];
            for (var i = 0; i < views[id].length; i++) {
                const url = views[id][i].view.webContents.getURL();

                datas.push({ id: views[id][i].view.webContents.id, title: views[id][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
            }
            e.sender.send(`browserview-get-${id}`, { views: datas });
        });

        ipcMain.on(`browserview-goBack-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    if (webContents.canGoBack())
                        webContents.goBack();
                }
            });
        });

        ipcMain.on(`browserview-goForward-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    if (webContents.canGoForward())
                        webContents.goForward();
                }
            });
        });

        ipcMain.on(`browserview-reload-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    webContents.reload();
                }
            });
        });

        ipcMain.on(`browserview-stop-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    webContents.stop();
                }
            });
        });

        ipcMain.on(`browserview-goHome-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    webContents.loadURL(config.get('homePage.defaultPage'));
                }
            });
        });

        ipcMain.on(`browserview-loadURL-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    webContents.loadURL(args.url);
                }
            });
        });

        ipcMain.on(`browserview-loadFile-${id}`, (e, args) => {
            views[id].filter(function (view, i) {
                if (view.view.webContents.id == args.id) {
                    let webContents = views[id][i].view.webContents;
                    webContents.loadFile(args.url);
                }
            });
        });

        ipcMain.on(`data-bookmark-add-${id}`, (e, args) => {
            views[id].filter((view, i) => {
                if (view.view.webContents.id == args.id) {
                    let v = views[id][i].view;
                    db.bookmarks.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
                    this.updateBookmarkState(id, v);
                }
            });
        });

        ipcMain.on(`data-bookmark-remove-${id}`, (e, args) => {
            views[id].filter((view, i) => {
                if (view.view.webContents.id == args.id) {
                    let v = views[id][i].view;
                    db.bookmarks.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
                    this.updateBookmarkState(id, v);
                }
            });
        });

        ipcMain.on(`data-bookmark-has-${id}`, (e, args) => {
            views[id].filter((view, i) => {
                if (view.view.webContents.id == args.id) {
                    let v = views[id][i].view;
                    db.bookmarks.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
                        e.sender.send(`data-bookmark-has-${id}`, { isBookmarked: (docs.length > 0 ? true : false) });
                    });
                }
            });
        });
    }

    getFavicon = (url) => {
        const parsed = parse(url);
        return url.startsWith(`${protocolStr}://`) || url.startsWith(`${fileProtocolStr}://`) ? undefined : `https://www.google.com/s2/favicons?domain=${parsed.protocol}//${parsed.hostname}`;
    }

    updateNavigationState = (id, view) => {
        const window = this.windows.get(id);
        window.webContents.send(`update-navigation-state-${id}`, {
            id: view.webContents.id,
            canGoBack: view.webContents.canGoBack(),
            canGoForward: view.webContents.canGoForward(),
        });
    }

    updateBookmarkState = (id, view) => {
        const window = this.windows.get(id);
        db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(id).startsWith('private')) }, (err, docs) => {
            const url = view.webContents.getURL();
            window.webContents.send(`browserview-load-${id}`, { id: view.webContents.id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), isBookmarked: (docs.length > 0 ? true : false) });
        });
    }

    fixBounds = (windowId, isFloating = false) => {
        const window = this.windows.get(windowId);

        if (window.getBrowserView() == undefined) return;
        const view = window.getBrowserView();

        const { width, height } = window.getContentBounds();

        view.setAutoResize({ width: true, height: true });
        if (isFloating) {
            window.setMinimizable(false);
            window.setMaximizable(false);
            window.setAlwaysOnTop(true);
            window.setVisibleOnAllWorkspaces(true);
            view.setBounds({
                x: 1,
                y: 1,
                width: width - 2,
                height: height - 2,
            });
        } else {
            window.setMinimizable(true);
            window.setMaximizable(true);
            window.setAlwaysOnTop(false);
            window.setVisibleOnAllWorkspaces(false);
            if (window.isFullScreen()) {
                view.setBounds({
                    x: 0,
                    y: 0,
                    width: width,
                    height: height,
                });
            } else {
                view.setBounds({
                    x: 1,
                    y: 73 + 1,
                    width: width - 2,
                    height: window.isMaximized() ? height - 73 : (height - 73) - 2,
                });
            }
        }
        view.setAutoResize({ width: true, height: true });
    }

    addView = (id, url, isActive) => {
        if (String(id).startsWith('private')) {
            loadSessionAndProtocolWithPrivateMode(id);
        }

        this.addTab(id, url, isActive);
    }

    removeView = (windowId, id) => {
        views[windowId].filter((view, i) => {
            if (view.view.webContents.id == id) {
                const index = i;

                if (index + 1 < views[windowId].length) {
                    this.selectView2(windowId, index + 1);
                } else if (index - 1 >= 0) {
                    this.selectView2(windowId, index - 1);
                }

                views[windowId][index].view.destroy();
                views[windowId].splice(index, 1);
            }
        });
    }

    selectView = (windowId, id) => {
        const window = this.windows.get(windowId);
        views[windowId].filter((view, i) => {
            if (id == view.view.webContents.id) {
                window.setBrowserView(views[windowId][i].view);
                window.setTitle(views[windowId][i].view.webContents.getTitle());
                window.webContents.send(`browserview-set-${windowId}`, { id: id });
                this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
            }
        });
    }

    selectView2 = (windowId, i) => {
        const window = this.windows.get(windowId);
        const item = views[windowId][i];

        window.setBrowserView(item.view);
        window.setTitle(item.view.webContents.getTitle());
        window.webContents.send(`browserview-set-${windowId}`, { id: item.id });
        this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
    }

    getViews = (windowId) => {
        let datas = [];
        for (var i = 0; i < views[windowId].length; i++) {
            const url = views[windowId][i].view.webContents.getURL();

            datas.push({ id: views[windowId][i].view.webContents.id, title: views[windowId][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
        }
        const window = this.windows.get(windowId);
        window.webContents.send(`browserview-get-${windowId}`, { views: datas });
    }

    addTab = (windowId, url = config.get('homePage.defaultPage'), isActive = true) => {
        const view = new BrowserView({
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: false,
                plugins: true,
                experimentalFeatures: true,
                safeDialogs: true,
                safeDialogsMessage: '今後このページではダイアログを表示しない',
                ...(String(windowId).startsWith('private') && { partition: windowId }),
                preload: require.resolve('./Preload')
            }
        });

        const window = this.windows.get(windowId);
        const id = view.webContents.id;

        runAdblockService(window, windowId, id, view.webContents.session);

        view.webContents.on('did-start-loading', () => {
            if (view.isDestroyed()) return;

            window.webContents.send(`browserview-start-loading-${windowId}`, { id: id });
        });
        view.webContents.on('did-stop-loading', () => {
            if (view.isDestroyed()) return;

            window.webContents.send(`browserview-stop-loading-${windowId}`, { id: id });
        });

        view.webContents.on('did-finish-load', (e) => {
            if (view.isDestroyed()) return;

            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
            this.updateBookmarkState(windowId, view);

            this.updateNavigationState(windowId, view);
        });
        view.webContents.on('did-fail-load', (e, code, description, url, isMainFrame, processId, routingId) => {
            if (view.isDestroyed()) return;

            dialog.showMessageBox({message: `${code}: ${description}`});
        });

        view.webContents.on('did-start-navigation', (e) => {
            if (view.isDestroyed()) return;

            const url = view.webContents.getURL();

            if (config.get('adBlocker') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
                removeAds(url, view.webContents);

            this.updateNavigationState(windowId, view);
        });

        view.webContents.on('page-title-updated', (e) => {
            if (view.isDestroyed()) return;

            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
            this.updateBookmarkState(windowId, view);

            if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
                db.historys.insert({ title: view.webContents.getTitle(), url: view.webContents.getURL() });

            this.updateNavigationState(windowId, view);
        });

        view.webContents.on('page-favicon-updated', (e, favicons) => {
            if (view.isDestroyed()) return;

            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
            db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
                const url = view.webContents.getURL();
                window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), isBookmarked: (docs.length > 0 ? true : false) });
            });

            this.updateNavigationState(windowId, view);
        });

        view.webContents.on('did-change-theme-color', (e, color) => {
            if (view.isDestroyed()) return;

            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
            db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
                window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), color: color, isBookmarked: (docs.length > 0 ? true : false) });
            });
        });

        view.webContents.on('new-window', (e, url) => {
            if (view.isDestroyed()) return;

            e.preventDefault();
            this.addView(windowId, url, true);
        });

        view.webContents.on('certificate-error', (e, url, error, certificate, callback) => {
            e.preventDefault();
            // callback(true);
            const dlg = dialog.showMessageBox({
                type: 'warning',
                title: 'プライバシーエラー',
                message: 'この接続ではプライバシーが保護されません',
                detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
                noLink: true,
                buttons: ['続行', 'キャンセル'],
                defaultId: 1,
                cancelId: 1
            });
            // e.preventDefault();
            callback(dlg === 0);
        });

        view.webContents.on('context-menu', (e, params) => {
            if (view.isDestroyed()) return;

            const menu = Menu.buildFromTemplate(
                [
                    ...(params.linkURL !== '' ?
                        [
                            {
                                label: '新しいタブで開く',
                                click: () => {
                                    this.addView(windowId, params.linkURL, true);
                                }
                            },
                            {
                                label: '新しいウィンドウで開く',
                                enabled: false,
                                click: () => { view.webContents.openDevTools(); }
                            },
                            {
                                label: 'プライベート ウィンドウで開く',
                                enabled: false,
                                click: () => { view.webContents.openDevTools(); }
                            },
                            { type: 'separator' },
                            {
                                label: 'リンクをコピー',
                                accelerator: 'CmdOrCtrl+C',
                                click: () => {
                                    clipboard.clear();
                                    clipboard.writeText(params.linkURL);
                                }
                            },
                            { type: 'separator' }
                        ] : []),
                    ...(params.hasImageContents ?
                        [
                            {
                                label: '新しいタブで画像を開く',
                                click: () => {
                                    this.addView(windowId, params.srcURL, true);
                                }
                            },
                            {
                                label: '画像をコピー',
                                click: () => {
                                    const img = nativeImage.createFromDataURL(params.srcURL);

                                    clipboard.clear();
                                    clipboard.writeImage(img);
                                }
                            },
                            {
                                label: '画像アドレスをコピー',
                                click: () => {
                                    clipboard.clear();
                                    clipboard.writeText(params.srcURL);
                                }
                            },
                            { type: 'separator' }
                        ] : []),
                    ...(params.isEditable ?
                        [
                            {
                                label: '元に戻す',
                                accelerator: 'CmdOrCtrl+Z',
                                enabled: params.editFlags.canUndo,
                                click: () => { view.webContents.undo(); }
                            },
                            {
                                label: 'やり直す',
                                accelerator: 'CmdOrCtrl+Y',
                                enabled: params.editFlags.canRedo,
                                click: () => { view.webContents.redo(); }
                            },
                            { type: 'separator' },
                            {
                                label: '切り取り',
                                accelerator: 'CmdOrCtrl+X',
                                enabled: params.editFlags.canCut,
                                click: () => { view.webContents.cut(); }
                            },
                            {
                                label: 'コピー',
                                accelerator: 'CmdOrCtrl+C',
                                enabled: params.editFlags.canCopy,
                                click: () => { view.webContents.copy(); }
                            },
                            {
                                label: '貼り付け',
                                accelerator: 'CmdOrCtrl+V',
                                enabled: params.editFlags.canPaste,
                                click: () => { view.webContents.paste(); }
                            },
                            { type: 'separator' },
                            {
                                label: 'すべて選択',
                                accelerator: 'CmdOrCtrl+A',
                                enabled: params.editFlags.canSelectAll,
                                click: () => { view.webContents.selectAll(); }
                            },
                            { type: 'separator' }
                        ] : []),
                    ...(params.selectionText !== '' && !params.isEditable ?
                        [
                            {
                                label: 'コピー',
                                accelerator: 'CmdOrCtrl+C',
                                click: () => { view.webContents.copy(); }
                            },
                            {
                                label: `Googleで「${params.selectionText}」を検索`,
                                click: () => {
                                    this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
                                }
                            },
                            { type: 'separator' }
                        ] : []),
                    {
                        label: '戻る',
                        accelerator: 'Alt+Left',
                        icon: view.webContents.canGoBack() ? `${app.getAppPath()}/static/arrow_back.png` : `${app.getAppPath()}/static/arrow_back_inactive.png`,
                        enabled: view.webContents.canGoBack(),
                        click: () => { view.webContents.goBack(); }
                    },
                    {
                        label: '進む',
                        accelerator: 'Alt+Right',
                        icon: view.webContents.canGoForward() ? `${app.getAppPath()}/static/arrow_forward.png` : `${app.getAppPath()}/static/arrow_forward_inactive.png`,
                        enabled: view.webContents.canGoForward(),
                        click: () => { view.webContents.goForward(); }
                    },
                    {
                        label: !view.webContents.isLoadingMainFrame() ? '再読み込み' : '読み込み中止',
                        accelerator: 'CmdOrCtrl+R',
                        icon: !view.webContents.isLoadingMainFrame() ? `${app.getAppPath()}/static/refresh.png` : `${app.getAppPath()}/static/close.png`,
                        click: () => { !view.webContents.isLoadingMainFrame() ? view.webContents.reload() : view.webContents.stop(); }
                    },
                    { type: 'separator' },
                    {
                        label: 'Floating Window (Beta)',
                        type: 'checkbox',
                        checked: (floatingWindows.indexOf(windowId) != -1),
                        enabled: (!window.isFullScreen() && !window.isMaximized() && config.get('window.isCustomTitlebar')),
                        click: () => {
                            if (floatingWindows.indexOf(windowId) != -1) {
                                floatingWindows.filter((win, i) => {
                                    if (windowId == win) {
                                        floatingWindows.splice(i, 1);
                                    }
                                });
                            } else {
                                floatingWindows.push(windowId);
                            }
                            this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
                        }
                    },
                    { type: 'separator' },
                    {
                        label: 'ページの保存',
                        accelerator: 'CmdOrCtrl+S',
                        enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                        click: () => {
                            view.webContents.savePage(`${app.getPath('downloads')}/${view.webContents.getTitle()}.html`, 'HTMLComplete', (err) => {
                                if (!err) console.log('Page Save successfully');
                            });
                        }
                    },
                    {
                        label: '印刷',
                        accelerator: 'CmdOrCtrl+P',
                        icon: `${app.getAppPath()}/static/print.png`,
                        enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                        click: () => { view.webContents.print(); }
                    },
                    { type: 'separator' },
                    {
                        label: 'デベロッパーツール',
                        accelerator: 'CmdOrCtrl+Shift+I',
                        enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                        click: () => { if (view.webContents.isDevToolsOpened()) { view.webContents.devToolsWebContents.focus(); } else { view.webContents.openDevTools(); } }
                    }
                ]
            );

            menu.popup();
        });

        view.webContents.on('before-input-event', (e, input) => {
            if (view.isDestroyed()) return;

            if (input.control || (platform.isDarwin && input.meta)) {
                console.log(input.control, input.alt, input.shift, input.key);

                if (input.shift && input.key == 'I') {
                    e.preventDefault();
                    if (view.webContents.isDevToolsOpened()) {
                        view.webContents.devToolsWebContents.focus();
                    } else {
                        view.webContents.openDevTools();
                    }
                } else if (input.shift && input.key == 'R') {
                    e.preventDefault();
                    view.webContents.reloadIgnoringCache();
                } else if (input.key == 'T') {
                    e.preventDefault();
                    this.addView(windowId, config.get('homePage.defaultPage'), true)
                }
            } else if (input.alt) {
                if (input.key == 'ArrowLeft') {
                    e.preventDefault();
                    if (view.webContents.canGoBack())
                        view.webContents.goBack();
                } else if (input.key == 'ArrowRight') {
                    e.preventDefault();
                    if (view.webContents.canGoForward())
                        view.webContents.goForward();
                }
            } else {
                if (input.key == 'F11') {
                    e.preventDefault();
                    window.setFullScreen(!window.isFullScreen());
                    this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
                }
            }
        });

        view.webContents.session.on('will-download', (event, item, webContents) => {
            const str = this.getRandString(12);
            db.downloads.insert({ id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() });
            item.on('updated', (e, state) => {
                db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
            });

            item.once('done', (e, state) => {
                db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
            });
        });

        view.webContents.loadURL(url);

        if (views[windowId] == undefined)
            views[windowId] = [];
        views[windowId].push({ id, view, isNotificationBar: false });
        console.log(views);

        if (isActive) {
            window.webContents.send(`browserview-set-${windowId}`, { id: id });
            window.setBrowserView(view);
        }

        this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
        this.getViews(windowId);
    }

    getRandString = (length) => {
        const char = 'abcdefghijklmnopqrstuvwxyz0123456789';
        const charLength = char.length;

        let str = '';
        for (var i = 0; i < length; i++) {
            str += char[Math.floor(Math.random() * charLength)];
        }

        return str;
    }
}