"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var xml2js = require("xml2js");
var ts = require("./typesystem");
var _ = require("underscore");
var restrictions_1 = require("./restrictions");
var metainfo_1 = require("./metainfo");
var typesystem_1 = require("./typesystem");
exports.messageRegistry = require("../../resources/errorMessages");
var XML_ERRORS = '@unexpected_root_attributes_and_elements';
var bodyNames = [
    'application/x-www-form-urlencoded',
    'application/json',
    'application/xml',
    'multipart/form-data'
];
function xmlOpen(tagname, attributes, level, newLine) {
    if (level === void 0) { level = 0; }
    if (newLine === void 0) { newLine = true; }
    var result = '<' + tagname;
    for (var i = 0; i < level; i++) {
        result = '    ' + result;
    }
    if (attributes && Object.keys(attributes).length > 0) {
        Object.keys(attributes).forEach(function (key) {
            if (typeof attributes[key] !== 'string') {
                return;
            }
            result = result + ' ' + key + '="' + attributes[key] + '"';
        });
    }
    result = result + '>' + (newLine ? '\n' : '');
    return result;
}
;
function xmlClose(tagname, level) {
    if (level === void 0) { level = 0; }
    var result = '</' + tagname;
    for (var i = 0; i < level && i > -1; i++) {
        result = '    ' + result;
    }
    var result = (level > -1 ? '\n' : '') + result + '>\n';
    return result;
}
function serializeToXML(object, type) {
    type = actualType(type);
    var expectedRootNodeName = rootXmlName(type);
    var infos = getInfos(type);
    var result = xmlOpen(expectedRootNodeName, getAttributesFromJson(object, infos));
    var valueWrapper = {};
    valueWrapper[expectedRootNodeName] = object;
    if (type.isArray()) {
        var componentMeta = type.meta().filter(function (metaInfo) { return metaInfo instanceof restrictions_1.ComponentShouldBeOfType; })[0];
        result = result + getArrayFromJson(valueWrapper, type, 1, true);
    }
    else {
        result = result + getElementsFromJson(object, infos, 1);
    }
    var result = result + xmlClose(expectedRootNodeName);
    return result;
}
exports.serializeToXML = serializeToXML;
function getAttributesFromJson(node, infos) {
    var nodeAttributes = {};
    var attributeInfos = _.filter(infos, function (info) { return xmlDescriptor(info.value()).attribute; });
    attributeInfos.forEach(function (info) {
        var key = info.propId();
        var xmlKey = xmlName(info);
        if (node[key]) {
            nodeAttributes[xmlKey] = node[key].toString();
        }
    });
    return nodeAttributes;
}
function getElementFromJson(node, type, level) {
    var jsonKey = Object.keys(node)[0];
    var jsonValue = node[jsonKey];
    var result = null;
    if (type.isScalar()) {
        var infos = getInfos(type);
        result = xmlOpen(jsonKey, getAttributesFromJson(jsonValue, infos), level, !type.isScalar()) + jsonValue.toString();
    }
    else if (type.isUnion()) {
        return getElementFromJson(node, selectTypeFromJson(jsonValue, type), level);
    }
    else {
        var infos = getInfos(type);
        result = xmlOpen(jsonKey, getAttributesFromJson(jsonValue, infos), level, !type.isScalar()) + getElementsFromJson(jsonValue, infos, level + 1);
    }
    result = result + xmlClose(jsonKey, type.isScalar() ? -1 : level);
    return result;
}
function selectTypeFromJson(value, unionType) {
    var canBeTypes = unionType.typeFamily();
    var results = [];
    var result = null;
    canBeTypes.forEach(function (type) {
        var xmlValue = serializeToXML(value, type);
        if (!xmlValue) {
            return;
        }
        var jsonValue = readObject(xmlValue, type);
        if (jsonValue) {
            var errors = getXmlErrors(jsonValue);
            results.push({ type: type, errors: (errors && errors.length) || 0 });
        }
    });
    result = results.length > 0 ? results[0] : { type: canBeTypes[0] };
    results.forEach(function (canBe) {
        if (canBe.errors < result.errors) {
            result = canBe;
        }
    });
    return result.type;
}
function getElementsFromJson(node, infos, level) {
    var elementInfos = _.filter(infos, function (info) { return !xmlDescriptor(info.value()).attribute; });
    var result = '';
    elementInfos.forEach(function (info) {
        var xmlKey = xmlName(info);
        var key = info.propId();
        var value = {};
        value[xmlKey] = node[key];
        if (info.value().isArray()) {
            result = result + getArrayFromJson(value, info.value(), level);
        }
        else {
            result = result + ((node[key] || node[key] === '') ? getElementFromJson(value, info.value(), level) : '');
        }
    });
    return result;
}
function getArrayFromJson(values, type, level, rootNode) {
    if (rootNode === void 0) { rootNode = false; }
    var jsonKey = Object.keys(values)[0];
    var jsonValue = values[jsonKey];
    var descriptor = xmlDescriptor(type);
    var isWrapped = rootNode || descriptor.wrapped;
    var result = '';
    var componentType = arrayElementType(type);
    var typeName = componentType && componentType.name();
    var elementsLevel = level;
    if (isWrapped && !rootNode) {
        result = xmlOpen(jsonKey, null, level);
        elementsLevel = elementsLevel + 1;
    }
    if (jsonValue && isArray(jsonValue)) {
        jsonValue.forEach(function (item) {
            var itemWrapper = {};
            itemWrapper[isWrapped ? typeName : jsonKey] = item;
            result = result + getElementFromJson(itemWrapper, componentType, elementsLevel);
        });
    }
    if (isWrapped) {
        result = result + xmlClose(jsonKey, level);
    }
    return result;
}
function readObject(content, t) {
    var result = null;
    var opts = {};
    opts.explicitChildren = false;
    opts.explicitArray = false;
    opts.explicitRoot = isSchema(t) || !t.isExternal();
    xml2js.parseString(content, opts, function (err, res) {
        result = res;
        if (err) {
            throw new Error();
        }
    });
    result = isSchema(t) ? result : postProcess(result, actualType(t));
    return result;
}
exports.readObject = readObject;
function getXmlErrors(root) {
    var errors = root[XML_ERRORS];
    delete root[XML_ERRORS];
    if (!errors || errors.length === 0) {
        return null;
    }
    return errors.map(function (error) { return new typesystem_1.Status(typesystem_1.Status.ERROR, "", error, {}); });
}
exports.getXmlErrors = getXmlErrors;
function actualType(type) {
    if (!type) {
        return type;
    }
    if (isBodyLike(type)) {
        if (!type.superTypes() || type.superTypes().length === 0) {
            return type;
        }
        if (type.superTypes().length === 1) {
            return type.superTypes()[0];
        }
        return _.find(type.allSuperTypes(), function (superType) { return superType.name() === 'object'; }) || type;
    }
    return type;
}
function isBodyLike(type) {
    if (!type) {
        return false;
    }
    return _.find(bodyNames, function (name) { return type.name() === name; }) ? true : false;
}
function isSchema(t) {
    if (isXmlContent(t)) {
        return true;
    }
    return _.find(t.allSuperTypes(), function (supertype) { return isXmlContent(supertype); }) ? true : false;
}
function isXmlContent(t) {
    if (t.isExternal() && t._content && typeof t._content === 'string' && t._content.trim().indexOf('<') === 0) {
        return true;
    }
    return false;
}
function postProcess(result, type) {
    var rootNodeName = Object.keys(result)[0];
    var rootNode = result[rootNodeName];
    var errors = [];
    var expectedRootNodeName = rootXmlName(type);
    //allowing arbitrary root tags
    // if(expectedRootNodeName !== rootNodeName) {
    //     errors.push('Unexpected root node "' + rootNodeName + '", "' + expectedRootNodeName + '" is expected.');
    // }
    var newJson;
    if (type.isArray()) {
        var expectedAttributeNames = [];
        var expectedElementNames = [];
        var componentMeta = type.meta().filter(function (metaInfo) { return metaInfo instanceof restrictions_1.ComponentShouldBeOfType; })[0];
        var typeName = componentMeta && componentMeta.value().name();
        expectedElementNames.push(typeName);
        newJson = getArray(rootNode, type, errors, true);
        fillExtras(rootNode, errors, expectedAttributeNames, expectedElementNames);
    }
    else {
        newJson = buildJson(rootNode, type.isUnion() ? selectFromUnion(rootNode, type) : type, errors);
    }
    newJson[XML_ERRORS] = errors;
    return newJson;
}
function checkErrors(rootNode, actualType) {
    var errors = [];
    var newJson;
    newJson = buildJson(rootNode, actualType, errors);
    var validationErrors = actualType.validateDirect(newJson, true, false).getErrors();
    return errors.length + (validationErrors && validationErrors.length);
}
function selectFromUnion(rootNode, union) {
    var results = [];
    union.typeFamily().forEach(function (type) { return results.push({ type: type, errors: checkErrors(JSON.parse(JSON.stringify(rootNode)), type) }); });
    if (results.length === 0) {
        return union;
    }
    var result = results[0];
    results.forEach(function (oneOf) {
        if (oneOf.errors < result.errors) {
            result = oneOf;
        }
    });
    return result.type;
}
function buildJson(node, type, errors) {
    var initialRoot = {};
    if (!type) {
        return node;
    }
    if (type.isScalar()) {
        return toPrimitiveValue(type, node, errors);
    }
    if (type.isUnion()) {
        return buildJson(node, selectFromUnion(node, type), errors);
    }
    var infos = getInfos(type);
    var expectedAttributeNames = [];
    var expectedElementNames = [];
    getAttributes(node, infos, expectedAttributeNames).forEach(function (attribute) { return initialRoot[Object.keys(attribute)[0]] = attribute[Object.keys(attribute)[0]]; });
    getElements(node, infos, expectedElementNames, errors).forEach(function (element) { return initialRoot[Object.keys(element)[0]] = element[Object.keys(element)[0]]; });
    fillExtras(node, errors, expectedAttributeNames, expectedElementNames);
    return initialRoot;
}
function fillExtras(node, errors, expectedAttributeNames, expectedElementNames, remove) {
    if (remove === void 0) { remove = false; }
    if (typeof node !== "object") {
        return;
    }
    if (!node['$']) {
        node['$'] = {};
    }
    expectedAttributeNames.forEach(function (name) {
        delete node['$'][name];
    });
    expectedElementNames.forEach(function (name) {
        delete node[name];
    });
    var extraAttributes = Object.keys(node['$']);
    delete node['$'];
    var extraElements = Object.keys(node);
    extraAttributes.forEach(function (name) {
        errors.push(ts.error(exports.messageRegistry.UNEXPECTED_XML_ATTRIBUTE, null, { name: name }).getMessage());
    });
    extraElements.forEach(function (name) {
        var message;
        if (name == "_" && typeof node[name] === "string") {
            message = ts.error(exports.messageRegistry.TEXT_CONTENT_NOT_ALLOWED_FOR_THE_XML_ELEMENT, null).getMessage();
        }
        else {
            message = ts.error(exports.messageRegistry.UNEXPECTED_XML_ELEMENT, null, { name: name }).getMessage();
        }
        errors.push(message);
        if (remove) {
            delete node[name];
        }
    });
}
function getInfos(type) {
    return type.meta().filter(function (info) { return info instanceof restrictions_1.PropertyIs; }).map(function (info) { return info; }) || [];
}
function getAttributes(node, infos, expectedNames) {
    var nodeAttributes = node['$'];
    if (!nodeAttributes) {
        return [];
    }
    var attributeInfos = _.filter(infos, function (info) { return xmlDescriptor(info.value()).attribute; });
    return attributeInfos.map(function (info) {
        var attribute = {};
        var key = info.propId();
        var xmlKey = xmlName(info);
        expectedNames.push(xmlKey);
        var value = nodeAttributes[xmlKey];
        attribute[key] = toPrimitiveValue(info.value(), value);
        return attribute[key] === null ? null : attribute;
    }).filter(function (attribute) { return attribute; });
}
function getElements(node, infos, expectedNames, errors) {
    var elementInfos = _.filter(infos, function (info) { return !xmlDescriptor(info.value()).attribute; });
    return elementInfos.map(function (info) {
        var element = {};
        var descriptor = xmlDescriptor(info.value());
        var key = info.propId();
        var xmlKey = xmlName(info);
        expectedNames.push(xmlKey);
        var value = node[xmlKey];
        if (info.value().isArray()) {
            element[key] = getArray(node[xmlKey], info.value(), errors);
        }
        else {
            element[key] = (value || value === '') ? buildJson(value, info.value(), errors) : null;
        }
        return element[key] === null ? null : element;
    }).filter(function (info) { return info; });
}
function getArray(values, type, errors, rootNode) {
    if (rootNode === void 0) { rootNode = false; }
    var descriptor = xmlDescriptor(type);
    var isWrapped = rootNode || descriptor.wrapped;
    var componentType = arrayElementType(type);
    var typeName = componentType && componentType.name();
    if (isWrapped) {
        var valuesWrapper = values;
        values = values && values[typeName];
        fillExtras(valuesWrapper, errors, [], [typeName], true);
    }
    if (!values) {
        return [];
    }
    values = getArrayValues(values);
    if (isArray(values)) {
        values = values.map(function (value) { return buildJson(value, componentType, errors); });
    }
    else {
        values = (typeof values === 'object' && values) || [];
    }
    return values;
}
function getArrayValues(preValues) {
    if (isArray(preValues)) {
        return preValues;
    }
    if (typeof preValues === 'object') {
        return [preValues];
    }
    return [];
}
function arrayElementType(arrayType) {
    if (!arrayType || !arrayType.isArray()) {
        return null;
    }
    var componentMetas = arrayType.meta().filter(function (metaInfo) { return metaInfo instanceof restrictions_1.ComponentShouldBeOfType; });
    return componentMetas && componentMetas.length > 0 && componentMetas[0].value();
}
function xmlName(property) {
    var descriptor = xmlDescriptor(property.value());
    var ramlName = property.propId();
    var actualName = descriptor.name || ramlName;
    if (descriptor.namespace) {
        actualName = descriptor.namespace + ':' + actualName;
    }
    return (descriptor.prefix || '') + actualName;
}
function rootXmlName(type) {
    var descriptor = xmlDescriptor(type);
    var ramlName = type.name();
    if (ramlName === '' && type.isUnion()) {
        ramlName = 'object';
    }
    var actualName = descriptor.name || ramlName;
    return (descriptor.prefix || '') + actualName;
}
function xmlDescriptor(type) {
    var info = type.meta().filter(function (xmlInfo) { return xmlInfo instanceof metainfo_1.XMLInfo; }).map(function (xmlInfo) { return xmlInfo; })[0];
    var result = {
        attribute: false,
        wrapped: false,
        name: false,
        namespace: false,
        prefix: false
    };
    if (!info) {
        return result;
    }
    var infoValue = info.value();
    if (!infoValue) {
        return result;
    }
    Object.keys(result).forEach(function (key) {
        result[key] = infoValue[key] || result[key];
    });
    return result;
}
function toPrimitiveValue(type, actual, errors) {
    if (errors === void 0) { errors = []; }
    if (typeof actual === 'object') {
        var result = toPrimitiveValue(type, actual['_']);
        delete actual['_'];
        fillExtras(actual, errors, [], [], true);
        return result;
    }
    if (!actual && actual.trim() !== '') {
        return null;
    }
    if (type.isNumber()) {
        var parsedValue = parseFloat(actual);
        if (!isNaN(parsedValue)) {
            return parsedValue;
        }
    }
    if (type.isBoolean()) {
        if (actual === 'true') {
            return true;
        }
        if (actual === 'false') {
            return false;
        }
    }
    if (typeof actual === 'string') {
        return actual;
    }
    return null;
}
function isArray(instance) {
    if (!instance) {
        return false;
    }
    return typeof instance === 'object' && typeof instance.length === 'number';
}
//# sourceMappingURL=xmlio.js.map