package sample.application_self_contained;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.ChunkedInput;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.filter.LoggingFilter;

import javax.net.ssl.*;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.*;

/**
 * <pre>
 * Sample2SubscriptionRaw aka the "Straight line subscription sample"
 *
 * The class is self-contained: it does not depend on any other class from the "RestClient" source package.
 *
 * This class is an easy to understand but rough code that does the same thing as the
 * Sample2Subscription but without the defense code:
 * - authenticates user 1
 * - opens session and subscribes to telephonic events for user 1
 * - initiates a call from user 1 to user 2 (user 2 does not answer)
 * - gets the call ref from this call
 * - and uses this call ref to release the call .
 * - At last subscription and session are closed.
 *
 * </pre>
 */
public class Sample2SubscriptionRaw {

    public static final String USER_COOKIE_NAME = "AlcUserId";

    enum User {
        SP("lark01", "123123", "8021", "9921"),
        PLEC("lark02", "123123", "8022", "9922");

        final String login;
        final String password;
        final String number;
        final String deviceId;

        User(String login, String password, String number, String deviceId) {
            this.login = login;
            this.password = password;
            this.number = number;
            this.deviceId = deviceId;
        }
    }

    @JsonIgnoreProperties(ignoreUnknown=true)
    static class Subscription {
        String privatePollingUrl;
        String subscriptionId;

        void setPrivatePollingUrl(String privatePollingUrl) {
            this.privatePollingUrl = privatePollingUrl;
        }

        void setSubscriptionId(String subscriptionId) {
            this.subscriptionId = subscriptionId;
        }
    }

    String SERVER_IP = "172.25.122.10";
    String PUBLIC_ROOT = "http://" + SERVER_IP + "/api/rest";
    String ROOT_PATH = "https://" + SERVER_IP + "/api/rest/1.0";
    Client client;
    WebTarget webTarget;

    public static void main(String[] args)
            throws InterruptedException, IOException, KeyManagementException, ExecutionException {

        Sample2SubscriptionRaw scenario = new Sample2SubscriptionRaw();
        scenario.run();
    }

    private void run() throws InterruptedException, IOException, ExecutionException, KeyManagementException {

        client = createClient();
        webTarget = client.target(ROOT_PATH);

        // I) authentication:

        Cookie spCookie = authenticate(User.SP.login, User.SP.password);

        // II) open session:

        openSession(spCookie);

        // III) subscribe to some events, and wait for a call-ref:

        Subscription subscription = subscribe(User.SP.login, spCookie);

        // listening to events is done in another thread:
        Future<String> callRefFuture = getFirstCallRef(subscription.privatePollingUrl);

        // IV) make calls:

        Thread.sleep(1000L);

        makeCall(spCookie, User.SP.deviceId, User.PLEC.number);

        // V) wait for the first event with a call-ref:

        String callId = callRefFuture.get();

        Thread.sleep(2000L);

        // VI) releasing the call

        releaseCall(spCookie, callId);

        Thread.sleep(1000L);

        // VII) cleanup

        unsubscribe(User.SP.login, spCookie, subscription.subscriptionId);

        Thread.sleep(1000L);

        closeSession(spCookie);

        client.close();
    }


    private Cookie authenticate(String login, String password) {

        Response response1 = client.target(PUBLIC_ROOT).request().get();
        System.out.println("response1:\n" + response1);
        // here, getting the first API version available:
        JsonNode jsonNode1 = response1.readEntity(JsonNode.class);
        String publicUrl = jsonNode1.get("versions").get(0).get("publicUrl").getTextValue();
        System.out.println("public Url: " + publicUrl);

        Response response2 = client.target(publicUrl).request().get();
        System.out.println("response2:\n" + response2);

        Response response3 = client.target(response2.getLocation())
                .register(HttpAuthenticationFeature.basic(login, password)).request().get();
        System.out.println("response3:\n" + response3);

        Map<String, NewCookie> cookies = response3.getCookies();
        System.out.println(cookies);

        Cookie result = cookies.get(USER_COOKIE_NAME);
        System.out.println("cookie = " + result);
        return result;
    }

