package net.heteroclinic.socialnetwork;

/**
 * <p/>
 * <p/>
 * Foaken: A tutorial project to handle Facebook Access Token for Java Desktop Application
 * <p>Third party licenses/rights are automatically cascaded.</p>
 * @author Zhikai Wang/www.heteroclinic.net
 *
 * <p>Please read the READMEnLICENSE.txt and the HOWTO.txt come with this program.</p>
 * 
 * References (may not be complete):
 * https://developers.facebook.com   -- retrieved on Aug 15, 2013
 * https://developers.facebook.com/docs/facebook-login/access-tokens/  -- retrieved on Aug 15, 2013
 * http://stackoverflow.com/questions/724043/http-url-address-encoding-in-java  -- retrieved on Aug 15, 2013
 * http://restfb.com  -- retrieved on Aug 15, 2013
 * http://benbiddington.wordpress.com/2010/04/23/facebook-graph-api-getting-access-tokens/   -- retrieved on Aug 15, 2013
 * http://www.mkyong.com/java/java-https-client-httpsurlconnection-example/  -- retrieved on Aug 15, 2013
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;

import com.restfb.DefaultFacebookClient;
import com.restfb.FacebookClient;
import com.restfb.types.Page;
import com.restfb.types.User;

import fi.iki.elonen.NanoHTTPD;

/**
 * <p>
 * Class ServerRunner is part of NanoHTTPD source code.
 * </p>
 * <p>
 * NanoHTTPD (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias
 * </p>
 */
