var util = require('../../../util');
var RepulsionMixin = require('./RepulsionMixin');
var HierarchialRepulsionMixin = require('./HierarchialRepulsionMixin');
var BarnesHutMixin = require('./BarnesHutMixin');

/**
 * Toggling barnes Hut calculation on and off.
 *
 * @private
 */
exports._toggleBarnesHut = function () {
  this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  this._loadSelectedForceSolver();
  this.moving = true;
  this.start();
};


/**
 * This loads the node force solver based on the barnes hut or repulsion algorithm
 *
 * @private
 */
exports._loadSelectedForceSolver = function () {
  // this overloads the this._calculateNodeForces
  if (this.constants.physics.barnesHut.enabled == true) {
    this._clearMixin(RepulsionMixin);
    this._clearMixin(HierarchialRepulsionMixin);

    this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
    this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
    this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
    this.constants.physics.damping = this.constants.physics.barnesHut.damping;

    this._loadMixin(BarnesHutMixin);
  }
  else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
    this._clearMixin(BarnesHutMixin);
    this._clearMixin(RepulsionMixin);

    this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
    this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
    this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
    this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;

    this._loadMixin(HierarchialRepulsionMixin);
  }
  else {
    this._clearMixin(BarnesHutMixin);
    this._clearMixin(HierarchialRepulsionMixin);
    this.barnesHutTree = undefined;

    this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
    this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
    this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
    this.constants.physics.damping = this.constants.physics.repulsion.damping;

    this._loadMixin(RepulsionMixin);
  }
};

/**
 * Before calculating the forces, we check if we need to cluster to keep up performance and we check
 * if there is more than one node. If it is just one node, we dont calculate anything.
 *
 * @private
 */
exports._initializeForceCalculation = function () {
  // stop calculation if there is only one node
  if (this.nodeIndices.length == 1) {
    this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  }
  else {
    // if there are too many nodes on screen, we cluster without repositioning
    if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
      this.clusterToFit(this.constants.clustering.reduceToNodes, false);
    }

    // we now start the force calculation
    this._calculateForces();
  }
};


/**
 * Calculate the external forces acting on the nodes
 * Forces are caused by: edges, repulsing forces between nodes, gravity
 * @private
 */
exports._calculateForces = function () {
  // Gravity is required to keep separated groups from floating off
  // the forces are reset to zero in this loop by using _setForce instead
  // of _addForce

  this._calculateGravitationalForces();
  this._calculateNodeForces();

  if (this.constants.physics.springConstant > 0) {
    if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
      this._calculateSpringForcesWithSupport();
    }
    else {
      if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
        this._calculateHierarchicalSpringForces();
      }
      else {
        this._calculateSpringForces();
      }
    }
  }
};


/**
 * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
 * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
 * This function joins the datanodes and invisible (called support) nodes into one object.
 * We do this so we do not contaminate this.nodes with the support nodes.
 *
 * @private
 */
exports._updateCalculationNodes = function () {
  if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
    this.calculationNodes = {};
    this.calculationNodeIndices = [];

    for (var nodeId in this.nodes) {
      if (this.nodes.hasOwnProperty(nodeId)) {
        this.calculationNodes[nodeId] = this.nodes[nodeId];
      }
    }
    var supportNodes = this.sectors['support']['nodes'];
    for (var supportNodeId in supportNodes) {
      if (supportNodes.hasOwnProperty(supportNodeId)) {
        if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
          this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
        }
        else {
          supportNodes[supportNodeId]._setForce(0, 0);
        }
      }
    }

    for (var idx in this.calculationNodes) {
      if (this.calculationNodes.hasOwnProperty(idx)) {
        this.calculationNodeIndices.push(idx);
      }
    }
  }
  else {
    this.calculationNodes = this.nodes;
    this.calculationNodeIndices = this.nodeIndices;
  }
};


/**
 * this function applies the central gravity effect to keep groups from floating off
 *
 * @private
 */
