
//Link is like an Edge in the graph/tree structure
class Link {
  constructor(from, to) {
    this.to = to;
    this.from = from;
  }
}
//Node--> Vertex
class Node {
	constructor(id){
  	this.id = id;
  } 
}


class Graph{
	constructor(NodeType){
		this.nodes = {};
		this.links = {}
	}

	addNode(node){
		if(this.nodes[node.id]){
			throw new Error("Node already exists with ID "+node.id);
		}
		this.nodes[node.id] = node;
		if(!this.links[node.id]) this.links[node.id] = this._emptyLink();
	}
	_emptyLink() {
	    return {
	      	out: new Set(),
	      	in: new Set()
	    };
	}
	
	//we give the link we 
	addLink(link){
		let from = link.from;
		let to = link.to;
		if(!this.nodes[from.id] || !this.nodes[to.id]){
			throw new Error("One of the Nodes Doesnt Exist");
		}
		if(this.links[from.id].out.has(link) || this.links[to.id].in.has(link)){
			throw new Error("Link already exists");
		}
		this.links[from.id].out.add(link);
		this.links[to.id].in.add(link);
	}

	removeLink(link){
		this.links[link.from.id].out.delete(link);
		this.links[link.to.id].in.delete(link);
	}

	removeNode(node){
		if(!this.nodes[node.id]) return true;
		let outlinks = this.links[node.id].out
		let inlinks = this.links[node.id].in;
		outlinks.forEach( link => this.removeLink(link))
		inlinks.forEach( link => this.removeLink(link))
		delete this.nodes[node.id];
		delete this.links[node.id];
		return node;
	}

	DFSRecursive(start, cb){
		if(!start) return null;
		var resultList = [];
		var visited = {};
		var links = this.links;
		var found = false;
		(function dfs(vertex){
			visited[vertex.id] = true;
			resultList.push(vertex);
			//console.log(resultList)
			if( cb(vertex) === true ) {
				//console.log("####true")
				found = true;
				return resultList;
			}
			for(var link of links[vertex.id].out) {
				if(!visited[link.to.id]){
					let res = dfs(link.to.id);
					if(found)return;
					resultList.pop();
				}
			} 
		})(start)
		return resultList;
	}

	//depth first 
	DFS(start,cb){
		var visited = {};
		visited[start.id] = true;
		var stack = [start];
		var stackRes = [[]];
		let vertex;
		while(stack.length){
			vertex  = stack.pop();
			let res = stackRes.pop();
			// console.log(res);
			if( cb(vertex) === true ) {
				return [...res, vertex];
			} 

			for( var link of this.links[vertex.id].out ) {
				if(!visited[link.to.id]){
					visited[link.to.id] = true;
					stack.push(link.to);
					stackRes.push([...res, vertex]);
				}
			}
			// resultList.pop();
		}
		return false;
	}


	BFS(start,cb){
		var visited = {[start.id]:true}
		var stack = [start];
		var stackRes = [[]];
		let vertex;
		while(stack.length){
			vertex  = stack.shift();
			let res = stackRes.shift();
			if( cb(vertex) === true ) {
				return [...res, vertex];
			} 

			for( var link of this.links[vertex.id].out ) {
				if(!visited[link.to.id]){
					visited[link.to.id] = true;
					stack.push(link.to);
					stackRes.push([...res, vertex]);
				}
			}
		}
		return false;
	}



	_debug(){

	}

}



var graph = new Graph(Node);
var A = new Node("A");
var B = new Node('B');
var C = new Node('C');
var D = new Node('D');
var E = new Node('E');
var F = new Node("F");

var AB = new Link(A,B);
var AD = new Link(A,D);
var BC = new Link(B,C);
var CF = new Link(C,F);
var DB = new Link(D,B);
var DE = new Link(D,E);
var EF = new Link(E,F);
var FE = new Link(F,E);
/*
graph.addNode(A);
graph.addNode(B);
graph.addNode(C);
graph.addNode(D);
graph.addNode(E);
graph.addNode(F);
graph.addLink(AB);
graph.addLink(AD);
graph.addLink(BC);
graph.addLink(CF);
graph.addLink(DB);
graph.addLink(DE);
graph.addLink(EF);
graph.addLink(FE);
*/

//let recur = graph.DFS(A,(node) => node.id === E.id);
//let bfs = graph.BFS(A,(node) => node.id === C.id);


module.exports = {
	Node,
	Link,
	Graph
};



//console.log(tree.links)