Bucket.js

'use strict';

var DataMethods = require('./methods/BucketData.js');
var UserMethods = require('./methods/BucketUser.js');
var AlertMethods = require('./methods/BucketAlert.js');
var ActionMethods = require('./methods/BucketAction.js');

var Worker = require('./services/worker.js');
var Http = require('./utils/http.js');
var deep_extends = require('./utils/deep_extends.js');

/**
 * Bucket
 * @constructor
 * @memberof Keymetrics
 * @alias bucket
 *
 * @param {object}      opts                Options
 * @property {object}   _id          ID of currently selected bucket
 * @property {object}   current_raw  Raw data from current selected Bucket
 * @property {object}   servers      (Realtime) List of connected Bucket servers
 * @property {object}   apps         (Realtime) List of connected Bucket applications
 * @property {object}   apps_server  (Realtime) List of connected apps per server
 * @property {object}   mini_metrics (Realtime) List of metrics per app per server
 */

var Bucket = function (opts) {
  var self = this;
  if (!(this instanceof Bucket)) {
    return new Bucket(opts);
  }

  this._id = opts.bucket || null;
  this.bus = opts.bus;
  this.root_url = opts.root_url;
  this.current_raw = null;
  this.available = [];
  this.http = new Http();
  this.resetStateBucket();

  this.Data = new DataMethods(this);
  this.User = new UserMethods(this);
  this.Alert = new AlertMethods(this);
  this.Action = new ActionMethods(this);

  this.worker = new Worker(opts);

  this.bus.on('bucket:active', function (id) {
    self._id = id;
    self.URL = self.root_url + '/bucket/' + id;
  });

  this.bus.on('auth:ready', function (data) {
    // update Authorization header
    self.http.set('Authorization', 'Bearer ' + data.access_token);
  });

  this.bus.on('data:*:status', function (data) {
    deep_extends(self, data);
  });

  this.bus.on('data:*:process:exception', function (data) {
    if (self.exceptions_summary == null) return;

    data.forEach(function (excpt) {
      if (!self.exceptions_summary[excpt.process.server])
        self.exceptions_summary[excpt.process.server] = {};
      if (!self.exceptions_summary[excpt.process.server][excpt.process.name])
        self.exceptions_summary[excpt.process.server][excpt.process.name] = 0;
      self.exceptions_summary[excpt.process.server][excpt.process.name]++;
    });
  });
};