exports._calculateGravitationalForces = function () {
  var dx, dy, distance, node, i;
  var nodes = this.calculationNodes;
  var gravity = this.constants.physics.centralGravity;
  var gravityForce = 0;

  for (i = 0; i < this.calculationNodeIndices.length; i++) {
    node = nodes[this.calculationNodeIndices[i]];
    node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
    // gravity does not apply when we are in a pocket sector
    if (this._sector() == "default" && gravity != 0) {
      dx = -node.x;
      dy = -node.y;
      distance = Math.sqrt(dx * dx + dy * dy);

      gravityForce = (distance == 0) ? 0 : (gravity / distance);
      node.fx = dx * gravityForce;
      node.fy = dy * gravityForce;
    }
    else {
      node.fx = 0;
      node.fy = 0;
    }
  }
};




/**
 * this function calculates the effects of the springs in the case of unsmooth curves.
 *
 * @private
 */
exports._calculateSpringForces = function () {
  var edgeLength, edge, edgeId;
  var dx, dy, fx, fy, springForce, distance;
  var edges = this.edges;

  // forces caused by the edges, modelled as springs
  for (edgeId in edges) {
    if (edges.hasOwnProperty(edgeId)) {
      edge = edges[edgeId];
      if (edge.connected) {
        // only calculate forces if nodes are in the same sector
        if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
          edgeLength = edge.physics.springLength;
          // this implies that the edges between big clusters are longer
          edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;

          dx = (edge.from.x - edge.to.x);
          dy = (edge.from.y - edge.to.y);
          distance = Math.sqrt(dx * dx + dy * dy);

          if (distance == 0) {
            distance = 0.01;
          }

          // the 1/distance is so the fx and fy can be calculated without sine or cosine.
          springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;

          fx = dx * springForce;
          fy = dy * springForce;

          edge.from.fx += fx;
          edge.from.fy += fy;
          edge.to.fx -= fx;
          edge.to.fy -= fy;
        }
      }
    }
  }
};




/**
 * This function calculates the springforces on the nodes, accounting for the support nodes.
 *
 * @private
 */
exports._calculateSpringForcesWithSupport = function () {
  var edgeLength, edge, edgeId, combinedClusterSize;
  var edges = this.edges;

  // forces caused by the edges, modelled as springs
  for (edgeId in edges) {
    if (edges.hasOwnProperty(edgeId)) {
      edge = edges[edgeId];
      if (edge.connected) {
        // only calculate forces if nodes are in the same sector
        if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
          if (edge.via != null) {
            var node1 = edge.to;
            var node2 = edge.via;
            var node3 = edge.from;

            edgeLength = edge.physics.springLength;

            combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;

            // this implies that the edges between big clusters are longer
            edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
            this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
            this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
          }
        }
      }
    }
  }
};


/**
 * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
 *
 * @param node1
 * @param node2
 * @param edgeLength
 * @private
 */
exports._calculateSpringForce = function (node1, node2, edgeLength) {
  var dx, dy, fx, fy, springForce, distance;

  dx = (node1.x - node2.x);
  dy = (node1.y - node2.y);
  distance = Math.sqrt(dx * dx + dy * dy);

  if (distance == 0) {
    distance = 0.01;
  }

  // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;

  fx = dx * springForce;
  fy = dy * springForce;

  node1.fx += fx;
  node1.fy += fy;
  node2.fx -= fx;
  node2.fy -= fy;
};


/**
 * Load the HTML for the physics config and bind it
 * @private
 */
