/** @module */

'use strict';

const TokenHandler = require('./TokenHandler.js');
const lastItem = require('../../utils/jsutils.js').JSUtils.lastItem;
const { TagTk, EndTagTk, SelfclosingTagTk, EOFTk } = require('../../tokens/TokenTypes.js');

/**
 * Small utility class that encapsulates the common 'collect all tokens
 * starting from a token of type x until token of type y or (optionally) the
 * end-of-input'. Only supported for synchronous in-order transformation
 * stages (SyncTokenTransformManager), as async out-of-order expansions
 * would wreak havoc with this kind of collector.
 *
 * @class
 * @extends module:wt2html/tt/TokenHandler
 */
class TokenCollector extends TokenHandler {
	constructor(manager, options) {
		super(manager, options);
		this.onAnyEnabled = false;
		this.scopeStack = [];
	}

	onTag(token) {
		return token.name === this.NAME() ? this._onDelimiterToken(token) : token;
	}

	onEnd(token) {
		return this.onAnyEnabled ? this._onDelimiterToken(token) : token;
	}

	onAny(token) {
		return this._onAnyToken(token);
	}

	// Token type to register for ('tag', 'text' etc)
	TYPE() { throw new Error('Not implemented'); }
	// (optional, only for token type 'tag'): tag name.
	NAME() { throw new Error('Not implemented'); }
	// Match the 'end' tokens as closing tag as well (accept unclosed sections).
	TOEND() { throw new Error('Not implemented'); }
	// FIXME: Document this!?
	ACKEND() { throw new Error('Not implemented'); }

	// Transform function
	transformation() {
		console.assert(false, 'Transformation not implemented!');
	}

	/**
	 * Handle the delimiter token.
	 * XXX: Adjust to sync phase callback when that is modified!
	 * @private
	 */
	_onDelimiterToken(token) {
		var haveOpenTag = this.scopeStack.length > 0;
		var tc = token.constructor;
		if (tc === TagTk) {
			if (this.scopeStack.length === 0) {
				this.onAnyEnabled = true;
				// Set up transforms
				this.manager.env.log('debug', 'starting collection on ', token);
			}

			// Push a new scope
			var newScope = [];
			this.scopeStack.push(newScope);
			newScope.push(token);

			return { };
		} else if (tc === SelfclosingTagTk) {
			// We need to handle <ref /> for example, so call the handler.
			return this.transformation([token, token]);
		} else if (haveOpenTag) {
			// EOFTk or EndTagTk
			this.manager.env.log('debug', 'finishing collection on ', token);

			// Pop top scope and push token onto it
			var activeTokens = this.scopeStack.pop();
			activeTokens.push(token);

			// clean up
			if (this.scopeStack.length === 0 || token.constructor === EOFTk) {
				this.onAnyEnabled = false;
			}

			if (tc === EndTagTk) {
				// Transformation can be either sync or async, but receives all collected
				// tokens instead of a single token.
				return this.transformation(activeTokens);
				// XXX sync version: return tokens
			} else {
				// EOF -- collapse stack!
				var allToks = [];
				for (var i = 0, n = this.scopeStack.length; i < n; i++) {
					allToks = allToks.concat(this.scopeStack[i]);
				}
				allToks = allToks.concat(activeTokens);

				var res = this.TOEND() ? this.transformation(allToks) : { tokens: allToks };
				if (res.tokens && res.tokens.length &&
						lastItem(res.tokens).constructor !== EOFTk) {
					this.manager.env.log("error", this.NAME(), "handler dropped the EOFTk!");

					// preserve the EOFTk
					res.tokens.push(token);
				}

				return res;
			}
		} else {
			// EndTagTk should be the only one that can reach here.
			console.assert(token.constructor === EndTagTk, "Expected an end tag.");
			if (this.ACKEND()) {
				return this.transformation([ token ]);
			} else {
				// An unbalanced end tag. Ignore it.
				return { tokens: [ token ] };
			}
		}
	}

	/**
	 * Handle 'any' token in between delimiter tokens. Activated when
	 * encountering the delimiter token, and collects all tokens until the end
	 * token is reached.
	 * @private
	 */
	_onAnyToken(token) {
		// Simply collect anything ordinary in between
		lastItem(this.scopeStack).push(token);
		return { };
	}
}

if (typeof module === "object") {
	module.exports.TokenCollector = TokenCollector;
}