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

    var Montage = require("../../core").Montage;
    var MontageAst = require("./montage-ast");

    /**
     * ElementReference
     * @class MontageBuilder
     */
    var MontageBuilder = Montage.specialize(/** @lends MontageBuilder# */ {
        _root: {value: null},
        _stack: {value: null},
        // _references structure is:
        // {
        //     <label>: [<reference>*]
        // }
        _references: {value: null},
        _placeholderProperty: {value: Object.create(null)},

        constructor: {
            value: function MontageBuilder() {
                this.init();
            }
        },

        createElementReference: {
            value: function (id) {
                return new MontageAst.ElementReference()
                    .initWithRootAndId(this._root, id);
            }
        },

        createModuleReference: {
            value: function (moduleId) {
                return new MontageAst.ModuleReference()
                    .initWithRootAndModuleId(this._root, moduleId);
            }
        },

        init: {
            value: function() {
                this._references = Object.create(null);
                this._root = new MontageAst.Root();
                this._stack = [this._root];

                return this;
            }
        },

        cleanup: {
            value: function() {
                this._references = null;
                this._root = null;
                this._stack = null;
            }
        },

        getExternalReferences: {
            value: function(oldLabel, newLabel) {
                var references = this._references,
                    root = this._root,
                    externalReferences = [];

                for (var label in references) {
                    // placeholder values are not created at reference creation
                    // time, so we need to check for both states, before a
                    // placeholder is created and after.
                    if (!root.hasProperty(label) ||
                        root.getProperty(label) === this._placeholderProperty) {
                        externalReferences.push(label);
                    }
                }

                return externalReferences;
            }
        },

        relabelReferences: {
            value: function(oldLabel, newLabel) {
                var references = this._references[oldLabel];

                if (references) {
                    references = references.slice(0);
                    for (var i = 0, ii = references.length; i < ii; i++) {
                        references[i].value = newLabel;
                    }
                }
            }
        },

        _registerReference: {
            value: function(reference) {
                var references = this._references,
                    label = reference.value;

                if (references[label]) {
                    references[label].push(reference);
                } else {
                    references[label] = [reference];
                }
            }
        },

        _unregisterReference: {
            value: function(reference) {
                var label = reference.label,
                    labelReferences = this._references[label],
                    ix;

                if (labelReferences.length === 1) {
                    delete this._references[label];
                } else {
                    ix = labelReferences.indexOf(reference);

                    if (ix === -1) {
                        throw new Error("Reference '" + label + "' not found in registry.");
                    } else {
                        labelReferences.splice(ix, 1);
                    }
                }
            }
        },

        _createPlaceholdersForReferences: {
            value: function() {
                var references = this._references,
                    root = this._root;

                for (var label in references) {
                    if (Object.hasOwnProperty.call(references, label)) {
                        if (!root.hasProperty(label)) {
                            root.setProperty(label, this._placeholderProperty);
                        }
                    }
                }
            }
        },

        getSerialization: {
            value: function(indent) {
                this._createPlaceholdersForReferences();

                return this._root.serialize(indent);
            }
        },

        root: {
            get: function() {
                return this._root;
            }
        },

        top: {
            get: function() {
                return this._stack[0];
            }
        },

        push: {
            value: function(value) {
                return this._stack.unshift(value);
            }
        },

        pop: {
            value: function() {
                return this._stack.shift();
            }
        },

        createObjectLiteral: {
            value: function() {
                return new MontageAst.ObjectLiteral(this._root, Object.create(null));
            }
        },

        createArray: {
            value: function() {
                return new MontageAst.ObjectLiteral(this._root, []);
            }
        },

        createObjectReference: {
            value: function(label) {
                var reference = new MontageAst.ObjectReference(this._root, label);

                this._registerReference(reference);

                return reference;
            }
        },

        createRegExp: {
            value: function(regexp) {
                return new MontageAst.RegExpObject(this._root, regexp);
            }
        },

        createString: {
            value: function(string) {
                return new MontageAst.Value(this._root, string);
            }
        },

        createNumber: {
            value: function(number) {
                return new MontageAst.Value(this._root, number);
            }
        },

        createBoolean: {
            value: function(boolean) {
                return new MontageAst.Value(this._root, boolean);
            }
        },

        createNull: {
            value: function(boolean) {
                return new MontageAst.Value(this._root, null);
            }
        },

        createCustomObject: {
            value: function() {
                return new MontageAst.CustomObject(this._root);
            }
        }
    });

    exports.MontageBuilder = MontageBuilder;