# status.js

    const _ = require('underscore');
    const Promise = require('./promise');
    const AVRequest = require('./request')._request;
    const { getSessionToken } = require('./utils');

    module.exports = function(AV) {
      const getUser = (options = {}) => {
        const sessionToken = getSessionToken(options);
        if (sessionToken) {
          return AV.User._fetchUserBySessionToken(getSessionToken(options));
        }
        return AV.User.currentAsync();
      };

      const getUserPointer = options =>
        getUser(options).then(currUser =>
          AV.Object.createWithoutData('_User', currUser.id)._toPointer()
        );

      /**
       * Contains functions to deal with Status in LeanCloud.
       * @class
       */
      AV.Status = function(imageUrl, message) {
        this.data = {};
        this.inboxType = 'default';
        this.query = null;
        if (imageUrl && typeof imageUrl === 'object') {
          this.data = imageUrl;
        } else {
          if (imageUrl) {
            this.data.image = imageUrl;
          }
          if (message) {
            this.data.message = message;
          }
        }
        return this;
      };

      _.extend(
        AV.Status.prototype,
        /** @lends AV.Status.prototype */ {
          /**
           * Gets the value of an attribute in status data.
           * @param {String} attr The string name of an attribute.
           */
          get: function(attr) {
            return this.data[attr];
          },
          /**
           * Sets a hash of model attributes on the status data.
           * @param {String} key The key to set.
           * @param {} value The value to give it.
           */
          set: function(key, value) {
            this.data[key] = value;
            return this;
          },
          /**
           * Destroy this status,then it will not be avaiable in other user's inboxes.
           * @param {AuthOptions} options
           * @return {Promise} A promise that is fulfilled when the destroy
           *     completes.
           */
          destroy: function(options) {
            if (!this.id)
              return Promise.reject(new Error('The status id is not exists.'));
            var request = AVRequest('statuses', null, this.id, 'DELETE', options);
            return request;
          },
          /**
           * Cast the AV.Status object to an AV.Object pointer.
           * @return {AV.Object} A AV.Object pointer.
           */
          toObject: function() {
            if (!this.id) return null;
            return AV.Object.createWithoutData('_Status', this.id);
          },
          _getDataJSON: function() {
            var json = _.clone(this.data);
            return AV._encode(json);
          },
          /**
           * Send a status by a AV.Query object.
           * @since 0.3.0
           * @param {AuthOptions} options
           * @return {Promise} A promise that is fulfilled when the send
           *     completes.
           * @example
           *     // send a status to male users
           *     var status = new AVStatus('image url', 'a message');
           *     status.query = new AV.Query('_User');
           *     status.query.equalTo('gender', 'male');
           *     status.send().then(function(){
           *              //send status successfully.
           *      }, function(err){
           *             //an error threw.
           *             console.dir(err);
           *      });
           */
          send: function(options = {}) {
            if (!getSessionToken(options) && !AV.User.current()) {
              throw new Error('Please signin an user.');
            }
            if (!this.query) {
              return AV.Status.sendStatusToFollowers(this, options);
            }

            return getUserPointer(options)
              .then(currUser => {
                var query = this.query.toJSON();
                query.className = this.query.className;
                var data = {};
                data.query = query;
                this.data = this.data || {};
                this.data.source = this.data.source || currUser;
                data.data = this._getDataJSON();
                data.inboxType = this.inboxType || 'default';

                return AVRequest('statuses', null, null, 'POST', data, options);
              })
              .then(response => {
                this.id = response.objectId;
                this.createdAt = AV._parseDate(response.createdAt);
                return this;
              });
          },

          _finishFetch: function(serverData) {
            this.id = serverData.objectId;
            this.createdAt = AV._parseDate(serverData.createdAt);
            this.updatedAt = AV._parseDate(serverData.updatedAt);
            this.messageId = serverData.messageId;
            delete serverData.messageId;
            delete serverData.objectId;
            delete serverData.createdAt;
            delete serverData.updatedAt;
            this.data = AV._decode(serverData);
          },
        }
      );

      /**
       * Send a status to current signined user's followers.
       * @since 0.3.0
       * @param {AV.Status} status  A status object to be send to followers.
       * @param {AuthOptions} options
       * @return {Promise} A promise that is fulfilled when the send
       *     completes.
       * @example
       *     var status = new AVStatus('image url', 'a message');
       *     AV.Status.sendStatusToFollowers(status).then(function(){
       *              //send status successfully.
       *      }, function(err){
       *             //an error threw.
       *             console.dir(err);
       *      });
       */
      AV.Status.sendStatusToFollowers = function(status, options = {}) {
        if (!getSessionToken(options) && !AV.User.current()) {
          throw new Error('Please signin an user.');
        }
        return getUserPointer(options).then(currUser => {
          var query = {};
          query.className = '_Follower';
          query.keys = 'follower';
          query.where = { user: currUser };
          var data = {};
          data.query = query;
          status.data = status.data || {};
          status.data.source = status.data.source || currUser;
          data.data = status._getDataJSON();
          data.inboxType = status.inboxType || 'default';

          var request = AVRequest('statuses', null, null, 'POST', data, options);
          return request.then(function(response) {
            status.id = response.objectId;
            status.createdAt = AV._parseDate(response.createdAt);
            return status;
          });
        });
      };

      /**
       * <p>Send  a status from current signined user to other user's private status inbox.</p>
       * @since 0.3.0
       * @param {AV.Status} status  A status object to be send to followers.
       * @param {String} target The target user or user's objectId.
       * @param {AuthOptions} options
       * @return {Promise} A promise that is fulfilled when the send
       *     completes.
       * @example
       *     // send a private status to user '52e84e47e4b0f8de283b079b'
       *     var status = new AVStatus('image url', 'a message');
       *     AV.Status.sendPrivateStatus(status, '52e84e47e4b0f8de283b079b').then(function(){
       *              //send status successfully.
       *      }, function(err){
       *             //an error threw.
       *             console.dir(err);
       *      });
       */
      AV.Status.sendPrivateStatus = function(status, target, options = {}) {
        if (!getSessionToken(options) && !AV.User.current()) {
          throw new Error('Please signin an user.');
        }
        if (!target) {
          throw new Error('Invalid target user.');
        }
        var userObjectId = _.isString(target) ? target : target.id;
        if (!userObjectId) {
          throw new Error('Invalid target user.');
        }
        return getUserPointer(options).then(currUser => {
          var query = {};
          query.className = '_User';
          query.where = { objectId: userObjectId };
          var data = {};
          data.query = query;
          status.data = status.data || {};
          status.data.source = status.data.source || currUser;
          data.data = status._getDataJSON();
          data.inboxType = 'private';
          status.inboxType = 'private';

          var request = AVRequest('statuses', null, null, 'POST', data, options);
          return request.then(function(response) {
            status.id = response.objectId;
            status.createdAt = AV._parseDate(response.createdAt);
            return status;
          });
        });
      };

      /**
       * Count unread statuses in someone's inbox.
       * @since 0.3.0
       * @param {AV.User} owner The status owner.
       * @param {String} inboxType The inbox type, 'default' by default.
       * @param {AuthOptions} options
       * @return {Promise} A promise that is fulfilled when the count
       *     completes.
       * @example
       *  AV.Status.countUnreadStatuses(AV.User.current()).then(function(response){
       *    console.log(response.unread); //unread statuses number.
       *    console.log(response.total);  //total statuses number.
       *  });
       */
      AV.Status.countUnreadStatuses = function(
        owner,
        inboxType = 'default',
        options = {}
      ) {
        if (!_.isString(inboxType)) options = inboxType;
        if (!getSessionToken(options) && owner == null && !AV.User.current()) {
          throw new Error('Please signin an user or pass the owner objectId.');
        }
        return Promise.resolve(owner || getUser(options)).then(owner => {
          var params = {};
          params.inboxType = AV._encode(inboxType);
          params.owner = AV._encode(owner);
          return AVRequest(
            'subscribe/statuses/count',
            null,
            null,
            'GET',
            params,
            options
          );
        });
      };

      /**
       * reset unread statuses count in someone's inbox.
       * @since 2.1.0
       * @param {AV.User} owner The status owner.
       * @param {String} inboxType The inbox type, 'default' by default.
       * @param {AuthOptions} options
       * @return {Promise} A promise that is fulfilled when the reset
       *     completes.
       * @example
       *  AV.Status.resetUnreadCount(AV.User.current()).then(function(response){
       *    console.log(response.unread); //unread statuses number.
       *    console.log(response.total);  //total statuses number.
       *  });
       */
      AV.Status.resetUnreadCount = function(
        owner,
        inboxType = 'default',
        options = {}
      ) {
        if (!_.isString(inboxType)) options = inboxType;
        if (!getSessionToken(options) && owner == null && !AV.User.current()) {
          throw new Error('Please signin an user or pass the owner objectId.');
        }
        return Promise.resolve(owner || getUser(options)).then(owner => {
          var params = {};
          params.inboxType = AV._encode(inboxType);
          params.owner = AV._encode(owner);
          return AVRequest(
            'subscribe/statuses/resetUnreadCount',
            null,
            null,
            'POST',
            params,
            options
          );
        });
      };

      /**
       * Create a status query to find someone's published statuses.
       * @since 0.3.0
       * @param {AV.User} source The status source, typically the publisher.
       * @return {AV.Query} The query object for status.
       * @example
       *   //Find current user's published statuses.
       *   var query = AV.Status.statusQuery(AV.User.current());
       *   query.find().then(function(statuses){
       *      //process statuses
       *   });
       */
      AV.Status.statusQuery = function(source) {
        var query = new AV.Query('_Status');
        if (source) {
          query.equalTo('source', source);
        }
        return query;
      };

      /**
       * <p>AV.InboxQuery defines a query that is used to fetch somebody's inbox statuses.</p>
       * @class
       */
      AV.InboxQuery = AV.Query._extend(
        /** @lends AV.InboxQuery.prototype */ {
          _objectClass: AV.Status,
          _sinceId: 0,
          _maxId: 0,
          _inboxType: 'default',
          _owner: null,
          _newObject: function() {
            return new AV.Status();
          },
          _createRequest: function(params, options) {
            return AV.InboxQuery.__super__._createRequest.call(
              this,
              params,
              options,
              '/subscribe/statuses'
            );
          },

          /**
           * Sets the messageId of results to skip before returning any results.
           * This is useful for pagination.
           * Default is zero.
           * @param {Number} n the mesage id.
           * @return {AV.InboxQuery} Returns the query, so you can chain this call.
           */
          sinceId: function(id) {
            this._sinceId = id;
            return this;
          },
          /**
           * Sets the maximal messageId of results。
           * This is useful for pagination.
           * Default is zero that is no limition.
           * @param {Number} n the mesage id.
           * @return {AV.InboxQuery} Returns the query, so you can chain this call.
           */
          maxId: function(id) {
            this._maxId = id;
            return this;
          },
          /**
           * Sets the owner of the querying inbox.
           * @param {AV.User} owner The inbox owner.
           * @return {AV.InboxQuery} Returns the query, so you can chain this call.
           */
          owner: function(owner) {
            this._owner = owner;
            return this;
          },
          /**
           * Sets the querying inbox type.default is 'default'.
           * @param {Object} type The inbox type.
           * @return {AV.InboxQuery} Returns the query, so you can chain this call.
           */
          inboxType: function(type) {
            this._inboxType = type;
            return this;
          },
          toJSON: function() {
            var params = AV.InboxQuery.__super__.toJSON.call(this);
            params.owner = AV._encode(this._owner);
            params.inboxType = AV._encode(this._inboxType);
            params.sinceId = AV._encode(this._sinceId);
            params.maxId = AV._encode(this._maxId);
            return params;
          },
        }
      );

      /**
       * Create a inbox status query to find someone's inbox statuses.
       * @since 0.3.0
       * @param {AV.User} owner The inbox's owner
       * @param {String} inboxType The inbox type,'default' by default.
       * @return {AV.InboxQuery} The inbox query object.
       * @see AV.InboxQuery
       * @example
       *   //Find current user's default inbox statuses.
       *   var query = AV.Status.inboxQuery(AV.User.current());
       *   //find the statuses after the last message id
       *   query.sinceId(lastMessageId);
       *   query.find().then(function(statuses){
       *      //process statuses
       *   });
       */
      AV.Status.inboxQuery = function(owner, inboxType) {
        var query = new AV.InboxQuery(AV.Status);
        if (owner) {
          query._owner = owner;
        }
        if (inboxType) {
          query._inboxType = inboxType;
        }
        return query;
      };
    };