/******************************************************************************* 
 * Copyright (c) 2007 Red Hat, Inc. 
 * Distributed under license by Red Hat, Inc. All rights reserved. 
 * This program is made available under the terms of the 
 * Eclipse Public License v1.0 which accompanies this distribution, 
 * and is available at http://www.eclipse.org/legal/epl-v10.html 
 * 
 * Contributors: 
 * Red Hat, Inc. - initial API and implementation 
 ******************************************************************************/ 
package org.jboss.tools.common.model.loaders;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;

import org.jboss.tools.common.xml.XMLUtilities;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * 
 * @author Viacheslav Kabanovich
 *
 */
public class XMLRecognizerContext extends EntityRecognizerContext {
	protected boolean isDTD = false;

	protected DoctypeInfo doctypeInfo = null;
	
	static protected class DoctypeInfo {
		protected String publicId;
		protected String systemId;
		protected String name;		
	}
	static DoctypeInfo NULL_INFO = new DoctypeInfo();

	public XMLRecognizerContext(EntityRecognizerContext context) {
		super(context.getFileName(), context.getExtension(), context.getBody());
		init();
	}

	void init() {
		if(body == null) return;

		String doctypeText = getUnformattedDoctypeFromBody(body);
		if (doctypeText != null) {
			isDTD = true;
			doctypeInfo = checkDocType(doctypeText);
		}

	}

	public boolean isDTD() {
		return isDTD;
	}

	public String getPublicId() {
		return doctypeInfo == null ? null : doctypeInfo.publicId;
	}

	public String getSystemId() {
		return doctypeInfo == null ? null : doctypeInfo.systemId;
	}

	public String getRootName() {
		if(doctypeInfo != null) {
			return doctypeInfo.name;
		}
		//TODO
		return null;
	}

	public XMLRecognizerContext getXMLContext() {
		return this;
	}

	static HashMap<String, DoctypeInfo> doctypes = new HashMap<String, DoctypeInfo>();

	private DoctypeInfo checkDocType(String docTypeString) {
		if(doctypes.containsKey(docTypeString)) {
			return doctypes.get(docTypeString);
		}
		Reader xml = new StringReader(docTypeString + "<root></root>"); //$NON-NLS-1$
		DocumentBuilder db = XMLUtilities.createDocumentBuilder(false);
		if (db == null) 
			return NULL_INFO;
	
		try {
			db.setErrorHandler(new ErrorHandler() {
				public void warning(SAXParseException exception)
						throws SAXException {
				}

				public void fatalError(SAXParseException exception)
						throws SAXException {
				}

				public void error(SAXParseException exception)
						throws SAXException {
				}
			});
			db.setEntityResolver(new EntityResolver() {
				public InputSource resolveEntity(String publicId,
						String systemId) throws SAXException, IOException {
					return new InputSource(new StringReader("")); //$NON-NLS-1$
				}
			});
			Document doc = db.parse(new InputSource(xml));
			if(doc != null) {
				DocumentType dt = doc.getDoctype();
				DoctypeInfo doctypeInfo = new DoctypeInfo();
				doctypeInfo.publicId = dt.getPublicId();
				doctypeInfo.systemId = dt.getSystemId();
				doctypeInfo.name = dt.getName();
				//'docTypeString' is a substring of the complete text of the file,
				//which can be very long; and this substring keeps reference to
				//char[] of the entire text. Adding an empty string ensures
				//that the result will not keep the reference. 
				doctypes.put("" + docTypeString, doctypeInfo);
				// doctypeInfo.publicId + " " + doctypeInfo.systemId + " " +
				// doctypeInfo.name);
				return doctypeInfo;
			}
		} catch (SAXException e1) {
			//ignore - doctype is corrupted
		} catch (IOException e2) {
			//ignore - impossible
		} finally {
			try {
				xml.close();
			} catch (IOException e) {
			}
		}
		//See comment to 'docTypeString' above.
		doctypes.put("" + docTypeString, NULL_INFO);
		return NULL_INFO;
	}

	private String getUnformattedDoctypeFromBody(String body) {
		int i = body.indexOf("<!DOCTYPE"); //$NON-NLS-1$
		if (i < 0)
			return null;
		int j = body.indexOf(">", i); //$NON-NLS-1$
		if (j < 0)
			return null;
		return body.substring(i, j+1);
	}

}
