/** ========================================================================= *
 * Copyright (C) 2008, 2009 IBM Corporation ( http://www.ibm.com/ )           *
 *                            All rights reserved.                            *
 * Code contributor(s): Stephan H. Wissel ( notessensei@sg.ibm.com )          *
 * ========================================================================== *
 *                                                                            *
 * 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 org.lotususers.tools;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
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.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.DomSerializer;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.TagNode;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Reads the content generated by Domino, optional snips out some content using
 * an XPath expression
 * 
 * @author stw
 * 
 */
public class HTTPReader {

	private static final String AUTHENTICATION_URL = "names.nsf?Login";
	
	private static final String LOGOUT_URL = "names.nsf?Logout";

	public final static void get(HTTPReader r, String docID) {
		System.out.println("=================== " + docID
				+ " ========================");
		System.out.println(r.getDocument(docID));
		System.out.println("=================== DONE " + docID
				+ " ===================");

	}

	public final static void main(String[] args) {

		String server = "http://localhost";
		String databaseURL = "sample.nsf";
		String view = "0";
		String xPath = "//div";

		HTTPReader r = new HTTPReader();

		// The static parts
		r.setServerURL(server);
		r.setDatabaseURL(databaseURL);
		r.setViewName(view);
		r.setXPath(xPath);
		r.setUseSSO(false);
		//r.setUserName("Doctor Notes");
		//r.setPassWord("lotusnotes");

		// Now per document
		String document = "EB38C0CFF29BA1FA48256B1B0012B722";
		get(r, document);
		
		System.out.println(r.getURL(server+"/"+databaseURL+"/Demo?OpenForm"));

		// document = "886EA9FFFBFB859048256C790020D04A";
		// get(r,document);

		r.recycle();

		System.out.println("=================== DONE ===================");
	}

	/**
	 * The current user - if we don't use SSO
	 */
	private String userName = null;
	/**
	 * Her password
	 */
	private String passWord = null;
	/**
	 * Should we use SSO?
	 */
	private boolean useSSO = false;
	/**
	 * Optional the SSO Token
	 */
	private String LTPACookie = null;
	/**
	 * The URL to the server without / at the end
	 */
	private String serverURL = null;
	/**
	 * Are we logged in properly?
	 */
	private boolean isLoggedIn = false;
	/**
	 * The path to the database ending with .nsf
	 */
	private String databaseURL = null;

	/**
	 * Name of the view if any.
	 */
	private String viewName = "0";
	/**
	 * XPath expression to limit the amount of data that we retrieve
	 */
	private String XPath = null;

	/**
	 * The HTTPClient to retrieve all the data
	 */
	private HttpClient httpClient = null;

	/**
	 * Default constructor needs to be parameterless to be usefull in LS2J
	 * 
	 */
	public HTTPReader() {

	}

	/**
	 * Basic loging submits username and password to the standard login form of
	 * Domino We don't do basith authentication
	 */
	private void doBasicLogin() {

		if (this.userName == null || this.passWord == null || this.isLoggedIn) {
			// Can't login without username / password
			return;
		}

		try {
			String postURL = this.serverURL + HTTPReader.AUTHENTICATION_URL;
			String redirectto = "/" + this.databaseURL;
			System.out.println("Login via:"+postURL);
			System.out.println("redirectto:"+redirectto);
			// Wir posten username + password an die URL
			HttpPost httpost = new HttpPost(postURL);
			

			List<NameValuePair> loginParams = new ArrayList<NameValuePair>();
			loginParams.add(new BasicNameValuePair("username", this.userName));
			loginParams.add(new BasicNameValuePair("password", this.passWord));
			loginParams.add(new BasicNameValuePair("redirectto", redirectto));

			httpost.setEntity(new UrlEncodedFormEntity(loginParams, HTTP.UTF_8));
			
			HttpResponse response = this.httpClient.execute(httpost);
			HttpEntity entity = response.getEntity();

			System.out.println("Login form result: " + response.getStatusLine());
			if (entity != null) {
				entity.consumeContent();
			}
			
			System.out.println("Logged in as"+this.userName);
			this.isLoggedIn = true;
			
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			this.isLoggedIn = false;
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			this.isLoggedIn = false;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			this.isLoggedIn = false;
		}


	}