exports._loadPhysicsConfiguration = function () {
  if (this.physicsConfiguration === undefined) {
    this.backupConstants = {};
    util.deepExtend(this.backupConstants,this.constants);

    var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
    this.physicsConfiguration = document.createElement('div');
    this.physicsConfiguration.className = "PhysicsConfiguration";
    this.physicsConfiguration.innerHTML = '' +
      '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
      '<tr>' +
      '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
      '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
      '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
      '</tr>' +
      '</table>' +
      '<table id="graph_BH_table" style="display:none">' +
      '<tr><td><b>Barnes Hut</b></td></tr>' +
      '<tr>' +
      '<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td  width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3"  value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05"  style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.barnesHut.springLength + '" step="1" style="width:300px" id="graph_BH_sl"></td><td>500</td><td><input value="' + this.constants.physics.barnesHut.springLength + '" id="graph_BH_sl_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.001" style="width:300px" id="graph_BH_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.barnesHut.damping + '" step="0.005" style="width:300px" id="graph_BH_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.barnesHut.damping + '" id="graph_BH_damp_value" style="width:60px"></td>' +
      '</tr>' +
      '</table>' +
      '<table id="graph_R_table" style="display:none">' +
      '<tr><td><b>Repulsion</b></td></tr>' +
      '<tr>' +
      '<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="' + this.constants.physics.repulsion.nodeDistance + '" step="1" style="width:300px" id="graph_R_nd"></td><td width="50px">300</td><td><input value="' + this.constants.physics.repulsion.nodeDistance + '" id="graph_R_nd_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3"  value="' + this.constants.physics.repulsion.centralGravity + '" step="0.05"  style="width:300px" id="graph_R_cg"></td><td>3</td><td><input value="' + this.constants.physics.repulsion.centralGravity + '" id="graph_R_cg_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.repulsion.springLength + '" step="1" style="width:300px" id="graph_R_sl"></td><td>500</td><td><input value="' + this.constants.physics.repulsion.springLength + '" id="graph_R_sl_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.repulsion.springConstant + '" step="0.001" style="width:300px" id="graph_R_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.repulsion.springConstant + '" id="graph_R_sc_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.repulsion.damping + '" step="0.005" style="width:300px" id="graph_R_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.repulsion.damping + '" id="graph_R_damp_value" style="width:60px"></td>' +
      '</tr>' +
      '</table>' +
      '<table id="graph_H_table" style="display:none">' +
      '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
      '<tr>' +
      '<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="' + this.constants.physics.hierarchicalRepulsion.nodeDistance + '" step="1" style="width:300px" id="graph_H_nd"></td><td width="50px">300</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.nodeDistance + '" id="graph_H_nd_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3"  value="' + this.constants.physics.hierarchicalRepulsion.centralGravity + '" step="0.05"  style="width:300px" id="graph_H_cg"></td><td>3</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.centralGravity + '" id="graph_H_cg_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.hierarchicalRepulsion.springLength + '" step="1" style="width:300px" id="graph_H_sl"></td><td>500</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.springLength + '" id="graph_H_sl_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.hierarchicalRepulsion.springConstant + '" step="0.001" style="width:300px" id="graph_H_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.springConstant + '" id="graph_H_sc_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.hierarchicalRepulsion.damping + '" step="0.005" style="width:300px" id="graph_H_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.damping + '" id="graph_H_damp_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">direction</td><td>1</td><td><input type="range" min="0" max="3" value="' + hierarchicalLayoutDirections.indexOf(this.constants.hierarchicalLayout.direction) + '" step="1" style="width:300px" id="graph_H_direction"></td><td>4</td><td><input value="' + this.constants.hierarchicalLayout.direction + '" id="graph_H_direction_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">levelSeparation</td><td>1</td><td><input type="range" min="0" max="500" value="' + this.constants.hierarchicalLayout.levelSeparation + '" step="1" style="width:300px" id="graph_H_levsep"></td><td>500</td><td><input value="' + this.constants.hierarchicalLayout.levelSeparation + '" id="graph_H_levsep_value" style="width:60px"></td>' +
      '</tr>' +
      '<tr>' +
      '<td width="150px">nodeSpacing</td><td>1</td><td><input type="range" min="0" max="500" value="' + this.constants.hierarchicalLayout.nodeSpacing + '" step="1" style="width:300px" id="graph_H_nspac"></td><td>500</td><td><input value="' + this.constants.hierarchicalLayout.nodeSpacing + '" id="graph_H_nspac_value" style="width:60px"></td>' +
      '</tr>' +
      '</table>' +
      '<table><tr><td><b>Options:</b></td></tr>' +
      '<tr>' +
      '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
      '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
      '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
      '</tr>' +
      '</table>'
    this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
    this.optionsDiv = document.createElement("div");
    this.optionsDiv.style.fontSize = "14px";
    this.optionsDiv.style.fontFamily = "verdana";
    this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);

    var rangeElement;
    rangeElement = document.getElementById('graph_BH_gc');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
    rangeElement = document.getElementById('graph_BH_cg');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
    rangeElement = document.getElementById('graph_BH_sc');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
    rangeElement = document.getElementById('graph_BH_sl');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
    rangeElement = document.getElementById('graph_BH_damp');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");

    rangeElement = document.getElementById('graph_R_nd');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
    rangeElement = document.getElementById('graph_R_cg');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
    rangeElement = document.getElementById('graph_R_sc');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
    rangeElement = document.getElementById('graph_R_sl');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
    rangeElement = document.getElementById('graph_R_damp');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");

    rangeElement = document.getElementById('graph_H_nd');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
    rangeElement = document.getElementById('graph_H_cg');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
    rangeElement = document.getElementById('graph_H_sc');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
    rangeElement = document.getElementById('graph_H_sl');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
    rangeElement = document.getElementById('graph_H_damp');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
    rangeElement = document.getElementById('graph_H_direction');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
    rangeElement = document.getElementById('graph_H_levsep');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
    rangeElement = document.getElementById('graph_H_nspac');
    rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");

    var radioButton1 = document.getElementById("graph_physicsMethod1");
    var radioButton2 = document.getElementById("graph_physicsMethod2");
    var radioButton3 = document.getElementById("graph_physicsMethod3");
    radioButton2.checked = true;
    if (this.constants.physics.barnesHut.enabled) {
      radioButton1.checked = true;
    }
    if (this.constants.hierarchicalLayout.enabled) {
      radioButton3.checked = true;
    }

    var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
    var graph_repositionNodes = document.getElementById("graph_repositionNodes");
    var graph_generateOptions = document.getElementById("graph_generateOptions");

    graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
    graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
    graph_generateOptions.onclick = graphGenerateOptions.bind(this);
    if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
      graph_toggleSmooth.style.background = "#A4FF56";
    }
    else {
      graph_toggleSmooth.style.background = "#FF8532";
    }


    switchConfigurations.apply(this);

    radioButton1.onchange = switchConfigurations.bind(this);
    radioButton2.onchange = switchConfigurations.bind(this);
    radioButton3.onchange = switchConfigurations.bind(this);
  }
};

