package com.googlecode.totallylazy.template;
import com.googlecode.totallylazy.Maps;
import com.googlecode.totallylazy.Pair;
import com.googlecode.totallylazy.parser.Parser;
import com.googlecode.totallylazy.template.Renderers.Empty;
import com.googlecode.totallylazy.template.ast.Anonymous;
import com.googlecode.totallylazy.template.ast.Arguments;
import com.googlecode.totallylazy.template.ast.Attribute;
import com.googlecode.totallylazy.template.ast.Expression;
import com.googlecode.totallylazy.template.ast.FunctionCall;
import com.googlecode.totallylazy.template.ast.Grammar;
import com.googlecode.totallylazy.template.ast.ImplicitArguments;
import com.googlecode.totallylazy.template.ast.Indirection;
import com.googlecode.totallylazy.template.ast.Mapping;
import com.googlecode.totallylazy.template.ast.Name;
import com.googlecode.totallylazy.template.ast.NamedArguments;
import com.googlecode.totallylazy.template.ast.Text;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import static com.googlecode.totallylazy.Maps.map;
import static com.googlecode.totallylazy.Maps.mapValues;
import static com.googlecode.totallylazy.Sequences.one;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Strings.contains;
import static com.googlecode.totallylazy.Strings.string;
import static com.googlecode.totallylazy.Unchecked.cast;
public class Template implements Renderer<Map<String, Object>> {
private final List<Expression> template;
private final Renderers parent;
private Template(List<Expression> template, Renderers parent) {
this.template = template;
this.parent = parent;
}
public static Template template(String template) {
return template(template, Empty.Instance);
}
public static Template template(String template, Renderers parent) {
return template(template, parent, Grammar.parser());
}
public static Template template(String template, Parser<List<Expression>> parser) {
return new Template(parser.parse(template).value(), Empty.Instance);
}
public static Template template(String template, Renderers parent, Parser<List<Expression>> parser) {
return new Template(parser.parse(template).value(), parent);
}
public static Template template(InputStream template) {
return template(template, Empty.Instance);
}
public static Template template(InputStream template, Renderers parent) {
return template(template, parent, Grammar.parser());
}
public static Template template(InputStream template, Parser<List<Expression>> parser) {
return new Template(parser.parse(template).value(), Empty.Instance);
}
public static Template template(InputStream template, Renderers parent, Parser<List<Expression>> parser) {
return new Template(parser.parse(template).value(), parent);
}
@Override
public Appendable render(Map<String, Object> context, Appendable appendable) throws IOException {
return sequence(template).
fold(appendable, (a, node) -> append(node, context, a));
}
Appendable append(Expression expression, Map<String, Object> context, Appendable appendable) throws Exception {
if(expression instanceof Text) return appendable.append(((Text) expression).value());
if(expression instanceof Attribute) return append((Attribute) expression, context, appendable);
if(expression instanceof FunctionCall) return append((FunctionCall) expression, context, appendable);
if(expression instanceof Indirection) return append(((Indirection) expression).value(), context, appendable);
if(expression instanceof Anonymous) return append((Anonymous) expression, context, appendable);
if(expression instanceof Mapping) return append((Mapping) expression, context, appendable);
throw new IllegalArgumentException("Unknown expression type: " + expression);
}
Appendable append(Attribute attribute, Map<String, Object> context, Appendable appendable) throws Exception {
return parent.render(value(attribute, context), appendable);
}
Appendable append(FunctionCall functionCall, Map<String, Object> context, Appendable appendable) throws Exception {
return parent.get(name(functionCall.name(), context)).render(arguments(functionCall.arguments(), context), appendable);
}
Appendable append(Anonymous anonymous, Map<String, Object> context, Appendable appendable) throws Exception {
return new Template(anonymous.template(), parent).render(context, appendable);
}
Appendable append(Mapping mapping, Map<String, Object> context, Appendable appendable) throws Exception {
Anonymous anonymous = mapping.expression();
Object value = value(mapping.attribute(), context);
if(value instanceof Map) return appendPairs(anonymous, Maps.pairs(cast(value)), appendable);
if(value instanceof Iterable) return appendIterable(anonymous, (Iterable<?>) value, appendable);
return appendIterable(anonymous, one(value), appendable);
}
Appendable appendIterable(Anonymous anonymous, Iterable<?> values, Appendable appendable) throws Exception {
return appendPairs(anonymous, sequence(values).zipWithIndex(), appendable);
}
Appendable appendPairs(Anonymous anonymous, Iterable<? extends Pair<?, ?>> pairs, Appendable appendable) throws Exception {
return sequence(pairs).fold(appendable, (a, p) ->
append(anonymous,
map(sequence(anonymous.paramaeterNames()).zip(sequence(p.second(), p.first()))), a));
}
String name(Expression value, Map<String, Object> context) throws Exception {
if(value instanceof Name) return ((Name) value).value();
if(value instanceof Indirection) return string(value(((Indirection) value).value(), context));
throw new IllegalArgumentException("Unknown name type: " + value);
}
Object value(Expression value, Map<String, Object> context) throws Exception {
if(value instanceof Text) return ((Text)value).value();
if(value instanceof Name) return context.get(name(value, context));
if(value instanceof Indirection) return context.get(name(value, context));
if(value instanceof Attribute) return value((Attribute) value, context);
if(value instanceof FunctionCall) return value((FunctionCall) value, context);
if(value instanceof Anonymous) return value((Anonymous) value, context);
throw new IllegalArgumentException("Unknown value type: " + value);
}
String value(Anonymous anonymous, Map<String, Object> context) throws Exception {
return new Template(anonymous.template(), parent).render(context);
}
String value(FunctionCall functionCall, Map<String, Object> context) throws Exception {
return parent.get(name(functionCall.name(), context)).render(arguments(functionCall.arguments(), context));
}
Object value(Attribute attribute, Map<String, Object> context) {
return sequence(attribute.value()).fold(context, (Object container, Expression name) -> {
if (container instanceof Map) return value(name, cast(container));
if (container == null) return null;
throw new IllegalArgumentException("Unknown container type: " + container);
});
}
Object arguments(Arguments<?> arguments, Map<String, Object> context) throws Exception {
if(arguments instanceof ImplicitArguments) return arguments((ImplicitArguments) arguments, context);
if(arguments instanceof NamedArguments) return mapValues(((NamedArguments) arguments).value(), n -> value(n, context));
throw new IllegalArgumentException("Unknown arguments type: " + arguments);
}
Object arguments(ImplicitArguments arguments, Map<String, Object> context) throws Exception {
List<Expression> list = arguments.value();
if(list.isEmpty()) return context;
if(list.size() == 1) return value(list.get(0), context);
return sequence(list).map(arg -> value(arg, context)).toList();
}
@Override
public String toString() {
return sequence(template).toString("");
}
}