import { ExtendedRpcInfo, UnhookFunction, callbackFunction, AsyncFunction } from "../backend/RPCSocketServer";
var bsock = require('bsock')

//fix args with defaults like "force = true" -> "force"
function stripAfterEquals(str:string){
    return str.split("=")[0]
}

type RPCReceiver = {
    [RPCGroup in string]: any
}

/**
 * Dynamic library to communicate with FrontblockService remotely
 *
 * This will be automatically injected into the webpages served by FrontblockService
 * Will ask it's service for available RPCs and parse them into methods of this object
 * for convenient access.
 */
export class RPCaller implements RPCReceiver{
    private socket
    
    constructor(port:number, server: string, tls: boolean = false){
        this.socket = bsock.connect(port, server, tls)
    }

    async connect(){
        const info:ExtendedRpcInfo[] = await this.info()
        info.forEach(i => {
            let f: any
            switch (i.type) {
                case 'call':
                    f = this.callGenerator(i.uniqueName, i.argNames)    
                    break                
                case 'hook':
                    f = this.hookGenerator(i.uniqueName, i.argNames)                    
                    break
                case 'unhook':
                    f = this.unhookGenerator(i.uniqueName, i.argNames)                    
                    break
            }
            if(this[i.owner] == null)
                this[i.owner] = {}
            this[i.owner][i.name] = f
            this[i.owner][i.name].bind(this)
        })
    }

    async info(){
        return await this.socket.call('info')
    }

    private callGenerator(fnName, fnArgs:string[]): AsyncFunction{
        const headerArgs = fnArgs.join(",")
        const argParams = fnArgs.map(stripAfterEquals).join(",")
        return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
    }

    private hookGenerator(fnName, fnArgs:string[]): callbackFunction{
        const headerArgs = fnArgs.join(",")
        const argParams = fnArgs.map(stripAfterEquals).join(",")
        return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
                            const r = await this.socket.call("`+fnName+`", `+argParams+`)
                            if(r.uid != null){
                                this.socket.hook(res.uid, callback)
                            }
                            return res
                        } )()` )
    }

    private unhookGenerator(fnName, fnArgs:string[]): UnhookFunction{
        const headerArgs = fnArgs.join(",")
        const argParams = fnArgs.map(stripAfterEquals).join(",")
        if(fnArgs.length != 1)
            console.error("UnhookFunction", fnName, "specified more than one argument: ("+headerArgs+")")

        return eval( `( () => async (`+headerArgs+`) => {
                            const r = await this.socket.call("`+fnName+`", `+argParams+`)
                            this.socket.unhook(`+argParams+`)
                            return res
                        } )()` )
    }

}