/**
 * This overwrites the this.constants.
 *
 * @param constantsVariableName
 * @param value
 * @private
 */
exports._overWriteGraphConstants = function (constantsVariableName, value) {
  var nameArray = constantsVariableName.split("_");
  if (nameArray.length == 1) {
    this.constants[nameArray[0]] = value;
  }
  else if (nameArray.length == 2) {
    this.constants[nameArray[0]][nameArray[1]] = value;
  }
  else if (nameArray.length == 3) {
    this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  }
};


/**
 * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
 */
function graphToggleSmoothCurves () {
  this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  else                                     {graph_toggleSmooth.style.background = "#FF8532";}

  this._configureSmoothCurves(false);
}

/**
 * this function is used to scramble the nodes
 *
 */
function graphRepositionNodes () {
  for (var nodeId in this.calculationNodes) {
    if (this.calculationNodes.hasOwnProperty(nodeId)) {
      this.calculationNodes[nodeId].vx = 0;  this.calculationNodes[nodeId].vy = 0;
      this.calculationNodes[nodeId].fx = 0;  this.calculationNodes[nodeId].fy = 0;
    }
  }
  if (this.constants.hierarchicalLayout.enabled == true) {
    this._setupHierarchicalLayout();
    showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
    showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
    showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
    showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
    showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  }
  else {
    this.repositionNodes();
  }
  this.moving = true;
  this.start();
}

/**
 *  this is used to generate an options file from the playing with physics system.
 */
