<commit_before>var Cell = require('./Cell');

function PlayerCell() {
    Cell.apply(this, Array.prototype.slice.call(arguments));

    this.cellType = 0;
    this.recombineTicks = 0; // Ticks passed after the cell has split
    this.shouldRecombine = false; // Should the cell combine. If true, collision with own cells happens
}

module.exports = PlayerCell;
PlayerCell.prototype = new Cell();

// Main Functions

PlayerCell.prototype.visibleCheck = function(box, centerPos) {
    // Use old fashioned checking method if cell is small
    if (this.mass < 100) {
        return this.collisionCheck(box.bottomY, box.topY, box.rightX, box.leftX);
    }

    // Checks if this cell is visible to the player
    var cellSize = this.getSize();
    var lenX = cellSize + box.width >> 0; // Width of cell + width of the box (Int)
    var lenY = cellSize + box.height >> 0; // Height of cell + height of the box (Int)

    return (this.abs(this.position.x - centerPos.x) < lenX) && (this.abs(this.position.y - centerPos.y) < lenY);
};

PlayerCell.prototype.simpleCollide = function(check, d) {
    // Simple collision check
    var len = 2 * d >> 0; // Width of cell + width of the box (Int)

    return (this.abs(this.position.x - check.x) < len) &&
        (this.abs(this.position.y - check.y) < len);
};

PlayerCell.prototype.calcMergeTime = function(base) {
    // The recombine mechanic has been completely revamped.
    // As time passes on, recombineTicks gets larger, instead of getting smaller.
    // When the owner has only 1 cell, ticks and shouldRecombine will be reset by gameserver.
    var r = false;
    if (base == 0 || this.owner.mergeOverride) {
        // Instant recombine in config or merge command was triggered for this client
        r = true;
    } else {
        var rec = Math.floor(base + ((0.02 * this.mass))); // base seconds + 0.02% of mass
        if (this.recombineTicks > rec) r = true; // Can combine with other cells
    }
    this.shouldRecombine = r;
};

// Movement

PlayerCell.prototype.calcMove = function(x2, y2, gameServer, moveCell) {
    if (moveCell) {
        // Get angle of mouse
        var deltaY = y2 - this.position.y;
        var deltaX = x2 - this.position.x;
        var angle = Math.atan2(deltaX, deltaY);

        if (isNaN(angle)) {
            return;
        }

        var dist = this.getDist(this.position.x, this.position.y, x2, y2);
        var speed = Math.min(this.getSpeed(), dist);

        // Move cell
        this.position.x += Math.sin(angle) * speed;
        this.position.y += Math.cos(angle) * speed;
    }

    this.collision(gameServer);
};

PlayerCell.prototype.collision = function(gameServer) {
    var config = gameServer.config;
    var r = this.getSize(); // Cell radius

    var x1 = this.position.x;
    var y1 = this.position.y;

    var collidedCells = 0; // Amount of cells collided this tick

    // Collision check for other cells
    for (var i = 0; i < this.owner.cells.length; i++) {
        var cell = this.owner.cells[i];

        if (this.nodeId == cell.nodeId) {
            continue;
        }

        if ((!cell.shouldRecombine) || (!this.shouldRecombine)) {
            // Cannot recombine - Collision with your own cells
            var collisionDist = cell.getSize() + r; // Minimum distance between the 2 cells
            dist = this.getDist(x1, y1, cell.position.x, cell.position.y); // Distance between these two cells

            // Calculations
            if (dist < collisionDist) { // Collided
                // The moving cell pushes the colliding cell
                // Strength however depends on cell1 speed divided by cell2 speed
                collidedCells++;
                if (this.collisionRestoreTicks > 0 || cell.collisionRestoreTicks > 0) continue;

                var c1Speed = this.getSpeed();
                var c2Speed = cell.getSpeed();
                var Tmult = (c1Speed / c2Speed) / 2;

                var dY = y1 - cell.position.y;
                var dX = x1 - cell.position.x;
                var newAngle = Math.atan2(dX, dY);

                var Tmove = (collisionDist - dist) * Tmult;

                x1 += (Tmove * Math.sin(newAngle)) >> 0;
                y1 += (Tmove * Math.cos(newAngle)) >> 0;

                // Also move the other cell
                dist = this.getDist(x1, y1, cell.position.x, cell.position.y); // Recalculate distance
                var Cmult = (c2Speed / c1Speed) / 2;
                var Cmove = (collisionDist - dist) * Cmult;

                cell.position.x -= (Cmove * Math.sin(newAngle)) >> 0;
                cell.position.y -= (Cmove * Math.cos(newAngle)) >> 0;
            }
        }
    }

    gameServer.gameMode.onCellMove(x1, y1, this);

    if (collidedCells == 0) this.collisionRestoreTicks = 0; // Automate process of collision restoration as no cells are colliding

    // Check to ensure we're not passing the world border (shouldn't get closer than a quarter of the cell's diameter)
    if (x1 < config.borderLeft + r / 2) {
        x1 = config.borderLeft + r / 2;
    }
    if (x1 > config.borderRight - r / 2) {
        x1 = config.borderRight - r / 2;
    }
    if (y1 < config.borderTop + r / 2) {
        y1 = config.borderTop + r / 2;
    }
    if (y1 > config.borderBottom - r / 2) {
        y1 = config.borderBottom - r / 2;
    }

    this.position.x = x1 >> 0;
    this.position.y = y1 >> 0;
}

// Override

PlayerCell.prototype.getEatingRange = function() {
    return this.getSize() / 3.14;
};

PlayerCell.prototype.onConsume = function(consumer, gameServer) {
    // Add an inefficiency for eating other players' cells
    var factor = ( consumer.owner === this.owner ? 1 : gameServer.config.playerMassAbsorbed );
    consumer.addMass(factor * this.mass);
};

PlayerCell.prototype.onAdd = function(gameServer) {
    // Add to special player node list
    gameServer.nodesPlayer.push(this);
    // Gamemode actions
    gameServer.gameMode.onCellAdd(this);
};

PlayerCell.prototype.onRemove = function(gameServer) {
    var index;
    // Remove from player cell list
    index = this.owner.cells.indexOf(this);
    if (index != -1) {
        this.owner.cells.splice(index, 1);
    }
    // Remove from special player controlled node list
    index = gameServer.nodesPlayer.indexOf(this);
    if (index != -1) {
        gameServer.nodesPlayer.splice(index, 1);
    }
    // Gamemode actions
    gameServer.gameMode.onCellRemove(this);
};

PlayerCell.prototype.moveDone = function(gameServer) {
    // Well, nothing.
};

// Lib

PlayerCell.prototype.abs = function(x) {
    return x < 0 ? -x : x;
};

PlayerCell.prototype.getDist = function(x1, y1, x2, y2) {
    var xs = x2 - x1;
    xs = xs * xs;

    var ys = y2 - y1;
    ys = ys * ys;

    return Math.sqrt(xs + ys);
};
<commit_msg>Another anti-bot measure<commit_after>var Cell = require('./Cell');

function PlayerCell() {
    Cell.apply(this, Array.prototype.slice.call(arguments));

    this.cellType = 0;
    this.recombineTicks = 0; // Ticks passed after the cell has split
    this.shouldRecombine = false; // Should the cell combine. If true, collision with own cells happens
}

module.exports = PlayerCell;
PlayerCell.prototype = new Cell();

// Main Functions

PlayerCell.prototype.visibleCheck = function(box, centerPos) {
    // Use old fashioned checking method if cell is small
    if (this.mass < 100) {
        return this.collisionCheck(box.bottomY, box.topY, box.rightX, box.leftX);
    }

    // Checks if this cell is visible to the player
    var cellSize = this.getSize();
    var lenX = cellSize + box.width >> 0; // Width of cell + width of the box (Int)
    var lenY = cellSize + box.height >> 0; // Height of cell + height of the box (Int)

    return (this.abs(this.position.x - centerPos.x) < lenX) && (this.abs(this.position.y - centerPos.y) < lenY);
};

PlayerCell.prototype.simpleCollide = function(check, d) {
    // Simple collision check
    var len = 2 * d >> 0; // Width of cell + width of the box (Int)

    return (this.abs(this.position.x - check.x) < len) &&
        (this.abs(this.position.y - check.y) < len);
};

PlayerCell.prototype.calcMergeTime = function(base) {
    // The recombine mechanic has been completely revamped.
    // As time passes on, recombineTicks gets larger, instead of getting smaller.
    // When the owner has only 1 cell, ticks and shouldRecombine will be reset by gameserver.
    var r = false;
    if (base == 0 || this.owner.mergeOverride) {
        // Instant recombine in config or merge command was triggered for this client
        r = true;
    } else {
        var rec = Math.floor(base + ((0.02 * this.mass))); // base seconds + 0.02% of mass
        if (this.recombineTicks > rec) r = true; // Can combine with other cells
    }
    this.shouldRecombine = r;
};

// Movement

PlayerCell.prototype.calcMove = function(x2, y2, gameServer, moveCell) {
    if (moveCell) {
        // Get angle of mouse
        var deltaY = y2 - this.position.y;
        var deltaX = x2 - this.position.x;
        var angle = Math.atan2(deltaX, deltaY);

        if (isNaN(angle)) {
            return;
        }

        var dist = this.getDist(this.position.x, this.position.y, x2, y2);
        var speed = Math.min(this.getSpeed(), dist);

        // Move cell
        this.position.x += Math.sin(angle) * speed;
        this.position.y += Math.cos(angle) * speed;
    }

    this.collision(gameServer);
};

PlayerCell.prototype.collision = function(gameServer) {
    var config = gameServer.config;
    var r = this.getSize(); // Cell radius

    var x1 = this.position.x;
    var y1 = this.position.y;

    var collidedCells = 0; // Amount of cells collided this tick

    // Collision check for other cells
    for (var i = 0; i < this.owner.cells.length; i++) {
        var cell = this.owner.cells[i];

        if (this.nodeId == cell.nodeId) {
            continue;
        }

        if ((!cell.shouldRecombine) || (!this.shouldRecombine)) {
            // Cannot recombine - Collision with your own cells
            var collisionDist = cell.getSize() + r; // Minimum distance between the 2 cells
            dist = this.getDist(x1, y1, cell.position.x, cell.position.y); // Distance between these two cells

            // Calculations
            if (dist < collisionDist) { // Collided
                // The moving cell pushes the colliding cell
                // Strength however depends on cell1 speed divided by cell2 speed
                collidedCells++;
                if (this.collisionRestoreTicks > 0 || cell.collisionRestoreTicks > 0) continue;

                var c1Speed = this.getSpeed();
                var c2Speed = cell.getSpeed();
                var Tmult = (c1Speed / c2Speed) / 2;

                var dY = y1 - cell.position.y;
                var dX = x1 - cell.position.x;
                var newAngle = Math.atan2(dX, dY);

                var Tmove = (collisionDist - dist) * Tmult;

                x1 += (Tmove * Math.sin(newAngle)) >> 0;
                y1 += (Tmove * Math.cos(newAngle)) >> 0;

                // Also move the other cell
                dist = this.getDist(x1, y1, cell.position.x, cell.position.y); // Recalculate distance
                var Cmult = (c2Speed / c1Speed) / 2;
                var Cmove = (collisionDist - dist) * Cmult;

                cell.position.x -= (Cmove * Math.sin(newAngle)) >> 0;
                cell.position.y -= (Cmove * Math.cos(newAngle)) >> 0;
            }
        }
    }

    gameServer.gameMode.onCellMove(x1, y1, this);

    if (collidedCells == 0) this.collisionRestoreTicks = 0; // Automate process of collision restoration as no cells are colliding

    // Check to ensure we're not passing the world border (shouldn't get closer than a quarter of the cell's diameter)
    if (x1 < config.borderLeft + r / 2) {
        x1 = config.borderLeft + r / 2;
    }
    if (x1 > config.borderRight - r / 2) {
        x1 = config.borderRight - r / 2;
    }
    if (y1 < config.borderTop + r / 2) {
        y1 = config.borderTop + r / 2;
    }
    if (y1 > config.borderBottom - r / 2) {
        y1 = config.borderBottom - r / 2;
    }

    this.position.x = x1 >> 0;
    this.position.y = y1 >> 0;
}