    private void openSession(Cookie cookie)  {

        class SessionRequest { public String getApplicationName() { return "Test API"; } }

        Response response = webTarget.path("sessions").request().cookie(cookie).post(Entity.json(new SessionRequest()));

        System.out.println("open response: " + response);
    }


    private void closeSession(Cookie cookie) {

        Response response = webTarget.path("sessions").request().cookie(cookie).delete();

        System.out.println("close response: " + response);
    }


    private Subscription subscribe(final String login, final Cookie cookie) {

        System.out.println("Subscribe for " + login);

        // subscribing to "telephony" package
        String version = "1.0";
        String filter = "{\"selectors\":[{\"ids\":[\"" + login + "\"],\"names\":[\"telephony\"],\"families\":[],\"origins\":[]}]}";
        final String subscriptionRequest = "{\"notificationUrl\":null"
                + ",\"filter\":" + filter
                + ",\"version\":\"" + version + '\"'
                + '}';

        return webTarget.path("subscriptions").request().cookie(cookie).post(Entity.json(subscriptionRequest), Subscription.class);
    }

    private Future<String> getFirstCallRef(final String privateUrlForChunks) {

        System.out.println("for polling: " + privateUrlForChunks);

        ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                0L, TimeUnit.SECONDS,  // so the created thread will die after the task has been executed
                new SynchronousQueue<Runnable>());

        Future<String> result = executorService.submit(new Callable<String>() {
            @Override
            public String call() {
                Response response = client.target(privateUrlForChunks).request().get();
                ChunkedInput<JsonNode> chunkedInput = response.readEntity(new GenericType<ChunkedInput<JsonNode>>() { });
                JsonNode chunk;
                // waiting for the first callRef:
                while ((chunk = chunkedInput.read()) != null) {
                    System.out.println("Next chunk received: " + chunk);
                    String callRef = chunk.get("callRef").getTextValue();
                    if (callRef != null) {
                        System.out.println("callRef = " + callRef);
                        return callRef;
                    }
                }
                return null;
            }
        });

        return result;
    }

    private void unsubscribe(final String login, final Cookie cookie, String subscriptionId) {

        System.out.println("Unsubscribe for " + login);

        Response response = webTarget.path("subscriptions").path(subscriptionId).request().cookie(cookie).delete();

        System.out.println("Unsubscribe response: " + response);
    }


    private void makeCall(Cookie cookie, final String callerDeviceId, final String calleePhoneNumber) {

        class MakeCallRequest {
            public boolean getAutoAnswer() { return true; }
            public String getDeviceId() { return callerDeviceId; }
            public String getCallee() { return calleePhoneNumber; }
        }

        Response response = webTarget.path("telephony").path("calls").request().cookie(cookie).post(Entity.json((new MakeCallRequest())));

        System.out.println(response);
    }

    private void releaseCall(Cookie cookie, String callRef) throws IOException {

        System.out.println("now releasing call " + callRef);

        Response response = webTarget.path("telephony/calls/" + callRef + "/terminate").request().cookie(cookie).post(Entity.json("{ }"));

        System.out.println(response);
    }

    private static Client createClient() throws KeyManagementException, IOException {

        ClientConfig clientConfig = new ClientConfig(JacksonJsonProvider.class);
        clientConfig.register(new LoggingFilter());

        SSLContext sslContext = SslConfigurator.newInstance().createSSLContext();
        sslContext.init(null, new TrustManager[ ] {
                new X509TrustManager() {
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType)
                            throws CertificateException {
                    }
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType)
                            throws CertificateException {
                    }
                }
        }, new SecureRandom());

        Client client = ClientBuilder.newBuilder().withConfig(clientConfig).hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        }).sslContext(sslContext).build();

        client.property(ClientProperties.FOLLOW_REDIRECTS, Boolean.FALSE);

        return client;
    }

}

