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;