// Override

PlayerCell.prototype.getEatingRange = function() {
    return this.getSize() / 3.14;
};

PlayerCell.prototype.onConsume = function(consumer, gameServer) {
    // Add an inefficiency for eating other players' cells
    var factor = ( consumer.owner === this.owner ? 1 : gameServer.config.playerMassAbsorbed );
    factor = (consumer.mass <= 625 && this.mass <= 17) ? 0 : factor; // Anti-bot measure
    consumer.addMass(factor * this.mass);
};

PlayerCell.prototype.onAdd = function(gameServer) {
    // Add to special player node list
    gameServer.nodesPlayer.push(this);
    // Gamemode actions
    gameServer.gameMode.onCellAdd(this);
};

PlayerCell.prototype.onRemove = function(gameServer) {
    var index;
    // Remove from player cell list
    index = this.owner.cells.indexOf(this);
    if (index != -1) {
        this.owner.cells.splice(index, 1);
    }
    // Remove from special player controlled node list
    index = gameServer.nodesPlayer.indexOf(this);
    if (index != -1) {
        gameServer.nodesPlayer.splice(index, 1);
    }
    // Gamemode actions
    gameServer.gameMode.onCellRemove(this);
};

PlayerCell.prototype.moveDone = function(gameServer) {
    // Well, nothing.
};

// Lib

PlayerCell.prototype.abs = function(x) {
    return x < 0 ? -x : x;
};

PlayerCell.prototype.getDist = function(x1, y1, x2, y2) {
    var xs = x2 - x1;
    xs = xs * xs;

    var ys = y2 - y1;
    ys = ys * ys;

    return Math.sqrt(xs + ys);
};
<|endoftext|>


<commit_before>/**
 * Created by hannes on 27.10.14.
 */

var message = require('../message');
var StatusCode = require('../connector/Message').Message.StatusCode;

var Code;

exports.Code = Code = Object.inherit(/** @lends baqend.metamodel.Metamodel.prototype */ {

  /**
   * @type baqend.connector.Connector
   * @private
   */
  _connector: null,

  /**
   * @type Object
   * @private
   */
  _handler: null,

  /**
   * @type Object
   * @private
   */
  _code: null,

  /**
   * @type Boolean
   * @private
   */
  _isLoaded: false,

  /**
   * @param {baqend.connector.Connector} connector
   */
  initialize: function(connector) {
    this._handler = {};
    this._code = {};
    this._connector = connector;
  },

  /**
   * Converts the given funtion to a atring
   * @param {Function} fn The JavaScript function to serialize
   * @return {String} The serialized function
   */
  functionToString: function(fn) {
    if(!fn)
      return "";

    fn = fn.toString();
    return fn.slice(fn.indexOf("{") + 1, fn.lastIndexOf("}"));
  },

  /**
   * Converts the given atring to a function
   * @param {String} code The JavaScript function to deserialize
   * @return {Function} The deserialized function
   */
  stringToFunction: function(code) {
    return new Function('db', code);
  },

  /**
   * Sets the handler function for the given bucket and type
   *
   * @param {String} bucket
   * @param {String} codeType (insert, update, delete)
   * @param {(Function|String)} fn will be converted to a String
   */
  setHandler: function(bucket, codeType, fn) {
    this._handler[bucket] = this._handler[bucket] || {};
    this._handler[bucket][codeType] = Function.isInstance(fn)? fn: this.stringToFunction(fn);
  },

  /**
   * Returns the handler function for the given bucket and type
   *
   * @param {String} bucket
   * @param {String} codeType (insert, update, delete)
   * @returns {Function}
   */
  getHandler: function(bucket, codeType) {
    var bucketCode = this._handler[bucket] || {};
    var code = bucketCode[codeType];
    if(code) {
      return code;
    } else {
      return null;
    }
  },

  /**
   * Removes the function of the given bucket and handler type
   *
   * @param {String} bucket
   * @param {String} codeType
   */
  removeHandler: function(bucket, codeType) {
    var bucketCode = this._handler[bucket] || {};
    delete bucketCode[codeType];
    if (Object.keys(bucketCode).length == 0) {
      delete this._handler[bucket];
    }
  },

  /**
   * Sets the Baqend code function for the given bucket name
   *
   * @param {String} bucket Name of the Baqend code
   * @param {(Function|String)} fn Function of the Baqend code which will be converted to a String
   */
  setCode: function(bucket, fn) {
    this._code[bucket] = Function.isInstance(fn)? fn: this.stringToFunction(fn);
  },

  /**
   * Returns the Baqend code function for the given bucket name
   *
   * @param {String} bucket Name of the Baqend code
   * @returns {Function}
   */
  getCode: function(bucket) {
    var code = this._code[bucket];
    if(code) {
      return code;
    } else {
      return null;
    }
  },

  /**
   * Removes the Baqend code function for the given bucket name
   *
   * @param {String} bucket Name of the Baqend code
   */
  removeCode: function(bucket) {
    delete this._code[bucket];
  },

  /**
   * Loads the insert code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadInsert: function(type, token) {
    return this._loadHandler(type.name, 'insert', token);
  },

  /**
   * Saves the insert code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveInsert: function(type, code, token) {
    return this._saveHandler(type.name, 'insert', code, token);
  },

  /**
   * Deletes the insert code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteInsert: function(type, token) {
    return this._deleteHandler(type.name, 'insert', token);
  },

  /**
   * Loads the update code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadUpdate: function(type, token) {
    return this._loadHandler(type.name, 'update', token);
  },

  /**
   * Saves the update code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveUpdate: function(type, code, token) {
    return this._saveHandler(type.name, 'update', code, token);
  },

  /**
   * Deletes the update code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteUpdate: function(type, token) {
    return this._deleteHandler(type.name, 'update', token);
  },

  /**
   * Loads the delete code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadDelete: function(type, token) {
    return this._loadHandler(type.name, 'delete', token);
  },

  /**
   * Saves the delete code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveDelete: function(type, code, token) {
    return this._saveHandler(type.name, 'delete', code, token);
  },

  /**
   * Deletes the delete code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteDelete: function(type, token) {
    return this._deleteHandler(type.name, 'delete', token);
  },

  /**
   * Loads the validation code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadValidate: function(type, token) {
    return this._loadHandler(type.name, 'validate', token).then(function() {
      type.validationCode = this.getHandler(type.name, 'validate');
      return type.validationCode;
    }.bind(this));
  },

  /**
   * Saves the validate code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveValidate: function(type, code, token) {
    return this._saveHandler(type.name, 'validate', code, token).then(function() {
      type.validationCode = this._handler[type.name]['validate'];
      return type.validationCode;
    }.bind(this));
  },

  /**
   * Deletes the validate code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteValidate: function(type, token) {
    return this._deleteHandler(type.name, 'validate', token).then(function() {
      type.validationCode = null;
    });
  },

  /**
   * Saves Baqend code which will be identified by the given bucket
   *
   * @param {String} bucket Name of the Baqend code
   * @param {Function} fn Baqend code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveCode: function(bucket, fn, token) {
    var msg = new message.SetBaqendCode(bucket, this.functionToString(fn));
    msg.withAuthorizationToken(token);

    return this._connector.send(msg).then(function(code) {
      return this.stringToFunction(code.response.entity);
    }.bind(this));
  },

  /**
   * Loads Baqend code for the given bucket name
   *
   * @param {String} bucket to identify the Baqend code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadCode: function(bucket, token) {
    var msg = new message.GetBaqendCode(bucket);
    msg.withAuthorizationToken(token);

    return this._connector.send(msg).then(function(code) {
      return this.stringToFunction(code.response.entity);
    }.bind(this));
  },

  /**
   * Deletes Baqend code for the given bucket name
   *
   * @param {String} bucket to identify the Baqend code
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteCode: function(bucket, token) {
    var msg = new message.DeleteBaqendCode(bucket);
    msg.withAuthorizationToken(token);

    return this._connector.send(msg).then(function() {
      this.setCode(bucket, "");
    }.bind(this));
  },

  /**
   * Calls the baqend code, which is identified by the given bucket.
   * The obj parameter will be used as the "this" object.
   *
   * @param {String} bucket Name of the baqend code
   * @param {Object=} obj Input of the baqend code function
   * @param {String} token to authenticate the user
   * @param {baqend.EntityManager} entityManager is needed when the code will be called locally
   * @returns {Promise<Object>}
   */
  callCode: function(bucket, obj, token, entityManager) {
    var fn = this.getCode(bucket);
    if(fn) {
      return new Promise(function(resolve) {
        resolve(fn.call(obj, entityManager));
      });
    } else {
      var msg = new message.CallBaqendCode(bucket, obj);
      msg.withAuthorizationToken(token);
      return this._connector.send(msg).then(function(code) {
        return code.response.entity;
      });
    }
  },

  _saveHandler: function(bucket, codeType, fn, token) {
    return this._saveLoadHandler(bucket, codeType, fn, token);
  },

  _loadHandler: function(bucket, codeType, token) {
    return this._saveLoadHandler(bucket, codeType, null, token);
  },

  _saveLoadHandler: function(bucket, codeType, fn, token) {
    var msg = fn === null? new message.GetBaqendHandler(bucket, codeType): new message.SetBaqendHandler(bucket, codeType, this.functionToString(fn));
    msg.withAuthorizationToken(token);
    return this._connector.send(msg).then(function(code) {
      this.setHandler(bucket, codeType, code.response.entity);
    }.bind(this), function(e) {
      if (e.statusCode == StatusCode.OBJECT_NOT_FOUND) {
        return null;
      } else {
        throw e;
      }
    });
  },

  _deleteHandler: function(bucket, codeType, token) {
    var msg = new message.DeleteBaqendHandler(bucket, codeType);
    msg.withAuthorizationToken(token);
    return this._connector.send(msg).then(function() {
      this.removeHandler(bucket, codeType);
    }.bind(this));
  }
});
<commit_msg>load returns the loaded handler now remove trailing linebreaks in handler code<commit_after>/**
 * Created by hannes on 27.10.14.
 */

var message = require('../message');
var StatusCode = require('../connector/Message').Message.StatusCode;

var Code;

