/*
 *  Keyframe-Based Video Summarization Designer
 *  Copyright (C) 2014 Waltenberger Andreas
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package at.ac.tuwien.ims.util;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import javafx.embed.swing.SwingFXUtils;

import javax.imageio.ImageIO;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import at.ac.tuwien.ims.Designer;
import at.ac.tuwien.ims.layout.Template;
import at.ac.tuwien.ims.layout.Tile;
import at.ac.tuwien.ims.ui.LayoutPane;
import at.ac.tuwien.ims.ui.Notification;

/**
 * Class that checks Templates for validity and assembles and imports them
 * @author Andreas Waltenberger
 *
 */
public class TemplateImporter {

	private Source schemaFile;
	private Source templateFile;
	private SchemaFactory schemaFactory;
	private Schema schema;
	private Validator validator;
	private DocumentBuilderFactory builderFactory;
	private DocumentBuilder builder;
	private Document document;

	private Notification notification;

	private static final int minWidth = 150;
	private static final int minHeight = 50;

	public TemplateImporter() {
		// define the schema for the Templates
		schemaFile = new StreamSource(getClass().getResourceAsStream("/template/layout.xsd"));
		schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		builderFactory = DocumentBuilderFactory.newInstance();
		try {
			schema = schemaFactory.newSchema(schemaFile);
			builder = builderFactory.newDocumentBuilder();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
		validator = schema.newValidator();
	}

	public boolean importTemplate(File xmlTemplate, LayoutPane pane) {
		templateFile = new StreamSource(xmlTemplate);

		try {
			validator.validate(templateFile);
			document = builder.parse(xmlTemplate);
			Element template = document.getDocumentElement();

			// check if the Template is valid
			if (!checkTiles(0, template))
				return false;

			pane.setContent(xmlTemplate);

			// create the small preview image to display in the TemplateChooser
			BufferedImage original = SwingFXUtils.fromFXImage(pane.getContent().snapshot(null, null), null);
			BufferedImage preview = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
			Graphics2D g = preview.createGraphics();

			// create the image with a size of 200x200 pixels
			if (original.getHeight() > original.getWidth()) {
				Image scaled = original.getScaledInstance(-1, 200, Image.SCALE_SMOOTH);
				// position the image horizontaly centered
				g.drawImage(scaled, (200 - scaled.getWidth(null)) / 2, 0, null);
			} else {
				Image scaled = original.getScaledInstance(200, -1, Image.SCALE_SMOOTH);
				// position the image vertically centered
				g.drawImage(scaled, 0, (200 - scaled.getHeight(null)) / 2, null);
			}
			g.dispose();

			// save the Template and the preview image in the Template directory
			long templateNumber = System.currentTimeMillis();
			ImageIO.write(preview, "png", new File(Designer.templateDir + File.separator + templateNumber + ".png"));
			Files.copy(xmlTemplate.toPath(), new File(Designer.templateDir + File.separator + templateNumber + ".xml").toPath());

			return true;
		} catch (SAXException e) {
			notification = new Notification(templateFile.getSystemId() + " is not valid");
			notification.showAndWait();
			return false;
		} catch (IOException e) {
			return false;
		}
	}

	// function that checks the Template for validity after the schema validation
	private boolean checkTiles(int height, Element root) {
		int tilesWidth, rowsHeight = 0;
		int width = Integer.parseInt(root.getAttribute("width"));

		// iterate over every row in the Template ...
		NodeList rows = root.getChildNodes();
		for (int r = 0; r < rows.getLength(); r++) {
			Node row = rows.item(r);
			if (row instanceof Element) {
				Element rowE = (Element) row;
				int rowHeight = Integer.parseInt(rowE.getAttribute("height"));
				// ... and check for the correct height
				if (rowHeight < minHeight) {
					notification = new Notification("Tiles have to be at least " + minHeight + " high");
					notification.showAndWait();
					return false;
				}
				rowsHeight += rowHeight;

				NodeList tiles = rowE.getChildNodes();
				tilesWidth = 0;
				// iterate over every Tile in a row ...
				for (int t = 0; t < tiles.getLength(); t++) {
					Node tile = tiles.item(t);
					if (tile instanceof Element) {
						Element tileE = (Element) tile;
						int tileWidth = Integer.parseInt(tileE.getAttribute("width"));
						// ... and check for the correct width
						if (tileWidth < minWidth) {
							notification = new Notification("Tiles have to be at least " + minWidth + " wide");
							notification.showAndWait();
							return false;
						}
						tilesWidth += tileWidth;

						// if a Tile has children, which means it contains sub-rows
						if (tileE.hasChildNodes()) {
							// this method gets called recursively to check that Template
							if (!checkTiles(Integer.parseInt(rowE.getAttribute("height")), tileE)) {
								return false;
							}
						}
					}
				}

				// the width of all sub-tiles combined has to align with the parent Tile
				if (tilesWidth != width) {
					notification = new Notification("Width of tiles in row do not align with template width");
					notification.showAndWait();
					return false;
				}
			}
		}

		// the height of all sub-rows combined has to align with the height of the parent row
		if (height != 0 && rowsHeight != height) {
			notification = new Notification("Height of rows in tile do not align with tile height");
			notification.showAndWait();
			return false;
		}

		return true;
	}

	// function builds the Template to display in the UI
	private static Template createLayout(Element root) {
		Template layout = new Template(Integer.parseInt(root.getAttribute("width")));

		NodeList rows = root.getChildNodes();
		// iterate over every row in the Template ...
		for (int r = 0; r < rows.getLength(); r++) {
			Node row = rows.item(r);
			if (row instanceof Element) {
				Element rowE = (Element) row;

				NodeList tiles = rowE.getChildNodes();
				// ... and every Tile in a row ...
				for (int t = 0; t < tiles.getLength(); t++) {
					Node tile = tiles.item(t);
					if (tile instanceof Element) {
						Element tileE = (Element) tile;
						// ... if a Tile has sub-rows, its a new sub-Template ...
						if (tileE.hasChildNodes()) {
							layout.getChildren().add(createLayout(tileE));
						} else {
							// ... otherwise it gets added to the Template as a Tile
							int width = Integer.parseInt(tileE.getAttribute("width"));
							int height = Integer.parseInt(rowE.getAttribute("height"));
							layout.getChildren().add(new Tile(width, height));
						}
					}
				}
			}
		}
		return layout;
	}

	public static Template createLayout(File file) {
		try {
			DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = builderFactory.newDocumentBuilder();
			Document document = builder.parse(file);
			Element template = document.getDocumentElement();
			return createLayout(template);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}
