/**
 * Licensed Materials - Property of IBM Corporation.
 * 
 * 5725-A20
 * 
 * Copyright IBM Corporation 2017. All Rights Reserved.
 * 
 * US Government Users Restricted Rights - Use, duplication or disclosure
 * restricted by GSA ADP Schedule Contract with IBM Corporation.
 */
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

import org.apache.wink.json4j.JSONArray;
import org.apache.wink.json4j.JSONException;
import org.apache.wink.json4j.JSONObject;

/**
 * This Java template provides an example of how you might call the Blueworks Live UserList API using a Service ID.
 * You will need a Service ID in the User Management category. Contact your account admin if you do not have one. Ensure
 * they provide you with the JSON file associated with that Service ID.
 * 
 * The example demonstrates how you would:
 *  - Identify the URL of the server to call.
 *  - Request a short-lived OAuth2 token using the client ID and client secret provided with your Service ID.
 *  - Issue an API call using the OAuth2 token.
 *  - Detect token expiration and request a new token.
 * 
 * The template uses the JSON4J library from the Apache Wink project (http://wink.apache.org/) to parse the
 * JSON responses sent back by the API.
 * 
 * 1. Download the JAR from https://mvnrepository.com/artifact/org.apache.wink/wink-json4j/1.4
 * 
 * 2. Compile the sample (The following code assumes you are using the Windows command prompt):
 *    javac -cp .;wink-json4j-1.4.0.jar RestApiServiceIdClientTemplate.java
 * 
 * 3. Run it, changing the client credentials to something valid:
 *    java -cp .;wink-json4j-1.4.0.jar RestApiServiceIdClientTemplate
 * 
 * You can use your favorite JSON library.
 * 
 */
public class RestApiServiceIdClientTemplate {

    /**
     * The Blueworks Live server to access the APIs from.
     * If you're not sure what to set this to, don't worry. The first time you run this example, you'll get a message
     * telling you what URL to specify here.
     */
    private final static String BWL_SERVER = "https://www.blueworkslive.com"; //TODO: replace this with your Blueworks Live server address.

    /**
     * The path to the UserList API.
     */
    private final static String USERLIST_API_PATH = BWL_SERVER + "/scr/api/UserList";

    /**
     * The version of the API you want to use. Different versions of the API require different input parameters and
     * return results in different formats.
     */
    private final static String USERLIST_VERSION = "20110917";

    /**
     * The path to the endpoint where you can request an access token using the client ID and client secret obtained from
     * creating or reactivating your Service ID.
     */
    private final static String TOKEN_ENDPOINT = BWL_SERVER + "/oauth/token";
    
    /**
     * The client ID and client secret for the Service ID accessing the REST APIs. This example hard codes the
     * values for ease of instruction, but in reality, you would use a robust approach that can accommodate change.
     * For example, consider adding code to parse and extract the client ID and client secret from the JSON
     * file you receive when you create or reactivate a Service ID.
     * 
     * In this example, because you are accessing the UserList API, the Service ID that you use must belong
     * to the User Management category.
     */
    private final static String CLIENT_ID = "your client ID"; //TODO: replace this with your client ID. For example: 08b60891-1002-45f5-8e05-9876aee2ed88
    private final static String CLIENT_SECRET = "your client secret"; //TODO: replace this with your client secret. For example: YWRtaW4xNDk2ODQ5NTMyNjM4NWY1MDFlNTE5Y2RmYg==