exports.Code = Code = Object.inherit(/** @lends baqend.metamodel.Metamodel.prototype */ {

  /**
   * @type baqend.connector.Connector
   * @private
   */
  _connector: null,

  /**
   * @type Object
   * @private
   */
  _handler: null,

  /**
   * @type Object
   * @private
   */
  _code: null,

  /**
   * @type Boolean
   * @private
   */
  _isLoaded: false,

  /**
   * @param {baqend.connector.Connector} connector
   */
  initialize: function(connector) {
    this._handler = {};
    this._code = {};
    this._connector = connector;
  },

  /**
   * Converts the given funtion to a atring
   * @param {Function} fn The JavaScript function to serialize
   * @return {String} The serialized function
   */
  functionToString: function(fn) {
    if(!fn)
      return "";

    var str = fn.toString();
    str = str.substring(str.indexOf("{") + 1, str.lastIndexOf("}"));
    if (str.charAt(0) == '\n')
      str = str.substring(1);

    if (str.charAt(str.length - 1) == '\n')
      str = str.substring(0, str.length - 1);

    return str;
  },

  /**
   * Converts the given atring to a function
   * @param {String} code The JavaScript function to deserialize
   * @return {Function} The deserialized function
   */
  stringToFunction: function(code) {
    return new Function('db', code);
  },

  /**
   * Sets the handler function for the given bucket and type
   *
   * @param {String} bucket
   * @param {String} codeType (insert, update, delete)
   * @param {(Function|String)} fn will be converted to a String
   */
  setHandler: function(bucket, codeType, fn) {
    this._handler[bucket] = this._handler[bucket] || {};
    this._handler[bucket][codeType] = Function.isInstance(fn)? fn: this.stringToFunction(fn);
  },

  /**
   * Returns the handler function for the given bucket and type
   *
   * @param {String} bucket
   * @param {String} codeType (insert, update, delete)
   * @returns {Function}
   */
  getHandler: function(bucket, codeType) {
    var bucketCode = this._handler[bucket] || {};
    var code = bucketCode[codeType];
    if(code) {
      return code;
    } else {
      return null;
    }
  },

  /**
   * Removes the function of the given bucket and handler type
   *
   * @param {String} bucket
   * @param {String} codeType
   */
  removeHandler: function(bucket, codeType) {
    var bucketCode = this._handler[bucket] || {};
    delete bucketCode[codeType];
    if (Object.keys(bucketCode).length == 0) {
      delete this._handler[bucket];
    }
  },

  /**
   * Sets the Baqend code function for the given bucket name
   *
   * @param {String} bucket Name of the Baqend code
   * @param {(Function|String)} fn Function of the Baqend code which will be converted to a String
   */
  setCode: function(bucket, fn) {
    this._code[bucket] = Function.isInstance(fn)? fn: this.stringToFunction(fn);
  },

  /**
   * Returns the Baqend code function for the given bucket name
   *
   * @param {String} bucket Name of the Baqend code
   * @returns {Function}
   */
  getCode: function(bucket) {
    var code = this._code[bucket];
    if(code) {
      return code;
    } else {
      return null;
    }
  },

  /**
   * Removes the Baqend code function for the given bucket name
   *
   * @param {String} bucket Name of the Baqend code
   */
  removeCode: function(bucket) {
    delete this._code[bucket];
  },

  /**
   * Loads the insert code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadInsert: function(type, token) {
    return this._loadHandler(type.name, 'insert', token);
  },

  /**
   * Saves the insert code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveInsert: function(type, code, token) {
    return this._saveHandler(type.name, 'insert', code, token);
  },

  /**
   * Deletes the insert code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteInsert: function(type, token) {
    return this._deleteHandler(type.name, 'insert', token);
  },

  /**
   * Loads the update code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadUpdate: function(type, token) {
    return this._loadHandler(type.name, 'update', token);
  },

  /**
   * Saves the update code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveUpdate: function(type, code, token) {
    return this._saveHandler(type.name, 'update', code, token);
  },

  /**
   * Deletes the update code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteUpdate: function(type, token) {
    return this._deleteHandler(type.name, 'update', token);
  },

  /**
   * Loads the delete code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadDelete: function(type, token) {
    return this._loadHandler(type.name, 'delete', token);
  },

  /**
   * Saves the delete code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveDelete: function(type, code, token) {
    return this._saveHandler(type.name, 'delete', code, token);
  },

  /**
   * Deletes the delete code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteDelete: function(type, token) {
    return this._deleteHandler(type.name, 'delete', token);
  },

  /**
   * Loads the validation code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadValidate: function(type, token) {
    return this._loadHandler(type.name, 'validate', token).then(function() {
      type.validationCode = this.getHandler(type.name, 'validate');
      return type.validationCode;
    }.bind(this));
  },

  /**
   * Saves the validate code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {Function} code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveValidate: function(type, code, token) {
    return this._saveHandler(type.name, 'validate', code, token).then(function() {
      type.validationCode = this._handler[type.name]['validate'];
      return type.validationCode;
    }.bind(this));
  },

  /**
   * Deletes the validate code for the given type
   *
   * @param {baqend.metamodel.ManagedType} type
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteValidate: function(type, token) {
    return this._deleteHandler(type.name, 'validate', token).then(function() {
      type.validationCode = null;
    });
  },

  /**
   * Saves Baqend code which will be identified by the given bucket
   *
   * @param {String} bucket Name of the Baqend code
   * @param {Function} fn Baqend code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  saveCode: function(bucket, fn, token) {
    var msg = new message.SetBaqendCode(bucket, this.functionToString(fn));
    msg.withAuthorizationToken(token);

    return this._connector.send(msg).then(function(code) {
      return this.stringToFunction(code.response.entity);
    }.bind(this));
  },

  /**
   * Loads Baqend code for the given bucket name
   *
   * @param {String} bucket to identify the Baqend code
   * @param {String} token to authenticate the user
   * @returns {Promise<Function>}
   */
  loadCode: function(bucket, token) {
    var msg = new message.GetBaqendCode(bucket);
    msg.withAuthorizationToken(token);

    return this._connector.send(msg).then(function(code) {
      return this.stringToFunction(code.response.entity);
    }.bind(this));
  },

  /**
   * Deletes Baqend code for the given bucket name
   *
   * @param {String} bucket to identify the Baqend code
   * @param {String} token to authenticate the user
   * @returns {Promise}
   */
  deleteCode: function(bucket, token) {
    var msg = new message.DeleteBaqendCode(bucket);
    msg.withAuthorizationToken(token);

    return this._connector.send(msg).then(function() {
      this.setCode(bucket, "");
    }.bind(this));
  },

  /**
   * Calls the baqend code, which is identified by the given bucket.
   * The obj parameter will be used as the "this" object.
   *
   * @param {String} bucket Name of the baqend code
   * @param {Object=} obj Input of the baqend code function
   * @param {String} token to authenticate the user
   * @param {baqend.EntityManager} entityManager is needed when the code will be called locally
   * @returns {Promise<Object>}
   */
  callCode: function(bucket, obj, token, entityManager) {
    var fn = this.getCode(bucket);
    if(fn) {
      return new Promise(function(resolve) {
        resolve(fn.call(obj, entityManager));
      });
    } else {
      var msg = new message.CallBaqendCode(bucket, obj);
      msg.withAuthorizationToken(token);
      return this._connector.send(msg).then(function(code) {
        return code.response.entity;
      });
    }
  },

  _saveHandler: function(bucket, codeType, fn, token) {
    return this._saveLoadHandler(bucket, codeType, fn, token);
  },

  _loadHandler: function(bucket, codeType, token) {
    return this._saveLoadHandler(bucket, codeType, null, token);
  },

  _saveLoadHandler: function(bucket, codeType, fn, token) {
    var msg = fn === null? new message.GetBaqendHandler(bucket, codeType): new message.SetBaqendHandler(bucket, codeType, this.functionToString(fn));
    msg.withAuthorizationToken(token);
    return this._connector.send(msg).then(function(code) {
      this.setHandler(bucket, codeType, code.response.entity);
      return this.getHandler(bucket, codeType);
    }.bind(this), function(e) {
      if (e.statusCode == StatusCode.OBJECT_NOT_FOUND) {
        return null;
      } else {
        throw e;
      }
    });
  },

  _deleteHandler: function(bucket, codeType, token) {
    var msg = new message.DeleteBaqendHandler(bucket, codeType);
    msg.withAuthorizationToken(token);
    return this._connector.send(msg).then(function() {
      this.removeHandler(bucket, codeType);
    }.bind(this));
  }
});
<|endoftext|>


<commit_before>
// DATA
import { UNEARNED_INCOME_SOURCES } from '../../../data/state/massachusetts/name-cores';

// UTILITIES
import { toCashflow, sumCashflow, getGrossUnearnedIncomeMonthly } from '../../../helpers/cashflow';
import { sum } from '../../../helpers/math';

// TO RETURN
import { Result } from '../../../helpers/Result';

/**
* There should be no required properties since we want results from the start
* and everything at the start is `undefined`.
*/

/** Using old and new cash flow data, return new subsidy amount,
* include new rent share.
*
* @function
* @since 09/2017
*
* With Project Hopes's guidance, we're using old known values (as
* known as they can get) to derive new values.
* @see Deriving: {@link https://docs.google.com/document/d/1o1Tm0HioHeY4NcBSDcjSbXDOjmCAJdI47kG5Fyms0UI/edit#}.
* @see Definitions: {@link https://portal.hud.gov/hudportal/documents/huddoc?id=DOC_11749.pdf}
*
* @todo Find out how close to 0/change the benefit amount needs to be in
* order for the client to be warned.
*/
const getHousingBenefit = function ( client ) {
  /*
  * var diff = new min ttp - old min ttp;
  * var new rent share = old rent share + diff;
  * var new subsidy = contract rent - new rent share
  */
  var prev = 'previous';

  var ttps        = getTTPs( client ),
      diff        = ttps.newTTP - ttps.oldTTP,
      newShare    = diff + toCashflow( client, prev, 'RentShare' ),
      contrRent   = toCashflow( client, prev, 'ContractRent' );

  // Don't pay more rent than the landlord is asking for
  var maxShare    = Math.min( contrRent, newShare ),
      newSubsidy  = contrRent - maxShare;

  var result = {
    result: 'good',
    details: 'All good!',
    benefitValue: newSubsidy,
    data: { newRentShare: maxShare }
  };

  /** @todo When to give a warning for Section 8? */
  if ( newSubsidy <= 500 ) {
    result.result   = 'information';
    result.details  = 'Your housing subsidy is getting low.';
  }

  var officialResult = new Result( result );

  return officialResult;
};  // End getHousingBenefit

/**
* @todo Function description
*
* '#' refers to # item on form at Appendix B of http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
* Is using raw monthly values or converting values to monthly amounts
*/
const getTTPs = function ( client ) {

  var oldNet = getNetIncome( client, 'previous' ),
      newNet = getNetIncome( client, 'current' );

  var oldAdj = getAdjustedIncome( client, 'previous', oldNet ),
      newAdj = getAdjustedIncome( client, 'current', newNet );

  /** @todo A placeholder till we know what to do with negative values */
  oldAdj = Math.max( 0, oldAdj );
  newAdj = Math.max( 0, newAdj );

  // #17, 30% of Adjusted Monthly Income
  var oldAdjToTest = oldAdj * 0.3,
      newAdjToTest = newAdj * 0.3,
      // #18, 10% of Monthly Income
      oldNetToTest = oldNet * 0.1,
      newNetToTest = newNet * 0.1;

  /**
  * TTP = 'total tenant payment'. One place (pg 59) calls this min ttp
  * but another (Appendix B) calls it max. Second makes more sense.
  * Represents max rent share.
  *
  * Note: welfare rent and PHA min rent are not known and so not
  * included in the test. MA may not have welfare rent and PHA min
  * can be waived.
  */
  var oldMaxTTP = Math.max( oldNetToTest, oldAdjToTest ),
      newMaxTTP = Math.max( newNetToTest, newAdjToTest ),
      ttps      = { oldTTP: oldMaxTTP, newTTP: newMaxTTP };

  return ttps;
};  // End getTTPs()