function graphGenerateOptions () {
  var options = "No options are required, default values used.";
  var optionsSpecific = [];
  var radioButton1 = document.getElementById("graph_physicsMethod1");
  var radioButton2 = document.getElementById("graph_physicsMethod2");
  if (radioButton1.checked == true) {
    if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
    if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity)                         {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
    if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength)                             {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
    if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant)                         {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
    if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping)                                       {optionsSpecific.push("damping: " + this.constants.physics.damping);}
    if (optionsSpecific.length != 0) {
      options = "var options = {";
      options += "physics: {barnesHut: {";
      for (var i = 0; i < optionsSpecific.length; i++) {
        options += optionsSpecific[i];
        if (i < optionsSpecific.length - 1) {
          options += ", "
        }
      }
      options += '}}'
    }
    if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
      if (optionsSpecific.length == 0) {options = "var options = {";}
      else {options += ", "}
      options += "smoothCurves: " + this.constants.smoothCurves.enabled;
    }
    if (options != "No options are required, default values used.") {
      options += '};'
    }
  }
  else if (radioButton2.checked == true) {
    options = "var options = {";
    options += "physics: {barnesHut: {enabled: false}";
    if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance)  {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
    if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity)        {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
    if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength)            {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
    if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant)        {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
    if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping)                      {optionsSpecific.push("damping: " + this.constants.physics.damping);}
    if (optionsSpecific.length != 0) {
      options += ", repulsion: {";
      for (var i = 0; i < optionsSpecific.length; i++) {
        options += optionsSpecific[i];
        if (i < optionsSpecific.length - 1) {
          options += ", "
        }
      }
      options += '}}'
    }
    if (optionsSpecific.length == 0) {options += "}"}
    if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
      options += ", smoothCurves: " + this.constants.smoothCurves;
    }
    options += '};'
  }
  else {
    options = "var options = {";
    if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance)  {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
    if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity)        {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
    if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength)            {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
    if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant)        {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
    if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping)                      {optionsSpecific.push("damping: " + this.constants.physics.damping);}
    if (optionsSpecific.length != 0) {
      options += "physics: {hierarchicalRepulsion: {";
      for (var i = 0; i < optionsSpecific.length; i++) {
        options += optionsSpecific[i];
        if (i < optionsSpecific.length - 1) {
          options += ", ";
        }
      }
      options += '}},';
    }
    options += 'hierarchicalLayout: {';
    optionsSpecific = [];
    if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction)                       {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
    if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
    if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing)                   {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
    if (optionsSpecific.length != 0) {
      for (var i = 0; i < optionsSpecific.length; i++) {
        options += optionsSpecific[i];
        if (i < optionsSpecific.length - 1) {
          options += ", "
        }
      }
      options += '}'
    }
    else {
      options += "enabled:true}";
    }
    options += '};'
  }


  this.optionsDiv.innerHTML = options;
}

/**
 * this is used to switch between barnesHut, repulsion and hierarchical.
 *
 */
function switchConfigurations () {
  var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  var tableId = "graph_" + radioButton + "_table";
  var table = document.getElementById(tableId);
  table.style.display = "block";
  for (var i = 0; i < ids.length; i++) {
    if (ids[i] != tableId) {
      table = document.getElementById(ids[i]);
      table.style.display = "none";
    }
  }
  this._restoreNodes();
  if (radioButton == "R") {
    this.constants.hierarchicalLayout.enabled = false;
    this.constants.physics.hierarchicalRepulsion.enabled = false;
    this.constants.physics.barnesHut.enabled = false;
  }
  else if (radioButton == "H") {
    if (this.constants.hierarchicalLayout.enabled == false) {
      this.constants.hierarchicalLayout.enabled = true;
      this.constants.physics.hierarchicalRepulsion.enabled = true;
      this.constants.physics.barnesHut.enabled = false;
      this.constants.smoothCurves.enabled = false;
      this._setupHierarchicalLayout();
    }
  }
  else {
    this.constants.hierarchicalLayout.enabled = false;
    this.constants.physics.hierarchicalRepulsion.enabled = false;
    this.constants.physics.barnesHut.enabled = true;
  }
  this._loadSelectedForceSolver();
  var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  else                                     {graph_toggleSmooth.style.background = "#FF8532";}
  this.moving = true;
  this.start();
}


/**
 * this generates the ranges depending on the iniital values.
 *
 * @param id
 * @param map
 * @param constantsVariableName
 */
function showValueOfRange (id,map,constantsVariableName) {
  var valueId = id + "_value";
  var rangeValue = document.getElementById(id).value;

  if (Array.isArray(map)) {
    document.getElementById(valueId).value = map[parseInt(rangeValue)];
    this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  }
  else {
    document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
    this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  }

  if (constantsVariableName == "hierarchicalLayout_direction" ||
    constantsVariableName == "hierarchicalLayout_levelSeparation" ||
    constantsVariableName == "hierarchicalLayout_nodeSpacing") {
    this._setupHierarchicalLayout();
  }
  this.moving = true;
  this.start();
}
