text: var LGraphObject = require('./LGraphObject');
  var Integer = require('./util/Integer');
  var LayoutConstants = require('./LayoutConstants');
  var LGraphManager = require('./LGraphManager');
  var LNode = require('./LNode');
  var LEdge = require('./LEdge');
  var RectangleD = require('./util/RectangleD');
  var Point = require('./util/Point');
  var LinkedList = require('./util/LinkedList');
  
  function LGraph(parent, obj2, vGraph) {
    LGraphObject.call(this, vGraph);
    this.estimatedSize = Integer.MIN_VALUE;
    this.margin = LayoutConstants.DEFAULT_GRAPH_MARGIN;
    this.edges = [];
    this.nodes = [];
    this.isConnected = false;
    this.parent = parent;
  
    if (obj2 != null && obj2 instanceof LGraphManager) {
      this.graphManager = obj2;
    }
    else if (obj2 != null && obj2 instanceof Layout) {
      this.graphManager = obj2.graphManager;
    }
  }
  
  LGraph.prototype = Object.create(LGraphObject.prototype);
  for (var prop in LGraphObject) {
    LGraph[prop] = LGraphObject[prop];
  }
  
  LGraph.prototype.getNodes = function () {
    return this.nodes;
  };
  
  LGraph.prototype.getEdges = function () {
    return this.edges;
  };
  
  LGraph.prototype.getGraphManager = function ()
  {
    return this.graphManager;
  };
  
  LGraph.prototype.getParent = function ()
  {
    return this.parent;
  };
  
  LGraph.prototype.getLeft = function ()
  {
    return this.left;
  };
  
  LGraph.prototype.getRight = function ()
  {
    return this.right;
  };
  
  LGraph.prototype.getTop = function ()
  {
    return this.top;
  };
  
  LGraph.prototype.getBottom = function ()
  {
    return this.bottom;
  };
  
  LGraph.prototype.isConnected = function ()
  {
    return this.isConnected;
  };
  
  LGraph.prototype.add = function (obj1, sourceNode, targetNode) {
    if (sourceNode == null && targetNode == null) {
      var newNode = obj1;
      if (this.graphManager == null) {
        throw "Graph has no graph mgr!";
      }
      if (this.getNodes().indexOf(newNode) > -1) {
        throw "Node already in graph!";
      }
      newNode.owner = this;
      this.getNodes().push(newNode);
  
      return newNode;
    }
    else {
      var newEdge = obj1;
      if (!(this.getNodes().indexOf(sourceNode) > -1 && (this.getNodes().indexOf(targetNode)) > -1)) {
        throw "Source or target not in graph!";
      }
  
      if (!(sourceNode.owner == targetNode.owner && sourceNode.owner == this)) {
        throw "Both owners must be this graph!";
      }
  
      if (sourceNode.owner != targetNode.owner)
      {
        return null;
      }
  
      // set source and target
      newEdge.source = sourceNode;
      newEdge.target = targetNode;
  
      // set as intra-graph edge
      newEdge.isInterGraph = false;
  
      // add to graph edge list
      this.getEdges().push(newEdge);
  
      // add to incidency lists
      sourceNode.edges.push(newEdge);
  
      if (targetNode != sourceNode)
      {
        targetNode.edges.push(newEdge);
      }
  
      return newEdge;
    }
  };
  
  LGraph.prototype.remove = function (obj) {
    var node = obj;
    if (obj instanceof LNode) {
      if (node == null) {
        throw "Node is null!";
      }
      if (!(node.owner != null && node.owner == this)) {
        throw "Owner graph is invalid!";
      }
      if (this.graphManager == null) {
        throw "Owner graph manager is invalid!";
      }
      // remove incident edges first (make a copy to do it safely)
      var edgesToBeRemoved = node.edges.slice();
      var edge;
      var s = edgesToBeRemoved.length;
      for (var i = 0; i < s; i++)
      {
        edge = edgesToBeRemoved[i];
  
        if (edge.isInterGraph)
        {
          this.graphManager.remove(edge);
        }
        else
        {
          edge.source.owner.remove(edge);
        }
      }
  
      // now the node itself
      var index = this.nodes.indexOf(node);
      if (index == -1) {
        throw "Node not in owner node list!";
      }
  
      this.nodes.splice(index, 1);
    }
    else if (obj instanceof LEdge) {
      var edge = obj;
      if (edge == null) {
        throw "Edge is null!";
      }
      if (!(edge.source != null && edge.target != null)) {
        throw "Source and/or target is null!";
      }
      if (!(edge.source.owner != null && edge.target.owner != null &&
              edge.source.owner == this && edge.target.owner == this)) {
        throw "Source and/or target owner is invalid!";
      }
  
      var sourceIndex = edge.source.edges.indexOf(edge);
      var targetIndex = edge.target.edges.indexOf(edge);
      if (!(sourceIndex > -1 && targetIndex > -1)) {
        throw "Source and/or target doesn't know this edge!";
      }
  
      edge.source.edges.splice(sourceIndex, 1);
  
      if (edge.target != edge.source)
      {
        edge.target.edges.splice(targetIndex, 1);
      }
  
      var index = edge.source.owner.getEdges().indexOf(edge);
      if (index == -1) {
        throw "Not in owner's edge list!";
      }
  
      edge.source.owner.getEdges().splice(index, 1);
    }
  };
  
  LGraph.prototype.updateLeftTop = function ()
  {
    var top = Integer.MAX_VALUE;
    var left = Integer.MAX_VALUE;
    var nodeTop;
    var nodeLeft;
    var margin;
  
    var nodes = this.getNodes();
    var s = nodes.length;
  
    for (var i = 0; i < s; i++)
    {
      var lNode = nodes[i];
      nodeTop = lNode.getTop();
      nodeLeft = lNode.getLeft();
  
      if (top > nodeTop)
      {
        top = nodeTop;
      }
  
      if (left > nodeLeft)
      {
        left = nodeLeft;
      }
    }
  
    // Do we have any nodes in this graph?
    if (top == Integer.MAX_VALUE)
    {
      return null;
    }
  
    if(nodes[0].getParent().paddingLeft != undefined){
      margin = nodes[0].getParent().paddingLeft;
    }
    else{
      margin = this.margin;
    }
  
    this.left = left - margin;
    this.top = top - margin;
  
    // Apply the margins and return the result
    return new Point(this.left, this.top);
  };
  
  LGraph.prototype.updateBounds = function (recursive)
  {
    // calculate bounds
    var left = Integer.MAX_VALUE;
    var right = -Integer.MAX_VALUE;
    var top = Integer.MAX_VALUE;
    var bottom = -Integer.MAX_VALUE;
    var nodeLeft;
    var nodeRight;
    var nodeTop;
    var nodeBottom;
    var margin;
  
    var nodes = this.nodes;
    var s = nodes.length;
    for (var i = 0; i < s; i++)
    {
      var lNode = nodes[i];
  
      if (recursive && lNode.child != null)
      {
        lNode.updateBounds();
      }
      nodeLeft = lNode.getLeft();
      nodeRight = lNode.getRight();
      nodeTop = lNode.getTop();
      nodeBottom = lNode.getBottom();
  
      if (left > nodeLeft)
      {
        left = nodeLeft;
      }
  
      if (right < nodeRight)
      {
        right = nodeRight;
      }
  
      if (top > nodeTop)
      {
        top = nodeTop;
      }
  
      if (bottom < nodeBottom)
      {
        bottom = nodeBottom;
      }
    }
  
    var boundingRect = new RectangleD(left, top, right - left, bottom - top);
    if (left == Integer.MAX_VALUE)
    {
      this.left = this.parent.getLeft();
      this.right = this.parent.getRight();
      this.top = this.parent.getTop();
      this.bottom = this.parent.getBottom();
    }
  
    if(nodes[0].getParent().paddingLeft != undefined){
      margin = nodes[0].getParent().paddingLeft;
    }
    else{
      margin = this.margin;
    }
  
    this.left = boundingRect.x - margin;
    this.right = boundingRect.x + boundingRect.width + margin;
    this.top = boundingRect.y - margin;
    this.bottom = boundingRect.y + boundingRect.height + margin;
  };
  
  LGraph.calculateBounds = function (nodes)
  {
    var left = Integer.MAX_VALUE;
    var right = -Integer.MAX_VALUE;
    var top = Integer.MAX_VALUE;
    var bottom = -Integer.MAX_VALUE;
    var nodeLeft;
    var nodeRight;
    var nodeTop;
    var nodeBottom;
  
    var s = nodes.length;
  
    for (var i = 0; i < s; i++)
    {
      var lNode = nodes[i];
      nodeLeft = lNode.getLeft();
      nodeRight = lNode.getRight();
      nodeTop = lNode.getTop();
      nodeBottom = lNode.getBottom();
  
      if (left > nodeLeft)
      {
        left = nodeLeft;
      }
  
      if (right < nodeRight)
      {
        right = nodeRight;
      }
  
      if (top > nodeTop)
      {
        top = nodeTop;
      }
  
      if (bottom < nodeBottom)
      {
        bottom = nodeBottom;
      }
    }
  
    var boundingRect = new RectangleD(left, top, right - left, bottom - top);
  
    return boundingRect;
  };
  
  LGraph.prototype.getInclusionTreeDepth = function ()
  {
    if (this == this.graphManager.getRoot())
    {
      return 1;
    }
    else
    {
      return this.parent.getInclusionTreeDepth();
    }
  };
  
  LGraph.prototype.getEstimatedSize = function ()
  {
    if (this.estimatedSize == Integer.MIN_VALUE) {
      throw "assert failed";
    }
    return this.estimatedSize;
  };
  
  LGraph.prototype.calcEstimatedSize = function ()
  {
    var size = 0;
    var nodes = this.nodes;
    var s = nodes.length;
  
    for (var i = 0; i < s; i++)
    {
      var lNode = nodes[i];
      size += lNode.calcEstimatedSize();
    }
  
    if (size == 0)
    {
      this.estimatedSize = LayoutConstants.EMPTY_COMPOUND_NODE_SIZE;
    }
    else
    {
      this.estimatedSize = size / Math.sqrt(this.nodes.length);
    }
  
    return this.estimatedSize;
  };
  
  LGraph.prototype.updateConnected = function ()
  {
    var self = this;
    if (this.nodes.length == 0)
    {
      this.isConnected = true;
      return;
    }
  
    var queue = new LinkedList();
    var visited = new Set();
    var currentNode = this.nodes[0];
    var neighborEdges;
    var currentNeighbor;
    var childrenOfNode = currentNode.withChildren();
    childrenOfNode.forEach(function(node) {
      queue.push(node);
      visited.add(node);
    });
  
    while (queue.length !== 0)
    {
      currentNode = queue.shift();
  
      // Traverse all neighbors of this node
      neighborEdges = currentNode.getEdges();
      var size = neighborEdges.length;
      for (var i = 0; i < size; i++)
      {
        var neighborEdge = neighborEdges[i];
        currentNeighbor =
                neighborEdge.getOtherEndInGraph(currentNode, this);
  
        // Add unvisited neighbors to the list to visit
        if (currentNeighbor != null &&
                !visited.has(currentNeighbor))
        {
          var childrenOfNeighbor = currentNeighbor.withChildren();
  
          childrenOfNeighbor.forEach(function(node) {
            queue.push(node);
            visited.add(node);
          });
        }
      }
    }
  
    this.isConnected = false;
  
    if (visited.size >= this.nodes.length)
    {
      var noOfVisitedInThisGraph = 0;
  
      visited.forEach(function(visitedNode) {
        if (visitedNode.owner == self)
        {
          noOfVisitedInThisGraph++;
        }
      });
  
      if (noOfVisitedInThisGraph == this.nodes.length)
      {
        this.isConnected = true;
      }
    }
  };
  
  module.exports = LGraph;
  