/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.glassfish.jersey.examples.server.async;

import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.WebTarget;

/**
 * Long-running asynchronous service client.
 *
 * @author Marek Potociar (marek.potociar at oracle.com)
 */
public class Main {
    /**
     * Main client entry point.
     *
     * @param args command-line arguments.
     */
    public static void main(String[] args) {
        System.exit(runClient(args));
    }

    /**
     * Client - business logic.
     *
     * @param args command-line arguments.
     * @return exit code of the utility. {@code 0} if everything completed without errors, {@code -1} otherwise.
     */
    static int runClient(final String[] args) {
        // Parsing command-line arguments
        final Config config = Config.parse(args);
        System.out.println(String.format("\nStarting to execute %d requests:\n", config.requests));
        // Creating JAX-RS client
        final Client client = ClientBuilder.newClient();
        // Targeting echo resource at URI "<baseUri>/long-running/(sync|async)/{echo}"
        final WebTarget echoResource = client.target(config.baseUri).path("long-running/{mode}/{echo}")
                .resolveTemplate("mode", (config.sync) ? "sync" : "async");

        final CountDownLatch latch = new CountDownLatch(config.requests);
        final Queue<String> errors = new ConcurrentLinkedQueue<String>();
        final AtomicInteger requestCounter = new AtomicInteger(0);

        final long tic = System.currentTimeMillis();
        for (int i = 0; i < config.requests; i++) {
            final int reqId = i;
            echoResource.resolveTemplate("echo", reqId).request().async().get(new InvocationCallback<String>() {
                private final AtomicInteger retries = new AtomicInteger(0);

                @Override
                public void completed(String response) {
                    final String requestId = Integer.toString(reqId);
                    if (requestId.equals(response)) {
                        System.out.print("*");
                        requestCounter.incrementAndGet();
                    } else {
                        System.out.print("!");
                        errors.offer(String.format("Echo response '%s' not equal to request '%s'", response, requestId));
                    }
                    latch.countDown();
                }

                @Override
                public void failed(Throwable error) {
                    if (error.getCause() instanceof IOException && retries.getAndIncrement() < 3) {
                        // resend
                        echoResource.resolveTemplate("echo", reqId).request().async().get(this);
                    } else {
                        System.out.print("!");
                        errors.offer(String.format("Request '%d' has failed: %s", reqId, error.toString()));
                        latch.countDown();
                    }
                }
            });
        }

        try {
            if (!latch.await(60, TimeUnit.SECONDS)) {
                errors.offer("Waiting for requests to complete has timed out.");
            }
        } catch (InterruptedException e) {
            errors.offer("Waiting for requests to complete has been interrupted.");
        }
        final long toc = System.currentTimeMillis();

        System.out.println(String.format("\n\nExecution finished in %d ms.\nSuccess rate: %6.2f %%",
                toc - tic,
                ((double) requestCounter.get() / config.requests) * 100));
        if (errors.size() > 0) {
            System.out.println("Following errors occurred during the request execution");
            for (String error : errors) {
                System.out.println("\t" + error);
            }
        }

        client.close();

        return errors.size() > 0 ? -1 : 0;
    }

    static class Config {
        /**
         * Default base URI of the async echo web application.
         */
        public static final String DEFAULT_BASE_URI = "http://localhost:8080/server-async-standalone-webapp/";

        final String baseUri;
        final boolean sync;
        final int requests;

        Config(String baseUri, boolean sync, int requests) {
            this.baseUri = baseUri;
            this.sync = sync;
            this.requests = requests;
        }

        public static Config parse(String[] args) {
            String baseUri = DEFAULT_BASE_URI;
            boolean sync = false;
            int requests = 10;

            for (String arg : args) {
                final String[] keyValuePair = arg.trim().split("=");

                if ("uri".equals(keyValuePair[0])) {
                    baseUri = keyValuePair[1];
                } else if ("mode".equals(keyValuePair[0])) {
                    sync = "sync".equals(keyValuePair[1]);
                } else if ("req".equals(keyValuePair[0])) {
                    requests = Integer.parseInt(keyValuePair[1]);
                } else {
                    System.out.println("WARNING: Unknown parameter: " + keyValuePair[0]);
                }
            }

            return new Config(baseUri, sync, requests);
        }

        @Override
        public String toString() {
            return "Config{" +
                    "baseUri='" + baseUri + '\'' +
                    ", sync=" + sync +
                    ", requests=" + requests +
                    '}';
        }
    }
}