// =============================
// NET INCOME
// =============================

/**
* @todo Function description
*/
const getNetIncome = function ( client, timeframe ) {
  var unearned = getGrossUnearnedIncomeMonthly( client, timeframe ),
      gross    = unearned + toCashflow( client, timeframe, 'EarnedIncome' ),
      net      = gross - toCashflow( client, timeframe, 'IncomeExclusions' );
  return net;
};  // End getNetIncome()

// =============================
// ADJUSTED INCOME
// =============================
/** Properties for all child care expenses */
const childcareProps = [
  'ChildDirectCareCosts', 'ChildBeforeAndAfterSchoolCareCosts',
  'ChildTransportationCosts', 'ChildOtherCareCosts'
];

/**
* @todo Function description
*
* This is only adjusted income as defined for the Section 8 program
* '#' refers to # item on form at Appendix B of http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
* 'pg' refers to the written page number of https://portal.hud.gov/hudportal/documents/huddoc?id=DOC_11749.pdf (gone)
*
* Is using raw monthly values or converting values to monthly amounts
*/
const getAdjustedIncome = function ( client, timeframe, net ) {

  var time       = timeframe,
      allowances = [];

  // #4 & #5
  var depAllowanceAnnual  = (client[ time + 'NumberOfDependents'] || 0) * 480;
  allowances.push( depAllowanceAnnual/12 );
  // #6
  var childcare   = sumCashflow( client, time, childcareProps ),
      ccIncome    = toCashflow( client, time, 'EarnedIncomeBecauseOfChildCare' ),
      /** @todo If student or looking for work, during those hours the expense isn't limited? 2007 Ch. 5 doc */
      ccMin       = Math.min( childcare, ccIncome );
  allowances.push( ccMin );
  // #7 - 13
  var disAndMed = getDisabledAndMedicalAllowancesSum( client, timeframe, net )
  allowances.push( disAndMed );
  // #14
  /** @todo Fix 'current' + etc not existing while 'previous' + etc does */
  if ( client[ time + 'DisabledOrElderlyHeadOrSpouse' ] ) { allowances.push( 400/12 ); }

  var total = sum( allowances ),
      adj   = net - total;

  /** @todo What do we do if this is negative, e.g. dependent allowances are greater than income? */
  return adj;
};  // End getAdjustedIncome()

/**
* Medical allowance needs assistance allowance amounts
* '#' refers to # item on form at Appendix B of http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
* 'pg' refers to the written page number of https://portal.hud.gov/hudportal/documents/huddoc?id=DOC_11749.pdf (gone)
* Is using raw monthly values
*/
const getDisabledAndMedicalAllowancesSum = function ( client, timeframe, net ) {

  var time          = timeframe,
      netSubtractor = net * 0.03;  // #8, #13

  // ----- Assistance Allowance #C, #7 - 11 ----- \\
  // pg 5-30 to 5-31
  var handcpExpense  = toCashflow( client, time, 'DisabledAssistance' ),  // #7
      asstSubtracted = handcpExpense - netSubtractor,  // #9
      asstIncome     = toCashflow( client, time, 'EarnedIncomeBecauseOfAdultCare' ), // #10
      hcapAllowance  = Math.min( asstSubtracted, asstIncome ),  // #11
      hcapMin        = Math.max( 0, hcapAllowance );  // Don't get negative

  // ----- Maybe Stop #D ----- \\
  /** Only keep going if there's a disabled/elderly head or spouse (or sole member) */
  if ( !client[ time + 'DisabledOrElderlyHeadOrSpouse' ] ) { return hcapMin; }

  // ----- Medical Allowance #12 - 13 ----- \\
  // #12, pg 5-31 to 5-32
  var disOrElderlyMedical = toCashflow( client, time, 'DisabledOrElderlyMedical' ),
      // pg 5-31 says all medical expenses count for this household
      otherMedical        = toCashflow( client, time, 'RegularMedical' ),
      medicalExpenses     = disOrElderlyMedical + otherMedical;  // #12

  // Read all of pg 5-32 and 5-34 for the following conditional.
  // Jumps around because cases are overlapping.

  var medAllowance = 0;
  // #13a, pg 5-32 bottom (5-33 to 5-34 example falls in here)
  // Note: #13 forgets the '>=' part and just says '>'
  if ( asstSubtracted >= 0 ) {
    medAllowance = medicalExpenses;

  // #13b
  // pg 5-33 middle /and/ pg 5-32 middle, above "special calcuations"
  // In both cases handcpExpense is >= 0 and can be safely aded.
  } else {
    var sum = medicalExpenses + handcpExpense;
    medAllowance = sum - netSubtractor;
  }

  var medMin = Math.max( 0, medAllowance );

  // #15 contribution ( #11 + #13 )
  return hcapMin + medMin;
};  // End getDisabledAndMedicalAllowancesSum()

export {
  getHousingBenefit
};
<commit_msg>Restores place for required properties<commit_after>
// DATA
import { UNEARNED_INCOME_SOURCES } from '../../../data/state/massachusetts/name-cores';

// UTILITIES
import { toCashflow, sumCashflow, getGrossUnearnedIncomeMonthly } from '../../../helpers/cashflow';
import { sum } from '../../../helpers/math';

// TO RETURN
import { Result } from '../../../helpers/Result';

/**
* Properties `client` object is required to have to get a valid
* result. This doesn't mean the user filled in all the data, just
* that the object passed into here contains everything needed in the
* form that it's needed.
*/
var subsidyRequiredProps = [];

/** Using old and new cash flow data, return new subsidy amount,
* include new rent share.
*
* @function
* @since 09/2017
*
* With Project Hopes's guidance, we're using old known values (as
* known as they can get) to derive new values.
* @see Deriving: {@link https://docs.google.com/document/d/1o1Tm0HioHeY4NcBSDcjSbXDOjmCAJdI47kG5Fyms0UI/edit#}.
* @see Definitions: {@link https://portal.hud.gov/hudportal/documents/huddoc?id=DOC_11749.pdf}
*
* @todo Find out how close to 0/change the benefit amount needs to be in
* order for the client to be warned.
*/
const getHousingBenefit = function ( client ) {
  /*
  * var diff = new min ttp - old min ttp;
  * var new rent share = old rent share + diff;
  * var new subsidy = contract rent - new rent share
  */
  var prev = 'previous';

  var ttps        = getTTPs( client ),
      diff        = ttps.newTTP - ttps.oldTTP,
      newShare    = diff + toCashflow( client, prev, 'RentShare' ),
      contrRent   = toCashflow( client, prev, 'ContractRent' );

  // Don't pay more rent than the landlord is asking for
  var maxShare    = Math.min( contrRent, newShare ),
      newSubsidy  = contrRent - maxShare;

  var result = {
    result: 'good',
    details: 'All good!',
    benefitValue: newSubsidy,
    data: { newRentShare: maxShare }
  };

  /** @todo When to give a warning for Section 8? */
  if ( newSubsidy <= 500 ) {
    result.result   = 'information';
    result.details  = 'Your housing subsidy is getting low.';
  }

  var officialResult = new Result( result );

  return officialResult;
};  // End getHousingBenefit

/**
* @todo Function description
*
* '#' refers to # item on form at Appendix B of http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
* Is using raw monthly values or converting values to monthly amounts
*/
const getTTPs = function ( client ) {

  var oldNet = getNetIncome( client, 'previous' ),
      newNet = getNetIncome( client, 'current' );

  var oldAdj = getAdjustedIncome( client, 'previous', oldNet ),
      newAdj = getAdjustedIncome( client, 'current', newNet );

  /** @todo A placeholder till we know what to do with negative values */
  oldAdj = Math.max( 0, oldAdj );
  newAdj = Math.max( 0, newAdj );

  // #17, 30% of Adjusted Monthly Income
  var oldAdjToTest = oldAdj * 0.3,
      newAdjToTest = newAdj * 0.3,
      // #18, 10% of Monthly Income
      oldNetToTest = oldNet * 0.1,
      newNetToTest = newNet * 0.1;

  /**
  * TTP = 'total tenant payment'. One place (pg 59) calls this min ttp
  * but another (Appendix B) calls it max. Second makes more sense.
  * Represents max rent share.
  *
  * Note: welfare rent and PHA min rent are not known and so not
  * included in the test. MA may not have welfare rent and PHA min
  * can be waived.
  */
  var oldMaxTTP = Math.max( oldNetToTest, oldAdjToTest ),
      newMaxTTP = Math.max( newNetToTest, newAdjToTest ),
      ttps      = { oldTTP: oldMaxTTP, newTTP: newMaxTTP };

  return ttps;
};  // End getTTPs()

// =============================
// NET INCOME
// =============================

/**
* @todo Function description
*/
const getNetIncome = function ( client, timeframe ) {
  var unearned = getGrossUnearnedIncomeMonthly( client, timeframe ),
      gross    = unearned + toCashflow( client, timeframe, 'EarnedIncome' ),
      net      = gross - toCashflow( client, timeframe, 'IncomeExclusions' );
  return net;
};  // End getNetIncome()

// =============================
// ADJUSTED INCOME
// =============================
/** Properties for all child care expenses */
const childcareProps = [
  'ChildDirectCareCosts', 'ChildBeforeAndAfterSchoolCareCosts',
  'ChildTransportationCosts', 'ChildOtherCareCosts'
];

/**
* @todo Function description
*
* This is only adjusted income as defined for the Section 8 program
* '#' refers to # item on form at Appendix B of http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
* 'pg' refers to the written page number of https://portal.hud.gov/hudportal/documents/huddoc?id=DOC_11749.pdf (gone)
*
* Is using raw monthly values or converting values to monthly amounts
*/
const getAdjustedIncome = function ( client, timeframe, net ) {

  var time       = timeframe,
      allowances = [];

  // #4 & #5
  var depAllowanceAnnual  = (client[ time + 'NumberOfDependents'] || 0) * 480;
  allowances.push( depAllowanceAnnual/12 );
  // #6
  var childcare   = sumCashflow( client, time, childcareProps ),
      ccIncome    = toCashflow( client, time, 'EarnedIncomeBecauseOfChildCare' ),
      /** @todo If student or looking for work, during those hours the expense isn't limited? 2007 Ch. 5 doc */
      ccMin       = Math.min( childcare, ccIncome );
  allowances.push( ccMin );
  // #7 - 13
  var disAndMed = getDisabledAndMedicalAllowancesSum( client, timeframe, net )
  allowances.push( disAndMed );
  // #14
  /** @todo Fix 'current' + etc not existing while 'previous' + etc does */
  if ( client[ time + 'DisabledOrElderlyHeadOrSpouse' ] ) { allowances.push( 400/12 ); }

  var total = sum( allowances ),
      adj   = net - total;

  /** @todo What do we do if this is negative, e.g. dependent allowances are greater than income? */
  return adj;
};  // End getAdjustedIncome()

