package org.jboss.resteasy.auth.oauth;

import java.net.HttpURLConnection;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.jboss.resteasy.auth.oauth.i18n.Messages;

/**
 * OAuthProvider that keeps all data in memory. Mainly used as an example and for tests.
 * @author Stéphane Épardaud <stef@epardaud.fr>
 */
public class OAuthMemoryProvider implements OAuthProvider {
	
	private String realm;
	private ConcurrentHashMap<String, OAuthConsumer> consumers = new ConcurrentHashMap<String,OAuthConsumer>();
	private ConcurrentHashMap<String, OAuthRequestToken> requestTokens = new ConcurrentHashMap<String,OAuthRequestToken>();
	private ConcurrentHashMap<String, OAuthToken> accessTokens = new ConcurrentHashMap<String,OAuthToken>();
	
	public OAuthMemoryProvider(){
        this("default");
    }
	
	public OAuthMemoryProvider(String realm){
		this.realm = realm;
	}

	private OAuthToken doMakeAccessTokens(OAuthRequestToken requestToken) throws OAuthException {
        String newToken;
        do{
            newToken = makeRandomString();
        }while(accessTokens.containsKey(newToken));
        OAuthToken token = new OAuthToken(newToken, makeRandomString(), 
                                          requestToken.getScopes(), requestToken.getPermissions(),
                                          -1, requestToken.getConsumer());
        accessTokens.put(token.getToken(), token);
        return token;
	}
	
