<commit_before>// Constants
var leftMargin = 20;
var buttonWidth = 60;
var buttonHeight = 40;
var buttonSpacing = 20;
var smallMovement = 3;
var stepDelay = 500;

$(document).ready(function() {
    var ctx = $('#canvas').get(0).getContext('2d');
    ctx.textAlign = "center";

    // Scaling and panning
    var f = 30;
    var px = 0;
    var py = 0;

    // Mouse
    var mx = 0;
    var my = 0;
    var mdx = 0;
    var mdy = 0;
    var mouseDown = false;
    var selectedObjs = [];
    var movingGroup = false;

    // Keys
    var spaceDown = false;
    var shiftDown = false;

    // Button hover
    var leftIndex = -1;

    // Simulation
    var simulating = false;
    var intervalID = undefined;

    // Track IDs of switches and LEDs
    var idCounter = 0;

    var leftButtons = [
      {label: "GO", action: toggleSimulation},
      {label: "AND", action: newGate('and')},
      {label: "OR", action: newGate('or')},
      {label: "NOT", action: newGate('not')},
      {label: "SW", action: newNode('io')},
      {label: "LED", action: newNode('led')}
    ];

    function newGate(type) {
      return function() {
        circuit.gates.push({
          type: type,
          ins: [],
          outs: [],
          pos: [400 - px, 300 - py]
        })
      }
    }

    function newNode(type){
      return function() {
        circuit.nodes.push({
          id: idCounter++,
          type: type,
          pos: [400 - px, 300 - py],
          value: false
        })
      }
    }

    function draw_buttons() {
      ctx.font = "20px sans";
      var y = buttonSpacing;
      for (var i = 0; i < leftButtons.length; i++) {
        if (i === leftIndex) {
          ctx.fillStyle = "cyan";
          ctx.fillRect(leftMargin, y, buttonWidth, buttonHeight);
        }
        ctx.strokeRect(leftMargin, y, buttonWidth, buttonHeight);
        ctx.fillStyle = "black";
        ctx.fillText(leftButtons[i].label, leftMargin + buttonWidth / 2, y + 30);
        y += buttonHeight + buttonSpacing;
      }
    }

    // Helper function that finds the position of a node
    function draw_wires(){
      for (var i = 0; i < circuit.nodes.length; i++){

      }
    }

    // Drawing Gates
    function draw_and_gate(gate) {
      var x0 = px + gate.pos[0];
      var y0 = py + gate.pos[1];

      ctx.beginPath();

      //draw the circle part of the and gate
      ctx.arc(x0 + f, y0 + f/2, f/2, -0.5 * Math.PI, 0.5 * Math.PI);
      ctx.stroke();

      //draw the rectangle part of the and gate
      ctx.moveTo(x0 + f, y0 + f)
      ctx.lineTo(x0, y0 + f);
      ctx.lineTo(x0, y0);
      ctx.lineTo(x0 + f, y0);
      ctx.stroke();

      //draw the leads
      ctx.moveTo(x0, y0 + f/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f/3);
      ctx.moveTo(x0, y0 + f * 2/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f * 2/3);
      ctx.moveTo(x0 + 1.5 * f, y0 + 0.5 * f)
      ctx.lineTo(x0 + 2 * f,y0 + 0.5 * f )
      ctx.stroke();

      // function to find the position of given node

    }

    function draw_or_gate(gate){
      var x0 = px + gate.pos[0];
      var y0 = py + gate.pos[1];

      ctx.beginPath();

      //front part
      ctx.moveTo(x0, y0);
      ctx.quadraticCurveTo(x0 + 3 * f, y0 + 0.5 * f, x0, y0 + f);

      //back part
      ctx.quadraticCurveTo(x0 + 0.5*f, y0 + 0.5 * f, x0, y0);
      ctx.stroke();

      //draw the leads
      ctx.moveTo(x0 + 0.2 * f, y0 + f/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f/3);
      ctx.moveTo(x0 + 0.2 * f, y0 + f * 2/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f * 2/3);
      ctx.moveTo(x0 + 1.5 * f, y0 + 0.5 * f)
      ctx.lineTo(x0 + 2 * f,y0 + 0.5 * f )
      ctx.stroke();
    }

    function draw_not_gate(gate){
      var x0 = px + gate.pos[0];
      var y0 = py + gate.pos[1];

      ctx.beginPath();

      //triangle part
      ctx.moveTo(x0, y0);
      ctx.lineTo(x0 + 1.3 * f, y0 + 0.5 * f);
      ctx.lineTo(x0, y0 + f);
      ctx.closePath();

      //circle part
      var r = 0.1 * f;
      ctx.moveTo(x0 + 1.3 * f + 2 * r, y0 + 0.5 * f)
      ctx.arc(x0 + 1.3 * f + r, y0 + 0.5 * f, r, 0, Math.PI*2);
      ctx.stroke();

      //draw the leads whee
      ctx.moveTo(x0, y0 + f/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f/3);
      ctx.moveTo(x0, y0 + f * 2/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f * 2/3);
      ctx.moveTo(x0 + 1.5 * f, y0 + 0.5 * f)
      ctx.lineTo(x0 + 2 * f,y0 + 0.5 * f )
      ctx.stroke();
    }

    function draw_generic_gate(gate) {
      var x0 = px + gate.pos[0];
      var y0 = py + gate.pos[1];

      ctx.font = Math.round(0.5 * f) + "px sans";
      ctx.strokeRect(x0, y0, 1.5 * f, f);
      ctx.fillText(gate.type, x0 + 0.75 * f, y0 + 0.8 * f);
    }

    function draw_gate(gate) {
      switch (gate.type) {
        case 'and':
          draw_and_gate(gate);
          break;
        case 'or':
          draw_or_gate(gate);
          break;
        case 'not':
          draw_not_gate(gate);
          break;
        default:
          draw_generic_gate(gate);
          break;
      }
    }

//Drawing Nodes
    function draw_switch(node){
      var x0 = px + node.pos[0];
      var y0 = py + node.pos[1];

      if (node.value){
        ctx.fillStyle="#FF6600"; //orange for on
      }
      else {
        ctx.fillStyle="#000000"; //black for off
      }
      ctx.fillRect(x0, y0, f, f);
    }

    function draw_led(node){
      var x0 = px + node.pos[0];
      var y0 = py + node.pos[1];

      if (node.value){
        ctx.fillStyle="#00B8E6"; //blue for on
      }
      else {
        ctx.fillStyle="#000000"; //black for off
      }
      ctx.arc(x0, y0, f/2, 0 * Math.PI, 2 * Math.PI);
      ctx.fill();
    }

    function draw_any_node(node) {
      switch (node.type) {
        case 'io':
          draw_switch(node);
          break;
        case 'led':
          draw_led(node);
          break;
        default:
          draw_node(node);
          break;
      }
    }

    function draw_node(node) {
      var x0 = px + node.pos[0];
      var y0 = py + node.pos[1];

      ctx.fillStyle="#FF6600"; //orange
      ctx.fillRect(x0, y0, 0.3*f, 0.3*f);
    }

    function render() {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      for (var i = 0; i < circuit.gates.length; i++) {
        if (selectedObjs.indexOf(circuit.gates[i]) > -1) {
          ctx.strokeStyle = "magenta";
        }
        draw_gate(circuit.gates[i]);
        ctx.strokeStyle = "black";
      }

      for (var i = 0; i < circuit.nodes.length; i++) {
          if (selectedObjs.indexOf(circuit.nodes[i]) > -1) {
            ctx.strokeStyle = "magenta";
          }
          draw_any_node(circuit.nodes[i]);
          ctx.strokeStyle = "black";
      }

      if (mouseDown && !spaceDown && !movingGroup) {
        ctx.fillStyle = "rgba(0, 1, 1, 0.2)";
        ctx.fillRect(mdx, mdy, mx - mdx, my - mdy);
      }

      draw_buttons();
    }

    function checkLeftButtons() {
      if (mx >= leftMargin && mx <= leftMargin + buttonWidth) {
        if (my % (buttonSpacing + buttonHeight) >= buttonSpacing) {
          var i = Math.floor(my / (buttonSpacing + buttonHeight));
          if (i < leftButtons.length) {
            leftIndex = i;
            return;
          }
        }
      }
      leftIndex = -1;
    }

    function objectsUnderCursor() {
      objs = [];
      for (var i = 0; i < circuit.gates.length; i++) {
        var x0 = px + circuit.gates[i].pos[0];
        var y0 = py + circuit.gates[i].pos[1];
        if (mx >= x0 && my >= y0 && mx <= x0 + 1.5 * f && my <= y0 + f) {
          objs.push(circuit.gates[i]);
        }
      }
      for (var i = 0; i < circuit.nodes.length; i++) {
        var x0 = px + circuit.nodes[i].pos[0];
        var y0 = px + circuit.nodes[i].pos[1];
        var size;
        if (circuit.nodes[i].type == undefined) {
          size = 0.2 * f;
        } else {
          size = 2 * f;
        }
        if (mx >= x0 && my >= y0 && mx <= x0 + nodeSize && my <= y0 + nodeSize) {
          objs.push(circuit.nodes[i]);
        }
      }
      return objs;
    }

    function objectsInMarquee() {
      objs = [];
      var xa = Math.min(mx, mdx);
      var xb = Math.max(mx, mdx);
      var ya = Math.min(my, mdy);
      var yb = Math.max(my, mdy);
      for (var i = 0; i < circuit.gates.length; i++) {
        var x0 = px + circuit.gates[i].pos[0];
        var y0 = py + circuit.gates[i].pos[1];
        if (x0 >= xa && y0 >= ya && x0 + 1.5 * f <= xb && y0 + f <= yb) {
          objs.push(circuit.gates[i]);
        }
      }
      for (var i = 0; i < circuit.nodes.length; i++) {
        var x0 = px + circuit.nodes[i].pos[0];
        var y0 = px + circuit.nodes[i].pos[1];
        var size;
        if (circuit.nodes[i].type == undefined) {
          size = 0.2 * f;
        } else {
          size = 2 * f;
        }
        if (x0 >= xa && y0 >= ya && x0 + size <= xb && y0 + size <= yb) {
          objs.push(circuit.nodes[i]);
        }
      }
      return objs;
    }

    function simulate() {
      updateAgenda();
      for (var i = 0; i < agenda.length; i++) {
        render();
        var tasks = agenda[i];
        for (var j = 0; j < tasks.length; j++) {
          apply(tasks[j]);
        }
      }
    }

    function step() {
      if (agenda.length === 0) {
        toggleSimulation();
      } else {
        for (var i = 0; i < agenda[0].length; i++) {
          apply(agenda[0][i]);
        }
        shift(agenda);
      }
    }

    function toggleSimulation() {
      simulating = !simulating;
      if (simulating) {
        leftButtons[0].label = "STOP";
        updateAgenda();
        intervalID = setInterval(step, stepDelay);
      } else {
        leftButtons[0].label = "GO";
        agenda = [];
        if (intervalID != undefined) {
          clearInterval(intervalID);
          intervalID = undefined;
        }
      }
      render();
    }

    $('#canvas').mousedown(function(e) {
      mouseDown = true;
      mdx = e.offsetX;
      mdy = e.offsetY;

      if (leftIndex !== -1) {
        if (!simulating || leftIndex == 0) {
          leftButtons[leftIndex].action();
          render();
        }
        return;
      }

      if (simulating) {
        return;
      }

      if (!spaceDown) {
        var objs = objectsUnderCursor();
        if (objs.length > 0) {
          movingGroup = true;
          var existing = false
          for (var i = 0; i < objs.length; i++) {
            for (var j = 0; j < selectedObjs.length; j++) {
              if (objs[i] == selectedObjs[j]) {
                existing = true;
                break;
              }
            }
          }
          if (!existing) {
            if (shiftDown) {
              selectedObjs.push(objs[0]);
            } else {
              selectedObjs = [objs[0]];
            }
          }
        }
      }

      render();
    });

    $('#canvas').mousemove(function(e) {
      if (mouseDown) {
        if (spaceDown) {
          px += e.offsetX - mx;
          py += e.offsetY - my;
        } else if (movingGroup) {
          for (var i = 0; i < selectedObjs.length; i++) {
            selectedObjs[i].pos[0] += e.offsetX - mx;
            selectedObjs[i].pos[1] += e.offsetY - my;
          }
        }
      }

      mx = e.offsetX;
      my = e.offsetY;

      var old = leftIndex;
      checkLeftButtons();
      if (old !== leftIndex || mouseDown) {
        render();
      }
    });

    $('#canvas').mouseup(function(e) {
      if (leftIndex === -1) {
        if (Math.abs(mx - mdx) <= smallMovement && Math.abs(my - mdy) <= smallMovement) {
          var objs = objectsUnderCursor();
          if (objs.length > 0) {
            if (shiftDown) {
              selectedObjs.push(objs[0]);
            } else {
              selectedObjs = [objs[0]];
            }
          } else {
            selectedObjs = [];
          }
        } else if (!spaceDown && !movingGroup) {
          selectedObjs = objectsInMarquee();
        }
      }
      mouseDown = false;
      movingGroup = false;
      render();
    });

    $(document).keydown(function(e) {
      if (e.which === 32) {
        e.preventDefault();
        spaceDown = true;
      } else if (e.which == 16) {
        shiftDown = true;
      }
    });

    $(document).keyup(function(e) {
      if (e.which === 32) {
        e.preventDefault();
        spaceDown = false;
      } else if (e.which == 16) {
        shiftDown = false;
      }
    });

    render();
});
<commit_msg>Proper scaling and fix some bugs<commit_after>// Constants
var leftMargin = 20;
var buttonWidth = 60;
var buttonHeight = 40;
var buttonSpacing = 20;
var smallMovement = 3;
var stepDelay = 500;