/**
* Medical allowance needs assistance allowance amounts
* '#' refers to # item on form at Appendix B of http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
* 'pg' refers to the written page number of https://portal.hud.gov/hudportal/documents/huddoc?id=DOC_11749.pdf (gone)
* Is using raw monthly values
*/
const getDisabledAndMedicalAllowancesSum = function ( client, timeframe, net ) {

  var time          = timeframe,
      netSubtractor = net * 0.03;  // #8, #13

  // ----- Assistance Allowance #C, #7 - 11 ----- \\
  // pg 5-30 to 5-31
  var handcpExpense  = toCashflow( client, time, 'DisabledAssistance' ),  // #7
      asstSubtracted = handcpExpense - netSubtractor,  // #9
      asstIncome     = toCashflow( client, time, 'EarnedIncomeBecauseOfAdultCare' ), // #10
      hcapAllowance  = Math.min( asstSubtracted, asstIncome ),  // #11
      hcapMin        = Math.max( 0, hcapAllowance );  // Don't get negative

  // ----- Maybe Stop #D ----- \\
  /** Only keep going if there's a disabled/elderly head or spouse (or sole member) */
  if ( !client[ time + 'DisabledOrElderlyHeadOrSpouse' ] ) { return hcapMin; }

  // ----- Medical Allowance #12 - 13 ----- \\
  // #12, pg 5-31 to 5-32
  var disOrElderlyMedical = toCashflow( client, time, 'DisabledOrElderlyMedical' ),
      // pg 5-31 says all medical expenses count for this household
      otherMedical        = toCashflow( client, time, 'RegularMedical' ),
      medicalExpenses     = disOrElderlyMedical + otherMedical;  // #12

  // Read all of pg 5-32 and 5-34 for the following conditional.
  // Jumps around because cases are overlapping.

  var medAllowance = 0;
  // #13a, pg 5-32 bottom (5-33 to 5-34 example falls in here)
  // Note: #13 forgets the '>=' part and just says '>'
  if ( asstSubtracted >= 0 ) {
    medAllowance = medicalExpenses;

  // #13b
  // pg 5-33 middle /and/ pg 5-32 middle, above "special calcuations"
  // In both cases handcpExpense is >= 0 and can be safely aded.
  } else {
    var sum = medicalExpenses + handcpExpense;
    medAllowance = sum - netSubtractor;
  }

  var medMin = Math.max( 0, medAllowance );

  // #15 contribution ( #11 + #13 )
  return hcapMin + medMin;
};  // End getDisabledAndMedicalAllowancesSum()

export {
  getHousingBenefit
};
<|endoftext|>


<commit_before>var do_encrypt_selectors = [
   "._552m",
    "._1rv"
];

var textareaUsernameGetters = {
	'_552m' : function($textarea) {
		var $anchor = $focused.closest('.fbNubFlyoutFooter')
					.siblings('.titlebar').find('.titlebarText');
		if ($anchor.hasClass('noLink')) {
			var userInputs = $anchor.closest('.fbNubFlyoutTitlebar')
				.siblings('.fbNubFlyoutHeader').find('input[name="participants[]"]');
			return userInputs.map(function(index, elm) {
				return elm.value;
			});
		}
		return textareaUsernameGetters.fromAnchor($anchor);
	},
	'_1rv' : function() {
		var $anchor = $('#webMessengerHeaderName').find('a');
		return textareaUsernameGetters.fromAnchor($anchor);
	},
	'fromAnchor' : function($anchor) {
		var user_id = getURLParameter($anchor.attr('href'), 'id')
		if (user_id) return user_id;

		var friendUrl = $anchor.attr('href').split('/');
		return [friendUrl[friendUrl.length - 1]];
	}
}

$(function() {

	makeOverlays();

});

function makeOverlays() {
	$('textarea').each(function() {
		if ($(this).data('checkedOverlay')) {
			return;
		}

		var classes = $(this).attr('class').split(' ');
		var encryptClasses = classes.filter(function(elm) {
			return do_encrypt_selectors.indexOf('.' + elm) >= 0;
		});
		if (encryptClasses.length == 0) {
			$(this).data('checkedOverlay', true);
			return;
		}
		$(this).data('checkedOverlay', true);
		$(this).data('usernames', textareaUsernameGetters[encryptClasses[0]]($(this)) );

		//need a function to check if will_encrypt for usernames
		makeOverlay($(this));

	});
}

function makeOverlay($textarea) {
	if (!$textarea.is(':visible')) {
		return;
	}
	var $fakeblockArea = $textarea.clone();
	$textarea.after($fakeblockArea).hide();

	$fakeblockArea.data('encryptedArea', $textarea);

	$fakeblockArea.keyup(function() {
		encryptHandler($(this));
	});
	var firstInput = $textarea.val();
	encryptHandler($fakeblockArea, firstInput);
	$fakeblockArea.focus();
	$fakeblockArea.val(firstInput);
}

function getFakeBlock($textarea) {
	//dummy function
	return $.parseJSON($textarea.val().split('|')[2]);
}

function requestEncrypt($encryptedArea, message) {
	//use this to test while extension not running
	$encryptedArea.val("|fakeblock|" + JSON.stringify({'ciphertext' : message }) + "|endfakeblock|");
	return;

	sendMessage({
		"action" : "encrypt",
		"message" : message,
		"usernames" : $encryptedArea.data('usernames')
	}, function(response) {
		var message = $.parseJSON(response).res.cipher_text;
		if (typeof message === "string") {
			$encryptedArea.val(message);
		} else {
  			$encryptedArea.val("|fakeblock|" +
				JSON.stringify(message) +
				"|endfakeblock|"
			);
		}
	});
}

function encryptHandler($unencryptedArea, message) {
	var message = (message) ? message : $unencryptedArea.val();

	//send message to encrypt to josh
	encrypted = requestEncrypt($unencryptedArea.data('encryptedArea'),
		message);
}

function getURLParameter(url, name) {
    return decodeURI(
        (RegExp(name + '=' + '(.+?)(&|$)').exec(url)||[,null])[1]
    );
}
<commit_msg>double fuuuuck the overlay<commit_after>var do_encrypt_selectors = [
   "._552m",
    "._1rv"
];

var textareaUsernameGetters = {
	'_552m' : function($textarea) {
		var $anchor = $focused.closest('.fbNubFlyoutFooter')
					.siblings('.titlebar').find('.titlebarText');
		if ($anchor.hasClass('noLink')) {
			var userInputs = $anchor.closest('.fbNubFlyoutTitlebar')
				.siblings('.fbNubFlyoutHeader').find('input[name="participants[]"]');
			return userInputs.map(function(index, elm) {
				return elm.value;
			});
		}
		return textareaUsernameGetters.fromAnchor($anchor);
	},
	'_1rv' : function() {
		var $anchor = $('#webMessengerHeaderName').find('a');
		return textareaUsernameGetters.fromAnchor($anchor);
	},
	'fromAnchor' : function($anchor) {
		var user_id = getURLParameter($anchor.attr('href'), 'id')
		if (user_id) return user_id;

		var friendUrl = $anchor.attr('href').split('/');
		return [friendUrl[friendUrl.length - 1]];
	}
}

$(function() {

	makeOverlays();

});

function makeOverlays() {
	$('textarea').each(function() {
		if ($(this).data('checkedOverlay')) {
			return;
		}

		var classes = $(this).attr('class').split(' ');
		var encryptClasses = classes.filter(function(elm) {
			return do_encrypt_selectors.indexOf('.' + elm) >= 0;
		});
		if (encryptClasses.length == 0) {
			$(this).data('checkedOverlay', true);
			return;
		}
		$(this).data('checkedOverlay', true);
		$(this).data('usernames', textareaUsernameGetters[encryptClasses[0]]($(this)) );

		//need a function to check if will_encrypt for usernames
		makeOverlay($(this));

	});
}

function makeOverlay($textarea) {
	if (!$textarea.is(':visible')) {
		return;
	}
	var $fakeblockArea = $textarea.clone();
	$textarea.after($fakeblockArea).hide();

	$fakeblockArea.data('encryptedArea', $textarea);

	$fakeblockArea.keyup(function() {
		encryptHandler($(this));
	});
	var firstInput = $textarea.val();
	encryptHandler($fakeblockArea, firstInput);
	$fakeblockArea.focus();
	$fakeblockArea.val(firstInput);
}

function requestEncrypt($encryptedArea, message) {

	sendMessage({
		"action" : "encrypt",
		"message" : message,
		"usernames" : $encryptedArea.data('usernames')
	}, function(response) {
		var message = $.parseJSON(response).res.cipher_text;
		if (typeof message === "string") {
			$encryptedArea.val(message);
		} else {
  			$encryptedArea.val("|fakeblock|" +
				JSON.stringify(message) +
				"|endfakeblock|"
			);
		}
	});
}

function encryptHandler($unencryptedArea, message) {
	var message = (message) ? message : $unencryptedArea.val();

	//send message to encrypt to josh
	encrypted = requestEncrypt($unencryptedArea.data('encryptedArea'),
		message);
}

function getURLParameter(url, name) {
    return decodeURI(
        (RegExp(name + '=' + '(.+?)(&|$)').exec(url)||[,null])[1]
    );
}
<|endoftext|>


<commit_before>'use strict';

/**
 * This wraps interaction between the client device and the server-side Player
 * service. The Player service is the Player's view of the world, and the
 * websocket between the client and this player-focused hub service should
 * contain most of the back and forth required for interacting with the
 * game (room/chat, inventory), and the leaderboard (storing/retrieving
 * earned trophies and badges).
 *
 * @ngdoc service
 * @name playerApp.playerSocket
 * @description
 * # playerSocket
 * Factory in the playerApp.
 */
