Source: renderers/MustacheRenderer.js

import { Shark } from "../Shark";
import Mustache from "mustache";
import $ from "jquery";
import SmartModel from "../SmartModel";
import { Utils } from "../Utils";

/**
 * Is the bridge between the Shark main class and Mustache.
 * @constructor
 */

var MustacheRenderer = function () {
	if (typeof Mustache === "undefined") {
		throw(new Error("Mustache is needed to use MustacheRenderer!"));
	}
}

/**
 * Takes a Jaw, get the template from Shark, put the dataObject from the Jaw into the template and add the rendered data into the container
 * passed in the options.
 *
 * @param {Jaw} jaw - The view to render.
 * @param {object} options - The object with the options to setup the render.
 * @param {string} options.container - The CSS selector to use to insert the rendered content. To place the content is used jQuery.html() method.
 */
MustacheRenderer.prototype.render = function (jaw, options) {
	//Dave.ToDo: Qui va inserita tutta la validazione per essere certi che il renderer abbia tutto ciò che gli server.
	console.time("MustacheRenderer.render");

	Shark.trace("MustacheRenderer.render: ", jaw, options, Shark.TRACE_INFO);

	var defaultOptions = {
		renderOnScreen: true
	};

	options = $.extend(defaultOptions, options);

	if (!jaw.__templateParsed) {
		//Dave.ToDo: Sarebbe interessante poter gestire più template per un singolo componente (tipo, il bottone che apre il modal e il modal) da poter richiamare tipo componente.template
		var templateContent = jaw.templateContent;// || Shark.getTemplate(jaw.templateRoute);
	
		//Dave.ToDo: Questa cosa andrebbe spostata nell'init, così da non doverlo fare ad ogni render
		var pc = 0;

		var subTemplateList = templateContent.match(/{{>[\w\.]+\}\}/g);

		while (subTemplateList && pc < 100) {
			subTemplateList.forEach(function (item) {
				var componentName = item.replace("{{>", "").replace("}}", "");

				if (componentName) {
					//Dave: ho rimosso "Shark.getTemplate" ma qui non lo tolgo.. se qualcuno lo usa si deve rompere..
					templateContent = templateContent.replace(item, Shark.getTemplate(componentName));
				}
				else {
					Shark.trace("MustacheRenderer.render > Missing SubTemplate: " + componentName, Shark.TRACE_ERROR);
				}
			});

			subTemplateList = templateContent.match(/{{>[\w\.]+\}\}/g);
			pc++;
		}

		var componentList = templateContent.match(/{{=>[\w\.]+\}\}/g);
		//	var componentTemplates = {};
	
		while (componentList) {
			componentList.forEach(function (item) {
				let processDefaultTemplate = true;
				let componentName = item.replace("{{=>", "").replace("}}", "");
				
				if (componentName.indexOf(".") > -1) {
					componentName = componentName.substr(0, componentName.indexOf("."));
					processDefaultTemplate = false;
				}

				let component = Shark.__components[componentName];
			
				if (component) {
					if (processDefaultTemplate) {
						templateContent = templateContent.replace(item, component.templateContent/* || Shark.getTemplate(component.templateRoute)*/);//templateRoute will be Deprecated
					}

					if (component.__templates) {
						for (let currTemplateName in component.__templates) {
							templateContent = templateContent.replace(`{{=>${componentName}.${currTemplateName}}}`, component.__templates[currTemplateName]);
						}
					}
				}
				else {
					Shark.trace("MustacheRenderer.render > Missing component: " + componentName, Shark.TRACE_ERROR);
					templateContent = templateContent.replace(item, item.replace("{{=>", "{{>"));
				}
			});

			componentList = templateContent.match(/{{=>[\w\.]+\}\}/g);
		}

		//------------------------------------------------------------------------
		let customHtmlComponentRegEx = new RegExp(/<shark-[\w]+.+\/*>/g);
		let customHtmlComponentList = templateContent.match(customHtmlComponentRegEx);
	
		while (customHtmlComponentList) {
			customHtmlComponentList.forEach(function (item) {
				let componentName = item;
				let templateName = "";

				if (componentName.indexOf("></shark-") > -1) {
					componentName = componentName.substr(0, componentName.indexOf("></shark-"));
				}
				if (componentName.indexOf("/>") > -1) {
					componentName = componentName.substr(0, componentName.indexOf("/>"));
				}
				if (componentName.indexOf(">") > -1) {
					componentName = componentName.substr(0, componentName.indexOf(">"));
				}
				if (componentName.indexOf(" ") > -1) {
					componentName = componentName.substr(0, componentName.indexOf(" "));
				}
				componentName = componentName.replace("<shark-", "");
				
				if (componentName.indexOf("-") > -1) {
					componentName = Utils.dashedToCamel(componentName);
				}

				let templateAttrStart = item.indexOf("template=\"");
				if (templateAttrStart > -1) {
					templateName = item.substring(templateAttrStart + 10, item.indexOf("\"", templateAttrStart + 11));

					if (templateName === "default") {
						templateName = "";
					}
				}

				let component = Shark.__components[componentName];
				if (component) {
					if (templateName === "") {
						templateContent = templateContent.replace(item, component.templateContent);
					}
					else {
						if (component.__templates && component.__templates[templateName]) {
							templateContent = templateContent.replace(item, component.__templates[templateName]);
						}
					}
				}
				else {
					Shark.trace("MustacheRenderer.render > Missing component: " + componentName + ", removing from template.", Shark.TRACE_ERROR);
					templateContent = templateContent.replace(item, "");
				}
			});

			customHtmlComponentList = templateContent.match(customHtmlComponentRegEx);
		}
		//------------------------------------------------------------------------

		templateContent = templateContent.replace(/data-sh-model=\"(.\S+)\"/g, `$& data-shguid="{{$1.__shguid}}"`);
		// templateContent = templateContent.replace(/data-sh-model=\"(.\S+)\"/g, `$& data-__shguid="{{__shguid}}"`);

		jaw.__autoBoundListSection = [];
		const mustacheDynamic = templateContent.match(/{{\[\>[\w\.]+\}\}/g);
		// const mustacheDynamic = /{{\[\>[\w\.]+\}\}/g.exec(templateContent);

		if (mustacheDynamic) {
			mustacheDynamic.forEach(listSection => {
				const propertyName = listSection.replace("{{[>", "").replace("}}", "");
				const mustacheDynamicRE = new RegExp("{{\\[\\>" + propertyName + "\\}\\}([\\s\\S]*){{" + propertyName + "\\<\\]\\}\\}", "g");
				const templates = mustacheDynamicRE.exec(templateContent);
				let itemTemplate = templates[1];

				const tagStart = itemTemplate.indexOf(">");

				itemTemplate = itemTemplate.substr(0, tagStart) + ` data-shguid="{{__shguid}}"` + itemTemplate.substr(tagStart);
				// itemTemplate = itemTemplate.substr(0, tagStart) + ` data-__shguid="{{__shguid}}"` + itemTemplate.substr(tagStart);

				console.log({listSection})
				jaw.__autoBoundListSection.push({
					propertyName,
					template: itemTemplate
				});

				const openTagRE = new RegExp("{{\\[\\>(" + propertyName + ")\\}\\}", "g");
				const closeTagRE = new RegExp("{{(" + propertyName + ")\\<\\]\\}\\}", "g");

				templateContent = templateContent.replace(openTagRE, "<shark-list property=\"" + propertyName + "\">{{#$1}}");
				templateContent = templateContent.replace(closeTagRE, "{{/$1}}</shark-list>");
			});
			console.log({__autoBoundListSection: jaw.__autoBoundListSection});
		}

		// /{{\[\>taskList\}\}([\s\S]*){{taskList\<\]\}\}/g

		//Occhio alle {{{ }}}
		//First attempt to manage autorender of bound properties
		console.time("autoBind");
		Shark.trace({
			"renderField": templateContent.match(/{{[\w\.]+\}\}/g),
			"renderListOrPresence": templateContent.match(/{{#[\w\.]+\}\}/g),
			"renderAbsence": templateContent.match(/{{\^[\w\.]+\}\}/g),
		}, Shark.TRACE_LOG);
		Shark.trace({
			"renderField": [...new Set(templateContent.match(/{{[\w\.]+\}\}/g))].map(item => item.replace("{{", "").replace("}}", "")),
			"renderListOrPresence": [...new Set(templateContent.match(/{{#[\w\.]+\}\}/g))].map(item => item.replace("{{#", "").replace("}}", "")),
			"renderAbsence": [...new Set(templateContent.match(/{{\^[\w\.]+\}\}/g))].map(item => item.replace("{{^", "").replace("}}", "")),
		}, Shark.TRACE_LOG);

		const renderField = [...[...new Set(templateContent.match(/{{[\w\.]+\}\}/g))].map(item => item.replace("{{", "").replace("}}", "")),
		...[...new Set(templateContent.match(/{{#[\w\.]+\}\}/g))].map(item => item.replace("{{#", "").replace("}}", "")),
		...[...new Set(templateContent.match(/{{\^[\w\.]+\}\}/g))].map(item => item.replace("{{^", "").replace("}}", ""))];

		Shark.__autoBoundProperties = [];
	
		for (const value in Shark.__store) {
			if (renderField.indexOf(value) > -1) {
				Shark.__autoBoundProperties.push(value);
				jaw.__autoBoundProperties.push(value);
			}
		}
		Shark.trace({ renderField, SharkAutoBoundProperties: Shark.__autoBoundProperties, JawAutoBoundProperties: jaw.__autoBoundProperties });
		console.timeEnd("autoBind");
	
		for (const value in Shark.__store) {
			if (Shark.__store[value] instanceof SmartModel) {
				Shark.__store[value].__boundProperties = [];
			}
		}
		// for (const value in Shark.appData) {
		// 	if (Shark.appData[value] instanceof SmartModel) {
		// 		Shark.appData[value].__boundProperties = [];
		// 	}
		// }
		const dynamicFields = templateContent.match(/{{[\w\.]+\}\}/g);
		// var dynamicFields = templateContent.match(/[{[\w\.]+\]\}/g);
		if (dynamicFields && dynamicFields.length > 0) {
			dynamicFields.forEach(item => {
				// const propertyData = item.replace("{[", "").replace("]}", "").split(".");
				const propertyData = item.replace("{{", "").replace("}}", "").split(".");
				const model = Shark.get(propertyData[0]);

				if (model && model instanceof SmartModel) {
					if (model.__boundProperties.indexOf(propertyData[1]) === -1) {
						model.__boundProperties.push(propertyData[1]);
						Shark.trace("dynamicFields", model, "log");
					}
				}

				// templateContent = templateContent.replace(item, "");
			});
		}


		Shark.__dynamicSections = {};
		const dynamicSections = templateContent.match(/\<shark-section(.*?)\>/g);
		// const dynamicSections = templateContent.match(/{{@[\w\.]+\}\}/g);
		if (dynamicSections && dynamicSections.length > 0) {
			dynamicSections.forEach(item => {

				//<shark-section model="commentList">
				//</shark-section>
			
				const openTag = item;//.replace("@", "#");
				const closeTag = "</shark-section>";//item.replace("@", "@/");
				const dynamicSection = {
					closeTag,
					openTag,
					rGuid: Shark.__generateGUID("rid_"),
					storeReference: /model=\"(.*?)\"/g.exec(item)[1]// item.replace("{{@", "").replace("}}", "")
				};

				var templateRE = new RegExp(openTag + "(.*?)" + closeTag.replace("/", "\/"), "gs");

				dynamicSection.templateContent = templateRE.exec(templateContent)[1];
				// dynamicSection.templateContent = openTag + templateRE.exec(templateContent)[1] + closeTag;

				if (window.customElements) {
					templateContent = templateContent.replace(item, `<shark-section data-sh-rguid='${dynamicSection.rGuid}'>${openTag}`).replace(closeTag, `${closeTag}</shark-section>`);
				}
				else {
					templateContent = templateContent.replace(item, `<span data-sh-rguid='${dynamicSection.rGuid}'>${openTag}`).replace(closeTag, `${closeTag}</span>`);
				}

				if (Shark.__dynamicSections[dynamicSection.storeReference]) {
					Shark.__dynamicSections[dynamicSection.storeReference].push(dynamicSection);
				}
				else {
					Shark.__dynamicSections[dynamicSection.storeReference] = [dynamicSection];
				}
			});
			Shark.trace("dynamicSections", Shark.__dynamicSections, "log");
		}
		
		templateContent = templateContent.replace(/\[\[([\w\.]+)\]\]/g, `{{{__renderer.$1}}}`);

		jaw.__parsedTemplate = templateContent;
		jaw.__templateParsed = true;
	}

	const componentsStore = {};


	const processedComponentList = [];

	jaw.__activeComponents.forEach(component => {
		if (processedComponentList.indexOf(component.id) === -1) {
			Object.assign(componentsStore, component.__store);
		}

		//Ovviamente questa cosa va estratta per essere recursiva
		component.__activeComponents.forEach(subComponent => {
			if (processedComponentList.indexOf(subComponent.id) === -1) {
				Object.assign(componentsStore, subComponent.__store);
			}
		});
	});

	// jaw.__activeComponents.forEach(function (item) {
	// 	Object.assign(componentsStore, item.__store);
	// });

	const childTemplates = $.extend({}, Shark.__templateMap, jaw.__childTemplates);
	const renderStore = Object.assign({}, Shark.__store, componentsStore, jaw.__store);
	// console.log({ renderStore, Shark__store: Shark.__store, componentsStore, jaw__store: jaw.__store });
	const output = Mustache.render(jaw.__parsedTemplate, renderStore, childTemplates);

	if (options.renderOnScreen) {
		$(options.container).html(output);
		console.timeEnd("MustacheRenderer.render");
	}
	else {
		console.timeEnd("MustacheRenderer.render");
		return output;
	}
};

/**
 * Takes an options object to render a template with some data into a specific container.
 *
 * @param {object} options - The object with the options to setup the render.
 * @param {boolean} [options.append = false] - If true, the rendered content is appended to the containerSelector parameter and will not replace its content.
 * @param {object} [options.childTemplates = {}] - An object that contains sub-templates data to be added to main template.
 * @param {string} [options.containerSelector = ""] - The CSS selector used to insert the rendered content. To place the content is used jQuery.html() method.
 * <br />If an empty string or other "falsable" value is passed, the rendered content is returned instead of being added to screen.
 * @param {object} [options.renderItems = {}] - An object containing data and functions to be rendered in the template.
 * @param {boolean} [options.replace = false] - If true, the containerSelector parameter is used to fully replace an item on the page instead of render inside that item. If this is set to true, the containerSelector parameter must be provided.
 * @param {string} options.templateSource - A string containing an html source representing the template to be rendered.
 * @deprecated - This method will be definitively removed in a future version.
 */
MustacheRenderer.prototype.renderItem = function (options) {
	if (!options.templateSource) {
		throw(new Error("MustacheRenderer.renderItem: To render an item you must specify the template parameter"));
	}

	var settings = {
		append: false,
		childTemplates: {},
		containerSelector: "",
		renderItems: {},
		replace: false,
		templateSource: null
	};
	$.extend(true, settings, options);

	Shark.trace("MustacheRenderer.renderItem", settings, Shark.TRACE_INFO);

	if (options.replace && options.containerSelector === "") {
		throw(new Error("Could not use replace parameter without setting a containerSelector!"));
	}

	var childTemplates = $.extend({}, Shark.__templateMap, settings.childTemplates);
	var output = Mustache.render(settings.templateSource, settings.renderItems, childTemplates);//settings.childTemplates);

	if (options.containerSelector !== "" && options.containerSelector) {
		if (options.append) {
			$(options.containerSelector).append(output);
		}
		else {
			if (options.replace) {
				$(options.containerSelector).replaceWith(output);
			}
			else {
				$(options.containerSelector).html(output);
			}
		}
	}
	else {
		return output;
	}
};

export { MustacheRenderer };