$(document).ready(function() {
    var ctx = $('#canvas').get(0).getContext('2d');
    ctx.textAlign = "center";

    // Scaling and panning
    var f = 30;
    var px = 0;
    var py = 0;

    // Mouse
    var mx = 0;
    var my = 0;
    var mdx = 0;
    var mdy = 0;
    var mouseDown = false;
    var selectedObjs = [];
    var movingGroup = false;

    // Keys
    var spaceDown = false;
    var shiftDown = false;

    // Button hover
    var leftIndex = -1;

    // Simulation
    var simulating = false;
    var intervalID = undefined;

    // Track IDs of switches and LEDs
    var idCounter = 0;

    var leftButtons = [
      {label: "GO", action: toggleSimulation},
      {label: "AND", action: newGate('and')},
      {label: "OR", action: newGate('or')},
      {label: "NOT", action: newGate('not')},
      {label: "SW", action: newNode('io')},
      {label: "LED", action: newNode('led')}
    ];

    function newGate(type) {
      return function() {
        circuit.gates.push({
          type: type,
          ins: [],
          outs: [],
          pos: [400 * 30 / f - px, 300 * 30 / f - py]
        })
      }
    }

    function newNode(type){
      return function() {
        circuit.nodes.push({
          id: idCounter++,
          type: type,
          pos: [400 - px, 300 - py],
          value: false
        })
      }
    }

    function draw_buttons() {
      ctx.font = "20px sans";
      var y = buttonSpacing;
      for (var i = 0; i < leftButtons.length; i++) {
        if (i === leftIndex) {
          ctx.fillStyle = "cyan";
          ctx.fillRect(leftMargin, y, buttonWidth, buttonHeight);
        }
        ctx.strokeRect(leftMargin, y, buttonWidth, buttonHeight);
        ctx.fillStyle = "black";
        ctx.fillText(leftButtons[i].label, leftMargin + buttonWidth / 2, y + 30);
        y += buttonHeight + buttonSpacing;
      }
    }

    // Helper function that finds the position of a node
    function nodePos(nodeID) {
      for (var i = 0; i < circuit.nodes.length; i++){
        if (circuit.nodes[i].id == nodeId) {
          return circuit.nodes[i].pos;
        }
      }
    }

    // Drawing Gates
    function draw_and_gate(gate) {
      var x0 = f / 30 * (px + gate.pos[0]);
      var y0 = f / 30 * (py + gate.pos[1]);

      ctx.beginPath();

      //draw the circle part of the and gate
      ctx.arc(x0 + f, y0 + f/2, f/2, -0.5 * Math.PI, 0.5 * Math.PI);
      ctx.stroke();

      //draw the rectangle part of the and gate
      ctx.moveTo(x0 + f, y0 + f)
      ctx.lineTo(x0, y0 + f);
      ctx.lineTo(x0, y0);
      ctx.lineTo(x0 + f, y0);
      ctx.stroke();

      //draw the leads
      ctx.moveTo(x0, y0 + f/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f/3);
      ctx.moveTo(x0, y0 + f * 2/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f * 2/3);
      ctx.moveTo(x0 + 1.5 * f, y0 + 0.5 * f)
      ctx.lineTo(x0 + 2 * f,y0 + 0.5 * f )
      ctx.stroke();
      //
      // var pos;
      // if (gate.ins[0] !== -1) {
      //   pos = nodePos(gate.ins[0]);
      //   ctx.moveTo(x0, y0 + f/3);
      //   ctx.lineTo(px + pos[0], py + pos[1]);
      // }

      // function to find the position of given node

    }

    function draw_or_gate(gate){
      var x0 = f / 30 * (px + gate.pos[0]);
      var y0 = f / 30 * (py + gate.pos[1]);

      ctx.beginPath();

      //front part
      ctx.moveTo(x0, y0);
      ctx.quadraticCurveTo(x0 + 3 * f, y0 + 0.5 * f, x0, y0 + f);

      //back part
      ctx.quadraticCurveTo(x0 + 0.5*f, y0 + 0.5 * f, x0, y0);
      ctx.stroke();

      //draw the leads
      ctx.moveTo(x0 + 0.2 * f, y0 + f/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f/3);
      ctx.moveTo(x0 + 0.2 * f, y0 + f * 2/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f * 2/3);
      ctx.moveTo(x0 + 1.5 * f, y0 + 0.5 * f)
      ctx.lineTo(x0 + 2 * f,y0 + 0.5 * f )
      ctx.stroke();
    }

    function draw_not_gate(gate){
      var x0 = f / 30 * (px + gate.pos[0]);
      var y0 = f / 30 * (py + gate.pos[1]);

      ctx.beginPath();

      //triangle part
      ctx.moveTo(x0, y0);
      ctx.lineTo(x0 + 1.3 * f, y0 + 0.5 * f);
      ctx.lineTo(x0, y0 + f);
      ctx.closePath();

      //circle part
      var r = 0.1 * f;
      ctx.moveTo(x0 + 1.3 * f + 2 * r, y0 + 0.5 * f)
      ctx.arc(x0 + 1.3 * f + r, y0 + 0.5 * f, r, 0, Math.PI*2);
      ctx.stroke();

      //draw the leads whee
      ctx.moveTo(x0, y0 + f/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f/3);
      ctx.moveTo(x0, y0 + f * 2/3);
      ctx.lineTo(x0 - 0.5*f, y0 + f * 2/3);
      ctx.moveTo(x0 + 1.5 * f, y0 + 0.5 * f)
      ctx.lineTo(x0 + 2 * f,y0 + 0.5 * f )
      ctx.stroke();
    }

    function draw_generic_gate(gate) {
      var x0 = f / 30 * (px + gate.pos[0]);
      var y0 = f / 30 * (py + gate.pos[1]);

      ctx.font = Math.round(0.5 * f) + "px sans";
      ctx.strokeRect(x0, y0, 1.5 * f, f);
      ctx.fillText(gate.type, x0 + 0.75 * f, y0 + 0.8 * f);
    }

    function draw_gate(gate) {
      switch (gate.type) {
        case 'and':
          draw_and_gate(gate);
          break;
        case 'or':
          draw_or_gate(gate);
          break;
        case 'not':
          draw_not_gate(gate);
          break;
        default:
          draw_generic_gate(gate);
          break;
      }
    }

//Drawing Nodes
    function draw_switch(node){
      var x0 = f / 30 * (px + node.pos[0]);
      var y0 = f / 30 * (py + node.pos[1]);

      ctx.strokeRect(x0, y0, f, f);
      if (node.value){
        ctx.fillStyle="#FF6600"; //orange for on
        ctx.fillRect(x0, y0, f, f);
      }
    }

    function draw_led(node){
      var x0 = f / 30 * (px + node.pos[0]);
      var y0 = f / 30 * (py + node.pos[1]);

      ctx.beginPath();

      ctx.moveTo(x0 + f, y0 + f/2);
      ctx.arc(x0 + f/2, y0 + f/2, f/2, 0 * Math.PI, 2 * Math.PI);
      ctx.stroke();
      if (node.value){
        ctx.fillStyle="#00B8E6"; //blue for on
        ctx.fill();
      }
    }

    function draw_any_node(node) {
      switch (node.type) {
        case 'io':
          draw_switch(node);
          break;
        case 'led':
          draw_led(node);
          break;
        default:
          draw_node(node);
          break;
      }
    }

    function draw_node(node) {
      var x0 = f / 30 * (px + node.pos[0]);
      var y0 = f / 30 * (py + node.pos[1]);

      ctx.fillStyle="#FF6600"; //orange
      ctx.fillRect(x0, y0, 0.3*f, 0.3*f);
    }

    function render() {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      for (var i = 0; i < circuit.gates.length; i++) {
        if (selectedObjs.indexOf(circuit.gates[i]) > -1) {
          ctx.strokeStyle = "magenta";
        }
        draw_gate(circuit.gates[i]);
        ctx.strokeStyle = "black";
      }

      for (var i = 0; i < circuit.nodes.length; i++) {
          if (selectedObjs.indexOf(circuit.nodes[i]) > -1) {
            ctx.strokeStyle = "magenta";
          }
          draw_any_node(circuit.nodes[i]);
          ctx.strokeStyle = "black";
      }

      if (mouseDown && !spaceDown && !movingGroup) {
        ctx.fillStyle = "rgba(0, 1, 1, 0.2)";
        ctx.fillRect(mdx, mdy, mx - mdx, my - mdy);
      }

      draw_buttons();
    }

    function checkLeftButtons() {
      if (mx >= leftMargin && mx <= leftMargin + buttonWidth) {
        if (my % (buttonSpacing + buttonHeight) >= buttonSpacing) {
          var i = Math.floor(my / (buttonSpacing + buttonHeight));
          if (i < leftButtons.length) {
            leftIndex = i;
            return;
          }
        }
      }
      leftIndex = -1;
    }

    function objectsUnderCursor() {
      objs = [];
      for (var i = 0; i < circuit.gates.length; i++) {
        var x0 = f / 30 * (px + circuit.gates[i].pos[0]);
        var y0 = f / 30 * (py + circuit.gates[i].pos[1]);
        if (mx >= x0 && my >= y0 && mx <= x0 + 1.5 * f && my <= y0 + f) {
          objs.push(circuit.gates[i]);
        }
      }
      for (var i = 0; i < circuit.nodes.length; i++) {
        var x0 = f / 30 * (px + circuit.nodes[i].pos[0]);
        var y0 = f / 30 * (px + circuit.nodes[i].pos[1]);
        var size;
        if (circuit.nodes[i].type == undefined) {
          size = 0.2 * f;
        } else {
          size = 2 * f;
        }
        if (mx >= x0 && my >= y0 && mx <= x0 + size && my <= y0 + size) {
          objs.push(circuit.nodes[i]);
        }
      }
      return objs;
    }

    function objectsInMarquee() {
      objs = [];
      var xa = Math.min(mx, mdx);
      var xb = Math.max(mx, mdx);
      var ya = Math.min(my, mdy);
      var yb = Math.max(my, mdy);
      for (var i = 0; i < circuit.gates.length; i++) {
        var x0 = f / 30 * (px + circuit.gates[i].pos[0]);
        var y0 = f / 30 * (py + circuit.gates[i].pos[1]);
        if (x0 >= xa && y0 >= ya && x0 + 1.5 * f <= xb && y0 + f <= yb) {
          objs.push(circuit.gates[i]);
        }
      }
      for (var i = 0; i < circuit.nodes.length; i++) {
        var x0 = f / 30 * (px + circuit.nodes[i].pos[0]);
        var y0 = f / 30 * (px + circuit.nodes[i].pos[1]);
        var size;
        if (circuit.nodes[i].type == undefined) {
          size = 0.2 * f;
        } else {
          size = 2 * f;
        }
        if (x0 >= xa && y0 >= ya && x0 + size <= xb && y0 + size <= yb) {
          objs.push(circuit.nodes[i]);
        }
      }
      return objs;
    }

    function simulate() {
      updateAgenda();
      for (var i = 0; i < agenda.length; i++) {
        render();
        var tasks = agenda[i];
        for (var j = 0; j < tasks.length; j++) {
          apply(tasks[j]);
        }
      }
    }

    function step() {
      if (agenda.length === 0) {
        toggleSimulation();
      } else {
        for (var i = 0; i < agenda[0].length; i++) {
          apply(agenda[0][i]);
        }
        shift(agenda);
      }
    }

    function toggleSimulation() {
      simulating = !simulating;
      if (simulating) {
        leftButtons[0].label = "STOP";
        updateAgenda();
        intervalID = setInterval(step, stepDelay);
      } else {
        leftButtons[0].label = "GO";
        agenda = [];
        if (intervalID != undefined) {
          clearInterval(intervalID);
          intervalID = undefined;
        }
      }
      render();
    }

    $('#canvas').mousedown(function(e) {
      mouseDown = true;
      mdx = e.offsetX;
      mdy = e.offsetY;

      if (leftIndex !== -1) {
        if (!simulating || leftIndex == 0) {
          leftButtons[leftIndex].action();
          render();
        }
        return;
      }

      if (simulating) {
        return;
      }

      if (!spaceDown) {
        var objs = objectsUnderCursor();
        if (objs.length > 0) {
          movingGroup = true;
          var existing = false
          for (var i = 0; i < objs.length; i++) {
            for (var j = 0; j < selectedObjs.length; j++) {
              if (objs[i] == selectedObjs[j]) {
                existing = true;
                break;
              }
            }
          }
          if (!existing) {
            if (shiftDown) {
              selectedObjs.push(objs[0]);
            } else {
              selectedObjs = [objs[0]];
            }
          }
        }
      }

      render();
    });

    $('#canvas').mousemove(function(e) {
      if (mouseDown) {
        if (spaceDown) {
          px += (30 / f) * (e.offsetX - mx);
          py += (30 / f) * (e.offsetY - my);
        } else if (movingGroup) {
          for (var i = 0; i < selectedObjs.length; i++) {
            selectedObjs[i].pos[0] += (30 / f) * (e.offsetX - mx);
            selectedObjs[i].pos[1] += (30 / f) * (e.offsetY - my);
          }
        }
      }

      mx = e.offsetX;
      my = e.offsetY;

      var old = leftIndex;
      checkLeftButtons();
      if (old !== leftIndex || mouseDown) {
        render();
      }
    });

    $('#canvas').mouseup(function(e) {
      if (leftIndex === -1) {
        if (Math.abs(mx - mdx) <= smallMovement && Math.abs(my - mdy) <= smallMovement) {
          var objs = objectsUnderCursor();
          if (objs.length > 0) {
            if (shiftDown) {
              selectedObjs.push(objs[0]);
            } else {
              selectedObjs = [objs[0]];
            }
          } else {
            selectedObjs = [];
          }
        } else if (!spaceDown && !movingGroup) {
          selectedObjs = objectsInMarquee();
        }
      }
      mouseDown = false;
      movingGroup = false;
      render();
    });

    $(document).keydown(function(e) {
      if (e.which === 32) {
        e.preventDefault();
        spaceDown = true;
      } else if (e.which == 16) {
        shiftDown = true;
      }
    });

    $(document).keyup(function(e) {
      if (e.which === 32) {
        e.preventDefault();
        spaceDown = false;
      } else if (e.which == 16) {
        shiftDown = false;
      }
    });

    render();
});
<|endoftext|>


