/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.actionbazaar.application;

import com.actionbazaar.domain.Bid;
import com.actionbazaar.interfaces.socket.BidEvent;
import com.actionbazaar.interfaces.socket.BidEventEncoderDecoder;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

/**
 * 
 * An example of an integration test case using a contrived scenario to validate
 * the WebSocket and JAX-RS interactions with each other
 *
 * More interestingly, this test case is using the Client APIs for both JAX-RS
 * and WebSocket to enable the test to be conducted with the back end services.
 * 
 */
public class RestWebSocketIT {

    private static final Logger logger = Logger.getLogger(RestWebSocketIT.class.getName());
    
    final String TEST_HOST = System.getProperty ("TEST_HOST", "localhost");
    
    private final String WebSoxURL = String.format("ws://%s:7001/actionbazaar/bidnotifier", TEST_HOST);
    private final String RESTURL = String.format("http://%s:7001/actionbazaar/rest/bids", TEST_HOST);
    private final String RESTURL_ID = String.format("http://%s:7001/actionbazaar/rest/bids/{id}", TEST_HOST);

    public RestWebSocketIT() {
        logger.log(Level.INFO, "Testing against {0} {1}", 
                new Object[]{ WebSoxURL, RESTURL} );
    }    

    private final Bid[] TEST_BIDS = new Bid[]{
        createTestBid("Bidder A", "Item AA", 10D),
        createTestBid("Bidder B", "Item BB", 20D),
        createTestBid("Bidder C", "Item CC", 30D),
        createTestBid("Bidder D", "Item DD", 40D),
        createTestBid("Bidder E", "Item EE", 50D),
        createTestBid("Bidder F", "Item FF", 60D)
    };
    
    
    /**
     * This is a test that verifies that a WebSocket endpoint receives messages
     * that correspond to activity that occurs via the REST service.
     *
     * 1. Create a WebSocket Endpoint that receives BidEvent messages 2. Insert
     * Bids via REST 3. Ensure the Bids received by the WebSocket match the Bids
     * that were inserted via REST
     * @throws java.lang.Exception
     */
    @Test
    public void testAddWithRESTReceiveWithWebSocket() throws Exception {

        final List<BidEvent> RECEIVED_BID_EVENTS = new ArrayList<>();
        final CountDownLatch latch = new CountDownLatch(TEST_BIDS.length);

        //
        // Create a WebSocket Endpoint
        //
        URI uri = new URI(WebSoxURL);
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();

        ClientEndpointConfig configuration = ClientEndpointConfig.Builder
                .create()
                .decoders(Arrays.<Class<? extends Decoder>>asList(BidEventEncoderDecoder.class))
                .encoders(Arrays.<Class<? extends Encoder>>asList(BidEventEncoderDecoder.class))
                .build();

        Endpoint webSocketClient = new Endpoint() {
            @Override
            public void onOpen(Session session, EndpointConfig config) {
                logger.log(Level.INFO, "onOpen");

                session.addMessageHandler(new MessageHandler.Whole<BidEvent>() {
                    @Override
                    public void onMessage(BidEvent bidEvent) {
                        logger.log(Level.INFO, "onMessage {0}", bidEvent.toString());
                        RECEIVED_BID_EVENTS.add(bidEvent);
                        latch.countDown();
                    }
                });
            }

            @Override
            public void onError(Session session, Throwable thr) {
                logger.log(Level.INFO, "onError: " + thr.getLocalizedMessage());
            }

            @Override
            public void onClose(Session session, CloseReason closeReason) {
                logger.log(Level.INFO, "onClose");
            }
        };

        // Start the WebSocket Endpoint
        container.connectToServer(webSocketClient, configuration, uri);

        // 
        // Add new Bid objects using JAX-RS Client API
        //
        WebTarget target = ClientBuilder.newClient().target(RESTURL);
        for (Bid bid : TEST_BIDS) {
            logger.log(Level.INFO, "posting {0}", bid);
            bid = target.request("application/json").post(Entity.json(bid), Bid.class);
        }

        // Wait for the test to complete using the CountDownLatch which
        // will suspend the Thread until either the countdown reaches 0 or
        // the timeout is reached
        latch.await(60, TimeUnit.SECONDS);

        //
        // Check and see that each Bid was added and received correctly
        //
        // 1. Check the actual count of received events
        assertEquals("Did not receive correct amount of events", TEST_BIDS.length, RECEIVED_BID_EVENTS.size());

        // 2. Check each received event and verify it matches a bids
        for (BidEvent RECEIVED_BID_EVENT : RECEIVED_BID_EVENTS) {
            Bid RECEIVED_BID = RECEIVED_BID_EVENT.getBid();

            assertEquals("Event type should only be Add", RECEIVED_BID_EVENT.getType(), "Add");

            boolean found = false;
            for (Bid TEST_BID : TEST_BIDS) {
                if (TEST_BID.getBidder().equals(RECEIVED_BID.getBidder())
                        && TEST_BID.getItem().equals(RECEIVED_BID.getItem())) {
                    found = true;
                    break;
                }
            }
            assertTrue("A bid was not added/received correctly", found == true);
        }
    }

