/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package eu.blogforever.mfextractor;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;

import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.deri.any23.encoding.EncodingDetector;
import org.deri.any23.encoding.TikaEncodingDetector;
import org.deri.any23.extractor.html.TagSoupParser;
import org.deri.any23.http.DefaultHTTPClient;
import org.deri.any23.http.HTTPClient;
import org.deri.any23.mime.MIMEType;
import org.deri.any23.rdf.Any23ValueFactoryWrapper;
import org.deri.any23.source.ByteArrayDocumentSource;
import org.deri.any23.source.DocumentSource;
import org.deri.any23.source.FileDocumentSource;
import org.deri.any23.source.HTTPDocumentSource;
import org.deri.any23.source.LocalCopyFactory;
import org.deri.any23.source.MemCopyFactory;
import org.deri.any23.source.StringDocumentSource;
import org.openrdf.model.URI;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Node;


/**
 * A reusable wrapper around HTML documents. Supports different formats as input, such as
 * byte arrays, strings, DOM trees, Files and URLs.    
 * Keeps information about the document, such as document's uri, charset, mime type, etc, 
 * and provides convenience access to them. 
 * Also provides the dom representation of the document and an input stream for the content. 
 *
 * @author Schinas Manos (manosetro@hotmail.com)
 */
public class ExtractionInput {
	
	private URI documentURI = null;
	
	// The content in different formats. 
	private DocumentSource source = null;   // Content as org.deri.any23.source.DocumentSource.  
	private Document dom = null;			// Content as org.w3c.dom.Document.
	private InputStream is = null;			// Content as java.io.InputStream.
	
	private String encoding = null;
	private String documentLanguage = null;
	
	// MIME type of documents is always text/html. 
	static private MIMEType mime = MIMEType.parse("text/html;");
	
	EncodingDetector encoderDetector = new TikaEncodingDetector();
	
	/**
	 * Set input using  	  
	 * @param input The content of the specific document as a String
	 * @param documentURI The URI of the specific document, e.g. "http://www.example.com"
	 * @param contentType The content type of the document, e.g. "text/html". 
	 * 
	 * @throws IOException
	 * @throws TransformerFactoryConfigurationError 
	 * @throws TransformerException 
	 * @throws TransformerConfigurationException 
	 */
	public void setInput(String input, String documentURI) 
			throws IOException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError{
		
		this.source = new StringDocumentSource( input , documentURI, "text/html");
		this.documentURI = new Any23ValueFactoryWrapper(
				ValueFactoryImpl.getInstance()).createURI(documentURI
		);
		
		initInput();
	}
	
	/** 
	 * @param input The content of the specific document as a byte array.
	 * @param documentURI The URI of the specific document, e.g. "http://www.example.com"
	 * @param contentType The content type of the document, e.g. "text/html". 
	 * 
	 * @throws IOException
	 * @throws TransformerFactoryConfigurationError 
	 * @throws TransformerException 
	 * @throws TransformerConfigurationException 
	 */
	public void setInput(byte[] input, String documentURI) 
			throws IOException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError{
		
		this.source = new ByteArrayDocumentSource( input , documentURI, "text/html");
		this.documentURI = new Any23ValueFactoryWrapper(
				ValueFactoryImpl.getInstance()).createURI(documentURI
		);
		
		initInput();
	}
	
	/** 
	 * @param input The content of the specific document as a {@link org.w3c.dom.Document}
	 * @param documentURI The URI of the specific document, e.g. "http://www.example.com"
	 * @param contentType The content type of the document, e.g. "text/html". 
	 * 
	 * @throws IOException
	 * @throws TransformerConfigurationException
	 * @throws TransformerException
	 * @throws TransformerFactoryConfigurationError
	 */
	public void setInput(Document input, String documentURI) 
			throws IOException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError{

		this.documentURI = new Any23ValueFactoryWrapper(
				ValueFactoryImpl.getInstance()).createURI(documentURI
		);
		this.dom = input;

		initInput();
	}

	/**
	 * @param input The URL of a document as a {@link java.net.URL}
	 * 
	 * @throws URISyntaxException 
	 * @throws IOException 
	 * @throws TransformerConfigurationException
	 * @throws TransformerException
	 * @throws TransformerFactoryConfigurationError
	 */
	public void setInput(URL input, String documentURI) 
			throws URISyntaxException, IOException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError{
		
		LocalCopyFactory streamCache = new MemCopyFactory();
		HTTPClient httpClient = new DefaultHTTPClient();
		String userAgent = "Blog_4_ever";
		httpClient.init(userAgent, null);
		this.source = new HTTPDocumentSource( httpClient , documentURI);
		this.source = streamCache.createLocalCopy(this.source);	
	
		this.documentURI = new Any23ValueFactoryWrapper(
				ValueFactoryImpl.getInstance()).createURI( documentURI 
		);
		
		initInput();
	}
	