<commit_before>'use strict';

function duel(){
    if(!can_duel){
        return;
    }

    can_duel = false;
    var healths = [
      players[0]['health-current'],
      players[1]['health-current'],
    ];
    var manas = [
      players[0]['mana-current'],
      players[1]['mana-current'],
    ];
    var output = '

Player 0 goes first!';
    var turn_counter = 0;

    while(healths[0] > 0
      && healths[1] > 0
      && turn_counter < 100){
        for(var player in players){
            if(core_random_integer() < players[player]['health-regen%']
              && healths[player] < players[player]['health']){
                healths[player] = Math.min(
                  players[player]['health'],
                  healths[player] + 1
                );

                output += '
Player '
                  + player
                  + ' regenerated 1 health. '
                  + healths[player]
                  + '/'
                  + players[player]['health'];
            }

            if(healths[player] <= 0){
                output += '
Player '
                  + player
                  + ' lost the duel.'

                continue;
            }

            if(core_random_integer() < players[player]['mana-regen%']
              && manas[player] < players[player]['mana']){
                manas[player] = Math.min(
                  players[player]['mana'],
                  manas[player] + 1
                );

                output += '
Player '
                  + player
                  + ' replenished 1 mana. '
                  + manas[player]
                  + '/'
                  + players[player]['mana'];
            }

            if(core_random_integer() <= players[player]['hit%']
              && core_random_integer() >= players[1 - player]['dodge%']){
                var damage = Math.max(0, players[player]['damage'] - players[1 - player]['defense']);

                healths[1 - player] -= damage;

                output += '
Player '
                  + player
                  + ' hits Player '
                  + (1 - player)
                  + ' for '
                  + damage
                  + ' damage. '
                  + healths[1 - player]
                  + '/'
                  + players[1 - player]['health'];

                if(players[1 - player]['reflect'] > 0){
                    damage = Math.max(0, players[1 - player]['reflect'] - players[1 - player]['defense']);

                    healths[player] -= damage;

                    output += '
Player '
                      + (1 - player)
                      + ' reflects '
                      + damage
                      + ' damage to Player '
                      + player
                      + '. '
                      + healths[player]
                      + '/'
                      + players[player]['health'];
                }

            }else{
                output += '
Player '
                  + player
                  + ' missed.';
            }
        }

        turn_counter += 1;
    }

    if(turn_counter > 99){
        output += '
Tie!';
    }

    output += '
';

    document.getElementById('duel').innerHTML = output;
}

