
package org.mule.module.linkedin.config;

import java.net.URLDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.signature.AuthorizationHeaderSigningStrategy;
import oauth.signpost.signature.HmacSha1MessageSigner;
import org.mule.api.MessagingException;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.adapter.OAuth1Adapter;
import org.mule.api.callback.HttpCallback;
import org.mule.api.context.MuleContextAware;
import org.mule.api.lifecycle.Initialisable;
import org.mule.api.lifecycle.Startable;
import org.mule.api.lifecycle.Stoppable;
import org.mule.api.processor.MessageProcessor;
import org.mule.config.i18n.MessageFactory;
import org.mule.module.linkedin.LinkedInConnector;
import org.mule.module.linkedin.config.spring.DefaultHttpCallback;


/**
 * A {@code LinkedInConnectorOAuth1Adapter} is a wrapper around {@link LinkedInConnector } that adds OAuth capabilites to the pojo.
 * 
 */
public class LinkedInConnectorOAuth1Adapter
    extends LinkedInConnectorHttpCallbackAdapter
    implements OAuth1Adapter, MuleContextAware, Initialisable, Startable, Stoppable
{

    private final static Pattern AUTH_CODE_PATTERN = Pattern.compile("oauth_verifier=([^&]+)");
    private MuleContext muleContext;
    private String requestToken;
    private String requestTokenSecret;
    private String oauthVerifier;
    private String redirectUrl;
    private String accessToken;
    private String accessTokenSecret;
    private OAuthConsumer consumer;
    private HttpCallback oauthCallback;

    /**
     * Sets muleContext
     * 
     * @param value Value to set
     */
    public void setMuleContext(MuleContext value) {
        this.muleContext = value;
    }

    /**
     * Retrieves oauthVerifier
     * 
     */
    public String getOauthVerifier() {
        return this.oauthVerifier;
    }

    /**
     * Sets oauthVerifier
     * 
     * @param value Value to set
     */
    public void setOauthVerifier(String value) {
        this.oauthVerifier = value;
    }

    /**
     * Retrieves redirectUrl
     * 
     */
    public String getRedirectUrl() {
        return this.redirectUrl;
    }

    /**
     * Retrieves accessToken
     * 
     */
    public String getAccessToken() {
        return this.accessToken;
    }

    /**
     * Sets accessToken
     * 
     * @param value Value to set
     */
    public void setAccessToken(String value) {
        this.accessToken = value;
    }

    /**
     * Retrieves accessTokenSecret
     * 
     */
    public String getAccessTokenSecret() {
        return this.accessTokenSecret;
    }

    /**
     * Sets accessTokenSecret
     * 
     * @param value Value to set
     */
    public void setAccessTokenSecret(String value) {
        this.accessTokenSecret = value;
    }

    private void createConsumer() {
        consumer = new DefaultOAuthConsumer(getApiKey(), getApiSecret());
        consumer.setMessageSigner(new HmacSha1MessageSigner());
        consumer.setSigningStrategy(new AuthorizationHeaderSigningStrategy());
    }

    public void start()
        throws MuleException
    {
        super.start();
        oauthCallback.start();
        redirectUrl = oauthCallback.getUrl();
    }

    public void stop()
        throws MuleException
    {
        super.stop();
        oauthCallback.stop();
    }

    public void initialise() {
        super.initialise();
        oauthCallback = new DefaultHttpCallback(new LinkedInConnectorOAuth1Adapter.OnOAuthCallbackMessageProcessor(), muleContext, getDomain(), getLocalPort(), getRemotePort(), getAsync());
        createConsumer();
    }

    public String getAuthorizationUrl() {
        String requestTokenUrl = "https://api.linkedin.com/uas/oauth/requestToken";
        OAuthProvider provider = new DefaultOAuthProvider(requestTokenUrl, "https://api.linkedin.com/uas/oauth/accessToken", "https://api.linkedin.com/uas/oauth/authorize");
        provider.setOAuth10a(true);
        String authorizationUrl;
        try {
            authorizationUrl = provider.retrieveRequestToken(consumer, redirectUrl);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        requestToken = consumer.getToken();
        requestTokenSecret = consumer.getTokenSecret();
        return authorizationUrl;
    }

    public void fetchAccessToken() {
        String requestTokenUrl = "https://api.linkedin.com/uas/oauth/requestToken";
        OAuthProvider provider = new DefaultOAuthProvider(requestTokenUrl, "https://api.linkedin.com/uas/oauth/accessToken", "https://api.linkedin.com/uas/oauth/authorize");
        provider.setOAuth10a(true);
        consumer.setTokenWithSecret(requestToken, requestTokenSecret);
        try {
            provider.retrieveAccessToken(consumer, oauthVerifier);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        accessToken = consumer.getToken();
        accessTokenSecret = consumer.getTokenSecret();
    }

    private class OnOAuthCallbackMessageProcessor
        implements MessageProcessor
    {


        public MuleEvent process(MuleEvent event)
            throws MuleException
        {
            try {
                oauthVerifier = extractAuthorizationCode(event.getMessageAsString());
            } catch (Exception e) {
                throw new MessagingException(MessageFactory.createStaticMessage("Could not extract OAuth verifier"), event, e);
            }
            return event;
        }

        private String extractAuthorizationCode(String response)
            throws Exception
        {
            Matcher matcher = AUTH_CODE_PATTERN.matcher(response);
            if (matcher.find()&&(matcher.groupCount()>= 1)) {
                return URLDecoder.decode(matcher.group(1), "UTF-8");
            } else {
                throw new Exception(String.format("OAuth authorization code could not be extracted from: %s", response));
            }
        }

    }

}
