"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CdmDataPartitionPatternDefinition = void 0;
const internal_1 = require("../internal");
const cdmObjectTypeGuards_1 = require("../Utilities/cdmObjectTypeGuards");
const Logger_1 = require("../Utilities/Logging/Logger");
const StorageUtils_1 = require("../Utilities/StorageUtils");
const using_statement_1 = require("using-statement");
/**
 * The object model implementation for Data Partition Pattern.
 */
class CdmDataPartitionPatternDefinition extends internal_1.CdmObjectDefinitionBase {
    /**
     * Creates a new instance of Data Partition Pattern Impl.
     * @param ctx The context.
     * @param name The name.
     */
    constructor(ctx, name) {
        super(ctx);
        this.TAG = CdmDataPartitionPatternDefinition.name;
        this.objectType = internal_1.cdmObjectType.dataPartitionPatternDef;
        this.name = name;
    }
    /**
     * LastChildFileModifiedTime is not valid for DataPartitionPatterns since they do not contain any children objects.
     */
    get lastChildFileModifiedTime() {
        throw new Error('Not implemented');
    }
    /**
     * LastChildFileModifiedTime is not valid for DataPartitionPatterns since they do not contain any children objects.
     */
    set lastChildFileModifiedTime(time) {
        throw new Error('Not implemented');
    }
    static get objectType() {
        return internal_1.cdmObjectType.dataPartitionPatternDef;
    }
    /**
     * @inheritdoc
     */
    getObjectType() {
        return internal_1.cdmObjectType.dataPartitionPatternDef;
    }
    /**
     * @inheritdoc
     */
    validate() {
        if (!this.rootLocation) {
            let missingFields = ['rootLocation'];
            Logger_1.Logger.error(this.ctx, this.TAG, this.validate.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnIntegrityCheckFailure, missingFields.map((s) => `'${s}'`).join(', '), this.atCorpusPath);
            return false;
        }
        return true;
    }
    /**
     * @inheritdoc
     */
    copy(resOpt, host) {
        if (resOpt === undefined) {
            resOpt = new internal_1.resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
        }
        let copy;
        if (!host) {
            copy = new CdmDataPartitionPatternDefinition(this.ctx, this.name);
        }
        else {
            copy = host;
            copy.name = this.name;
        }
        copy.rootLocation = this.rootLocation;
        copy.globPattern = this.globPattern;
        copy.regularExpression = this.regularExpression;
        copy.lastFileStatusCheckTime = this.lastFileStatusCheckTime;
        copy.lastFileModifiedTime = this.lastFileModifiedTime;
        copy.parameters = this.parameters ? this.parameters.slice() : undefined;
        if (this.specializedSchema) {
            copy.specializedSchema = this.specializedSchema;
        }
        this.copyDef(resOpt, copy);
        return copy;
    }
    /**
     * @inheritdoc
     */
    getName() {
        return this.name;
    }
    /**
     * @inheritdoc
     */
    visit(pathFrom, preChildren, postChildren) {
        const path = this.fetchDeclaredPath(pathFrom);
        if (preChildren && preChildren(this, path)) {
            return false;
        }
        if (this.visitDef(path, preChildren, postChildren)) {
            return true;
        }
        if (postChildren && postChildren(this, path)) {
            return true;
        }
        return false;
    }
    /**
     * @internal
     */
    fetchDeclaredPath(pathFrom) {
        return pathFrom + (this.getName() || 'UNNAMED');
    }
    /**
     * @inheritdoc
     */
    isDerivedFrom(base, resOpt) {
        return false;
    }
    /**
     * @inheritdoc
     */
    fileStatusCheckAsync() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield using_statement_1.using(Logger_1.enterScope(CdmDataPartitionPatternDefinition.name, this.ctx, this.fileStatusCheckAsync.name), (_) => __awaiter(this, void 0, void 0, function* () {
                let namespace = undefined;
                let adapter = undefined;
                // make sure the root is a good full corpus path
                let rootCleaned = this.rootLocation && this.rootLocation.endsWith('/') ? this.rootLocation.substring(0, this.rootLocation.length - 1) : this.rootLocation;
                if (rootCleaned === undefined) {
                    rootCleaned = '';
                }
                const rootCorpus = this.ctx.corpus.storage.createAbsoluteCorpusPath(rootCleaned, this.inDocument);
                let fileInfoList;
                try {
                    // Remove namespace from path
                    const pathTuple = StorageUtils_1.StorageUtils.splitNamespacePath(rootCorpus);
                    if (!pathTuple) {
                        Logger_1.Logger.error(this.ctx, this.TAG, this.fileStatusCheckAsync.name, this.atCorpusPath, internal_1.cdmLogCode.ErrStorageNullCorpusPath);
                        return;
                    }
                    namespace = pathTuple[0];
                    adapter = this.ctx.corpus.storage.fetchAdapter(namespace);
                    if (adapter === undefined) {
                        Logger_1.Logger.error(this.ctx, this.TAG, this.fileStatusCheckAsync.name, this.atCorpusPath, internal_1.cdmLogCode.ErrDocAdapterNotFound, this.inDocument.name);
                        return;
                    }
                    // get a list of all corpusPaths under the root
                    fileInfoList = yield adapter.fetchAllFilesAsync(pathTuple[1]);
                }
                catch (e) {
                    Logger_1.Logger.warning(this.ctx, this.TAG, this.fileStatusCheckAsync.name, this.atCorpusPath, internal_1.cdmLogCode.WarnPartitionFileFetchFailed, rootCorpus, e.Message);
                }
                if (fileInfoList !== undefined && namespace !== undefined) {
                    // remove root of the search from the beginning of all paths so anything in the root is not found by regex
                    for (let i = 0; i < fileInfoList.length; i++) {
                        fileInfoList[i] = `${namespace}:${fileInfoList[i]}`;
                        fileInfoList[i] = fileInfoList[i].slice(rootCorpus.length);
                    }
                    if (cdmObjectTypeGuards_1.isLocalEntityDeclarationDefinition(this.owner)) {
                        // if both are present log warning and use glob pattern, otherwise use regularExpression
                        if (this.globPattern && this.globPattern.trim() !== '' && this.regularExpression && this.regularExpression.trim() !== '') {
                            Logger_1.Logger.warning(this.ctx, this.TAG, this.fileStatusCheckAsync.name, this.atCorpusPath, internal_1.cdmLogCode.WarnPartitionGlobAndRegexPresent, this.globPattern, this.regularExpression);
                        }
                        const regularExpression = this.globPattern && this.globPattern.trim() !== '' ? this.globPatternToRegex(this.globPattern) : this.regularExpression;
                        let regexPattern;
                        try {
                            regexPattern = new RegExp(regularExpression);
                        }
                        catch (e) {
                            Logger_1.Logger.error(this.ctx, this.TAG, this.fileStatusCheckAsync.name, this.atCorpusPath, internal_1.cdmLogCode.ErrValdnInvalidExpression, this.globPattern, this.regularExpression, e.message);
                        }
                        if (regexPattern !== undefined) {
                            const dataPartitionPathSet = new Set();
                            if (this.owner && (this.owner).dataPartitions) {
                                for (const dataPartition of (this.owner).dataPartitions) {
                                    const fullPath = this.ctx.corpus.storage.createAbsoluteCorpusPath(dataPartition.location, this.inDocument);
                                    dataPartitionPathSet.add(fullPath);
                                }
                            }
                            for (const fi of fileInfoList) {
                                const m = regexPattern.exec(fi);
                                if (m && m.length > 0 && m[0] === fi) {
                                    // create a map of arguments out of capture groups
                                    const args = new Map();
                                    // captures start after the string match at m[0]
                                    for (let i = 1; i < m.length; i++) {
                                        const iParam = i - 1;
                                        if (this.parameters && iParam < this.parameters.length) {
                                            const currentParam = this.parameters[iParam];
                                            if (!args.has(currentParam)) {
                                                args.set(currentParam, []);
                                            }
                                            args.get(currentParam)
                                                .push(m[i]);
                                        }
                                    }
                                    // put the origial but cleaned up root back onto the matched doc as the location stored in the partition
                                    const locationCorpusPath = `${rootCleaned}${fi}`;
                                    const fullPath = `${rootCorpus}${fi}`;
                                    // Remove namespace from path
                                    const pathTuple = StorageUtils_1.StorageUtils.splitNamespacePath(fullPath);
                                    if (!pathTuple) {
                                        Logger_1.Logger.error(this.ctx, this.TAG, this.fileStatusCheckAsync.name, this.atCorpusPath, internal_1.cdmLogCode.ErrStorageNullCorpusPath);
                                        return;
                                    }
                                    const lastModifiedTime = yield adapter.computeLastModifiedTimeAsync(pathTuple[1]);
                                    if (!dataPartitionPathSet.has(fullPath)) {
                                        (this.owner).createDataPartitionFromPattern(locationCorpusPath, this.exhibitsTraits, args, this.specializedSchema, lastModifiedTime);
                                        dataPartitionPathSet.add(fullPath);
                                    }
                                }
                            }
                        }
                    }
                }
                // update modified times
                this.lastFileStatusCheckTime = new Date();
            }));
        });
    }
    /**
     * @inheritdoc
     */
    reportMostRecentTimeAsync(childTime) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.owner.reportMostRecentTimeAsync && childTime) {
                yield this.owner.reportMostRecentTimeAsync(childTime);
            }
        });
    }
    /**
     * Converts a glob pattern to a regular expression
     */
    globPatternToRegex(pattern) {
        const newPattern = [];
        // all patterns should start with a slash
        newPattern.push("[/\\\\]");
        // if pattern starts with slash, skip the first character. We already added it above
        for (let i = (pattern[0] === '/' || pattern[0] === '\\' ? 1 : 0); i < pattern.length; i++) {
            const currChar = pattern[i];
            switch (currChar) {
                case '.':
                    // escape '.' characters
                    newPattern.push('\\.');
                    break;
                case '\\':
                    // convert backslash into slash
                    newPattern.push('[/\\\\]');
                    break;
                case '?':
                    // question mark in glob matches any single character
                    newPattern.push('.');
                    break;
                case '*':
                    const nextChar = i + 1 < pattern.length ? pattern[i + 1] : undefined;
                    if (nextChar === '*') {
                        const prevChar = i - 1 >= 0 ? pattern[i - 1] : undefined;
                        const postChar = i + 2 < pattern.length ? pattern[i + 2] : undefined;
                        // globstar must be at beginning of pattern, end of pattern, or wrapped in separator characters
                        if ((prevChar === undefined || prevChar === '/' || prevChar === '\\')
                            && (postChar === undefined || postChar === '/' || postChar === '\\')) {
                            newPattern.push('.*');
                            // globstar can match zero or more subdirectories. If it matches zero, then there should not be
                            // two consecutive '/' characters so make the second one optional
                            if ((prevChar === '/' || prevChar === '\\') && (postChar === '/' || postChar === '\\')) {
                                newPattern.push('/?');
                                i++;
                            }
                        }
                        else {
                            // otherwise, treat the same as '*'
                            newPattern.push('[^\/\\\\]*');
                        }
                        i++;
                    }
                    else {
                        // *
                        newPattern.push('[^\/\\\\]*');
                    }
                    break;
                default:
                    newPattern.push(currChar);
            }
        }
        return newPattern.join('');
    }
}
exports.CdmDataPartitionPatternDefinition = CdmDataPartitionPatternDefinition;
//# sourceMappingURL=CdmDataPartitionPatternDefinition.js.map