function load(){
    var imported = window.prompt(
      'Input JSON for both players:',
      ''
    );
    if(imported == null
      || imported.length === 0){
        return;
    }
    players = JSON.parse(imported);

    setmode(0);
}

function repo_escape(){
    if(mode > 0){
        setmode(0);
    }
}

function repo_init(){
    core_events_bind({
      'keybinds': {
        13: {
          'todo': function(){
              if(mode > 0){
                  duel();

              }else{
                  setmode(1);
              }
          },
        },
      },
    });
    reset(true);
    setmode(0);
}

function reset(skip){
    if(!skip
      && !window.confirm('Reset?')){
        return;
    }

    for(var player in players){
        for(var stat in stats){
            players[player][stat] = stats[stat];
        }
    }

    setmode(0);
}

function save(){
    window.prompt(
      'Save this String:',
      JSON.stringify(players)
    );
}

function setmode(newmode){
    mode = newmode;

    var output = '';

    if(mode > 0){
        for(var player in players){
            for(var stat in stats){
                players[player][stat] = parseInt(
                  document.getElementById(player + '-' + stat).value,
                  10
                );
            }
        }

        can_duel = true;

        output += 'Edit Players'
          + ' | Save'
          + '

Player 0
';

        for(var stat in stats){
            output += '
<input readonly value='
              + players[0][stat]
              + '> '
              + stat;
        }

        output += '

Player 1
';

        for(var stat in stats){
            output += '
<input readonly value='
              + players[1][stat]
              + '> '
              + stat;
        }

        output += '

Duel
';

    }else{
        output += 'Duel

Player 0
';

        for(var stat in stats){
            output += '
<input id="0-'
              + stat
              + '" value='
              + players[0][stat]
              + '> '
              + stat;
        }

        output += '

Player 1
';

        for(var stat in stats){
            output += '
<input id="1-'
              + stat
              + '" value='
              + players[1][stat]
              + '> '
              + stat;
        }

        output += '

Reset'
          + ' | Load';
    }

    document.body.innerHTML = output;
}

var can_duel = false;
var mode = 0;
var players = [
  {},
  {},
];
var stats = {
  'damage': 1,
  'defense': 0,
  'dodge%': 0,
  'health': 10,
  'health-current': 10,
  'health-regen%': 0,
  'hit%': 100,
  'mana': 5,
  'mana-current': 5,
  'mana-regen%': 0,
  'reflect': 0,
};
<commit_msg>added wrapper div<commit_after>'use strict';

function duel(){
    if(!can_duel){
        return;
    }

    can_duel = false;
    var healths = [
      players[0]['health-current'],
      players[1]['health-current'],
    ];
    var manas = [
      players[0]['mana-current'],
      players[1]['mana-current'],
    ];
    var output = '

Player 0 goes first!';
    var turn_counter = 0;

    while(healths[0] > 0
      && healths[1] > 0
      && turn_counter < 100){
        for(var player in players){
            if(core_random_integer() < players[player]['health-regen%']
              && healths[player] < players[player]['health']){
                healths[player] = Math.min(
                  players[player]['health'],
                  healths[player] + 1
                );

                output += '
Player '
                  + player
                  + ' regenerated 1 health. '
                  + healths[player]
                  + '/'
                  + players[player]['health'];
            }

            if(healths[player] <= 0){
                output += '
Player '
                  + player
                  + ' lost the duel.'

                continue;
            }

            if(core_random_integer() < players[player]['mana-regen%']
              && manas[player] < players[player]['mana']){
                manas[player] = Math.min(
                  players[player]['mana'],
                  manas[player] + 1
                );

                output += '
Player '
                  + player
                  + ' replenished 1 mana. '
                  + manas[player]
                  + '/'
                  + players[player]['mana'];
            }

            if(core_random_integer() <= players[player]['hit%']
              && core_random_integer() >= players[1 - player]['dodge%']){
                var damage = Math.max(0, players[player]['damage'] - players[1 - player]['defense']);

                healths[1 - player] -= damage;

                output += '
Player '
                  + player
                  + ' hits Player '
                  + (1 - player)
                  + ' for '
                  + damage
                  + ' damage. '
                  + healths[1 - player]
                  + '/'
                  + players[1 - player]['health'];

                if(players[1 - player]['reflect'] > 0){
                    damage = Math.max(0, players[1 - player]['reflect'] - players[1 - player]['defense']);

                    healths[player] -= damage;

                    output += '
Player '
                      + (1 - player)
                      + ' reflects '
                      + damage
                      + ' damage to Player '
                      + player
                      + '. '
                      + healths[player]
                      + '/'
                      + players[player]['health'];
                }

            }else{
                output += '
Player '
                  + player
                  + ' missed.';
            }
        }

        turn_counter += 1;
    }

    if(turn_counter > 99){
        output += '
Tie!';
    }

    output += '
';

    document.getElementById('duel').innerHTML = output;
}