	/**
	 * Login to the Domino using SSO / LTPA
	 */
	private void doSSOLogin() {

		if (this.LTPACookie == null) {
			// We can't without a cookie
			// TODO: log error
			return;
		}

		// TODO: implement
		return;

	}

	/**
	 * @return the databaseURL
	 */
	public String getDatabaseURL() {
		return databaseURL;
	}

	/**
	 * Convenience method to retrieve a document from an nsf
	 * 
	 * @param docID
	 * @return
	 */
	public String getDocument(String docID) {
		if (this.serverURL == null) {
			return "serverURL is missing";
		}

		String urlToFetch = this.serverURL;

		if (this.databaseURL == null) {
			return "database is missing";
		}
		urlToFetch += this.databaseURL;

		if (this.viewName == null || this.viewName.equals("")) {
			urlToFetch += "0/";
		} else {
			urlToFetch += this.viewName;
		}

		urlToFetch += docID;

		String result = this.getURL(urlToFetch);

		if (this.XPath != null && result != null) {
			return this.sliceResult(result);
		}

		return result;

	}

	/**
	 * @return the lTPACookie
	 */
	public String getLTPACookie() {
		return LTPACookie;
	}

	/**
	 * @return the passWord
	 */
	public String getPassWord() {
		return passWord;
	}

	/**
	 * @return the serverURL
	 */
	public String getServerURL() {
		return serverURL;
	}

