package com.github.scribejava.core.extractors;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.scribejava.core.exceptions.OAuthException;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.utils.OAuthEncoder;
import com.github.scribejava.core.utils.Preconditions;

/**
 * Custom implementation of {@link TokenExtractor} for OAuth 2.0
 */
public class OAuth2AccessTokenExtractor implements TokenExtractor<OAuth2AccessToken> {

    private static final Pattern ACCESS_TOKEN_REGEX_PATTERN = Pattern.compile("access_token=([^&]+)");
    private static final Pattern TOKEN_TYPE_REGEX_PATTERN = Pattern.compile("token_type=([^&]+)");
    private static final Pattern EXPIRES_IN_REGEX_PATTERN = Pattern.compile("expires_in=([^&]+)");
    private static final Pattern REFRESH_TOKEN_REGEX_PATTERN = Pattern.compile("refresh_token=([^&]+)");
    private static final Pattern SCOPE_REGEX_PATTERN = Pattern.compile("scope=([^&]+)");

    protected OAuth2AccessTokenExtractor() {
    }

    private static class InstanceHolder {

        private static final OAuth2AccessTokenExtractor INSTANCE = new OAuth2AccessTokenExtractor();
    }

    public static OAuth2AccessTokenExtractor instance() {
        return InstanceHolder.INSTANCE;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OAuth2AccessToken extract(Response response) throws IOException {
        final String body = response.getBody();
        Preconditions.checkEmptyString(body,
                "Response body is incorrect. Can't extract a token from an empty string");

        final String accessToken = extractParameter(body, ACCESS_TOKEN_REGEX_PATTERN, true);
        final String tokenType = extractParameter(body, TOKEN_TYPE_REGEX_PATTERN, false);
        final String expiresInString = extractParameter(body, EXPIRES_IN_REGEX_PATTERN, false);
        Integer expiresIn;
        try {
            expiresIn = expiresInString == null ? null : Integer.valueOf(expiresInString);
        } catch (NumberFormatException nfe) {
            expiresIn = null;
        }
        final String refreshToken = extractParameter(body, REFRESH_TOKEN_REGEX_PATTERN, false);
        final String scope = extractParameter(body, SCOPE_REGEX_PATTERN, false);

        return new OAuth2AccessToken(accessToken, tokenType, expiresIn, refreshToken, scope, body);
    }

    private static String extractParameter(String response, Pattern regexPattern, boolean required)
            throws OAuthException {

        final Matcher matcher = regexPattern.matcher(response);
        if (matcher.find()) {
            return OAuthEncoder.decode(matcher.group(1));
        } else if (required) {
            throw new OAuthException("Response body is incorrect. Can't extract a '" + regexPattern.pattern()
                    + "' from this: '" + response + "'", null);
        } else {
            return null;
        }
    }
}