function load(){
    var imported = window.prompt(
      'Input JSON for both players:',
      ''
    );
    if(imported == null
      || imported.length === 0){
        return;
    }
    players = JSON.parse(imported);

    setmode(0);
}

function repo_escape(){
    if(mode > 0){
        setmode(0);
    }
}

function repo_init(){
    document.body.appendChild(core_html({
      'properties': {
        'id': 'wrapper',
      },
    }));

    core_events_bind({
      'keybinds': {
        13: {
          'todo': function(){
              if(mode > 0){
                  duel();

              }else{
                  setmode(1);
              }
          },
        },
      },
    });
    reset(true);
    setmode(0);
}

function reset(skip){
    if(!skip
      && !window.confirm('Reset?')){
        return;
    }

    for(var player in players){
        for(var stat in stats){
            players[player][stat] = stats[stat];
        }
    }

    setmode(0);
}

function save(){
    window.prompt(
      'Save this String:',
      JSON.stringify(players)
    );
}

function setmode(newmode){
    mode = newmode;

    var output = '';

    if(mode > 0){
        for(var player in players){
            for(var stat in stats){
                players[player][stat] = parseInt(
                  document.getElementById(player + '-' + stat).value,
                  10
                );
            }
        }

        can_duel = true;

        output += 'Edit Players'
          + ' | Save'
          + '

Player 0
';

        for(var stat in stats){
            output += '
<input readonly value='
              + players[0][stat]
              + '> '
              + stat;
        }

        output += '

Player 1
';

        for(var stat in stats){
            output += '
<input readonly value='
              + players[1][stat]
              + '> '
              + stat;
        }

        output += '

Duel
';

    }else{
        output += 'Duel

Player 0
';

        for(var stat in stats){
            output += '
<input id="0-'
              + stat
              + '" value='
              + players[0][stat]
              + '> '
              + stat;
        }

        output += '

Player 1
';

        for(var stat in stats){
            output += '
<input id="1-'
              + stat
              + '" value='
              + players[1][stat]
              + '> '
              + stat;
        }

        output += '

Reset'
          + ' | Load';
    }

    document.getElementById('wrapper').innerHTML = output;
}

var can_duel = false;
var mode = 0;
var players = [
  {},
  {},
];
var stats = {
  'damage': 1,
  'defense': 0,
  'dodge%': 0,
  'health': 10,
  'health-current': 10,
  'health-regen%': 0,
  'hit%': 100,
  'mana': 5,
  'mana-current': 5,
  'mana-regen%': 0,
  'reflect': 0,
};
<|endoftext|>


<commit_before>var async = require('async');
var keystone = require('../..');
var ReactEngine = require('react-engine');
var view = require('react-engine/lib/expressView');
var engine = ReactEngine.server.create({});
var request = require('superagent');
var moment = require('moment');
var mongoose = require('mongoose');
var path = require('path');
var keystoneNightwatchE2e = require('keystone-nightwatch-e2e');

// Set app-specific env for nightwatch session
process.env['SELENIUM_SERVER'] = keystoneNightwatchE2e.seleniumPath;
process.env['PAGE_OBJECTS_PATH'] = keystoneNightwatchE2e.pageObjectsPath;

// determine the mongo uri and database name
var dbName = '/e2e' + (process.env.KEYSTONEJS_PORT || 3000);
var mongoUri = 'mongodb://' + (process.env.KEYSTONEJS_HOST || 'localhost') + dbName;

// initialize keystone
keystone.init({
	'name': 'e2e',
	'brand': 'e2e',

	'host': process.env.KEYSTONEJS_HOST || 'localhost',
	'port': process.env.KEYSTONEJS_PORT || 3000,

	'mongo': mongoUri,

	'less': 'public',
	'static': 'public',
	'favicon': 'adminuiCustom/favicon.ico',
	'views': 'templates/views',
	'view engine': '.jsx',
	'custom engine': engine,
	'view': view,

	'auto update': true,
	'session': true,
	'auth': true,
	'user model': 'User',
	'cookie secret': 'Secret',
	'adminui custom styles': 'adminuiCustom/styles.less',

	'cloudinary config': 'cloudinary://api_key:api_secret@cloud_name',
});

// import app models
keystone.import('models');

// setup any custom routes
keystone.set('routes', require('./routes'));

// setup application adminui navigation
keystone.set('nav', {
	'access': [
		'users',
	],
	'fields': [
		'booleans',
		'cloudinary-images',
		'cloudinary-image-multiples',
		'codes',
		'colors',
		'dates',
		'date-arrays',
		'datetimes',
		'emails',
		'files',
		'geo-points',
		'htmls',
		'keys',
		'locations',
		'markdowns',
		'money',
		'names',
		'numbers',
		'number-arrays',
		'passwords',
		'relationships',
		'selects',
		'texts',
		'text-arrays',
		'textareas',
		'urls',
	],
	'Miscs': [
		'date-field-maps',
		'depends-ons',
		'no-default-columns',
		'inline-relationships',
		'many-relationships',
		'hidden-relationships',
		'source-relationships',
		'target-relationships',
	]
});

// Function that drops the test database before starting testing
function dropTestDatabase(done) {
	console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: dropping test database');

	mongoose.connect(mongoUri,function(err){
		if (!err) {
			mongoose.connection.db.dropDatabase(function (err) {
				mongoose.connection.close(function(err) {
					done(err);
				})
			});
		} else {
			console.error([moment().format('HH:mm:ss:SSS')] + ' e2e: failed to connect to mongo: ' + err);
			done(err);
		}
	});
}

// Function that checks if keystone is ready before starting testing
function checkKeystoneReady (done) {
	async.retry({
		times: 10,
		interval: 3000
	}, function(done, result) {
		console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: checking if KeystoneJS ready for request');
		request
			.get('http://' + keystone.get('host') + ':' + keystone.get('port') + '/keystone')
			.end(done);
	}, function (err, result) {
		if (!err) {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS Ready!');
			done();
		} else {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS does not appear ready!');
			done(err);
		}
	})
}

// Function that starts the e2e common framework
function runE2E (options, done) {
	console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: starting tests...');

	keystoneNightwatchE2e.startE2E(options, done);
}

// Function that starts keystone
function runKeystone(cb) {
	console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: starting KeystoneJS...');

	keystone.start({
		onMount: function () {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS mounted Successfuly');
		},
		onStart: function() {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS Started Successfully');
			cb();
		},
	});
}

// Function that bootstraps the e2e test service
function start() {
	var runTests = process.argv.indexOf('--notest') === -1;
	var dropDB = process.argv.indexOf('--nodrop') === -1;
	var runSelenium = !(process.argv.indexOf('--selenium-in-background') === -1);

	async.series([

		function (cb) {
			if (dropDB) {
				dropTestDatabase(cb);
			}	else {
				cb();
			}
		},

		function (cb) {
			runKeystone(cb);
		},

		function (cb) {
			checkKeystoneReady(cb);
		},

		function (cb) {
			if (runTests) {
				runE2E({
					keystone: keystone,
					runSelenium: runSelenium
				}, cb);
			} else {
				cb();
			}
		}

	], function(err) {
		var exitProcess = false;
		if (err) {
			console.error([moment().format('HH:mm:ss:SSS')] + ' e2e: ' + err);
			exitProcess = true;
		}
		if (runTests) {
			exitProcess = true;
		}
		if (exitProcess) {
			console.error([moment().format('HH:mm:ss:SSS')] + ' e2e: exiting');
			process.exit();
		}
	});
}

