package net.gnehzr.tnoodle.server.webscrambles;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzert;
import static net.gnehzr.tnoodle.utils.GwtSafeUtils.parseExtension;
import static net.gnehzr.tnoodle.utils.GsonUtils.GSON;
import static net.gnehzr.tnoodle.utils.Utils.throwableToString;
import net.gnehzr.tnoodle.svglite.Svg;
import net.gnehzr.tnoodle.utils.GsonUtils;
import net.gnehzr.tnoodle.utils.BadLazyClassDescriptionException;
import net.gnehzr.tnoodle.utils.LazyInstantiatorException;
import java.lang.reflect.Type;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import net.gnehzr.tnoodle.svglite.Color;
import net.gnehzr.tnoodle.svglite.Dimension;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.SortedMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.util.SVGConstants;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;
import net.gnehzr.tnoodle.scrambles.InvalidScrambleException;
import net.gnehzr.tnoodle.scrambles.Puzzle;
import net.gnehzr.tnoodle.scrambles.PuzzleIcon;
import net.gnehzr.tnoodle.scrambles.PuzzlePlugins;
import net.gnehzr.tnoodle.scrambles.PuzzleImageInfo;
import net.gnehzr.tnoodle.server.SafeHttpServlet;
import net.gnehzr.tnoodle.utils.LazyInstantiator;
import net.lingala.zip4j.exception.ZipException;
import org.w3c.dom.DOMImplementation;
import com.itextpdf.text.DocumentException;
@SuppressWarnings("serial")
public class ScrambleViewHandler extends SafeHttpServlet {
private static final Logger l = Logger.getLogger(ScrambleViewHandler.class.getName());
private SortedMap<String, LazyInstantiator<Puzzle>> scramblers;
public ScrambleViewHandler() throws IOException, BadLazyClassDescriptionException {
this.scramblers = PuzzlePlugins.getScramblers();
}
// Copied from http://bbgen.net/blog/2011/06/java-svg-to-bufferedimage/
class BufferedImageTranscoder extends ImageTranscoder {
@Override
public BufferedImage createImage(int w, int h) {
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
return bi;
}
@Override
public void writeImage(BufferedImage img, TranscoderOutput output) {
this.img = img;
}
public BufferedImage getBufferedImage() {
return img;
}
private BufferedImage img = null;
}
static {
GsonUtils.registerTypeHierarchyAdapter(Puzzle.class, new Puzzlerizer());
}
private static class Puzzlerizer implements JsonSerializer<Puzzle>, JsonDeserializer<Puzzle> {
@Override
public Puzzle deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
String scramblerName = json.getAsString();
SortedMap<String, LazyInstantiator<Puzzle>> scramblers = PuzzlePlugins.getScramblers();
LazyInstantiator<Puzzle> lazyScrambler = scramblers.get(scramblerName);
if(lazyScrambler == null) {
throw new JsonParseException(scramblerName + " not found in: " + scramblers.keySet());
}
return lazyScrambler.cachedInstance();
} catch(Exception e) {
throw new JsonParseException(e);
}
}
@Override
public JsonElement serialize(Puzzle scrambler, Type typeOfT, JsonSerializationContext context) {
return new JsonPrimitive(scrambler.getShortName());
}
}
@Override
protected void wrappedService(HttpServletRequest request, HttpServletResponse response, String[] path, LinkedHashMap<String, String> query) throws ServletException, IOException, IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, DocumentException, ZipException, BadLazyClassDescriptionException, LazyInstantiatorException {
if (path.length == 0) {
sendError(request, response, "Please specify a puzzle.");
return;
}
String[] name_extension = parseExtension(path[0]);
String name = name_extension[0];
String extension = name_extension[1];
if (extension == null) {
sendError(request, response, "Please specify an extension");
return;
}
if (extension.equals("png") || extension.equals("json") || extension.equals("svg")) {
String puzzle = name;
LazyInstantiator<Puzzle> lazyScrambler = scramblers.get(puzzle);
if (lazyScrambler == null) {
sendError(request, response, "Invalid scrambler: " + puzzle);
return;
}
Puzzle scrambler = lazyScrambler.cachedInstance();
HashMap<String, Color> colorScheme = scrambler
.parseColorScheme(query.get("scheme"));
String scramble = query.get("scramble");
if (extension.equals("png")) {
ByteArrayOutputStream bytes;
if (query.containsKey("icon")) {
bytes = PuzzleIcon.loadPuzzleIconPng(scrambler.getShortName());
} else {
Svg svg;
try {
svg = scrambler.drawScramble(scramble, colorScheme);
} catch(InvalidScrambleException e) {
sendText(request, response, throwableToString(e));
return;
}
ByteArrayInputStream svgFile = new ByteArrayInputStream(svg.toString().getBytes());
Dimension size = scrambler.getPreferredSize();
BufferedImageTranscoder imageTranscoder = new BufferedImageTranscoder();
// Copied from http://stackoverflow.com/a/6634963
// with some tweaks.
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
TranscodingHints hints = new TranscodingHints();
hints.put(ImageTranscoder.KEY_WIDTH, new Float(size.width));
hints.put(ImageTranscoder.KEY_HEIGHT, new Float(size.height));
hints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION, impl);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, SVGConstants.SVG_SVG_TAG);
hints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, false);
imageTranscoder.setTranscodingHints(hints);
TranscoderInput input = new TranscoderInput(svgFile);
try {
imageTranscoder.transcode(input, null);
} catch(TranscoderException e) {
l.log(Level.SEVERE, "Failed to rasterize SVG.", e);
sendText(request, response, throwableToString(e));
return;
}
BufferedImage img = imageTranscoder.getBufferedImage();
bytes = new ByteArrayOutputStream();
ImageIO.write(img, "png", bytes);
}
response.setHeader("Content-Type", "image/png");
response.setContentLength(bytes.size());
bytes.writeTo(response.getOutputStream());
} else if(extension.equals("svg")) {
Svg svg;
try {
svg = scrambler.drawScramble(scramble, colorScheme);
} catch(InvalidScrambleException e) {
sendText(request, response, throwableToString(e));
return;
}
response.setHeader("Content-Type", "image/svg+xml");
byte[] bytes = svg.toString().getBytes();
response.setContentLength(bytes.length);
response.getOutputStream().write(bytes, 0, bytes.length);
} else if (extension.equals("json")) {
sendJSON(request, response, GSON.toJson(new PuzzleImageInfo(scrambler)));
} else {
azzert(false);
}
} else if (extension.equals("pdf") || extension.equals("zip")) {
if (!request.getMethod().equals("POST")) {
sendText(request, response, "You must POST to this url. Copying and pasting the url won't work.");
return;
}
BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream()));
StringBuilder body = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
body.append(line);
}
query = parseQuery(body.toString());
String json = query.get("sheets");
ScrambleRequest[] scrambleRequests = GSON.fromJson(json, ScrambleRequest[].class);
String password = query.get("password");
String generationUrl = query.get("generationUrl");
Date generationDate = new Date();
String globalTitle = name;
if (extension.equals("pdf")) {
ByteArrayOutputStream totalPdfOutput = ScrambleRequest
.requestsToPdf(globalTitle, generationDate, scrambleRequests, password);
response.setHeader("Content-Disposition", "inline");
// Workaround for Chrome bug with saving PDFs:
// https://bugs.chromium.org/p/chromium/issues/detail?id=69677#c35
response.setHeader("Cache-Control", "public");
sendBytes(request, response, totalPdfOutput, "application/pdf");
} else if (extension.equals("zip")) {
ByteArrayOutputStream zipOutput = ScrambleRequest
.requestsToZip(getServletContext(), globalTitle, generationDate, scrambleRequests, password, generationUrl);
String safeTitle = globalTitle.replaceAll("\"", "'");
response.setHeader("Content-Disposition", "attachment; filename=\"" + safeTitle + ".zip\"");
sendBytes(request, response, zipOutput, "application/zip");
} else {
azzert(false);
}
} else {
sendError(request, response, "Invalid extension: " + extension);
}
}
static {
GsonUtils.registerTypeAdapter(PuzzleImageInfo.class, new PuzzleImageInfoizer());
}
private static class PuzzleImageInfoizer implements JsonSerializer<PuzzleImageInfo> {
@Override
public JsonElement serialize(PuzzleImageInfo pii, Type typeOfT, JsonSerializationContext context) {
return context.serialize(pii.toJsonable());
}
}
}