/*
 * This file is part of the DITA Open Toolkit project hosted on
 * Sourceforge.net. See the accompanying license.txt file for 
 * applicable licenses.
 */

/*
 * (c) Copyright IBM Corp. 2005, 2006 All Rights Reserved.
 */
package org.dita.dost.platform;

import static org.dita.dost.util.Constants.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;

import org.dita.dost.log.DITAOTJavaLogger;
import org.dita.dost.log.DITAOTLogger;
import org.dita.dost.util.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DefaultHandler2;

/**
 * Generate outputfile with templates.
 * @author Zhang, Yuan Peng
 */
final class FileGenerator extends DefaultHandler2 {
	
	public static final String PARAM_LOCALNAME = "localname";
	public static final String PARAM_TEMPLATE = "template";

	private static final String DITA_OT_NS = "http://dita-ot.sourceforge.net";
	private static final String TEMPLATE_PREFIX = "_template.";
	
	private final XMLReader reader;
	private DITAOTLogger logger;
	private OutputStreamWriter output = null;
	/** Plug-in features. */
	private final Map<String,String> featureTable;
	private final Map<String, Features> pluginTable;
	/** Template file. */
	private File templateFile;

	/**
	 * Default Constructor.
	 */
	public FileGenerator() {
		this(null, null);
	}

	/**
	 * Constructor init featureTable.
	 * @param featureTbl featureTbl
	 */
	public FileGenerator(final Hashtable<String,String> featureTbl, final Map<String,Features> pluginTable) {
		this.featureTable = featureTbl;
		this.pluginTable = pluginTable;
		output = null;
		templateFile = null;
		
		try {
            reader = StringUtils.getXMLReader();
            reader.setContentHandler(this);
            reader.setProperty(LEXICAL_HANDLER_PROPERTY,this);
            reader.setFeature(FEATURE_NAMESPACE_PREFIX, true);
            //reader.setFeature(FEATURE_VALIDATION, true); 
            //reader.setFeature(FEATURE_VALIDATION_SCHEMA, true);

        } catch (final Exception e) {
        	throw new RuntimeException("Failed to initialize parser: " + e.getMessage(), e);
        }
	}
	
	/**
     * Set logger.
     * @param logger logger instance
     */
    public void setLogger(final DITAOTLogger logger) {
        this.logger = logger;
    }
	
	/**
	 * Generator the output file.
	 * @param fileName filename
	 */
	public void generate(final File fileName){
	    if (logger == null) {
	        logger = new DITAOTJavaLogger();
	    }
		FileOutputStream fileOutput = null;
		final File outputFile = removeTemplatePrefix(fileName);
		templateFile = fileName;
				
		try{
			fileOutput = new FileOutputStream(outputFile);
	        output = new OutputStreamWriter(fileOutput, UTF8);
			reader.parse(fileName.toURI().toString());
		} catch (final Exception e){
			logger.logException(e);
		}finally {
			if (fileOutput != null) {
	            try {
	                fileOutput.close();
	            }catch (final Exception e) {
	            	logger.logException(e);
	            }
			}
        }
	}

    private File removeTemplatePrefix(final File templateFile) {
        final String f = templateFile.getAbsolutePath();
        int i = f.lastIndexOf(TEMPLATE_PREFIX);
        if (i != -1) {
            return new File(f.substring(0, i)
                    + f.substring(i + TEMPLATE_PREFIX.length() - 1));
        } else {
            throw new IllegalArgumentException("File " + templateFile + " does not contain template prefix");
        }
    }

	@Override
	public void characters(final char[] ch, final int start, final int length) throws SAXException {
		try{
			output.write(StringUtils.escapeXML(ch,start,length));
		}catch (final Exception e) {
        	logger.logException(e);
        }
	}

	@Override
	public void endElement(final String uri, final String localName, final String qName) throws SAXException {
		try{
			if(!(DITA_OT_NS.equals(uri) && "extension".equals(localName))){
				output.write("</");
				output.write(qName);
				output.write(">");
			}
		}catch (final Exception e) {
        	logger.logException(e);
        }
	}

	@Override
	public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
		try{
			output.write(ch,start,length);
		}catch (final Exception e) {
        	logger.logException(e);
        }
	}

	@Override
	public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
		IAction action = null;
		try{
			if(DITA_OT_NS.equals(uri) && "extension".equals(localName)){
				// Element extension: <dita:extension id="extension-point" behavior="classname"/>
				action = (IAction)Class.forName(attributes.getValue("behavior")).newInstance();
				action.setLogger(logger);
				action.addParam(PARAM_TEMPLATE, templateFile.getAbsolutePath());
				final String extension = attributes.getValue("id");
				action.addParam("extension", extension);
				if(featureTable.containsKey(extension)){
					action.setInput(featureTable.get(extension));
				}
				action.setFeatures(pluginTable);
				output.write(action.getResult());
			}else{
				final int attLen = attributes.getLength();
				output.write("<");
				output.write(qName);
				for(int i = 0; i < attLen; i++){
					if (DITA_OT_NS.equals(attributes.getURI(i)))
					{
						// Attribute extension: <element dita:extension="localname classname ..." dita:localname="...">
						if (!("extension".equals(attributes.getLocalName(i))))
						{
							final String extensions = attributes.getValue(DITA_OT_NS, "extension");
							final StringTokenizer extensionTokenizer = new StringTokenizer(extensions);
							// Get the classname that implements this localname.
							while (extensionTokenizer.hasMoreTokens())
							{
								final String thisExtension = extensionTokenizer.nextToken();
								final String thisExtensionClass = extensionTokenizer.nextToken();
								if (thisExtension.equals(attributes.getLocalName(i)))
								{
									action = (IAction)Class.forName(thisExtensionClass).newInstance();
									break;
								}
							}
							action.setLogger(logger);
							action.setFeatures(pluginTable);
							action.addParam(PARAM_TEMPLATE, templateFile.getAbsolutePath());
							action.addParam(PARAM_LOCALNAME, attributes.getLocalName(i));
							action.setInput(attributes.getValue(i));
							output.write(action.getResult());
						}
					}
					else if (attributes.getQName(i).startsWith("xmlns:") &&
							DITA_OT_NS.equals(attributes.getValue(i)))
					{
						// Ignore xmlns:dita.
					}
					else
					{
						// Normal attribute.
						output.write(" ");
						output.write(new StringBuffer(attributes.getQName(i)).append("=\"").
								append(StringUtils.escapeXML(attributes.getValue(i))).append("\"").toString());
					}
				}
				output.write(">");
			}
		}catch(final Exception e){
		    e.printStackTrace();
			logger.logException(e);
		}
	}

	@Override
	public void endDocument() throws SAXException {
		try{
			output.flush();
			output.close();
		}catch(final Exception e){
			logger.logException(e);
		}
		
	}
	@Override
	public void skippedEntity(final String name) throws SAXException {
		logger.logError("Skipped entity " + name);
	}
	
	@Override
	public void comment(final char[] ch, final int start, final int length) throws SAXException {
		try{
    		output.write("<!--");
    		output.write(ch, start, length);
    		output.write("-->");
		}catch(final Exception e){
			logger.logException(e);
		}
	}
	
	@Override
	public void startDocument() throws SAXException {
		try{
    		output.write(XML_HEAD);
		}catch(final Exception e){
			logger.logException(e);
		}
		
	}
	
}