start();
<commit_msg>test(e2e):  fix mongoose errors during startup.<commit_after>var async = require('async');
var keystone = require('../..');
var ReactEngine = require('react-engine');
var view = require('react-engine/lib/expressView');
var engine = ReactEngine.server.create({});
var request = require('superagent');
var moment = require('moment');
var mongoose = require('mongoose');
var path = require('path');
var keystoneNightwatchE2e = require('keystone-nightwatch-e2e');

// Set app-specific env for nightwatch session
process.env['SELENIUM_SERVER'] = keystoneNightwatchE2e.seleniumPath;
process.env['PAGE_OBJECTS_PATH'] = keystoneNightwatchE2e.pageObjectsPath;

// determine the mongo uri and database name
var dbName = '/e2e' + (process.env.KEYSTONEJS_PORT || 3000);
var mongoUri = 'mongodb://' + (process.env.KEYSTONEJS_HOST || 'localhost') + dbName;

// Function that drops the test database before starting testing
function dropTestDatabase(done) {
	console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: dropping test database: ' + mongoUri);

	mongoose.connect(mongoUri,function(err){
		if (!err) {
			mongoose.connection.db.dropDatabase(function (err) {
				if (!err) {
					console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: dropped test database: ' + mongoUri);
				}
				mongoose.connection.close(function(err) {
					done(err);
				})
			});
		} else {
			console.error([moment().format('HH:mm:ss:SSS')] + ' e2e: failed to connect to mongo: ' + err);
			done(err);
		}
	});
}

// Function that checks if keystone is ready before starting testing
function checkKeystoneReady (done) {
	async.retry({
		times: 10,
		interval: 3000
	}, function(done, result) {
		console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: checking if KeystoneJS ready for request');
		request
			.get('http://' + keystone.get('host') + ':' + keystone.get('port') + '/keystone')
			.end(done);
	}, function (err, result) {
		if (!err) {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS Ready!');
			done();
		} else {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS does not appear ready!');
			done(err);
		}
	})
}

// Function that starts the e2e common framework
function runE2E (options, done) {
	console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: starting tests...');

	keystoneNightwatchE2e.startE2E(options, done);
}

// Function that starts keystone
function runKeystone(cb) {
	console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: starting KeystoneJS...');

// initialize keystone
	keystone.init({
		'name': 'e2e',
		'brand': 'e2e',

		'host': process.env.KEYSTONEJS_HOST || 'localhost',
		'port': process.env.KEYSTONEJS_PORT || 3000,

		'mongo': mongoUri,

		'less': 'public',
		'static': 'public',
		'favicon': 'adminuiCustom/favicon.ico',
		'views': 'templates/views',
		'view engine': '.jsx',
		'custom engine': engine,
		'view': view,

		'auto update': true,
		'session': true,
		'auth': true,
		'user model': 'User',
		'cookie secret': 'Secret',
		'adminui custom styles': 'adminuiCustom/styles.less',

		'cloudinary config': 'cloudinary://api_key:api_secret@cloud_name',
	});

// import app models
	keystone.import('models');

// setup any custom routes
	keystone.set('routes', require('./routes'));

// setup application adminui navigation
	keystone.set('nav', {
		'access': [
			'users',
		],
		'fields': [
			'booleans',
			'cloudinary-images',
			'cloudinary-image-multiples',
			'codes',
			'colors',
			'dates',
			'date-arrays',
			'datetimes',
			'emails',
			'files',
			'geo-points',
			'htmls',
			'keys',
			'locations',
			'markdowns',
			'money',
			'names',
			'numbers',
			'number-arrays',
			'passwords',
			'relationships',
			'selects',
			'texts',
			'text-arrays',
			'textareas',
			'urls',
		],
		'Miscs': [
			'date-field-maps',
			'depends-ons',
			'no-default-columns',
			'inline-relationships',
			'many-relationships',
			'hidden-relationships',
			'source-relationships',
			'target-relationships',
		]
	});

	keystone.start({
		onMount: function () {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS mounted Successfuly');
		},
		onStart: function() {
			console.log([moment().format('HH:mm:ss:SSS')] + ' e2e: KeystoneJS Started Successfully');
			cb();
		},
	});
}

// Function that bootstraps the e2e test service
function start() {
	var runTests = process.argv.indexOf('--notest') === -1;
	var dropDB = process.argv.indexOf('--nodrop') === -1;
	var runSelenium = !(process.argv.indexOf('--selenium-in-background') === -1);

	async.series([

		function (cb) {
			if (dropDB) {
				dropTestDatabase(cb);
			}	else {
				cb();
			}
		},

		function (cb) {
			runKeystone(cb);
		},

		function (cb) {
			checkKeystoneReady(cb);
		},

		function (cb) {
			if (runTests) {
				runE2E({
					keystone: keystone,
					runSelenium: runSelenium
				}, cb);
			} else {
				cb();
			}
		}

	], function(err) {
		var exitProcess = false;
		if (err) {
			console.error([moment().format('HH:mm:ss:SSS')] + ' e2e: ' + err);
			exitProcess = true;
		}
		if (runTests) {
			exitProcess = true;
		}
		if (exitProcess) {
			console.error([moment().format('HH:mm:ss:SSS')] + ' e2e: exiting');
			process.exit();
		}
	});
}

