/**
 * BetterDiscord IPC Module
 * Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
 * All rights reserved.
 * https://betterdiscord.net
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
*/

import { ipcRenderer } from 'electron';

const callbacks = new WeakMap();

const ClientIPC = new class ClientIPC {

    constructor() {
        this.on('ping', () => 'pong', true);
    }

    /**
     * Adds an IPC event listener.
     * @param {String} channel The channel to listen on
     * @param {Function} callback A function that will be called when a message is received
     * @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
     * @return {Promise}
     */
    on(channel, callback, reply) {
        channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;

        const boundCallback = async (event, args) => {
            const ipcevent = new BDIpcEvent(event, args);
            try {
                const r = callback(ipcevent, ipcevent.message);
                if (reply) ipcevent.reply(await r);
            } catch (err) {
                console.error('Error in IPC callback:', err);
                if (reply) ipcevent.reject(err);
            }
        };

        callbacks.set(callback, boundCallback);
        ipcRenderer.on(channel, boundCallback);
    }

    off(channel, callback) {
        ipcRenderer.removeListener(channel, callbacks.get(callback));
    }

    /**
     * Sends a message to the main process and returns a promise that is resolved when the main process replies.
     * @param {String} channel The channel to send a message to
     * @param {Any} message Data to send to the main process
     * @param {Boolean} error Whether to mark the message as an error
     * @return {Promise}
     */
    async send(channel, message, error) {
        channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;

        const eid = `bd-${Date.now().toString()}`;
        ipcRenderer.send(channel, { eid, message, error });

        return new Promise((resolve, reject) => {
            ipcRenderer.once(eid, (event, arg) => {
                if (arg.error) reject(arg.message);
                else resolve(arg.message);
            });
        });
    }

    /**
     * Sends a message to the Discord window and returns a promise that is resolved when it replies.
     * @param {String} channel The channel to send a message to
     * @param {Any} message Data to send to the renderer process
     * @return {Promise}
     */
    sendToDiscord(channel, message) {
        return this.send('bd-sendToDiscord', {
            channel, message
        });
    }

    /**
     * Sends a message to the CSS editor window and returns a promise that is resolved when it replies.
     * @param {String} channel The channel to send a message to
     * @param {Any} message Data to send to the CSS editor window
     * @return {Promise}
     */
    sendToCssEditor(channel, message) {
        return this.send('bd-sendToCssEditor', {
            channel, message
        });
    }

    ping() {
        return this.send('ping');
    }

    getConfig() {
        return this.send('getConfig');
    }

    showOpenDialog(options) {
        return this.send('native-open', options);
    }

    compileSass(options) {
        return this.send('compileSass', options);
    }

    dba(command) {
        return this.send('dba', command);
    }

    getPassword(service, account) {
        return this.send('keytar-get', {service, account});
    }

    setPassword(service, account, password) {
        return this.send('keytar-set', {service, account, password});
    }

    deletePassword(service, account) {
        return this.send('keytar-delete', {service, account});
    }

    findCredentials(service) {
        return this.send('keytar-find-credentials', {service});
    }

}

export default ClientIPC;

/**
 * An IPC event.
 */
class BDIpcEvent {

    constructor(event, args) {
        this.args = args;
        this.ipcEvent = event;
        this.replied = false;
    }

    bindings() {
        this.reply = this.reply.bind(this);
    }

    /**
     * Sends a message back to the message's sender.
     * @param {Any} message Data to send to this message's sender
     */
    get send() { return this.reply }
    reply(message, error) {
        if (this.replied)
            throw {message: 'This message has already been replied to.'};

        this.replied = true;
        return ClientIPC.send(this.eid, message, error);
    }

    reject(err) {
        return this.reply(err, true);
    }

    get message() {
        return this.args.message;
    }

    get error() {
        return this.args.error;
    }

    get eid() {
        return this.args.eid;
    }
}