    public static void main(String[] args) {
        try {
            // Construct the path to the API with the query parameters.
            StringBuilder urlBuilder = new StringBuilder(USERLIST_API_PATH);
            urlBuilder.append("?version=").append(USERLIST_VERSION);
            String pathWithQueryParams = urlBuilder.toString();

            InputStream restApiStream = null;
            HttpURLConnection restApiConnection = getRestApiConnection(pathWithQueryParams);

            // 1. Request an access token.
            String accessToken = requestAccessToken();

            // 2. Invoke the UserList API using the access token.
            addAuthenticationHeader(restApiConnection, accessToken);
            if (restApiConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                if (restApiConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
                    if (restApiConnection.getHeaderField("www-authenticate").contains("invalid_token")) {
                        // This response might mean that the token expired. Try a new connection with a new access token.
                        String newAccessToken = requestAccessToken();
                        HttpURLConnection restApiConnectionWithNewAccessToken = getRestApiConnection(pathWithQueryParams);
                        addAuthenticationHeader(restApiConnectionWithNewAccessToken, newAccessToken);

                        // Validate the connection established using the new token.
                        if (restApiConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                            System.err.println("Error calling the Blueworks Live REST API: " + restApiConnection.getResponseMessage());
                            System.exit(1);
                        }
                        restApiStream = restApiConnectionWithNewAccessToken.getInputStream();
                    }
                } else if (restApiConnection.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) {
                    // This response might mean that you made a request to the UserList API using the wrong Service ID category and
                    // you should handle this in your code.
                    System.err.println("Insufficient access to the Blueworks Live REST API: " + restApiConnection.getResponseMessage());
                }
                System.err.println("Error calling the Blueworks Live REST API: " + restApiConnection.getResponseMessage());
                System.exit(1);
            } else {
                restApiStream = restApiConnection.getInputStream();    
            }

            // 3. Process the JSON result. This example prints the name of each user.
            try {
                JSONObject userListApiResult = new JSONObject(restApiStream);
                JSONArray users = (JSONArray) userListApiResult.get("users");
                for (Object user : users) {
                    System.out.println("User name=" + ((JSONObject) user).get("name"));
                }
            } finally {
                // Clean up the streams you opened.
                if (restApiStream != null) {
                    restApiStream.close();
                }
            }
        } catch (Exception e) {
            // Handle the exceptions that might occur.
            // Perform exception handling suited to your application, which might include distinguishing 
            // the type of exception and handling it appropriately. For example, you might want to handle
            // authentication problems separately so that the user will know their credentials caused the problem.
            e.printStackTrace();
        }
    }

    /**
     * Set up the connection to a REST API including handling the Bearer Authentication request header that must be
     * present on every API call.
     * 
     * @param apiCall The URL string indicating the API call and parameters.
     * @return the open connection
     */
    private static HttpURLConnection getRestApiConnection(String apiCall) throws IOException {
        URL restApiUrl = new URL(apiCall);
        HttpURLConnection restApiURLConnection = (HttpURLConnection) restApiUrl.openConnection();
        return restApiURLConnection;
    }

    /**
     * Add the HTTP Bearer authentication header that must be present on every API call.
     * 
     * @param restApiURLConnection The open connection to the REST API.
     */
    private static void addAuthenticationHeader(HttpURLConnection restApiURLConnection, String accessToken) {
        restApiURLConnection.setRequestProperty("Authorization", "Bearer " + accessToken);
    }

    /**
     * Request an access token using the client ID and client secret obtained from your Service ID. Because access
     * tokens have an expiry time, it's best to request a token before fulfilling your requests.
     * 
     * @return the access token that you can use to access protected Blueworks Live resources.
     */
    private static String requestAccessToken() throws IOException, JSONException {
        byte[] postData = getRequestBodyForAccessToken();
        int postDataLength = postData.length;

        URL url = new URL(TOKEN_ENDPOINT);
        HttpURLConnection endPointRequestConnection = (HttpURLConnection) url.openConnection();           
        endPointRequestConnection.setRequestMethod("POST");
        endPointRequestConnection.setDoOutput(true);
        endPointRequestConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 
        endPointRequestConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
        endPointRequestConnection.setInstanceFollowRedirects(false);
        try (DataOutputStream dos = new DataOutputStream(endPointRequestConnection.getOutputStream())) {
           dos.write(postData);
        }

        if (endPointRequestConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
            if (endPointRequestConnection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM) {
                String serverURL = endPointRequestConnection.getHeaderField("Location");
                // TODO Replace the value of the BWL_SERVER constant with this new URL.
                System.err.println("Set the value of the BWL_SERVER constant to: " + serverURL);
            }
            System.err.println("Error in obtaining an access token. " + endPointRequestConnection.getResponseMessage());
            System.exit(1);
        }

        // Process the JSON result to retrieve the access token.
        String accessToken;
        try (InputStream tokenRequestStream = endPointRequestConnection.getInputStream()) {
            JSONObject tokenRequestResult = new JSONObject(tokenRequestStream);
            accessToken = (String) tokenRequestResult.get("access_token");
        }
        return accessToken;
    }

    /**
     * Get the request body to be used for the POST request when requesting an access token.
     */
    private static byte[] getRequestBodyForAccessToken() {
        StringBuilder sb = new StringBuilder("grant_type=client_credentials");
        sb.append("&client_id=")
          .append(CLIENT_ID)
          .append("&client_secret=")
          .append(CLIENT_SECRET);
        return sb.toString().getBytes(StandardCharsets.UTF_8);
    }
}