# Source: data/converter/raw-property-value-to-object-converter.js

    var Converter = require("core/converter/converter").Converter,
        Criteria = require("core/criteria").Criteria,
        DataQuery = require("data/model/data-query").DataQuery,
        ObjectDescriptorReference = require("core/meta/object-descriptor-reference").ObjectDescriptorReference,
        Promise = require("core/promise").Promise,
        Scope = require("frb/scope"),
        parse = require("frb/parse");

    /**
     * @class RawPropertyValueToObjectConverter
     * @classdesc Converts a property value of raw data to the referenced object.
     * @extends Converter
     */
    exports.RawPropertyValueToObjectConverter = Converter.specialize( /** @lends RawPropertyValueToObjectConverter# */ {

        /*********************************************************************
         * Serialization
         */

        serializeSelf: {
            value: function (serializer) {

                serializer.setProperty("convertExpression", this.convertExpression);

                serializer.setProperty("foreignDescriptor", this._foreignDescriptorReference);

                serializer.setProperty("revertExpression", this.revertExpression);

                serializer.setProperty("root", this.owner);

                serializer.setProperty("serviceIdentifier", this.serviceIdentifier);
                serializer.setProperty("service", this.service);

            }
        },

        deserializeSelf: {
            value: function (deserializer) {
                var value = deserializer.getProperty("convertExpression");
                if (value) {
                    this.convertExpression = value;
                }

                value = deserializer.getProperty("revertExpression");
                if (value) {
                    this.revertExpression = value;
                }

                value = deserializer.getProperty("foreignDescriptor");
                if (value) {
                    this._foreignDescriptorReference = value;
                }

                value = deserializer.getProperty("service");
                if (value) {
                    this.service = value;
                }

                value = deserializer.getObjectByLabel("root");
                if (value) {
                    this.owner = value;
                }

                value = deserializer.getProperty("serviceIdentifier");
                if (value) {
                    this.serviceIdentifier = value;
                }

                deserializer.deserializeUnit("bindings");
            }
        },

        /*********************************************************************
         * Initialization
         */

        /**
         * @param {string} convertExpression the expression to be used for building a criteria to obtain the object corresponding to the value to convert.
         * @return itself
         */
        initWithConvertExpression: {
            value: function (convertExpression) {
                this.convertExpression = convertExpression;
                return this;
            }
        },

        /*********************************************************************
         * Properties
         */

        _convertExpression: {
            value: null
        },

        /**
         * The expression used to convert a raw value into a modeled one, for example a foreign property value into the objet it represents.
         * @type {string}
         * */
        convertExpression: {
            get: function() {
                return this._convertExpression;
            },
            set: function(value) {
                if(value !== this._convertExpression) {
                    this._convertExpression = value;
                    this._convertSyntax = undefined;
                }
            }
        },

        _convertSyntax: {
            value: undefined
        },

        /**
         * Object created by parsing .convertExpression using frb/grammar.js that will
         * be used to initialize the convert query criteria
         * @type {Object}
         * */

        convertSyntax: {
            get: function() {
                return this._convertSyntax || (this._convertSyntax = parse(this.convertExpression));
            }
        },

        _revertExpression: {
            value: null
        },

        /**
         * The expression used to revert the modeled value into a raw one. For example,
         * reverting an object into it's primary key.
         * @type {string}
         * */
        revertExpression: {
            get: function() {
                return this._revertExpression;
            },
            set: function(value) {
                if(value !== this._revertExpression) {
                    this._revertExpression = value;
                    this._revertSyntax = undefined;
                }
            }
        },

        _revertSyntax: {
            value: undefined
        },

        /**
         * Object created by parsing .revertExpression using frb/grammar.js that will
         * be used to revert the modeled value into a raw one
         * @type {Object}
         * */
        revertSyntax: {
            get: function() {
                return this._revertSyntax || (this._revertSyntax = parse(this.revertExpression));
            }
        },

        /**
         * The descriptor of the destination object. If one is not provided,
         * .objectDescriptor will be used. If .objectDescriptor is not provided,
         * the value descriptor of the property descriptor that defines the
         * relationship will be used.
         * @type {?ObjectDescriptorReference}
         * */
        foreignDescriptor: {
            serializable: false,
            get: function () {
                return this._foreignDescriptorReference && this._foreignDescriptorReference.promise(require);
            },
            set: function (descriptor) {
                this._foreignDescriptorReference = new ObjectDescriptorReference().initWithValue(descriptor);
            }
        },

        /**
         * The descriptor of the source object. It will be used only if it is provided and
         * .foreignDescriptor is not provided.
         * @type {?ObjectDescriptorReference}
         **/
        objectDescriptor: {
            get: function () {
                return  this._objectDescriptor                      ?   Promise.resolve(this._objectDescriptor) :
                        this.owner && this.owner.objectDescriptor   ?   Promise.resolve(this.owner.objectDescriptor) :
                        this.owner && this.owner instanceof Promise ?   this._objectDescriptorReference :
                                                                        undefined;
            },
            set: function (value) {
                this._objectDescriptor = value;
            }
        },

        _objectDescriptorReference: {
            get: function () {
                var self = this;
                return this.owner.then(function (object) {
                    var objectDescriptor = object.objectDescriptor;
                    self.objectDescriptor = objectDescriptor;
                    return objectDescriptor;
                });
            }
        },

        /**
         * The descriptor for which to perform the fetch.
         * This returns foreignDescriptor, if it exists, and otherwise
         * returns objectDescriptor.
         * @type {?ObjectDescriptorReference}
         **/
        _descriptorToFetch: {
            get: function () {
                if (!this.__descriptorToFetch) {
                    var self = this;
                    this.__descriptorToFetch = this.foreignDescriptor ? this.foreignDescriptor.then(function (descriptor) {
                        return descriptor || self.objectDescriptor;
                    }) : Promise.resolve(this.objectDescriptor);
                }
                return this.__descriptorToFetch;
            }
        },

        owner: {
            get: function () {
                return this._owner ? this._owner.then ? this._owner : Promise.resolve(this._owner) : undefined;
            },
            set: function (value) {
                this._owner = value;
            }
        },

        __scope: {
            value: null
        },

        /**
         * Scope with which convert and revert expressions are evaluated.
         * @type {?Scope}
         **/
        scope: {
            get: function() {
                return this.__scope || (this.__scope = new Scope(this));
            }
        },

        /**
         * The service to use to make requests.
         */
        service: {
            get: function () {
                return  this._service ? this._service :
                        this.owner    ? this.owner.then(function (object) { return object.service; }) :
                                                           undefined;
            },
            set: function (value) {
                this._service = !value || value.then ? value : Promise.resolve(value);
            }
        },

        /**
         * Identifier of the child of .service that the query should be routed to
         */
        serviceIdentifier: {
            value: undefined
        },

        /*********************************************************************
         * Public API
         */

        /**
         * Converts the fault for the relationship to an actual object that has an ObjectDescriptor.
         * @function
         * @param {Property} v The value to format.
         * @returns {Promise} A promise for the referenced object.  The promise is
         * fulfilled after the object is successfully fetched.
         */
        convert: {
            value: function (v) {
                var self = this,
                    criteria = new Criteria().initWithSyntax(self.convertSyntax, v),
                    query;

                return this._descriptorToFetch.then(function (typeToFetch) {
                    var type = [typeToFetch.module.id, typeToFetch.name].join("/");

                    if (self.serviceIdentifier) {
                        criteria.parameters.serviceIdentifier = self.serviceIdentifier;
                    }

                    query = DataQuery.withTypeAndCriteria(type, criteria);

                    return self.service ? self.service.then(function (service) {
                        return service.rootService.fetchData(query);
                    }) : null;
                });
            }
        },

        /**
         * Reverts the relationship back to raw data.
         * @function
         * @param {Scope} v The value to revert.
         * @returns {string} v
         */
        revert: {
            value: function (v) {
                if (v) {
                    if (!this._revertSyntax) {
                        return Promise.resolve(v);
                    } else {
                        var scope = this.scope;
                        //Parameter is what is accessed as $ in expressions
                        scope.parameters = v;
                        return Promise.resolve(this._revertSyntax(scope));
                    }

                }
                return Promise.resolve(undefined);
            }
        }

    });