angular.module('playerApp')
  .factory('playerSocket',
  [          '$websocket','$log','user','auth','API','playerSession', 'marked',
    function ($websocket,  $log,  user,  auth,  API,  playerSession, marked) {

      var ws;
      var websocketURL = API.WS_URL + user.profile._id + "?jwt="+auth.token();
      var id = 0;

      // Collection for holding data: play.room.html displays
      // this scrolling collection.
      var roomEvents = [];

      // Collection of messages pending send
      var pendingSend = [];

      // Status of socket: available to send or not
      var canSend = false;

      // Restore some information from the session, like the bookmark (last seen message)
      var clientState = angular.extend({}, playerSession.get('clientState'));

      // More cached data: exits, inventory, etc.
      var gameData = angular.extend({}, playerSession.get('gameData'));

      // Clear the bookmark if the DB says we're in a different room than the local session does.
      if ( user.profile.location !== clientState.roomId ) {
        $log.debug('cache cleared');
        gameData = {}; // start over
        delete clientState.bookmark;
        clientState.roomId = user.profile.location;
      }
      clientState.username = user.profile.name;

      // Create a v1 websocket
      $log.debug("Opening player socket %o for %o",websocketURL, user.profile);
      ws = $websocket(websocketURL, {
             useApplyAsync: true,
             reconnectIfNotNormalClose: true
           });

      // On open, check in with the concierge
      ws.onOpen(function() {
        console.log('connection open');
        ws.send('ready,' + angular.toJson(clientState, 0));
      });

      // On received message, push to the correct collection
      ws.onMessage(function(event) {
        $log.debug("OnMessage %o",event);

        var comma = event.data.indexOf(',');
        var command = event.data.slice(0,comma);
        var payload = event.data.slice(comma+1);
        var target, res;

        if ( "ack" === command ) {
          res = parseJson(payload);
          $log.debug('ack received', res);
          clientState.mediatorId = res.mediatorId;
          clientState.roomId = res.roomId;
          clientState.roomName = res.roomName;
          if (res.fullName){
            clientState.fullName = res.fullName;
          }

          if ( res.commands ){
            gameData.commands = res.commands;
          }

          if ( !canSend ) {
            // indicate we can send again, and catch up with anything
            // we queued while the connection was re-establishing itself
            canSend = true;
            sendPending();
          }
        } else {
          comma = payload.indexOf(',');
          target = payload.slice(0,comma);
          payload = payload.slice(comma+1);

          res = parseJson(payload);
          clientState.bookmark = res.bookmark;
          res.id = id++; // this prevents element re-rendering in the UI

          if ( res.exits ) {
            gameData.exits = res.exits;
            $log.debug('exits updated', gameData);
          }

          if ( res.commands ) {
            gameData.commands = angular.extend({}, gameData.commands, res.commands);
            $log.debug('commands updated', gameData);
          }

          if ( res.objects ) {
            res.roomInventory = res.objects;
          }

          if ( res.fullName ) {
            clientState.fullName = res.fullName;
          }

          if ( res.roomInventory ) {
            gameData.roomInventory = res.roomInventory;
            $log.debug('room inventory updated', gameData);
          }

          switch (res.type) {
            case 'event':
              if ( res.content[user.profile._id] ) {
                res.content = marked(res.content[user.profile._id] || '');
              } else {
                res.content = marked(res.content['*'] || '');
              }
              roomEvents.push(res);
              break;
            default:
              res.description = marked(res.description || '');
              roomEvents.push(res);
              break;
          }
        }
      });

      // On error, report the error, and close the connection
      // (try to reconnect)
      ws.onError(function(event) {
        $log.debug('connection Error', event);
      });

      // On close, update the player session,
      // and indicate that we can no longer send outbound messages
      ws.onClose(function(event) {
        $log.debug('connection closed', event);

        if ( canSend && !event.wasClean ) {
          $log.debug('error shut down');
        }
        canSend = false;

        playerSession.set('clientState', clientState);
        playerSession.set('gameData', gameData);
      });

      var logout = function() {
        clientState = {};
        gameData = {};
        ws.close();

        auth.logout(); // will also reset the session
      };

      var parseJson = function(message) {
        var res;
        try {
          res = angular.fromJson(message);
        } catch(e) {
          $log.debug('parse %o %o', message, e);
          res = {username: user.username, content: message};
        }

        $log.debug('parse message: %o %o', message, res);
        return res;
      };

      var trySend = function(element) {
          // CARE! 'this' is the value passed from sendPending for iterating over
          // all pending elements (array.every), it is not playerSocket.
          if ( send(element) ) {
            this.i++;
            return true;
          }
          return false;
      };

      var sendPending = function() {
        var howfar = { i: 0 };

        // Iterate through the pending messages, stopping if the connection
        // is closed again *sigh*
        pendingSend.every(trySend, howfar);

        $log.debug('CATCH UP: pendingSend.length = %o, sent %o', pendingSend.length, howfar.i);
        pendingSend.splice(0, howfar.i);
      };

      var listExits = function() {
        // echo command to user's screen
        roomEvents.push({
          type: 'command',
          content: '/exits',
          id: id++
        });

        $log.debug('show cached exits: %o', gameData.exits);
        roomEvents.push({
          type: 'exits',
          content: gameData.exits,
          id: id++
        });

      };

      var listCommands = function() {
        roomEvents.push({
          type: 'command',
          content: '/helps',
          id: id++
        });

        $log.debug('show cached commands: %o', gameData.commands);
        roomEvents.push({
          type: 'commands',
          content: gameData.commands,
          id: id++
        });
      };

      var send = function(message) {
        if ( canSend ) {
          var sendMsg;

          var output = {
            username: user.profile.name,
            userId: user.profile._id,
            content: message
          };

          if ( message.charAt(0) === '/') {
            // echo command to user's screen
            roomEvents.push({
              type: 'command',
              content: message,
              id: id++
              });

            // Handle special case for commands here while we have the pieces
            if ( message.indexOf('/sos') === 0 ) {
              sendMsg = "sos,*," + angular.toJson(output);

              $log.debug('sending message: %o', sendMsg);
              ws.send(sendMsg);
              // DONE/SENT!, return whether or not we can still send,
              // which is updated via onClose
              return canSend;
            } else if ( message.indexOf('/exits') === 0 ) {
              $log.debug('show cached exits: %o', gameData.exits);
              roomEvents.push({
                type: 'exits',
                content: gameData.exits,
                id: id++
                });
              // DONE/SENT!, return whether or not we can still send,
              // which is updated via onClose
              return canSend;
            } else if (message.indexOf('/help') === 0 ) {
              $log.debug('show cached commands: %o', gameData.commands);
              roomEvents.push({
                type: 'wtf',
                content: gameData.commands,
                id: id++
              });
              return canSend;
            }
          }

          sendMsg = "room,"+clientState.roomId+","+angular.toJson(output);

          $log.debug('sending message: %o', sendMsg);
          ws.send(sendMsg);
        } else {
          pendingSend.push(message);
          $log.debug('Not able to send at the moment. Pushing message to pending list (%o): %o', pendingSend.length, message);
        }

        // return whether or not we can still send
        return canSend;
      };

      // Available methods and structures
      var sharedApi = {
        roomEvents: roomEvents,
        clientState: clientState,
        gameData: gameData,
        logout: logout,
        listExits: listExits,
        listCommands: listCommands,
        send: send
      };

      return sharedApi;
    }
  ]);
<commit_msg>fix it for real<commit_after>'use strict';

/**
 * This wraps interaction between the client device and the server-side Player
 * service. The Player service is the Player's view of the world, and the
 * websocket between the client and this player-focused hub service should
 * contain most of the back and forth required for interacting with the
 * game (room/chat, inventory), and the leaderboard (storing/retrieving
 * earned trophies and badges).
 *
 * @ngdoc service
 * @name playerApp.playerSocket
 * @description
 * # playerSocket
 * Factory in the playerApp.
 */
angular.module('playerApp')
  .factory('playerSocket',
  [          '$websocket','$log','user','auth','API','playerSession', 'marked',
    function ($websocket,  $log,  user,  auth,  API,  playerSession, marked) {

      var ws;
      var websocketURL = API.WS_URL + user.profile._id + "?jwt="+auth.token();
      var id = 0;

      // Collection for holding data: play.room.html displays
      // this scrolling collection.
      var roomEvents = [];

      // Collection of messages pending send
      var pendingSend = [];

      // Status of socket: available to send or not
      var canSend = false;

      // Restore some information from the session, like the bookmark (last seen message)
      var clientState = angular.extend({}, playerSession.get('clientState'));

      // More cached data: exits, inventory, etc.
      var gameData = angular.extend({}, playerSession.get('gameData'));

      // Clear the bookmark if the DB says we're in a different room than the local session does.
      if ( user.profile.location !== clientState.roomId ) {
        $log.debug('cache cleared');
        gameData = {}; // start over
        delete clientState.bookmark;
        clientState.roomId = user.profile.location;
      }
      clientState.username = user.profile.name;

      // Create a v1 websocket
      $log.debug("Opening player socket %o for %o",websocketURL, user.profile);
      ws = $websocket(websocketURL, {
             useApplyAsync: true,
             reconnectIfNotNormalClose: true
           });

      // On open, check in with the concierge
      ws.onOpen(function() {
        console.log('connection open');
        ws.send('ready,' + angular.toJson(clientState, 0));
      });

      // On received message, push to the correct collection
      ws.onMessage(function(event) {
        $log.debug("OnMessage %o",event);

        var comma = event.data.indexOf(',');
        var command = event.data.slice(0,comma);
        var payload = event.data.slice(comma+1);
        var target, res;

        if ( "ack" === command ) {
          res = parseJson(payload);
          $log.debug('ack received', res);
          clientState.mediatorId = res.mediatorId;
          clientState.roomId = res.roomId;
          clientState.roomName = res.roomName;
          if (res.fullName){
            clientState.fullName = res.fullName;
          }

          if ( res.commands ){
            gameData.commands = res.commands;
          }

          if ( !canSend ) {
            // indicate we can send again, and catch up with anything
            // we queued while the connection was re-establishing itself
            canSend = true;
            sendPending();
          }
        } else {
          comma = payload.indexOf(',');
          target = payload.slice(0,comma);
          payload = payload.slice(comma+1);

          res = parseJson(payload);
          clientState.bookmark = res.bookmark;
          res.id = id++; // this prevents element re-rendering in the UI

          if ( res.exits ) {
            gameData.exits = res.exits;
            $log.debug('exits updated', gameData);
          }

          if ( res.commands ) {
            gameData.commands = angular.extend({}, gameData.commands, res.commands);
            $log.debug('commands updated', gameData);
          }

          if ( res.objects ) {
            res.roomInventory = res.objects;
          }

          if ( res.fullName ) {
            clientState.fullName = res.fullName;
          }

          if ( res.roomInventory ) {
            gameData.roomInventory = res.roomInventory;
            $log.debug('room inventory updated', gameData);
          }

          switch (res.type) {
            case 'event':
              if ( res.content[user.profile._id] ) {
                res.content = marked(res.content[user.profile._id] || '');
              } else {
                res.content = marked(res.content['*'] || '');
              }
              roomEvents.push(res);
              break;
            default:
              res.description = marked(res.description || '');
              roomEvents.push(res);
              break;
          }
        }
      });

      // On error, report the error, and close the connection
      // (try to reconnect)
      ws.onError(function(event) {
        $log.debug('connection Error', event);
      });

      // On close, update the player session,
      // and indicate that we can no longer send outbound messages
      ws.onClose(function(event) {
        $log.debug('connection closed', event);

        if ( canSend && !event.wasClean ) {
          $log.debug('error shut down');
        }
        canSend = false;

        playerSession.set('clientState', clientState);
        playerSession.set('gameData', gameData);
      });

      var logout = function() {
        clientState = {};
        gameData = {};
        ws.close();

        auth.logout(); // will also reset the session
      };

      var parseJson = function(message) {
        var res;
        try {
          res = angular.fromJson(message);
        } catch(e) {
          $log.debug('parse %o %o', message, e);
          res = {username: user.username, content: message};
        }

        $log.debug('parse message: %o %o', message, res);
        return res;
      };

      var trySend = function(element) {
          // CARE! 'this' is the value passed from sendPending for iterating over
          // all pending elements (array.every), it is not playerSocket.
          if ( send(element) ) {
            this.i++;
            return true;
          }
          return false;
      };

      var sendPending = function() {
        var howfar = { i: 0 };

        // Iterate through the pending messages, stopping if the connection
        // is closed again *sigh*
        pendingSend.every(trySend, howfar);

        $log.debug('CATCH UP: pendingSend.length = %o, sent %o', pendingSend.length, howfar.i);
        pendingSend.splice(0, howfar.i);
      };

      var listExits = function() {
        // echo command to user's screen
        roomEvents.push({
          type: 'command',
          content: '/exits',
          id: id++
        });

        $log.debug('show cached exits: %o', gameData.exits);
        roomEvents.push({
          type: 'exits',
          content: gameData.exits,
          id: id++
        });

      };

      var listCommands = function() {
        roomEvents.push({
          type: 'command',
          content: '/help',
          id: id++
        });

        $log.debug('show cached commands: %o', gameData.commands);
        roomEvents.push({
          type: 'commands',
          content: gameData.commands,
          id: id++
        });
      };

      var send = function(message) {
        if ( canSend ) {
          var sendMsg;

          var output = {
            username: user.profile.name,
            userId: user.profile._id,
            content: message
          };

          if ( message.charAt(0) === '/') {
            // echo command to user's screen
            roomEvents.push({
              type: 'command',
              content: message,
              id: id++
              });

            // Handle special case for commands here while we have the pieces
            if ( message.indexOf('/sos') === 0 ) {
              sendMsg = "sos,*," + angular.toJson(output);

              $log.debug('sending message: %o', sendMsg);
              ws.send(sendMsg);
              // DONE/SENT!, return whether or not we can still send,
              // which is updated via onClose
              return canSend;
            } else if ( message.indexOf('/exits') === 0 ) {
              $log.debug('show cached exits: %o', gameData.exits);
              roomEvents.push({
                type: 'exits',
                content: gameData.exits,
                id: id++
                });
              // DONE/SENT!, return whether or not we can still send,
              // which is updated via onClose
              return canSend;
            } else if (message.indexOf('/help') === 0 ) {
              $log.debug('show cached commands: %o', gameData.commands);
              roomEvents.push({
                type: 'commands',
                content: gameData.commands,
                id: id++
              });
              return canSend;
            }
          }

          sendMsg = "room,"+clientState.roomId+","+angular.toJson(output);

          $log.debug('sending message: %o', sendMsg);
          ws.send(sendMsg);
        } else {
          pendingSend.push(message);
          $log.debug('Not able to send at the moment. Pushing message to pending list (%o): %o', pendingSend.length, message);
        }

        // return whether or not we can still send
        return canSend;
      };

      // Available methods and structures
      var sharedApi = {
        roomEvents: roomEvents,
        clientState: clientState,
        gameData: gameData,
        logout: logout,
        listExits: listExits,
        listCommands: listCommands,
        send: send
      };

      return sharedApi;
    }
  ]);
