"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const http = require("http");
const bsock = require("bsock");
const uuid = require("uuid/v4");
/* Responses */
class Response {
    constructor(message) {
        this.message = message;
    }
}
exports.Response = Response;
class SuccessResponse extends Response {
    constructor(message) {
        super(message);
        this.result = "Success";
    }
}
exports.SuccessResponse = SuccessResponse;
class ErrorResponse extends Response {
    constructor(message = "Unknown error") {
        super(message);
        this.result = "Error";
    }
}
exports.ErrorResponse = ErrorResponse;
class SubscriptionResponse extends SuccessResponse {
    constructor(uid, message) {
        super(message);
        this.uid = uid;
    }
}
exports.SubscriptionResponse = SubscriptionResponse;
exports.rpcToRpcinfo = (rpc, owner) => {
    switch (rpc.type) {
        case "call":
            return {
                owner: owner,
                argNames: extractArgs(rpc.func),
                type: rpc.type,
                visibility: rpc.visibility,
                name: rpc.name,
                func: rpc.func,
            };
        case "unhook":
            return {
                owner: owner,
                argNames: extractArgs(rpc.func),
                type: rpc.type,
                visibility: rpc.visibility,
                name: rpc.name,
                func: rpc.func,
            };
        case "hook":
            const generator = hookGenerator(rpc);
            return {
                owner: owner,
                argNames: extractArgs(generator(undefined)),
                type: rpc.type,
                visibility: rpc.visibility,
                name: rpc.name,
                unhook: rpc.unhook,
                generator: generator,
            };
    }
};
exports.rpcHooker = (socket, owner, RPCs, makeUnique = true) => {
    const suffix = makeUnique ? "-" + uuid().substr(0, 4) : "";
    return RPCs.map(rpc => exports.rpcToRpcinfo(rpc, owner))
        .map(info => {
        const ret = info;
        ret.uniqueName = info.name + suffix;
        switch (info.type) {
            case "hook":
                socket.hook(ret.uniqueName, info.generator(socket));
                break;
            default:
                socket.hook(ret.uniqueName, info.func);
        }
        socket.on('close', () => socket.unhook(info.name));
        return ret;
    });
};
const hookGenerator = (rpc) => {
    const argsArr = extractArgs(rpc.func);
    argsArr.pop();
    const args = argsArr.join(',');
    return eval(`(socket) => async (` + args + `) => { 
        const res = await rpc.func(` + args + (args.length !== 0 ? ',' : '') + ` (x) => {
            socket.call(res.uid, x)
        })
        if(res.result == 'Success'){
            socket.on('close', async () => {
                const unhookRes = await rpc.unhook(res.uid)
                console.log("Specific close handler for", rpc.name, res.uid, unhookRes)
            })

        }
        return res
    }`);
};
const extractArgs = (f) => {
    let fn = String(f);
    let args = fn.substr(0, fn.indexOf(")"));
    args = args.substr(fn.indexOf("(") + 1);
    let ret = args.split(",");
    return ret;
};
class RPCSocketServer {
    constructor(port, rpcExporters = [], visibility = "127.0.0.1", conf = {
        errorHandler: (socket) => (error) => { socket.destroy(); console.error(error); },
        closeHandler: (socket) => () => { console.log("Socket closing"); },
        connectionHandler: (socket) => { console.log("New websocket connection in port " + socket.port); }
    }) {
        this.port = port;
        this.rpcExporters = rpcExporters;
        this.visibility = visibility;
        this.conf = conf;
        this.io = bsock.createServer();
        this.wsServer = http.createServer();
        this.startWebsocket();
    }
    startWebsocket() {
        try {
            this.io.attach(this.wsServer);
            this.io.on('socket', (socket) => {
                socket.on('error', this.conf.errorHandler(socket));
                socket.on('close', this.conf.closeHandler(socket));
                this.initApis(socket);
            });
            this.wsServer.listen(this.port, this.visibility);
        }
        catch (e) {
            //@ts-ignore
            this.errorHandler(undefined)("Unable to connect to socket");
        }
    }
    initApis(socket) {
        const adminRPCs = [
            {
                name: 'info',
                type: 'call',
                visibility: 'private',
                func: async () => rpcInfos
            }
        ];
        const rpcInfos = [
            ...exports.rpcHooker(socket, "Admin", adminRPCs, false),
            ...this.rpcExporters.flatMap(exporter => exports.rpcHooker(socket, exporter.name, exporter.exportRPCs()))
        ];
    }
}
exports.RPCSocketServer = RPCSocketServer;
