const { Node, Link, Graph } = require("./graph");
class TreeNode extends Node {
	constructor(id){
		super(id);
		this.depth = null; 
	}
}

class Tree extends Graph {
	constructor(){
		super();
		this.root = null;
		this.levels = [];
	}

  	/*addLink(link) {
  		console.log("You cannot add Link because its deprecated ...Use Insert");
		return;
	}
	addNode(node){
		console.log("You cannot add Node because its deprecated ...Use Insert");
		return;
	}*/

	changeParent(node, parent) {
		let link = this.links[node.id].in.values().next().value;
		let oldparent = link.from;
		this.links[oldparent.id].out.delete(link);
		link.from = parent;
		this.links[parent.id].out.add(link);
		this.updateLevels(parent);
	}

	updateLevels(node) {
		let outSet = this.links[node.id].out;
		for(let link of outSet) {
			let child = this.getNode(link.to);
			if(child.depth !== node.depth + 1) {
				if(child.depth){
					this.levels[child.depth].delete(child.id);
				}
				
				child.depth = node.depth + 1;
				if(!this.levels[child.depth]){
					this.levels[child.depth] = new Set();
				}
				this.levels[child.depth].add(child.id);
				this.updateLevels(child);
			}
		}
	}

	insert(node,parentNode){
		if(this.nodes[node.id]){
			throw new Error("Node already Exists")
		}
		if(parentNode === null || parentNode === undefined) {
			super.addNode(node);
			this.root = node;
			this.root.depth = 0;
			this.levels = [new Set([this.root.id])];
			return;
		}
		if(!this.nodes[parentNode.id]){
			throw new Error("Parent Node does not Exist....Insert First ")
		}
		//console.log(node, parentNode);
		let link = new Link(parentNode.id,node.id);
		super.addNode(node);
		super.addLink(link);

		this.updateLevels(parentNode);
	}

	print() {
		console.log("§§§§§§§  Tree Structure 	 §§§§§§§§§§");

		this.levels.forEach((level, index) => {
			console.log("Level ", index);
			level.forEach( id => console.log(` ${id} `));
		});
		console.log("§§§§§§§  Tree Structure End §§§§§§§§§§");
	}

	remove(node,propagation = true){
		propagation ? this.cleanup(node) : null;
		this.levels[node.depth].delete(node.id);
		if(this.levels[node.depth].size === 0) {
			this.levels.length = node.depth;
		}
		super.removeNode(node);
	}

	cleanup(parent) {
		let children = this.getChildren(parent);
		children.forEach(child => {
			this.remove(child);
		});
	}
	
	getParent(node) {
		return [...this.links[node.id].in].map(link => link.from)[0];
	}

	getChildren(node) {
		return [...this.links[node.id].out].map(link => this.getNode(link.to));
	}

	getLevel(depth) {
		return this.levels[depth];
	}

	replaceNode(oldNode,newNode){	
		if(!this.nodes[oldNode.id]){
			throw new Error("The Node you are trying to replace doesnt exist in the current Tree")
		}
		newNode.depth = oldNode.depth
		let OldLinks = this.getLinks(oldNode.id);
		let OldFromNode;
		let OldToNodes = [];
		let OldDepth = oldNode.depth;
		let levels = this.levels;
		for(let m of OldLinks.in){
			OldFromNode = m.from
		}
		for(let k of OldLinks.out){
			OldToNodes.push(k.to)
		}

		this.remove(oldNode,false);
		
		//If the tree contains only the root this code will run
		if(this.levels.length === 0){
			this.levels =  [new Set([newNode.id])]
			super.addNode(newNode);
			this.root = newNode;
			return true;;
			
		}
		//THIS PROCESS 
		this.levels[OldDepth].add(newNode.id);
		super.addNode(newNode);
		if(OldFromNode){
			super.linkNodes(OldFromNode,newNode.id);
		}


		OldToNodes.map((node) => {
			
			super.linkNodes(newNode.id,node)
		})
		
		if(oldNode === this.root){
			console.log("never")
			this.root = newNode
		}

	}

	export() {
		let data = super.export();
		let levels = this.levels.map(level => Array.from(level));
		return {
			...data,
			levels,
			rootId: this.root.id
		};
	}

	import(data, nodeType = TreeNode, parentNode) {
		let { levels, rootId, ...rest } = data;
		super.import(data, nodeType);
		if(!parentNode) {
			this.root = super.getNode(rootId);
			this.levels = levels.map(setArray => new Set(setArray));
		} else {
			this.linkNodes(parentNode, rootId);
			this.updateLevels(this.getNode(parentNode));
		}
	}

}

//console.log("SSSSSSSSSSSSSSSS")
//console.log(A)

 //var B = new TreeNode('B');
 //var C = new TreeNode('C');
 //var D = new TreeNode('D');
 //var E = new TreeNode('E');
 //var F = new TreeNode("F");
//console.log(tree.root)
//console.log(tree)
// tree.insert(B,A)
// tree.insert(D,B)
// tree.insert(E,D)
// tree.insert(F,D)

// tree.print();

// tree.remove(D)

// tree.print();

module.exports = { Tree, TreeNode };