<|endoftext|>


<commit_before>const request = require('request');
const logger = require('cli-logger');
const config = require('config');
const getTicker = require('./lib/get-ticker');
const getMarketHistory = require('./lib/get-market-history');
const helper = require('./helper');
const _ = require('lodash');
const fs = require('fs');
const tradeStub = require('./lib/trade-stub');
const Account = require('./lib/account');

const log = logger({ level: logger.INFO });

const getCrossovers = market => new Promise(async (resolveGetCrossovers, rejectGetCrossovers) => request(`${config.get('bittrexCache.getcrossoverurl')}?market=${market}`, (error, response) => {
  if (error) {
    return rejectGetCrossovers(error);
  }

  if (response.body === undefined) {
    return rejectGetCrossovers(new Error('Empty body'));
  }

  let jsonBody;
  try {
    jsonBody = JSON.parse(response.body);
  } catch (jsonParseError) {
    return rejectGetCrossovers(jsonParseError);
  }

  return resolveGetCrossovers(jsonBody.result);
}));

(async () => {
  const tasks = [
    getTicker(config.get('bittrexMarket')),
    getCrossovers(config.get('bittrexMarket')),
  ];

  const [ticker, crossoverData] = await Promise.all(tasks);

  let tradeAmount = 100; // How much currency you have to trade
  const account = new Account(config.get('sienaAccount.baseCurrency'), tradeAmount);
  const buySellPoints = [];

  const strategyResult = crossoverData.reduce((accumatedPosition, crossoverPoint) => {
    const position = accumatedPosition;
    const timeSinceLastTrade = helper.cleanBittrexTimestamp(crossoverPoint.timestamp) -
      position.lastTradeTime;

    // Only buy if the trend is up, AND
    // you have some amount to trade, AND
    // it has been atleast sometime since your last trade
    log.info(`trend:${crossoverPoint.trend}, market:${(crossoverPoint.market || 'nevermind')}, balance:${position.account.getBalanceNumber()}, timeSinceLastTrade: ${helper.millisecondsToHours(timeSinceLastTrade)}, lastBuyPrice: ${(position.lastBuyPrice || 'nevermind')}, bidPrice: ${crossoverPoint.bidPrice}, securityBalance: ${position.security}`);
    if (crossoverPoint.trend === 'UP'
      && crossoverPoint.market !== 'BEAR'
      && position.lastTrade !== 'BUY'
      && position.account.getBalanceNumber() > 1
      && timeSinceLastTrade >= config.get('strategy.shortPeriod')) {
      log.info(`Time since last trade : ${helper.millisecondsToHours(timeSinceLastTrade)}`);
      // Buy at ask price
      // Commission is in USDT
      const quantity = position.account.getTradeAmount() / crossoverPoint.askPrice;
      const commission = tradeStub.getCommission(quantity, crossoverPoint.askPrice);
      const buyLesserQuantity = (position.account.getTradeAmount() - commission)
        / crossoverPoint.askPrice;
      const trade = tradeStub.buy(buyLesserQuantity, crossoverPoint.askPrice);

      position.security = trade.security;
      position.account.debit(trade.total);
      position.lastTradeTime = helper.cleanBittrexTimestamp(crossoverPoint.timestamp);
      position.lastBuyPrice = crossoverPoint.askPrice;
      position.lastTrade = 'BUY';
      buySellPoints.push(`${helper.cleanBittrexTimestamp(crossoverPoint.timestamp)},${crossoverPoint.askPrice},1`);
    } else if (
      (
        (
          crossoverPoint.trend === 'DOWN' &&
          crossoverPoint.bidPrice > position.lastBuyPrice
        ) ||
        (
          crossoverPoint.trend === 'BEAR'
        )
      ) && position.security > 0) {
      // Sell at the bid price
      // Commission is in USDT
      const quantity = position.security;
      const trade = tradeStub.sell(quantity, crossoverPoint.bidPrice);
      position.account.credit(trade.total);
      position.security = 0;
      position.lastTradeTime = helper.cleanBittrexTimestamp(crossoverPoint.timestamp);
      position.lastTrade = 'SELL';
      buySellPoints.push(`${helper.cleanBittrexTimestamp(crossoverPoint.timestamp)},${crossoverPoint.bidPrice},0`);
    }

    return (position);
  }, { security: 0, account, lastTradeTime: null });
  if (strategyResult.security > 0) {
    // Sell off any security
    const trade = tradeStub.sell(strategyResult.security, ticker.Ask);
    strategyResult.account.credit(trade.total);
  }
  log.info('Simulating strategy less buy and sale');
  // Buy it during the first value of the crossover
  const nonStrategyBuyTrade = tradeStub.buy(
    tradeAmount / crossoverData[0].askPrice,
    crossoverData[0].askPrice);
  const security = nonStrategyBuyTrade.security;

  // Sell it for the current asking price
  const nonStrategySellTrade = tradeStub.sell(security, ticker.Ask);
  tradeAmount = nonStrategySellTrade.total;

  // Generate a file with all the buy and sell points.
  const strategyResultDataFile = 'strategyResultData.txt';
  const strategyResultData = buySellPoints.join('\n');

  log.info(`Current balance based on strategy : ${strategyResult.account.getBalanceNumber()}, Current balance if you just bought and sold : ${tradeAmount}`);

  const tradeHistoryDataFile = 'tradeHistory.txt';

  // Get the market history to plot the data
  const toTimestamp = new Date().getTime();
  const fromTimestamp24 = toTimestamp - (3600000 * 168); // 24 hours

  const marketHistoryData = await getMarketHistory(config.get('bittrexMarket'), fromTimestamp24, toTimestamp, 'bittrexCache');
  const filteredData = helper.getSoldPricesBetweenPeriod(marketHistoryData,
    fromTimestamp24,
    toTimestamp);

  const timestamps = _.map(filteredData, object =>
    helper.cleanBittrexTimestamp(object.TimeStamp));
  log.info(`Timestamps between ${new Date(Math.min(...timestamps))} and ${new Date(Math.max(...timestamps))} for about ${(Math.max(...timestamps) - Math.min(...timestamps)) / (1000 * 60 * 60)} hours`);

  const tradeHistoryData = _.map(filteredData, object => `${helper.cleanBittrexTimestamp(object.TimeStamp)},${object.Price}`).join('\n');

  const fileWriteTasks = [
    new Promise(async resolveWrite => fs.writeFile(
      tradeHistoryDataFile,
      tradeHistoryData,
      resolveWrite)),
    new Promise(async resolveWrite => fs.writeFile(
      strategyResultDataFile,
      strategyResultData,
      resolveWrite)),
  ];

  await Promise.all(fileWriteTasks);

  log.info('Done!');
})();
<commit_msg>Playing around with the strategy<commit_after>const request = require('request');
const logger = require('cli-logger');
const config = require('config');
const getTicker = require('./lib/get-ticker');
const getMarketHistory = require('./lib/get-market-history');
const helper = require('./helper');
const _ = require('lodash');
const fs = require('fs');
const tradeStub = require('./lib/trade-stub');
const Account = require('./lib/account');

const log = logger({ level: logger.INFO });

const getCrossovers = market => new Promise(async (resolveGetCrossovers, rejectGetCrossovers) => request(`${config.get('bittrexCache.getcrossoverurl')}?market=${market}`, (error, response) => {
  if (error) {
    return rejectGetCrossovers(error);
  }

  if (response.body === undefined) {
    return rejectGetCrossovers(new Error('Empty body'));
  }

  let jsonBody;
  try {
    jsonBody = JSON.parse(response.body);
  } catch (jsonParseError) {
    return rejectGetCrossovers(jsonParseError);
  }

  return resolveGetCrossovers(jsonBody.result);
}));

