var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { credentials } from "grpc";
import EnvoyContext from "./envoy-context";
import EnvoyGrpcRequestParams from "./envoy-grpc-request-params";
/**
 * this function is to assign new method to the decorated original client
 * by assigning new method, user can call the method easier with async signature
 * @param name the function name
 * @internal
 */
function makeAsyncFunc(name) {
    return function (request, options) {
        return __awaiter(this, void 0, void 0, function* () {
            const params = new EnvoyGrpcRequestParams(this.envoyContext, options);
            return new Promise((resolve, reject) => {
                Object.getPrototypeOf(Object.getPrototypeOf(this))[name].call(this, request, params.assembleRequestMeta(), {
                    host: this.originalAddress
                }, (error, response) => {
                    if (error) {
                        reject(error);
                        return;
                    }
                    resolve(response);
                });
            });
        });
    };
}
/**
 * this is to wrap the original client stream func, to insert envoy metadata
 * @param name the name of the func
 */
function wrapClientStreamFunc(name) {
    return function (callback, options) {
        const params = new EnvoyGrpcRequestParams(this.envoyContext, options);
        return Object.getPrototypeOf(Object.getPrototypeOf(this))[name].call(this, params.assembleRequestMeta(), {
            host: this.originalAddress
        }, callback);
    };
}
/**
 * this is to wrap the original server stream func, to insert envoy metadata
 * @param name the name of the func
 */
function wrapServerStream(name) {
    return function (request, options) {
        const params = new EnvoyGrpcRequestParams(this.envoyContext, options);
        return Object.getPrototypeOf(Object.getPrototypeOf(this))[name].call(this, request, params.assembleRequestMeta(), {
            host: this.originalAddress
        });
    };
}
/**
 * this is to wrap the original bidirectional stream, to insert envoy metadata
 * @param name the func name
 */
function wrapBidiStream(name) {
    return function (options) {
        const params = new EnvoyGrpcRequestParams(this.envoyContext, options);
        return Object.getPrototypeOf(Object.getPrototypeOf(this))[name].call(this, params.assembleRequestMeta(), {
            host: this.originalAddress
        });
    };
}
/**
 * this method will decorate the client constructor to
 * 1. enable envoy context
 * 2. using async syntax for each call RPC
 *
 * Check `EnvoyClient` for more information
 *
 * TODO: optimize the typing if the typing of gRPC is updated
 * @param constructor Client constructor
 */
export default function envoyProtoDecorator(constructor, channelFactoryOverride = undefined) {
    const constructorAlias = constructor;
    const { service } = constructorAlias;
    const clazz = class extends constructor {
        constructor(address, ctx) {
            let envoyContext;
            if (ctx instanceof EnvoyContext) {
                envoyContext = ctx;
            }
            else {
                envoyContext = new EnvoyContext(ctx);
            }
            const actualAddr = envoyContext.shouldCallWithoutEnvoy(address)
                ? address
                : `${envoyContext.envoyEgressAddr}:${envoyContext.envoyEgressPort}`;
            super(actualAddr, credentials.createInsecure(), { channelFactoryOverride });
            this.originalAddress = address;
            this.envoyContext = envoyContext;
        }
    };
    const prototype = clazz.prototype;
    for (const name of Object.keys(service)) {
        const method = service[name];
        const { requestStream, responseStream } = method;
        if (!requestStream && !responseStream) {
            // tslint:disable-next-line:only-arrow-functions
            prototype[name] = makeAsyncFunc(name);
        }
        else if (method.requestStream && !method.responseStream) {
            prototype[name] = wrapClientStreamFunc(name);
        }
        else if (!method.requestStream && method.responseStream) {
            prototype[name] = wrapServerStream(name);
        }
        else {
            prototype[name] = wrapBidiStream(name);
        }
        const { originalName } = method;
        /* istanbul ignore next */
        if (originalName) {
            // should alway have
            prototype[originalName] = prototype[name];
        }
    }
    return clazz;
}
//# sourceMappingURL=envoy-proto-decorator.js.map