	public void setInput(URL input) 
			throws TransformerConfigurationException, URISyntaxException, IOException, TransformerException, TransformerFactoryConfigurationError{
		setInput(input, input.toString());
	}
	
	/**
	 * @param input A document as a {@link java.io.File}
	 * 
	 * @throws IOException
	 * @throws TransformerConfigurationException
	 * @throws TransformerException
	 * @throws TransformerFactoryConfigurationError
	 */
	public void setInput(File input, String documentURI) 
			throws IOException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError{
		
		this.source = new FileDocumentSource( input );
		this.documentURI = new Any23ValueFactoryWrapper(
				ValueFactoryImpl.getInstance()).createURI(documentURI
		);
		
		initInput();
		
	}
	
	/*
	 * Initialize the input. Specifically this method
	 * 
	 * 1) Opens an InputStream to the document's content.
	 * 2) Detects encoding of the content.
	 * 3) Extracts document language.
	 * 
	 * Also in case that the input is a byte array, a String, a File or a URL
	 * the method constructs the dom tree representation.     
	 *   
	 */
	private void initInput() 
			throws IOException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError {
		
		if(source != null){
	
			this.is = new BufferedInputStream( source.openInputStream() );	
			this.encoding = this.encoderDetector.guessEncoding(is);
			
			// Parse the document and get the dom representation of the html code.
			TagSoupParser tagSoupParser = new TagSoupParser(
                    is,
                    documentURI.stringValue(),
                    encoding
            );
			this.dom = tagSoupParser.getDOM();
	
		}
		else {
			
			this.dom.setDocumentURI(documentURI.stringValue());
		
			DOMSource domSource = new DOMSource(dom);
			StringWriter writer = new StringWriter();
			StreamResult result = new StreamResult(writer);
			TransformerFactory.newInstance().newTransformer().transform(domSource, result);
			this.is = new BufferedInputStream (
					new ByteArrayInputStream(writer.toString().getBytes())
			);
			
			this.encoding = this.encoderDetector.guessEncoding(is);
			this.source = new StringDocumentSource(
					writer.toString(), 
					this.documentURI.stringValue(), 
					encoding
			);
			
		}
		
		this.documentLanguage = extractDocumentLanguage();
	}
	
	
	/**
     * Extract the document default language using XPath.
     *
     * @return default language if any, <code>null</code> otherwise.
     */
	private String extractDocumentLanguage(){
		
		final XPath xPathEngine = XPathFactory.newInstance().newXPath();
		
		// Select html or HTML node. 
		final String xpathLanguageSelector = "/html|/HTML";
        Node html;
		try {
			html = (Node) xPathEngine.evaluate(xpathLanguageSelector, dom, XPathConstants.NODE);
        } catch (XPathExpressionException xpeee) {
            throw new IllegalStateException();
        }
        
        // Get lang (or xml:lang) attribute of the HTML node. 
        Node langAttribute = null;
        langAttribute = html.getAttributes().getNamedItem("lang");
        if(langAttribute==null)
        	langAttribute = html.getAttributes().getNamedItem("xml:lang");
        
		return langAttribute == null ? null : langAttribute.getTextContent();
		
	}
	
	
	/*
	 * A set of get methods.
	 */
	
	/**
	 * Get the dom representation of the specific document.
	 * @return {@link org.w3c.dom.Document;}
	 */
	public Document getDom(){
		return dom;
	}
	
	/**
	 * Get the specific document as an input stream 
	 * @return {@link java.io.InputStream}
	 */
	
	public InputStream getInputStream(){
		return is;
	}
	
	/**
	 * Get charset of the specific document
	 * @return String
	 */
	public String getEncoding(){
		return encoding;
	}
	
	/**
	 * Get MIME type of the specific document. 
	 * @return {@link org.deri.any23.mime.MIMEType}
	 */
	public MIMEType getMIMEType(){
		return mime;
	}
	
	/**
	 * Get the language of the specific document
	 * @return String
	 */
	public String getLanguage() {
		return documentLanguage;
	}
	
	/**
	 * Get URI of the specific document
	 * @return {@link java.net.URL}
	 */
	public URI getDocumentURI() {
		return documentURI;
	}
	
}