(async () => {
  const tasks = [
    getTicker(config.get('bittrexMarket')),
    getCrossovers(config.get('bittrexMarket')),
  ];

  const [ticker, crossoverData] = await Promise.all(tasks);

  let tradeAmount = 100; // How much currency you have to trade
  const account = new Account(config.get('sienaAccount.baseCurrency'), tradeAmount);
  const buySellPoints = [];

  const strategyResult = crossoverData.reduce((accumatedPosition, crossoverPoint) => {
    const position = accumatedPosition;
    const timeSinceLastTrade = helper.cleanBittrexTimestamp(crossoverPoint.timestamp) -
      position.lastTradeTime;

    // Only buy if the trend is up, AND
    // you have some amount to trade, AND
    // it has been atleast sometime since your last trade
    log.info(`trend:${crossoverPoint.trend}, market:${(crossoverPoint.market || 'nevermind')}, balance:${position.account.getBalanceNumber()}, timeSinceLastTrade: ${helper.millisecondsToHours(timeSinceLastTrade)}, lastBuyPrice: ${(position.lastBuyPrice || 'nevermind')}, bidPrice: ${crossoverPoint.bidPrice}, securityBalance: ${position.security}`);
    if (crossoverPoint.trend === 'UP'
      && crossoverPoint.market !== 'BEAR'
      && position.lastTrade !== 'BUY'
      && position.account.getBalanceNumber() > 1
      ) {
      log.info(`Time since last trade : ${helper.millisecondsToHours(timeSinceLastTrade)}`);
      // Buy at ask price
      // Commission is in USDT
      const quantity = position.account.getTradeAmount() / crossoverPoint.askPrice;
      const commission = tradeStub.getCommission(quantity, crossoverPoint.askPrice);
      const buyLesserQuantity = (position.account.getTradeAmount() - commission)
        / crossoverPoint.askPrice;
      const trade = tradeStub.buy(buyLesserQuantity, crossoverPoint.askPrice);

      position.security = trade.security;
      position.account.debit(trade.total);
      position.lastTradeTime = helper.cleanBittrexTimestamp(crossoverPoint.timestamp);
      position.lastBuyPrice = crossoverPoint.askPrice;
      position.lastTrade = 'BUY';
      buySellPoints.push(`${helper.cleanBittrexTimestamp(crossoverPoint.timestamp)},${crossoverPoint.askPrice},1`);
    } else if (
      (
        (
          crossoverPoint.bidPrice > (position.lastBuyPrice + (0.2 * position.lastBuyPrice))
        ) ||
        (
          crossoverPoint.market === 'BEAR'
        )
      ) && position.security > 0
        && position.lastTrade !== 'SELL') {
      // Sell at the bid price
      // Commission is in USDT
      const quantity = position.security;
      const trade = tradeStub.sell(quantity, crossoverPoint.bidPrice);
      position.account.credit(trade.total);
      position.security = 0;
      position.lastTradeTime = helper.cleanBittrexTimestamp(crossoverPoint.timestamp);
      position.lastTrade = 'SELL';
      buySellPoints.push(`${helper.cleanBittrexTimestamp(crossoverPoint.timestamp)},${crossoverPoint.bidPrice},0`);
    }

    return (position);
  }, { security: 0, account, lastTradeTime: null });
  if (strategyResult.security > 0) {
    // Sell off any security
    const trade = tradeStub.sell(strategyResult.security, ticker.Ask);
    strategyResult.account.credit(trade.total);
  }
  log.info('Simulating strategy less buy and sale');
  // Buy it during the first value of the crossover
  const nonStrategyBuyTrade = tradeStub.buy(
    tradeAmount / crossoverData[0].askPrice,
    crossoverData[0].askPrice);
  const security = nonStrategyBuyTrade.security;

  // Sell it for the current asking price
  const nonStrategySellTrade = tradeStub.sell(security, ticker.Ask);
  tradeAmount = nonStrategySellTrade.total;

  // Generate a file with all the buy and sell points.
  const strategyResultDataFile = 'strategyResultData.txt';
  const strategyResultData = buySellPoints.join('\n');

  log.info(`Current balance based on strategy : ${strategyResult.account.getBalanceNumber()}, Current balance if you just bought and sold : ${tradeAmount}`);

  const tradeHistoryDataFile = 'tradeHistory.txt';

  // Get the market history to plot the data
  const toTimestamp = new Date().getTime();
  const fromTimestamp24 = toTimestamp - (3600000 * 168); // 24 hours

  const marketHistoryData = await getMarketHistory(config.get('bittrexMarket'), fromTimestamp24, toTimestamp, 'bittrexCache');
  const filteredData = helper.getSoldPricesBetweenPeriod(marketHistoryData,
    fromTimestamp24,
    toTimestamp);

  const timestamps = _.map(filteredData, object =>
    helper.cleanBittrexTimestamp(object.TimeStamp));
  log.info(`Timestamps between ${new Date(Math.min(...timestamps))} and ${new Date(Math.max(...timestamps))} for about ${(Math.max(...timestamps) - Math.min(...timestamps)) / (1000 * 60 * 60)} hours`);

  const tradeHistoryData = _.map(filteredData, object => `${helper.cleanBittrexTimestamp(object.TimeStamp)},${object.Price}`).join('\n');

  const fileWriteTasks = [
    new Promise(async resolveWrite => fs.writeFile(
      tradeHistoryDataFile,
      tradeHistoryData,
      resolveWrite)),
    new Promise(async resolveWrite => fs.writeFile(
      strategyResultDataFile,
      strategyResultData,
      resolveWrite)),
  ];

  await Promise.all(fileWriteTasks);

  log.info('Done!');
})();
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var package = require('../package');
var emitter = require('hyper-emitter');
var utils = require('../lib/utils');
var $watchPath = utils.$watchPath;
var $safeApply = utils.$safeApply;
var merge = utils.merge;
var each = require('each');
var createLink = require('./hyper-link').create;
var qs = require('querystring');

/**
 * hyperForm
 */

package.directive('hyperForm', [
  '$location',
  function($location) {
    return {
      scope: true,
      require: 'form',
      link: function($scope, elem, attrs, form) {
        // disable hiding the element until loaded
        elem.addClass('ng-hyper-loading');

        // get a user-specified callback
        var cb = attrs.hyperHandle ? $scope.$eval(attrs.hyperHandle) : noop;

        $watchPath.call($scope, attrs.hyperForm, function(err, value, req) {
          // TODO come up with an error strategy
          if (err) return console.error(err.stack || err);

          if (!value || !value.action) return;

          // mark the element as loaded
          elem.removeClass('ng-hyper-loading');
          elem.addClass('ng-hyper-loaded');

          // TODO do we need to set these values? The only thing I can
          //      think of is rendering the site on the server..
          // Set the action and method for the form
          // elem.attr('method', value.method || 'GET');
          // elem.attr('action', value.action);

          // We're in the middle of editing our form
          if (form.$dirty) return;

          // Expose the list of shown inputs to the view
          $scope.values = {};

          var ins = [];
          each(value.input, function(name, conf) {
            if (conf.type === 'hidden') return $scope.values[name] = typeof conf.value === 'undefined' ? conf : conf.value;
            // We have to clone this object so hyper-path doesn't watch for changes on the model
            var input = smerge({
              model: conf.value,
              name: name
            }, conf);
            ins.push(input);
            ins[name] = input;
          });
          var inputs = $scope.inputs = merge($scope.inputs, ins);

          elem.bind('submit', function() {
            // TODO if the method is idempotent and the form is pristine don't submit
            $scope.submit();
          });

          function followLink() {
            var url = value.action + '?' + qs.stringify($scope.values);
            // TODO create a child scope
            var res = createLink(attrs.hyperAction, {query: {href: url}});
            if (!res.loaded) return;
            $safeApply.call($scope, function() {
              $location.path(res.href);
            });
          }

          $scope.set = function(name, value) {
            $scope.values[name] = value;
            if ($scope.inputs[name]) $scope.inputs[name].model = value;
          };

          $scope.submit = function() {
            $scope.hyperFormLoading = true;
            each(inputs, function(input) {
              if (typeof input.model === 'undefined') return;
              if ($scope.values[input.name]) return;
              $scope.values[input.name] = input.model;
            });
            attrs.hyperAction && value.method === 'GET'
              ? followLink()
              : emitter.submit(value.method, value.action, $scope.values, onfinish);
          };
        });

        function onfinish(err, res) {
          $safeApply.call($scope, function() {
            delete $scope.hyperFormLoading;
            $setPristine(form);
            cb(err, res);
            $scope.values = {};
            if (err) $scope.hyperFormError = err;
            // TODO what are other status that we want to expose?
          });
        }
      }
    };
  }
]);

function $setPristine(form) {
  if (form.$setPristine) return form.$setPristine();
  form.$pristine = true;
  form.$dirty = false;
  each(form, function(input, key) {
    if (input.$pristine) input.$pristine = true;
    if (input.$dirty) input.$dirty = false;
  });
}

function noop() {}

function smerge(a, b) {
  for (var k in b) {
    a[k] = b[k];
  }
  return a;
}
<commit_msg>only submit the form once<commit_after>/**
 * Module dependencies
 */

var package = require('../package');
var emitter = require('hyper-emitter');
var utils = require('../lib/utils');
var $watchPath = utils.$watchPath;
var $safeApply = utils.$safeApply;
var merge = utils.merge;
var each = require('each');
var createLink = require('./hyper-link').create;
var qs = require('querystring');

/**
 * hyperForm
 */

package.directive('hyperForm', [
  '$location',
  function($location) {
    return {
      scope: true,
      require: 'form',
      link: function($scope, elem, attrs, form) {
        // disable hiding the element until loaded
        elem.addClass('ng-hyper-loading');

        // get a user-specified callback
        var cb = attrs.hyperHandle ? $scope.$eval(attrs.hyperHandle) : noop;

        elem.bind('submit', function() {
          // TODO if the method is idempotent and the form is pristine don't submit
          if ($scope.submit) $scope.submit();
        });

        $watchPath.call($scope, attrs.hyperForm, function(err, value, req) {
          // TODO come up with an error strategy
          if (err) return console.error(err.stack || err);

          // It doesn't look like a form
          if (!value || !value.action) return;

          // mark the element as loaded
          elem.removeClass('ng-hyper-loading');
          elem.addClass('ng-hyper-loaded');

          // We're in the middle of editing our form
          if (form.$dirty) return;

          // Expose the list of shown inputs to the view
          $scope.values = {};

          var ins = [];
          each(value.input, function(name, conf) {
            if (conf.type === 'hidden') return $scope.values[name] = typeof conf.value === 'undefined' ? conf : conf.value;
            // We have to clone this object so hyper-path doesn't watch for changes on the model
            var input = smerge({
              model: conf.value,
              name: name
            }, conf);
            ins.push(input);
            ins[name] = input;
          });
          var inputs = $scope.inputs = merge($scope.inputs, ins);

          function followLink() {
            var url = value.action + '?' + qs.stringify($scope.values);
            // TODO create a child scope
            var res = createLink(attrs.hyperAction, {query: {href: url}});
            if (!res.loaded) return;
            $safeApply.call($scope, function() {
              $location.path(res.href);
            });
          }

          $scope.set = function(name, value) {
            $scope.values[name] = value;
            if ($scope.inputs[name]) $scope.inputs[name].model = value;
          };

          $scope.submit = function() {
            $scope.hyperFormLoading = true;
            each(inputs, function(input) {
              if (typeof input.model === 'undefined') return;
              if ($scope.values[input.name]) return;
              $scope.values[input.name] = input.model;
            });
            attrs.hyperAction && value.method === 'GET'
              ? followLink()
              : emitter.submit(value.method, value.action, $scope.values, onfinish);
          };
        });

        function onfinish(err, res) {
          $safeApply.call($scope, function() {
            delete $scope.hyperFormLoading;
            $setPristine(form);
            cb(err, res);
            $scope.values = {};
            if (err) $scope.hyperFormError = err;
            // TODO what are other status that we want to expose?
          });
        }
      }
    };
  }
]);

function $setPristine(form) {
  if (form.$setPristine) return form.$setPristine();
  form.$pristine = true;
  form.$dirty = false;
  each(form, function(input, key) {
    if (input.$pristine) input.$pristine = true;
    if (input.$dirty) input.$dirty = false;
  });
}

function noop() {}

function smerge(a, b) {
  for (var k in b) {
    a[k] = b[k];
  }
  return a;
}
<|endoftext|>


<commit_before>loader.executor.injector = def(
  [
    bolt.kernel,  // FIX
    loader.tag.script
  ],

  function (kernel, script) {
    var execute = function(data, onsuccess, onfailure) {
      var wrappedData = data;

      var inject = function (tag) {
        tag.text = wrappedData;
      };

      var noop = function () {};
      // Injection does not fire events, but execution happens synchronously,
      // so we just make an explicit callback
      script.insert(inject, noop);
      onsuccess();
    };

    return {
      execute: execute
    };
  }
);
<commit_msg>Whitespace.<commit_after>loader.executor.injector = def(
  [
    bolt.kernel,  // FIX
    loader.tag.script
  ],

  function (kernel, script) {
    var execute = function(data, onsuccess, onfailure) {
      var wrappedData = data;

      var inject = function (tag) {
        tag.text = wrappedData;
      };

      var noop = function () {};

      // Injection does not fire events, but execution happens synchronously,
      // so we just make an explicit callback
      script.insert(inject, noop);
      onsuccess();
    };

    return {
      execute: execute
    };
  }
);
<|endoftext|>