import { SubscriptionResponse, SuccessResponse, ErrorResponse } from "../../../../vendor/generic/Types";
import * as uuid from "uuid/v4"

type rpcType = 'hook' | 'unhook' | 'call'
type visibility = 'public' | 'private'

export type Outcome = "Success" | "Error"

export type UnhookFunction = (uid:string) => Promise<SuccessResponse | ErrorResponse>
export type callbackFunction = (...args) => Promise<SubscriptionResponse | ErrorResponse>
export type AsyncFunction = (...args) => Promise<any>

export interface RPCExporter{
    name: string
    exportRPCs() : socketioRPC[]
}

type baseRPC = { 
    type: rpcType
    name: string
    visibility: visibility
}

type hookRPC = baseRPC & {
    type: 'hook'
    func: callbackFunction
    unhook: UnhookFunction
}

type unhookRPC = baseRPC & {
    type: 'unhook'
    func: UnhookFunction
}

type callRPC = baseRPC & {
    type: 'call'
    func: (...args) => Promise<any>
}

export type socketioRPC = callRPC | unhookRPC | hookRPC

export type baseInfo = {
    owner: string,
    argNames: string[],
}

type HookInfo = baseRPC & baseInfo & { 
    type: 'hook', 
    generator: (socket) => callbackFunction
    unhook: UnhookFunction
}

type UnhookInfo = baseRPC & baseInfo & {
    type: 'unhook',
    func: UnhookFunction
}

type CallInfo = baseRPC & baseInfo & {
    type: 'call',
    func: AsyncFunction
}

type RpcInfo = HookInfo | UnhookInfo | CallInfo

export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } 

export const rpcToRpcinfo = (rpc : socketioRPC, owner: string):RpcInfo => {
    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,
            }
    }
}

export const rpcHooker = (socket, owner:string, RPCs: socketioRPC[], makeUnique = true):ExtendedRpcInfo[] => {
    const suffix = makeUnique?"-"+uuid().substr(0,4):""
    return RPCs.map(rpc => rpcToRpcinfo(rpc, owner))
    .map(info => {
        const ret:any = 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:hookRPC): HookInfo['generator'] => { 
    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
    }`)
}

function extractArgs(f:Function):string[]{
    let fn = String(f)
    let args = fn.substr(0, fn.indexOf(")"))
    args = args.substr(fn.indexOf("(")+1)
    let ret = args.split(",")
    return ret
}