"use strict";
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 __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());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const es6_promisify_1 = require("es6-promisify");
const events_1 = __importDefault(require("events"));
const fs_1 = __importDefault(require("fs"));
const jsonfile_1 = __importDefault(require("jsonfile"));
const lodash_1 = require("lodash");
const node_fetch_1 = __importDefault(require("node-fetch"));
const path_1 = __importDefault(require("path"));
const typedi_1 = require("typedi");
const v4_1 = __importDefault(require("uuid/v4"));
const appInfo_1 = require("./appInfo");
const jsonfileWriteFile = es6_promisify_1.promisify(jsonfile_1.default.writeFile);
const fsUnlink = es6_promisify_1.promisify(fs_1.default.unlink);
exports.ErrNameExists = new Error('name exists');
let RuleService = class RuleService extends events_1.default {
    constructor(appInfoService) {
        super();
        // userId - > (filename -> rule)
        this.rules = {};
        // 缓存数据: 正在使用的规则 userId -> inUsingRuleList
        this.usingRuleCache = {};
        const proxyDataDir = appInfoService.getProxyDataDir();
        this.ruleSaveDir = path_1.default.join(proxyDataDir, 'rule');
        const contentMap = fs_1.default
            .readdirSync(this.ruleSaveDir)
            .filter(name => name.endsWith('.json'))
            .reduce((prev, curr) => {
            try {
                prev[curr] = jsonfile_1.default.readFileSync(path_1.default.join(this.ruleSaveDir, curr));
            }
            catch (e) {
                // ignore
            }
            return prev;
        }, {});
        lodash_1.forEach(contentMap, (content, fileName) => {
            const ruleName = content.name;
            const userId = fileName.substr(0, this._getUserIdLength(fileName, ruleName));
            this.rules[userId] = this.rules[userId] || {};
            this.rules[userId][ruleName] = content;
        });
    }
    // 创建规则文件
    createRuleFile(userId, name, description) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.rules[userId] && this.rules[userId][name]) {
                return exports.ErrNameExists;
            }
            const ruleFile = {
                checked: false,
                content: [],
                description,
                meta: {
                    ETag: '',
                    remote: false,
                    remoteETag: '',
                    url: '',
                },
                name,
            };
            this.rules[userId] = this.rules[userId] || {};
            this.rules[userId][name] = ruleFile;
            // 写文件
            const filePath = this._getRuleFilePath(userId, name);
            yield jsonfileWriteFile(filePath, ruleFile, { encoding: 'utf-8' });
            // 发送消息通知
            this.emit('data-change', userId, this.getRuleFileList(userId));
            return true;
        });
    }
    // 返回用户的规则文件列表
    getRuleFileList(userId) {
        const ruleMap = (this.rules[userId] = this.rules[userId] || {});
        const rulesLocal = [];
        const rulesRemote = [];
        lodash_1.forEach(ruleMap, content => {
            if (content.meta && content.meta.remote) {
                rulesRemote.push({
                    checked: content.checked,
                    description: content.description,
                    disableSync: content.disableSync,
                    meta: content.meta,
                    name: content.name,
                });
            }
            else {
                rulesLocal.push({
                    checked: content.checked,
                    description: content.description,
                    meta: content.meta,
                    name: content.name,
                });
            }
        });
        return rulesLocal.concat(rulesRemote);
    }
    // 删除规则文件
    deleteRuleFile(userId, name) {
        return __awaiter(this, void 0, void 0, function* () {
            const rule = this.rules[userId][name];
            delete this.rules[userId][name];
            const ruleFilePath = this._getRuleFilePath(userId, name);
            yield fsUnlink(ruleFilePath);
            // 发送消息通知
            this.emit('data-change', userId, this.getRuleFileList(userId));
            if (rule.checked) {
                // 清空缓存
                delete this.usingRuleCache[userId];
            }
        });
    }
    // 设置规则文件的使用状态
    setRuleFileCheckStatus(userId, name, checked) {
        return __awaiter(this, void 0, void 0, function* () {
            this.rules[userId][name].checked = checked;
            const ruleFilePath = this._getRuleFilePath(userId, name);
            yield jsonfileWriteFile(ruleFilePath, this.rules[userId][name], {
                encoding: 'utf-8',
            });
            // 发送消息通知
            this.emit('data-change', userId, this.getRuleFileList(userId));
            delete this.usingRuleCache[userId];
        });
    }
    // 设置规则文件的禁用同步状态
    setRuleFileDisableSync(userId, name, disable) {
        return __awaiter(this, void 0, void 0, function* () {
            this.rules[userId][name].disableSync = disable;
            const ruleFilePath = this._getRuleFilePath(userId, name);
            yield jsonfileWriteFile(ruleFilePath, this.rules[userId][name], {
                encoding: 'utf-8',
            });
            // 发送消息通知
            this.emit('data-change', userId, this.getRuleFileList(userId));
            delete this.usingRuleCache[userId];
        });
    }
    // 获取规则文件的内容
    getRuleFile(userId, name) {
        return this.rules[userId][name];
    }
    // 保存规则文件(可能是远程、或者本地)
    saveRuleFile(userId, ruleFile) {
        return __awaiter(this, void 0, void 0, function* () {
            const userRuleMap = this.rules[userId] || {};
            const originRuleFile = userRuleMap[ruleFile.name];
            if (originRuleFile) {
                ruleFile.checked = originRuleFile.checked;
                ruleFile.meta = originRuleFile.meta;
            }
            userRuleMap[ruleFile.name] = ruleFile;
            this.rules[userId] = userRuleMap;
            // 写文件
            const filePath = this._getRuleFilePath(userId, ruleFile.name);
            yield jsonfileWriteFile(filePath, userRuleMap[ruleFile.name], {
                encoding: 'utf-8',
            });
            // 清空缓存
            delete this.usingRuleCache[userId];
            this.emit('data-change', userId, this.getRuleFileList(userId));
        });
    }
    // 修改规则文件名称
    updateFileInfo(userId, originName, { name, description, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const userRuleMap = this.rules[userId] || {};
            if (userRuleMap[name] && name !== originName) {
                throw exports.ErrNameExists;
            }
            const ruleFile = userRuleMap[originName];
            // 删除旧的rule
            delete this.rules[userId][originName];
            const ruleFilePath = this._getRuleFilePath(userId, originName);
            yield fsUnlink(ruleFilePath);
            // 修改rule名称
            ruleFile.name = name;
            ruleFile.description = description;
            yield this.saveRuleFile(userId, ruleFile);
        });
    }
    /**
     * 根据请求,获取处理请求的规则
     * @param method
     * @param urlObj
     */
    getProcessRule(userId, method, urlObj) {
        let candidateRule = null;
        const inusingRules = this._getInuseRules(userId);
        for (const rule of inusingRules) {
            // 捕获规则
            if (this._isUrlMatch(urlObj.href, rule.match) &&
                this._isMethodMatch(method, rule.method)) {
                candidateRule = rule;
                break;
            }
        }
        return candidateRule;
    }
    importRemoteRuleFile(userId, url) {
        return __awaiter(this, void 0, void 0, function* () {
            const ruleFile = yield this.fetchRemoteRuleFile(url);
            ruleFile.content.forEach(rule => {
                if (rule.action && !rule.actionList) {
                    rule.actionList = [rule.action];
                }
            });
            yield this.saveRuleFile(userId, ruleFile);
            return ruleFile;
        });
    }
    fetchRemoteRuleFile(url) {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield node_fetch_1.default(url);
            const responseData = yield response.json();
            const ETag = response.headers.etag || '';
            const content = responseData.content.map(remoteRule => {
                if (remoteRule.action && !remoteRule.actionList) {
                    remoteRule.actionList = [remoteRule.action];
                }
                const actionList = remoteRule.actionList.map(remoteAction => {
                    const actionData = {
                        dataId: '',
                        headerKey: remoteAction.data.headerKey || '',
                        headerValue: remoteAction.data.headerValue || '',
                        target: remoteAction.data.target || '',
                    };
                    const action = {
                        data: actionData,
                        type: remoteAction.type,
                    };
                    return action;
                });
                const rule = {
                    actionList,
                    checked: remoteRule.checked,
                    key: remoteRule.key || v4_1.default(),
                    match: remoteRule.match,
                    method: remoteRule.method,
                    name: remoteRule.name,
                };
                return rule;
            });
            const ruleFile = {
                checked: false,
                content,
                description: responseData.description,
                meta: {
                    ETag,
                    remote: true,
                    remoteETag: ETag,
                    url,
                },
                name: responseData.name,
            };
            return ruleFile;
        });
    }
    copyRuleFile(userId, name) {
        return __awaiter(this, void 0, void 0, function* () {
            const ruleFile = this.getRuleFile(userId, name);
            if (!ruleFile) {
                return;
            }
            const copied = lodash_1.cloneDeep(ruleFile);
            copied.checked = false;
            copied.name = `${copied.name}-复制`;
            copied.meta = Object.assign({}, copied.meta, {
                isCopy: true,
                remote: false,
            });
            yield this.saveRuleFile(userId, copied);
            return copied;
        });
    }
    _getInuseRules(userId) {
        if (this.usingRuleCache[userId]) {
            return this.usingRuleCache[userId];
        }
        const ruleMap = this.rules[userId] || {};
        // 计算使用中的规则
        const rulesLocal = [];
        const rulesRemote = [];
        lodash_1.forEach(ruleMap, (file, filename) => {
            if (!file.checked) {
                return;
            }
            lodash_1.forEach(file.content, rule => {
                if (!rule.checked) {
                    return;
                }
                const copy = lodash_1.cloneDeep(rule);
                copy.ruleFileName = filename;
                if (file.meta && file.meta.remote) {
                    rulesRemote.push(copy);
                }
                else {
                    rulesLocal.push(copy);
                }
            });
        });
        const merged = rulesLocal.concat(rulesRemote);
        this.usingRuleCache[userId] = merged;
        return merged;
    }
    _getRuleFilePath(userId, ruleName) {
        const fileName = `${userId}_${ruleName}.json`;
        const filePath = path_1.default.join(this.ruleSaveDir, fileName);
        return filePath;
    }
    _getUserIdLength(ruleFileName, ruleName) {
        return ruleFileName.length - ruleName.length - 6;
    }
    // 请求的方法是否匹配规则
    _isMethodMatch(reqMethod, ruleMethod) {
        const loweredReqMethod = lodash_1.lowerCase(reqMethod);
        const loweredRuleMethod = lodash_1.lowerCase(ruleMethod);
        return (loweredReqMethod === loweredRuleMethod ||
            !ruleMethod ||
            loweredReqMethod === 'option');
    }
    // 请求的url是否匹配规则
    _isUrlMatch(reqUrl, ruleMatchStr) {
        return (ruleMatchStr &&
            (reqUrl.indexOf(ruleMatchStr) >= 0 ||
                new RegExp(ruleMatchStr).test(reqUrl)));
    }
};
RuleService = __decorate([
    typedi_1.Service(),
    __metadata("design:paramtypes", [appInfo_1.AppInfoService])
], RuleService);
exports.RuleService = RuleService;
//# sourceMappingURL=rule.js.map