/* ===========================================================================
 * Copyright 2013 Nexedi SA and Contributors
 *
 * This file is part of DREAM.
 *
 * DREAM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DREAM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with DREAM.  If not, see <http://www.gnu.org/licenses/>.
 * =========================================================================== */

/*jslint indent: 2, maxlen: 80, nomen: true */
/*global window, jQuery, rJS*/

(function (window, $, rJS) {
  "use strict";

  $.widget("dream.grapheditor", {
    options: {
      node_class: "graph_node", // CSS class for nodes
      node_data_key: "grapheditor_node_data", // Key to store data on nodes using $.data
      // XXX is it good idea to use
      // $.data ???
      draggable_nodes: true // Can nodes be dragged
    },

    _create: function () {
      this.general = {};
      this.nodes = {};
      this.edges = {};

      if (this.options.graph) {
        this.graph(this.options.graph);
      }
    },

    destroy: function () {
      this.clear();
      this._destroy();
    },

    clear: function () {
      this.general = {};
      this.edges = {};
      $.each(this.nodes, $.proxy(function (node_id, node) {
        jsPlumb.remove(node);
        delete(this["nodes"][node_id]);
      }, this));
    },

    create_node_element: function (node_id, node_data) {
      // Create an element for a node, and add it to the main element.
      return $('<div>')
        .uniqueId()
        .addClass(this.options.node_class)
        .text(node_data['name'] || node_id)
        .appendTo(this.element);
    },

    add_node: function (node_id, node_data) {
      /* add a node */
      var element = this.create_node_element(node_id, node_data);
      if (this.options.draggable_nodes) {
        // make the node draggable
        jsPlumb.draggable(element, {
          containment: this.element,
          stop: $.proxy(function (node_id, event) {
            this.node_position(node_id, this.node_position(node_id));
            this._trigger("node_moved", event, node_id);
          }, this, node_id)
        });
      }
      this.nodes[node_id] = element;
      this.node_position(node_id, node_data);
      this.node_data(node_id, node_data);
      this._trigger('node_added', node_id);
    },

    create_edge_element: function (edge_id, source_node_id,
      destination_node_id, edge_data) {
      // Create an element for an edge
      var edge_label = edge_data['name'] || "";
      jsPlumb.connect({
        source: this.nodes[source_node_id].attr("id"),
        target: this.nodes[destination_node_id].attr("id"),
        paintStyle: {
          lineWidth: 1,
          strokeStyle: "#000"
        }, // XXX make this an option
        overlays: [
          ["Label", {
            label: edge_label
          }]
        ]
      });
    },

    add_edge: function (edge_id, source_node_id, destination_node_id,
      edge_data) {
      /* add an edge */
      this.create_edge_element(edge_id, source_node_id,
        destination_node_id, edge_data);
      this.edges[edge_id] = [source_node_id, destination_node_id,
        edge_data
      ];
      this._trigger('edge_added', edge_id);
    },

    node_data: function (node_id, node_data) {
      /* get or set data for a node */
      var node = this.nodes[node_id];
      if (node_data === undefined) {
        return node.data(this.options.node_data_key);
      }
      this.nodes[node_id].data(this.options.node_data_key, node_data);
      this._trigger("node_data_changed", node_id);
      return this;
    },

    edge_data: function (edge_id, edge_data) {
      /* get or set data for an edge */
      var edge = this.edges[edge_id];
      if (edge_data === undefined) {
        return edge[2];
      }
      this.edges[edge_id] = [edge[0], edge[1], edge_data];
      this._trigger("edge_data_changed", edge_id);
      return this;
    },

    node_position: function (node_id, position) {
      /* Get or set the position of a node with position given on a 0..1 scale */
      var node = this.nodes[node_id],
        node_position = node.position(),
        element_position = this.element.position();
      if (position === undefined) {
        return {
          "top": (node_position.top - element_position.top) / (this.element
            .height() - node.height()),
          "left": (node_position.left - element_position.left) / (this.element
            .width() - node.width())
        };
      }
      node.css({
        "top": element_position.top +
          Math.floor(position.top * (this.element.height() - node.height())) +
          "px",
        "left": element_position.left +
          Math.floor(position.left * (this.element.width() - node.width())) +
          "px"
      });
      // update node data with position
      this.node_data(node_id, $.extend(this.node_data(node_id), {
        top: position.top,
        left: position.left
      }));
      jsPlumb.repaintEverything();
      return this;
    },

    graph: function (value) {
      // get or set the graph data
      if (value === undefined) {
        // get
        var graph = {
          "general": {},
          "nodes": {},
          "edges": {}
        };
        $.each(this.general, $.proxy(function (key, value) {
          graph["general"][key] = value;
        }, this));
        $.each(this.nodes, $.proxy(function (node_id, node) {
          graph["nodes"][node_id] = node.data(this.options.node_data_key);
        }, this));
        $.each(this.edges, $.proxy(function (edge_id, edge) {
          graph["edges"][edge_id] = this.edges[edge_id];
        }, this));
        return graph;
      }

      this.clear();
      $.each(value.general, $.proxy(function (key, value) {
        this.general[key] = value;
      }, this));
      $.each(value.nodes, $.proxy(function (node_id, node) {
        this.add_node(node_id, node);
      }, this));
      $.each(value.edges, $.proxy(function (edge_id, edge) {
        this.add_edge(edge_id, edge[0], edge[1], edge[2] || {});
      }, this));
      return this;
    }
  });

  jsPlumb.bind("ready", function () {

    /* Initialize jsPlumb defaults
     */
    // TODO: default for connections ?
    jsPlumb.setRenderMode(jsPlumb.SVG);
    jsPlumb.importDefaults({
      Endpoint: ["Dot", {
        radius: 2
      }],
      ConnectionOverlays: [
        ["Arrow", {
          location: 1,
          width: 10
        }]
      ],
      Anchor: "Continuous"
    });

    if ($("#main").length > 0) {
    $.getJSON("../JSONInputs/Topology01.json", function (data) {
      $("#main").grapheditor({
        graph: data,
        node_moved: function (event, node_id) {
          $("#debug_zone").val(JSON.stringify(
            $("#main").grapheditor("graph"), undefined, " "));
        }
      });

      // Access the nodes
      var grapheditor = $("#main").data("dreamGrapheditor");
      $.each(grapheditor.nodes, function (node_id, node) {
        // test: click to add a property
        node.click(function (e) {
          $.mobile.changePage("#dialog", {
            role: "dialog"
          });
          $("#main").grapheditor("node_data", node_id,
            $.extend($("#main").grapheditor("node_data", node_id), {
              "dbclick": 1
            }));
        });
      });

      // reload from json
      $("#load_json").click(function () {
        $("#main").grapheditor("graph",
          JSON.parse($("#debug_zone").val()));
      });

    });
    };

    if ($("#simulation").length > 0) {
      $("#run_simulation").button().click(function () {
	var graph_dict = $('#main').grapheditor('graph');
	$("#json_output").val(JSON.stringify(graph_dict, undefined, ' '));
	$.ajax(
          '/runSimulation', {
            data: JSON.stringify({
              json: graph_dict
            }),
            contentType: 'application/json',
            type: 'POST',
            success: function (data, textStatus, jqXHR) {
            if (data['success']) {
              $("#json_result").text(JSON.stringify(data['success'],
                undefined, " "));

              // display demo graph.
              $("#graph_zone").show();
              var blockage_data = [],
                waiting_data = [],
                failure_data = [],
                working_data = [],
                ticks = [],
                counter = 1;
              $.each(data['success'].elementList, function (idx, obj) {
                if (obj.results.working_ratio !== undefined) {
                  /* when there is only one replication, the ratio is given as a float,
                      otherwise we have a mapping avg, min max */
		  if (obj.results.blockage_ratio !== undefined) {
                    blockage_data.push([counter, obj.results.blockage_ratio
					.avg || obj.results.blockage_ratio
				       ]);
		  } else {
                    blockage_data.push([counter, 0.0]);
		  }
                  waiting_data.push([counter, obj.results.waiting_ratio.avg ||
                    obj.results.waiting_ratio
                  ]);
		  if (obj.results.failure_ratio !== undefined) {
                    failure_data.push([counter, obj.results.failure_ratio
					.avg || obj.results.failure_ratio
				       ]);
		  } else {
                    failure_data.push([counter, 0.0]);
		  }
                  working_data.push([counter, obj.results.working_ratio.avg ||
                    obj.results.working_ratio
                  ]);
                  ticks.push([counter, obj.id]);
                  counter++;
                }
              });

              var series = [{
                label: "Working",
                data: working_data
              }, {
                label: "Waiting",
                data: waiting_data
              }, {
                label: "Failures",
                data: failure_data
              }, {
                label: "Blockage",
                data: blockage_data
              }];

              var options = {
                xaxis: {
                  minTickSize: 1,
                  ticks: ticks
                },
                yaxis: {
                  max: 100
                },
                series: {
                  bars: {
                    show: true,
                    barWidth: 0.9,
                    align: "center"
                  },
                  stack: true
                }
              };
              $.plot("#simulation_result", series, options);

            } else {
              $("#json_result").effect('shake', 50).text(data['error']);
            }
            }
          });
      });
    };
  });

  function init() {
  }

  rJS(window).declareMethod('getContent', function () {
    return JSON.stringify($('#main').grapheditor('graph'), undefined, ' ');
  })

    .declareMethod('setContent', function (content) {
      $('#main').grapheditor('graph', JSON.parse(content));
    })

    .declareMethod('clearContent', function () {
      $('#main').grapheditor('clear');
    })

    .ready(function () {
      init.apply(rJS(this));
    });

}(window, jQuery, rJS));