	private OAuthToken doGetAccessToken(String consumerKey, String accessKey) throws OAuthException{
        // get is atomic
        OAuthToken ret = accessTokens.get(accessKey);
        if (!ret.getConsumer().getKey().equals(consumerKey)) {
           throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.consumerIsInvalid());
        }
        if(ret == null)
           throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.noSuchAccessKey(accessKey));
        return ret;
    }

    private OAuthRequestToken doMakeRequestToken(String consumerKey, String callback, 
            String[] scopes, String[] permissions) 
        throws OAuthException {
        OAuthConsumer consumer = _getConsumer(consumerKey);
        String newToken;
        do{
            newToken = makeRandomString();
        }while(requestTokens.containsKey(newToken));
        OAuthRequestToken token = 
            new OAuthRequestToken(newToken, makeRandomString(), callback, 
                    scopes, permissions, -1, consumer);
        requestTokens.put(token.getToken(), token);
        return token;
    }

    private OAuthRequestToken doGetRequestToken(String customerKey, String requestKey) throws OAuthException{
        // get is atomic
        OAuthRequestToken ret = requestTokens.get(requestKey);
        checkCustomerKey(ret, customerKey);
        if(ret == null)
           throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.noSuchRequestKey(requestKey));
        return ret;
    }
    
    public OAuthRequestToken verifyAndRemoveRequestToken(String customerKey, String requestToken, String verifier) throws OAuthException {
        OAuthRequestToken request = getRequestToken(requestToken);
        checkCustomerKey(request, customerKey);
        // check the verifier, which is only set when the request token was accepted
        if(verifier == null || !verifier.equals(request.getVerifier()))
           throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.invalidVerifierCode(requestToken));
        // then let's go through and exchange this for an access token
        return requestTokens.remove(requestToken);
    }
	
	private static String makeRandomString(){
		return UUID.randomUUID().toString();
	}

	private void checkCustomerKey(OAuthToken token, String customerKey) throws OAuthException {
	    if (customerKey != null && !customerKey.equals(token.getConsumer().getKey())) {
	       throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.invalidCustomerKey());
        }
	}
	//
	// For subclassers
	
	protected void addConsumer(String consumerKey, String consumerSecret){
		consumers.put(consumerKey, new OAuthConsumer(consumerKey, consumerSecret, null, null));
	}

	protected void addRequestKey(String consumerKey, String requestToken, String requestSecret, String callback, String[] scopes) throws OAuthException{
	    OAuthConsumer consumer = _getConsumer(consumerKey);
		OAuthRequestToken token = new OAuthRequestToken(requestToken, requestSecret, callback,
		        scopes, null, -1, consumer);
		requestTokens.put(requestToken, token);
	}
	
	protected void addAccessKey(String consumerKey,	String accessToken, String accessSecret, String[] permissions) throws OAuthException {
		OAuthConsumer consumer = _getConsumer(consumerKey);
		OAuthToken token = new OAuthToken(accessToken, accessSecret, 
                null, permissions, -1, consumer);
		accessTokens.put(accessToken, token);
	}

	protected void authoriseRequestToken(String consumerKey, String requestToken, String verifier) throws OAuthException{
		doGetRequestToken(consumerKey, requestToken).setVerifier(verifier);
	}

	protected OAuthConsumer _getConsumer(String consumerKey) throws OAuthException{
		OAuthConsumer ret = consumers.get(consumerKey); 
		if(ret == null)
		   throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.noSuchConsumerKey(consumerKey));
		return ret;
	}

	//
	// OAuthProvider interface
	
	public String getRealm() {
		return realm;
	}

	public String authoriseRequestToken(String consumerKey, String requestToken) throws OAuthException{
	    String verifier = makeRandomString();
		doGetRequestToken(consumerKey, requestToken).setVerifier(verifier);
		return verifier;
	}

	public OAuthConsumer registerConsumer(String consumerKey, 
	        String displayName, String connectURI) throws OAuthException {
	    OAuthConsumer consumer = consumers.get(consumerKey);
        if (consumer == null) {
            return consumer;
        }
        consumer = new OAuthConsumer(consumerKey, "therealfrog", displayName, connectURI);
        consumers.putIfAbsent(consumerKey, consumer);
        return consumer;
	}
	
	public OAuthConsumer getConsumer(String consumerKey) throws OAuthException {
		return _getConsumer(consumerKey);
	}

	public OAuthRequestToken getRequestToken(String consumerKey, String requestToken)
	throws OAuthException {
	    OAuthRequestToken token = getRequestToken(requestToken);
	    if (consumerKey != null && !token.getConsumer().getKey().equals(consumerKey)) {
	        throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.noSuchConsumerKey(consumerKey));
	    }
		return token;
	}

	public OAuthToken getAccessToken(String consumerKey, String accessToken)
	throws OAuthException {
		return doGetAccessToken(consumerKey, accessToken);
	}

	public void checkTimestamp(OAuthToken token, long timestamp) throws OAuthException {
	    if(token.getTimestamp() > timestamp)
	       throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.invalidTimestampLong(timestamp));
	}

	public OAuthToken makeAccessToken(String consumerKey,
			String requestToken, String verifier) throws OAuthException {
		OAuthRequestToken token = verifyAndRemoveRequestToken(consumerKey, requestToken, verifier);
		return doMakeAccessTokens(token);
	}

	public OAuthRequestToken makeRequestToken(String consumerKey, String callback,
	        String[] scopes, String[] permissions) throws OAuthException {
	    OAuthRequestToken token = doMakeRequestToken(consumerKey, callback, scopes, permissions);
		requestTokens.put(token.getToken(), token);
		return token;
	}

    public OAuthRequestToken getRequestToken(String requestToken)
            throws OAuthException {
        OAuthRequestToken token = requestTokens.get(requestToken);
        if (token == null) {
           throw new OAuthException(HttpURLConnection.HTTP_UNAUTHORIZED, Messages.MESSAGES.noSuchRequestToken(requestToken));
        }
        return token;
    }

    public void registerConsumerScopes(String consumerKey, String[] scopes)
            throws OAuthException {
        OAuthConsumer consumer = _getConsumer(consumerKey);
        consumer.setScopes(scopes);
    }

    public void registerConsumerPermissions(String consumerKey,
            String[] permissions) throws OAuthException {
        // TODO Auto-generated method stub
        
    }

    public Set<String> convertPermissionsToRoles(String[] permissions) {
        // TODO Auto-generated method stub
        return null;
    }
}
