
const
	BLOCK_REGEX      = /\{\{([^>]+)\s?>\s?\}\}(.*)\{\{\s?<\s?\}\}/g,
	INLINE_TAG_REGEX = /\{\{([^>]+)>\s\}\}([\s\S]*?){\{\s<\s\}\}/g,
	TAG_REGEX        = /\{\{([^\}]+)\}\}/g;


var DIGEST = { };


function evaluateTernary(key, data) {
	var
		parts       = key.split('?'),
		condition   = parts[0].trim(),
		assignments = parts[1].split(':'),
		result      = data[condition] ? assignments[0] : (assignments[1] || '');

	result = result.trim();

	if (result && result[0] !== '"' && result[0] !== "'") {
		result = data[result] || '';
	} else {
		result = result.replace(/["']/g, '');
	}

	return result;
}

function evaluateInclude(key, data) {
	var
		parts        = key.split('>'),
		prefix       = parts[0].trim(),
		include_path = parts[1].trim(),
		template     = DIGEST[include_path];

	if (!template) {
		throw new Error(`
			Template partial not found for path: ${include_path}
		`);
	}

	if (!prefix) {
		return render(template, data);
	}

	var value = data[prefix];

	if (value === undefined) {
		return '';
	}

	if (TypeHelper.isArray(value)) {
		return value.map(function map(item, index) {
			if (TypeHelper.isPrimitive(item)) {
				return render(template, {
					value: item,
					index: index
				});
			} else {
				return render(template, ObjectHelper.extend({ index: index }, item));
			}
		}).join('');
	}

	if (TypeHelper.isPrimitive(value)) {
		return render(template, {
			value: value
		});
	} else {
		return render(template, value);
	}
}

function replaceBlock(data, wrapped, prefix, contents) {
	prefix = prefix.trim();

	if (!prefix) {
		return '';
	}

	var value = data[prefix];

	if (value === undefined) {
		return render(contents);
	}

	if (TypeHelper.isPrimitive(value)) {
		return render(contents, {
			value: value
		});
	}

	if (TypeHelper.isArray(value)) {
		return value.map(function map(item, index) {
			if (TypeHelper.isPrimitive(item)) {
				return render(contents, {
					index: index,
					value: item
				});
			} else {
				return render(contents, item);
			}
		}).join('');
	}

	return render(contents, value);
}

function replaceInlineTag(data, wrapped, key, template) {
	key = key.trim();

	if (!key) {
		return render(template, data);
	}

	var value = data[key];

	if (value === undefined) {
		return '';
	}

	if (TypeHelper.isArray(value)) {
		return value.map(function map(item, index) {
			if (TypeHelper.isPrimitive(item)) {
				return render(template, {
					value: item,
					index: index
				});
			} else {
				if (TypeHelper.isFunction(item.toJSON)) {
					item = item.toJSON();
				}

				return render(template, ObjectHelper.extend({ index: index }, item));
			}
		}).join('');
	}

	if (TypeHelper.isPrimitive(value)) {
		return render(template, {
			value: value
		});
	} else {
		return render(template, value);
	}
}

function replaceTag(data, wrapped, key) {
	if (key.indexOf('?') !== -1) {
		return evaluateTernary(key, data);
	}

	if (key.indexOf('>') !== -1) {
		return evaluateInclude(key, data);
	}

	var
		parts  = key.split('.'),
		subset = data;

	while (parts.length && subset) {
		let part = parts.shift();

		subset = subset[part];
	}

	if (subset === undefined) {
		return '';
	} else {
		return subset;
	}
}

function render(template, data) {
	if (typeof template !== 'string') {
		if (template && typeof template.render === 'function') {
			return template.render(data);
		}

		return '';
	}

	if (!data) {
		data = { };
	} else if (TypeHelper.isFunction(data.toJSON)) {
		data = data.toJSON();
	}

	template = template.replace(INLINE_TAG_REGEX, replaceInlineTag.bind(this, data));

	template = template.replace(BLOCK_REGEX, replaceBlock.bind(this, data));

	template = template.replace(TAG_REGEX, replaceTag.bind(this, data));

	return template.replace(/[\t|\n\s]+/g, ' ');
}

function compile(string) {
	return {
		render: function(data) {
			return render(string, data);
		}
	};
}

function define(key, body) {
	DIGEST[key] = body;
}

module.exports = {
	render,
	define,
	compile
};
