/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework 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 3 of the License any later version.
 * 
 * Nuts Framework 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 Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.struts2.actions;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import nuts.core.collections.ExpireMap;
import nuts.core.lang.ExceptionUtils;
import nuts.core.lang.HtmlUtils;
import nuts.core.lang.StringUtils;
import nuts.core.net.HttpClientAgent;
import nuts.core.servlet.HttpServletSupport;
import nuts.core.servlet.URLHelper;
import nuts.exts.struts2.NutsStrutsConstants;
import nuts.exts.struts2.util.StrutsContextUtils;

import org.xhtmlrenderer.pdf.ITextRenderer;

import com.itextpdf.text.DocListener;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.html.simpleparser.ChainedProperties;
import com.itextpdf.text.html.simpleparser.HTMLTagProcessor;
import com.itextpdf.text.html.simpleparser.HTMLWorker;
import com.itextpdf.text.html.simpleparser.ImageProvider;
import com.itextpdf.text.html.simpleparser.StyleSheet;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.LineSeparator;
import com.opensymphony.xwork2.inject.Inject;


public class Html2PdfAction2 extends CommonAction {

	protected static ExpireMap<String, Object> expire = new ExpireMap<String, Object>(
		new WeakHashMap<String, Object>(), 5 * 60 * 1000);

	protected static Map<String, Object> cache = Collections.synchronizedMap(expire);

	protected HttpClientAgent agent;
	protected ITextRenderer renderer;
	
	protected String fonts;
	protected String url;
	protected String charset;
	
	/**
	 * @return the fonts
	 */
	public String getFonts() {
		return fonts;
	}

	/**
	 * @param fonts the fonts to set
	 */
	@Inject(value=NutsStrutsConstants.NUTS_FONTS_PATH, required=false)
	public void setFonts(String fonts) {
		this.fonts = fonts;
	}

	/**
	 * @return the url
	 */
	public String getUrl() {
		return url;
	}

	/**
	 * @param url the url to set
	 */
	public void setUrl(String url) {
		this.url = url;
	}

	/**
	 * @return the charset
	 */
	public String getCharset() {
		return charset;
	}

	/**
	 * @param charset the charset to set
	 */
	public void setCharset(String charset) {
		this.charset = StringUtils.stripToNull(charset);
	}

	/**
	 * @param ms cache expire milliseconds to set
	 */
	public static void setCacheExpire(int ms) {
		expire.setExpire(ms);
	}

	protected static class Html2PdfWorker extends HTMLWorker {
		
		public Html2PdfWorker(DocListener document,
				Map<String, HTMLTagProcessor> tags, StyleSheet style) {
			super(document, tags, style);
		}

		public Html2PdfWorker(DocListener document) {
			super(document);
		}

		/**
		 * Creates a LineSeparator object.
		 * @param attrs	properties of the LineSeparator
		 * @return a LineSeparator object
		 * @since 5.0.6
		 */
		public LineSeparator createLineSeparator(final Map<String, String> attrs) {
			if (currentParagraph == null) {
				currentParagraph = new Paragraph();
			}
			return super.createLineSeparator(attrs);
		}

		/**
		 * Parses an HTML source to a List of Element objects
		 * @param reader	the HTML source
		 * @param style		a StyleSheet object
		 * @param tags		a map containing supported tags and their processors
		 * @param providers	map containing classes with extra info
		 * @return a List of Element objects
		 * @throws IOException
		 * @since 5.0.6
		 */
		public static List<Element> process(final Reader reader, final Map<String, Object> providers) throws IOException {
			Html2PdfWorker worker = new Html2PdfWorker(null);
			worker.document = worker;
			worker.setProviders(providers);
			worker.objectList = new ArrayList<Element>();
			worker.parse(reader);
			return worker.objectList;
		}
	}
	
	protected static class Html2PdfImageProvider implements ImageProvider {

		public Image getImage(String src, Map<String, String> attrs,
				ChainedProperties chain, DocListener doc) {
			// TODO Auto-generated method stub
			return null;
		}
	}
	
	
	protected void createHttpClientAgent() {
		agent = new HttpClientAgent();

		HttpServletRequest request = StrutsContextUtils.getServletRequest();

		String domain = URLHelper.getUrlDomain(url);
		String server = request.getServerName();

		if (domain.equalsIgnoreCase(server)) {
			agent.clearRequestHeaders();
			agent.addRequestHeaders(request);
		}
		agent.addRequestHeader("nuts-html2pdf", this.getClass().getName());
	}
	
	protected Reader getResponseReader() throws Exception {
		String responseText = agent.getResponseText(charset);

		org.w3c.dom.Document doc = HtmlUtils.parseDOM(responseText);
		
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer transformer = tf.newTransformer();
		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		StringWriter sw = new StringWriter();
		transformer.transform(new DOMSource(doc), new StreamResult(sw));
		
		return new StringReader(sw.toString());
	}

	
	protected byte[] html2pdf() throws Exception {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();

		createHttpClientAgent();
		
		agent.doGet(url);

		Reader reader = getResponseReader();
		
		Map<String, Object> providers = new HashMap<String, Object>();
	
//		providers.put(Html2PdfWorker.IMG_PROVIDER, new Html2PdfImageProvider());
//		providers.put(Html2PdfWorker.IMG_STORE, cache);
		providers.put(Html2PdfWorker.IMG_BASEURL, getBaseUrl());

		List<Element> objects = Html2PdfWorker.process(reader, providers);
		
		Document document = new Document();
		
		PdfWriter.getInstance(document, baos);

		document.open();
		
		for (Element element : objects) {
			document.add(element);
		}

		document.close();
		
		return baos.toByteArray();
	}

	protected boolean normalizeUrl() {
		if (url == null) {
			url = getText("url-default");
			return false;
		}
		
		url = StringUtils.stripToNull(url);
		if (url == null) {
			return false;
		}

		int i = url.indexOf("://");
		if (i < 0) {
			url = "http://" + url;
		}
		return true;
	}

	protected String getBaseUrl() {
		if (url.endsWith("/")) {
			return url;
		}
		
		int i = url.lastIndexOf('/');
		if (i >= 0) {
			return url.substring(0, i + 1);
		}
		return url;
	}

	private String getFileNameFromUrl(String url) {
		String fn = StringUtils.stripEnd(url, "/");
		int i = fn.lastIndexOf('/');
		if (i >= 0) {
			fn = fn.substring(i + 1);
		}
		i = fn.lastIndexOf('?');
		if (i >= 0) {
			fn = fn.substring(0, i);
		}
		i = fn.lastIndexOf(';');
		if (i >= 0) {
			fn = fn.substring(0, i);
		}
		return fn;
	}
	
	/**
	 * execute
	 * 
	 * @return INPUT
	 * @throws Exception if an error occurs
	 */
	public String execute() throws Exception {
		if (normalizeUrl()) {
			try {
				byte[] pdf = html2pdf();
				
				HttpServletRequest request = StrutsContextUtils.getServletRequest();
				HttpServletResponse response = StrutsContextUtils.getServletResponse();

				HttpServletSupport hsrs = new HttpServletSupport(request, response);
				
				hsrs.setContentLength(Integer.valueOf(pdf.length));
				hsrs.setContentType("application/pdf");
				hsrs.setFileName(getFileNameFromUrl(url) + ".pdf");
				hsrs.setNoCache(true);

				hsrs.writeResponseHeader();
				hsrs.writeResponseData(pdf);

				return NONE;
			}
			catch (Throwable e) {
				log.warn("html2pdf execute error", e);
				addActionError(ExceptionUtils.getStackTrace(e));
			}
		}

		return INPUT;
	}

}
