# Source: repository/AbstractNode.js

    (function(window)
    {
        var Gitana = window.Gitana;

        Gitana.AbstractNode = Gitana.AbstractRepositoryObject.extend(
        /** @lends Gitana.AbstractNode.prototype */
        {
            /**
             * @constructs
             * @augments Gitana.AbstractRepositoryObject
             *
             * @class Abstract base class for Gitana Node implementations.
             *
             * @param {Gitana.Branch} branch
             * @param {Object} [object]
             */
            constructor: function(branch, object)
            {
                // helper methods for system-managed state

                this.__qname = (function() {
                    var _qname = null;
                    return function(qname) {
                        if (!Gitana.isUndefined(qname)) { _qname = qname; }
                        return _qname;
                    };
                })();

                this.__type = (function() {
                    var _type = null;
                    return function(type) {
                        if (!Gitana.isUndefined(type)) { _type = type; }
                        return _type;
                    };
                })();

                this.__features = (function() {
                    var _features = {};
                    return function(features) {
                        if (!Gitana.isUndefined(features)) { _features = features; }
                        return _features;
                    };
                })();

                this.__stats = (function() {
                    var _stats = {};
                    return function(stats) {
                        if (!Gitana.isUndefined(stats)) { _stats = stats; }
                        return _stats;
                    };
                })();

                this.__is_association = (function() {
                    var _is_association = false;
                    return function(is_association) {
                        if (!Gitana.isUndefined(is_association)) { _is_association = is_association; }
                        return _is_association;
                    };
                })();

                // now call base
                // important this happens AFTER helpers above so that handleSystemProperties works
                this.base(branch.getRepository(), object);

                //////////////////////////////////////////////////////////////////////////////////////////////
                //
                // PRIVILEGED METHODS
                //
                //////////////////////////////////////////////////////////////////////////////////////////////

                /**
                 * Gets the Gitana Branch object.
                 *
                 * @inner
                 *
                 * @returns {Gitana.Branch} The Gitana Branch object
                 */
                this.getBranch = function() { return branch; };

                /**
                 * Gets the Gitana Branch id.
                 *
                 * @inner
                 *
                 * @returns {String} The Gitana Branch id
                 */
                this.getBranchId = function() { return branch.getId(); };
            },

            /**
             * @override
             */
            getUri: function()
            {
                return "/repositories/" + this.getRepositoryId() + "/branches/" + this.getBranchId() + "/nodes/" + this.getId();
            },

            /**
             * @override
             */
            ref: function()
            {
                return "node://" + this.getPlatformId() + "/" + this.getRepositoryId() + "/" + this.getBranchId() + "/" + this.getId();
            },

            /**
             * @override
             */
            clone: function()
            {
                return this.getFactory().node(this.getBranch(), this);
            },

            /**
             * @override
             */
            handleSystemProperties: function(response)
            {
                this.base(response);

                // strip out "_qname"
                if (this["_qname"])
                {
                    var _qname = this["_qname"];
                    delete this["_qname"];
                    this.__qname(_qname);
                }

                // strip out "_type"
                if (this["_type"])
                {
                    var _type = this["_type"];
                    delete this["_type"];
                    this.__type(_type);
                }

                // strip out "_features"
                if (this["_features"])
                {
                    var _features = this["_features"];
                    delete this["_features"];
                    this.__features(_features);
                }

                // strip out "_statistics"
                if (this["_statistics"] && typeof(this["_statistics"]) == "object")
                {
                    var stats = this["_statistics"];
                    delete this["_statistics"];
                    this.__stats(stats);
                }

                // strip out "_is_association"
                if (!Gitana.isUndefined(this["_is_association"]))
                {
                    var _is_association = this["_is_association"];
                    delete this["_is_association"];
                    this.__is_association(_is_association);
                }
            },

            /**
             * Override to include:
             *
             *   __qname
             *   __type
             *   __features
             *   __stats
             *   __is_association
             *
             * @param otherObject
             */
            chainCopyState: function(otherObject)
            {
                this.base(otherObject);

                if (otherObject.__qname) {
                    this.__qname(otherObject.__qname());
                }
                if (otherObject.__type) {
                    this.__type(otherObject.__type());
                }
                if (otherObject.__features) {
                    this.__features(otherObject.__features());
                }
                if (otherObject.__stats) {
                    this.__stats(otherObject.__stats());
                }
                if (otherObject.__is_association) {
                    this.__is_association(otherObject.__is_association());
                }
            },

            /**
             * Hands back the stats.
             */
            stats: function()
            {
                return this.__stats();
            },

            ///////////////////////////////////////////////////////////////////////////////////////////////////////////
            //
            // FEATURES
            //
            ///////////////////////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Hands back a list of the feature ids that this node has.
             *
             * @public
             *
             * @param [Function] callback optional callback
             *
             * @returns {Array} An array of strings that are the ids of the features.
             */
            getFeatureIds: function(callback)
            {
                var self = this;

                var f = function()
                {
                    var featureIds = [];
                    for (var featureId in this.__features()) {
                        featureIds[featureIds.length] = featureId;
                    }

                    return featureIds;
                };

                if (callback)
                {

                    return this.then(function() {
                        callback.call(this, f.call(self));
                    });
                }

                return f.call(self);
            },

            /**
             * Gets the configuration for a given feature.
             *
             * @public
             *
             * @param {String} featureId the id of the feature
             * @param [Function] callback optional callback
             *
             * @returns {Object} the JSON object configuration for the feature
             */
            getFeature: function(featureId, callback)
            {
                var self = this;

                if (callback)
                {
                    return this.then(function() {
                        callback.call(this, self.__features()[featureId]);
                    });
                }

                return self.__features()[featureId];
            },

            /**
             * Removes a feature from this node.
             *
             * @public
             *
             * @param {String} featureId the id of the feature
             */
            removeFeature: function(featureId)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/features/" + featureId;
                };

                return this.chainDelete(this, uriFunction).reload().then(function() {
                    self.loadFrom(this);
                });
            },

            /**
             * Adds a feature to this node.
             *
             * @public
             * @param {String} featureId the id of the feature
             * @param [Object] featureConfig the JSON object configuration for the feature
             */
            addFeature: function(featureId, featureConfig)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/features/" + featureId;
                };

                if (!featureConfig) {
                    featureConfig = {};
                }

                return this.chainPostEmpty(null, uriFunction, {}, featureConfig).reload().then(function() {
                    self.loadFrom(this);
                });
            },

            /**
             * Indicates whether this node has the given feature.
             *
             * @public
             *
             * @param {String} featureId the id of the feature
             * @param [Function] callback optional callback to receive result (for chaining)
             *
             * @returns {Boolean} whether this node has this feature
             */
            hasFeature: function(featureId, callback)
            {
                if (callback)
                {
                    return this.then(function() {

                        var hasFeature = !Gitana.isEmpty(this.__features()[featureId]);

                        callback.call(this, hasFeature);
                    });
                }

                return !Gitana.isEmpty(this.__features()[featureId]);
            },

            /**
             * Indicates whether the current object is an association.
             *
             * @public
             *
             * @returns {Boolean} whether this node is an association
             */
            isAssociation: function()
            {
                return this.__is_association();
            },

            /**
             * Indicates whether this node has the "f:container" feature
             *
             * @public
             *
             * @returns {Boolean} whether this node has the "f:container" feature
             */
            isContainer: function()
            {
                return this.hasFeature("f:container");
            },

            /**
             * Touches the node.  This allows the node to reindex and regenerate any renditions it may
             * have associated with it.
             *
             * @public
             *
             * @chained node (this)
             */
            touch: function()
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/touch";
                };

                return this.chainPost(null, uriFunction);
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // TYPE
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Gets the type QName for this node.
             *
             * @public
             *
             * @returns {String} the type qname of this node.
             */
            getTypeQName: function()
            {
                return this.__type();
            },

            /**
             * Changes the type QName for this node.
             *
             * @public
             * @param {String} typeQName the qname of the type to change to
             */
            changeTypeQName: function(typeQName)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/change_type?type=" + typeQName;
                };

                return this.chainPostEmpty(null, uriFunction).reload().then(function() {
                    self.loadFrom(this);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // QNAME
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Gets the QName for this node.
             *
             * @public
             *
             * @returns {String} the qname of this node.
             */
            getQName: function()
            {
                return this.__qname();
            },

            /**
             * Sets the QName of this node.
             *
             * @public
             * @param {String} typeQName the qname of the type to change to
             */
            changeQName: function(qname)
            {
                var self = this;

                var uriFunction = function()
                {
                    return self.getUri() + "/change_qname?qname=" + qname;
                };

                return this.chainPostEmpty(null, uriFunction).reload().then(function() {
                    self.loadFrom(this);
                });
            },

            //////////////////////////////////////////////////////////////////////////////////////////
            //
            // ATTACHMENTS
            //
            //////////////////////////////////////////////////////////////////////////////////////////

            /**
             * Lists the attachments of this node.
             *
             * If local is set to true, the attachments are drawn from precached values on the node.
             *
             * @chained attachment map
             *
             * @param local
             *
             * @public
             */
            listAttachments: Gitana.Methods.listAttachments(Gitana.NodeAttachmentMap),

            /**
             * Picks off a single attachment
             *
             * @chained attachment
             *
             * @param attachmentId (null for default)
             */
            attachment: function(attachmentId)
            {
                return this.listAttachments().select(attachmentId);
            },

            /**
             * Non-chained method for getting a download URI for this node's attachment.
             */
            attachmentDownloadUri: function(attachmentId)
            {
                return this.getDriver().baseURL + this.getUri() + "/attachments/" + attachmentId;
            },

            /**
             * Creates an attachment.
             *
             * When using this method from within the JS driver, it really only works for text-based content such
             * as JSON or text.
             *
             * @chained attachment
             *
             * @param attachmentId (use null or false for default attachment)
             * @param contentType
             * @param data
             * @param filename
             */
            attach: function(attachmentId, contentType, data, filename)
            {
                var paramsFunction = function(params) {
                    if (filename) { params["filename"] = filename; }
                };

                var delegate = Gitana.Methods.attach.call(this, Gitana.NodeAttachment, paramsFunction);
                return delegate.call(this, attachmentId, contentType, data);
            },

            /**
             * Generates a URI to a preview resource.
             */
            getPreviewUri: Gitana.Methods.getPreviewUri(),

            /**
             * Deletes an attachment.
             *
             * @param attachmentId
             */
            unattach: Gitana.Methods.unattach()

        });

    })(window);