Bucket.prototype = {

  resetStateBucket: function () {
    var self = this;

    this.servers = {};
    this.current_raw = null;
    this.apps = {};
  },

  /**
   * Retrieve Bucket from public key
   *
   * @param  {string} public_id   Bucket public key
   * @param  {callback} cb        description
   */
  retrieve: function (public_id, cb) {
    var self = this;

    this.http
      .get(this.root_url + '/bucket')
      .end(function (err, res) {
        if (err)
          return cb(err);
        for (var i = 0; i < res.body.length; i++) {
          if (res.body[i].public_id === public_id) {
            self.current_raw = res.body[i];
            self._id = self.current_raw._id;
            self.URL = self.root_url + '/bucket/' + self.current_raw._id;
            return cb(null, self.current_raw);
          }
        };
        return cb(new Error("Failed to find bucket"), null);
      });
  },


  /**
   * Connect to bucket from public key
   *
   * @param  {string} public_id Public key
   */
  connect: function (public_id) {
    var self = this;

    this.retrieve(public_id, function (err, bucket) {
      if (err) return self.bus.emit('error:bucket', err);
      self.bus.emit("bucket:active", self.current_raw._id);
    });
  },

  /**
   * Retrieve Bucket from ID
   *
   * @param  {string} raw_id      Bucket ID
   * @param  {callback} cb        Callback
   */
  retrieveFromID: function (raw_id, cb) {
    var self = this;

    this.http
      .get(this.root_url + '/bucket/' + raw_id)
      .end(function (err, res) {
        if (err)
          return cb(err);
        self.current_raw = res.body;
        self._id = self.current_raw._id;
        self.URL = self.root_url + '/bucket/' + self.current_raw._id;
        return cb(null, res.body);
      });
  },

  /**
   * Connect to bucket from id
   *
   * @param  {string} raw_id Bucket Id
   */
  connectFromID: function (raw_id) {
    var self = this;
    this.retrieveFromID(raw_id, function (err, bucket) {
      if (err) return self.bus.emit('error:bucket', err);

      self.bus.emit("bucket:active", bucket._id);
    });
  },

  /**
   * Populate all bucket information
   *
   * @param  {function} cb Callback
   */
  init: function (cb) {
    var self = this;
    this.all(function (err, buckets) {
      if (err) throw new Error(err);
      self.available = buckets;
      if (cb) return cb(null, buckets);
    });
  },

  /**
   * Retrieve all bucket information
   *
   * @param  {function} cb Callback
   */
  all: function (cb) {
    this.http
      .get(this.root_url + '/bucket')
      .end(function (err, res) {
        return cb(err, res.body);
      });
  },

  /**
   * Find current user role
   *
   * @param  {function} cb Callback
   */
  fetchUserRole: function (cb) {
    this.http
      .get(this.URL + '/current_role')
      .end(function (err, res) {
        if (err)
          return cb(err);
        return cb(null, res.text);
      });
  },

  /**
   * Retrieve current bucket information
   *
   * @param  {function} cb Callback
   */
  get: function (cb) {
    var self = this;

    this.http
      .get(this.URL)
      .end(function (err, res) {
        if (err)
          return cb(err);
        self.current_raw = res.body;
        return cb(null, res.body);
      });
  },

  /**
   * Retrieve avalaible Keymetrics plans
   *
   * @param  {function} cb Callback
   */
  getPlans: function (cb) {
    if (typeof window != 'undefined' && window.WIDGET_MODE === true)
      return cb(null, {});
    this.http
      .get(this.root_url + '/misc/plans')
      .end(function (err, res) {
        return cb(err, res.body);
      });
  },

  getCurrentPlan: function () {
    if (!this.current_raw || !this.current_raw.credits) return null;

    return this.current_raw.credits.offer_type;
  },

  /**
   * Retrieve probe data history
   *
   * @param  {object} opts Options
   * @param  {object} opts.app_name Application name
   * @param  {object} opts.server_name Server name
   * @param  {object} opts.minutes Minutes
   * @param  {object} opts.interval interval
   * @param  {function} cb Callback
   */
  getProbesHistory: function (opts, cb) {
    var URL = this.URL + '/data/probes/histogram';

    if (opts.app_name) URL += '?app_name=' + opts.app_name;
    if (opts.server_name) URL += '&server_name=' + opts.server_name;
    if (opts.minutes) URL += '&minutes=' + opts.minutes;
    if (opts.interval) URL += '&interval=' + opts.interval;

    this.http
      .get(URL)
      .end(function (err, res) {
        cb(err, res.body);
      });
  },

  /**
   * Retrieve probe meta history
   *
   * @param  {object} opts Options
   * @param  {object} opts.app_name Application name
   * @param  {object} opts.server_name Server name
   * @param  {object} opts.minutes Minutes
   * @param  {function} cb Callback
   */
  getProbesMeta: function (opts, cb) {
    var URL = this.URL + '/data/probes?app_name=' + opts.app_name;

    if (opts.minutes) URL += '&minutes=' + opts.minutes;
    if (opts.server_name) URL += '&server_name=' + opts.server_name;

    this.http
      .get(URL)
      .end(function (err, res) {
        return cb(err, res.body);
      });

  },

  /**
   * Retrieve servers metadata
   *
   * @param  {function} cb Callback
   */
  getMetaServers: function (cb) {
    this.http
      .get(this.URL + '/meta_servers')
      .end(function (err, res) {
        return cb(err, res.body);
      });
  },

  /**
   * Save server metadata
   *
   * @param  {object} server  Server
   * @param  {function} cb    Callback
   */
  saveMetaServer: function (server, cb) {
    this.http
      .post(this.URL + '/server/update', server)
      .send(server)
      .end(function (err, res) {
        if (err)
          return cb(err);
        return cb(null, res.body);
      });
  },

  /**
   * Update server metadata
   *
   * @param  {object} server  Server
   * @param  {function} cb    Callback
   */
  update: function (data, cb) {
    this.http
      .put(this.URL)
      .send(data)
      .end(function (err, res) {
        if (err)
          return cb(err);
        return cb(null, res.body);
      });
  },

  //Restricted, official client only
  retrieveCoupon: function (coupon, cb) {
    this.http
      .post(this.root_url + '/misc/stripe/retrieveCoupon', { coupon: coupon })
      .end(function (err, response) {
        return cb(err, response);
      });
  },

  claimTrial: function (bucket_id, cb) {
    if (typeof (bucket_id) == 'function') {
      cb = bucket_id;
      bucket_id = this.current_raw._id;
    }

    http
      .put(this.URL + '/start_trial')
      .end(function (err, response) {
        return cb(err, response);
      });
  },


  getSubscription: function (cb) {
    http.get(this.URL + '/subscription')
      .end(function (err, res) {
        return cb(err, res);
      });
  },

  refreshSubscription: function () {
    var self = this;

    this.getSubscription(function (err, subscription) {
      if (err) return console.error(err.message || err);
      if (subscription.trial_end && moment().diff(subscription.trial_end * 1000) < 0) {
        subscription.trial_ends_in = moment(subscription.trial_end * 1000).fromNow();
        subscription.is_trial = true;
      }
      else
        subscription.is_trial = false;
      self.current_subscription = subscription;
    });
  },

  delete: function (cb) {
    var self = this;
    var old_id = this.current_raw._id;

    http
      .delete(this.URL)
      .end(function (err, items) {
        if (err) return cb(err);

        self.unset();

        self.available.forEach(function (_buck, i) {
          if (_buck._id == old_id)
            self.available.splice(i, 1);
        });
        return cb(null, items);
      });
  },

  createClassic: function (data, cb) {
    this.http.post(this.root_url + '/bucket/create_classic')
      .send(data)
      .end(function (err, res) {
        return cb(err, res.body);
      });
  },

  upgrade: function (data, cb) {
    this.http.post(this.URL + '/upgrade', data)
      .end(function (err, response) {
        if (err)
          return cb(err);
        Bucket.set(response['bucket']);
        return cb(err, response);
      });
  },

  sendFeedback: function (feedback, cb) {
    this.http.put(this.URL + '/feedback', {
      feedback: feedback
    }).success(function (err, items) {
      return cb(err, items);
    });
  },

  //Formatting in front end
  sortByLoadAvg: function (new_status) {
    var index = this
      .sorted_servers
      .map(function (dt) {
        return dt[0];
      })
      .indexOf(new_status.server_name);

    if (index == -1) {
      this.sorted_servers.push([
        new_status.server_name,
        Math.floor(new_status.data.server.loadavg[0])
      ]);
    }
    else {
      this.sorted_servers[index] = [
        new_status.server_name,
        new_status.data.server.loadavg[0]
      ];
    }

    if (_refresh_sorting == (Bucket.sorted_servers.length * 5)) {
      _refresh_sorting = 0;
      this.sorted_servers.sort(function (a, b) {
        return a[1] < b[1] ? 1 : -1;
      });
      console.log(this.sorted_servers);
    }
    _refresh_sorting++;
  },

  appIsShowable: function (app_name) {
    if (this.selected_app_name) {
      if (this.selected_app_name == app_name)
        return true;
      else
        return false;
    }
    else
      return true;
  },

  isServerActive: function (server_name) {
    if (this.servers[server_name] && this.servers[server_name].data.active)
      return true;
    return false;
  },

  isAppActive: function (app_name) {
    if (!this.getAppObjectFromAppName(app_name))
      return false;
    return true;
  },

  findServerByName: function (name) {
    var self = this;
    var ret_server;

    Object.keys(this.servers).forEach(function (server_key) {
      if (self.servers[server_key].server_name == name)
        ret_server = self.servers[server_key];
    });

    return ret_server;
  },

  findMemoryForServer: function (name) {
    var server = this.findServerByName(name);

    return server.data.server.total_mem;
  },

  dumpModuleToConsole: function (app_name) {
    var k = Object.keys(this.apps[app_name].organization)[0];
    var j = Object.keys(this.apps[app_name].organization[k]);
    console.log(JSON.stringify(this.apps[app_name].organization[k][j].status));
  },

  findByName: function (name) {
    var buck;

    this.available.forEach(function (bucket) {
      if (bucket.name == name) {
        buck = bucket;
      }
    });
    return buck;
  },

  activeServers: function () {
    var self = this;
    var active_servers = {};

    Object.keys(this.servers).forEach(function (server_key) {
      if (self.servers[server_key].data &&
        self.servers[server_key].data.active) {
        active_servers[server_key] = self.servers[server_key];
      }
    });
    return active_servers;
  },

  activeProcesses: function () {
    var active_servers = this.activeServers();
    var active_processes = [];

    Object.keys(active_servers).forEach(function (server_key) {
      active_processes = active_processes.concat(active_servers[server_key].data.processes);
    });

    return active_processes;
  },

  getAppObjectFromAppName: function (app_name) {
    var servers = Object.keys(this.apps_server);
    var app = null;

    for (var i = 0; i < servers.length; i++) {
      app = this.apps_server[servers[i]][app_name];

      if (app) break;
    }

    return app;
  },

  serverIsShowable: function (server_name) {
    if (this.selected_server_name) {
      if (this.selected_server_name == server_name)
        return true;
      else
        return false;
    }
    else
      return true;
  },

  appOptionIsEnabled: function (app_name, option) {
    var ret = false;
    var self = this;

    Object
      .keys(this.apps[app_name].organization)
      .forEach(function (server) {
        var app = self.apps[app_name].organization[server];

        Object.keys(app).forEach(function (sub_app_key) {
          var sub_app = app[sub_app_key];

          if (sub_app.status.axm_options[option])
            ret = true;
        });
      });
    return ret;
  }
};

Bucket.prototype.set = function (bucket) {
  var self = this;

  this.resetStateBucket();
  this.current_raw = bucket;
  this.current_name = bucket.name;

  this.getPlans(function (err, plans) {
    self.current_plan = plans[self.current_raw.credits.offer_type];
  });
};

Bucket.prototype.unset = function () {
  this.resetStateBucket();
  this.current_raw = null;
  this.current_name = '';
  this.current_plan = {};
};

module.exports = Bucket;