var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./AjaxOptions", "../../App", "../../Atom", "../../core/AtomBridge", "../../core/types", "../../di/Inject", "../../di/TypeKey", "../CacheService", "../JsonService", "./JsonError"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.BaseService = exports.ServiceParameter = exports.Cancel = exports.Patch = exports.Put = exports.Delete = exports.Get = exports.Post = exports.XmlBody = exports.BodyFormModel = exports.RawBody = exports.Body = exports.Queries = exports.Query = exports.Header = exports.Path = void 0;
    const AjaxOptions_1 = require("./AjaxOptions");
    const App_1 = require("../../App");
    const Atom_1 = require("../../Atom");
    const AtomBridge_1 = require("../../core/AtomBridge");
    const types_1 = require("../../core/types");
    const Inject_1 = require("../../di/Inject");
    const TypeKey_1 = require("../../di/TypeKey");
    const CacheService_1 = require("../CacheService");
    const JsonService_1 = require("../JsonService");
    const JsonError_1 = require("./JsonError");
    // tslint:disable-next-line
    function methodBuilder(method) {
        // tslint:disable-next-line
        return function (url, options) {
            // tslint:disable-next-line
            return function (target, propertyKey, descriptor) {
                target.methods = target.methods || {};
                const a = target.methods[propertyKey];
                const oldFunction = descriptor.value;
                // tslint:disable-next-line:typedef
                descriptor.value = function (...args) {
                    if (this.testMode || Atom_1.Atom.designMode) {
                        // tslint:disable-next-line:no-console
                        console.log(`Test Design Mode: ${url} .. ${args.join(",")}`);
                        const ro = oldFunction.apply(this, args);
                        if (ro) {
                            return ro;
                        }
                    }
                    const jsCache = options ? options.jsCacheSeconds : 0;
                    if (jsCache) {
                        const cacheService = this.app.resolve(CacheService_1.default);
                        const jArgs = args.map((arg) => arg instanceof types_1.CancelToken ? null : arg);
                        const key = `${this.constructor.name}:${method}:${url}:${JSON.stringify(jArgs)}`;
                        return cacheService.getOrCreate(key, (e) => {
                            e.ttlSeconds = jsCache;
                            return this.invoke(url, method, a, args, options);
                        });
                    }
                    return this.invoke(url, method, a, args, options);
                };
            };
        };
    }
    // tslint:disable-next-line
    function parameterBuilder(paramName, defaultValue) {
        // tslint:disable-next-line
        return function (key) {
            // console.log("Declaration");
            // console.log({ key:key});
            // tslint:disable-next-line
            return function (target, propertyKey, parameterIndex) {
                // console.log("Instance");
                // console.log({ key:key, propertyKey: propertyKey,parameterIndex: parameterIndex });
                target.methods = target.methods || {};
                let a = target.methods[propertyKey];
                if (!a) {
                    a = [];
                    target.methods[propertyKey] = a;
                }
                a[parameterIndex] = new ServiceParameter(paramName, key, defaultValue);
            };
        };
    }
    /**
     * This will register Url path fragment on parameter.
     *
     * @example
     *
     *      @Get("/api/products/{category}")
     *      async getProducts(
     *          @Path("category")  category: number
     *      ): Promise<Product[]> {
     *      }
     *
     * @export
     * @function Path
     * @param {name} - Name of the parameter
     */
    exports.Path = parameterBuilder("Path");
    /**
     * This will register header on parameter.
     *
     * @example
     *
     *      @Get("/api/products/{category}")
     *      async getProducts(
     *          @Header("x-http-auth")  category: number
     *      ): Promise<Product[]> {
     *      }
     *
     * @export
     * @function Path
     * @param {name} - Name of the parameter
     */
    exports.Header = parameterBuilder("Header");
    /**
     * This will register Url query fragment on parameter.
     *
     * @example
     *
     *      @Get("/api/products")
     *      async getProducts(
     *          @Query("category")  category: number
     *      ): Promise<Product[]> {
     *      }
     *
     * @export
     * @function Query
     * @param {name} - Name of the parameter
     */
    exports.Query = parameterBuilder("Query");
    /**
     * This will register Url query fragments on parameter of type object
     *
     * @example
     *
     *     @Get("/api/products")
     *     async getProducts(
     *          @Queries queries: { [key: string]: string | number | boolean | null }
     *     ): Promise<Product[]> {
     *         return null;
     * }
     */
    exports.Queries = parameterBuilder("Queries")("");
    /**
     * This will register data fragment on ajax.
     *
     * @example
     *
     *      @Post("/api/products")
     *      async getProducts(
     *          @Query("id")  id: number,
     *          @Body product: Product
     *      ): Promise<Product[]> {
     *      }
     *
     * @export
     * @function Body
     */
    exports.Body = parameterBuilder("Body")("");
    exports.RawBody = parameterBuilder("RawBody")("");
    /**
     * This will register data fragment on ajax in old formModel way.
     *
     * @example
     *
     *      @Post("/api/products")
     *      async getProducts(
     *          @Query("id")  id: number,
     *          @BodyFormModel product: Product
     *      ): Promise<Product[]> {
     *      }
     *
     * @export
     * @function BodyFormModel
     */
    exports.BodyFormModel = parameterBuilder("BodyFormModel")("");
    exports.XmlBody = parameterBuilder("XmlBody")("");
    /**
     * Http Post method
     * @example
     *
     *      @Post("/api/products")
     *      async saveProduct(
     *          @Body product: Product
     *      ): Promise<Product> {
     *      }
     *
     * @export
     * @function Post
     * @param {url} - Url for the operation
     */
    exports.Post = methodBuilder("Post");
    /**
     * Http Get Method
     *
     * @example
     *
     *      @Get("/api/products/{category}")
     *      async getProducts(
     *          @Path("category") category?:string
     *      ): Promise<Product[]> {
     *      }
     *
     * @export
     * @function Body
     */
    exports.Get = methodBuilder("Get");
    /**
     * Http Delete method
     * @example
     *
     *      @Delete("/api/products")
     *      async deleteProduct(
     *          @Body product: Product
     *      ): Promise<Product> {
     *      }
     *
     * @export
     * @function Delete
     * @param {url} - Url for the operation
     */
    exports.Delete = methodBuilder("Delete");
    /**
     * Http Put method
     * @example
     *
     *      @Put("/api/products")
     *      async saveProduct(
     *          @Body product: Product
     *      ): Promise<Product> {
     *      }
     *
     * @export
     * @function Put
     * @param {url} - Url for the operation
     */
    exports.Put = methodBuilder("Put");
    /**
     * Http Patch method
     * @example
     *
     *      @Patch("/api/products")
     *      async saveProduct(
     *          @Body product: any
     *      ): Promise<Product> {
     *      }
     *
     * @export
     * @function Patch
     * @param {url} - Url for the operation
     */
    exports.Patch = methodBuilder("Patch");
    /**
     * Cancellation token
     * @example
     *
     *      @Put("/api/products")
     *      async saveProduct(
     *          @Body product: Product
     *          @Cancel cancel: CancelToken
     *      ): Promise<Product> {
     *      }
     *
     * @export
     * @function Put
     * @param {url} - Url for the operation
     */
    function Cancel(target, propertyKey, parameterIndex) {
        if (!target.methods) {
            target.methods = {};
        }
        let a = target.methods[propertyKey];
        if (!a) {
            a = [];
            target.methods[propertyKey] = a;
        }
        a[parameterIndex] = new ServiceParameter("cancel", "");
    }
    exports.Cancel = Cancel;
    class ServiceParameter {
        constructor(type, key, defaultValue) {
            this.type = type;
            this.key = key;
            this.defaultValue = defaultValue;
            this.type = type.toLowerCase();
            this.key = key;
        }
    }
    exports.ServiceParameter = ServiceParameter;
    function BaseUrl(baseUrl) {
        return (target) => {
            const key = TypeKey_1.TypeKey.get(target);
            BaseService.baseUrls[key] = baseUrl;
        };
    }
    exports.default = BaseUrl;
    const globalNS = (typeof global !== "undefined") ? global : window;
    if (!globalNS.XMLHttpRequest) {
        // tslint:disable-next-line: no-var-requires
        globalNS.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
    }
    /**
     *
     *
     * @export
     * @class BaseService
     */
    let BaseService = class BaseService {
        constructor(app, jsonService) {
            this.app = app;
            this.jsonService = jsonService;
            this.testMode = false;
            this.showProgress = true;
            this.showError = false;
            // bs
            this.methods = {};
            this.methodReturns = {};
            this.jsonOptions = null;
            this.headers = null;
            this.jsonOptions = Object.assign({}, this.jsonService.options);
        }
        encodeData(o) {
            o.dataType = "application/json";
            o.data = this.jsonService.stringify(o.data, this.jsonOptions);
            o.contentType = "application/json";
            return o;
        }
        sendResult(result, error) {
            return new Promise((resolve, reject) => {
                if (error) {
                    setTimeout(() => {
                        reject(error);
                    }, 1);
                    return;
                }
                setTimeout(() => {
                    resolve(result);
                }, 1);
            });
        }
        invoke(url, method, bag, values, methodOptions) {
            return __awaiter(this, void 0, void 0, function* () {
                if (this.baseUrl === undefined) {
                    let p = Object.getPrototypeOf(this);
                    while (p) {
                        const t = TypeKey_1.TypeKey.get(p.constructor || p);
                        const bu = BaseService.baseUrls[t];
                        if (bu) {
                            this.baseUrl = bu;
                            break;
                        }
                        p = Object.getPrototypeOf(p);
                    }
                    if (this.baseUrl === undefined) {
                        this.baseUrl = null;
                    }
                }
                if (this.baseUrl) {
                    if (!/^\//.test(url)) {
                        url = `${this.baseUrl}${url}`;
                    }
                }
                const busyIndicator = this.showProgress ? (this.app.createBusyIndicator()) : null;
                try {
                    url = UMD.resolvePath(url);
                    let options = new AjaxOptions_1.AjaxOptions();
                    options.method = method;
                    if (methodOptions) {
                        options.headers = methodOptions.headers;
                        options.dataType = methodOptions.accept;
                    }
                    const methodHeaders = (options.headers = options.headers || {});
                    const headers = this.headers
                        ? (Object.assign(Object.assign({}, this.headers), methodHeaders))
                        : methodHeaders;
                    // this is necessary to support IsAjaxRequest in ASP.NET MVC
                    if (!headers["X-Requested-With"]) {
                        headers["X-Requested-With"] = "XMLHttpRequest";
                    }
                    options.dataType = options.dataType || "application/json";
                    const jsonOptions = Object.assign(Object.assign({}, this.jsonOptions), (methodOptions ? methodOptions.jsonOptions : {}));
                    if (bag) {
                        for (let i = 0; i < bag.length; i++) {
                            const p = bag[i];
                            const vi = values[i];
                            const v = vi === undefined ? p.defaultValue : vi;
                            if (v instanceof types_1.CancelToken) {
                                options.cancel = v;
                                continue;
                            }
                            switch (p.type) {
                                case "path":
                                    if (v === undefined) {
                                        continue;
                                    }
                                    const vs = v + "";
                                    const replacer = `{${p.key}}`;
                                    url = url.split(replacer).join(vs);
                                    break;
                                case "query":
                                    if (v === undefined) {
                                        continue;
                                    }
                                    if (url.indexOf("?") === -1) {
                                        url += "?";
                                    }
                                    if (!/(\&|\?)$/.test(url)) {
                                        url += "&";
                                    }
                                    url += `${encodeURIComponent(p.key)}=${encodeURIComponent(v)}`;
                                    break;
                                case "queries":
                                    if (url.indexOf("?") === -1) {
                                        url += "?";
                                    }
                                    if (!/(\&|\?)$/.test(url)) {
                                        url += "&";
                                    }
                                    for (const key in v) {
                                        if (v.hasOwnProperty(key)) {
                                            const element = v[key];
                                            if (element !== undefined) {
                                                url += `${encodeURIComponent(key)}=${encodeURIComponent(element)}&`;
                                            }
                                        }
                                    }
                                    break;
                                case "body":
                                    options.data = v;
                                    options = this.encodeData(options);
                                    break;
                                case "bodyformmodel":
                                    options.data = v;
                                    break;
                                case "rawbody":
                                    options.data = v;
                                    break;
                                case "xmlbody":
                                    options.contentType = "text/xml";
                                    options.data = v;
                                    break;
                                case "cancel":
                                    options.cancel = v;
                                    break;
                                case "header":
                                    if (v === undefined) {
                                        continue;
                                    }
                                    headers[p.key] = v;
                                    break;
                            }
                        }
                    }
                    options.url = url;
                    const xhr = yield this.ajax(url, options);
                    if (/json/i.test(xhr.responseType)) {
                        const text = xhr.responseText;
                        const response = this.jsonService.parse(text, jsonOptions);
                        if (xhr.status >= 400) {
                            throw new JsonError_1.default(typeof response === "string"
                                ? response
                                : (response.exceptionMessage
                                    || response.message
                                    || text
                                    || "Json Server Error"), response);
                        }
                        if (methodOptions && methodOptions.returnHeaders) {
                            return {
                                headers: this.parseHeaders(xhr.responseHeaders),
                                value: response
                            };
                        }
                        return response;
                    }
                    if (xhr.status >= 400) {
                        throw new Error(xhr.responseText || "Server Error");
                    }
                    if (methodOptions && methodOptions.returnHeaders) {
                        return {
                            headers: this.parseHeaders(xhr.responseHeaders),
                            value: xhr.responseText
                        };
                    }
                    return xhr.responseText;
                }
                finally {
                    if (busyIndicator) {
                        busyIndicator.dispose();
                    }
                }
            });
        }
        parseHeaders(headers) {
            if (typeof headers === "object") {
                return headers;
            }
            return (headers || "")
                .split("\n")
                .reduce((pv, c) => {
                const cv = c.split(":");
                pv[cv[0]] = (cv[1] || "").trim();
                return pv;
            }, {});
        }
        ajax(url, options) {
            return __awaiter(this, void 0, void 0, function* () {
                // return new CancellablePromise();
                url = url || options.url;
                // no longer needed, watch must provide functionality of waiting and cancelling
                // await Atom.delay(1, options.cancel);
                if (options.cancel && options.cancel.cancelled) {
                    throw new Error("cancelled");
                }
                if (AtomBridge_1.AtomBridge.instance.ajax) {
                    return yield new Promise((resolve, reject) => {
                        AtomBridge_1.AtomBridge.instance.ajax(url, options, (r) => {
                            resolve(options);
                        }, (e) => {
                            reject(e);
                        }, null);
                    });
                }
                const xhr = new XMLHttpRequest();
                return yield new Promise((resolve, reject) => {
                    if (options.cancel && options.cancel.cancelled) {
                        reject(options.cancel.cancelled);
                        return;
                    }
                    if (options.cancel) {
                        options.cancel.registerForCancel((r) => {
                            xhr.abort();
                            reject(r);
                            return;
                        });
                    }
                    xhr.onreadystatechange = (e) => {
                        if (xhr.readyState === XMLHttpRequest.DONE) {
                            options.status = xhr.status;
                            options.responseText = xhr.responseText;
                            // options.responseHeaders = (xhr.getAllResponseHeaders())
                            //     .split("\n")
                            //     .map((s) => s.trim().split(":"))
                            //     .reduce((pv, cv) => pv[cv[0]] = cv[1], {});
                            options.responseHeaders = xhr.getAllResponseHeaders();
                            const ct = xhr.getResponseHeader("content-type");
                            options.responseType = ct || xhr.responseType;
                            resolve(options);
                        }
                    };
                    xhr.open(options.method, url, true);
                    if (options.dataType) {
                        xhr.setRequestHeader("accept", options.dataType);
                    }
                    if (options.contentType) {
                        xhr.setRequestHeader("content-type", options.contentType);
                    }
                    const h = options.headers;
                    if (h) {
                        for (const key in h) {
                            if (h.hasOwnProperty(key)) {
                                const element = h[key];
                                xhr.setRequestHeader(key, element.toString());
                            }
                        }
                    }
                    try {
                        xhr.send(options.data);
                    }
                    catch (e) {
                        options.status = xhr.status;
                        options.responseText = xhr.responseText;
                        // options.responseHeaders = (xhr.getAllResponseHeaders())
                        //     .split("\n")
                        //     .map((s) => s.trim().split(":"))
                        //     .reduce((pv, cv) => pv[cv[0]] = cv[1], {});
                        options.responseHeaders = xhr.getAllResponseHeaders();
                        const ct = xhr.getResponseHeader("content-type");
                        options.responseType = ct || xhr.responseType;
                        resolve(options);
                    }
                });
            });
        }
    };
    BaseService.baseUrls = {};
    BaseService = __decorate([
        __param(0, Inject_1.Inject),
        __param(1, Inject_1.Inject),
        __metadata("design:paramtypes", [App_1.App,
            JsonService_1.JsonService])
    ], BaseService);
    exports.BaseService = BaseService;
});
//# sourceMappingURL=RestService.js.map