    @Test
    public void testAddAndUpdateWithRESTReceiveWithWebSocket() throws Exception {
        
        final List<BidEvent> RECEIVED_BID_EVENTS = new ArrayList<>();
        final CountDownLatch latch = new CountDownLatch(2);
        final Double updateAmount = 150D;
        
        //
        // Create a WebSocket Endpoint
        //
        URI uri = new URI(WebSoxURL);
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();

        ClientEndpointConfig configuration = ClientEndpointConfig.Builder
                .create()
                .decoders(Arrays.<Class<? extends Decoder>>asList(BidEventEncoderDecoder.class))
                .encoders(Arrays.<Class<? extends Encoder>>asList(BidEventEncoderDecoder.class))
                .build();

        Endpoint webSocketClient = new Endpoint() {
            
            @Override
            public void onOpen(final Session session, EndpointConfig config) {
                logger.log(Level.INFO, "onOpen");

                session.addMessageHandler(new MessageHandler.Whole<BidEvent>() {
                    @Override
                    public void onMessage(BidEvent bidEvent) {
                        logger.log(Level.INFO, "onMessage {0}", bidEvent.toString());
                        latch.countDown();
                        RECEIVED_BID_EVENTS.add(bidEvent);
                        
                        if(latch.getCount() == 0) {
                            try {
                                session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Test latch countdown to zero"));
                            } catch(Exception e) {
                            }
                        }
                    }
                });
            }
            
            @Override
            public void onClose(Session session, CloseReason closeReason) {
                logger.log(Level.INFO, "onClose {0}", closeReason.toString());
            }                    
          
        };

        // Start the WebSocket Endpoint
        container.connectToServer(webSocketClient, configuration, uri);
       
        
        WebTarget target = null;
        Bid bid = createTestBid("Steve", "Foo Fighters Original CD", 100D);
        
        target = ClientBuilder.newClient().target(RESTURL);
        bid = target.request("application/json").post(Entity.json(bid), Bid.class);
        
        target = ClientBuilder.newClient()
                .target(RESTURL_ID)
                .resolveTemplate("id", bid.getId());

        // Update bid.
        bid = target.request("application/json").get(Bid.class);
        bid.setAmount(updateAmount);
        target.request().put(Entity.json(bid));
        
        // Wait for the test to complete using the CountDownLatch which
        // will suspend the Thread until either the countdown reaches 0 or
        // the timeout is reached
        latch.await(60, TimeUnit.SECONDS);

        //
        // Check and see that the operations were correct
        //
        // 1. Check the actual bid was updated 

        // Make sure bid was updated.
        Bid checkBid = target.request("application/json").get(Bid.class);
        assertEquals(updateAmount, checkBid.getAmount());
        
        assertEquals("Should be two received events", 2, RECEIVED_BID_EVENTS.size());
        assertEquals("Should be Add operation first", "Add", RECEIVED_BID_EVENTS.get(0).getType());
        assertEquals("Should be Update operation second", "Update", RECEIVED_BID_EVENTS.get(1).getType());
        assertEquals("Update amount should be " + updateAmount, updateAmount, RECEIVED_BID_EVENTS.get(1).getBid().getAmount());

        logger.info("Test complete");
        
        //latch.await(60, TimeUnit.SECONDS);
        //Thread.currentThread().sleep(60* 1000L);
        //logger.info("Test clear");
    
    }

    /**
     * Creates a Bid object
     *
     * @param bidder
     * @param item
     * @param amount
     * @return
     */
    private Bid createTestBid(String bidder, String item, Double amount) {
        Bid b = new Bid();
        b.setBidder(bidder);
        b.setItem(item);
        b.setAmount(amount);
        return b;
    }

}
