"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var _ = require("./utils");
var xmlUtil = require("./xmlUtil");
var jsonUtil = require("./jsonUtil");
var DOMParser = require('xmldom').DOMParser;
var ts = require("./typesystem");
var typesystem_1 = require("./typesystem");
var jsonToAST = require("json-to-ast");
var ValidationResult = /** @class */ (function () {
    function ValidationResult() {
    }
    return ValidationResult;
}());
exports.ValidationResult = ValidationResult;
var domParserOptions = {
    errorHandler: {
        warning: function () { return null; },
        error: function () { return null; },
        fatalError: function () { return null; }
    }
};
var useLint = true;
var CACHE_SIZE_BARRIER = 5 * 1024 * 1024;
var ErrorsCache = /** @class */ (function () {
    function ErrorsCache() {
        this.errors = {};
        this.size = 0;
    }
    ErrorsCache.prototype.getValue = function (key) {
        var e = this.errors[key];
        if (!e) {
            return null;
        }
        return e.value;
    };
    ErrorsCache.prototype.setValue = function (key, value) {
        var e = this.errors[key];
        if (!e) {
            e = {
                key: key,
                value: value
            };
            if (this.top) {
                this.top.next = e;
            }
            this.top = e;
            if (!this.last) {
                this.last = e;
            }
            this.errors[key] = e;
            this.size += key.length;
            while (this.size > CACHE_SIZE_BARRIER) {
                if (!this.last) {
                    break;
                }
                var k = this.last.key;
                delete this.errors[k];
                this.size -= k.length;
                this.last = this.last.next;
            }
        }
        else {
            e.value = value;
        }
    };
    return ErrorsCache;
}());
var globalCache = new ErrorsCache();
global.cleanCache = function () {
    globalCache = new ErrorsCache();
};
function isExtendedContentProvider(x) {
    var hasRootPath = x.rootPath != null && (typeof x.rootPath === "function");
    var hasIsWebPath = x.isWebPath != null && (typeof x.isWebPath === "function");
    var hasRelativePath = x.relativePath != null && (typeof x.relativePath === "function");
    return hasRootPath && hasIsWebPath && hasRelativePath;
}
var DummyProvider = /** @class */ (function () {
    function DummyProvider() {
    }
    DummyProvider.prototype.contextPath = function () {
        return "";
    };
    DummyProvider.prototype.normalizePath = function (url) {
        return "";
    };
    DummyProvider.prototype.content = function (reference) {
        return "";
    };
    DummyProvider.prototype.hasAsyncRequests = function () {
        return false;
    };
    DummyProvider.prototype.resolvePath = function (context, relativePath) {
        return "";
    };
    DummyProvider.prototype.isAbsolutePath = function (uri) {
        return false;
    };
    DummyProvider.prototype.contentAsync = function (reference) {
        var _this = this;
        return {
            then: function (arg) { return arg(_this.content(reference)); },
            resolve: function () { return null; }
        };
    };
    DummyProvider.prototype.promiseResolve = function (arg) {
        return {
            then: function (arg1) { return arg1(arg); },
            resolve: function () { return null; }
        };
    };
    return DummyProvider;
}());
var exampleKey = function (content, schema, contextPath) {
    return "__EXAMPLE_" + ("" + content).trim() + schema.trim() + contextPath;
};
var JSONSchemaObject = /** @class */ (function () {
    function JSONSchemaObject(schema, provider) {
        this.schema = schema;
        this.provider = provider;
        this.EXAMPLE_ERROR_ENTRY = typesystem_1.messageRegistry.CONTENT_DOES_NOT_MATCH_THE_SCHEMA;
        this.SCHEMA_ERROR_ENTRY = typesystem_1.messageRegistry.INVALID_JSON_SCHEMA_DETAILS;
        if (!provider) {
            this.provider = new DummyProvider();
        }
        else {
            this.provider = provider;
        }
        if (!schema || schema.trim().length == 0 || schema.trim().charAt(0) != '{') {
            throw new ts.ValidationError(typesystem_1.messageRegistry.INVALID_JSON_SCHEMA);
        }
        tryParseJSON(schema, false);
        var jsonSchemaObject = JSON.parse(schema);
        if (!jsonSchemaObject) {
            return;
        }
        try {
            var api = require('json-schema-compatibility');
            var contextPath = this.provider.contextPath();
            this.setupId(jsonSchemaObject, contextPath);
            this.updateGraph(jsonSchemaObject, contextPath);
            var schemaVer = "" + jsonSchemaObject["$schema"];
            if (schemaVer.indexOf("http://json-schema.org/draft-04/") == -1) {
                jsonSchemaObject = api.v4(jsonSchemaObject);
            }
            else {
                this.fixRequired(jsonSchemaObject);
            }
        }
        catch (e) {
            if (ts.ValidationError.isInstance(e)) {
                throw e;
            }
            throw new ts.ValidationError(typesystem_1.messageRegistry.INVALID_JSON_SCHEMA_DETAILS, { msg: e.message });
        }
        delete jsonSchemaObject['$schema'];
        this.jsonSchema = jsonSchemaObject;
    }
    JSONSchemaObject.prototype.fixRequired = function (obj) {
        // Object.keys(obj).forEach(x=>{
        //     var val=obj[x];
        //     if (x==="required"){
        //         if (typeof val==="string"){
        //             obj[x]=[val];
        //         }
        //     }
        //     if (x==="properties"||x==="items"||x==="additionalItems"||x==="patternProperties"){
        //         this.fixRequired(val);
        //     }
        //
        // })
    };
    JSONSchemaObject.prototype.getType = function () {
        return "source.json";
    };
    JSONSchemaObject.prototype.validateObject = function (object) {
        //TODO Validation of objects
        //xmlutil(content);
        this.validate(JSON.stringify(object));
    };
    JSONSchemaObject.prototype.getMissingReferences = function (references, normalize) {
        var _this = this;
        if (normalize === void 0) { normalize = false; }
        var result = [];
        var validator = jsonUtil.getValidator();
        references.forEach(function (reference) { return validator.setRemoteReference(reference.reference, reference.content || {}); });
        var schemaUrl = null;
        if (this.jsonSchema.id && typeof (this.jsonSchema.id) === "string") {
            schemaUrl = this.jsonSchema.id;
            var innerPos = schemaUrl.indexOf("#");
            if (innerPos != -1) {
                schemaUrl = schemaUrl.substr(0, innerPos);
            }
            //adding reference to the schema itself
            // validator.setRemoteReference(innerPos, this.jsonSchema);
        }
        try {
            validator.validateSchema(this.jsonSchema);
        }
        catch (Error) {
            //we should never be exploding here, instead we'll report this error later
            return [];
        }
        var result = validator.getMissingRemoteReferences();
        var filteredReferences = [];
        if (result)
            filteredReferences = _.filter(result, function (referenceUrl) {
                return !validator.isResourceLoaded(referenceUrl) && referenceUrl != schemaUrl;
            });
        return normalize ? filteredReferences.map(function (reference) { return _this.provider.normalizePath(reference); }) : filteredReferences;
    };
    JSONSchemaObject.prototype.getSchemaPath = function (schema, normalize) {
        if (normalize === void 0) { normalize = false; }
        if (!schema) {
            return "";
        }
        if (!schema.id) {
            return "";
        }
        var id = schema.id.trim();
        if (!(id.lastIndexOf('#') === id.length - 1)) {
            return id;
        }
        var result = id.substr(0, id.length - 1);
        if (!normalize) {
            return result;
        }
        return this.provider.normalizePath(result);
    };
    JSONSchemaObject.prototype.patchSchema = function (schema) {
        var _this = this;
        if (!schema) {
            return schema;
        }
        if (!schema.id) {
            return schema;
        }
        var id = schema.id.trim();
        if (!(id.lastIndexOf('#') === id.length - 1)) {
            id = id + '#';
            schema.id = id;
        }
        ;
        var currentPath = id.substr(0, id.length - 1);
        if (!this.provider.isAbsolutePath(currentPath)) {
            return schema;
        }
        currentPath = this.provider.normalizePath(currentPath);
        var refContainers = [];
        this.collectRefContainers(schema, refContainers);
        refContainers.forEach(function (refConatiner) {
            var reference = refConatiner['$ref'];
            if (typeof reference !== 'string') {
                return;
            }
            if (reference.indexOf('#') === 0) {
                return;
            }
            if (reference.indexOf('#') === -1) {
                reference = reference + '#';
            }
            var resolvedRef;
            if (_this.provider.isAbsolutePath(reference)) {
                resolvedRef = toProperWebURL(reference);
            }
            else {
                resolvedRef = _this.provider.resolvePath(currentPath, reference);
            }
            refConatiner['$ref'] = resolvedRef;
        });
    };
    JSONSchemaObject.prototype.removeFragmentPartOfIDs = function (obj) {
        var _this = this;
        if (!obj) {
            return;
        }
        if (Array.isArray(obj)) {
            obj.forEach(function (x) { return _this.removeFragmentPartOfIDs(x); });
            return;
        }
        else if (typeof obj != "object") {
            return;
        }
        var idValue = obj.id;
        if (idValue && typeof obj.id == "string") {
            var ind = idValue.indexOf("#");
            if (ind >= 0) {
                idValue = idValue.substring(0, ind).trim();
                if (!idValue) {
                    delete obj.id;
                }
                else {
                    obj.id = idValue;
                }
            }
        }
        Object.keys(obj).forEach(function (x) { return _this.removeFragmentPartOfIDs(obj[x]); });
    };
    JSONSchemaObject.prototype.collectRefContainers = function (rootObject, refContainers) {
        var _this = this;
        Object.keys(rootObject).forEach(function (key) {
            if (key === '$ref') {
                refContainers.push(rootObject);
                return;
            }
            if (!rootObject[key]) {
                return;
            }
            if (typeof rootObject[key] === 'object') {
                _this.collectRefContainers(rootObject[key], refContainers);
            }
        });
    };
    JSONSchemaObject.prototype.validate = function (content, alreadyAccepted) {
        var _this = this;
        if (alreadyAccepted === void 0) { alreadyAccepted = []; }
        var key = exampleKey(content, this.schema, this.provider.contextPath());
        var error = globalCache.getValue(key);
        if (error) {
            if (error instanceof Error) {
                throw error;
            }
            return;
        }
        else {
            var schKey = exampleKey("__SCHEMA_VALIDATION__", this.schema, this.provider.contextPath());
            var schError = globalCache.getValue(schKey);
            if (schError) {
                if (schError instanceof Error) {
                    if (ts.ValidationError.isInstance(schError)) {
                        var ve = schError;
                        var newVe = new ts.ValidationError(ve.messageEntry, ve.parameters);
                        newVe.filePath = ve.filePath;
                        newVe.additionalErrors = ve.additionalErrors;
                        newVe.internalPath = ve.internalPath;
                        newVe.isWarning = ve.isWarning;
                        schError = newVe;
                    }
                    throw schError;
                }
            }
        }
        if (alreadyAccepted.length == 0) {
            tryParseJSON(content, true);
            if (this.jsonSchema.id) {
                var schemaId = this.jsonSchema.id;
                if (schemaId.charAt(schemaId.length - 1) == "#") {
                    var schemaId1 = schemaId.substring(0, schemaId.length - 1);
                    alreadyAccepted.push({
                        reference: schemaId1,
                        content: this.jsonSchema
                    });
                }
            }
        }
        var exampleObject = JSON.parse(content);
        var validator = jsonUtil.getValidator();
        alreadyAccepted.forEach(function (accepted) { return validator.setRemoteReference(accepted.reference, accepted.content); });
        validator.validate(exampleObject, this.jsonSchema);
        var missingReferences = validator.getMissingRemoteReferences().filter(function (reference) { return !_.find(alreadyAccepted, function (acceptedReference) { return reference === acceptedReference.reference; }); });
        if (!missingReferences || missingReferences.length === 0) {
            this.acceptErrors(key, validator.getLastErrors(), content, true);
            return;
        }
        var acceptedReferences = [];
        missingReferences.forEach(function (_reference) {
            var remoteSchemeContent;
            var reference = decodeURL(_reference);
            var result = { reference: _reference };
            try {
                var api = require('json-schema-compatibility');
                var content_1 = _this.provider.content(reference);
                tryParseJSON(content_1, true);
                var jsonObject = JSON.parse(content_1);
                var nRef = _this.provider.normalizePath(reference);
                _this.setupId(jsonObject, nRef);
                _this.updateGraph(jsonObject, nRef);
                remoteSchemeContent = api.v4(jsonObject);
                delete remoteSchemeContent['$schema'];
                result.content = remoteSchemeContent;
            }
            catch (exception) {
                if (ts.ValidationError.isInstance(exception)) {
                    exception.filePath = reference;
                    globalCache.setValue(key, exception);
                    throw exception;
                }
                result.error = exception;
            }
            finally {
                acceptedReferences.push(result);
            }
        });
        if (this.provider.hasAsyncRequests()) {
            return;
        }
        acceptedReferences.forEach(function (accepted) {
            alreadyAccepted.push(accepted);
        });
        this.validate(content, alreadyAccepted);
    };
    /**
     * Checks for z-schema messages related to the inability to assign to a property of non-object variables.
     * @param message
     *
     * @returns null if related message is not detected, assigned value if it can be detected, and empty string
     * if related message is detected, but assigned value can not be found.
     */
    JSONSchemaObject.checkIfNonObjectAssignmentFailure = function (message) {
        var underscoreValidatedMessage = "__$validated";
        var nonObjectMessage = "called on non-object";
        if (!message)
            return null;
        if (message.indexOf(underscoreValidatedMessage) != -1) {
            var messageBeginning1 = "Cannot assign to read only property '__$validated' of ";
            var messageBeginning2 = "Cannot create property '__$validated' on string '";
            if (message.indexOf(messageBeginning1) == 0
                && message.length > messageBeginning1.length) {
                return message.substr(messageBeginning1.length, message.length - messageBeginning1.length);
            }
            else if (message.indexOf(messageBeginning2) == 0
                && message.length > messageBeginning2.length + 1 &&
                message.charAt(message.length - 1) == "'") {
                return message.substr(messageBeginning2.length, message.length - messageBeginning2.length - 1);
            }
            return "";
        }
        else if (message.indexOf(nonObjectMessage) != -1) {
            return "";
        }
        else {
            return null;
        }
    };
    JSONSchemaObject.prototype.validateSelf = function (alreadyAccepted) {
        var _this = this;
        if (alreadyAccepted === void 0) { alreadyAccepted = []; }
        var key = exampleKey("__SCHEMA_VALIDATION__", this.schema, this.provider.contextPath());
        var error = globalCache.getValue(key);
        if (error) {
            if (error instanceof Error) {
                throw error;
            }
            return;
        }
        else {
        }
        var validator = jsonUtil.getValidator();
        // if(alreadyAccepted.length==0&&this.jsonSchema.id){
        //     let schemaId = this.jsonSchema.id;
        //     if(schemaId.charAt(schemaId.length-1)=="#"){
        //         let schemaId1 = schemaId.substring(0,schemaId.length-1);
        //         alreadyAccepted.push({
        //             reference: schemaId1,
        //             content: this.jsonSchema
        //         });
        //     }
        // }
        alreadyAccepted.forEach(function (accepted) { return validator.setRemoteReference(accepted.reference, accepted.content); });
        try {
            delete this.jsonSchema.id;
            validator.validateSchema(this.jsonSchema);
        }
        catch (error) {
            var illegalRequiredMessageStart = "Cannot assign to read only property '__$validated' of ";
            var nonObjectAssignmentCheck = JSONSchemaObject.checkIfNonObjectAssignmentFailure(error.message);
            if (nonObjectAssignmentCheck !== null) {
                var propertyName = nonObjectAssignmentCheck;
                var message = "Assignment to non-object.";
                if (propertyName) {
                    message = "Unexpected value '" + propertyName + "'";
                }
                this.acceptErrors(key, [{
                        message: message,
                        params: []
                    }], null, true, true);
            }
            throw error;
        }
        var missingReferences = validator.getMissingRemoteReferences().filter(function (reference) { return !_.find(alreadyAccepted, function (acceptedReference) { return reference === acceptedReference.reference; }); });
        if (!missingReferences || missingReferences.length === 0) {
            this.acceptErrors(key, validator.getLastErrors(), null, true, true);
            return;
        }
        var acceptedReferences = [];
        missingReferences.forEach(function (_reference, i) {
            if (i > 0) {
                return;
            }
            var remoteSchemeContent;
            var reference = decodeURL(_reference);
            var result = { reference: _reference };
            try {
                var api = require('json-schema-compatibility');
                var content = _this.provider.content(reference);
                tryParseJSON(content, true);
                var jsonObject = JSON.parse(content);
                var nRef = _this.provider.normalizePath(reference);
                _this.setupId(jsonObject, nRef);
                _this.updateGraph(jsonObject, nRef);
                remoteSchemeContent = api.v4(jsonObject);
                delete remoteSchemeContent['$schema'];
                result.content = remoteSchemeContent;
            }
            catch (exception) {
                if (ts.ValidationError.isInstance(exception)) {
                    exception.filePath = reference;
                    globalCache.setValue(key, exception);
                    throw exception;
                }
                result.error = exception;
            }
            finally {
                acceptedReferences.push(result);
            }
        });
        if (this.provider.hasAsyncRequests()) {
            return;
        }
        acceptedReferences.forEach(function (accepted) {
            alreadyAccepted.push(accepted);
        });
        this.validateSelf(alreadyAccepted);
    };
    JSONSchemaObject.prototype.setupId = function (json, path) {
        if (!path) {
            return;
        }
        if (!json) {
            return;
        }
        this.removeFragmentPartOfIDs(json);
        if (json.id) {
            if (!this.provider.isAbsolutePath(json.id)) {
                json.id = this.provider.resolvePath(path, json.id);
            }
        }
        json.id = path.replace(/\\/g, '/');
        if (json.id.charAt(json.id.length - 1) != '#') {
            json.id = json.id + '#';
        }
        json.id = toProperWebURL(json.id);
        this.patchSchema(json);
    };
    JSONSchemaObject.prototype.acceptErrors = function (key, errors, exampleContent, throwImmediately, isWarning) {
        var _this = this;
        if (throwImmediately === void 0) { throwImmediately = false; }
        if (isWarning === void 0) { isWarning = false; }
        var isExamplesMode = exampleContent != null;
        var jsonContent = exampleContent != null ? exampleContent : this.schema;
        if (errors && errors.length > 0) {
            var jsonObj_1 = jsonToAST(jsonContent, { verbose: true });
            var vErrors = errors.map(function (x) {
                var regEntry;
                var isInFactExampleMode = isExamplesMode && JSONSchemaObject.EXAMPLE_ERROR_CODES[x.code];
                if (isInFactExampleMode) {
                    regEntry = _this.EXAMPLE_ERROR_ENTRY;
                }
                else {
                    regEntry = _this.SCHEMA_ERROR_ENTRY;
                }
                var ve = new ts.ValidationError(regEntry, { msg: x.message });
                if (isInFactExampleMode || exampleContent == null) {
                    ve.internalRange = getJSONRange(jsonContent, jsonObj_1, x.path);
                }
                ve.isWarning = isWarning;
                ve.error = x;
                ve.internalPath = x.path;
                return ve;
            });
            var res = vErrors[0];
            res.additionalErrors = vErrors.slice(1);
            globalCache.setValue(key, res);
            if (throwImmediately) {
                throw res;
            }
            return;
        }
        globalCache.setValue(key, 1);
    };
    JSONSchemaObject.prototype.contentAsync = function (_reference) {
        var _this = this;
        var reference = decodeURL(_reference);
        var remoteSchemeContent;
        var api = require('json-schema-compatibility');
        var contentPromise = this.provider.contentAsync(reference);
        if (!contentPromise) {
            return this.provider.promiseResolve({
                reference: reference,
                content: null,
                error: new ts.ValidationError(typesystem_1.messageRegistry.REFERENCE_NOT_FOUND, { ref: reference })
            });
        }
        var result = contentPromise.then(function (cnt) {
            var content = { reference: _reference };
            try {
                tryParseJSON(cnt, true);
                var jsonObject = JSON.parse(cnt);
                var nRef = _this.provider.normalizePath(reference);
                _this.setupId(jsonObject, nRef);
                _this.updateGraph(jsonObject, nRef);
                remoteSchemeContent = api.v4(jsonObject);
                delete remoteSchemeContent['$schema'];
                content.content = remoteSchemeContent;
            }
            catch (exception) {
                content.error = exception;
                content.reference = reference;
                throw exception;
            }
            return content;
        });
        return result;
    };
    JSONSchemaObject.prototype.updateGraph = function (json, schemaPath) {
        if (schemaPath.indexOf("#") < 0) {
            schemaPath += "#";
        }
        if (!this.graph) {
            this.graph = new SchemaGraph(schemaPath);
        }
        var cycle = this.graph.addNode(json, schemaPath);
        if (cycle) {
            if (isExtendedContentProvider(this.provider)) {
                var ep_1 = this.provider;
                var rootPath_1 = ep_1.rootPath();
                var rpl_1 = rootPath_1.length;
                cycle = cycle.map(function (x) {
                    var p = x.trim();
                    if (ep_1.isWebPath(p)) {
                        if (ep_1.isWebPath(rootPath_1) && p.length >= rpl_1 && p.substring(0, rpl_1) == rootPath_1) {
                            p = p.substring(rpl_1);
                        }
                    }
                    else {
                        p = ep_1.relativePath(rootPath_1, p).replace(/\\/g, "/");
                    }
                    if (p.length && p.charAt(p.length - 1) == '#') {
                        p = p.substring(0, p.length - 1);
                    }
                    return p;
                });
            }
            var ve = new ts.ValidationError(typesystem_1.messageRegistry.CIRCULAR_REFS_IN_JSON_SCHEMA_DETAILS, {
                cycle: cycle.join(" -> ")
            });
            throw ve;
        }
    };
    JSONSchemaObject.SCHEMA_ERROR_CODES = {
        "KEYWORD_TYPE_EXPECTED": true,
        "KEYWORD_MUST_BE": true,
        "KEYWORD_DEPENDENCY": true,
        "KEYWORD_PATTERN": true,
        "KEYWORD_UNDEFINED_STRICT": true,
        "KEYWORD_VALUE_TYPE": true,
        "CUSTOM_MODE_FORCE_PROPERTIES": true,
        "UNKNOWN_FORMAT": true,
        "PARENT_SCHEMA_VALIDATION_FAILED": true,
        "REF_UNRESOLVED": true,
        "KEYWORD_UNEXPECTED": true,
        "SCHEMA_NOT_AN_OBJECT": true,
        "SCHEMA_NOT_REACHABLE": true,
        "UNRESOLVABLE_REFERENCE": true,
    };
    JSONSchemaObject.EXAMPLE_ERROR_CODES = {
        "MULTIPLE_OF": true,
        "MAXIMUM": true,
        "MAXIMUM_EXCLUSIVE": true,
        "MAX_LENGTH": true,
        "MIN_LENGTH": true,
        "PATTERN": true,
        "ARRAY_ADDITIONAL_ITEMS": true,
        "ARRAY_LENGTH_LONG": true,
        "ARRAY_LENGTH_SHORT": true,
        "ARRAY_UNIQUE": true,
        "OBJECT_PROPERTIES_MAXIMUM": true,
        "OBJECT_PROPERTIES_MINIMUM": true,
        "OBJECT_MISSING_REQUIRED_PROPERTY": true,
        "OBJECT_ADDITIONAL_PROPERTIES": true,
        "OBJECT_DEPENDENCY_KEY": true,
        "ENUM_MISMATCH": true,
        "ANY_OF_MISSING": true,
        "ONE_OF_MISSING": true,
        "ONE_OF_MULTIPLE": true,
        "NOT_PASSED": true,
        "INVALID_FORMAT": true,
        "UNKNOWN_FORMAT": true,
        "INVALID_TYPE": true,
    };
    return JSONSchemaObject;
}());
exports.JSONSchemaObject = JSONSchemaObject;
function toProperWebURL(p) {
    if (p == null || p.trim().length == 0) {
        return p;
    }
    var l = "https://".length;
    if (p.length >= l && p.substring(0, l) == "https://") {
        return p;
    }
    p = p.replace("//", "__\/DOUBLESLASH\/__");
    p = p.replace(/^([a-zA-Z]):/, '$1__\/COLON\/__');
    var protoclStr = "https://__/APPENDED_PROTOCOL/__";
    if (p.charAt(0) != "/") {
        protoclStr += "/";
    }
    return protoclStr + p;
}
function decodeURL(p) {
    if (p == null || p.trim().length == 0) {
        return p;
    }
    var protocolStr = "https://__/APPENDED_PROTOCOL/__";
    var l = protocolStr.length;
    if (p.length < l || p.substring(0, l) != protocolStr) {
        return p;
    }
    p = p.substring(l, p.length);
    if (p.indexOf("__/COLON/__") > 0 && p.charAt(0) == "/") {
        p = p.substring(1);
    }
    p = p.replace("__/DOUBLESLASH/__", "//").replace("__/COLON/__", ":");
    return p;
}
var MAX_EXAMPLES_TRESHOLD = 10;
var XMLSchemaObject = /** @class */ (function () {
    function XMLSchemaObject(schema, provider) {
        this.schema = schema;
        this.provider = provider;
        this.extraElementData = null;
        this.references = {};
        this.contentToResult = {};
        if (!provider) {
            this.provider = new DummyProvider();
        }
        if (schema.charAt(0) != '<') {
            throw new ts.ValidationError(typesystem_1.messageRegistry.INVALID_XML_SCHEMA);
        }
        this.schemaString = this.handleReferenceElement(schema);
    }
    XMLSchemaObject.prototype.getType = function () {
        return "text.xml";
    };
    XMLSchemaObject.prototype.validateObject = function (object) {
        if (this.extraElementData) {
            var objectName = Object.keys(object)[0];
            var err = new ts.ValidationError(typesystem_1.messageRegistry.EXTERNAL_TYPE_ERROR, { typeName: this.extraElementData.requestedName, objectName: objectName });
            if (!this.extraElementData.type && !this.extraElementData.originalName) {
                this.acceptErrors("key", [err], true);
                return;
            }
            if (this.extraElementData.originalName && objectName !== this.extraElementData.originalName) {
                this.acceptErrors("key", [err], true);
                return;
            }
            if (this.extraElementData.type) {
                var root = object[objectName];
                delete object[objectName];
                object[this.extraElementData.name] = root;
            }
        }
        this.validate(xmlUtil.jsonToXml(object));
    };
    XMLSchemaObject.prototype.collectReferences = function (xmlString, context, references) {
        var _this = this;
        var doc;
        doc = new DOMParser(domParserOptions).parseFromString(xmlString);
        var schema = elementChildrenByName(doc, 'schema', this.namspacePrefix)[0];
        var imports = elementChildrenByName(schema, 'import', this.namspacePrefix);
        var includes = elementChildrenByName(schema, 'include', this.namspacePrefix);
        var refElements = imports.concat(includes);
        refElements.forEach(function (refElement) {
            var refString = refElement.getAttribute('schemaLocation');
            if (refString) {
                var fullPath = _this.provider.resolvePath(context, refString);
                var reference = references[fullPath];
                if (!reference) {
                    var index = Object.keys(references).length;
                    var loadedContent = _this.provider.content(fullPath);
                    var patchedContent;
                    try {
                        patchedContent = _this.collectReferences(loadedContent, fullPath, references);
                    }
                    catch (exception) {
                        patchedContent = loadedContent;
                    }
                    reference = xmlUtil.createXmlSchemaReference(fullPath, index, patchedContent);
                    references[fullPath] = reference;
                }
                refElement.setAttribute('schemaLocation', "file_" + reference.virtualIndex + ".xsd");
            }
        });
        return doc.toString();
    };
    XMLSchemaObject.prototype.getMissingReferences = function () {
        var _this = this;
        var doc;
        doc = new DOMParser(domParserOptions).parseFromString(this.schemaString);
        var schema = elementChildrenByName(doc, 'schema', this.namspacePrefix)[0];
        var imports = elementChildrenByName(schema, 'import', this.namspacePrefix);
        var includes = elementChildrenByName(schema, 'include', this.namspacePrefix);
        var refElements = imports.concat(includes);
        var result = [];
        refElements.forEach(function (refElement) {
            var refString = refElement.getAttribute('schemaLocation');
            if (refString) {
                var fullPath = _this.provider.resolvePath(_this.provider.contextPath(), refString);
                result.push(fullPath);
            }
        });
        return result;
    };
    XMLSchemaObject.prototype.collectReferencesAsync = function (xmlString, context, references) {
        var _this = this;
        var doc;
        doc = new DOMParser(domParserOptions).parseFromString(xmlString);
        var schema = elementChildrenByName(doc, 'schema', this.namspacePrefix)[0];
        var imports = elementChildrenByName(schema, 'import', this.namspacePrefix);
        var includes = elementChildrenByName(schema, 'include', this.namspacePrefix);
        var refElements = imports.concat(includes);
        return Promise.all(refElements.map(function (refElement) {
            var refString = refElement.getAttribute('schemaLocation');
            if (refString) {
                var fullPath = _this.provider.resolvePath(context, refString);
                var reference = references[fullPath];
                if (reference) {
                    refElement.setAttribute('schemaLocation', "file_" + reference.virtualIndex + ".xsd");
                    return {};
                }
                return _this.provider.contentAsync(fullPath).then(function (loadedContent) {
                    return _this.collectReferencesAsync(loadedContent, fullPath, references).then(function (patchedContent) {
                        return patchedContent;
                    }, function (reject) { return loadedContent; }).then(function (patchedContent) {
                        var index = Object.keys(references).length;
                        reference = xmlUtil.createXmlSchemaReference(fullPath, index, patchedContent);
                        references[fullPath] = reference;
                        refElement.setAttribute('schemaLocation', "file_" + reference.virtualIndex + ".xsd");
                        return {};
                    });
                });
            }
            return {};
        })).then(function (resolve) { return Promise.resolve(doc.toString()); });
    };
    XMLSchemaObject.prototype.loadSchemaReferencesAsync = function () {
        return this.collectReferencesAsync(this.schemaString, this.provider.contextPath(), {});
    };
    XMLSchemaObject.prototype.validate = function (xml) {
        try {
            var rs = this.contentToResult[xml];
            if (rs === false) {
                return;
            }
            if (rs) {
                throw rs;
            }
            var references = {};
            var patchedSchema = this.collectReferences(this.schemaString, this.provider.contextPath(), references);
            var validator = xmlUtil.getValidator(patchedSchema);
            if (this.provider.hasAsyncRequests()) {
                return;
            }
            var validationErrors = validator.validate(xml, Object.keys(references).map(function (key) { return references[key]; }));
            this.acceptErrors("key", validationErrors, true);
            this.contentToResult[xml] = false;
            if (Object.keys(this.contentToResult).length > MAX_EXAMPLES_TRESHOLD) {
                this.contentToResult = {};
            }
        }
        catch (e) {
            this.contentToResult[xml] = e;
            throw e;
        }
    };
    XMLSchemaObject.prototype.handleReferenceElement = function (content) {
        var doc = new DOMParser(domParserOptions).parseFromString(content);
        this.namspacePrefix = extractNamespace(doc);
        var schema = elementChildrenByName(doc, 'schema', this.namspacePrefix)[0];
        var elements = elementChildrenByName(schema, 'element', this.namspacePrefix);
        var element = _.find(elements, function (element) { return element.getAttribute('extraelement') === 'true'; });
        if (!element) {
            return content;
        }
        var extraElementData = {};
        extraElementData.name = element.getAttribute('name');
        extraElementData.type = element.getAttribute('type');
        extraElementData.originalName = element.getAttribute('originalname');
        extraElementData.requestedName = element.getAttribute('requestedname');
        if (!extraElementData.type) {
            schema.removeChild(element);
        }
        element.removeAttribute('originalname');
        element.removeAttribute('requestedname');
        element.removeAttribute('extraelement');
        this.extraElementData = extraElementData;
        return doc.toString();
    };
    XMLSchemaObject.prototype.acceptErrors = function (key, errors, throwImmediately) {
        if (throwImmediately === void 0) { throwImmediately = false; }
        if (errors && errors.length > 0) {
            var msg = errors.map(function (x) { return x.message; }).join(", ");
            var res = new ts.ValidationError(typesystem_1.messageRegistry.CONTENT_DOES_NOT_MATCH_THE_SCHEMA, { msg: msg });
            res.errors = errors;
            globalCache.setValue(key, res);
            if (throwImmediately) {
                throw res;
            }
            return;
        }
    };
    return XMLSchemaObject;
}());
exports.XMLSchemaObject = XMLSchemaObject;
function getJSONSchema(content, provider) {
    var key = schemaKey(provider, content);
    var rs = useLint ? globalCache.getValue(key) : false;
    if (rs && rs.provider) {
        return rs;
    }
    var res = new JSONSchemaObject(content, provider);
    globalCache.setValue(key, res);
    return res;
}
exports.getJSONSchema = getJSONSchema;
var schemaKey = function (provider, content) {
    var contextPath = "";
    if (provider) {
        contextPath = provider.contextPath();
    }
    var key = "__SCHEMA_" + ("" + content).trim() + contextPath.trim();
    return key;
};
function getXMLSchema(content, provider) {
    var key = schemaKey(provider, content);
    var rs = useLint ? globalCache.getValue(content) : false;
    if (rs) {
        return rs;
    }
    var res = new XMLSchemaObject(content, provider);
    if (useLint) {
        globalCache.setValue(content, res);
    }
    return res;
}
exports.getXMLSchema = getXMLSchema;
function createSchema(content, provider) {
    var isJSON = content && content.trim().length > 0 && content.trim().charAt(0) == "{";
    var key = schemaKey(provider, content);
    var rs = useLint ? globalCache.getValue(key) : false;
    if (rs) {
        return rs;
    }
    try {
        var res = new JSONSchemaObject(content, provider);
        if (useLint) {
            globalCache.setValue(key, res);
        }
        return res;
    }
    catch (e) {
        if (useLint && ts.ValidationError.isInstance(e) && isJSON) {
            globalCache.setValue(key, e);
            return e;
        }
        else {
            try {
                var res = new XMLSchemaObject(content, provider);
                globalCache.setValue(key, res);
                return res;
            }
            catch (e) {
                if (useLint) {
                    var rs_1 = new ts.ValidationError(typesystem_1.messageRegistry.CAN_NOT_PARSE_SCHEMA);
                    globalCache.setValue(key, rs_1);
                    return rs_1;
                }
            }
        }
    }
}
exports.createSchema = createSchema;
function elementChildrenByName(parent, tagName, ns) {
    if (ns == null) {
        ns = extractNamespace(parent);
    }
    if (ns.length > 0) {
        ns += ":";
    }
    var elements = parent.getElementsByTagName(ns + tagName);
    var result = [];
    for (var i = 0; i < elements.length; i++) {
        var child = elements[i];
        if (child.parentNode === parent) {
            result.push(child);
        }
    }
    return result;
}
function extractNamespace(documentOrElement) {
    var ns = "";
    if (documentOrElement) {
        var doc = documentOrElement;
        if (documentOrElement.ownerDocument) {
            doc = documentOrElement.ownerDocument;
        }
        if (doc) {
            var docElement = doc.documentElement;
            if (docElement) {
                ns = docElement.prefix;
            }
        }
    }
    return ns;
}
var JSON_TO_AST_MESSAGE1 = "Cannot tokenize symbol";
var JSON_TO_AST_MESSAGE2 = "Unexpected token";
function messageToValidationError(message, isExample) {
    if (isExample === void 0) { isExample = false; }
    var regEntry = isExample ? typesystem_1.messageRegistry.CAN_NOT_PARSE_JSON
        : typesystem_1.messageRegistry.INVALID_JSON_SCHEMA_DETAILS;
    var l1 = JSON_TO_AST_MESSAGE1.length;
    var l2 = JSON_TO_AST_MESSAGE2.length;
    var msg, l;
    if (message.substring(0, l1) == JSON_TO_AST_MESSAGE1) {
        msg = JSON_TO_AST_MESSAGE1;
        l = l1;
    }
    else if (message.substring(0, l2) == JSON_TO_AST_MESSAGE2) {
        msg = JSON_TO_AST_MESSAGE2;
        l = l2;
    }
    else {
        return new ts.ValidationError(regEntry, { msg: message });
        ;
    }
    if (msg && l) {
        var end = message.indexOf("\n", l);
        if (end < 0) {
            end = message.length;
        }
        var str = message.substring(l).trim();
        var i0 = str.indexOf("<");
        if (i0 < 0) {
            i0 = 0;
        }
        else {
            i0++;
        }
        var i3 = str.indexOf("\n");
        if (i3 < 0) {
            i3 = str.length;
        }
        var i2 = str.lastIndexOf("at", i3);
        if (i2 < 0) {
            i2 = i3;
        }
        else {
            i2 += "at".length;
        }
        var i1 = str.lastIndexOf(">", i2);
        if (i1 < 0) {
            i1 = i2;
        }
        var ch = str.substring(i0, i1);
        var posStr = str.substring(i2, i3).trim();
        var colonInd = posStr.indexOf(":");
        try {
            var line = parseInt(posStr.substring(0, colonInd)) - 1;
            var col = parseInt(posStr.substring(colonInd + 1, posStr.length)) - 1;
            var newMessage = msg + " '" + ch + "'";
            var result = new ts.ValidationError(regEntry, { msg: newMessage });
            result.internalRange = {
                start: {
                    line: line,
                    column: col,
                    position: null
                },
                end: {
                    line: line,
                    column: col + ch.length,
                    position: null
                }
            };
            return result;
        }
        catch (err) { }
    }
    return new ts.ValidationError(typesystem_1.messageRegistry.INVALID_JSON_SCHEMA_DETAILS, { msg: message });
    ;
}
exports.messageToValidationError = messageToValidationError;
function getJSONRange(jsonStrig, jsonObj, jsonPath) {
    if (!jsonPath || typeof jsonPath != "string") {
        return null;
    }
    jsonObj = jsonObj || jsonToAST(jsonStrig, { verbose: true });
    if (jsonPath.charAt(0) == "#") {
        jsonPath = jsonPath.substring(1);
    }
    if (jsonPath.charAt(0) == "/") {
        jsonPath = jsonPath.substring(1);
    }
    var obj = jsonObj;
    if (jsonPath.length > 0) {
        var segments = jsonPath.split("/");
        for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
            var seg = segments_1[_i];
            var nextObj = getJOSNValue(obj, seg);
            if (nextObj == null) {
                break;
            }
            obj = nextObj;
        }
    }
    var sLoc = obj.loc.start;
    var eLoc = obj.loc.end;
    return {
        start: {
            line: sLoc.line - 1,
            column: sLoc.column - 1,
            position: sLoc.offset
        },
        end: {
            line: eLoc.line - 1,
            column: eLoc.column - 1,
            position: eLoc.offset
        }
    };
}
exports.getJSONRange = getJSONRange;
function getJOSNValue(obj, key) {
    if (obj.type == "property") {
        obj = obj.value;
    }
    if (obj.type == "object") {
        return _.find(obj.children, function (x) { return x.key.value == key; });
    }
    else if (obj.type == "array") {
        return obj.children[key];
    }
    else if (obj.type == "array") {
        return obj;
    }
    return null;
}
function tryParseJSON(content, isExample) {
    try {
        if (typeof content != "string") {
            return;
        }
        jsonToAST(content, { verbose: true });
    }
    catch (err) {
        var ve = messageToValidationError(err.message, isExample);
        throw ve;
    }
}
exports.tryParseJSON = tryParseJSON;
var RefPath = /** @class */ (function () {
    function RefPath(segments) {
        this.segments = segments;
    }
    RefPath.prototype.length = function () {
        return this.segments.length;
    };
    return RefPath;
}());
var SchemaNode = /** @class */ (function () {
    function SchemaNode(url) {
        this.url = url;
        this.processed = false;
        this.refFromRoot = new RefPath([]);
        this.refsOut = {};
        this.referees = {};
    }
    return SchemaNode;
}());
var SchemaGraph = /** @class */ (function () {
    function SchemaGraph(rootPath) {
        this.rootPath = rootPath;
        this.nodes = {};
    }
    SchemaGraph.prototype.node = function (url) {
        return this.nodes[url];
    };
    SchemaGraph.prototype.addNode = function (json, schemaPath) {
        var node = this.nodes[schemaPath];
        if (node) {
            if (node.processed) {
                return;
            }
        }
        else {
            node = new SchemaNode(schemaPath);
            this.nodes[schemaPath] = node;
        }
        var refs = extractRefs(json);
        var referees = Object.keys(node.referees).map(function (x) { return node.referees[x]; });
        var cycle = null;
        var _loop_1 = function (ref) {
            var referee = node.referees[ref];
            if (referee) {
                cycle = [this_1.rootPath].concat(referee.refFromRoot.segments).concat(referee.refsOut[schemaPath].segments).concat([ref]);
            }
            var refNode = this_1.nodes[ref];
            if (!refNode) {
                refNode = new SchemaNode(ref);
                refNode.refFromRoot = new RefPath(node.refFromRoot.segments.concat([ref]));
                this_1.nodes[ref] = refNode;
            }
            var newReferees = referees.filter(function (x) { return !refNode.referees[x.url]; });
            for (var _i = 0, newReferees_1 = newReferees; _i < newReferees_1.length; _i++) {
                var r = newReferees_1[_i];
                refNode.referees[r.url] = r;
                r.refsOut[ref] = new RefPath(r.refsOut[schemaPath].segments.concat([ref]));
            }
            node.refsOut[ref] = new RefPath([ref]);
            refNode.referees[node.url] = node;
        };
        var this_1 = this;
        for (var _i = 0, refs_1 = refs; _i < refs_1.length; _i++) {
            var ref = refs_1[_i];
            _loop_1(ref);
        }
        node.processed = true;
        return cycle;
    };
    return SchemaGraph;
}());
function extractRefs(obj) {
    return _.unique(doExtractRefs(obj, []));
}
function doExtractRefs(obj, refs) {
    if (Array.isArray(obj)) {
        obj.forEach(function (x) { return doExtractRefs(x, refs); });
    }
    else if (obj != null && typeof obj === "object") {
        Object.keys(obj).forEach(function (x) {
            if (x === "$ref" && typeof obj[x] === "string") {
                refs.push(decodeURL(obj[x]));
            }
            else if (typeof obj[x] === "object") {
                doExtractRefs(obj[x], refs);
            }
        });
    }
    return refs;
}
//# sourceMappingURL=schemaUtil.js.map