/*!
 * VisualEditor DataModel TableNode class.
 *
 * @copyright See AUTHORS.txt
 */

/**
 * DataModel table node.
 *
 * @class
 * @extends ve.dm.BranchNode
 *
 * @constructor
 * @param {ve.dm.Node[]} [children]
 */
ve.dm.TableNode = function VeDmTableNode() {
	// Parent constructor
	ve.dm.TableNode.super.apply( this, arguments );

	// A dense representation of the sparse model to make manipulations
	// in presence of spanning cells feasible.
	this.matrix = new ve.dm.TableMatrix( this );

	// Events
	this.connect( this, { splice: 'onSplice' } );
};

/* Inheritance */

OO.inheritClass( ve.dm.TableNode, ve.dm.BranchNode );

/**
 * @event ve.dm.TableNode#cellAttributeChange
 * @param {ve.dm.TableCellableNode} cell
 */

/* Static Properties */

ve.dm.TableNode.static.name = 'table';

ve.dm.TableNode.static.childNodeTypes = [ 'tableSection', 'tableCaption' ];

ve.dm.TableNode.static.matchTagNames = [ 'table' ];

/* Methods */

/**
 * Handle splicing of child nodes
 */
ve.dm.TableNode.prototype.onSplice = function () {
	const nodes = Array.prototype.slice.call( arguments, 2 );
	this.getMatrix().invalidate();
	for ( let i = 0; i < nodes.length; i++ ) {
		nodes[ i ].connect( this, {
			cellAttributeChange: 'onCellAttributeChange'
		} );
	}
};

/**
 * Handle cell attribute changes
 *
 * @param {ve.dm.TableCellableNode} cell
 * @fires ve.dm.TableNode#cellAttributeChange
 */
ve.dm.TableNode.prototype.onCellAttributeChange = function ( cell ) {
	this.emit( 'cellAttributeChange', cell );
};

/**
 * Get table matrix for this table node
 *
 * @return {ve.dm.TableMatrix} Table matrix
 */
ve.dm.TableNode.prototype.getMatrix = function () {
	return this.matrix;
};

/**
 * Get the table's caption node, if it exists
 *
 * @return {ve.dm.TableCaptionNode|null} The table's caption node, or null if not found
 */
ve.dm.TableNode.prototype.getCaptionNode = function () {
	for ( let i = 0, l = this.children.length; i < l; i++ ) {
		if ( this.children[ i ] instanceof ve.dm.TableCaptionNode ) {
			return this.children[ i ];
		}
	}
	return null;
};

/**
 * Provides a cell iterator that allows convenient traversal regardless of
 * the structure with respect to sections.
 *
 * @return {ve.dm.TableNodeCellIterator}
 */
ve.dm.TableNode.prototype.getIterator = function () {
	return new ve.dm.TableNodeCellIterator( this );
};

/* Registration */

ve.dm.modelRegistry.register( ve.dm.TableNode );

/**
 * A helper class to iterate over the cells of a table node.
 *
 * It provides a unified interface to iterate cells in presence of table sections,
 * e.g., providing consecutive row indexes.
 *
 * @class
 * @mixes OO.EventEmitter
 *
 * @constructor
 * @param {ve.dm.TableNode} tableNode Table node to iterate through
 */
ve.dm.TableNodeCellIterator = function VeDmTableNodeCellIterator( tableNode ) {
	// Mixin constructors
	OO.EventEmitter.call( this );

	this.table = tableNode;

	this.sectionIndex = 0;
	this.rowIndex = 0;
	this.cellIndex = 0;

	this.sectionCount = this.table.children.length;
	this.rowCount = 0;
	this.cellCount = 0;

	this.sectionNode = null;
	this.rowNode = null;
	this.cellNode = null;

	this.finished = false;
};

/* Inheritance */

OO.mixinClass( ve.dm.TableNodeCellIterator, OO.EventEmitter );

/* Events */

/**
 * @event ve.dm.TableNodeCellIterator#newSection
 * @param {ve.dm.TableSectionNode} node Table section node
 */

/**
 * @event ve.dm.TableNodeCellIterator#newRow
 * @param {ve.dm.TableRowNode} node Table row node
 */

/* Methods */

/**
 * Check if the iterator has finished iterating over the cells of a table node.
 *
 * @return {boolean} Iterator is finished
 */
ve.dm.TableNodeCellIterator.prototype.isFinished = function () {
	return this.finished;
};

/**
 * Get the next cell node
 *
 * @return {ve.dm.TableCellNode|null|undefined} Next cell node, null if a not a table cell, or undefined if at the end
 * @throws {Error} TableNodeCellIterator has no more cells left.
 */
ve.dm.TableNodeCellIterator.prototype.next = function () {
	if ( this.isFinished() ) {
		throw new Error( 'TableNodeCellIterator has no more cells left.' );
	}
	this.nextCell( this );
	return this.cellNode;
};

/**
 * Move to the next table section
 *
 * @fires ve.dm.TableNodeCellIterator#newSection
 */
ve.dm.TableNodeCellIterator.prototype.nextSection = function () {
	// If there are no sections left, finish
	if ( this.sectionIndex >= this.sectionCount ) {
		this.finished = true;
		this.sectionNode = undefined;
		return;
	}
	// Get the next node and make sure it's a section node (and not an alien node)
	const sectionNode = this.table.children[ this.sectionIndex ];
	this.sectionIndex++;
	this.rowIndex = 0;
	if ( sectionNode instanceof ve.dm.TableSectionNode ) {
		this.sectionNode = sectionNode;
		this.rowCount = this.sectionNode.children.length;
		this.emit( 'newSection', this.sectionNode );
	} else {
		this.nextSection();
		return;
	}
};

/**
 * Move to the next table row
 *
 * @fires ve.dm.TableNodeCellIterator#newRow
 */
ve.dm.TableNodeCellIterator.prototype.nextRow = function () {
	// If there are no rows left, go to the next section
	if ( this.rowIndex >= this.rowCount ) {
		this.nextSection();
		if ( this.isFinished() ) {
			this.rowNode = undefined;
			return;
		}
	}
	// Get the next node and make sure it's a row node (and not an alien node)
	const rowNode = this.sectionNode.children[ this.rowIndex ];
	this.rowIndex++;
	this.cellIndex = 0;
	if ( rowNode instanceof ve.dm.TableRowNode ) {
		this.rowNode = rowNode;
		this.cellCount = this.rowNode.children.length;
		this.emit( 'newRow', this.rowNode );
	} else {
		this.nextRow();
		return;
	}
};

/**
 * Move to the next table cell
 */
ve.dm.TableNodeCellIterator.prototype.nextCell = function () {
	// For the first read, sectionNode and rowNode will be empty
	if ( !this.sectionNode ) {
		this.nextSection();
	}
	if ( !this.rowNode ) {
		this.nextRow();
	}
	// If there are no cells left, go to the next row
	if ( this.cellIndex >= this.cellCount ) {
		this.nextRow();
		// If calling next row finished the iterator, clear and return
		if ( this.isFinished() ) {
			this.cellNode = undefined;
			return;
		}
	}
	// Get the next node and make sure it's a cell node (and not an alien node)
	const cellNode = this.rowNode.children[ this.cellIndex ];
	this.cellNode = cellNode && cellNode.isCellable() ? cellNode : null;
	this.cellIndex++;
};
