const { app, shell, 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 { download } = require('electron-dl');
const platform = require('electron-platform');
const localShortcut = require('electron-localshortcut');

const Config = require('electron-store');
const config = new Config({
    defaults: {
        design: {
            isHomeButton: false,
            isBookmarkBar: false,
            isDarkTheme: false,
            isCustomTitlebar: true,
            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'
                }
            ]
        },
        isAdBlock: true,
        language: 'ja',
        window: {
            isCloseConfirm: true,
            isMaximized: false,
            bounds: {
                width: 1100,
                height: 680
            }
        }
    },
});

const lang = require(`${app.getAppPath()}/langs/${config.get('language')}.js`);

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 = 500, minHeight = 360, x, y, frame = false) => {
    return new BrowserWindow({
        width, height, minWidth, minHeight, x, y, titleBarStyle: 'hidden', frame, fullscreenable: true,
        icon: `${__dirname}/static/app/icon.png`,
        show: false,
        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) => {
            const url = parse(webContents.getURL());

            db.pageSettings.findOne({ origin: `${url.protocol}//${url.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 {
                    if (Notification.isSupported()) {
                        const notify = new Notification({
                            icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
                            title: `${url.protocol}//${url.hostname} が権限を要求しています。`,
                            body: '詳細はここをクリックしてください。',
                            silent: true
                        });

                        notify.show();

                        notify.on('click', (e) => {
                            dialog.showMessageBox({
                                type: 'info',
                                title: '権限の要求',
                                message: `${url.protocol}//${url.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: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, media: res }, { upsert: true });
                                    if (permission == 'geolocation')
                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, geolocation: res }, { upsert: true });
                                    if (permission == 'notifications')
                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, notifications: res }, { upsert: true });
                                    if (permission == 'midiSysex')
                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, midiSysex: res }, { upsert: true });
                                    if (permission == 'pointerLock')
                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, pointerLock: res }, { upsert: true });
                                    if (permission == 'fullscreen')
                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, fullscreen: res }, { upsert: true });
                                    if (permission == 'openExternal')
                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, openExternal: res }, { upsert: true });
                                }
                                return callback(res === 0);
                            });
                        });
                        notify.on('close', (e) => {
                            return callback(false);
                        });
                    } else {
                        dialog.showMessageBox({
                            type: 'info',
                            title: '権限の要求',
                            message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
                            detail: `要求内容: ${permission}`,
                            checkboxLabel: 'このサイトでは今後も同じ処理をする',
                            checkboxChecked: false,
                            noLink: true,
                            buttons: ['はい / Yes', 'いいえ / No'],
                            defaultId: 0,
                            cancelId: 1
                        }, (res, checked) => {
                            if (checked) {
                                if (permission == 'media')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, media: res }, { upsert: true });
                                if (permission == 'geolocation')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, geolocation: res }, { upsert: true });
                                if (permission == 'notifications')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, notifications: res }, { upsert: true });
                                if (permission == 'midiSysex')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, midiSysex: res }, { upsert: true });
                                if (permission == 'pointerLock')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, pointerLock: res }, { upsert: true });
                                if (permission == 'fullscreen')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, fullscreen: res }, { upsert: true });
                                if (permission == 'openExternal')
                                    db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, openExternal: res }, { upsert: true });
                            }
                            return callback(res === 0);
                        });
                    }
                }
            });
        });
    } else {
        ses.setPermissionRequestHandler((webContents, permission, callback) => {
            const url = parse(webContents.getURL());
            if (Notification.isSupported()) {
                const notify = new Notification({
                    icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
                    title: `${url.protocol}//${url.hostname} が権限を要求しています。`,
                    body: '詳細はここをクリックしてください。\nプライベート ウィンドウ',
                    silent: true
                });

                notify.show();

                notify.on('click', (e) => {
                    dialog.showMessageBox({
                        type: 'info',
                        title: '権限の要求',
                        message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
                        detail: `要求内容: ${permission}`,
                        noLink: true,
                        buttons: ['はい / Yes', 'いいえ / No'],
                        defaultId: 0,
                        cancelId: 1
                    }, (res) => {
                        return callback(res === 0);
                    });
                });
                notify.on('close', (e) => {
                    return callback(false);
                });
            } else {
                dialog.showMessageBox({
                    type: 'info',
                    title: '権限の要求',
                    message: `${url.protocol}//${url.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('window-fixBounds', (e, args) => {
            this.windows.forEach((value, key) => {
                this.fixBounds(key, (floatingWindows.indexOf(key) != -1));
            })
        });

        ipcMain.on('window-change-settings', (e, args) => {
            this.windows.forEach((value, key) => {
                value.webContents.send('window-change-settings', {});
            })
        });

        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.pageSettings.remove({}, { multi: true });

            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, 500, 360, x, y, !config.get('design.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('focus', () => {
            window.webContents.send(`window-focus-${id}`, {});
        });
        window.on('blur', () => {
            window.webContents.send(`window-blur-${id}`, {});
        });

        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 = [];

            views[id].map((item) => {
                const url = item.view.webContents.getURL();

                datas.push({ id: item.view.webContents.id, title: item.view.webContents.getTitle(), url, icon: this.getFavicon(url), color: '#0a84ff', isBookmarked: false });
            });
            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), color: '#0a84ff', isBookmarked: (docs.length > 0 ? true : false) });
        });
    }

    getColor = (view) => {
        return new Promise((resolve, reject) => {
            if (view !== null && !view.isDestroyed() && view.webContents !== null) {
                view.webContents.executeJavaScript(
                    `(function () {
                        const heads = document.head.children;
                        for (var i = 0; i < heads.length; i++) {
                            if (heads[i].getAttribute('name') === 'theme-color') {
                                return heads[i].getAttribute('content');
                            }
                        } 
                    })()`, false, async (result) => {
                        resolve(result !== null ? result : '#0a84ff');
                    });
            } else {
                reject(new Error('WebContents are not available'))
            }
        });
    };

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

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

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

        const baseBarHeight = 73;
        const bookMarkBarHeight = 28;

        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: config.get('design.isCustomTitlebar') ? 1 : 0,
                    y: config.get('design.isCustomTitlebar') ? this.getHeight(true, height, baseBarHeight, bookMarkBarHeight) + 1 : this.getHeight(true, height, baseBarHeight, bookMarkBarHeight),
                    width: config.get('design.isCustomTitlebar') ? width - 2 : width,
                    height: window.isMaximized() ? this.getHeight(false, height, baseBarHeight, bookMarkBarHeight) : (config.get('design.isCustomTitlebar') ? (this.getHeight(false, height, baseBarHeight, bookMarkBarHeight)) - 2 : (this.getHeight(false, height, baseBarHeight, bookMarkBarHeight)) - 1),
                });
            }
        }
        view.setAutoResize({ width: true, height: true });
    }

    getHeight = (b, height, baseBarHeight, bookMarkBarHeight) => {
        if (b) {
            return config.get('design.isBookmarkBar') ? (baseBarHeight + bookMarkBarHeight) : baseBarHeight;
        } else {
            return height - (config.get('design.isBookmarkBar') ? (baseBarHeight + bookMarkBarHeight) : baseBarHeight);
        }
    };

    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()} - ${pkg.name}`);
                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()} - ${pkg.name}`);
        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() || !isMainFrame || code === -3) 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('isAdBlock') && !(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}`);

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

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

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

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

            window.webContents.send(`browserview-theme-color-${windowId}`, { id: view.webContents.id, color });
        });

        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();
            if (Notification.isSupported()) {
                const notify = new Notification({
                    icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
                    title: `プライバシー エラー`,
                    body: '詳細はここをクリックしてください。',
                    silent: true
                });

                notify.show();

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

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

            let menu;
            if (params.linkURL !== '' && !params.hasImageContents) {
                menu = Menu.buildFromTemplate(
                    [
                        {
                            label: lang.window.view.contextMenu.link.newTab,
                            click: () => {
                                this.addView(windowId, params.linkURL, true);
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.link.newWindow,
                            enabled: false,
                            click: () => { view.webContents.openDevTools(); }
                        },
                        {
                            label: lang.window.view.contextMenu.link.openPrivateWindow,
                            enabled: false,
                            click: () => { view.webContents.openDevTools(); }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.link.copy,
                            accelerator: 'CmdOrCtrl+C',
                            click: () => {
                                clipboard.clear();
                                clipboard.writeText(params.linkURL);
                            }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.devTool,
                            accelerator: 'CmdOrCtrl+Shift+I',
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                if (view.webContents.isDevToolsOpened())
                                    view.webContents.devToolsWebContents.focus();
                                else
                                    view.webContents.openDevTools();
                            }
                        }
                    ]
                );
            } else if (params.linkURL === '' && params.hasImageContents) {
                menu = Menu.buildFromTemplate(
                    [
                        {
                            label: lang.window.view.contextMenu.image.newTab,
                            click: () => {
                                this.addView(windowId, params.srcURL, true);
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.image.saveImage,
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                download(window, params.srcURL, {
                                    directory: app.getPath('downloads'),
                                    saveAs: true
                                });
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.image.copyImage,
                            click: () => {
                                const img = nativeImage.createFromDataURL(params.srcURL);

                                clipboard.clear();
                                clipboard.writeImage(img);
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.image.copyLink,
                            click: () => {
                                clipboard.clear();
                                clipboard.writeText(params.srcURL);
                            }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.devTool,
                            accelerator: 'CmdOrCtrl+Shift+I',
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                if (view.webContents.isDevToolsOpened())
                                    view.webContents.devToolsWebContents.focus();
                                else
                                    view.webContents.openDevTools();
                            }
                        }
                    ]
                );
            } else if (params.linkURL !== '' && params.hasImageContents) {
                menu = Menu.buildFromTemplate(
                    [
                        {
                            label: lang.window.view.contextMenu.link.newTab,
                            click: () => {
                                this.addView(windowId, params.linkURL, true);
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.link.newWindow,
                            enabled: false,
                            click: () => { view.webContents.openDevTools(); }
                        },
                        {
                            label: lang.window.view.contextMenu.link.openPrivateWindow,
                            enabled: false,
                            click: () => { view.webContents.openDevTools(); }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.link.copy,
                            accelerator: 'CmdOrCtrl+C',
                            click: () => {
                                clipboard.clear();
                                clipboard.writeText(params.linkURL);
                            }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.image.newTab,
                            click: () => {
                                this.addView(windowId, params.srcURL, true);
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.image.saveImage,
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                download(window, params.srcURL, {
                                    directory: app.getPath('downloads'),
                                    saveAs: true
                                });
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.image.copyImage,
                            click: () => {
                                const img = nativeImage.createFromDataURL(params.srcURL);

                                clipboard.clear();
                                clipboard.writeImage(img);
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.image.copyLink,
                            click: () => {
                                clipboard.clear();
                                clipboard.writeText(params.srcURL);
                            }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.devTool,
                            accelerator: 'CmdOrCtrl+Shift+I',
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                if (view.webContents.isDevToolsOpened())
                                    view.webContents.devToolsWebContents.focus();
                                else
                                    view.webContents.openDevTools();
                            }
                        }
                    ]
                );
            } else if (params.isEditable) {
                menu = Menu.buildFromTemplate(
                    [
                        {
                            label: lang.window.view.contextMenu.editable.undo,
                            accelerator: 'CmdOrCtrl+Z',
                            enabled: params.editFlags.canUndo,
                            click: () => { view.webContents.undo(); }
                        },
                        {
                            label: lang.window.view.contextMenu.editable.redo,
                            accelerator: 'CmdOrCtrl+Y',
                            enabled: params.editFlags.canRedo,
                            click: () => { view.webContents.redo(); }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.editable.cut,
                            accelerator: 'CmdOrCtrl+X',
                            enabled: params.editFlags.canCut,
                            click: () => { view.webContents.cut(); }
                        },
                        {
                            label: lang.window.view.contextMenu.editable.copy,
                            accelerator: 'CmdOrCtrl+C',
                            enabled: params.editFlags.canCopy,
                            click: () => { view.webContents.copy(); }
                        },
                        {
                            label: lang.window.view.contextMenu.editable.paste,
                            accelerator: 'CmdOrCtrl+V',
                            enabled: params.editFlags.canPaste,
                            click: () => { view.webContents.paste(); }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.editable.selectAll,
                            accelerator: 'CmdOrCtrl+A',
                            enabled: params.editFlags.canSelectAll,
                            click: () => { view.webContents.selectAll(); }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.devTool,
                            accelerator: 'CmdOrCtrl+Shift+I',
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                if (view.webContents.isDevToolsOpened())
                                    view.webContents.devToolsWebContents.focus();
                                else
                                    view.webContents.openDevTools();
                            }
                        }
                    ]
                );
            } else if (params.selectionText !== '' && !params.isEditable) {
                menu = Menu.buildFromTemplate(
                    [
                        {
                            label: lang.window.view.contextMenu.selection.copy,
                            accelerator: 'CmdOrCtrl+C',
                            click: () => { view.webContents.copy(); }
                        },
                        {
                            label: String(lang.window.view.contextMenu.selection.textSearch).replace(/{name}/, 'Google').replace(/{text}/, params.selectionText),
                            click: () => {
                                this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
                            }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.devTool,
                            accelerator: 'CmdOrCtrl+Shift+I',
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                if (view.webContents.isDevToolsOpened())
                                    view.webContents.devToolsWebContents.focus();
                                else
                                    view.webContents.openDevTools();
                            }
                        }
                    ]
                );
            } else {
                menu = Menu.buildFromTemplate(
                    [
                        {
                            label: lang.window.view.contextMenu.back,
                            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: lang.window.view.contextMenu.forward,
                            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() ? lang.window.view.contextMenu.reload.reload : lang.window.view.contextMenu.reload.stop,
                            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: lang.window.view.contextMenu.floatingWindow,
                            type: 'checkbox',
                            checked: (floatingWindows.indexOf(windowId) != -1),
                            enabled: (!window.isFullScreen() && !window.isMaximized() && config.get('design.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: lang.window.view.contextMenu.savePage,
                            accelerator: 'CmdOrCtrl+S',
                            icon: `${app.getAppPath()}/static/save.png`,
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => {
                                dialog.showSaveDialog(window, {
                                    defaultPath: `${app.getPath('downloads')}/${view.webContents.getTitle()}.html`,
                                    filters: [
                                        { name: 'HTML', extensions: ['htm', 'html'] },
                                        { name: 'All Files', extensions: ['*'] }
                                    ]
                                }, (fileName) => {
                                    if (fileName != undefined) {
                                        view.webContents.savePage(fileName, 'HTMLComplete', (err) => {
                                            if (!err) console.log('Page Save successfully');
                                        });
                                    }
                                });
                            }
                        },
                        {
                            label: lang.window.view.contextMenu.print,
                            accelerator: 'CmdOrCtrl+P',
                            icon: `${app.getAppPath()}/static/print.png`,
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => { view.webContents.print(); }
                        },
                        { type: 'separator' },
                        {
                            label: lang.window.view.contextMenu.viewSource,
                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
                            click: () => { this.addView(windowId, `view-source:${view.webContents.getURL()}`, true); }
                        },
                        {
                            label: lang.window.view.contextMenu.devTool,
                            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;
            console.log((input.control || (platform.isDarwin && input.meta)) || input.alt)
            window.webContents.setIgnoreMenuShortcuts(true);

            if (input.control) {
                if (input.shift && input.key == 'I') {
                    e.preventDefault();
                    window.webContents.setIgnoreMenuShortcuts(true);
                    view.webContents.setIgnoreMenuShortcuts(true);

                    if (view.webContents.isDevToolsOpened()) {
                        view.webContents.devToolsWebContents.focus();
                    } else {
                        view.webContents.openDevTools();
                    }
                } else if (input.shift && input.key == 'R') {
                    e.preventDefault();
                    window.webContents.setIgnoreMenuShortcuts(true);
                    view.webContents.setIgnoreMenuShortcuts(true);

                    view.webContents.reloadIgnoringCache();
                } else if (input.key == 'T') {
                    e.preventDefault();
                    window.webContents.setIgnoreMenuShortcuts(true);
                    // view.webContents.setIgnoreMenuShortcuts(true);

                    console.log('test');
                    this.addView(windowId, config.get('homePage.defaultPage'), true);
                }
            } else if (input.alt) {
                if (input.key == 'ArrowLeft') {
                    e.preventDefault();
                    // window.webContents.setIgnoreMenuShortcuts(true);
                    view.webContents.setIgnoreMenuShortcuts(true);

                    if (view.webContents.canGoBack())
                        view.webContents.goBack();
                } else if (input.key == 'ArrowRight') {
                    e.preventDefault();
                    // window.webContents.setIgnoreMenuShortcuts(true);
                    view.webContents.setIgnoreMenuShortcuts(true);

                    if (view.webContents.canGoForward())
                        view.webContents.goForward();
                }
            } else {
                if (input.key == 'F11') {
                    e.preventDefault();
                    window.webContents.setIgnoreMenuShortcuts(true);
                    view.webContents.setIgnoreMenuShortcuts(true);

                    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.find({ id: str }, (err, docs) => {
                if (docs < 1)
                    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) => {
                const filePath = item.getSavePath();
                db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
                if (state === 'completed') {
                    window.webContents.send(`notification-${windowId}`, { id: id, content: `${item.getFilename()} のダウンロードが完了しました。` });

                    if (!Notification.isSupported()) return;
                    const notify = new Notification({
                        icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
                        title: 'ダウンロード完了',
                        body: `${item.getFilename()} のダウンロードが完了しました。\n詳細はここをクリックしてください。`
                    });

                    notify.show();


                    notify.on('click', (e) => {
                        if (filePath !== undefined)
                            shell.openItem(filePath);
                    });
                } else {
                    console.log(`Download failed: ${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;
    }
}