package net.sourceforge.rtf.template.velocity;

import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.text.Format;
import java.util.Properties;

import net.sourceforge.rtf.document.RTFDocument;
import net.sourceforge.rtf.template.AbstractTemplateEngine;
import net.sourceforge.rtf.template.IContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ParseErrorException;

public class VelocityTemplateEngineImpl extends AbstractTemplateEngine {
	/**
	 * Default logger.
	 */
	@SuppressWarnings("unused")
	private static Log logger = LogFactory.getLog(VelocityTemplateEngineImpl.class);

	/**
	 * Velocity engine used for merge.
	 */
	private VelocityEngine velocityEngine;

	private boolean isVelocityEngineInitialized;

	public IContext newContext() {
		return new VelocityContextImpl();
	}

	protected void mergeWithTemplateEngine(Writer writer) throws Exception {
		if (velocityEngine == null)
			throw new IOException(
					"Velocity engine must be defined. Use setVelocityEngine method to set it.");
		initializeVelocityEngine();

		Context context = (VelocityContextImpl) super.getContext();
		EventCartridge events = new EventCartridge();
		events.addEventHandler(new InsertionHandler());
		events.attachToContext(context);

		Reader reader = template;
		try {
			velocityEngine.evaluate(context, writer, "", reader);
		} catch (ParseErrorException e) {
			logger.error("ParseErrorException occurred during RTFTemplate processing. dump of content follow:");
			RTFDocument.RTFDocumentReader rtfDocumentReader = (RTFDocument.RTFDocumentReader)reader;
			StringReader currentReader = (StringReader)rtfDocumentReader.getCurrentReader();
			currentReader.reset();
			LineNumberReader lnr = new LineNumberReader(currentReader);
			String line = lnr.readLine();
			while (line != null) {
				System.out.println(line);
				line = lnr.readLine();
			}
			logger.error("------------------- END OF DUMP ----------------------");
			throw e;
		} finally {
			reader.close();
		}
	}

	/**
	 * The handler for insertion of references. It applies default formatting -
	 * if set with {@link #setDefaultFormat(Class, Format)} - to values
	 * according to their class. <br>
	 * <code>null</code> values are replaced with an empty string. This
	 * prevents keys to show up in the result of the merge if a method of a
	 * business object in the context returns <code>null</code> - in this case
	 * velocity complains about an invalid references :(.
	 */
	protected class InsertionHandler implements ReferenceInsertionEventHandler {

		public Object referenceInsert(String reference, Object value) {
			if (value == null) {
				// Velocity doesn't handle NULL values as we wish ...
				value = "";
			} else {
				boolean escaped = mustBeEscaped(value);
				Format format = (Format) formats.get(value.getClass());
				if (format == null && value instanceof InputStream) {
					format = (Format) formats.get(InputStream.class);
				}
				if (format != null) {
					value = format.format(value);
                    if (value == null) {
                        // Velocity doesn't handle NULL values as we wish ...
                        value = "";
                    }
				}
				if (escaped)
					// Value is not Inpustream (not an image), escape characters
					value = escapeSpecialCharacters(value);

			}
			return value;
		}
	}

	/**
	 * Velocity doesn't accept <code>null</code> values due to context
	 * chaining issues. This subclass liftens this restriction.
	 */
	protected class RTFContext extends VelocityContext {
		public static final long serialVersionUID = 1L;

		public RTFContext() {
			super();
		}

		/**
		 * Chaining constructor,
		 * 
		 * @param innerContext
		 *            context impl to wrap
		 */
		public RTFContext(Context innerContext) {
			super(innerContext);
		}

		/**
		 * Overridden so that the <code>null</code> values are accepted.
		 * 
		 */
		public Object put(String key, Object value) {
			if (key == null) {
				return null;
			}
			return internalPut(key, value);
		}
	}

	public VelocityEngine getVelocityEngine() {
		return velocityEngine;
	}

	public void setVelocityEngine(VelocityEngine velocityEngine) {
		this.velocityEngine = velocityEngine;
		this.isVelocityEngineInitialized = false;
	}

	private void initializeVelocityEngine() throws Exception {
		/*
		 * velocityEngine.setProperty( VelocityEngine.INPUT_ENCODING, "KOI8-R");
		 * velocityEngine.setProperty( VelocityEngine.OUTPUT_ENCODING,
		 * "KOI8-R");
		 */
		Properties p = new Properties();
		/*
		 * p.setProperty( VelocityEngine.INPUT_ENCODING, "Windows-1251");
		 * p.setProperty( VelocityEngine.OUTPUT_ENCODING, "Windows-1251");
		 * p.setProperty(VelocityEngine.ENCODING_DEFAULT, "Windows-1251");
		 */
		if (!isVelocityEngineInitialized)
			velocityEngine.init(p);
		/*
		 * velocityEngine.setProperty("input.encoding", "UTF-8");
		 * velocityEngine.setProperty("output.encoding", "koxsi8");
		 * velocityEngine.setProperty("response.encoding", "xsoi8");
		 */

		this.isVelocityEngineInitialized = true;
	}

}
