# Source: core/serialization/serializer/montage-labeler.js

    var Montage = require("../../core").Montage;

    exports.MontageLabeler = Montage.specialize({
        _labelRegexp: {value: /^[a-zA-Z_$][0-9a-zA-Z_$]*$/},
        _labels: {value: null},
        // hash(object) -> label
        _objectsLabels: {value: null},
        _objects: {value: null},
        // Labels generation sequence is "label", "label2", "label3", ..., hence
        // starting at 2.
        _INITIAL_LABEL_NUMBER: {value: 2},
        _baseNamesIndex: {value: null},
        _userDefinedLabels: {value: null},

        constructor: {
            value: function MontageLabeler() {
                this._labels = Object.create(null);
                this._objectsLabels = Object.create(null);
                this._objects = Object.create(null);
                this._baseNamesIndex = Object.create(null);
                this._userDefinedLabels = Object.create(null);
            }
        },

        getTemplatePropertyLabel: {
            value: function (object) {
                var label = this._getObjectLabel(object);

                if (label[0] !== ":") {
                    throw new Error("Template property's labels need to start with a colon (:), (\"" + label + "\").");
                }

                return label;
            }
        },

        _getObjectLabel: {
            value: function(object) {
                var hash = Object.hash(object),
                    label;

                if (hash in this._objectsLabels) {
                    if (Object.hasOwnProperty.call(this._objectsLabels, hash)) {
                        label = this._objectsLabels[hash];
                    }
                } else {
                    label = this.generateObjectLabel(object);
                    this.setObjectLabel(object, label);
                }

                return label;
            }
        },

        getObjectLabel: {
            value: function (object) {
                var label = this._getObjectLabel(object);

                if (label[0] === ":") {
                    throw new Error("Labels starting with colon (:) can only be used for template values, (\"" + label + "\").");
                }

                return label;
            }
        },

        getObjectName: {
            value: function (object) {
                var identifier = object.identifier,
                    objectName;

                if (identifier && this._labelRegexp.test(identifier)) {
                    objectName = object.identifier;
                } else if (object && typeof object === "object" && "getInfoForObject" in object || "getInfoForObject" in object.constructor ) {
                    objectName = Montage.getInfoForObject(object).objectName;
                    objectName = objectName.toLowerCase();
                } else {
                    if (Array.isArray(object)) {
                        objectName = "array";
                    } else if (RegExp.isRegExp(object)) {
                        objectName = "regexp";
                    } else {
                        objectName =  typeof object;
                    }
                }

                return objectName;
            }
        },

        /**
         * When the labeler is initialized with objects these objects are
         * considered user defined objects.
         */
        initWithObjects: {
            value: function(labels) {
                for (var label in labels) {
                    if (labels.hasOwnProperty(label)) {
                        this.setObjectLabel(labels[label], label);
                        this._userDefinedLabels[label] = true;
                    }
                }
            }
        },

        cleanup: {
            value: function() {
                this._labels = null;
                this._objectsLabels = null;
                this._objects = null;
                this._baseNamesIndex = null;
                this._userDefinedLabels = null;
            }
        },

        generateLabel: {
            value: function(baseName) {
                var index = this._baseNamesIndex[baseName],
                    label;

                do {
                    if (index) {
                        label = baseName + index;
                        this._baseNamesIndex[baseName] = index = index + 1;
                    } else {
                        label = baseName;
                        this._baseNamesIndex[baseName] = index = this._INITIAL_LABEL_NUMBER;
                    }
                } while (label in this._labels);

                return label;
            }
        },

        getLabelBaseName: {
            value: function(label) {
                return label.replace(/\d*$/, "");
            }
        },

        addLabel: {
            value: function(label) {
                this._labels[label] = true;
            }
        },

        addLabels: {
            value: function(labels) {
                for (var i = 0, ii = labels.length; i < ii; i++) {
                    this.addLabel(labels[i]);
                }
            }
        },

        isLabelDefined: {
            value: function(label) {
                return label in this._labels;
            }
        },

        isUserDefinedLabel: {
            value: function(label) {
                return label in this._userDefinedLabels;
            }
        },

        generateObjectLabel: {
            value: function(object) {
                var objectName = this.getObjectName(object);

                return this.generateLabel(objectName);
            }
        },

        setObjectLabel: {
            value: function(object, label) {
                if (typeof object !== "undefined") {
                    var hash = Object.hash(object);

                    this.addLabel(label);
                    this._objectsLabels[hash] = label;
                    this._objects[label] = object;
                }
            }
        },

        getObjectByLabel: {
            value: function(label) {
                return this._objects[label];
            }
        }
    });