class ServerRunner {
//	public static void run(Class<?> serverClass) {
//		try {
//			executeInstance((fi.iki.elonen.NanoHTTPD) serverClass.newInstance());
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//	}
	public static void run(fi.iki.elonen.NanoHTTPD instance) {
		try {
			executeInstance(instance);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void executeInstance(fi.iki.elonen.NanoHTTPD server) {
		try {
			server.start();
		} catch (IOException ioe) {
			System.err.println("Couldn't start server:\n" + ioe);
			System.exit(-1);
		}

		System.out.println("Server started, Hit Enter to stop.\n");

		try {
			System.in.read();
		} catch (Throwable ignored) {
		}

		server.stop();
		System.out.println("Server stopped.\n");
	}
}

/**
 * @author Zhikai Wang/www.heteroclinic.net
 * Foaken: A Mini-project to Retrieve Facebook Access Token for Java Desktop Application
 */
//TO-DO before publishing change debugMode to false and remove app secret.
public class Foaken extends fi.iki.elonen.NanoHTTPD {
	// FriendsTorus Facebook App ID: 307692296043892 
	/**
	 * Facebook App ID of Friends Torus
	 */
	private static final String FACEBOOK_APP_ID = "307692296043892";
	private static final String TOKEN_URI_KEYWORD = "friendstorus";
	private static boolean i_do_not_want_to_publish_my_app_secret = true; // change this to false and change the next String when you build your Facebook APP.
	private static final String FACEBOOK_APP_SECRET = "replace_this_string_with_your_Facebook_App_Secret";//

	/**
	 * Print the debug information or not
	 */
	public static final boolean debugMode = false;

	/**
	 * For verification of access token using HTTPS connection
	 */
	private static final String USER_AGENT = "Mozilla/5.0";

	/**
	 * Due to time limit, Class Foaken is not well OO sealed. Feel free to
	 * modify the following parameters to fit in a HTTPSession etc.
	 */
	public static boolean login_passed = false;
	public static boolean token_retrieved = false;
	public static String access_token = "";

	private static int server_listen_port = 8080;
	
	

	public Foaken() {
		super(server_listen_port);
	}

	/**
	 * @param args
	 *            , if args list is not empty, the server will use the first
	 *            argument/integer as the listen port. If there is not port
	 *            specified, the default is 8080. You can also call
	 *            Foaken.main() in the kick-start of a new thread. You can thus
	 *            call server.stop() in your shutdown-hook thread.
	 */
	public static void main(String[] args) {

		if (args.length >= 1) {
			server_listen_port = Integer.parseInt(args[0]);
		}
		Foaken foaken = new Foaken();
		//ServerRunner.run(Foaken.class);
		ServerRunner.run(foaken);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see fi.iki.elonen.NanoHTTPD#serve(java.lang.String,
	 * fi.iki.elonen.NanoHTTPD.Method, java.util.Map, java.util.Map,
	 * java.util.Map)
	 */
	@SuppressWarnings("unused")
	@Override
	public Response serve(String uri, Method method,
			Map<String, String> header, Map<String, String> parms,
			Map<String, String> files) {
		if (uri != null && uri.toString().contains(TOKEN_URI_KEYWORD)) {
			if (null != uri && debugMode) {
				System.out.println("debug uri:\n" + uri);
			}
			if (null != method && debugMode) {
				System.out.println("method:\n" + method);
			}
			if (null != header && debugMode) {
				System.out.println("header:\n" + header);
				for (Map.Entry<String, String> entry : header.entrySet()) {
					System.out
							.println(entry.getKey() + "\t" + entry.getValue());
				}
			}
			if (null != parms) {
				for (Map.Entry<String, String> entry : parms.entrySet()) {
					if (debugMode)
						System.out.println(entry.getKey() + "\t"
								+ entry.getValue());
					if (entry.getKey().equalsIgnoreCase("urlwithtoken")) {
						String tmp = retrieveFacebookAccessTokenFromURLString(entry
								.getValue().trim());
						if (tmp != null && tmp.length() > 0) {
							token_retrieved = true;
							access_token = tmp;
							if (debugMode)
								System.out.println("access_token: "
										+ access_token);
						}
					}
				}
			}

			if (null != files && debugMode) {
				System.out.println("files:\n" + files);
				for (Map.Entry<String, String> entry : files.entrySet()) {
					System.out
							.println(entry.getKey() + "\t" + entry.getValue());
				}
				System.out.println("\n\n");
			}
			if (uri != null && uri.toString().contains(TOKEN_URI_KEYWORD))
				login_passed = true;
		}

		StringBuilder sb = new StringBuilder();
		sb.append("<html>");
		sb.append("<head><title>Debug Server</title></head>");
		sb.append("<body>");

		if (!login_passed) {
			sb.append("<h1>Please log in through Facebook</h1>");
			String fboauth = "https://www.facebook.com/dialog/oauth?client_id="
					+ FACEBOOK_APP_ID + "&response_type=token"
					+ "&redirect_uri=http://127.0.0.1:8080/"
					+ TOKEN_URI_KEYWORD + "/torus.tsp";
			sb.append("<meta http-equiv=\"refresh\" content=\"1; url="
					+ fboauth + "\"> ");
		} else {

			sb.append("<h1>Log in successful!</h1>");
			if (!token_retrieved) {
				sb.append("<form action=\"gettoken.tsp\" method=\"get\">");
				sb.append("Copy paste the address(whole url) here: <input type=\"text\" name=\"urlwithtoken\"><br>");
				sb.append("<input type=\"submit\" value=\"Submit\">");

				sb.append("</form> ");
			} else {
				sb.append("<p>Access token successfully retrieved by Desktop App.<br></p>");

				if (! i_do_not_want_to_publish_my_app_secret) {
//					String https_url = completeFacebookDebugTokenHTTPSURL(access_token, "replace_this_string_with_your_Facebook_App_ID", 
//					"replace_this_string_with_your_Facebook_App_Secret");
					String https_url = completeFacebookDebugTokenHTTPSURL(access_token, FACEBOOK_APP_ID, 
							FACEBOOK_APP_SECRET);
					if (null != https_url && https_url.length() >0 ) {	
						System.out.println("completeFacebookDebugTokenHTTPSURL: " +  https_url );
						verify_Fackbook_Access_Token_with_HTTPS_GET(https_url);
					}
				}
				verify_Fackbook_Access_Token_with_RestfbExample(access_token,sb);
			}
		}

		sb.append("</body>");
		sb.append("</html>");
		return new Response(sb.toString());
	}


	/**
	 * @param access_token
	 * @param sb
	 */
	public static  void verify_Fackbook_Access_Token_with_RestfbExample(String access_token,
			StringBuilder sb) {
		FacebookClient facebookClient = new DefaultFacebookClient(access_token);
		User user = facebookClient.fetchObject("me", User.class);
		Page page = facebookClient.fetchObject("cocacola", Page.class);

		sb.append("User name(facebookClient.fetchObject(\"me\", User.class)): " + user.getName()+"<br/>");
		sb.append("Page likes of Cocacola (facebookClient.fetchObject(\"cocacola\", Page.class)): " + page.getLikes()+"<br/>");
	}
	public static  void verify_Fackbook_Access_Token_with_RestfbExample(String access_token) {
		FacebookClient facebookClient = new DefaultFacebookClient(access_token);
		User user = facebookClient.fetchObject("me", User.class);
		Page page = facebookClient.fetchObject("cocacola", Page.class);

		System.out.println("User name: " + user.getName());
		System.out.println("Page likes: " + page.getLikes());
	}

	/**
	 * @param Access_token_retrieved_in_this_login_session
	 * @param Facebook_App_id
	 * @param Facebook_App_Secret
	 *            Where to get the parameters, as on Aug 14, 2013. 
	 *            Facebook login -> Developers -> Your App -> Edit App 
	 *            It shows 
	 *            App Name: xxxxxx
	 *            App ID: xxxxxx 
	 *            App Secret: xxxxxxxxxx
	 *            Settings -> Advanced 
	 *            Valid OAuth redirect URIs (see also in the serve() function):
	 *            e.g. http://127.0.0.1:8080/friendstorus/torus.tsp
	 * @return https_url
	 */
	public static String completeFacebookDebugTokenHTTPSURL(
			String Access_token_retrieved_in_this_login_session,
			String Facebook_App_ID, String Facebook_App_Secret) {
		if ( Access_token_retrieved_in_this_login_session == null || Facebook_App_ID== null || Facebook_App_Secret==null	)
			return "";
		String https_url = "https://graph.facebook.com/debug_token?input_token="
				+ Access_token_retrieved_in_this_login_session
				+ "&access_token="+Facebook_App_ID+"|"+Facebook_App_Secret;

		return https_url;
	}

	/**
	 * @param https_url
	 */
	public static void verify_Fackbook_Access_Token_with_HTTPS_GET(String https_url) {
		URL url;
		try {
			url = new URL(https_url);
			HttpsURLConnection con = (HttpsURLConnection) url
					.openConnection();
			con.setRequestMethod("GET");
			con.setRequestProperty("User-Agent", USER_AGENT);
			print_https_cert(con);
			print_content(con);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * @param urlwithtoken
	 *            String in format like this
	 *            "http://127.0.0.1/friendstorus/torus.tsp#access_token=CAAEX2DIBfXQBAMLK0tdMFaMmbO3ct2GJ1KWwDXawgX3jtN4GZCtRl3sY5OMjdZBrMoZBlcAzXDAi0aDeJScSQ5EcJkfCmFttIffQZARkZBvsFfrpHygi9MJisvlRlHIZBgP1wZBkAtbuxv8Oy7jJQVAXypGqGwVqmwZD&expires_in=5564"
	 *            ;
	 * @return access_token, or "" if not found.
	 */
	public static String retrieveFacebookAccessTokenFromURLString(
			String urlwithtoken) {

		int i1 = urlwithtoken.indexOf("access_token")
				+ "access_token=".length();
		if (i1 < 0)
			return "";
		String sub1 = urlwithtoken.substring(i1);
		String[] sarr = sub1.split("&");
		if (sarr.length >= 1)
			return sarr[0].trim();
		else
			return "";
	}

	/**
	 * Please refer to http://www.mkyong.com/java/java-https-client-httpsurlconnection-example/
	 * @param con
	 */
	private static void print_https_cert(HttpsURLConnection con) {

		if (con != null) {

			try {

				System.out.println("Response Code : " + con.getResponseCode());
				System.out.println("Cipher Suite : " + con.getCipherSuite());
				System.out.println("\n");

				Certificate[] certs = con.getServerCertificates();
				for (Certificate cert : certs) {
					System.out.println("Cert Type : " + cert.getType());
					System.out.println("Cert Hash Code : " + cert.hashCode());
					System.out.println("Cert Public Key Algorithm : "
							+ cert.getPublicKey().getAlgorithm());
					System.out.println("Cert Public Key Format : "
							+ cert.getPublicKey().getFormat());
					System.out.println("\n");
				}

			} catch (SSLPeerUnverifiedException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}

	}

	/**
	 * Please refer to http://www.mkyong.com/java/java-https-client-httpsurlconnection-example/
	 * @param con
	 */
	private static void print_content(HttpsURLConnection con) {
		if (con != null) {

			try {

				System.out.println("****** Content of the URL ********");
				BufferedReader br = new BufferedReader(new InputStreamReader(
						con.getInputStream()));

				String input;

				while ((input = br.readLine()) != null) {
					System.out.println(input);
				}
				br.close();

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

		}

	}
}
