/**
 * Prim's algorithm is a greedy algorithm that finds a minimum
 * spanning tree for a connected weighted undirected graph.
 *
 * @example
 *
 * var Prim = require('path-to-algorithms/src/graphs/spanning-trees/prim');
 * var Graph = Prim.Graph;
 * var Edge = Prim.Edge;
 * var Vertex = Prim.Vertex;
 *
 * var graph, edges = [];
 * edges.push(new Edge(new Vertex(0), new Vertex(1), 4));
 * edges.push(new Edge(new Vertex(0), new Vertex(7), 8));
 * edges.push(new Edge(new Vertex(1), new Vertex(7), 11));
 * edges.push(new Edge(new Vertex(1), new Vertex(2), 8));
 * edges.push(new Edge(new Vertex(2), new Vertex(8), 2));
 * edges.push(new Edge(new Vertex(2), new Vertex(3), 7));
 * edges.push(new Edge(new Vertex(2), new Vertex(5), 4));
 * edges.push(new Edge(new Vertex(2), new Vertex(3), 7));
 * edges.push(new Edge(new Vertex(3), new Vertex(4), 9));
 * edges.push(new Edge(new Vertex(3), new Vertex(5), 14));
 * edges.push(new Edge(new Vertex(4), new Vertex(5), 10));
 * edges.push(new Edge(new Vertex(5), new Vertex(6), 2));
 * edges.push(new Edge(new Vertex(6), new Vertex(8), 6));
 * edges.push(new Edge(new Vertex(8), new Vertex(7), 7));
 * graph = new Graph(edges, edges.length);
 *
 * // { edges:
 * //    [ { e: '1', v: 0, distance: 4 },
 * //      { e: '2', v: 8, distance: 2 },
 * //      { e: '3', v: 2, distance: 7 },
 * //      { e: '4', v: 3, distance: 9 },
 * //      { e: '5', v: 2, distance: 4 },
 * //      { e: '6', v: 5, distance: 2 },
 * //      { e: '7', v: 0, distance: 8 },
 * //      { e: '8', v: 7, distance: 7 } ],
 * //   nodesCount: 0 }
 * var spanningTree = graph.prim();
 *
 * @module graphs/spanning-trees/prim
 */
(function (exports) {

  'use strict';

  var Heap = require('../../data-structures/heap').Heap;
  exports.Vertex = require('../../data-structures/vertex').Vertex;
  exports.Edge = require('../../data-structures/edge').Edge;

  /**
   * Graph.
   *
   * @constructor
   * @public
   * @param {Array} edges Array with graph edges.
   * @param {Number} nodesCount Number of nodes in graph.
   */
  exports.Graph = function (edges, nodesCount) {
    this.edges = edges || [];
    this.nodesCount = nodesCount || 0;
  };

  /**
   * Executes Prim's algorithm and returns minimum spanning tree.
   *
   * @public
   * @method
   * @return {Graph} Graph which is the minimum spanning tree.
   */
  exports.Graph.prototype.prim = (function () {
    var queue;

    /**
     * Used for comparitions in the heap
     *
     * @private
     * @param {Vertex} a First operand of the comparition.
     * @param {Vertex} b Second operand of the comparition.
     * @return {number} Number which which is equal, greater or
     *  less then zero and indicates whether the first vertex is
     *  "greater" than the second.
     */
    function compareEdges(a, b) {
      return b.distance - a.distance;
    }

    /**
     * Initialize the algorithm.
     *
     * @private
     */
    function init() {
      queue = new Heap(compareEdges);
    }

    return function () {
      init.call(this);
      var inTheTree = {};
      var startVertex = this.edges[0].e.id;
      var spannigTree = [];
      var parents = {};
      var distances = {};
      var current;
      inTheTree[startVertex] = true;
      queue.add({
        node: startVertex,
        distance: 0
      });
      for (var i = 0; i < this.nodesCount - 1; i += 1) {
        current = queue.extract().node;
        inTheTree[current] = true;
        this.edges.forEach(function (e) {
          if (inTheTree[e.v.id] && inTheTree[e.e.id]) {
            return;
          }
          var collection = queue.getCollection();
          var node;
          if (e.e.id === current) {
            node = e.v.id;
          } else if (e.v.id === current) {
            node = e.e.id;
          } else {
            return;
          }
          for (var i = 0; i < collection.length; i += 1) {
            if (collection[i].node === node) {
              if (collection[i].distance > e.distance) {
                queue.changeKey(i, {
                  node: node,
                  distance: e.distance
                });
                parents[node] = current;
                distances[node] = e.distance;
              }
              return;
            }
          }
          queue.add({
            node: node,
            distance: e.distance
          });
          parents[node] = current;
          distances[node] = e.distance;
        });
      }
      for (var node in parents) {
        spannigTree.push(
          new exports.Edge(node, parents[node], distances[node]));
      }
      return new exports.Graph(spannigTree);
    };

  }());

})(typeof window === 'undefined' ? module.exports : window);