start();
<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class exmo extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'exmo',
            'name': 'EXMO',
            'countries': [ 'ES', 'RU' ], // Spain, Russia
            'rateLimit': 1000, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second
            'version': 'v1',
            'has': {
                'CORS': false,
                'fetchOrder': true,
                'fetchOpenOrders': true,
                'fetchOrderBooks': true,
                'fetchTickers': true,
                'withdraw': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg',
                'api': 'https://api.exmo.com',
                'www': 'https://exmo.me',
                'doc': [
                    'https://exmo.me/en/api_doc',
                    'https://github.com/exmo-dev/exmo_api_lib/tree/master/nodejs',
                ],
                'fees': 'https://exmo.com/en/docs/fees',
            },
            'api': {
                'public': {
                    'get': [
                        'currency',
                        'order_book',
                        'pair_settings',
                        'ticker',
                        'trades',
                    ],
                },
                'private': {
                    'post': [
                        'user_info',
                        'order_create',
                        'order_cancel',
                        'user_open_orders',
                        'user_trades',
                        'user_cancelled_orders',
                        'order_trades',
                        'required_amount',
                        'deposit_address',
                        'withdraw_crypt',
                        'withdraw_get_txid',
                        'excode_create',
                        'excode_load',
                        'wallet_history',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.2 / 100,
                    'taker': 0.2 / 100,
                },
                'funding': {
                    'withdraw': {
                        'BTC': 0.001,
                        'LTC': 0.01,
                        'DOGE': 1,
                        'DASH': 0.01,
                        'ETH': 0.01,
                        'WAVES': 0.001,
                        'ZEC': 0.001,
                        'USDT': 25,
                        'XMR': 0.05,
                        'XRP': 0.02,
                        'KICK': 350,
                        'ETC': 0.01,
                        'BCH': 0.001,
                    },
                    'deposit': {
                        'USDT': 15,
                        'KICK': 50,
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetPairSettings ();
        let keys = Object.keys (markets);
        let result = [];
        for (let p = 0; p < keys.length; p++) {
            let id = keys[p];
            let market = markets[id];
            let symbol = id.replace ('_', '/');
            let [ base, quote ] = symbol.split ('/');
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'limits': {
                    'amount': {
                        'min': market['min_quantity'],
                        'max': market['max_quantity'],
                    },
                    'price': {
                        'min': market['min_price'],
                        'max': market['max_price'],
                    },
                    'cost': {
                        'min': market['min_amount'],
                        'max': market['max_amount'],
                    },
                },
                'precision': {
                    'amount': 8,
                    'price': 8,
                },
                'info': market,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostUserInfo ();
        let result = { 'info': response };
        let currencies = Object.keys (this.currencies);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let account = this.account ();
            if (currency in response['balances'])
                account['free'] = parseFloat (response['balances'][currency]);
            if (currency in response['reserved'])
                account['used'] = parseFloat (response['reserved'][currency]);
            account['total'] = this.sum (account['free'], account['used']);
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOrderBook (this.extend ({
            'pair': market['id'],
        }, params));
        let result = response[market['id']];
        let orderbook = this.parseOrderBook (result, undefined, 'bid', 'ask');
        return this.extend (orderbook, {
            'bids': this.sortBy (orderbook['bids'], 0, true),
            'asks': this.sortBy (orderbook['asks'], 0),
        });
    }

    async fetchOrderBooks (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let ids = undefined;
        if (!symbols) {
            ids = this.ids.join (',');
            // max URL length is 2083 symbols, including http schema, hostname, tld, etc...
            if (ids.length > 2048) {
                let numIds = this.ids.length;
                throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks');
            }
        } else {
            ids = this.marketIds (symbols);
            ids = ids.join (',');
        }
        let response = await this.publicGetOrderBook (this.extend ({
            'pair': ids,
        }, params));
        let result = {};
        ids = Object.keys (response);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let symbol = id;
            if (id in this.markets_by_id) {
                let market = this.markets_by_id[id];
                symbol = market['symbol'];
            }
            result[symbol] = this.parseOrderBook (response[id], undefined, 'bid', 'ask');
        }
        return result;
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = ticker['updated'] * 1000;
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': parseFloat (ticker['high']),
            'low': parseFloat (ticker['low']),
            'bid': parseFloat (ticker['buy_price']),
            'ask': parseFloat (ticker['sell_price']),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['last_trade']),
            'change': undefined,
            'percentage': undefined,
            'average': parseFloat (ticker['avg']),
            'baseVolume': parseFloat (ticker['vol']),
            'quoteVolume': parseFloat (ticker['vol_curr']),
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTicker (params);
        let result = {};
        let ids = Object.keys (response);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let market = this.markets_by_id[id];
            let symbol = market['symbol'];
            let ticker = response[id];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTicker (params);
        let market = this.market (symbol);
        return this.parseTicker (response[market['id']], market);
    }

    parseTrade (trade, market) {
        let timestamp = trade['date'] * 1000;
        return {
            'id': trade['trade_id'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'order': undefined,
            'type': undefined,
            'side': trade['type'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['quantity']),
            'cost': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTrades (this.extend ({
            'pair': market['id'],
        }, params));
        return this.parseTrades (response[market['id']], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let prefix = '';
        if (type === 'market')
            prefix = 'market_';
        if (typeof price === 'undefined')
            price = 0;
        let order = {
            'pair': this.marketId (symbol),
            'quantity': amount,
            'price': price,
            'type': prefix + side,
        };
        let response = await this.privatePostOrderCreate (this.extend (order, params));
        return {
            'info': response,
            'id': response['order_id'].toString (),
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostOrderCancel ({ 'order_id': id });
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        let market = undefined;
        if (typeof symbol !== 'undefined')
            market = this.market (symbol);
        let response = await this.privatePostOrderTrades (this.extend ({ 'id': id }, params));
        return this.parseOrder (response, market);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        let market = undefined;
        if (typeof symbol !== 'undefined') {
            await this.loadMarkets ();
            market = this.market (symbol);
        }
        let orders = await this.privatePostUserOpenOrders ();
        return this.parseOrders (orders, market, since, limit);
    }

    parseOrder (order, market = undefined) {
        let id = this.safeString (order, 'id');
        let timestamp = undefined;
        let iso8601 = undefined;
        let symbol = undefined;
        if (typeof market === 'undefined') {
            if ('in_currency' in order) {
                if ('out_currency' in order) {
                    let marketId = order['in_currency'] + '_' + order['out_currency'];
                    if (marketId in this.markets_by_id)
                        market = this.markets_by_id[marketId];
                }
            }
        }
        let amount = this.safeFloat (order, 'in_amount');
        let filled = 0.0;
        let trades = [];
        let transactions = this.safeValue (order, 'trades');
        let feeCost = undefined;
        let cost = undefined;
        if (typeof transactions !== 'undefined') {
            if (Array.isArray (transactions)) {
                for (let i = 0; i < transactions.length; i++) {
                    let trade = this.parseTrade (transactions[i], market);
                    if (typeof timestamp === 'undefined')
                        timestamp = 0;
                    if (timestamp < trade['timestamp'])
                        timestamp = trade['timestamp'];
                    filled += trade['amount'];
                    if (typeof feeCost === 'undefined')
                        feeCost = 0.0;
                    // feeCost += trade['fee']['cost'];
                    if (typeof cost === 'undefined')
                        cost = 0.0;
                    cost += trade['cost'];
                    trades.push (trade);
                }
            }
        }
        if (typeof timestamp !== 'undefined')
            iso8601 = this.iso8601 (timestamp);
        let remaining = undefined;
        if (typeof amount !== 'undefined')
            remaining = amount - filled;
        let status = undefined;
        if (filled === 0.0)
            status = 'open';
        else if (filled === amount)
            status = 'closed';
        else
            status = 'partial';
        let side = this.safeString (order, 'type');
        if (typeof market === 'undefined')
            market = this.getMarketFromTrades (trades);
        let feeCurrency = undefined;
        if (typeof market !== 'undefined') {
            symbol = market['symbol'];
            feeCurrency = market['quote'];
        }
        let price = undefined;
        if (typeof cost === 'undefined') {
            if (typeof price !== 'undefined')
                cost = price * filled;
        } else if (typeof price === 'undefined') {
            if (filled > 0)
                price = cost / filled;
        }
        let fee = {
            'cost': feeCost,
            'currency': feeCurrency,
        };
        return {
            'id': id,
            'datetime': iso8601,
            'timestamp': timestamp,
            'status': status,
            'symbol': symbol,
            'type': undefined,
            'side': side,
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'trades': trades,
            'fee': fee,
            'info': order,
        };
    }

    getMarketFromTrades (trades) {
        let tradesBySymbol = this.indexBy (trades, 'pair');
        let symbols = Object.keys (tradesBySymbol);
        let numSymbols = symbols.length;
        if (numSymbols === 1)
            return this.markets[symbols[0]];
        return undefined;
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {
            'amount': amount,
            'currency': currency,
            'address': address,
        };
        if (typeof tag !== 'undefined')
            request['invoice'] = tag;
        let result = await this.privatePostWithdrawCrypt (this.extend (request, params));
        return {
            'info': result,
            'id': result['task_id'],
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.version + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length)
                url += '?' + this.urlencode (params);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            body = this.urlencode (this.extend ({ 'nonce': nonce }, params));
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Key': this.apiKey,
                'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'),
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('result' in response) {
            if (response['result'])
                return response;
            throw new ExchangeError (this.id + ' ' + this.json (response));
        }
        return response;
    }
};
<commit_msg>added fetchMyTrades to exmo<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class exmo extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'exmo',
            'name': 'EXMO',
            'countries': [ 'ES', 'RU' ], // Spain, Russia
            'rateLimit': 1000, // once every 350 ms ≈ 180 requests per minute ≈ 3 requests per second
            'version': 'v1',
            'has': {
                'CORS': false,
                'fetchOrder': true,
                'fetchOpenOrders': true,
                'fetchOrderBooks': true,
                'fetchMyTrades': true,
                'fetchTickers': true,
                'withdraw': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg',
                'api': 'https://api.exmo.com',
                'www': 'https://exmo.me',
                'doc': [
                    'https://exmo.me/en/api_doc',
                    'https://github.com/exmo-dev/exmo_api_lib/tree/master/nodejs',
                ],
                'fees': 'https://exmo.com/en/docs/fees',
            },
            'api': {
                'public': {
                    'get': [
                        'currency',
                        'order_book',
                        'pair_settings',
                        'ticker',
                        'trades',
                    ],
                },
                'private': {
                    'post': [
                        'user_info',
                        'order_create',
                        'order_cancel',
                        'user_open_orders',
                        'user_trades',
                        'user_cancelled_orders',
                        'order_trades',
                        'required_amount',
                        'deposit_address',
                        'withdraw_crypt',
                        'withdraw_get_txid',
                        'excode_create',
                        'excode_load',
                        'wallet_history',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.2 / 100,
                    'taker': 0.2 / 100,
                },
                'funding': {
                    'withdraw': {
                        'BTC': 0.001,
                        'LTC': 0.01,
                        'DOGE': 1,
                        'DASH': 0.01,
                        'ETH': 0.01,
                        'WAVES': 0.001,
                        'ZEC': 0.001,
                        'USDT': 25,
                        'XMR': 0.05,
                        'XRP': 0.02,
                        'KICK': 350,
                        'ETC': 0.01,
                        'BCH': 0.001,
                    },
                    'deposit': {
                        'USDT': 15,
                        'KICK': 50,
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetPairSettings ();
        let keys = Object.keys (markets);
        let result = [];
        for (let p = 0; p < keys.length; p++) {
            let id = keys[p];
            let market = markets[id];
            let symbol = id.replace ('_', '/');
            let [ base, quote ] = symbol.split ('/');
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'limits': {
                    'amount': {
                        'min': market['min_quantity'],
                        'max': market['max_quantity'],
                    },
                    'price': {
                        'min': market['min_price'],
                        'max': market['max_price'],
                    },
                    'cost': {
                        'min': market['min_amount'],
                        'max': market['max_amount'],
                    },
                },
                'precision': {
                    'amount': 8,
                    'price': 8,
                },
                'info': market,
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostUserInfo ();
        let result = { 'info': response };
        let currencies = Object.keys (this.currencies);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            let account = this.account ();
            if (currency in response['balances'])
                account['free'] = parseFloat (response['balances'][currency]);
            if (currency in response['reserved'])
                account['used'] = parseFloat (response['reserved'][currency]);
            account['total'] = this.sum (account['free'], account['used']);
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetOrderBook (this.extend ({
            'pair': market['id'],
        }, params));
        let result = response[market['id']];
        let orderbook = this.parseOrderBook (result, undefined, 'bid', 'ask');
        return this.extend (orderbook, {
            'bids': this.sortBy (orderbook['bids'], 0, true),
            'asks': this.sortBy (orderbook['asks'], 0),
        });
    }

    async fetchOrderBooks (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let ids = undefined;
        if (!symbols) {
            ids = this.ids.join (',');
            // max URL length is 2083 symbols, including http schema, hostname, tld, etc...
            if (ids.length > 2048) {
                let numIds = this.ids.length;
                throw new ExchangeError (this.id + ' has ' + numIds.toString () + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks');
            }
        } else {
            ids = this.marketIds (symbols);
            ids = ids.join (',');
        }
        let response = await this.publicGetOrderBook (this.extend ({
            'pair': ids,
        }, params));
        let result = {};
        ids = Object.keys (response);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let symbol = id;
            if (id in this.markets_by_id) {
                let market = this.markets_by_id[id];
                symbol = market['symbol'];
            }
            result[symbol] = this.parseOrderBook (response[id], undefined, 'bid', 'ask');
        }
        return result;
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = ticker['updated'] * 1000;
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': parseFloat (ticker['high']),
            'low': parseFloat (ticker['low']),
            'bid': parseFloat (ticker['buy_price']),
            'ask': parseFloat (ticker['sell_price']),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['last_trade']),
            'change': undefined,
            'percentage': undefined,
            'average': parseFloat (ticker['avg']),
            'baseVolume': parseFloat (ticker['vol']),
            'quoteVolume': parseFloat (ticker['vol_curr']),
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTicker (params);
        let result = {};
        let ids = Object.keys (response);
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let market = this.markets_by_id[id];
            let symbol = market['symbol'];
            let ticker = response[id];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetTicker (params);
        let market = this.market (symbol);
        return this.parseTicker (response[market['id']], market);
    }

    parseTrade (trade, market) {
        let timestamp = trade['date'] * 1000;
        return {
            'id': trade['trade_id'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'order': undefined,
            'type': undefined,
            'side': trade['type'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['quantity']),
            'cost': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTrades (this.extend ({
            'pair': market['id'],
        }, params));
        return this.parseTrades (response[market['id']], market, since, limit);
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {};
        let market = undefined;
        if (typeof symbol !== 'undefined') {
            market = this.market (symbol);
            request['pair'] = market['id'];
        }
        let response = await this.privatePostUserTrades (this.extend (request, params));
        return this.parseTrades (response, market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let prefix = '';
        if (type === 'market')
            prefix = 'market_';
        if (typeof price === 'undefined')
            price = 0;
        let order = {
            'pair': this.marketId (symbol),
            'quantity': amount,
            'price': price,
            'type': prefix + side,
        };
        let response = await this.privatePostOrderCreate (this.extend (order, params));
        return {
            'info': response,
            'id': response['order_id'].toString (),
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostOrderCancel ({ 'order_id': id });
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        let market = undefined;
        if (typeof symbol !== 'undefined')
            market = this.market (symbol);
        let response = await this.privatePostOrderTrades (this.extend ({ 'id': id }, params));
        return this.parseOrder (response, market);
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        let market = undefined;
        if (typeof symbol !== 'undefined') {
            await this.loadMarkets ();
            market = this.market (symbol);
        }
        let orders = await this.privatePostUserOpenOrders ();
        return this.parseOrders (orders, market, since, limit);
    }

    parseOrder (order, market = undefined) {
        let id = this.safeString (order, 'id');
        let timestamp = undefined;
        let iso8601 = undefined;
        let symbol = undefined;
        if (typeof market === 'undefined') {
            if ('in_currency' in order) {
                if ('out_currency' in order) {
                    let marketId = order['in_currency'] + '_' + order['out_currency'];
                    if (marketId in this.markets_by_id)
                        market = this.markets_by_id[marketId];
                }
            }
        }
        let amount = this.safeFloat (order, 'in_amount');
        let filled = 0.0;
        let trades = [];
        let transactions = this.safeValue (order, 'trades');
        let feeCost = undefined;
        let cost = undefined;
        if (typeof transactions !== 'undefined') {
            if (Array.isArray (transactions)) {
                for (let i = 0; i < transactions.length; i++) {
                    let trade = this.parseTrade (transactions[i], market);
                    if (typeof timestamp === 'undefined')
                        timestamp = 0;
                    if (timestamp < trade['timestamp'])
                        timestamp = trade['timestamp'];
                    filled += trade['amount'];
                    if (typeof feeCost === 'undefined')
                        feeCost = 0.0;
                    // feeCost += trade['fee']['cost'];
                    if (typeof cost === 'undefined')
                        cost = 0.0;
                    cost += trade['cost'];
                    trades.push (trade);
                }
            }
        }
        if (typeof timestamp !== 'undefined')
            iso8601 = this.iso8601 (timestamp);
        let remaining = undefined;
        if (typeof amount !== 'undefined')
            remaining = amount - filled;
        let status = undefined;
        if (filled === 0.0)
            status = 'open';
        else if (filled === amount)
            status = 'closed';
        else
            status = 'partial';
        let side = this.safeString (order, 'type');
        if (typeof market === 'undefined')
            market = this.getMarketFromTrades (trades);
        let feeCurrency = undefined;
        if (typeof market !== 'undefined') {
            symbol = market['symbol'];
            feeCurrency = market['quote'];
        }
        let price = undefined;
        if (typeof cost === 'undefined') {
            if (typeof price !== 'undefined')
                cost = price * filled;
        } else if (typeof price === 'undefined') {
            if (filled > 0)
                price = cost / filled;
        }
        let fee = {
            'cost': feeCost,
            'currency': feeCurrency,
        };
        return {
            'id': id,
            'datetime': iso8601,
            'timestamp': timestamp,
            'status': status,
            'symbol': symbol,
            'type': undefined,
            'side': side,
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'trades': trades,
            'fee': fee,
            'info': order,
        };
    }

    getMarketFromTrades (trades) {
        let tradesBySymbol = this.indexBy (trades, 'pair');
        let symbols = Object.keys (tradesBySymbol);
        let numSymbols = symbols.length;
        if (numSymbols === 1)
            return this.markets[symbols[0]];
        return undefined;
    }

    async withdraw (currency, amount, address, tag = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {
            'amount': amount,
            'currency': currency,
            'address': address,
        };
        if (typeof tag !== 'undefined')
            request['invoice'] = tag;
        let result = await this.privatePostWithdrawCrypt (this.extend (request, params));
        return {
            'info': result,
            'id': result['task_id'],
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.version + '/' + path;
        if (api === 'public') {
            if (Object.keys (params).length)
                url += '?' + this.urlencode (params);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            body = this.urlencode (this.extend ({ 'nonce': nonce }, params));
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Key': this.apiKey,
                'Sign': this.hmac (this.encode (body), this.encode (this.secret), 'sha512'),
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('result' in response) {
            if (response['result'])
                return response;
            throw new ExchangeError (this.id + ' ' + this.json (response));
        }
        return response;
    }
};
<|endoftext|>


<commit_before>exports = module.exports = function(serializeClientCb, deserializeClientCb) {
  var Encoder = require('tokens').Encoder;

  var encoder = new Encoder();

  // TODO: Make these plugins

  encoder.use('jwt', require('tokens').encode.sat());

  return encoder;
}

exports['@provides'] = 'io.modulate.security.tokens.Encoder';
// TODO: Require by interface
exports['@require'] = [];
exports['@singleton'] = true;

<commit_msg>Token encoder.<commit_after>exports = module.exports = function(serializeClientCb, deserializeClientCb) {
  var Encoder = require('tokens').Encoder;

  var encoder = new Encoder();

  // TODO: Make these plugins

  encoder.use('jwt', require('tokens').encode.sat({ issuer: 'me', key: 'secret', algorithm: 'HS256' }));

  return encoder;
}

exports['@provides'] = 'io.modulate.security.tokens.Encoder';
// TODO: Require by interface
exports['@require'] = [];
exports['@singleton'] = true;

<|endoftext|>