'use strict';


class TreeListNode {


  /** @summary Create a node for the TreeList
   * @author Arthur Beaulieu
   * @since October 2020
   * @description <blockquote>This class is made to create each node in the TreeList. Nodes are build recursively and stored in 
   * a child nodes array. Each node contains identification information, among other useful values ; depth in tree, expand state
   * and leaf state. They also hold any mouse interaction the user could have on the node. Finally, those nodes are expandable/collapsable
   * if they contain child nodes.</blockquote>
   * @param {object} options - The node options
   * @param {number} [options.id=-1] - The element ID, must be provided in model input object 
   * @param {string} options.name - The node name, mandatory as it is the <code>innerHTML</code> content 
   * @param {number} options.depth - The node depth in tree list, mandatory as it is the <code>innerHTML</code> content 
   * @param {function} [options.clicked] - The callback function to provided that will be called each time the node is clicked */
	constructor(options = {}) {
    // Prevent wrong type for arguments, fallback according to attribute utility
    if (typeof options.id !== 'number') {
      options.id = -1;
    }
    if (typeof options.name !== 'string') {
      options.name = 'Invalid model item';
    }
    if (typeof options.depth !== 'number') {
      options.depth = -1;
    }
    if (typeof options.clicked !== 'function') {
      options.clicked = undefined;
    }     
    /** @private
     * @member {number} - The node ID */
		this._id = options.id;
    /** @private
     * @member {string} - The node name to display */		
		this._name = options.name;
    /** @private
     * @member {number} - The node depth in graph */		
		this._depth = options.depth;
    /** @private
     * @member {function} - The callback function to call when node is clicked */		
		this._clicked = options.clicked;
		// Node flags for leaf type or expand state		
    /** @private
     * @member {boolean} - The leaf state of node */		
		this._isLeaf = false;
    /** @private
     * @member {boolean} - The expand state of node */
		this._isExpanded = false;
		// Children must be build later, in caller side with setExpander() method
    /** @private
     * @member {array} - The node children */
		this._children = [];
		// TreeListNode DOM elements
    /** @private
     * @member {object} - The node DOM elements */		
		this._dom = {
			container: null,
			label: null,
			expander: null,
			icon: null,
			iconPath: null
		};
		// Collapse/Expand svg pathes
    /** @private
     * @member {object} - Expand/Collapse icon svg pathes */		
		this._svgPath = {
			expand: 'M 15.000348,6.2497966 H 8.7502034 V -3.4865e-4 H 6.2501452 V 6.2497966 H 0 V 8.7498548 H 6.2501452 V 15 H 8.7502034 V 8.7498548 h 6.2501446 z',
			collapse: 'M 0,6.2493128 H 15.004124 V 8.7506872 H -0.0041235 z'
		};
		// Event binding for proper remove listener on destroy node
		this._onNodeClick = this._onNodeClick.bind(this);
		this._onNodeToggle = this._onNodeToggle.bind(this);
		// Build the node DOM with its internal info
		this._buildUI();
	}


  /** @method
   * @name destroy
   * @public
   * @memberof TreeListNode
   * @description <blockquote>TreeListNode destructor. Will delete node and its children, their events and their properties.</blockquote> */
	destroy() {
		// Call destroy method on each child node
		for (let i = 0; i < this._children.length; ++i) {
			this._children[i].destroy();
		}
		// Remove click listener
		this._dom.label.removeEventListener('click', this._onNodeClick, false);
		// On node toggle is only listened if node is expandable/collapsable
		if (!this._isLeaf) {
			this._dom.expander.removeEventListener('click', this._onNodeToggle, false);
		}
    // Delete object attributes
    Object.keys(this).forEach(key => {
      delete this[key];
    });		
	}


  /*  --------------------------------------------------------------------------------------------------------------- */
  /*  ------------------------------------  TREELISTNODE JS INTERN METHODS  ----------------------------------------  */
  /*                                                                                                                  */
  /*  The following methods are made to build node and make them interactive.                                         */
  /*  --------------------------------------------------------------------------------------------------------------- */ 


  /** @method
   * @name _buildUI
   * @private
   * @memberof TreeListNode
   * @description <blockquote>This method will build the node DOM element and fill their attributes.</blockquote> */
	_buildUI() {
		// Create node DOM elements
		this._dom.container = document.createElement('DIV');
		this._dom.label = document.createElement('P');
		// Update node DOM internal attributes
		this._dom.container.classList.add('tree-list-node');
		this._dom.container.dataset.id = this._id;
		this._dom.label.innerHTML = this._name;
		// React to event and append to DOM
		this._dom.label.addEventListener('click', this._onNodeClick, false);
		this._dom.container.appendChild(this._dom.label);
	}


