package org.skyve.impl.report.freemarker;

import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;

import org.skyve.CORE;
import org.skyve.domain.Bean;
import org.skyve.util.Binder;

import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
import freemarker.template.utility.DeepUnwrap;

/**
 * FreeMarker user-defined directive that performs a Skyve display format on the specified value.
 *
 * <p>
 * <b>Directive info</b>
 * </p>
 *
 * <p>
 * Directive parameters:
 * <ul>
 * <li><code>bean</code>: The bean which the attribute to format belongs to
 * <li><code>binding</code>: The attribute name to format, can be a compound binding
 * </ul>
 * <p>
 * Loop variables: None
 * <p>
 * Directive nested content: No
 * 
 * <p>
 * Usage: <code><@format bean=fund binding="dateEff" /></code>.
 * </p>
 */
public class FormatDirective implements TemplateDirectiveModel {
	private static final String PARAM_NAME_BEAN = "bean";
	private static final String PARAM_NAME_BINDING = "binding";

	@Override
	public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
	throws TemplateException, IOException {
		if (params.isEmpty()) {
			throw new TemplateModelException("This directive requires parameters.");
		}

		if (loopVars.length != 0) {
			throw new TemplateModelException("This directive doesn't allow loop variables.");
		}

		// If there is non-empty nested content:
		if (body != null) {
			throw new TemplateModelException("This directive doesn't allow nested content.");
		}

		// process the parameters
		Bean beanParam = null;
		String bindingParam = null;

		Iterator<?> paramIter = params.entrySet().iterator();
		while (paramIter.hasNext()) {
			Map.Entry<?, ?> ent = (Map.Entry<?, ?>) paramIter.next();

			String paramName = (String) ent.getKey();
			TemplateModel paramValue = (TemplateModel) ent.getValue();

			if (paramName.equals(PARAM_NAME_BEAN)) {
				// unwrap to try get the skyve object
				Object beanObj = DeepUnwrap.permissiveUnwrap(paramValue);
				if (! (beanObj instanceof Bean)) {
					throw new TemplateModelException(String.format("The '%s' parameter must be a Skyve bean.", PARAM_NAME_BEAN));
				}
				beanParam = (Bean) beanObj;
			}
			else if (paramName.equals(PARAM_NAME_BINDING)) {
				if (! (paramValue instanceof TemplateScalarModel)) {
					throw new TemplateModelException(String.format("The '%s' parameter must be a String.", PARAM_NAME_BINDING));
				}
				bindingParam = ((TemplateScalarModel) paramValue).getAsString();
			}
			else {
				throw new TemplateModelException("Unsupported parameter: " + paramName);
			}
		}

		// do the actual directive execution
		try (Writer out = env.getOut()) {
			if (beanParam != null && bindingParam != null) {
				out.write(Binder.getDisplay(CORE.getCustomer(), beanParam, bindingParam));
			}
		}
	}
}

@@ -137,16 +137,67 @@
 l.Map;%0A%0A
+import org.apache.commons.lang3.StringEscapeUtils;%0A
 import o
@@ -308,16 +308,65 @@
 onment;%0A
+import freemarker.template.TemplateBooleanModel;%0A
 import f
@@ -1389,16 +1389,102 @@
 nding%22;%0A
+%09private static final String PARAM_ESCAPE = %22escape%22;%0A%09%0A%09@SuppressWarnings(%22rawtypes%22)
 %0A%09@Overr
@@ -2096,16 +2096,46 @@
  = null;
+%0A%09%09boolean escapeParam = true;
 %0A%0A%09%09Iter
@@ -2792,17 +2792,16 @@
 %09%09%09if (!
- 
 (paramVa
@@ -3028,28 +3028,477 @@
 ring();%0A%09%09%09%7D
-%0A%09%09%09
+ else if (paramName.equals(PARAM_ESCAPE)) %7B%0A%09%09%09%09if (paramValue instanceof TemplateBooleanModel) %7B%0A%09%09%09%09%09escapeParam = ((TemplateBooleanModel) paramValue).getAsBoolean();%09%0A%09%09%09%09%7D else if(paramValue instanceof TemplateScalarModel) %7B%0A%09%09%09%09%09escapeParam = Boolean.parseBoolean(((TemplateScalarModel) paramValue).getAsString());%0A%09%09%09%09%7D else %7B%0A%09%09%09%09%09throw new TemplateModelException(String.format(%22The '%25s' parameter must be a boolean.%22, PARAM_ESCAPE));%0A%09%09%09%09%7D%0A%09%09%09%7D 
 else %7B%0A%09%09%09%09t
@@ -3624,13 +3624,8 @@
 n%0A%09%09
-try (
 Writ
@@ -3645,21 +3645,18 @@
 getOut()
-) %7B%0A%09
+;%0A
 %09%09if (be
@@ -3705,19 +3705,25 @@
 %0A%09%09%09
-%09out.write(
+String display = 
 Bind
@@ -3780,23 +3780,196 @@
 ngParam)
-);%0A%09%09%09%7D
+;%0A%09%09%09if (escapeParam) %7B%0A%09%09%09%09display = StringEscapeUtils.escapeHtml4(display);%0A%09%09%09%7D%0A%0A%09%09%09// replace /n with %3Cbr/%3E%0A%09%09%09display = display.replace(%22%5Cn%22, %22%3Cbr/%3E%22);%0A%0A%09%09%09out.write(display);
 %0A%09%09%7D%0A%09%7D%0A

Update FormatDirective to deal with escaped text