<commit_before>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

if (!window['Gaia'])
  var Gaia = {};

(function() {
  var runningApps = [];

  var TaskTray = function(canvas, iconWidth, iconHeight, border) {
    this.canvas = canvas;
    this.iconWidth = iconWidth;
    this.iconHeight = iconHeight;
    this.sceneGraph = new SceneGraph(canvas);
    this.border = border || 0.1;
    this.icons = [];
    this.currentPage = 0;
    this.physics = createPhysicsFor(this);
    this.reflow(canvas.width, canvas.height, 0);

    // Set up event handlers.
    var events = [
      'touchstart', 'touchmove', 'touchend',
      'mousedown', 'mousemove', 'mouseup',
      'contextmenu'
    ];

    events.forEach((function(evt) {
      canvas.addEventListener(evt, this, true);
    }).bind(this));

    window.addEventListener('resize', this, true);
  }

  TaskTray.prototype = {
    add: function(src, label, url) {

      // Create the icon in the tray.
      var icons = this.icons;
      var icon = new Icon(this, icons.length);

      icons.push(icon);

      // Load the image, sprite will be created when image load is complete.
      var img = new Image();
      img.src = src;
      img.label = label;
      img.url = url;
      img.icon = icon;
      img.onload = function() {
        // Update the icon (this will trigger a reflow and a repaint).
        var icon = this.icon;
        icon.update(this, this.label, this.url);
      }

      return icon;
    },

    remove: function(icon) {
      this.icons.splice(icon.index);

      if (icon.sprite)
        sceneGraph.remove(icon.sprite);
    },

    reflow: function(width, height, duration) {
      // Recalculate all the layout information.
      this.containerWidth = width;
      this.containerHeight = height;
      this.panelWidth = this.containerWidth;
      this.pageIndicatorWidth = this.containerWidth;
      this.pageIndicatorHeight = Math.min(Math.max(this.containerHeight * 0.7, 14), 20);
      this.panelHeight = this.containerHeight - this.pageIndicatorHeight;
      this.columns = Math.floor(this.panelWidth / this.iconWidth);
      this.rows = Math.floor(this.panelHeight / this.iconHeight);
      this.itemsPerPage = this.rows * this.columns;
      this.itemBoxWidth = Math.floor(this.panelWidth / this.columns);
      this.itemBoxHeight = Math.floor(this.panelHeight / this.rows);

      // Switch to the right page.
      this.setPage(this.currentPage, duration);

      // Reflow all the icons.
      var icons = this.icons;
      for (var n = 0; n < icons.length; ++n)
        icons[n].reflow(duration);
    },

    getLastPage: function() {
      var itemsPerPage = this.itemsPerPage;
      var lastPage = Math.floor((this.icons.length + (itemsPerPage - 1)) / itemsPerPage);

      if (lastPage > 0)
        --lastPage;

      return lastPage;
    },

    setPage: function(page, duration) {
      page = Math.max(0, page);
      page = Math.min(page, this.getLastPage());
      this.sceneGraph.setViewportTopLeft(this.containerWidth * page, 0, duration);
      this.currentPage = page;
    },

    tap: function(x, y) {
      var screen = document.getElementById('screen');
      var rect = screen.getBoundingClientRect();
      var height = rect.bottom - rect.top - 140;

      this.sceneGraph.forHit(
        x, y - height,
        function(sprite) {
          Gaia.AppManager.closeTaskManager();
          Gaia.AppManager.launch(sprite.icon.url);
        });
    },

    handleEvent: function(e) {
      var physics = this.physics;

      switch (e.type) {
        case 'touchstart':
        case 'mousedown':
          this.canvas.setCapture(false);
          physics.onTouchStart(e.touches ? e.touches[0] : e);
          break;
        case 'touchmove':
        case 'mousemove':
          physics.onTouchMove(e.touches ? e.touches[0] : e);
          break;
        case 'contextmenu':
          var sourceURL = window.document.URL;
          showSourceViewer(sourceURL);
          document.releaseCapture();
          physics.touchState.active = false;
          break;
        case 'touchend':
        case 'mouseup':
          document.releaseCapture();
          physics.onTouchEnd(e.touches ? e.touches[0] : e);
          break;
        case 'resize':
          var canvas = this.canvas;
          var width = canvas.width = window.innerWidth;
          var height = canvas.height = 140;
          this.sceneGraph.blitter.viewportWidth = width;
          this.sceneGraph.blitter.viewportHeight = height;
          this.reflow(width, height, 0);
          break;
        default:
          return;
      }

      e.preventDefault();
    }
  };

  Gaia.AppManager = {
    _foregroundWindows: [],
    _taskTray: null,

    set foregroundWindow(win) {
      this._foregroundWindows.push(win);
    },

    get foregroundWindow() {
      var foregroundWindows = this._foregroundWindows;
      var count = foregroundWindows.length;
      for (var i = count; i > 0; i--) {
        if (foregroundWindows[i - 1].hasAttribute('hidden'))
          foregroundWindows.pop();
      }

      count = foregroundWindows.length;
      if (!count)
        return;
      return foregroundWindows[count - 1];
    },

    get isTaskManagerOpen() {
      return this.screen.classList.contains('animateTaskManagerOpen');
    },

    get screen() {
      delete this.screen;
      return this.screen = document.getElementById('screen');
    },

    get windowsContainer() {
      delete this.windowsContainer;
      return this.windowsContainer = document.getElementById('windows');
    },

    init: function() {
      window.addEventListener('keypress', this);
      window.addEventListener('appclose', this);

      var taskManagerContainer = document.getElementById('taskManager');
      var taskManagerRect = taskManagerContainer.getBoundingClientRect();
      var taskManagerWidth = taskManagerRect.right - taskManagerRect.left;
      var taskManagerHeight = 140;
      var taskTrayCanvas = document.getElementById('taskTrayCanvas');

      taskTrayCanvas.width = taskManagerWidth;
      taskTrayCanvas.height = taskManagerHeight;

      this._taskTray = new TaskTray(taskTrayCanvas, 120, 120, 0.2);
    },

    handleEvent: function(evt) {
      switch (evt.type) {
        case 'keypress':
          if (evt.keyCode == evt.DOM_VK_ESCAPE) {
            // Open/Close TaskManager
            if (this.isTaskManagerOpen) {
              this.closeTaskManager();
            } else {
              this.openTaskManager();
            }
          }
          break;
        case 'appclose':
          this.close();
          break;
        default:
          throw new Error('Unhandled event in AppManager');
          break;
      }
    },

    openTaskManager: function() {
      this.screen.classList.remove('animateTaskManagerClose');
      this.screen.classList.add('animateTaskManagerOpen');
    },

    closeTaskManager: function() {
      this.screen.classList.remove('animateTaskManagerOpen');
      this.screen.classList.add('animateTaskManagerClose');
    },

    getInstalledApps: function() {
      // TODO: Query navigator.mozApps for installed app list.
      return [{
        name: 'Phone',
        icons: {
          size_128: 'images/Phone.png'
        },
        url: 'dialer/dialer.html'
      }, {
        name: 'Messages',
        icons: {
          size_128: 'images/Messages.png'
        },
        url: 'sms/sms.html'
      }, {
        name: 'Contacts',
        icons: {
          size_128: 'images/Contacts.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Video',
        icons: {
          size_128: 'images/Video.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Gallery',
        icons: {
          size_128: 'images/Gallery.png'
        },
        url: 'gallery/gallery.html'
      }, {
        name: 'Camera',
        icons: {
          size_128: 'images/Camera.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Maps',
        icons: {
          size_128: 'images/Maps.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Calculator',
        icons: {
          size_128: 'images/Calculator.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Clock',
        icons: {
          size_128: 'images/Clock.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Browser',
        icons: {
          size_128: 'images/Browser.png'
        },
        url: 'browser/browser.html'
      }, {
        name: 'Music',
        icons: {
          size_128: 'images/Music.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Weather',
        icons: {
          size_128: 'images/Weather.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Settings',
        icons: {
          size_128: 'images/Settings.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Stocks',
        icons: {
          size_128: 'images/Stocks.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Market',
        icons: {
          size_128: 'images/Market.png'
        },
        url: 'data:text/html,Hello'
      }];
    },

    getRunningApps: function() {
      return runningApps;
    },

    getInstalledAppForURL: function(url) {
      var installedApps = this.getInstalledApps();

      for (var i = 0; i < installedApps.length; i++) {
        if (installedApps[i].url === url)
          return installedApps[i];
      }

      return null;
    },

    getAppInstance: function(url) {
      for (var i = 0; i < runningApps.length; i++) {
        if (runningApps[i].url === url)
          return runningApps[i];
      }
      return null;
    },

    launch: function(url) {
      var appInstance = this.getAppInstance(url);

      // App is already running, set focus to the existing instance.
      if (appInstance) {
        var foregroundWindow = this.foregroundWindow = appInstance.window;
        foregroundWindow.removeAttribute('hidden');
        foregroundWindow.contentWindow.Apps.init();
      }

      // App is not yet running, create a new instance.
      else {
        var app = this.getInstalledAppForURL(url);
        var newWindow = document.createElement('iframe');
        var foregroundWindow = this.foregroundWindow = newWindow;
        foregroundWindow.className = 'appWindow';
        foregroundWindow.src = url;

        this.windowsContainer.appendChild(foregroundWindow);

        runningApps.push({
          url: url,
          window: foregroundWindow
        });

        if (app) {
          this._taskTray.add(app.icons.size_128, app.name, app.url);
        }
      }

      var animationCompleteHandler = function() {
        window.removeEventListener('animationend', animationCompleteHandler);

        foregroundWindow.classList.remove('animateOpening');
        foregroundWindow.focus();

        var appOpenEvent = document.createEvent('UIEvents');

        appOpenEvent.initUIEvent('appopen', true, true,
                                 foregroundWindow.contentWindow, 0);
        window.dispatchEvent(appOpenEvent);
      };
      window.addEventListener('animationend', animationCompleteHandler);

      foregroundWindow.classList.add('animateOpening');
      this.windowsContainer.removeAttribute('hidden');

      return foregroundWindow;
    },

    close: function() {
      var foregroundWindow = this.foregroundWindow;
      if (!foregroundWindow)
        return;

      var animationCompleteHandler = (function() {
        window.removeEventListener('animationend', animationCompleteHandler);

        foregroundWindow.classList.remove('animateClosing');
        foregroundWindow.blur();
        foregroundWindow.setAttribute('hidden', true);

        var newForegroundWindow = this.foregroundWindow;
        if (newForegroundWindow)
          newForegroundWindow.focus();
        else
          this.windowsContainer.setAttribute('hidden', true);
      }).bind(this);

      window.addEventListener('animationend', animationCompleteHandler);
      foregroundWindow.contentWindow.Apps.uninit();
      foregroundWindow.classList.add('animateClosing');
    },

    kill: function(url) {
      for (var i = 0; i < runningApps.length; i++) {
        if (runningApps[i].url === url) {
          this.windowsContainer.removeChild(runningApps[i].window);
          runningApps.splice(i, 1);
          break;
        }
      }
    }
  };
})();

<commit_msg>Cleaned up some TaskManager code. Added lazy loading for the TaskTray WebGL context.<commit_after>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

if (!window['Gaia'])
  var Gaia = {};

(function() {
  var runningApps = [];

  var TaskTray = function(canvas, iconWidth, iconHeight, border) {
    this.canvas = canvas;
    this.iconWidth = iconWidth;
    this.iconHeight = iconHeight;
    this.sceneGraph = new SceneGraph(canvas);
    this.border = border || 0.1;
    this.icons = [];
    this.currentPage = 0;
    this.physics = createPhysicsFor(this);
    this.reflow(canvas.width, canvas.height, 0);

    // Set up event handlers.
    var events = [
      'touchstart', 'touchmove', 'touchend',
      'mousedown', 'mousemove', 'mouseup',
    ];

    events.forEach((function(evt) {
      canvas.addEventListener(evt, this, true);
    }).bind(this));

    window.addEventListener('resize', this, true);
  }

  TaskTray.prototype = {
    add: function(src, label, url) {

      // Create the icon in the tray.
      var icons = this.icons;
      var icon = new Icon(this, icons.length);

      icons.push(icon);

      // Load the image, sprite will be created when image load is complete.
      var img = new Image();
      img.src = src;
      img.label = label;
      img.url = url;
      img.icon = icon;
      img.onload = function() {
        // Update the icon (this will trigger a reflow and a repaint).
        var icon = this.icon;
        icon.update(this, this.label, this.url);
      }

      return icon;
    },

    remove: function(icon) {
      this.icons.splice(icon.index);

      if (icon.sprite)
        sceneGraph.remove(icon.sprite);
    },

    reflow: function(width, height, duration) {
      // Recalculate all the layout information.
      this.containerWidth = width;
      this.containerHeight = height;
      this.panelWidth = this.containerWidth;
      this.pageIndicatorWidth = this.containerWidth;
      this.pageIndicatorHeight = Math.min(Math.max(this.containerHeight * 0.7, 14), 20);
      this.panelHeight = this.containerHeight - this.pageIndicatorHeight;
      this.columns = Math.floor(this.panelWidth / this.iconWidth);
      this.rows = Math.floor(this.panelHeight / this.iconHeight);
      this.itemsPerPage = this.rows * this.columns;
      this.itemBoxWidth = Math.floor(this.panelWidth / this.columns);
      this.itemBoxHeight = Math.floor(this.panelHeight / this.rows);

      // Switch to the right page.
      this.setPage(this.currentPage, duration);

      // Reflow all the icons.
      var icons = this.icons;
      for (var n = 0; n < icons.length; ++n)
        icons[n].reflow(duration);
    },

    getLastPage: function() {
      var itemsPerPage = this.itemsPerPage;
      var lastPage = Math.floor((this.icons.length + (itemsPerPage - 1)) / itemsPerPage);

      if (lastPage > 0)
        --lastPage;

      return lastPage;
    },

    setPage: function(page, duration) {
      page = Math.max(0, page);
      page = Math.min(page, this.getLastPage());
      this.sceneGraph.setViewportTopLeft(this.containerWidth * page, 0, duration);
      this.currentPage = page;
    },

    tap: function(x, y) {
      var screen = document.getElementById('screen');
      var rect = screen.getBoundingClientRect();
      var height = rect.bottom - rect.top - 140;

      this.sceneGraph.forHit(
        x, y - height,
        function(sprite) {
          Gaia.AppManager.closeTaskManager();
          Gaia.AppManager.launch(sprite.icon.url);
        });
    },

    handleEvent: function(e) {
      var physics = this.physics;

      switch (e.type) {
        case 'touchstart':
        case 'mousedown':
          this.canvas.setCapture(false);
          physics.onTouchStart(e.touches ? e.touches[0] : e);
          break;
        case 'touchmove':
        case 'mousemove':
          physics.onTouchMove(e.touches ? e.touches[0] : e);
          break;
        case 'touchend':
        case 'mouseup':
          document.releaseCapture();
          physics.onTouchEnd(e.touches ? e.touches[0] : e);
          break;
        case 'resize':
          var canvas = this.canvas;
          var width = canvas.width = window.innerWidth;
          var height = canvas.height = 140;
          this.sceneGraph.blitter.viewportWidth = width;
          this.sceneGraph.blitter.viewportHeight = height;
          this.reflow(width, height, 0);
          break;
        default:
          return;
      }

      e.preventDefault();
    }
  };

  Gaia.AppManager = {
    _foregroundWindows: [],

    set foregroundWindow(win) {
      this._foregroundWindows.push(win);
    },

    get foregroundWindow() {
      var foregroundWindows = this._foregroundWindows;
      var count = foregroundWindows.length;
      for (var i = count; i > 0; i--) {
        if (foregroundWindows[i - 1].hasAttribute('hidden'))
          foregroundWindows.pop();
      }

      count = foregroundWindows.length;
      if (!count)
        return;
      return foregroundWindows[count - 1];
    },

    get isTaskManagerOpen() {
      return this.screen.classList.contains('animateTaskManagerOpen');
    },

    get screen() {
      delete this.screen;
      return this.screen = document.getElementById('screen');
    },

    get taskTray() {
      delete this.taskTray;

      var taskManagerContainer = document.getElementById('taskManager');
      var taskManagerRect = taskManagerContainer.getBoundingClientRect();
      var taskTrayCanvas = document.getElementById('taskTrayCanvas');

      taskTrayCanvas.width = taskManagerRect.width;
      taskTrayCanvas.height = taskManagerRect.height;

      return this.taskTray = new TaskTray(taskTrayCanvas, 120, 120, 0.2);
    },

    get windowsContainer() {
      delete this.windowsContainer;
      return this.windowsContainer = document.getElementById('windows');
    },

    init: function() {
      window.addEventListener('keypress', this);
      window.addEventListener('appclose', this);
    },

    handleEvent: function(evt) {
      switch (evt.type) {
        case 'keypress':
          if (evt.keyCode == evt.DOM_VK_ESCAPE) {
            // Open/Close TaskManager
            if (this.isTaskManagerOpen) {
              this.closeTaskManager();
            } else {
              this.openTaskManager();
            }
          }
          break;
        case 'appclose':
          this.close();
          break;
        default:
          throw new Error('Unhandled event in AppManager');
          break;
      }
    },

    openTaskManager: function() {
      var screenClassList = this.screen.classList;
      screenClassList.remove('animateTaskManagerClose');
      screenClassList.add('animateTaskManagerOpen');
    },

    closeTaskManager: function() {
      var screenClassList = this.screen.classList;
      screenClassList.remove('animateTaskManagerOpen');
      screenClassList.add('animateTaskManagerClose');
    },

    getInstalledApps: function() {
      // TODO: Query navigator.mozApps for installed app list.
      return [{
        name: 'Phone',
        icons: {
          size_128: 'images/Phone.png'
        },
        url: 'dialer/dialer.html'
      }, {
        name: 'Messages',
        icons: {
          size_128: 'images/Messages.png'
        },
        url: 'sms/sms.html'
      }, {
        name: 'Contacts',
        icons: {
          size_128: 'images/Contacts.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Video',
        icons: {
          size_128: 'images/Video.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Gallery',
        icons: {
          size_128: 'images/Gallery.png'
        },
        url: 'gallery/gallery.html'
      }, {
        name: 'Camera',
        icons: {
          size_128: 'images/Camera.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Maps',
        icons: {
          size_128: 'images/Maps.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Calculator',
        icons: {
          size_128: 'images/Calculator.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Clock',
        icons: {
          size_128: 'images/Clock.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Browser',
        icons: {
          size_128: 'images/Browser.png'
        },
        url: 'browser/browser.html'
      }, {
        name: 'Music',
        icons: {
          size_128: 'images/Music.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Weather',
        icons: {
          size_128: 'images/Weather.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Settings',
        icons: {
          size_128: 'images/Settings.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Stocks',
        icons: {
          size_128: 'images/Stocks.png'
        },
        url: 'data:text/html,Hello'
      }, {
        name: 'Market',
        icons: {
          size_128: 'images/Market.png'
        },
        url: 'data:text/html,Hello'
      }];
    },

    getRunningApps: function() {
      return runningApps;
    },

    getInstalledAppForURL: function(url) {
      var installedApps = this.getInstalledApps();

      for (var i = 0; i < installedApps.length; i++) {
        if (installedApps[i].url === url)
          return installedApps[i];
      }

      return null;
    },

    getAppInstance: function(url) {
      for (var i = 0; i < runningApps.length; i++) {
        if (runningApps[i].url === url)
          return runningApps[i];
      }
      return null;
    },

    launch: function(url) {
      var appInstance = this.getAppInstance(url);

      // App is already running, set focus to the existing instance.
      if (appInstance) {
        var foregroundWindow = this.foregroundWindow = appInstance.window;
        foregroundWindow.removeAttribute('hidden');
        foregroundWindow.contentWindow.Apps.init();
      }

      // App is not yet running, create a new instance.
      else {
        var app = this.getInstalledAppForURL(url);
        var newWindow = document.createElement('iframe');
        var foregroundWindow = this.foregroundWindow = newWindow;
        foregroundWindow.className = 'appWindow';
        foregroundWindow.src = url;

        this.windowsContainer.appendChild(foregroundWindow);

        runningApps.push({
          url: url,
          window: foregroundWindow
        });

        if (app) {
          this.taskTray.add(app.icons.size_128, app.name, app.url);
        }
      }

      var animationCompleteHandler = function() {
        window.removeEventListener('animationend', animationCompleteHandler);

        foregroundWindow.classList.remove('animateOpening');
        foregroundWindow.focus();

        var appOpenEvent = document.createEvent('UIEvents');

        appOpenEvent.initUIEvent('appopen', true, true,
                                 foregroundWindow.contentWindow, 0);
        window.dispatchEvent(appOpenEvent);
      };
      window.addEventListener('animationend', animationCompleteHandler);

      foregroundWindow.classList.add('animateOpening');
      this.windowsContainer.removeAttribute('hidden');

      return foregroundWindow;
    },

    close: function() {
      var foregroundWindow = this.foregroundWindow;
      if (!foregroundWindow)
        return;

      var animationCompleteHandler = (function() {
        window.removeEventListener('animationend', animationCompleteHandler);

        foregroundWindow.classList.remove('animateClosing');
        foregroundWindow.blur();
        foregroundWindow.setAttribute('hidden', true);

        var newForegroundWindow = this.foregroundWindow;
        if (newForegroundWindow)
          newForegroundWindow.focus();
        else
          this.windowsContainer.setAttribute('hidden', true);
      }).bind(this);

      window.addEventListener('animationend', animationCompleteHandler);
      foregroundWindow.contentWindow.Apps.uninit();
      foregroundWindow.classList.add('animateClosing');
    },

    kill: function(url) {
      for (var i = 0; i < runningApps.length; i++) {
        if (runningApps[i].url === url) {
          this.windowsContainer.removeChild(runningApps[i].window);
          runningApps.splice(i, 1);
          break;
        }
      }
    }
  };
})();

<|endoftext|>


<commit_before>/*
* @Author: Manraj Singh
* @Date:   2016-08-13 20:28:25
* @Last Modified by:   Manraj Singh
* @Last Modified time: 2016-08-16 21:00:35
*/

'use strict';

const mv = require('mv');
const fs = require('fs');
const path = require('path');

const mkdir = (path) => {
  try {
    fs.mkdirSync(path);
  } catch (err) {
    if (err.code != 'EEXIST') {
      throw new Error("Error occurred while creating a new directory");
    }
  }
}

const helpers = {
  getExtension: (fileName) => {
    let i = fileName.lastIndexOf('.');
    return (i < 0) ? '' : fileName.substr(i + 1);
  },
  getFileNames: (directory) => {
    return fs.readdirSync(directory);
  },
  organize: (directory, fileName, type) => {
    let dir = path.resolve(directory, 'Organize_' + type);
    mkdir(dir);
    mv(path.resolve(directory, fileName), path.resolve(dir, fileName), function (err) {
      if (err) {
        throw new Error("Couldn't move " + fileName + " because of following error: " + err);
      }
    });
  }
};

module.exports = helpers;
<commit_msg>Fixes prefix issue<commit_after>/*
* @Author: Manraj Singh
* @Date:   2016-08-13 20:28:25
* @Last Modified by:   Manraj Singh
* @Last Modified time: 2016-08-16 21:02:42
*/

'use strict';

const mv = require('mv');
const fs = require('fs');
const path = require('path');

const mkdir = (path) => {
  try {
    fs.mkdirSync(path);
  } catch (err) {
    if (err.code != 'EEXIST') {
      throw new Error("Error occurred while creating a new directory");
    }
  }
}

const helpers = {
  getExtension: (fileName) => {
    let i = fileName.lastIndexOf('.');
    return (i < 0) ? '' : fileName.substr(i + 1);
  },
  getFileNames: (directory) => {
    return fs.readdirSync(directory);
  },
  organize: (directory, fileName, type) => {
    let dir = path.resolve(directory, type);
    mkdir(dir);
    mv(path.resolve(directory, fileName), path.resolve(dir, fileName), function (err) {
      if (err) {
        throw new Error("Couldn't move " + fileName + " because of following error: " + err);
      }
    });
  }
};

module.exports = helpers;
<|endoftext|>


<commit_before>"use strict";

var deepEqual = require("deep-equal");

module.exports = {

    /**
     * Extend the object with the members of provided dictionaries.
     *
     * @param {Object} obj receiving object
     * @param {Object} ..members additional members dictionaries
     * @returns {Object} the object
     */
    extend: function (obj) {
        [].slice.call(arguments, 1).forEach(function (members) {
            Object.keys(members).forEach(function (name) {
                obj[name] = members[name];
            });
        });
        return obj;
    },

    /**
     * Deep compare objects.
     *
     * @param {Object} obj1
     * @param {Object} obj2
     * @returns {Boolean} The objects are equal
     */
    equal: function (obj1, obj2) {
        return deepEqual(obj1, obj2, {
            strict: true
        });
    },

    /**
     * Used for CSV import: process columns definition
     *
     * @param {String[]} defaultColumns
     * returns {Function}
     */
    getColumnsHandler: function (defaultColumns) {
        if (defaultColumns) {
            return function () {
                return defaultColumns;
            };
        }
        return function (row) {
            while (!row[row.length - 1]) {
                row.pop(); // Removes empty columns
            }
            return row;
        };
    },

    /**
     * Used for CSV import: build new columns on the record by applying mapping dictionary
     *
     * @param {Object} record Record to update
     * @param {Object} mapping Field definition dictionary
     * @returns {Object} record
     */
    mapColumns: function (record, mapping) {
        Object.keys(mapping).forEach(function (key) {
            var value = mapping[key];
            Object.keys(record).forEach(function (member) {
                var search = "{" + member + "}";
                if (-1 < value.indexOf(search)) {
                    value = value.split(search).join(record[member]);
                }
            });
            record[key] = value;
        });
    },

    /**
     * Used for CSV records: convert fields based on types definition
     *
     * @param {Object} record Record to convert
     * @param {Object} types Types definition dictionary, supported types are:
     * - boolean
     * - number
     * @returns {Object} record
     */
    convertColumns: function (record, types) {
        Object.keys(types).forEach(function (fieldName) {
            var type = types[fieldName],
                value = record[fieldName];
            if ("boolean" === type) {
                value = "true" === value;
            } else if ("number" === type) {
                value = parseFloat(value);
            }
            record[fieldName] = value;
        });
    },

    /**
     * Gets a formatted timestamp
     *
     * @returns {String}
     */
    getTimestamp: function () {
        var now = new Date();
        function z (value) {
            if (value < 10) {
                return "0" + value;
            }
            return value.toString();
        }
        return [now.getFullYear(), z(now.getMonth() + 1), z(now.getDate())].join("-");
    }

};
<commit_msg>(de)serialize columns<commit_after>"use strict";

var deepEqual = require("deep-equal");

module.exports = {

    /**
     * Extend the object with the members of provided dictionaries.
     *
     * @param {Object} obj receiving object
     * @param {Object} ..members additional members dictionaries
     * @returns {Object} the object
     */
    extend: function (obj) {
        [].slice.call(arguments, 1).forEach(function (members) {
            Object.keys(members).forEach(function (name) {
                obj[name] = members[name];
            });
        });
        return obj;
    },

    /**
     * Deep compare objects.
     *
     * @param {Object} obj1
     * @param {Object} obj2
     * @returns {Boolean} The objects are equal
     */
    equal: function (obj1, obj2) {
        return deepEqual(obj1, obj2, {
            strict: true
        });
    },

    /**
     * Used for CSV import: process columns definition
     *
     * @param {String[]} defaultColumns
     * returns {Function}
     */
    getColumnsHandler: function (defaultColumns) {
        if (defaultColumns) {
            return function () {
                return defaultColumns;
            };
        }
        return function (row) {
            while (!row[row.length - 1]) {
                row.pop(); // Removes empty columns
            }
            return row;
        };
    },

    /**
     * Used for CSV import: build new columns on the record by applying mapping dictionary
     *
     * @param {Object} record Record to update
     * @param {Object} mapping Field definition dictionary
     * @returns {Object} record
     */
    mapColumns: function (record, mapping) {
        Object.keys(mapping).forEach(function (key) {
            var value = mapping[key];
            Object.keys(record).forEach(function (member) {
                var search = "{" + member + "}";
                if (-1 < value.indexOf(search)) {
                    value = value.split(search).join(record[member]);
                }
            });
            record[key] = value;
        });
    },

    /**
     * Used to read CSV records: adapt CSV fields based on types definition
     *
     * @param {Object} record Record read from CSV
     * @param {Object} types Types definition dictionary, supported types are:
     * - boolean
     * - number
     * @returns {Object} record
     */
    deserializeColumns: function (record, types) {
        Object.keys(types).forEach(function (fieldName) {
            var type = types[fieldName],
                value = record[fieldName];
            if ("boolean" === type) {
                value = "true" === value;
            } else if ("number" === type) {
                value = parseFloat(value);
            }
            record[fieldName] = value;
        });
    },

    /**
     * Used to write CSV records: adapt CSV fields based on types definition
     *
     * @param {Object} record Record to write to CSV
     * @param {Object} types Types definition dictionary, supported types are:
     * - boolean
     * - number
     * @returns {Object} record
     */
    serializeColumns: function (record, types) {
        Object.keys(types).forEach(function (fieldName) {
            var value = record[fieldName];
            record[fieldName] = value.toString();
        });
    },

    /**
     * Gets a formatted timestamp
     *
     * @returns {String}
     */
    getTimestamp: function () {
        var now = new Date();
        function z (value) {
            if (value < 10) {
                return "0" + value;
            }
            return value.toString();
        }
        return [now.getFullYear(), z(now.getMonth() + 1), z(now.getDate())].join("-");
    }

};
<|endoftext|>


<commit_before>module('users.timfelgentreff.experimental.assert').requires('users.timfelgentreff.babelsberg.constraintinterpreter').toRun(function() {

	JSLoader.loadJs(module('users.timfelgentreff.csp.underscore-min').uri());

	/***************************************************************
	 * Continuous asserts
	 ***************************************************************/

	// TODO: improve syntax of bbb.assert
	// TODO: multiple asserts and multiple variable interconnected
	// TODO: enable and disable AssertConstraints
	// TODO: integration with other solvers

	Error.subclass("ContinuousAssertError", {
		initialize: function ContinuousAssertError(msg) {
			if (Error.captureStackTrace) {
				Error.captureStackTrace(this, ContinuousAssertError);
			} else {
				this.stack = (new Error).stack || '';
			}
			this.message = msg;
		},
		name: "ContinuousAssertError"
	});

	Object.subclass("AssertSolver", {
		initialize: function(message) {
			this.message = message;
		},
	    isConstraintObject: function() { return true; },
	    always: function(opts, func) {
	    	//console.log("AssertSolver.always");
	        var ctx = opts.ctx;
	        func.varMapping = ctx;
	        var cobj = new Constraint(func, this);
	        cobj.addPrimitiveConstraint(new AssertSolver.Constraint(this, cobj));
			try {
				if(!opts.postponeEnabling) { cobj.enable(); }
			} catch(e) {
				if(e instanceof ContinuousAssertError) {
					cobj.disable();
				}
				throw e;
			}
	        return cobj;
	    },
	    constraintVariableFor: function(value, ivarname, bbbCVar) {
	    	//console.log("AssertSolver.constraintVariableFor", value, ivarname, bbbCVar);
	    	return new AssertSolver.Variable(this, value, ivarname, bbbCVar);
	    },
	    solve: function() {
	    	//console.log("AssertSolver.solve");
	    	this.check();
	    },
	    check: function() {
	    	//console.log("AssertSolver.check");
	    	if(this.constraint && this.constraint.enabled && typeof this.constraint.predicate === "function")
	    		if(!this.constraint.predicate())
	    			throw new ContinuousAssertError(this.message);
	    },
	    weight: 10
	});

	Object.subclass("AssertSolver.Variable", {
		initialize: function(solver, value, ivarname, bbbCVar) {
	    	//console.log("AssertSolver.Variable.initialize");
			this.solver = solver;
			this.__val__ = value;
		},
	    isConstraintObject: function() { return true; },
	    toFulfill: function(assertPredicate) {
	    	//console.log("AssertSolver.Variable.toFulfill", assertPredicate);
	    	this.assertPredicate = assertPredicate;
	    },
	    suggestValue: function(value) {
	    	//console.log("AssertSolver.Variable.suggestValue", value);
			var prev = this.__val__;
	    	this.__val__ = value;
			try {
				this.solver.solve();
			} catch(e) {
				// revert value in case of a violated assertion
				if(e instanceof ContinuousAssertError) {
					this.__val__ = prev;
				}
				throw e;
			}
	    	return this.__val__;
	    },
	    value: function() {
	    	return this.__val__;
	    },
	    setReadonly: function(bool) {
	    	//console.log("AssertSolver.Variable.setReadonly", bool);
	    	this.readonly = bool;
	    },
	    isReadonly: function() {
	    	//console.log("AssertSolver.Variable.isReadonly");
	        return this.readonly;
	    },
	    cnEquals: function() {
	    	//console.log("AssertSolver.Variable.cnEquals");
	        return new AssertSolver.PrimitiveConstraint(this, arguments);
	    }
	});

	Object.subclass("AssertSolver.PrimitiveConstraint", {
	    isConstraintObject: function() { return true; },
	    initialize: function(variable, args) {
	    	//console.log("AssertSolver.PrimitiveConstraint.initialize", variable, args);
			this.enabled = false;
	    	this.solver = variable.solver;
	    },
	    enable: function() {
	    	//console.log("AssertSolver.PrimitiveConstraint.enable");
			this.enabled = true;
	    },
	    disable: function() {
	    	//console.log("AssertSolver.PrimitiveConstraint.disable");
			this.enabled = false;
	    }
	});

	Object.subclass("AssertSolver.Constraint", {
	    isConstraintObject: function() { return true; },
	    initialize: function(solver, bbbConstraint) {
	    	//console.log("AssertSolver.Constraint.initialize");
			this.enabled = false;
	    	this.solver = solver;
	    	this.solver.constraint = this;

	    	// extract predicate from bbbConstraint
	    	this.predicate = _.chain(bbbConstraint.constraintvariables)
	    		.map(function(variable) {
	    			return variable.externalVariables(this.solver);
	    		}, this)
	    		.find(function(externalVariable) {
	    			return typeof externalVariable.assertPredicate === "function";
	    		})
	    		.value()
	    		.assertPredicate;
	    },
	    enable: function() {
	    	//console.log("AssertSolver.Constraint.enable");
			this.enabled = true;
	    },
	    disable: function() {
	    	//console.log("AssertSolver.Constraint.disable");
			this.enabled = false;
	    }
	});

	Object.extend(Babelsberg.prototype, {
		assert: function(opts, func) {
			opts.solver = new AssertSolver(opts.message);
			opts.allowUnsolvableOperations = true;
			opts.allowTests = true;
			//opts.debugging = true;
	        return this.always(opts, func);
		}
	});

	/***************************************************************
	 * Triggering
	 ***************************************************************/
	// TODO: queue triggered actions and process list after assignments are done

	AssertSolver.subclass("TriggerSolver", {
		initialize: function(callback) {
			this.callback = callback;
			this.triggeredOnce = false;
		},
	    always: function(opts, func) {
	    	//console.log("TriggerSolver.always");
	        var ctx = opts.ctx;
	        func.varMapping = ctx;
	        var cobj = new Constraint(func, this);
	        cobj.addPrimitiveConstraint(new AssertSolver.Constraint(this, cobj));
			if(!opts.postponeEnabling) { cobj.enable(); }
	        return cobj;
	    },
	    solve: function() {
	    	//console.log("TriggerSolver.solve", this.constraint.predicate());
	    	if(this.constraint &&
				this.constraint.enabled &&
				typeof this.constraint.predicate === "function" &&
				!this.triggeredOnce
			) {
	    		if(this.constraint.predicate()) {
					this.triggeredOnce = true;
					this.constraint.disable();
	    			this.callback();
				} else {
					// resetOnFalse?
				}
			}
	    },
	    weight: 10
	});

	Object.extend(Babelsberg.prototype, {
		trigger: function(opts, func) {
			opts.solver = new TriggerSolver(opts.callback);
			opts.allowUnsolvableOperations = true;
			opts.allowTests = true;
			//opts.debugging = true;
	        return this.always(opts, func);
		}
	});

	/***************************************************************
	 * Layer activation
	 ***************************************************************/
	AssertSolver.subclass("LayerActivationSolver", {
		initialize: function(layer) {
			this.layer = layer;
		},
	    always: function(opts, func) {
	    	//console.log("LayerActivationSolver.always");
	        var ctx = opts.ctx;
	        func.varMapping = ctx;
	        var cobj = new Constraint(func, this);
	        cobj.addPrimitiveConstraint(new AssertSolver.Constraint(this, cobj));
			cobj.enable();
	        return cobj;
	    },
	    solve: function() {
	    	//console.log("LayerActivationSolver.solve", this.constraint.predicate());
	    	if(this.constraint &&
				this.constraint.enabled &&
				typeof this.constraint.predicate === "function"
			) {
				var predicateFulfilled = this.constraint.predicate();
	    		if(predicateFulfilled && !this.layer.isGlobal()) {
	    			this.layer.beGlobal();
				} else  if(!predicateFulfilled && this.layer.isGlobal()) {
	    			this.layer.beNotGlobal();
				}
			}
	    },
	    weight: 10
	});

	// hotfix
	// instead of cop.GlobalLayers.removeAt(idx); just use standard conform splice method
	Object.extend(cop, {
		disableLayer: function(layer) {
			var idx = cop.GlobalLayers.indexOf(layer)
			if (idx < 0) return;
			cop.GlobalLayers.splice(idx, 1);
			cop.invalidateLayerComposition();
		},
	});

	Object.extend(Layer.prototype, {
		activeOn: function(opts, func) {
			opts.solver = new LayerActivationSolver(this);
			opts.allowUnsolvableOperations = true;
			opts.allowTests = true;
			//opts.debugging = true;
	        opts.solver.always(opts, func);

			return this;
		}
	});

	/***************************************************************
	 * Scoped constraints
	 ***************************************************************/
	Object.extend(Layer.prototype, {
		always: function(opts, func) {
			opts.postponeEnabling = !this.isGlobal();
			var cobj = bbb.always(opts, func);

			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.push(cobj);
		},
		assert: function(opts, func) {
			opts.postponeEnabling = !this.isGlobal();
			var cobj = bbb.assert(opts, func);

			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.push(cobj);
		},
		trigger: function(opts, func) {
			opts.postponeEnabling = !this.isGlobal();
			var cobj = bbb.trigger(opts, func);

			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.push(cobj);
		},
		_activate: function() {
			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.forEach(function(cobj) {
				cobj.enable();
			});
		},
		_deactivate: function() {
			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.forEach(function(cobj) {
				cobj.disable();
			});
		}
	});

	var copWithLayers = cop.withLayers;
	var copWithoutLayers = cop.withoutLayers;
	var copEnableLayer = cop.enableLayer;
	var copDisableLayer = cop.disableLayer;
	Object.extend(cop, {
		/* Layer Activation */
		withLayers: function withLayers(layers, func) {
			layers.forEach(function(layer) { layer._activate(); });

			try {
				return copWithLayers.apply(this, arguments);
			} finally {
				layers.forEach(function(layer) { layer._deactivate(); });
			}
		},

		withoutLayers: function withoutLayers(layers, func) {
			layers.forEach(function(layer) { layer._deactivate(); });

			try {
				return copWithoutLayers.apply(this, arguments);
			} finally {
				layers.forEach(function(layer) { layer._activate(); });
			}
		},

		/* Global Layer Activation */
		enableLayer: function(layer) {
			layer._activate();
			return copEnableLayer.apply(this, arguments);
		},

		disableLayer: function(layer) {
			layer._deactivate();
			return copDisableLayer.apply(this, arguments);
		}
	});

});
<commit_msg>set new flag allowFailing and attach test function to external constraint<commit_after>module('users.timfelgentreff.experimental.assert').requires('users.timfelgentreff.babelsberg.constraintinterpreter').toRun(function() {

	JSLoader.loadJs(module('users.timfelgentreff.csp.underscore-min').uri());

	/***************************************************************
	 * Continuous asserts
	 ***************************************************************/

	// TODO: improve syntax of bbb.assert
	// TODO: multiple asserts and multiple variable interconnected
	// TODO: enable and disable AssertConstraints
	// TODO: integration with other solvers

	Error.subclass("ContinuousAssertError", {
		initialize: function ContinuousAssertError(msg) {
			if (Error.captureStackTrace) {
				Error.captureStackTrace(this, ContinuousAssertError);
			} else {
				this.stack = (new Error).stack || '';
			}
			this.message = msg;
		},
		name: "ContinuousAssertError"
	});

	Object.subclass("AssertSolver", {
		initialize: function(message) {
			this.message = message;
		},
	    isConstraintObject: function() { return true; },
	    always: function(opts, func) {
	    	//console.log("AssertSolver.always");
	        var ctx = opts.ctx;
	        func.varMapping = ctx;
	        var cobj = new Constraint(func, this);
			cobj.allowFailing = true;
	        cobj.addPrimitiveConstraint(new AssertSolver.Constraint(this, cobj, func));
			try {
				if(!opts.postponeEnabling) { cobj.enable(); }
			} catch(e) {
				if(e instanceof ContinuousAssertError) {
					cobj.disable();
				}
				throw e;
			}
	        return cobj;
	    },
	    constraintVariableFor: function(value, ivarname, bbbCVar) {
	    	//console.log("AssertSolver.constraintVariableFor", value, ivarname, bbbCVar);
	    	return new AssertSolver.Variable(this, value, ivarname, bbbCVar);
	    },
	    solve: function() {
	    	//console.log("AssertSolver.solve");
	    	this.check();
	    },
	    check: function() {
	    	//console.log("AssertSolver.check");
	    	if(this.constraint && this.constraint.enabled && typeof this.constraint.predicate === "function")
	    		if(!this.constraint.predicate())
	    			throw new ContinuousAssertError(this.message);
	    },
	    weight: 10
	});

	Object.subclass("AssertSolver.Variable", {
		initialize: function(solver, value, ivarname, bbbCVar) {
	    	//console.log("AssertSolver.Variable.initialize");
			this.solver = solver;
			this.__val__ = value;
		},
	    isConstraintObject: function() { return true; },
	    toFulfill: function(assertPredicate) {
	    	//console.log("AssertSolver.Variable.toFulfill", assertPredicate);
	    	this.assertPredicate = assertPredicate;
	    },
	    suggestValue: function(value) {
	    	//console.log("AssertSolver.Variable.suggestValue", value);
			var prev = this.__val__;
	    	this.__val__ = value;
			try {
				this.solver.solve();
			} catch(e) {
				// revert value in case of a violated assertion
				if(e instanceof ContinuousAssertError) {
					this.__val__ = prev;
				}
				throw e;
			}
	    	return this.__val__;
	    },
	    value: function() {
	    	return this.__val__;
	    },
	    setReadonly: function(bool) {
	    	//console.log("AssertSolver.Variable.setReadonly", bool);
	    	this.readonly = bool;
	    },
	    isReadonly: function() {
	    	//console.log("AssertSolver.Variable.isReadonly");
	        return this.readonly;
	    },
	    cnEquals: function() {
	    	//console.log("AssertSolver.Variable.cnEquals");
	        return new AssertSolver.PrimitiveConstraint(this, arguments);
	    }
	});

	Object.subclass("AssertSolver.PrimitiveConstraint", {
	    isConstraintObject: function() { return true; },
	    initialize: function(variable, args) {
	    	//console.log("AssertSolver.PrimitiveConstraint.initialize", variable, args);
			this.enabled = false;
	    	this.solver = variable.solver;
	    },
	    enable: function() {
	    	//console.log("AssertSolver.PrimitiveConstraint.enable");
			this.enabled = true;
	    },
	    disable: function() {
	    	//console.log("AssertSolver.PrimitiveConstraint.disable");
			this.enabled = false;
	    }
	});

	Object.subclass("AssertSolver.Constraint", {
	    isConstraintObject: function() { return true; },
	    initialize: function(solver, bbbConstraint, func) {
	    	//console.log("AssertSolver.Constraint.initialize");
			this.enabled = false;
	    	this.solver = solver;
	    	this.solver.constraint = this;

	    	// extract predicate from bbbConstraint
	    	this.predicate = func;/*_.chain(bbbConstraint.constraintvariables)
	    		.map(function(variable) {
	    			return variable.externalVariables(this.solver);
	    		}, this)
	    		.find(function(externalVariable) {
	    			return typeof externalVariable.assertPredicate === "function";
	    		})
	    		.value()
	    		//.assertPredicate;*/
	    },
	    enable: function() {
	    	//console.log("AssertSolver.Constraint.enable");
			this.enabled = true;
	    },
	    disable: function() {
	    	//console.log("AssertSolver.Constraint.disable");
			this.enabled = false;
	    }
	});

	Object.extend(Babelsberg.prototype, {
		assert: function(opts, func) {
			opts.solver = new AssertSolver(opts.message);
			opts.allowUnsolvableOperations = true;
			opts.allowTests = true;
			//opts.debugging = true;
	        return this.always(opts, func);
		}
	});

	/***************************************************************
	 * Triggering
	 ***************************************************************/
	// TODO: queue triggered actions and process list after assignments are done

	AssertSolver.subclass("TriggerSolver", {
		initialize: function(callback) {
			this.callback = callback;
			this.triggeredOnce = false;
		},
	    always: function(opts, func) {
	    	//console.log("TriggerSolver.always");
	        var ctx = opts.ctx;
	        func.varMapping = ctx;
	        var cobj = new Constraint(func, this);
			cobj.allowFailing = true;
	        cobj.addPrimitiveConstraint(new AssertSolver.Constraint(this, cobj, func));
			if(!opts.postponeEnabling) { cobj.enable(); }
	        return cobj;
	    },
	    solve: function() {
	    	//console.log("TriggerSolver.solve", this.constraint.predicate());
	    	if(this.constraint &&
				this.constraint.enabled &&
				typeof this.constraint.predicate === "function" &&
				!this.triggeredOnce
			) {
	    		if(this.constraint.predicate()) {
					this.triggeredOnce = true;
					this.constraint.disable();
	    			this.callback();
				} else {
					// resetOnFalse?
				}
			}
	    },
	    weight: 10
	});

	Object.extend(Babelsberg.prototype, {
		trigger: function(opts, func) {
			opts.solver = new TriggerSolver(opts.callback);
			opts.allowUnsolvableOperations = true;
			opts.allowTests = true;
			//opts.debugging = true;
	        return this.always(opts, func);
		}
	});

	/***************************************************************
	 * Layer activation
	 ***************************************************************/
	AssertSolver.subclass("LayerActivationSolver", {
		initialize: function(layer) {
			this.layer = layer;
		},
	    always: function(opts, func) {
	    	//console.log("LayerActivationSolver.always");
	        var ctx = opts.ctx;
	        func.varMapping = ctx;
	        var cobj = new Constraint(func, this);
			cobj.allowFailing = true;
	        cobj.addPrimitiveConstraint(new AssertSolver.Constraint(this, cobj, func));
			cobj.enable();
	        return cobj;
	    },
	    solve: function() {
	    	//console.log("LayerActivationSolver.solve", this.constraint.predicate());
	    	if(this.constraint &&
				this.constraint.enabled &&
				typeof this.constraint.predicate === "function"
			) {
				var predicateFulfilled = this.constraint.predicate();
	    		if(predicateFulfilled && !this.layer.isGlobal()) {
	    			this.layer.beGlobal();
				} else  if(!predicateFulfilled && this.layer.isGlobal()) {
	    			this.layer.beNotGlobal();
				}
			}
	    },
	    weight: 10
	});

	// hotfix
	// instead of cop.GlobalLayers.removeAt(idx); just use standard conform splice method
	Object.extend(cop, {
		disableLayer: function(layer) {
			var idx = cop.GlobalLayers.indexOf(layer)
			if (idx < 0) return;
			cop.GlobalLayers.splice(idx, 1);
			cop.invalidateLayerComposition();
		},
	});

	Object.extend(Layer.prototype, {
		activeOn: function(opts, func) {
			opts.solver = new LayerActivationSolver(this);
			opts.allowUnsolvableOperations = true;
			opts.allowTests = true;
			//opts.debugging = true;
	        bbb.always(opts, func);

			return this;
		}
	});

	/***************************************************************
	 * Scoped constraints
	 ***************************************************************/
	Object.extend(Layer.prototype, {
		always: function(opts, func) {
			opts.postponeEnabling = !this.isGlobal();
			var cobj = bbb.always(opts, func);

			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.push(cobj);
		},
		assert: function(opts, func) {
			opts.postponeEnabling = !this.isGlobal();
			var cobj = bbb.assert(opts, func);

			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.push(cobj);
		},
		trigger: function(opts, func) {
			opts.postponeEnabling = !this.isGlobal();
			var cobj = bbb.trigger(opts, func);

			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.push(cobj);
		},
		_activate: function() {
			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.forEach(function(cobj) {
				cobj.enable();
			});
		},
		_deactivate: function() {
			this.constraintObjects = this.constraintObjects || [];
			this.constraintObjects.forEach(function(cobj) {
				cobj.disable();
			});
		}
	});

	var copWithLayers = cop.withLayers;
	var copWithoutLayers = cop.withoutLayers;
	var copEnableLayer = cop.enableLayer;
	var copDisableLayer = cop.disableLayer;
	Object.extend(cop, {
		/* Layer Activation */
		withLayers: function withLayers(layers, func) {
			layers.forEach(function(layer) { layer._activate(); });

			try {
				return copWithLayers.apply(this, arguments);
			} finally {
				layers.forEach(function(layer) { layer._deactivate(); });
			}
		},

		withoutLayers: function withoutLayers(layers, func) {
			layers.forEach(function(layer) { layer._deactivate(); });

			try {
				return copWithoutLayers.apply(this, arguments);
			} finally {
				layers.forEach(function(layer) { layer._activate(); });
			}
		},

		/* Global Layer Activation */
		enableLayer: function(layer) {
			layer._activate();
			return copEnableLayer.apply(this, arguments);
		},

		disableLayer: function(layer) {
			layer._deactivate();
			return copDisableLayer.apply(this, arguments);
		}
	});

});
<|endoftext|>


<commit_before>
var _  = require('lodash');
var jf = require('jsonfile');
var fx = require('money');

var Promise = require('bluebird');
var request = Promise.promisify(require('request'));
var config = require('./config.js');

var baseCurrency = 'USD';

var ratesPromise = request('http://openexchangerates.org/api/latest.json?app_id=' + config.rates.app_id, {json:true}).spread(function(request, data){
	_.extend(fx, data);
	// console.log('testväxlar: ', fx.convert(1000, {from : "USD", to: "EUR"}));

	// console.log('got data: ', data);
});

var trader = require('./trader.js');

/*
    Two functions to take into account fees.
*/
function buying_cost(amount, fee){
    return amount * (1 + fee);
}
function selling_rev(amount, fee){
    return amount * (1 - fee);
}

/*
    Filter function that basically returns if two spreads have a profitable
    arbitrage oportunity.
*/
function arbitrage_possibilities(from, to){
    if(Array.isArray(from)){ // handle pairform as well
        to = from[1];
        from = from[0];
    }

//    console.log(from.exchange, from.currency, to.exchange, to.currency);
    // console.log(from, to);
    // buying in from market, selling in to-market
    // console.log(from.exchange, 'asks: ', from.asks.length, to.exchange, 'bids: ', to.bids.length, 'to fee', to.fee, 'from fee', from.fee);
    var lowest_ask = fx.convert(from.asks[0][0], {
    	from: from.currency,
    	to : baseCurrency
    });
    var highest_bid = fx.convert(to.bids[0][0], {
    	from : to.currency,
    	to: baseCurrency
    });

    return(selling_rev(highest_bid, to.fee) > buying_cost(lowest_ask, from.fee) * 1); // add 1% requirement margin
}

/*
    Compares a given spread data for an exchange with all other exchanges spread data
    stored, returns array of which spreads an arbitrage oportunity exists between.
*/
function detectCrossCurrencyArbitrageForSpread(spread){
    // var arbitragePossibilities = [];
    var spreadPairs = [];

	var allSpreads = [];

	_.forOwn(trader.RawSpreads, function(currencyIndex) {
		_.forOwn(currencyIndex, function(oneSpread){
			allSpreads.push(oneSpread);
		});
	});

    allSpreads.forEach(function(otherExchange){
    	if(_.isEqual(spread, otherExchange)){return;} // don't compare with self.

        spreadPairs.push([spread, otherExchange]);
        spreadPairs.push([otherExchange, spread]);
        console.log('adding pair: ', spread.exchange, otherExchange.exchange, 'fees: ', spread.fee, otherExchange.fee);
    });

    var arbitragePossibilities = _.filter(spreadPairs, arbitrage_possibilities);

    if(arbitragePossibilities.length > 0){
        arbitragePossibilities.forEach(processArbitrage);
    } else {
        console.log('no arbitrage possible on ' + spread.exchange);
    }

    return arbitragePossibilities;
}

var combinedSpreads = {
	bids : [],
	asks : []
};

/**
* The merge part of the merge sort algorithm.
*
* @param {function} comparatorFn The comparator function.
* @param {array} arr1 The first sorted array.
* @param {array} arr2 The second sorted array.
* @returns {array} The merged and sorted array.
*/
function merge(comparatorFn, arr1, arr2) {
var result = [], left1 = arr1.length, left2 = arr2.length;
while (left1 > 0 && left2 > 0) {
  if (comparatorFn(arr1[0], arr2[0]) <= 0) {
    result.push(arr1.shift());
    left1--;
  } else {
    result.push(arr2.shift());
    left2--;
  }
}
if (left1 > 0) {
  result.push.apply(result, arr1);
} else {
  result.push.apply(result, arr2);
}
return result;
}
function equivComparatorAsc(a, b){
	return (a.deFactoPrice - b.deFactoPrice);
}
function equivComparatorDesc(a, b){
	return (b.deFactoPrice - a.deFactoPrice);
}

var currentCurrency, currentExchange, currentFee, currencyMultiplier, feeMultiplier, deFactoMultiplier;
function objectifySpreadItem(item){
	return {
		price : item[0],
		amount : item[1],
		exchange : currentExchange,
		currency : currentCurrency,
		equivPrice :  (item[0] * currencyMultiplier).toFixed(1),
		deFactoPrice : (item[0] * deFactoMultiplier).toFixed(1)
	};
}
function addToEquivIndex(spread){
	console.log('updated spread data 	', spread.exchange);

	// clear out previous data for this exchange/currency combo, we'll add new ones now!
	_.remove(combinedSpreads.asks, {exchange:spread.exchange, currency: spread.currency});
	_.remove(combinedSpreads.bids, {exchange:spread.exchange, currency: spread.currency});

	// weird workaround, should probably just pass it in as an object...
	currentCurrency = spread.currency;
	currentExchange = spread.exchange;
	currentFee = spread.fee;
	currencyMultiplier = fx(1).from(currentCurrency).to(baseCurrency)

	// for buying
	feeMultiplier = buying_cost(1, currentFee);
	deFactoMultiplier = feeMultiplier * currencyMultiplier;

	combinedSpreads.asks = _.first(merge(equivComparatorAsc, combinedSpreads.asks, _.map(spread.asks, objectifySpreadItem)), 20);

	// for selling
	feeMultiplier = selling_rev(1, currentFee);
	deFactoMultiplier = feeMultiplier * currencyMultiplier;
	combinedSpreads.bids = _.first(merge(equivComparatorDesc, combinedSpreads.bids, _.map(spread.bids, objectifySpreadItem)), 20);

	// console.log('merged asks: ', combinedSpreads.asks);
	// console.log('merged bids: ', combinedSpreads.bids);
}

var balances;

function initBalances(){
	// leaving this
	if(balances){
		return;
	}
	balances = {};

	_.each(trader.exchanges, function(exchange, name){
		balances[name] = _.cloneDeep(exchange.balance);
	});
}

// much spaghetti in here... :/
function affordedTradeAmount(buySell, nextTrade, neededAmount){
	initBalances();

	var maxAffordedAmount;
	// TODO: only taking fees in account when buying, not when selling (fees normally charged in fiat). Is that right?
	if(buySell.toLowerCase() === 'buy'){
		var fee = trader.exchanges[nextTrade.exchange].fee;
		maxAffordedAmount = balances[nextTrade.exchange][nextTrade.currency] / buying_cost(nextTrade.price,fee);
	} else { // sell
		maxAffordedAmount = balances[nextTrade.exchange]['BTC'];
	}

	if(_.isNaN(maxAffordedAmount)){
		throw "Something went wrong with calculating maxAffordedAmount" + JSON.stringify(nextTrade);
	}

	var comparedAmounts = [maxAffordedAmount, nextTrade.amount];
	if(neededAmount){ // sometimes I just want to get whatever
		comparedAmounts.push(neededAmount);
	}

	// this is what will be returned
	var actualTradeAmount = _.min(comparedAmounts);

	if(actualTradeAmount == 0) {
		return 0;
	}

	// update balances object.
	if(buySell.toLowerCase() === 'buy'){
		balances[nextTrade.exchange][nextTrade.currency] -= actualTradeAmount * buying_cost(nextTrade.price,fee);
	} else {
		balances[nextTrade.exchange]['BTC'] -= actualTradeAmount;
	}

	return actualTradeAmount;
}

function theoreticalTradeAmount(nextTrade, neededAmount){
	return _.min([nextTrade.amount, neededAmount]);
}

function bestTrade(buySell, neededAmount, theoretical){
	var availableTrades = _.cloneDeep( (buySell.toLowerCase() === 'buy') ? combinedSpreads.asks : combinedSpreads.bids );
	var tradeAmount, nextTrade;
	var outputTrades = [], possibleAmounts = [];
	while(neededAmount > 0 && (nextTrade = availableTrades.shift())){
		// figure out how much I can trade
		tradeAmount = theoretical ? theoreticalTradeAmount(nextTrade, neededAmount) : affordedTradeAmount(buySell, nextTrade, neededAmount);

		if(tradeAmount === 0){ // this trade can't be done, prolly broke on exchange
			console.log('cannot afford to make trade on ', nextTrade.exchange)
			continue;
		}

		neededAmount -= tradeAmount;
		nextTrade.amount = tradeAmount;
		outputTrades.push(nextTrade);
	}
	return outputTrades;
}

/*
    Compares a given spread data for an exchange with all other exchanges spread data
    stored, returns array of which spreads an arbitrage oportunity exists between.
*/
function detectArbitrageFor(spread){
    // var arbitragePossibilities = [];
    var spreadPairs = [];

    var everyOther = _.values(_.omit(trader.RawSpreads[spread.currency], spread.exchange));

    everyOther.forEach(function(otherExchange){
        spreadPairs.push([spread, otherExchange]);
        spreadPairs.push([otherExchange, spread]);
        console.log('adding pair: ', spread.exchange, otherExchange.exchange, 'fees: ', spread.fee, otherExchange.fee);
    });

    var arbitragePossibilities = _.filter(spreadPairs, arbitrage_possibilities);

    if(arbitragePossibilities.length > 0){
        arbitragePossibilities.forEach(processArbitrage);
    } else {
        console.log('no arbitrage possible on ' + spread.exchange);
    }

    return arbitragePossibilities;
}

/*
    Either simulates or trades on two exchanges if arbitrage possibilities
    arise. This is where things like fees and balances are taken into account.
*/
function spitArbitrage(gtfo, theoretical){
	var arbitrades = [];
    var asks = _.cloneDeep(combinedSpreads.asks);
    var bids = _.cloneDeep(combinedSpreads.bids);

    var lowAsk = asks.shift();
    var highBid = bids.shift();

    gtfo = gtfo || 1;

    var buyingAffordance, sellingAffordance, tradeAmount;

    console.log('best prices: 	buy ',
    	lowAsk.exchange, lowAsk.price, parseFloat(lowAsk.deFactoPrice).toFixed(2), lowAsk.amount, '	sell ', highBid.exchange, highBid.price, parseFloat(highBid.deFactoPrice).toFixed(2), highBid.amount);

    while ( lowAsk && highBid && lowAsk.deFactoPrice * gtfo < highBid.deFactoPrice ){
    	if( theoretical ) {
    		buyingAffordance = lowAsk.amount;
    		sellingAffordance = highBid.amount;
    	} else {
	    	// getting a realistic buy and sell
	    	buyingAffordance = affordedTradeAmount('buy', lowAsk);
	    	if( buyingAffordance == 0 ){
	    		lowAsk = asks.shift();
	    		continue;
	    	}
	    	sellingAffordance = affordedTradeAmount('sell', highBid);
	    	if( sellingAffordance == 0 ){
	    		highBid = bids.shift();
	    		continue;
	    	}
    	}
		lowAsk.buySell = 'buy';
		highBid.buySell = 'sell';

    	// we're in magic land now, buy and sell can happen and they're more than gtfo apart! Yey!
    	tradeAmount = _.min([buyingAffordance, sellingAffordance]);

    	console.log('searching for infinity: ', tradeAmount, buyingAffordance, sellingAffordance);

    	if(tradeAmount === buyingAffordance){
    		arbitrades.push(lowAsk);
    		highBid.amount -= tradeAmount;
    		arbitrades.push(highBid);
    		lowAsk = asks.shift();
    	} else {
    		arbitrades.push(highBid);
    		lowAsk.amount -= tradeAmount;
    		arbitrades.push(lowAsk);
    		highBid = bids.shift();
    	}
    }
    return arbitrades;
}

function combineArbitrades(arbitrades){
	// compress arbitrades so multiple on same exchange with same price get combined.
	var processedArbitrades = [], combinations = [];

	while(arbitrades.length > 0){
		item = arbitrades.shift();
		combinations = arbitrades.remove(_.pick(item, ['buySell', 'deFactoPrice', 'exchange', 'currency']));
		_.each(combinations, function(comboItem){
			item.amount += comboItem.amount;
		});
		processedArbitrades.push(item);
	}
    return processedArbitrades;
}

function processArbitrage(from, to){
    if(Array.isArray(from)){ // handle pairform as well
        to = from[1];
        from = from[0];
    }

    console.log('processing arbitrage', from.exchange, from.currency, to.exchange, to.currency);

    var asks = _.cloneDeep(from.asks);
    var bids = _.cloneDeep(to.bids);

    var lowest_ask = asks.shift();
    var highest_bid = bids.shift();

    console.log('THEORETICAL best trade: ', fx(lowest_ask[0]).from(from.currency).to(baseCurrency), fx(highest_bid[0]).from(to.currency).to(baseCurrency));

    var mixed_fees = (1+from.fee)*(1+to.fee);

    var buyingBalance = trader.exchanges[from.exchange].balance[from.currency];
    var sellingAmount = trader.exchanges[to.exchange].balance['BTC'];

    console.log('buying balance: ', buyingBalance, 'selling amount: ', sellingAmount);

    if(buyingBalance < 1){
        console.log('Dont have fiat on ', from.exchange.toUpperCase());
        return false;
    }
    if(sellingAmount < 0.001){
        console.log('Dont have crypto on ', to.exchange.toUpperCase());
        return false;
    }

    var buyingAffordance = buyingBalance / buying_cost(lowest_ask[0], from.fee);

    var amount, profit, margin, buyCost, sellRev;
    var totalAmount = 0, totalProfit = 0, totalCost = 0, totalRevenue = 0, totalMargin = 0;

    while (lowest_ask && highest_bid && lowest_ask[0]*mixed_fees < highest_bid[0]){ // while it's still profitable
        buyCost = buying_cost(lowest_ask[0], from.fee);
        sellRev = selling_rev(highest_bid[0], to.fee);

        amount = Math.min(sellingAmount, buyingAffordance, lowest_ask[1], highest_bid[1]);

        lowest_ask[1]   -= amount;
        highest_bid[1]  -= amount;
        sellingAmount -= amount;
        buyingAffordance  -= amount;

        profit = (amount * (highest_bid[0] - lowest_ask[0])).toFixed(3);
        margin = (((highest_bid[0] / lowest_ask[0]) - 1) * 100).toFixed(2);

        if(amount === 0){
            // console.log(from.asks, to.bids);
            throw 'ZeroAmount, something went wrong!';
        }
        console.log('buy ' + amount + ' @ ' + from.exchange + ' à ' +lowest_ask[0] + ', sell @' + to.exchange + ' à ' + highest_bid[0] + ', profit: ' + profit + ', margin: ' + margin + '%');

        totalCost += (amount * lowest_ask[0]);
        totalRevenue += (amount * highest_bid[0]);

        if(lowest_ask[1] == 0){
            console.log('full buy');
            lowest_ask = asks.shift();
        }
        if(highest_bid[1] == 0){
            console.log('full sell');
            highest_bid = bids.shift();
        }
        if(sellingAmount == 0){
            console.log('no more coins to sell');
            break;
        }
        if(buyingAffordance == 0){
            console.log('no more funds to buy');
            break;
        }
    }
    totalProfit = totalRevenue - totalCost;
    totalMargin = (((totalRevenue / totalCost) - 1) * 100).toFixed(2);

    console.log('trade cost : ' + totalCost + ', revenue: ' + totalRevenue + ', profit : ' + totalProfit + ', margin : ' + totalMargin+ '%');
}

trader.init().then(function () {
	trader.on('updated_spread_data', function(spread){
		addToEquivIndex(spread);
		var possibleArbitrages = spitArbitrage(1, true);
		if(possibleArbitrages.length > 0){
			console.log('possible arbitrage: ', possibleArbitrages);
		}
	});

	ratesPromise.then(function(){
	    trader.watch('EUR');
	    trader.watch('USD');

	 //    console.log('inited trader!');
	 //    // console.log();

		// Promise.all(_.map(_.keys(trader.RawSpreads), trader.getAllSpreads, trader)).then(function(){
		// 	console.log('got all spreads')

		// 	console.log(bestTrade('sell', 20));

		// });

	});
});
<commit_msg>Cleaned out old style code (in separate branch now), and added a few comments<commit_after>
var _  = require('lodash');
var jf = require('jsonfile');
var fx = require('money');

var Promise = require('bluebird');
var request = Promise.promisify(require('request'));
var config = require('./config.js');

var baseCurrency = 'USD';

var ratesPromise = request('http://openexchangerates.org/api/latest.json?app_id=' + config.rates.app_id, {json:true}).spread(function(request, data){
	_.extend(fx, data);
});

var trader = require('./trader.js');

/*
    Two functions to take into account fees.
*/
function buying_cost(amount, fee){
    return amount * (1 + fee);
}
function selling_rev(amount, fee){
    return amount * (1 - fee);
}

var combinedSpreads = {
	bids : [],
	asks : []
};

/**
* The merge part of the merge sort algorithm.
*
* @param {function} comparatorFn The comparator function.
* @param {array} arr1 The first sorted array.
* @param {array} arr2 The second sorted array.
* @returns {array} The merged and sorted array.
*/
function merge(comparatorFn, arr1, arr2) {
var result = [], left1 = arr1.length, left2 = arr2.length;
while (left1 > 0 && left2 > 0) {
  if (comparatorFn(arr1[0], arr2[0]) <= 0) {
    result.push(arr1.shift());
    left1--;
  } else {
    result.push(arr2.shift());
    left2--;
  }
}
if (left1 > 0) {
  result.push.apply(result, arr1);
} else {
  result.push.apply(result, arr2);
}
return result;
}
function equivComparatorAsc(a, b){
	return (a.deFactoPrice - b.deFactoPrice);
}
function equivComparatorDesc(a, b){
	return (b.deFactoPrice - a.deFactoPrice);
}

var currentCurrency, currentExchange, currentFee, currencyMultiplier, feeMultiplier, deFactoMultiplier;

function objectifySpreadItem(item){
	return {
		price : item[0],
		amount : item[1],
		exchange : currentExchange,
		currency : currentCurrency,
		equivPrice :  (item[0] * currencyMultiplier).toFixed(1),
		deFactoPrice : (item[0] * deFactoMultiplier).toFixed(1)
	};
}

/*
	Maintains a sorted of best trades available in the system
*/
function addToEquivIndex(spread){
	console.log('updated spread data 	', spread.exchange);

	// clear out previous data for this exchange/currency combo, we'll add new ones now!
	_.remove(combinedSpreads.asks, {exchange:spread.exchange, currency: spread.currency});
	_.remove(combinedSpreads.bids, {exchange:spread.exchange, currency: spread.currency});

	// weird workaround, should probably just pass it in as an object...
	currentCurrency = spread.currency;
	currentExchange = spread.exchange;
	currentFee = spread.fee;
	currencyMultiplier = fx(1).from(currentCurrency).to(baseCurrency)

	// for buying
	feeMultiplier = buying_cost(1, currentFee);
	deFactoMultiplier = feeMultiplier * currencyMultiplier;

	combinedSpreads.asks = _.first(merge(equivComparatorAsc, combinedSpreads.asks, _.map(spread.asks, objectifySpreadItem)), 20);

	// for selling
	feeMultiplier = selling_rev(1, currentFee);
	deFactoMultiplier = feeMultiplier * currencyMultiplier;
	combinedSpreads.bids = _.first(merge(equivComparatorDesc, combinedSpreads.bids, _.map(spread.bids, objectifySpreadItem)), 20);

	// console.log('merged asks: ', combinedSpreads.asks);
	// console.log('merged bids: ', combinedSpreads.bids);
}

var balances;

function initBalances(){
	// leaving this
	if(balances){
		return;
	}
	balances = {};

	_.each(trader.exchanges, function(exchange, name){
		balances[name] = _.cloneDeep(exchange.balance);
	});
}

// much spaghetti in here... :/
function affordedTradeAmount(buySell, nextTrade, neededAmount){
	initBalances();

	var maxAffordedAmount;
	// TODO: only taking fees in account when buying, not when selling (fees normally charged in fiat). Is that right?
	if(buySell.toLowerCase() === 'buy'){
		var fee = trader.exchanges[nextTrade.exchange].fee;
		maxAffordedAmount = balances[nextTrade.exchange][nextTrade.currency] / buying_cost(nextTrade.price,fee);
	} else { // sell
		maxAffordedAmount = balances[nextTrade.exchange]['BTC'];
	}

	if(_.isNaN(maxAffordedAmount)){
		throw "Something went wrong with calculating maxAffordedAmount" + JSON.stringify(nextTrade);
	}

	var comparedAmounts = [maxAffordedAmount, nextTrade.amount];
	if(neededAmount){ // sometimes I just want to get whatever
		comparedAmounts.push(neededAmount);
	}

	// this is what will be returned
	var actualTradeAmount = _.min(comparedAmounts);

	if(actualTradeAmount == 0) {
		return 0;
	}

	// update balances object.
	if(buySell.toLowerCase() === 'buy'){
		balances[nextTrade.exchange][nextTrade.currency] -= actualTradeAmount * buying_cost(nextTrade.price,fee);
	} else {
		balances[nextTrade.exchange]['BTC'] -= actualTradeAmount;
	}

	return actualTradeAmount;
}

function theoreticalTradeAmount(nextTrade, neededAmount){
	return _.min([nextTrade.amount, neededAmount]);
}

/*
	Gives you the best trades to buy or sell neededAmount or BTC.
	Either practically (takes into account balances) or theoretically.
*/
function bestTrade(buySell, neededAmount, theoretical){
	var availableTrades = _.cloneDeep( (buySell.toLowerCase() === 'buy') ? combinedSpreads.asks : combinedSpreads.bids );
	var tradeAmount, nextTrade;
	var outputTrades = [], possibleAmounts = [];
	while(neededAmount > 0 && (nextTrade = availableTrades.shift())){
		// figure out how much I can trade
		tradeAmount = theoretical ? theoreticalTradeAmount(nextTrade, neededAmount) : affordedTradeAmount(buySell, nextTrade, neededAmount);

		if(tradeAmount === 0){ // this trade can't be done, prolly broke on exchange
			console.log('cannot afford to make trade on ', nextTrade.exchange)
			continue;
		}

		neededAmount -= tradeAmount;
		nextTrade.amount = tradeAmount;
		outputTrades.push(nextTrade);
	}
	return outputTrades;
}

/*
    Either simulates or trades on two exchanges if arbitrage possibilities
    arise. This is where things like fees and balances are taken into account.
*/
function spitArbitrage(gtfo, theoretical){
	var arbitrades = [];
    var asks = _.cloneDeep(combinedSpreads.asks);
    var bids = _.cloneDeep(combinedSpreads.bids);

    var lowAsk = asks.shift();
    var highBid = bids.shift();

    gtfo = gtfo || 1;

    var buyingAffordance, sellingAffordance, tradeAmount;

    console.log('best prices: 	buy ',
    	lowAsk.exchange, lowAsk.price, parseFloat(lowAsk.deFactoPrice).toFixed(2), lowAsk.amount, '	sell ', highBid.exchange, highBid.price, parseFloat(highBid.deFactoPrice).toFixed(2), highBid.amount);

    while ( lowAsk && highBid && lowAsk.deFactoPrice * gtfo < highBid.deFactoPrice ){
    	if( theoretical ) {
    		buyingAffordance = lowAsk.amount;
    		sellingAffordance = highBid.amount;
    	} else {
	    	// getting a realistic buy and sell
	    	buyingAffordance = affordedTradeAmount('buy', lowAsk);
	    	if( buyingAffordance == 0 ){
	    		lowAsk = asks.shift();
	    		continue;
	    	}
	    	sellingAffordance = affordedTradeAmount('sell', highBid);
	    	if( sellingAffordance == 0 ){
	    		highBid = bids.shift();
	    		continue;
	    	}
    	}
		lowAsk.buySell = 'buy';
		highBid.buySell = 'sell';

    	// we're in magic land now, buy and sell can happen and they're more than gtfo apart! Yey!
    	tradeAmount = _.min([buyingAffordance, sellingAffordance]);

    	console.log('searching for infinity: ', tradeAmount, buyingAffordance, sellingAffordance);

    	if(tradeAmount === buyingAffordance){
    		arbitrades.push(lowAsk);
    		highBid.amount -= tradeAmount;
    		arbitrades.push(highBid);
    		lowAsk = asks.shift();
    	} else {
    		arbitrades.push(highBid);
    		lowAsk.amount -= tradeAmount;
    		arbitrades.push(lowAsk);
    		highBid = bids.shift();
    	}
    }
    return arbitrades;
}

/*
	Compress arbitrades so multiple on same exchange with same price get combined.
*/
function combineArbitrades(arbitrades){
	var processedArbitrades = [], combinations = [];

	while(arbitrades.length > 0){
		item = arbitrades.shift();
		combinations = arbitrades.remove(_.pick(item, ['buySell', 'deFactoPrice', 'exchange', 'currency']));
		_.each(combinations, function(comboItem){
			item.amount += comboItem.amount;
		});
		processedArbitrades.push(item);
	}
    return processedArbitrades;
}

trader.init().then(function () {
	trader.on('updated_spread_data', function(spread){
		addToEquivIndex(spread);
		var possibleArbitrages = spitArbitrage(1, true);
		if(possibleArbitrages.length > 0){
			console.log('possible arbitrage: ', possibleArbitrages);
		}
	});

	ratesPromise.then(function(){
	    trader.watch('EUR');
	    trader.watch('USD');

		// Promise.all(_.map(_.keys(trader.RawSpreads), trader.getAllSpreads, trader)).then(function(){
		// 	console.log('got all spreads')

		// 	console.log(bestTrade('sell', 20));

		// });

	});
});
<|endoftext|>


<commit_before>var asciitree = require('asciitree');
var bitwise = require('../utils/bitwise');

var PADDING = '0'.repeat(4);

function numberToBitstring(number) {
  return (PADDING + number.toString(2)).slice(-4);
}

// DAFSA transducer etc. todo: splay get value on top if not ordered
// todo: only compare last part of string
// http://benlynn.blogspot.com/2013/11/crit-bit-tree-micro-optimizations_3.html
// http://benlynn.blogspot.com/2013/11/crit-bit-tree-micro-optimizations_3.html
// https://github.com/blynn/blt/blob/master/blt.c
// https://dotat.at/prog/qp/blog-2015-10-04.html
// TODO: tiny sparse array
// TODO: encode direction as reverted mask | x (probably useless in JS but may avoid one condition)
function findCriticalBit(a, b) {
  var i = 0;

  var min = Math.min(a.length, b.length);

  while (i < min) {
    if (a[i] !== b[i]) {
      return [i, bitwise.criticalBit8Mask(a.charCodeAt(i), b.charCodeAt(i))];
    }

    i++;
  }

  return a.length === b.length ? null : [i, 0];
}

function get(key, address) {
  return bitwise.testCriticalBit8(key.charCodeAt(address[0]), address[1]);
}

function criticalGt(a, b) {
  if (a[0] > b[0])
    return true;

  if (a[0] < b[0])
    return false;

  if (a[1] > b[1])
    return true;

  return false;
}

function InternalNode(critical) {
  this.critical = critical;
  this.left = null;
  this.right = null;
}

function ExternalNode(key) {
  this.key = key;
}

function CritBitTree() {
  this.root = null;
}

CritBitTree.prototype.add = function(key) {
  if (this.root === null) {
    this.root = new ExternalNode(key);
    return;
  }

  var node = this.root,
      ancestors = [];

  while (true) {
    if (node instanceof ExternalNode) {
      var critical = findCriticalBit(key, node.key);

      var internal = new InternalNode(critical);

      var left = get(key, critical) === 0;

      if (left) {
        internal.left = new ExternalNode(key);
        internal.right = node;
      }
      else {
        internal.left = node;
        internal.right = new ExternalNode(key);
      }

      if (this.root === node) {
        this.root = internal;
      }
      else {

        // Bubbling up
        var best = null;

        for (var i = ancestors.length - 1; i >= 0; i--) {
          var [a] = ancestors[i];

          if (criticalGt(a.critical, critical))
            continue;

          best = i;
          break;
        }

        if (best !== null) {
          var [parent, wentRight] = ancestors[best];

          if (left) {
            internal.right = parent.left;
          }
          else {
            internal.left = parent.right;
          }

          if (wentRight) {
            parent.right = internal;
          }
          else {
            parent.left = internal;
          }
        }
        else {
          var [parent, wentRight] = ancestors[0];

          this.root = internal;

          if (left)
            internal.right = parent;
          else
            internal.left = parent;
        }
      }

      return;
    }

    else {
      var bit = get(key, node.critical);

      if (bit === 0) {
        if (!node.left) {
          node.left = new ExternalNode(key);
          return;
        }

        ancestors.push([node, false]);
        node = node.left;
      }
      else {
        if (!node.right) {
          node.right = new ExternalNode(key);
          return;
        }

        ancestors.push([node, true]);
        node = node.right;
      }
    }
  }
};

CritBitTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = function() {
  return this.root;
};

function printNode(node) {
  if (!node)
    return '';

  if (node instanceof InternalNode)
    return '(' + node.critical[0] + ',' + ((~node.critical[1] >>> 0) & 0xff) + ')';

  return node.key;
}

function log(tree) {

  const title = printNode;

  const children = node => (node instanceof ExternalNode ? null : [node.left , node.right]);

  console.log(asciitree(tree.root, title, children));
}

var tree = new CritBitTree();

// tree.add(0);
// tree.add(1);
// tree.add(2);
// tree.add(3);
// tree.add(4);
// tree.add(5);
// tree.add(6);
// tree.add(7);
// tree.add(8);
// tree.add(9);
// tree.add(10);
// tree.add(11);
// tree.add(12);
// tree.add(13);
// tree.add(14);
// tree.add(15);

// THOSE DO NOT WORK
// tree.add('abcdef');
// tree.add('bcd');
tree.add('abb');
tree.add('abc');
tree.add('abd');
tree.add('abe');
tree.add('aba');
tree.add('abz');

// console.log(tree);
log(tree);
<commit_msg>Working on critbit<commit_after>var asciitree = require('asciitree');
var bitwise = require('../utils/bitwise');

var PADDING = '0'.repeat(8);

function numberToBitstring(number) {
  return (PADDING + number.toString(2)).slice(-8);
}

function unmask(x) {
  return 8 - Math.log2((~x >>> 0) & 0xff) - 1;
}

// DAFSA transducer etc. todo: splay get value on top if not ordered
// todo: only compare last part of string
// http://benlynn.blogspot.com/2013/11/crit-bit-tree-micro-optimizations_3.html
// http://benlynn.blogspot.com/2013/11/crit-bit-tree-micro-optimizations_3.html
// https://github.com/blynn/blt/blob/master/blt.c
// https://dotat.at/prog/qp/blog-2015-10-04.html
// TODO: tiny sparse array
// TODO: encode direction as reverted mask | x (probably useless in JS but may avoid one condition)
function findCriticalBit(a, b) {
  var i = 0;

  var min = Math.min(a.length, b.length);

  while (i < min) {
    if (a[i] !== b[i]) {
      return [i, bitwise.criticalBit8Mask(a.charCodeAt(i), b.charCodeAt(i))];
    }

    i++;
  }

  return a.length === b.length ? null : [i, 0];
}

function get(key, address) {
  return bitwise.testCriticalBit8(key.charCodeAt(address[0]), address[1]);
}

function criticalGt(a, b) {
  if (a[0] > b[0])
    return true;

  if (a[0] < b[0])
    return false;

  // TODO: issue here because of the mask?
  if (a[1] > b[1])
    return true;

  return false;
}

function InternalNode(critical) {
  this.critical = critical;
  this.left = null;
  this.right = null;
}

function ExternalNode(key) {
  this.key = key;
}

function CritBitTree() {
  this.root = null;
}

CritBitTree.prototype.add = function(key) {
  if (this.root === null) {
    this.root = new ExternalNode(key);
    return;
  }

  var node = this.root,
      ancestors = [];

  while (true) {
    if (node instanceof ExternalNode) {
      var critical = findCriticalBit(key, node.key);

      var internal = new InternalNode(critical);

      var left = get(key, critical) === 0;

      if (left) {
        internal.left = new ExternalNode(key);
        internal.right = node;
      }
      else {
        internal.left = node;
        internal.right = new ExternalNode(key);
      }

      if (this.root === node) {
        this.root = internal;
      }
      else {

        // Bubbling up
        var best = null;

        for (var i = ancestors.length - 1; i >= 0; i--) {
          var [a] = ancestors[i];

          if (criticalGt(a.critical, critical))
            continue;

          best = i;
          break;
        }

        if (best !== null) {
          var [parent, wentRight] = ancestors[best];

          if (left) {
            internal.right = parent.left;
          }
          else {
            internal.left = parent.right;
          }

          if (wentRight) {
            parent.right = internal;
          }
          else {
            parent.left = internal;
          }
        }
        else {
          var [parent, wentRight] = ancestors[0];

          this.root = internal;

          if (left)
            internal.right = parent;
          else
            internal.left = parent;
        }
      }

      return;
    }

    else {
      var bit = get(key, node.critical);

      if (bit === 0) {
        if (!node.left) {
          node.left = new ExternalNode(key);
          return;
        }

        ancestors.push([node, false]);
        node = node.left;
      }
      else {
        if (!node.right) {
          node.right = new ExternalNode(key);
          return;
        }

        ancestors.push([node, true]);
        node = node.right;
      }
    }
  }
};

CritBitTree.prototype[Symbol.for('nodejs.util.inspect.custom')] = function() {
  return this.root;
};

function printNode(node) {
  if (!node)
    return '';

  if (node instanceof InternalNode)
    return '(' + node.critical[0] + ',' + unmask(node.critical[1]) + ')';

  return node.key + '•' + Array.from(node.key, k => k.charCodeAt(0)).map(numberToBitstring);
}

function log(tree) {

  const title = printNode;

  const children = node => (node instanceof ExternalNode ? null : [node.left , node.right]);

  console.log(asciitree(tree.root, title, children));
}

var tree = new CritBitTree();

// tree.add(0);
// tree.add(1);
// tree.add(2);
// tree.add(3);
// tree.add(4);
// tree.add(5);
// tree.add(6);
// tree.add(7);
// tree.add(8);
// tree.add(9);
// tree.add(10);
// tree.add(11);
// tree.add(12);
// tree.add(13);
// tree.add(14);
// tree.add(15);

// THOSE DO NOT WORK
// tree.add('abcdef');
// tree.add('bcd');
tree.add('abb');
tree.add('abc');
tree.add('abd');
tree.add('abe');
tree.add('aba');
// tree.add('abz');

// console.log(tree);
log(tree);
<|endoftext|>


<commit_before>'use strict'
module.exports = ficInflate

const Bluebird = require('bluebird')
const qw = require('qw')

const progress = use('progress')

// Take a fic and fetch all its chapters and ensure its per-chapter metadata
// is fully complete.

function ficInflate (fic, fetch, tracker) {
  if (!tracker) tracker = progress.tracker
  return fic.then(fic => {
    const fics = [fic].concat(fic.fics)
    const completion = new Map()
    for (let fic of fics) {
      completion.set(fic, tracker.newItem(fic.title, fic.chapters.length))
    }
    return Bluebird.map(fics, fic => {
      const tracker = completion.get(fic)
      process.emit('debug', 'Inflating', fic.title, 'chapters:', fic.chapters.length)
      let words = 0
      return Bluebird.map(fic.chapters, chapterInfo => {
        const chapterContent = chapterInfo.getContent(fetch)
        return progress.completeWorkWhenResolved(chapterContent, tracker).then(chapter => {
          process.emit('debug', `Got content for #${chapterInfo.order}: ${chapterInfo.name}`)
          progress.show(fic.title, `${chapterInfo.name}`)
          chapterInfo.words = chapter.words
          words += chapter.words
          if (chapterInfo.link == null) chapterInfo.link = chapter.chapterLink
          for (let prop of qw`name author authorUrl created modified headings`) {
            if (chapterInfo[prop] == null) chapterInfo[prop] = chapter[prop]
          }
          if (chapterInfo.author === fic.author || chapterInfo.authorUrl === fic.authorUrl) {
            chapterInfo.author = null
            chapterInfo.authorUrl = null
          }
        }).catch(err => {
          chapterInfo.error = err
          process.emit('error', err.message)
        })
      }).finally(() => {
        if (!fic.words) fic.words = words
        tracker.finish()
      })
    })
  }).thenReturn(fic)
}
<commit_msg>fic-inflate: Update first/last chapter create/mod times from fic if missing<commit_after>'use strict'
module.exports = ficInflate

const Bluebird = require('bluebird')
const qw = require('qw')

const progress = use('progress')

// Take a fic and fetch all its chapters and ensure its per-chapter metadata
// is fully complete.

function ficInflate (fic, fetch, tracker) {
  if (!tracker) tracker = progress.tracker
  return fic.then(fic => {
    const fics = [fic].concat(fic.fics)
    const completion = new Map()
    for (let fic of fics) {
      completion.set(fic, tracker.newItem(fic.title, fic.chapters.length))
    }
    return Bluebird.map(fics, fic => {
      const tracker = completion.get(fic)
      process.emit('debug', 'Inflating', fic.title, 'chapters:', fic.chapters.length)
      let words = 0
      return Bluebird.map(fic.chapters, chapterInfo => {
        const chapterContent = chapterInfo.getContent(fetch)
        return progress.completeWorkWhenResolved(chapterContent, tracker).then(chapter => {
          process.emit('debug', `Got content for #${chapterInfo.order}: ${chapterInfo.name}`)
          progress.show(fic.title, `${chapterInfo.name}`)
          chapterInfo.words = chapter.words
          words += chapter.words
          if (chapterInfo.link == null) chapterInfo.link = chapter.chapterLink
          for (let prop of qw`name author authorUrl created modified headings`) {
            if (chapterInfo[prop] == null) chapterInfo[prop] = chapter[prop]
          }
          if (chapterInfo.author === fic.author || chapterInfo.authorUrl === fic.authorUrl) {
            chapterInfo.author = null
            chapterInfo.authorUrl = null
          }
        }).catch(err => {
          chapterInfo.error = err
          process.emit('error', err.message)
        })
      }).finally(() => {
        if (!fic.words) fic.words = words
        const firstChapter = fic.chapters[0]
        const lastChapter = fic.chapters[fic.chapters.length - 1]
        if (firstChapter.created == null && firstChapter.modified == null && fic.created != null) {
          firstChapter.created = fic.created
        }
        if (lastChapter.created == null && lastChapter.modified == null && fic.modified != null) {
          lastChapter.modified = fic.modified
        }
        tracker.finish()
      })
    })
  }).thenReturn(fic)
}
<|endoftext|>