  /** @method
   * @name _onNodeClick
   * @private
   * @memberof TreeListNode
   * @description <blockquote>This method is called on click event on the node container. It will call the given callback
   * if any was provided.</blockquote> */
	_onNodeClick() {
		// Only fire callback if one has been provided
		if (typeof this._clicked === 'function') {
			this._clicked({
				id: this._id,
				name: this._name,
				depth: this._depth,
				isLeaf: this._isLeaf,
				isExpanded: this._isExpanded
			});
		}
	}


  /** @method
   * @name _onNodeToggle
   * @private
   * @memberof TreeListNode
   * @description <blockquote>This method will collapse or expand node only if it has children.</blockquote> */
	_onNodeToggle() {
		// Only toggle if node has children
		if (this._children.length > 0) {
			if (this._isExpanded) { // Collapse node
				this.collapse(false);
			} else { // Expand node
				this.expand(false);
			}
		}
	}


  /*  --------------------------------------------------------------------------------------------------------------- */
  /*  --------------------------------------  TREELIST JS PUBLIC METHOD  -------------------------------------------  */
  /*                                                                                                                  */
  /*  The following methods are made expand all or collapse all nodes in the tree list.                               */
  /*  --------------------------------------------------------------------------------------------------------------- */


  /** @method
   * @name setExpander
   * @public
   * @memberof TreeListNode
   * @description <blockquote>This method will append an expander to the node only if it has children. It must be called
   * we all the TreeList is created, since it is recursive. See TreeList implementation.</blockquote> */
	setExpander() {
		if (this._children.length === 0) { // No children, no expander
			this._isLeaf = true;
		} else { // Create expander for node
			// Icon DOM elements
			this._dom.expander = document.createElement('DIV');
			this._dom.icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
			this._dom.iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
			// Update icon elements attributes
			this._dom.expander.classList.add('vector');
			this._dom.iconPath.setAttribute('d', this._svgPath.expand);
    	this._dom.iconPath.setAttribute('fill', '#000000');
    	// react to toggle event and append icon elements to the node container
			this._dom.expander.addEventListener('click', this._onNodeToggle, false);
  		this._dom.icon.appendChild(this._dom.iconPath);
  		this._dom.expander.appendChild(this._dom.icon);
			this._dom.container.insertBefore(this._dom.expander, this._dom.container.firstChild); // Insert before to put it first
		}
	}


  /** @method
   * @name expand
   * @public
   * @memberof TreeListNode
   * @description <blockquote>This method will expand the node only if it has children. The expand can be recursiv through the
   * node children if given flag is provided.</blockquote>
   * @param {boolean} [recursive=false] - Propagate the expand call through each children of this node */
	expand(recursive = false) {
		if (this._children.length > 0) {
			this._dom.iconPath.setAttribute('d', this._svgPath.collapse);
			// Append children into node container
			for (let i = 0; i < this._children.length; ++i) {
				// Call expand on children and pass recursive flag
				if (recursive) {
					this._children[i].expand(recursive);
				}
				// Append child from DOM container
				this._dom.container.appendChild(this._children[i].container);
			}
			// Update expand flag
			this._isExpanded = true;
		}
	}


  /** @method
   * @name collapse
   * @public
   * @memberof TreeListNode
   * @description <blockquote>This method will collapse the node only if it has children. The collapse can be recursiv through the
   * node children if given flag is provided.</blockquote>
   * @param {boolean} [recursive=false] - Propagate the collapse call through each children of this node */
	collapse(recursive = false) {
		if (this._children.length > 0) {
			this._dom.iconPath.setAttribute('d', this._svgPath.expand);
			// Remove children from node container
			for (let i = 0; i < this._children.length; ++i) {
				// Call collapse on children and pass recursive flag
				if (recursive) {
					this._children[i].collapse(recursive);
				}
				// Remove child from DOM container
				this._dom.container.removeChild(this._children[i].container);
			}
			// Update expand flag
			this._isExpanded = false;
		}
	}


  /** @method
   * @name expandAll
   * @public
   * @memberof TreeListNode
   * @description <blockquote>This method will expand all nodes starting from this node.</blockquote> */
	expandAll() {
		// Recursively expand node
		this.expand(true);
	}


  /** @method
   * @name collapseAll
   * @public
   * @memberof TreeListNode
   * @description <blockquote>This method will collapse all nodes starting from this node.</blockquote> */
	collapseAll() {
		// Recursively collapse node
		this.collapse(true);
	}


  /** @public
   * @member {array} - The node children */
	get children() { return this._children; }


  /** @public
   * @member {object} - The node DOM container */
	get container() { return this._dom.container; }


};


export default TreeListNode;