	/**
	 * Retrieves the content from an URL
	 * 
	 * @param url
	 *            the full qualified URL
	 * @return whatever String came from there
	 */
	public String getURL(String url) {

		System.out.println("Fetching " + url);

		if (this.httpClient == null) {
			this.initializeHTTPSession();
		}

		ResponseHandler<String> responseHandler = new BasicResponseHandler();
		HttpGet get = new HttpGet(url);

		String result = null;

		try {
			result = this.httpClient.execute(get, responseHandler);
		} catch (HttpResponseException e) {
			System.out.println(e.getMessage());
			return null;
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			result = "The host is invalid: " + url;
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * @return the userName
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * @return the viewName
	 */
	public String getViewName() {
		return viewName;
	}

	/**
	 * @return the xPath
	 */
	public String getXPath() {
		return XPath;
	}

	/**
	 * Get the HTTP client and do authentication if needed
	 */
	private void initializeHTTPSession() {
		this.httpClient = new DefaultHttpClient();
		if (this.useSSO) {
			this.doSSOLogin();
		} else {
			this.doBasicLogin();
		}

	}

	/**
	 * @return the isLoggedIn
	 */
	public boolean isLoggedIn() {
		return isLoggedIn;
	}

	/**
	 * @return the useSSO
	 */
	public boolean isUseSSO() {
		return useSSO;
	}

	/**
	 * Once we are done we reset the Java object
	 */
	public void recycle() {
		if (this.httpClient != null) {
			
			// If we are logged in we try to properly log out
			if (this.isLoggedIn && !this.useSSO) {
				System.out.println("Logout attempt");
				String logoutURL = this.serverURL+ HTTPReader.LOGOUT_URL;
				this.getURL(logoutURL);
			}
			
			this.httpClient.getConnectionManager().shutdown();
			this.httpClient = null;
		}
	}

	/**
	 * @param databaseURL
	 *            the databaseURL to set
	 */
	public void setDatabaseURL(String databaseURL) {
		if (!databaseURL.endsWith("/")) {
			databaseURL += "/";
		}
		this.databaseURL = databaseURL;
	}

	/**
	 * @param cookie
	 *            the lTPACookie to set
	 */
	public void setLTPACookie(String cookie) {
		LTPACookie = cookie;
	}

	/**
	 * @param passWord
	 *            the passWord to set
	 */
	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}

	/**
	 * @param serverURL
	 *            the serverURL to set
	 */
	public void setServerURL(String serverURL) {
		if (!serverURL.endsWith("/")) {
			serverURL += "/";
		}
		this.serverURL = serverURL;
	}

	/**
	 * @param userName
	 *            the userName to set
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}

	/**
	 * @param useSSO
	 *            the useSSO to set
	 */
	public void setUseSSO(boolean useSSO) {
		this.useSSO = useSSO;
	}

	/**
	 * @param viewName
	 *            the viewName to set
	 */
	public void setViewName(String viewName) {
		if (!viewName.endsWith("/")) {
			viewName += "/";
		}
		this.viewName = viewName;
	}

	/**
	 * @param path
	 *            the xPath to set
	 */
	public void setXPath(String path) {
		XPath = path;
	}

	/**
	 * SliceResult tries to transform an String into a DOM Object and to execute
	 * a XPath expression on it. Along the way HTML inconsistencies are weeded
	 * out
	 * 
	 * @param raw
	 * @return result the converted string
	 * @throws ParserConfigurationException 
	 */
	private String sliceResult(String raw) {
		StringReader sr = new StringReader(raw);
		HtmlCleaner cleaner = new HtmlCleaner();
		CleanerProperties props = cleaner.getProperties();
		props.setOmitComments(true);
		props.setOmitDoctypeDeclaration(true);
		props.setOmitUnknownTags(false);
		props.setOmitXmlDeclaration(true);
		props.setRecognizeUnicodeChars(true);
		props.setOmitDeprecatedTags(true);
		props.setUseEmptyElementTags(true);
		
		TagNode resultTag;
		try {
			resultTag = cleaner.clean(sr);
			//ByteArrayOutputStream out = new ByteArrayOutputStream();
			//PrintWriter writer = new PrintWriter(out);
			//SimpleXmlSerializer ss = new SimpleXmlSerializer(props);
			//resultTag.serialize(ss, writer);
			//writer.flush();
			//return out.toString();
			
			// Now serialize
			DomSerializer ds = new DomSerializer(props);
			Document doc = ds.createDOM(resultTag);
			return extractFromDocument(doc, this.XPath);

		} catch (IOException e) {

			e.printStackTrace();
		
		} catch (ParserConfigurationException e) {
		
			e.printStackTrace();
		}
		
		// If we got her it failed! So we at least
		// return the raw result
		return raw;
		
	}

	/**
	 * Runs an XPath expression against the result and returns a string might
	 * not have a root node
	 * 
	 * @param doc
	 * @param xPath
	 * @return
	 */
	private String extractFromDocument(Document doc, String xPathString) {
		StringBuffer b = new StringBuffer();

		Object exprResult = null;
		XPath xpath = XPathFactory.newInstance().newXPath();

		try {
			exprResult = xpath.evaluate(xPathString, doc,
					XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			System.err.println("XPATH failed for " + xPathString);
			System.err.println(e.getMessage());
		}
		// if it failed we abort
		if (exprResult == null) {
			return null;
		}
		// We now extract the Node list of our results. Can be one or more
		// hits which we process all
		NodeList nodes = (NodeList) exprResult;

		if (nodes == null || nodes.getLength() == 0) {
			System.out.println("XPath had no results:" + xPathString);
			// we resturn the original document
			return this.dom2String(doc);
		}

		System.out.print("XPath found results for: " + xPathString + ": ");
		System.out.println(nodes.getLength());

		int nodeCount = nodes.getLength();

		for (int i = 0; i < nodeCount; i++) {
			if (i > 0) {
				b.append("\n");
			}
			Node curNode = nodes.item(i);
			b.append(this.dom2String(curNode));

		}

		return b.toString();

	}

	/**
	 * Converts a DOM into a String to handle it outside
	 * 
	 * @param dom
	 *            - a DOM
	 * @return the DOM in string format
	 */
	private String dom2String(Node dom) {
		String result = null;

		StreamResult xResult = null;
		DOMSource source = null;

		try {
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			xResult = new StreamResult(new StringWriter());
			source = new DOMSource(dom);
			// We don't want the XML declaration in front
			transformer.setOutputProperty("omit-xml-declaration", "yes");
			transformer.transform(source, xResult);
			result = xResult.getWriter().toString();

		} catch (Exception e) {
			e.printStackTrace();
		}

		return this.stripEmptyLines(result);
	}

	/**
	 * Removes empty lines from Strings. Comes in handy after XSLT
	 * Transformations
	 * 
	 * @param inString
	 * @return
	 */
	private String stripEmptyLines(String inString) {
		StringBuffer b = new StringBuffer();

		Scanner lineScanner = new Scanner(inString);

		while (lineScanner.hasNextLine()) {
			String curLine = lineScanner.nextLine().trim();
			if (!curLine.equals("") && !curLine.replace("\n", "").equals("")) {
				b.append(curLine);
				b.append("\n");
			}
		}

		return b.toString();
	}

}

