import * as tslib_1 from "tslib";
import { isNumber, isString } from 'lodash';
import { isMoment } from 'moment';
import { Binary } from './type/binary.type';
import { MomentType } from './type/moment.type';
import { NullType } from './type/null.type';
import { UndefinedType } from './type/undefined.type';
var BUFFER_TYPES = [
    'Buffer',
    'File',
    'Blob',
    'ArrayBuffer',
    'DataView',
    'Int8Array',
    'Uint8Array',
    'Uint8ClampedArray',
    'Int16Array',
    'Uint16Array',
    'Int32Array',
    'Uint32Array',
    'Float32Array',
    'Float64Array',
];
var Util = /** @class */ (function () {
    function Util() {
    }
    /**
     * Detects the dynamodb type to which an collection value should be mapped. Empty collections will be mapped to L(ist).
     * Collections of type array where all the values are either String | Number | Binary will be mapped to the corresponding S(et)
     * type. If the item types are heterogeneous or it is a non supported set type the returned type will be L(ist).
     * The logic for collection fo type Set is the same.
     *
     * @param {any[] | Set<any>} collection
     * @returns {AttributeCollectionType}
     */
    Util.detectCollectionType = function (collection) {
        var e_1, _a;
        if (Array.isArray(collection)) {
            if (collection.length) {
                if (collection.every(isString)) {
                    return 'SS';
                }
                if (collection.every(isNumber)) {
                    return 'NS';
                }
                if (collection.every(Util.isBinary)) {
                    return 'BS';
                }
            }
            return 'L';
        }
        else if (Util.isSet(collection)) {
            var firstValueType = collection.size
                ? Util.detectType(collection.values().next().value)
                : null;
            var heterogeneous = false;
            try {
                for (var collection_1 = tslib_1.__values(collection), collection_1_1 = collection_1.next(); !collection_1_1.done; collection_1_1 = collection_1.next()) {
                    var item = collection_1_1.value;
                    var type = Util.detectType(item);
                    if (type !== firstValueType) {
                        heterogeneous = true;
                        break;
                    }
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (collection_1_1 && !collection_1_1.done && (_a = collection_1.return)) _a.call(collection_1);
                }
                finally { if (e_1) throw e_1.error; }
            }
            if (heterogeneous) {
                return 'L';
            }
            else {
                switch (firstValueType) {
                    case 'S':
                        return 'SS';
                    case 'N':
                        return 'NS';
                    case 'B':
                        return 'BS';
                    default:
                        return 'L';
                }
            }
        }
        else {
            throw new Error('given collection was no array or Set -> type could not be detected');
        }
    };
    Util.typeByConvention = function (propertyKey) {
        var type;
        Object.keys(Util.REGEX_CONVENTIONS).forEach(function (key) {
            if (Util.REGEX_CONVENTIONS[key].test(propertyKey)) {
                type = key;
            }
        });
        return type;
    };
    Util.isCollection = function (value) {
        return value && (Array.isArray(value) || Util.isSet(value));
    };
    Util.isSet = function (value) {
        return ((value !== null && value !== undefined && value.hasOwnProperty('name') && value.name === 'Set') ||
            value instanceof Set);
    };
    Util.detectType = function (value) {
        if (Util.isCollection(value)) {
            return Util.detectCollectionType(value);
        }
        else {
            if (isString(value)) {
                return 'S';
            }
            if (isNumber(value)) {
                return 'N';
            }
            if (Util.isBinary(value)) {
                return 'B';
            }
            if (value === null) {
                return 'NULL';
            }
            if (typeof value === 'boolean') {
                return 'BOOL';
            }
            if (typeof value === 'object') {
                return 'M';
            }
        }
        throw new Error("the type for value " + value + " could not be detected");
    };
    /**
     * Will resolve the type based on given data.
     *
     * @param data
     * @returns {AttributeModelTypeName}
     */
    Util.typeOf = function (data) {
        if (data === null) {
            return NullType;
        }
        else {
            if (Array.isArray(data)) {
                return Array;
            }
            else if (data instanceof Set) {
                return Set;
            }
            else if (data instanceof Map) {
                return Map;
            }
            else if (data instanceof Date) {
                return Date;
            }
            else if (isMoment(data)) {
                return MomentType;
            }
            else if (Util.isBinary(data)) {
                return Binary;
            }
            else {
                switch (typeof data) {
                    case 'string':
                        return String;
                    case 'number':
                        return Number;
                    case 'boolean':
                        return Boolean;
                    case 'undefined':
                        return UndefinedType;
                    case 'object':
                        return Object;
                }
            }
        }
        throw new Error("typeof data " + data + " could not be detected");
    };
    /*
     * copied from https://github.com/aws/aws-sdk-js/blob/0c974a7ff6749a541594de584b43a040978d4b72/lib/dynamodb/types.js
     * should we work with string match
     */
    Util.typeOfFromDb = function (attributeValue) {
        if (attributeValue) {
            var dynamoType = Object.keys(attributeValue)[0];
            switch (dynamoType) {
                case 'S':
                    if (Util.DATE_TIME_ISO8601.test(attributeValue.S)) {
                        return MomentType;
                    }
                    else {
                        return String;
                    }
                case 'N':
                    return Number;
                case 'B':
                    return Binary;
                case 'BOOL':
                    return Boolean;
                case 'SS':
                case 'NS':
                case 'BS':
                    return Set;
                case 'L':
                    return Array;
                case 'M':
                    return Object;
                case 'NULL':
                    return NullType;
            }
        }
        throw new Error("could not resolve the dynamo db type for attribute value " + attributeValue);
    };
    Util.isBinary = function (data) {
        if (Util.isNode()) {
            // TODO LOW:BINARY should add || data instanceof Stream
            return Buffer.isBuffer(data);
        }
        else {
            return BUFFER_TYPES.some(function (type) {
                return data !== undefined &&
                    data.constructor &&
                    (Util.isType(data, type) || Util.typeName(data.constructor) === type);
            });
        }
    };
    Util.isBufferType = function (type) {
        return BUFFER_TYPES.includes(type);
    };
    /*
     * copied from https://github.com/aws/aws-sdk-js/blob/0c974a7ff6749a541594de584b43a040978d4b72/lib/util.js
     */
    Util.isType = function (obj, type) {
        // handle cross-"frame" objects
        if (typeof type === 'function') {
            type = Util.typeName(type);
        }
        return Object.prototype.toString.call(obj) === '[object ' + type + ']';
    };
    Util.isBrowser = function () {
        return process && process.browser;
    };
    Util.isNode = function () {
        return !Util.isBrowser();
    };
    /**
     * Returns the name of the given Type. null and undefined are special cases were we return 'Null' vs. 'Undefined'
     * @param type
     * @returns {string}
     */
    Util.typeName = function (type) {
        if (type !== null && type !== undefined) {
            if (Object.prototype.hasOwnProperty.call(type, 'name')) {
                return type.name;
            }
            else {
                var str = type.toString();
                var match = str.match(/^\s*function (.+)\(/);
                return match ? match[1] : str;
            }
        }
        else {
            if (type === null) {
                return 'Null';
            }
            else if (type === undefined) {
                return 'Undefined';
            }
        }
        throw new Error("was not able to resolve type name for type " + type);
    };
    // FIXME UUID replace with a more bullet proof implementation node uuid module requires crypto, need to figure out how to use it with browser
    Util.uuidv4 = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            // tslint:disable
            var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
            return v.toString(16);
        });
    };
    Util.REGEX_CONVENTIONS = {
        date: /^(?:date|[\w]+(?:Date|At)(?:[A-Z]{1}[\w]+)?)$/,
    };
    // tslint:disable-next-line:max-line-length
    Util.DATE_TIME_ISO8601 = /^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)$/;
    return Util;
}());
export { Util };
//# sourceMappingURL=util.js.map