"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Module for handling serialization and deserialization of data contracts
* (contracts sent from the server using the VSO default REST api serialization settings)
*/
var ContractSerializer;
(function (ContractSerializer) {
    var _legacyDateRegExp;
    /**
     * Process a contract in its raw form (e.g. date fields are Dates, and Enums are numbers) and
     * return a pure JSON object that can be posted to REST endpoint.
     *
     * @param data The object to serialize
     * @param contractMetadata The type info/metadata for the contract type being serialized
     * @param preserveOriginal If true, don't modify the original object. False modifies the original object (the return value points to the data argument).
     */
    function serialize(data, contractMetadata, preserveOriginal) {
        if (data && contractMetadata) {
            if (Array.isArray(data)) {
                return _getTranslatedArray(data, contractMetadata, true, preserveOriginal);
            }
            else {
                return _getTranslatedObject(data, contractMetadata, true, preserveOriginal);
            }
        }
        else {
            return data;
        }
    }
    ContractSerializer.serialize = serialize;
    /**
     * Process a pure JSON object (e.g. that came from a REST call) and transform it into a JS object
     * where date strings are converted to Date objects and enum values are converted from strings into
     * their numerical value.
     *
     * @param data The object to deserialize
     * @param contractMetadata The type info/metadata for the contract type being deserialize
     * @param preserveOriginal If true, don't modify the original object. False modifies the original object (the return value points to the data argument).
     * @param unwrapWrappedCollections If true check for wrapped arrays (REST apis will not return arrays directly as the root result but will instead wrap them in a { values: [], count: 0 } object.
     */
    function deserialize(data, contractMetadata, preserveOriginal, unwrapWrappedCollections) {
        if (data) {
            if (unwrapWrappedCollections && Array.isArray(data.value)) {
                // Wrapped json array - unwrap it and send the array as the result
                data = data.value;
            }
            if (contractMetadata) {
                if (Array.isArray(data)) {
                    data = _getTranslatedArray(data, contractMetadata, false, preserveOriginal);
                }
                else {
                    data = _getTranslatedObject(data, contractMetadata, false, preserveOriginal);
                }
            }
        }
        return data;
    }
    ContractSerializer.deserialize = deserialize;
    function _getTranslatedArray(array, typeMetadata, serialize, preserveOriginal) {
        var resultArray = array;
        var arrayCopy = [];
        var i;
        for (i = 0; i < array.length; i++) {
            var item = array[i];
            var processedItem;
            // handle arrays of arrays
            if (Array.isArray(item)) {
                processedItem = _getTranslatedArray(item, typeMetadata, serialize, preserveOriginal);
            }
            else {
                processedItem = _getTranslatedObject(item, typeMetadata, serialize, preserveOriginal);
            }
            if (preserveOriginal) {
                arrayCopy.push(processedItem);
                if (processedItem !== item) {
                    resultArray = arrayCopy;
                }
            }
            else {
                array[i] = processedItem;
            }
        }
        return resultArray;
    }
    function _getTranslatedObject(typeObject, typeMetadata, serialize, preserveOriginal) {
        var processedItem = typeObject, copiedItem = false;
        if (typeObject && typeMetadata.fields) {
            for (var fieldName in typeMetadata.fields) {
                var fieldMetadata = typeMetadata.fields[fieldName];
                var fieldValue = typeObject[fieldName];
                var translatedValue = _getTranslatedField(fieldValue, fieldMetadata, serialize, preserveOriginal);
                if (fieldValue !== translatedValue) {
                    if (preserveOriginal && !copiedItem) {
                        processedItem = this._extend({}, typeObject);
                        copiedItem = true;
                    }
                    processedItem[fieldName] = translatedValue;
                }
            }
        }
        return processedItem;
    }
    function _getTranslatedField(fieldValue, fieldMetadata, serialize, preserveOriginal) {
        if (!fieldValue) {
            return fieldValue;
        }
        if (fieldMetadata.isArray) {
            if (Array.isArray(fieldValue)) {
                var newArray = [], processedArray = fieldValue;
                for (var index = 0; index < fieldValue.length; index++) {
                    var arrayValue = fieldValue[index];
                    var processedValue = arrayValue;
                    if (fieldMetadata.isDate) {
                        processedValue = _getTranslatedDateValue(arrayValue, serialize);
                    }
                    else if (fieldMetadata.enumType) {
                        processedValue = _getTranslatedEnumValue(fieldMetadata.enumType, arrayValue, serialize);
                    }
                    else if (fieldMetadata.typeInfo) {
                        if (Array.isArray(arrayValue)) {
                            processedValue = _getTranslatedArray(arrayValue, fieldMetadata.typeInfo, serialize, preserveOriginal);
                        }
                        else {
                            processedValue = _getTranslatedObject(arrayValue, fieldMetadata.typeInfo, serialize, preserveOriginal);
                        }
                    }
                    if (preserveOriginal) {
                        newArray.push(processedValue);
                        if (processedValue !== arrayValue) {
                            processedArray = newArray;
                        }
                    }
                    else {
                        fieldValue[index] = processedValue;
                    }
                }
                return processedArray;
            }
            else {
                return fieldValue;
            }
        }
        else if (fieldMetadata.isDictionary) {
            var dictionaryModified = false;
            var newDictionary = {};
            for (var key in fieldValue) {
                var dictionaryValue = fieldValue[key];
                var newKey = key, newValue = dictionaryValue;
                if (fieldMetadata.dictionaryKeyIsDate) {
                    newKey = _getTranslatedDateValue(key, serialize);
                }
                else if (fieldMetadata.dictionaryKeyEnumType) {
                    newKey = _getTranslatedEnumValue(fieldMetadata.dictionaryKeyEnumType, key, serialize);
                }
                if (fieldMetadata.dictionaryValueIsDate) {
                    newValue = _getTranslatedDateValue(dictionaryValue, serialize);
                }
                else if (fieldMetadata.dictionaryValueEnumType) {
                    newValue = _getTranslatedEnumValue(fieldMetadata.dictionaryValueEnumType, dictionaryValue, serialize);
                }
                else if (fieldMetadata.dictionaryValueTypeInfo) {
                    newValue = _getTranslatedObject(newValue, fieldMetadata.dictionaryValueTypeInfo, serialize, preserveOriginal);
                }
                else if (fieldMetadata.dictionaryValueFieldInfo) {
                    newValue = _getTranslatedField(dictionaryValue, fieldMetadata.dictionaryValueFieldInfo, serialize, preserveOriginal);
                }
                newDictionary[newKey] = newValue;
                if (key !== newKey || dictionaryValue !== newValue) {
                    dictionaryModified = true;
                }
            }
            return dictionaryModified ? newDictionary : fieldValue;
        }
        else {
            if (fieldMetadata.isDate) {
                return _getTranslatedDateValue(fieldValue, serialize);
            }
            else if (fieldMetadata.enumType) {
                return _getTranslatedEnumValue(fieldMetadata.enumType, fieldValue, serialize);
            }
            else if (fieldMetadata.typeInfo) {
                return _getTranslatedObject(fieldValue, fieldMetadata.typeInfo, serialize, preserveOriginal);
            }
            else {
                return fieldValue;
            }
        }
    }
    function _getTranslatedEnumValue(enumType, valueToConvert, serialize) {
        if (serialize && typeof valueToConvert === "number") {
            // Serialize: number --> String
            // Because webapi handles the numerical value for enums, there is no need to convert to string.
            // Let this fall through to return the numerical value.
        }
        else if (!serialize && typeof valueToConvert === "string") {
            // Deserialize: String --> number
            var result = 0;
            if (valueToConvert) {
                var splitValue = valueToConvert.split(",");
                for (var i = 0; i < splitValue.length; i++) {
                    var valuePart = splitValue[i];
                    //equivalent to jquery trim
                    //copied from https://github.com/HubSpot/youmightnotneedjquery/blob/ef987223c20e480fcbfb5924d96c11cd928e1226/comparisons/utils/trim/ie8.js
                    var enumName = valuePart.replace(/^\s+|\s+$/g, '') || "";
                    if (enumName) {
                        var resultPart = enumType.enumValues[enumName];
                        if (!resultPart) {
                            // No matching enum value. Try again but case insensitive
                            var lowerCaseEnumName = enumName.toLowerCase();
                            if (lowerCaseEnumName !== enumName) {
                                for (var name in enumType.enumValues) {
                                    var value = enumType.enumValues[name];
                                    if (name.toLowerCase() === lowerCaseEnumName) {
                                        resultPart = value;
                                        break;
                                    }
                                }
                            }
                        }
                        if (resultPart) {
                            result |= resultPart;
                        }
                    }
                }
            }
            return result;
        }
        return valueToConvert;
    }
    function _getTranslatedDateValue(valueToConvert, serialize) {
        if (!serialize && typeof valueToConvert === "string") {
            // Deserialize: String --> Date
            var dateValue = new Date(valueToConvert);
            if (isNaN(dateValue) && navigator.userAgent && /msie/i.test(navigator.userAgent)) {
                dateValue = _convertLegacyIEDate(valueToConvert);
            }
            return dateValue;
        }
        return valueToConvert;
    }
    function _convertLegacyIEDate(dateStringValue) {
        // IE 8/9 does not handle parsing dates in ISO form like:
        // 2013-05-13T14:26:54.397Z
        var match;
        if (!_legacyDateRegExp) {
            _legacyDateRegExp = new RegExp("(\\d+)-(\\d+)-(\\d+)T(\\d+):(\\d+):(\\d+).(\\d+)Z");
        }
        match = _legacyDateRegExp.exec(dateStringValue);
        if (match) {
            return new Date(Date.UTC(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]), parseInt(match[7])));
        }
        else {
            return null;
        }
    }
    // jquery extend method in native javascript (used to clone objects)
    // copied from https://github.com/HubSpot/youmightnotneedjquery/blob/ef987223c20e480fcbfb5924d96c11cd928e1226/comparisons/utils/extend/ie8.js
    var _extend = function (out) {
        out = out || {};
        for (var i = 1; i < arguments.length; i++) {
            if (!arguments[i])
                continue;
            for (var key in arguments[i]) {
                if (arguments[i].hasOwnProperty(key))
                    out[key] = arguments[i][key];
            }
        }
        return out;
    };
})(ContractSerializer = exports.ContractSerializer || (exports.ContractSerializer = {}));