package eu.dnetlib.functionality.alert.alerter.twitter;

import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.log4j.Logger;

import eu.dnetlib.domain.functionality.AlertSubscription;
import eu.dnetlib.functionality.alert.alerter.Alerter;
import eu.dnetlib.functionality.alert.alerter.AlerterException;

import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.exception.OAuthException;

/**
 * This class implements an alerter that is capable of posting alerts as tweets in twitter.
 * The subscribers served by this alerter must have a URI with scheme "data" and a scheme specific part that is a valid URL query string excluding the initial question mark.
 * This query string must contain two parameters named "accessToken" and "accessTokenSecret" with values the OAuth access token and the OAuth access token secret to use respectively.
 * All the parameters should be URL encoded in UTF-8.
 * The final tweet consists only of the message and the link (if provided), while title is ignored.
 * Message may be truncated to assert that the maximum tweet length is not exceeded.
 * This class also includes an utility main method for requesting user's authorization for a twitter account interactively. 
 * @author thanos@di.uoa.gr
 *
 */

public class TwitterAlerter extends Alerter {
	private static final Logger logger = Logger.getLogger(TwitterAlerter.class);
	private static final String TWITTER = "twitter";
	private static final String DATA = "data";
	private static final SortedSet<String> SUPPORTED_ALERT_MODES = new TreeSet<String>();
	private static final SortedSet<String> SUPPORTED_URI_SCHEMES = new TreeSet<String>();
	private static final String REQUEST_TOKEN_URL = "http://twitter.com/oauth/request_token";
	private static final String ACCESS_TOKEN_URL = "http://twitter.com/oauth/access_token";
	private static final String AUTHORIZE_URL = "http://twitter.com/oauth/authorize";
	private static final String STATUS_URL = "http://api.twitter.com/1/statuses/update.xml";
	private static final String ACCESS_TOKEN = "accessToken";
	private static final String ACCESS_TOKEN_SECRET = "accessTokenSecret";
	private static final String STATUS = "status";
	private static final int MAX_TWEET = 140;
	private static final int LINK_LENGTH = 20; // a link always counts for 20 characters in tweeter (even if shorter)

	static {
		SUPPORTED_ALERT_MODES.add(TWITTER);
		SUPPORTED_URI_SCHEMES.add(DATA);
	}
	
	private String oAuthConsumerKey;
	private String oAuthConsumerSecret;
	private OAuthConsumer oAuthConsumer;
	
	/**
	 * Interactively request user authorization for a twitter account. 
	 * @param arguments command line arguments which should be OAuth consumer key and OAuth consumer secret 
	 */
	public static void main(final String[] arguments) {
		try {
			if (arguments.length < 2) {
				System.err.println("usage: java " + TwitterAlerter.class.getName() + " <OAuth consumer key> <OAuth consumer secret>");
				return;
			}
			final OAuthProvider twitter = new DefaultOAuthProvider(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZE_URL);
			final OAuthConsumer oauthConsumer = new DefaultOAuthConsumer(arguments[0], arguments[1]);
			Desktop.getDesktop().browse(new URL(twitter.retrieveRequestToken(oauthConsumer, OAuth.OUT_OF_BAND)).toURI());
			try {
				Thread.sleep(5000); // just wait for the browser to open to avoid mixing PIN with Desktop.getDesktop().browse() output
			} catch (final InterruptedException e) {}
			twitter.retrieveAccessToken(oauthConsumer, new String(System.console().readPassword("Enter PIN: ")));
			System.out.println("Access Token: " + oauthConsumer.getToken());
			System.out.println("Access Token Secret: " + oauthConsumer.getTokenSecret());
			System.out.println("Subscription URI: " + new URI(DATA, URLEncoder.encode(ACCESS_TOKEN, "UTF-8") + "=" + URLEncoder.encode(oauthConsumer.getToken(), "UTF-8") + "&" +
					URLEncoder.encode(ACCESS_TOKEN_SECRET, "UTF-8") + "=" + URLEncoder.encode(oauthConsumer.getTokenSecret(), "UTF-8"), null));
		} catch (final IOException e) {
			System.err.println("Error acquiring user's auhtorization");
			e.printStackTrace(System.err);
		} catch (final OAuthException e) {
			System.err.println("Error acquiring user's auhtorization");
			e.printStackTrace(System.err);
		} catch (final URISyntaxException e) {
			System.err.println("Error acquiring user's auhtorization");
			e.printStackTrace(System.err);
		}
	}

	public void setOAuthConsumerKey(final String oAuthConsumerKey) {
		this.oAuthConsumerKey = oAuthConsumerKey;
	}
	
	public void setOAuthConsumerSecret(final String oAuthConsumerSecret) {
		this.oAuthConsumerSecret = oAuthConsumerSecret;
	}
	
	@Override
	public void init() {
		oAuthConsumer = new DefaultOAuthConsumer(oAuthConsumerKey, oAuthConsumerSecret);
		logger.info("twitter alerter initialization complete");
	}
	
	@Override
	public SortedSet<String> getSupportedAlertModes() {
		return SUPPORTED_ALERT_MODES;
	}
	
	@Override
	public void alert(final AlertSubscription subscription, final String title, final String message, final URL link) throws AlerterException {
		validateSubscription(subscription);
		try {
			final Properties parameters = new Properties();
			for (String parameter : subscription.getSubscriber().getRawSchemeSpecificPart().split("&")) {
				String[] nameValue = parameter.split("=");
				parameters.setProperty(URLDecoder.decode(nameValue[0], "UTF-8"), (nameValue.length > 1) ? URLDecoder.decode(nameValue[1], "UTF-8") : null);
			}
			oAuthConsumer.setTokenWithSecret(parameters.getProperty(ACCESS_TOKEN), parameters.getProperty(ACCESS_TOKEN_SECRET));
			final StringBuilder tweet = new StringBuilder(message);
			if (tweet.length() + ((link == null) ? 0 : (" ".length() + LINK_LENGTH)) > MAX_TWEET) // tweet is too long
				tweet.replace(MAX_TWEET - "...".length() - ((link == null) ? 0 : (" ".length() + LINK_LENGTH)), tweet.length(), "...");				
			if (link != null)
				tweet.append(" ").append(link);
			final HttpURLConnection connection = (HttpURLConnection) new URL(STATUS_URL + "?" + URLEncoder.encode(STATUS, "UTF-8") + "=" + URLEncoder.encode(tweet.toString(), "UTF-8")).openConnection();
			connection.setRequestMethod("POST");
			oAuthConsumer.sign(connection);
			connection.connect();
			logger.info("Connected to " + STATUS_URL);
			try {
				if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
					String line = null;
					final BufferedReader error = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
					final StringBuilder errorMessage = new StringBuilder();
					while ((line = error.readLine()) != null)
						errorMessage.append(line).append("\n");
					throw new AlerterException("error updating twitter status of " + subscription.getSubscriber() + ": " + STATUS_URL + " responded " + connection.getResponseCode() + " " + connection.getResponseMessage() +
							"\n" + errorMessage);
				}
				logger.info("Updated twitter status of " + subscription.getSubscriber());
			} finally {
				connection.disconnect();
				logger.info("Closed connection to " + STATUS_URL);
			}
		} catch (final IOException e) {
			throw new AlerterException("error updating twitter status of " + subscription.getSubscriber(), e);
		} catch (final OAuthException e) {
			throw new AlerterException("error updating twitter status of " + subscription.getSubscriber(), e);
		}
	}
	
	@Override
	protected SortedSet<String> getSupportedUriSchemes() {
		return SUPPORTED_URI_SCHEMES;
	}
}
