"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InClauseUnderConjunction = void 0;
var tslib_1 = require("tslib");
var path = require("path");
var head = require("lodash.head");
var values = require("lodash.values");
var keys = require("lodash.keys");
var get = require("lodash.get");
var flattenDeep = require("lodash.flattendeep");
var isEmpty = require("lodash.isempty");
var startsWith = require("lodash.startswith");
var includes = require("lodash.includes");
var compact = require("lodash.compact");
var ddfcsv_error_1 = require("../ddfcsv-error");
var ddf_query_validator_1 = require("ddf-query-validator");
var Papa = require('papaparse');
var WHERE_KEYWORD = 'where';
var JOIN_KEYWORD = 'join';
var KEY_IN = '$in';
var KEY_NIN = '$nin';
var KEY_AND = '$and';
var KEY_OR = '$or';
var getFirstConditionClause = function (clause) { return head(values(clause)); };
var getFirstKey = function (obj) { return head(keys(obj)); };
var isOneKeyBased = function (obj) { return keys(obj).length === 1; };
var InClauseUnderConjunction = (function () {
    function InClauseUnderConjunction(queryParam, options) {
        this.options = options;
        this.flow = {};
        this.fileReader = options.fileReader;
        this.datasetPath = options.basePath;
        this.query = queryParam;
        this.datapackage = options.datapackage;
        this.conceptsLookup = options.conceptsLookup;
    }
    InClauseUnderConjunction.prototype.isMatched = function () {
        var _this = this;
        this.flow.joinObject = get(this.query, JOIN_KEYWORD);
        var relatedFeatures = compact(ddf_query_validator_1.featureDetectors.map(function (detector) { return detector(_this.query, _this.conceptsLookup); }));
        return this.query.from === "datapoints";
    };
    InClauseUnderConjunction.prototype.getRecommendedFilesSet = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _a, debug, error, warning, result, data, err_1, message;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        _a = this.options.diagnostic.prepareDiagnosticFor('getRecommendedFilesSet'), debug = _a.debug, error = _a.error, warning = _a.warning;
                        if (!this.isMatched()) return [3, 5];
                        debug('plugin matched');
                        result = void 0;
                        _b.label = 1;
                    case 1:
                        _b.trys.push([1, 3, , 4]);
                        this.fillResourceToFileHash();
                        this.collectProcessableClauses();
                        this.collectEntityFilesNames();
                        return [4, this.collectEntities()];
                    case 2:
                        data = _b.sent();
                        this.fillEntityValuesHash(data);
                        this.getFilesGroupsQueryClause();
                        result = this.getOptimalFilesGroup();
                        return [3, 4];
                    case 3:
                        err_1 = _b.sent();
                        error('wrong data processing', err_1);
                        return [2, []];
                    case 4:
                        debug('recommended files found', result);
                        return [2, result];
                    case 5:
                        message = "Plugin \"InClauseUnderConjunction\" is not matched!";
                        warning(message);
                        throw new ddfcsv_error_1.DdfCsvError(message, 'InClauseUnderConjunction plugin');
                }
            });
        });
    };
    InClauseUnderConjunction.prototype.fillResourceToFileHash = function () {
        this.flow.resourceToFile = get(this.datapackage, 'resources', []).reduce(function (hash, resource) {
            var constraints = resource.schema.fields.reduce(function (result, field) {
                var _a;
                if ((_a = field.constraints) === null || _a === void 0 ? void 0 : _a.enum) {
                    result.set(field.name, field.constraints.enum);
                }
                return result;
            }, new Map());
            hash.set(resource.name, {
                path: resource.path,
                constraints: constraints
            });
            return hash;
        }, new Map());
        return this;
    };
    InClauseUnderConjunction.prototype.collectProcessableClauses = function () {
        var e_1, _a, _b, _c, _d;
        var _this = this;
        var joinKeys = keys(this.flow.joinObject);
        this.flow.processableClauses = [];
        try {
            for (var joinKeys_1 = tslib_1.__values(joinKeys), joinKeys_1_1 = joinKeys_1.next(); !joinKeys_1_1.done; joinKeys_1_1 = joinKeys_1.next()) {
                var joinKey = joinKeys_1_1.value;
                var where = get(this.flow.joinObject, "".concat(joinKey, ".").concat(WHERE_KEYWORD), {});
                if (this.singleAndField(where)) {
                    (_b = this.flow.processableClauses).push.apply(_b, tslib_1.__spreadArray([], tslib_1.__read(flattenDeep(where[KEY_AND].map(function (el) { return _this.getProcessableClauses(el); }))), false));
                }
                else if (this.singleOrField(where)) {
                    (_c = this.flow.processableClauses).push.apply(_c, tslib_1.__spreadArray([], tslib_1.__read(flattenDeep(where[KEY_OR].map(function (el) { return _this.getProcessableClauses(el); }))), false));
                }
                else {
                    (_d = this.flow.processableClauses).push.apply(_d, tslib_1.__spreadArray([], tslib_1.__read(this.getProcessableClauses(where)), false));
                }
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (joinKeys_1_1 && !joinKeys_1_1.done && (_a = joinKeys_1.return)) _a.call(joinKeys_1);
            }
            finally { if (e_1) throw e_1.error; }
        }
        return this;
    };
    InClauseUnderConjunction.prototype.collectEntityFilesNames = function () {
        var e_2, _a, e_3, _b, e_4, _c;
        this.flow.entityFilesNames = new Set();
        this.flow.fileNameToPrimaryKeyHash = new Map();
        try {
            for (var _d = tslib_1.__values(this.datapackage.ddfSchema.entities), _e = _d.next(); !_e.done; _e = _d.next()) {
                var schemaResourceRecord = _e.value;
                try {
                    for (var _f = (e_3 = void 0, tslib_1.__values(this.flow.processableClauses)), _g = _f.next(); !_g.done; _g = _f.next()) {
                        var clause = _g.value;
                        var primaryKey = getFirstKey(clause);
                        if (head(schemaResourceRecord.primaryKey) === primaryKey) {
                            try {
                                for (var _h = (e_4 = void 0, tslib_1.__values(schemaResourceRecord.resources)), _j = _h.next(); !_j.done; _j = _h.next()) {
                                    var resourceName = _j.value;
                                    var file = this.flow.resourceToFile.get(resourceName).path;
                                    this.flow.entityFilesNames.add(file);
                                    this.flow.fileNameToPrimaryKeyHash.set(file, primaryKey);
                                }
                            }
                            catch (e_4_1) { e_4 = { error: e_4_1 }; }
                            finally {
                                try {
                                    if (_j && !_j.done && (_c = _h.return)) _c.call(_h);
                                }
                                finally { if (e_4) throw e_4.error; }
                            }
                        }
                    }
                }
                catch (e_3_1) { e_3 = { error: e_3_1 }; }
                finally {
                    try {
                        if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
                    }
                    finally { if (e_3) throw e_3.error; }
                }
            }
        }
        catch (e_2_1) { e_2 = { error: e_2_1 }; }
        finally {
            try {
                if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
            }
            finally { if (e_2) throw e_2.error; }
        }
        return this;
    };
    InClauseUnderConjunction.prototype.collectEntities = function () {
        var self = this;
        var actions = tslib_1.__spreadArray([], tslib_1.__read(self.flow.entityFilesNames), false).map(function (file) { return new Promise(function (actResolve, actReject) {
            self.fileReader.readText(path.join(self.datasetPath, file), function (err, text) {
                if (err) {
                    return actReject(err);
                }
                Papa.parse(text, {
                    header: true,
                    skipEmptyLines: true,
                    complete: function (result) { return actResolve({ file: file, result: result }); },
                    error: function (error) { return actReject(error); }
                });
            });
        }); });
        return Promise.all(actions);
    };
    InClauseUnderConjunction.prototype.fillEntityValuesHash = function (entitiesData) {
        var e_5, _a, e_6, _b;
        var getSubdomainsFromRecord = function (record) { return compact(keys(record)
            .filter(function (key) { return startsWith(key, 'is--') && (record[key] === 'TRUE' || record[key] === 'true'); })
            .map(function (key) { return key.replace(/^is--/, ''); })); };
        this.flow.entityValueToFileHash = new Map();
        this.flow.entityValueToDomainHash = new Map();
        try {
            for (var entitiesData_1 = tslib_1.__values(entitiesData), entitiesData_1_1 = entitiesData_1.next(); !entitiesData_1_1.done; entitiesData_1_1 = entitiesData_1.next()) {
                var entityFileDescriptor = entitiesData_1_1.value;
                try {
                    for (var _c = (e_6 = void 0, tslib_1.__values(entityFileDescriptor.result.data)), _d = _c.next(); !_d.done; _d = _c.next()) {
                        var entityRecord = _d.value;
                        var primaryKeyForThisFile = this.flow.fileNameToPrimaryKeyHash.get(entityFileDescriptor.file);
                        var domainsForCurrentRecord = tslib_1.__spreadArray([], tslib_1.__read(getSubdomainsFromRecord(entityRecord)), false);
                        if (isEmpty(domainsForCurrentRecord)) {
                            domainsForCurrentRecord.push(primaryKeyForThisFile);
                        }
                        var primaryKeyCellValue = entityRecord[primaryKeyForThisFile] || entityRecord[domainsForCurrentRecord[0]];
                        this.flow.entityValueToDomainHash.set(primaryKeyCellValue, domainsForCurrentRecord);
                        this.flow.entityValueToFileHash.set(primaryKeyCellValue, entityFileDescriptor.file);
                    }
                }
                catch (e_6_1) { e_6 = { error: e_6_1 }; }
                finally {
                    try {
                        if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
                    }
                    finally { if (e_6) throw e_6.error; }
                }
            }
        }
        catch (e_5_1) { e_5 = { error: e_5_1 }; }
        finally {
            try {
                if (entitiesData_1_1 && !entitiesData_1_1.done && (_a = entitiesData_1.return)) _a.call(entitiesData_1);
            }
            finally { if (e_5) throw e_5.error; }
        }
        return this;
    };
    InClauseUnderConjunction.prototype.getFilesGroupsQueryClause = function () {
        var e_7, _a, e_8, _b, e_9, _c, e_10, _d, e_11, _e, e_12, _f, e_13, _g;
        var _this = this;
        var getEntitiesExcept = function (entityValuesToExclude) {
            var e_14, _a;
            var result = [];
            try {
                for (var _b = tslib_1.__values(_this.flow.entityValueToDomainHash.keys()), _c = _b.next(); !_c.done; _c = _b.next()) {
                    var entityKey = _c.value;
                    if (!includes(entityValuesToExclude, entityKey)) {
                        result.push(entityKey);
                    }
                }
            }
            catch (e_14_1) { e_14 = { error: e_14_1 }; }
            finally {
                try {
                    if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
                }
                finally { if (e_14) throw e_14.error; }
            }
            return result;
        };
        var filesGroupsByClause = new Map();
        try {
            for (var _h = tslib_1.__values(this.flow.processableClauses), _j = _h.next(); !_j.done; _j = _h.next()) {
                var clause = _j.value;
                var filesGroupByClause = {
                    entities: this.flow.entityFilesNames,
                    datapoints: new Set(),
                    concepts: new Set()
                };
                var firstConditionClause = getFirstConditionClause(clause);
                var entityValuesFromClause = firstConditionClause[KEY_IN] || getEntitiesExcept(firstConditionClause[KEY_NIN]);
                try {
                    for (var entityValuesFromClause_1 = (e_8 = void 0, tslib_1.__values(entityValuesFromClause)), entityValuesFromClause_1_1 = entityValuesFromClause_1.next(); !entityValuesFromClause_1_1.done; entityValuesFromClause_1_1 = entityValuesFromClause_1.next()) {
                        var entityValueFromClause = entityValuesFromClause_1_1.value;
                        var entitiesByQuery = this.flow.entityValueToDomainHash.get(entityValueFromClause);
                        try {
                            for (var entitiesByQuery_1 = (e_9 = void 0, tslib_1.__values(entitiesByQuery)), entitiesByQuery_1_1 = entitiesByQuery_1.next(); !entitiesByQuery_1_1.done; entitiesByQuery_1_1 = entitiesByQuery_1.next()) {
                                var entityByQuery = entitiesByQuery_1_1.value;
                                try {
                                    for (var _k = (e_10 = void 0, tslib_1.__values(this.datapackage.ddfSchema.datapoints)), _l = _k.next(); !_l.done; _l = _k.next()) {
                                        var schemaResourceRecord = _l.value;
                                        try {
                                            for (var _m = (e_11 = void 0, tslib_1.__values(schemaResourceRecord.resources)), _o = _m.next(); !_o.done; _o = _m.next()) {
                                                var resourceName = _o.value;
                                                var file = this.flow.resourceToFile.get(resourceName);
                                                if (includes(schemaResourceRecord.primaryKey, entityByQuery)) {
                                                    var constraint = file.constraints.get(entityByQuery);
                                                    if (constraint) {
                                                        if (constraint.includes(entityValueFromClause)) {
                                                            filesGroupByClause.datapoints.add(file.path);
                                                        }
                                                    }
                                                    else {
                                                        filesGroupByClause.datapoints.add(file.path);
                                                    }
                                                }
                                            }
                                        }
                                        catch (e_11_1) { e_11 = { error: e_11_1 }; }
                                        finally {
                                            try {
                                                if (_o && !_o.done && (_e = _m.return)) _e.call(_m);
                                            }
                                            finally { if (e_11) throw e_11.error; }
                                        }
                                    }
                                }
                                catch (e_10_1) { e_10 = { error: e_10_1 }; }
                                finally {
                                    try {
                                        if (_l && !_l.done && (_d = _k.return)) _d.call(_k);
                                    }
                                    finally { if (e_10) throw e_10.error; }
                                }
                            }
                        }
                        catch (e_9_1) { e_9 = { error: e_9_1 }; }
                        finally {
                            try {
                                if (entitiesByQuery_1_1 && !entitiesByQuery_1_1.done && (_c = entitiesByQuery_1.return)) _c.call(entitiesByQuery_1);
                            }
                            finally { if (e_9) throw e_9.error; }
                        }
                    }
                }
                catch (e_8_1) { e_8 = { error: e_8_1 }; }
                finally {
                    try {
                        if (entityValuesFromClause_1_1 && !entityValuesFromClause_1_1.done && (_b = entityValuesFromClause_1.return)) _b.call(entityValuesFromClause_1);
                    }
                    finally { if (e_8) throw e_8.error; }
                }
                try {
                    for (var _p = (e_12 = void 0, tslib_1.__values(this.datapackage.ddfSchema.concepts)), _q = _p.next(); !_q.done; _q = _p.next()) {
                        var schemaResourceRecord = _q.value;
                        try {
                            for (var _r = (e_13 = void 0, tslib_1.__values(schemaResourceRecord.resources)), _s = _r.next(); !_s.done; _s = _r.next()) {
                                var resourceName = _s.value;
                                filesGroupByClause.concepts.add(this.flow.resourceToFile.get(resourceName).path);
                            }
                        }
                        catch (e_13_1) { e_13 = { error: e_13_1 }; }
                        finally {
                            try {
                                if (_s && !_s.done && (_g = _r.return)) _g.call(_r);
                            }
                            finally { if (e_13) throw e_13.error; }
                        }
                    }
                }
                catch (e_12_1) { e_12 = { error: e_12_1 }; }
                finally {
                    try {
                        if (_q && !_q.done && (_f = _p.return)) _f.call(_p);
                    }
                    finally { if (e_12) throw e_12.error; }
                }
                filesGroupsByClause.set(clause, filesGroupByClause);
            }
        }
        catch (e_7_1) { e_7 = { error: e_7_1 }; }
        finally {
            try {
                if (_j && !_j.done && (_a = _h.return)) _a.call(_h);
            }
            finally { if (e_7) throw e_7.error; }
        }
        this.flow.filesGroupsByClause = filesGroupsByClause;
        return this;
    };
    InClauseUnderConjunction.prototype.getOptimalFilesGroup = function () {
        var e_15, _a;
        var clauseKeys = this.flow.filesGroupsByClause.keys();
        var appropriateClauseKey;
        var appropriateClauseSize;
        try {
            for (var clauseKeys_1 = tslib_1.__values(clauseKeys), clauseKeys_1_1 = clauseKeys_1.next(); !clauseKeys_1_1.done; clauseKeys_1_1 = clauseKeys_1.next()) {
                var key = clauseKeys_1_1.value;
                var size = this.flow.filesGroupsByClause.get(key).datapoints.size +
                    this.flow.filesGroupsByClause.get(key).entities.size +
                    this.flow.filesGroupsByClause.get(key).concepts.size;
                if (!appropriateClauseKey || size < appropriateClauseSize) {
                    appropriateClauseKey = key;
                    appropriateClauseSize = size;
                }
            }
        }
        catch (e_15_1) { e_15 = { error: e_15_1 }; }
        finally {
            try {
                if (clauseKeys_1_1 && !clauseKeys_1_1.done && (_a = clauseKeys_1.return)) _a.call(clauseKeys_1);
            }
            finally { if (e_15) throw e_15.error; }
        }
        if (!this.flow.filesGroupsByClause.get(appropriateClauseKey)) {
            return [];
        }
        return tslib_1.__spreadArray(tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(Array.from(this.flow.filesGroupsByClause.get(appropriateClauseKey).concepts)), false), tslib_1.__read(Array.from(this.flow.filesGroupsByClause.get(appropriateClauseKey).entities)), false), tslib_1.__read(Array.from(this.flow.filesGroupsByClause.get(appropriateClauseKey).datapoints)), false);
    };
    InClauseUnderConjunction.prototype.getProcessableClauses = function (clause) {
        var e_16, _a;
        var result = [];
        var clauseKeys = keys(clause);
        try {
            for (var clauseKeys_2 = tslib_1.__values(clauseKeys), clauseKeys_2_1 = clauseKeys_2.next(); !clauseKeys_2_1.done; clauseKeys_2_1 = clauseKeys_2.next()) {
                var key = clauseKeys_2_1.value;
                if (!startsWith(key, '$') && isOneKeyBased(clause[key])) {
                    var conditionKey = head(keys(clause[key]));
                    if (conditionKey === KEY_IN || conditionKey === KEY_NIN) {
                        result.push(clause);
                    }
                }
            }
        }
        catch (e_16_1) { e_16 = { error: e_16_1 }; }
        finally {
            try {
                if (clauseKeys_2_1 && !clauseKeys_2_1.done && (_a = clauseKeys_2.return)) _a.call(clauseKeys_2);
            }
            finally { if (e_16) throw e_16.error; }
        }
        return result;
    };
    InClauseUnderConjunction.prototype.singleAndField = function (clause) {
        return isOneKeyBased(clause) && !!get(clause, KEY_AND);
    };
    InClauseUnderConjunction.prototype.singleOrField = function (clause) {
        return isOneKeyBased(clause) && !!get(clause, KEY_OR);
    };
    return InClauseUnderConjunction;
}());
exports.InClauseUnderConjunction = InClauseUnderConjunction;
//# sourceMappingURL=in-clause-under-conjunction.js.map