# Source: core/promise-controller.js

    var Montage = require("./core").Montage;
    var Promise = require("./promise").Promise;

    /**
     * A promise does not have bindable properties. This controller tracks the
     * state of a promise with bindable properties so you can create a substitution
     * that binds the viewed component with the state of the promise.
     *
     * -    "pending" with "progress"
     *          when in the pending state, bind the substituted component to the
     *          progress value, which is null when indeterminate or a 0-1 value
     *          when determinate. Most promises are indeterminate due to the
     *          complexities of composing progress information into meaningful
     *          data.
     * -    "fulfilled" with "value"
     *          when in the fulfilled state, bind the substituted component to the
     *          value.
     * -    "rejected" with "error"
     *          when in the rejected state, bind the substituted component to the
     *          given error, if you can surface that error to the user.
     * -    "state" (one of "pending", "fulfilled", or "rejected")
     *          suitable for substitutions that will always render an indeterminate
     *          progress in the "pending" state.
     * -    "progressState" (one of "determinate", "indeterminate", "fuilfilled", "rejected")
     *          suitable for substitutions that provide alternative view for
     *          determinate and indeterminate progress.
     *
     * @class PromiseController
     * @classdesc Provides bindable properties for the state of a promise
     */
    exports.PromiseController = Montage.specialize( {

        constructor: {
            value: function PromiseController() {
                this.reset = null;
                this.addOwnPropertyChangeListener("promise", this);
                this.promise = null;
                this.defineBindings({
                    "state == 'pending'": {"<-": "pending"},
                    "state == 'fulfilled'": {"<-": "fulfilled"},
                    "state == 'rejected'": {"<-": "rejected"},
                    "progressState == 'determinate'": {"<-": "pending && determinate"},
                    "progressState == 'indeterminate'": {"<-": "pending && determinate"},
                    "progressState == 'fulfilled'": {"<-": "fulfilled"},
                    "progressState == 'rejected'": {"<-": "rejected"}
                });
            }
        },

        /**
         * The promise whose state is interesting.
         * @type {Promise}
         */
        promise: {value: null},

        /**
         * One of "pending", "fulfilled", "rejected".
         * @type {string}
         */
        state: {value: null},

        /**
         * One of "determinate", "indeterminate", "fulfilled", "rejected".
         * @type {string}
         */
        progressState: {value: null},

        /**
         * Whether `promise` is in a pending state, in progress.
         * @type {boolean}
         */
        pending: {value: null},

        /**
         * Whether `promise` is in a fulfilled state, with a `value`.
         * @type {boolean}
         */
        fulfilled: {value: null},

        /**
         * Whether `promise` is in a rejected state, with a `error`.
         * @type {boolean}
         */
        rejected: {value: null},

        /**
         * Whether `promise` is in a determinate progress sate, meaning that it has
         * provided a `progress` ratio with its most recent progress event.
         * @type {boolean}
         */
        determinate: {value: null},

        /**
         * The last known progress value emitted by the promise.
         * @type {string}
         */
        progress: {value: null},

        /**
         * The fulfillment value of the promise, if any.
         * @type {object}
         */
        value: {value: null},

        /**
         * The error from the promise, if it was rejected.
         * @type {?Error}
         */
        error: {value: null},

        handlePromiseChange: {
            value: function (promise) {
                var self = this;
                promise = Promise.resolve(promise); // coerce

                // When a promise controller changes hands, the previous "then"
                // call might send updates. Resetting the previous promise prevents
                // those updates from being applied to the controller.
                if (self.reset) {
                    self.reset();
                }
                var reset = false;
                self.reset = function () {
                    reset = true;
                };

                // these get set in the context of constructor, in order, helping
                // toward the goal that all promise controllers have the same
                // hidden class by the end of this sequence
                self.value = null;
                self.error = null;
                self.progress = 0;
                self.determinate = false;
                self.pending = true;
                self.fulfilled = false;
                self.rejected = false;

                promise.then(
                    function (value) {
                        if (reset) {
                            return;
                        }
                        self.fulfilled = true;
                        self.value = value;
                        self.progress = 1;
                    },
                    function (error) {
                        if (reset) {
                            return;
                        }
                        self.rejected = true;
                        self.error = error;
                    },
                    function (progress) {
                        if (reset) {
                            return;
                        }
                        self.progress = progress;
                        self.determinate = typeof progress === "number";
                    }
                );

            }
        }

    }, /** @lends PromiseController. */ {

        objectDescriptorModuleId:require("./core")._objectDescriptorModuleIdDescriptor,

        objectDescriptor:require("./core")._objectDescriptorDescriptor,

        blueprintModuleId:require("./core")._blueprintModuleIdDescriptor,

        blueprint:require("./core")._blueprintDescriptor

    });