package xapi.ui.html.api;
import xapi.annotation.common.Property;
import xapi.collect.api.StringTo;
import xapi.dev.source.DomBuffer;
import xapi.log.X_Log;
import xapi.source.write.MappedTemplate;
import xapi.ui.api.StyleService;
import xapi.ui.autoui.api.BeanValueProvider;
import xapi.ui.html.X_Html;
import xapi.ui.html.api.FontFamily.HasGoogleFont;
import xapi.ui.html.api.Style.AlignHorizontal;
import xapi.ui.html.api.Style.AlignVertical;
import xapi.ui.html.api.Style.BorderStyle;
import xapi.ui.html.api.Style.BoxSizing;
import xapi.ui.html.api.Style.Clear;
import xapi.ui.html.api.Style.Cursor;
import xapi.ui.html.api.Style.Display;
import xapi.ui.html.api.Style.Floats;
import xapi.ui.html.api.Style.FontStyle;
import xapi.ui.html.api.Style.FontWeight;
import xapi.ui.html.api.Style.Overflow;
import xapi.ui.html.api.Style.Position;
import xapi.ui.html.api.Style.Rgb;
import xapi.ui.html.api.Style.TextDecoration;
import xapi.ui.html.api.Style.Transition;
import xapi.ui.html.api.Style.Unit;
import xapi.ui.html.api.Style.UnitType;
import xapi.util.X_String;
import xapi.util.api.ConvertsValue;
import xapi.util.impl.LazyProvider;
import static xapi.collect.X_Collect.newStringMap;
import com.google.gwt.reflect.shared.GwtReflect;
import javax.inject.Provider;
import java.io.IOException;
public class HtmlSnippet <T> implements ConvertsValue<T, String> {
public static String appendTo(final Appendable sheet, final Style style) {
try {
return doAppend(sheet, style);
} catch (final IOException e) {
X_Log.error(HtmlSnippet.class, "Error rendering",style,e);
return "";
}
}
public static String doAppend(final Appendable sheet, final Style style) throws IOException {
append("left", sheet, style.left());
append("right", sheet, style.right());
append("top", sheet, style.top());
append("bottom", sheet, style.bottom());
append("width", sheet, style.width());
append("height", sheet, style.height());
append("max-height", sheet, style.maxHeight());
append("max-width", sheet, style.maxWidth());
append("min-height", sheet, style.minHeight());
append("min-width", sheet, style.minWidth());
if (style.boxSizing() != BoxSizing.Inherit) {
append("box-sizing", sheet, style.boxSizing().styleName);
}
if (style.display() != Display.Inherit) {
append("display", sheet, style.display().styleName());
}
if (style.position() != Position.Inherit) {
append("position", sheet, style.position().styleName());
}
if (style.fontStyle() != FontStyle.Inherit) {
append("font-style", sheet, style.fontStyle().styleName());
}
if (style.textDecoration() != TextDecoration.NOT_SET) {
append("text-decoration", sheet, style.textDecoration().styleName());
}
if (style.fontWeight() != FontWeight.Inherit) {
append("font-weight", sheet, style.fontWeight().styleName());
}
if (style.textAlign() != AlignHorizontal.Auto) {
append("text-align", sheet, style.textAlign().styleName());
}
if (style.verticalAign() != AlignVertical.Auto) {
append("vertical-align", sheet, style.verticalAign().styleName());
}
if (style.cursor() != Cursor.Inherit) {
append("cursor", sheet, style.cursor().styleName());
}
if (style.floats() != Floats.Auto) {
append("float", sheet, style.floats().styleName());
}
if (style.clear() != Clear.Auto) {
append("clear", sheet, style.clear().styleName());
}
if (style.opacity() != 1) {
append("opacity", sheet,Double.toString(style.opacity()));
}
StringBuilder extra = new StringBuilder();
if (style.fontFamily().length > 0) {
final Class<? extends FontFamily>[] fonts = style.fontFamily();
final StringBuilder b = new StringBuilder();
for (int i = 0, m = fonts.length; i < m; i ++ ) {
if (i > 0) {
b.append(", ");
}
if (fonts[i] == null) {
throw new NullPointerException("The font at position " + i + " in the fontFamily property of " + style+" was not loaded (check above in the log to see what class). " +
"\nCheck that this type is loaded (as a class, not just source) on the Gwt classpath.");
}
try {
if (fonts[i].isInterface()) {
Object value = GwtReflect.invoke(fonts[i], "name", new Class<?>[0], null);
b.append(value);
if (HasGoogleFont.class.isAssignableFrom(fonts[i])) {
value = GwtReflect.invoke(fonts[i], "googleFont", new Class<?>[0], null);
extra.append(X_Html.toGoogleFontUrl((String)value));
}
} else {
FontFamily fontInstance = fonts[i].newInstance();
b.append(fontInstance.name());
if (fontInstance instanceof HasGoogleFont) {
String font = ((HasGoogleFont) fontInstance).googleFont();
extra.append(X_Html.toGoogleFontUrl(font));
}
}
} catch (final Throwable e) {
X_Log.warn(HtmlSnippet.class, "Error loading font family for "+fonts[i], e);
}
}
sheet.append("font-family").append(":").append(b).append(";");
}
final Transition[] transitions = style.transition();
if (transitions.length > 0) {
final StringBuilder b = new StringBuilder();
for (int i = 0, m = transitions.length; i < m; i ++) {
if (i > 0) {
b.append(", ");
}
final Transition transition = transitions[i];
b
.append(transition.value())
.append(" ")
.append(transition.time())
.append(transition.unit())
;
}
append("transition", sheet, b.toString());
}
if (style.color().length > 0) {
appendColor("color", sheet, style.color()[0]);
}
if (style.backgroundColor().length > 0) {
appendColor("background-color", sheet, style.backgroundColor()[0]);
}
append("font-size", sheet, style.fontSize());
append("line-height", sheet, style.lineHeight());
append("padding", sheet, style.padding(),
style.paddingTop(), style.paddingRight(), style.paddingBottom(), style.paddingLeft());
append("margin", sheet, style.margin(),
style.marginTop(), style.marginRight(), style.marginBottom(), style.marginLeft());
if (style.overflow() != Overflow.Inherit) {
append("overflow", sheet, style.overflow().styleName());
}
if (style.overflowX() != Overflow.Inherit) {
append("overflow-x", sheet, style.overflowX().styleName());
}
if (style.overflowY() != Overflow.Inherit) {
append("overflow-y", sheet, style.overflowY().styleName());
}
for (final Property prop : style.properties()) {
append(prop.name(), sheet, prop.value());
}
final Unit[] borderRadius = style.borderRadius();
if (borderRadius.length > 0) {
sheet.append("border-radius:");
for (final Unit unit : borderRadius) {
sheet.append(" ").append(toString(unit));
}
sheet.append(";");
}
append("border", "width", sheet, style.borderWidth(), style.borderWidthTop(),
style.borderWidthRight(), style.borderWidthBottom(), style.borderWidthLeft());
if (style.borderStyle().length > 0) {
sheet.append("border-style: ");
for (final BorderStyle borderStyle : style.borderStyle()) {
sheet.append(borderStyle.styleName()).append(' ');
}
sheet.append(";");
}
if (style.borderColor().length > 0) {
sheet.append("border-color: ");
for (final Rgb borderColor : style.borderColor()) {
sheet.append(toColor(borderColor)).append(' ');
}
sheet.append(";");
}
return extra.toString();
}
private static void append(final String type, final Appendable sheet, final String value) throws IOException {
sheet
.append(type)
.append(":")
.append(value)
.append(";");
}
private static void append(final String type, final Appendable sheet, final Unit unit) throws IOException {
if (unit.type() != UnitType.Unset) {
sheet
.append(type)
.append(":")
.append(toString(unit))
.append(";");
}
}
private static void append(final String type, final Appendable sheet, final Unit[] all,
final Unit top, final Unit right, final Unit bottom, final Unit left) throws IOException {
append(type, "", sheet, all, top, right, bottom, left);
}
private static void append(final String type0, final String type1, final Appendable sheet, final Unit unit) throws IOException {
if (unit.type() != UnitType.Unset) {
sheet
.append(type0)
.append(type1)
.append(":")
.append(toString(unit))
.append(";");
}
}
private static void append(final String type, String typeSuffix, final Appendable sheet, final Unit[] all,
final Unit top, final Unit right, final Unit bottom, final Unit left) throws IOException {
if (all.length > 0) {
sheet.append(type).append(":");
for (final Unit unit : all) {
sheet.append(toString(unit)).append(' ');
}
sheet.append(";");
}
if (typeSuffix.length() > 0 && typeSuffix.charAt(0) != '-') {
typeSuffix="-"+typeSuffix;
}
append(type,"-top"+typeSuffix, sheet, top);
append(type,"-right"+typeSuffix, sheet, right);
append(type,"-bottom"+typeSuffix, sheet, bottom);
append(type,"-left"+typeSuffix, sheet, left);
}
private static void appendColor(final String type, final Appendable sheet, final Rgb rgb) throws IOException {
sheet.append(type).append(":");
if (rgb.opacity() == 0xff) {
sheet
.append("#")
.append(toHexString(rgb.r()))
.append(toHexString(rgb.g()))
.append(toHexString(rgb.b()))
;
} else {
sheet
.append("rgba(")
.append(Integer.toString(rgb.r()))
.append(",")
.append(Integer.toString(rgb.g()))
.append(",")
.append(Integer.toString(rgb.b()))
.append(",")
.append(Integer.toString(rgb.opacity()))
.append(")")
;
}
sheet.append(";");
}
private static String toColor(final Rgb rgb) {
if (rgb.opacity() == 0xff) {
return "#"+toHexString(rgb.r())+toHexString(rgb.g())+toHexString(rgb.b());
} else {
return "rgba("+rgb.r()+","+rgb.g()+","+rgb.b()+","+rgb.opacity()+")";
}
}
private static String toHexString(final int r) {
final String s = Integer.toHexString(r);
return s.length() == 1 ? "0"+s: s;
}
private static String toString(final Unit unit) {
final String important = unit.important() ? " !important" : "";
switch (unit.type()) {
case Auto:
return "auto" + important;
case Pct:
return unit.value() + "%"+ important;
case Em:
return unit.value() + "em" + important;
case Rem:
return unit.value() + "rem" + important;
case Px:
return unit.value() + "px" + important;
default:
throw new UnsupportedOperationException("Type "+unit+" not supported");
}
}
protected static final Runnable NO_OP = new Runnable() {
@Override public void run() {}
};
private final Provider<ConvertsValue<T, DomBuffer>> generator;
public HtmlSnippet(
final Html html,
final BeanValueProvider values,
final StyleService<?> context
) {
assert html != null : "Do not send null @Html to HtmlSnippet!";
assert values != null : "Do not send null BeanValueProvider to HtmlSnippet!";
generator = new LazyProvider<>(new Provider<ConvertsValue<T, DomBuffer>>() {
@Override
public ConvertsValue<T, DomBuffer> get() {
return new ConvertsValue<T, DomBuffer>() {
@Override
public DomBuffer convert(final T from) {
final DomBuffer buffer = newBuffer(html, from);
final Iterable<String> keys = values.getChildKeys();
for (final El el : html.body()) {
final DomBuffer child = newChild(buffer, el);
for (final Property prop : el.properties()) {
child.setAttribute(prop.name(), toValue(values, keys, prop.value(), from));
}
for (final Style style : el.style()) {
toStyleSheet(style, context);
}
for (final String clsName : el.className()) {
child.addClassName(toValue(values, keys, clsName, from));
}
for (final String html : el.html()) {
final MappedTemplate m = new MappedTemplate(html, keys);
final StringTo<Object> vals = newStringMap(Object.class);
values.fillMap("", m, vals, from);
child.append(m.applyMap(vals.entries()));
}
}
return buffer;
}
};
}
});
}
public HtmlSnippet(final Provider<ConvertsValue<T, DomBuffer>> generator) {
this.generator = new LazyProvider<>(generator);
}
@Override
public String convert(final T from) {
return toBuffer(from).toString();
}
public DomBuffer toBuffer(final T from) {
return generator.get().convert(from);
}
protected DomBuffer newBuffer(final Html html, final T from) {
return new DomBuffer();
}
protected DomBuffer newChild(final DomBuffer buffer, final El el) {
return buffer.makeTag(el.tag()).setNewLine(false);
}
protected String toValue(final BeanValueProvider values, final Iterable<String> keys, final String template, final T from) {
final MappedTemplate m = new MappedTemplate(template, keys);
final StringTo<Object> vals = newStringMap(Object.class);
values.fillMap("", m, vals, from);
return m.applyMap(vals.entries());
}
private void toStyleSheet(final Style style, final StyleService<?> context) {
final StringBuilder sheet = new StringBuilder();
final String[] names = style.names();
for (int i = 0, m = names.length; i < m; ++i) {
if (i > 0) {
sheet.append(", ");
}
sheet.append(names[i]);
}
if (names.length > 0) {
sheet.append("{\n");
}
String extra = appendTo(sheet, style);
if (names.length > 0) {
sheet.append("}\n");
}
context.addCss(sheet.toString(), style.priority());
if (X_String.isNotEmpty(extra)) {
context.addCss(extra, Integer.MIN_VALUE);
}
}
}