'use strict';

var serialize = require('./serialize');

module.exports = Exposed;

function Exposed() {
    Object.defineProperties(this, {
        // Brand.
        '@exposed': {value: true},

        // Defines a "hidden" property which holds an ordered list of exposed
        // namespaces. When new namespaces are exposed, existing ones are
        // examined and removed if they are to become noops.
        '__namespaces__': {value: []}
    });
}

Exposed.create = function (exposed) {
    if (!Exposed.isExposed(exposed)) {
        return new Exposed();
    }

    // Inherit current namespaces state from the parent exposed instance.
    var namespaces = exposed.__namespaces__.concat();

    // Creates a new exposed object with the specified `exposed` instance as
    // its prototype. This allows the new object to inherit from, *and* shadow
    // the existing object.
    return Object.create(exposed, {
        __namespaces__: {value: namespaces}
    });
};

Exposed.isExposed = function (obj) {
    return !!(obj && obj['@exposed']);
};

// TODO: Should this be a static method so it doesn't reserve the "add"
// namespace on all Exposed instances?
Exposed.prototype.add = function (namespace, value) {
    var nsRegex       = new RegExp('^' + namespace + '(?:$|\\..+)'),
        namespaces    = this.__namespaces__,
        oldNamespaces = namespaces.filter(nsRegex.test.bind(nsRegex));

    // Removes previously exposed namespaces and values which no longer apply
    // and have become noops.
    oldNamespaces.forEach(function (namespace) {
        delete this[namespace];
        namespaces.splice(namespaces.indexOf(namespace), 1);
    }, this);

    // Stores the new exposed namespace and its current value.
    namespaces.push(namespace);
    this[namespace] = value;
};

Exposed.prototype.toString = function () {
    var rendered   = {},
        namespaces = [],
        exposed    = [];

    // Values are exposed at their namespace in the order they were `add()`ed.
    this.__namespaces__.forEach(function (namespace) {
        var parts    = namespace.split('.'),
            leafPart = parts.pop(),
            nsPart   = 'root';

        // Renders the JavaScript to instantiate each namespace as needed, and
        // does so efficiently making sure to only instantiate each part of the
        // namespace once.
        while (parts.length) {
            nsPart += '.' + parts.shift();

            if (!rendered[nsPart]) {
                rendered[nsPart] = true;
                namespaces.push(nsPart  + ' || (' + nsPart + ' = {});');
            }
        }

        // Renders the JavaScript to assign the serialized value to the
        // namespace. These assignments are done in the order in which they were
        // exposed via the `add()` method.
        nsPart += '.' + leafPart;
        exposed.push(nsPart + ' = ' + serialize(this[namespace]) + ';');
    }, this);

    return [
        '',
        '(function (root) {',
        '// -- Namespaces --',
        namespaces.join('\n'),
        '',
        '// -- Data --',
        exposed.join('\n'),
        '}(this));',
        ''
    ].join('\n');
};

@@ -1881,18 +1881,18 @@
 paces = 
-%5B%5D
+''
 ,%0A      
@@ -1897,23 +1897,23 @@
     
-exposed
+data  
     
+ 
 = 
-%5B%5D
+''
 ;%0A%0A 
@@ -2460,49 +2460,8 @@
 ) %7B%0A
-                rendered%5BnsPart%5D = true;%0A
     
@@ -2482,22 +2482,20 @@
 mespaces
-.push(
+ += 
 nsPart  
@@ -2523,18 +2523,60 @@
 ' = %7B%7D);
-')
+%5Cn';%0A                rendered%5BnsPart%5D = true
 ;%0A      
@@ -2836,21 +2836,16 @@
     
-exposed.push(
+data += 
 nsPa
@@ -2888,18 +2888,19 @@
 e%5D) + ';
+%5Cn
 '
-)
 ;%0A    %7D,
@@ -2919,17 +2919,17 @@
  return 
-%5B
+(
 %0A       
@@ -2930,28 +2930,18 @@
        '
-',%0A        '
+%5Cn
 (functio
@@ -2950,27 +2950,31 @@
 (root) %7B
-',%0A
+%5Cn' +%0A 
         
 '// -- N
@@ -2961,24 +2961,27 @@
  +%0A         
+   
 '// -- Names
@@ -2988,18 +2988,21 @@
 paces --
-',
+%5Cn' +
 %0A       
@@ -2994,32 +2994,36 @@
 --%5Cn' +%0A        
+    
 namespaces.join(
@@ -3020,33 +3020,15 @@
 aces
-.join('%5Cn'),%0A        '',%0A
+ +%0A    
     
@@ -3028,24 +3028,26 @@
            '
+%5Cn
 // -- Data -
@@ -3051,83 +3051,51 @@
 a --
-',%0A        exposed.join('%5Cn'),%0A        '%7D(this));',%0A        ''%0A    %5D.join('
+%5Cn' +%0A            data +%0A        '%7D(this));
 %5Cn')

Improve `toString()` serialization performance