/**
 * This is a Serializer class that will compare two versions of a DOM
 * and re-use the original wikitext for unmodified regions of the DOM.
 * Originally this relied on special change markers inserted by the
 * editor, but we now generate these ourselves using DOMDiff.
 * @module
 */

'use strict';

const { DOMDiff } = require('./DOMDiff.js');
const { ContentUtils } = require('../utils/ContentUtils.js');
const { DOMUtils } = require('../utils/DOMUtils.js');
const Promise = require('../utils/promise.js');
const { WikitextSerializer } = require('./WikitextSerializer.js');

/**
 * If we have the page source (this.env.page.src), we use the selective
 * serialization method, only reporting the serialized wikitext for parts of
 * the page that changed. Else, we fall back to serializing the whole DOM.
 *
 * @class
 * @param {Object} options Options for the serializer.
 * @param {MWParserEnvironment} options.env
 * @param {WikitextSerializer} [options.wts]
 */
class SelectiveSerializer {
	constructor(options) {
		this.env = options.env;
		this.wts = options.wts || new WikitextSerializer(options);

		// Debug options
		this.trace = this.env.conf.parsoid.traceFlags &&
				this.env.conf.parsoid.traceFlags.has("selser");

		// Performance Timing option
		this.metrics = this.env.conf.parsoid.metrics;
	}

	/**
	 * Selectively serialize an HTML DOM document.
	 *
	 * WARNING: You probably want to use FromHTML.serializeDOM instead.
	 * @func
	 * @param {Node} body
	 * @return {Promise}
	 */
	*serializeDOMG(body) {
		console.assert(DOMUtils.isBody(body), 'Expected a body node.');
		console.assert(this.env.page.editedDoc, 'Should be set.');  // See WSP.serializeDOM

		var serializeStart, domDiffStart;
		var metrics = this.metrics;
		var r;

		if (metrics) {
			serializeStart = Date.now();
		}
		if ((!this.env.page.dom && !this.env.page.domdiff) || this.env.page.src === null) {
			// If there's no old source, fall back to non-selective serialization.
			r = yield this.wts.serializeDOM(body, false);
			if (metrics) {
				metrics.endTiming('html2wt.full.serialize', serializeStart);
			}
		} else {
			var diff;
			// Use provided diff-marked DOM (used during testing)
			// or generate one (used in production)
			if (this.env.page.domdiff) {
				diff = this.env.page.domdiff;
				body = diff.dom;
			} else {
				if (metrics) {
					domDiffStart = Date.now();
				}

				// Strip <section> and mw:FallbackId <span> tags, if present.
				// This ensures that we can accept HTML from CX / VE
				// and other clients that might have stripped them.
				ContentUtils.stripSectionTagsAndFallbackIds(body);
				ContentUtils.stripSectionTagsAndFallbackIds(this.env.page.dom);

				diff = (new DOMDiff(this.env)).diff(this.env.page.dom, body);

				if (metrics) {
					metrics.endTiming('html2wt.selser.domDiff', domDiffStart);
				}
			}

			if (diff.isEmpty) {
				// Nothing was modified, just re-use the original source
				r = this.env.page.src;
			} else {
				if (this.trace || (this.env.conf.parsoid.dumpFlags &&
					this.env.conf.parsoid.dumpFlags.has('dom:post-dom-diff'))) {
					ContentUtils.dumpDOM(body, 'DOM after running DOMDiff', {
						storeDiffMark: true,
						env: this.env,
					});
				}

				// Call the WikitextSerializer to do our bidding
				r = yield this.wts.serializeDOM(body, true);
			}
			if (metrics) {
				metrics.endTiming('html2wt.selser.serialize', serializeStart);
			}
		}
		return r;
	}
}

SelectiveSerializer.prototype.serializeDOM = Promise.async(SelectiveSerializer.prototype.serializeDOMG);

if (typeof module === 'object') {
	module.exports.SelectiveSerializer = SelectiveSerializer;
}