/*
 * 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. 2004, 2005 All Rights Reserved.
 */
package org.dita.dost.reader;

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

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Stack;
import java.util.StringTokenizer;

import org.dita.dost.exception.DITAOTXMLErrorHandler;
import org.dita.dost.log.MessageUtils;
import org.dita.dost.module.Content;
import org.dita.dost.module.ContentImpl;
import org.dita.dost.util.FileUtils;
import org.dita.dost.util.MergeUtils;
import org.dita.dost.util.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * MergeMapParser reads the ditamap file after preprocessing and merges
 * different files into one intermediate result. It calls MergeTopicParser
 * to process the topic file.
 * 
 * @author Zhang, Yuan Peng
 */
public final class MergeMapParser extends AbstractXMLReader {
	private XMLReader reader = null;
	private StringBuffer mapInfo = null;
	private MergeTopicParser topicParser = null;
	private MergeUtils util;
	private ContentImpl content;
	private String dirPath = null;
	private String tempdir = null;
	
	private Stack<String> processStack;
	private int processLevel = 0;

	/**
	 * Default Constructor.
	 */
	public MergeMapParser() {
		try{
			if(reader == null){
				reader = StringUtils.getXMLReader();
				reader.setContentHandler(this);
				reader.setFeature(FEATURE_NAMESPACE_PREFIX, true);
			}
			if(mapInfo == null){
				mapInfo = new StringBuffer(INT_1024);
			}
			
			processStack = new Stack<String>();
			processLevel = 0;

	        util = new MergeUtils();
			topicParser = new MergeTopicParser(util);
			topicParser.setLogger(logger);
			content = new ContentImpl();
		}catch (final Exception e){
			throw new RuntimeException("Failed to initialize merge map parser: " + e.getMessage(), e);
		}
	}

	@Override
	public Content getContent() {
		content.setValue(mapInfo.append((StringBuffer)topicParser.getContent().getValue()));
		return content;
	}

	@Override
	public void read(String ditaInput) {
		try{
			String filename;
			if(ditaInput.contains(STICK)){
				filename = ditaInput.substring(0, ditaInput.indexOf(STICK));
				tempdir = ditaInput.substring(ditaInput.indexOf(STICK)+1);
			}else{
				filename = ditaInput;
				tempdir = new File(filename).getParent();
			}
			
			final File input = new File(filename);
			dirPath = input.getParent();
			reader.setErrorHandler(new DITAOTXMLErrorHandler(filename));
			reader.parse(filename);
		}catch(final Exception e){
			logger.logException(e);
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if (processLevel > 0) {
			String value = processStack.peek();
			if (processLevel == processStack.size()) {
				value = processStack.pop();
			}
			processLevel--;
			
			if (ATTR_PROCESSING_ROLE_VALUE_RESOURCE_ONLY.equalsIgnoreCase(value)) {
				return;
			}
		}
		mapInfo.append(LESS_THAN)
		.append(SLASH)
		.append(qName)
		.append(GREATER_THAN);
	}
	
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		if (processStack.empty() || !ATTR_PROCESSING_ROLE_VALUE_RESOURCE_ONLY.equalsIgnoreCase(processStack.peek())){
			mapInfo.append(StringUtils.escapeXML(ch, start, length));
		}
	}

	@Override
	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		final String attrValue = atts.getValue(ATTRIBUTE_NAME_PROCESSING_ROLE);
	    if (attrValue != null) {
	        processStack.push(attrValue);
	        processLevel++;
	        if (ATTR_PROCESSING_ROLE_VALUE_RESOURCE_ONLY.equalsIgnoreCase(attrValue)) {
	        	// @processing-role='resource-only'
	        	return;
	        }
	    } else if (processLevel > 0) {
	        processLevel++;
	        if (ATTR_PROCESSING_ROLE_VALUE_RESOURCE_ONLY.equalsIgnoreCase(processStack.peek())) {
	        	// Child of @processing-role='resource-only'
	        	return;
	        }
	    }
		
		mapInfo.append(LESS_THAN).append(qName);
		final String classValue = atts.getValue(ATTRIBUTE_NAME_CLASS);
		
		final int attsLen = atts.getLength();
		for (int i = 0; i < attsLen; i++) {
            final String attQName = atts.getQName(i);
            String attValue = atts.getValue(i);
            if(ATTRIBUTE_NAME_HREF.equals(attQName) 
            		&& !StringUtils.isEmptyString(attValue)
            		&& classValue != null
            		&& classValue.indexOf(ATTR_CLASS_VALUE_TOPICREF)!=-1){
                final String scopeValue = atts.getValue(ATTRIBUTE_NAME_SCOPE);
        		final String formatValue = atts.getValue(ATTRIBUTE_NAME_FORMAT);
        		
        		if((scopeValue == null 
    					|| ATTR_SCOPE_VALUE_LOCAL.equalsIgnoreCase(scopeValue))
    					&& (formatValue == null 
    							|| ATTR_FORMAT_VALUE_DITA.equalsIgnoreCase(formatValue))){
        			final String ohref = attValue;
                    final String copyToValue = atts.getValue(ATTRIBUTE_NAME_COPY_TO);
                    if (!StringUtils.isEmptyString(copyToValue)) {
                        attValue = copyToValue;
                    }
    				if (util.isVisited(attValue)){
    					mapInfo.append(STRING_BLANK)
            			.append("ohref").append(EQUAL).append(QUOTATION)
            			.append(StringUtils.escapeXML(ohref)).append(QUOTATION);
    					
    					attValue = new StringBuffer(SHARP).append(util.getIdValue(attValue)).toString();
    				}else{
    					mapInfo.append(STRING_BLANK)
            			.append("ohref").append(EQUAL).append(QUOTATION)
            			.append(StringUtils.escapeXML(ohref)).append(QUOTATION);
    					    					
    					//parse the topic
    					final String fileId = topicParser.parse(attValue,dirPath);
    					util.visit(attValue);
    					attValue = new StringBuffer(SHARP).append(fileId).toString();
    				}
    			}
        		
            }
            
            //output all attributes
            mapInfo.append(STRING_BLANK)
            		.append(attQName).append(EQUAL).append(QUOTATION)
            		.append(StringUtils.escapeXML(attValue)).append(QUOTATION);
        }
		mapInfo.append(GREATER_THAN);
		
	}
	
	@Override
	public void processingInstruction(String target, String data)
			throws SAXException {
		final String pi = (data != null) ? target + STRING_BLANK + data : target;
        mapInfo.append(LESS_THAN + QUESTION 
                + pi + QUESTION + GREATER_THAN);
	}

	@Override
	public void endDocument() throws SAXException {
		// read href dita topic list
		// compare visitedSet with the list
		// if list item not in visitedSet then call MergeTopicParser to parse it
		final Properties property = new Properties();
	    final File ditalist = new File(tempdir, FILE_NAME_DITA_LIST);
        final File xmlDitalist = new File(tempdir, FILE_NAME_DITA_LIST_XML);
        InputStream in = null;
        try{
	        if(xmlDitalist.exists()) {
	        	in = new FileInputStream(xmlDitalist);
	        	property.loadFromXML(in);
	        } else {
	        	in = new FileInputStream(ditalist);
	        	property.loadFromXML(in);
	        }
	        final String hrefTargetList = property.getProperty(HREF_TARGET_LIST);
	        String resourceOnlySet = property.getProperty(RESOURCE_ONLY_LIST);
	        resourceOnlySet = (resourceOnlySet == null ? "" : resourceOnlySet);
	        String skipTopicSet = property.getProperty(CHUNK_TOPIC_LIST);
	        skipTopicSet = (skipTopicSet == null ? "" : skipTopicSet);
	        String chunkedTopicSet = property.getProperty(CHUNKED_TOPIC_LIST);
	        chunkedTopicSet = (chunkedTopicSet == null ? "" : chunkedTopicSet);
			final StringTokenizer tokenizer = new StringTokenizer(hrefTargetList,COMMA);
			while(tokenizer.hasMoreElements())
			{
			    String element = (String)tokenizer.nextElement();
				if (!new File(dirPath).equals(new File(tempdir))) {
                    element = FileUtils.getRelativePathFromMap(new File(dirPath,"a.ditamap").getAbsolutePath(), new File(tempdir,element).getAbsolutePath());
                }
				if(!util.isVisited(element)){
					util.visit(element);
					if (!resourceOnlySet.contains(element) && (chunkedTopicSet.contains(element)
							|| !skipTopicSet.contains(element))){
						//ensure the file exists
						if(new File(dirPath, element).exists()){
							topicParser.parse(element, dirPath);
						}else{
							final String fileName = new File(dirPath, element).getAbsolutePath();
							final Properties prop = new Properties();
			            	prop.put("%1", fileName);
			            	logger.logWarn(MessageUtils.getMessage("DOTX008W", prop).toString());
						}
					}
						
				}
			}
        }catch (final Exception e){
        	logger.logException(e);
        } finally {
        	if (in != null) {
        		try {
        			in.close();
        		} catch (final IOException e) {
        			logger.logException(e);
        		}
        	}
        }
	}

}
