/*
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,  
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.switchyard.internal;

import java.util.EventObject;

import javax.xml.namespace.QName;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;
import org.switchyard.BaseHandler;
import org.switchyard.Exchange;
import org.switchyard.ExchangeHandler;
import org.switchyard.ExchangePhase;
import org.switchyard.ExchangeState;
import org.switchyard.HandlerException;
import org.switchyard.Message;
import org.switchyard.MockDomain;
import org.switchyard.MockHandler;
import org.switchyard.ServiceReference;
import org.switchyard.event.EventObserver;
import org.switchyard.metadata.InOutOperation;
import org.switchyard.metadata.InOutService;
import org.switchyard.metadata.JavaTypes;
import org.switchyard.runtime.event.ExchangeCompletionEvent;
import org.switchyard.runtime.event.ExchangeInitiatedEvent;
import org.switchyard.spi.Dispatcher;
import org.switchyard.transform.BaseTransformer;

/**
 *  Unit tests for the ExchangeImpl class.
 */
public class ExchangeImplTest {
    
    private MockDomain _domain;
	private Dispatcher _dispatch;

    @Before
    public void setUp() throws Exception {
        _domain = new MockDomain();
        ServiceReference reference = _domain.createInOnlyService(new QName("foo"));
        _dispatch = _domain.getBus().createDispatcher(reference);
    }
    
    @Test
    public void testSendFaultOnNewExchange() {
        Exchange exchange = new ExchangeImpl(_domain, _dispatch);
        try {
            exchange.sendFault(exchange.createMessage());
            Assert.fail("Sending a fault on a new exchange is not allowed");
        } catch (IllegalStateException illEx) {
            return;
        }
    }
    
    @Test
    public void testPhaseIsNullOnNewExchange() {
        Exchange exchange = new ExchangeImpl(_domain, _dispatch);
        Assert.assertNull(exchange.getPhase());
    }
    
    @Test
    public void testPhaseIsInAfterInputMessage() {
        ServiceReference service = _domain.createInOnlyService(new QName("InPhase"));
        Exchange exchange = service.createExchange();
        exchange.send(exchange.createMessage());
        Assert.assertEquals(ExchangePhase.IN, exchange.getPhase());
    }
    
    @Test
    public void testPhaseIsOutAfterOutputMessage() {
        MockHandler replyHandler = new MockHandler();
        ServiceReference service = _domain.createInOutService(
                new QName("OutPhase"), new MockHandler().forwardInToOut());
        Exchange exchange = service.createExchange(replyHandler);
        exchange.send(exchange.createMessage());
        replyHandler.waitForOKMessage();
        Assert.assertEquals(ExchangePhase.OUT, exchange.getPhase());
    }
    
    @Test
    public void testPhaseIsOutAfterFaultMessage() {
        MockHandler replyHandler = new MockHandler();
        ServiceReference service = _domain.createInOutService(
                new QName("FaultPhase"), new MockHandler().forwardInToFault());
        Exchange exchange = service.createExchange(replyHandler);
        exchange.send(exchange.createMessage());
        replyHandler.waitForFaultMessage();
        Assert.assertEquals(ExchangePhase.OUT, exchange.getPhase());
    }
    
    @Test
    public void testMessageIdSetOnSend() {
        ServiceReference service = _domain.createInOnlyService(new QName("IdTest"));
        Exchange exchange = service.createExchange();
        exchange.send(exchange.createMessage());
        Assert.assertNotNull(exchange.getMessage().getContext().getProperty(Exchange.MESSAGE_ID));
    }
    
    @Test
    public void testRelatesToSetOnReply() {
        ServiceReference service = _domain.createInOutService(
            new QName("ReplyTest"), new MockHandler().forwardInToOut());
        MockHandler replyHandler = new MockHandler();
        Exchange exchange = service.createExchange(replyHandler);
        Message message = exchange.createMessage();
        exchange.send(message);

        String requestId = message.getContext().getPropertyValue(Exchange.MESSAGE_ID);
        String replyId = exchange.getMessage().getContext().getPropertyValue(Exchange.MESSAGE_ID);
        String replyRelatesTo = exchange.getMessage().getContext().getPropertyValue(Exchange.RELATES_TO);

        Assert.assertEquals(requestId, replyRelatesTo);
        Assert.assertFalse(requestId.equals(replyId));
    }

    @Test
    public void testNullSend() {
        Exchange exchange = new ExchangeImpl(_domain, _dispatch);
        try {
            exchange.send(null);
            Assert.fail("Expected IllegalArgumentException.");
        } catch (IllegalArgumentException e) {
        	boolean messageMatch = e.getMessage().contains("SWITCHYARD014019");
            Assert.assertTrue(messageMatch);
        }
    }

    @Test
    public void testNullSendFault() {
        Exchange exchange = new ExchangeImpl(_domain, _dispatch);
        try {
            exchange.sendFault(null);
            Assert.fail("Expected IllegalArgumentException.");
        } catch (IllegalArgumentException e) {
        	boolean messageMatch = e.getMessage().contains("SWITCHYARD014019");
        	Assert.assertTrue(messageMatch);
        }
    }
    
    @Test
    public void testExchangeInitiatedEvent() {
        EventCounter counter = new EventCounter();
        _domain.addEventObserver(counter, ExchangeInitiatedEvent.class);
        
        // send in-only and check the count
        ServiceReference inOnlyService = _domain.createInOnlyService(new QName("ExchangeInitiatedEvent-1"));
        Exchange inOnly = inOnlyService.createExchange();
        inOnly.send(inOnly.createMessage());
        Assert.assertEquals(1, counter.initiatedCount);
        
        // reset count
        counter.initiatedCount = 0;
        
        // send in-out and check the count
        ServiceReference inOutService = _domain.createInOutService(
                new QName("ExchangeInitiatedEvent-2"), new MockHandler().forwardInToOut());
        Exchange inOut = inOutService.createExchange(new MockHandler());
        inOut.send(inOut.createMessage());
        Assert.assertEquals(1, counter.initiatedCount);
    }

    @Test
    public void testExchangeCompletedEvent() {
        EventCounter counter = new EventCounter();
        _domain.addEventObserver(counter, ExchangeCompletionEvent.class);
        
        // send in-only and check the count
        ServiceReference inOnlyService = _domain.createInOnlyService(new QName("ExchangeCompleteEvent-1"));
        Exchange inOnly = inOnlyService.createExchange();
        inOnly.send(inOnly.createMessage());
        Assert.assertEquals(1, counter.completedCount);
        
        // reset count
        counter.completedCount = 0;
        
        // send in-out and check the count
        ServiceReference inOutService = _domain.createInOutService(
                new QName("ExchangeCompleteEvent-2"), new MockHandler().forwardInToOut());
        Exchange inOut = inOutService.createExchange(new MockHandler());
        inOut.send(inOut.createMessage());
        Assert.assertEquals(1, counter.completedCount);
    }
    
    @Test
    public void testAllExchangeEventsReceived() throws Exception {
        EventCounter counter = new EventCounter();
        _domain.addEventObserver(counter, ExchangeInitiatedEvent.class);
        _domain.addEventObserver(counter, ExchangeCompletionEvent.class);
        
        // send 10 in-only messages and check the counters
        for (int i = 0; i < 10; i++) {
            ServiceReference inOnlyService = _domain.createInOnlyService(new QName("ExchangeEvent-0" + i));
            Exchange inOnly = inOnlyService.createExchange();
            inOnly.send(inOnly.createMessage());
        }
        
        Assert.assertEquals(10, counter.initiatedCount);
        Assert.assertEquals(10, counter.completedCount);
        
        // initialize counters
        counter.initiatedCount = 0;
        counter.completedCount = 0;
        
        // send 10 in-out and check the count
        for (int i = 0; i < 10; i++) {
            ServiceReference inOutService = _domain.createInOutService(
                    new QName("ExchangeEvent-1" + i), new MockHandler().forwardInToOut());
            Exchange inOut = inOutService.createExchange(new MockHandler());
            inOut.send(inOut.createMessage());
        }
        
        Assert.assertEquals(10, counter.initiatedCount);
        Assert.assertEquals(10, counter.completedCount);
    }

    /**
     * Make sure that the current message is set correctly when an exchange
     * is sent.
     */
    @Test
    public void testGetMessage() throws Exception {
        
        final QName serviceName = new QName("bleh");
        final String inMsgContent = "in message";
        final String outMsgContent = "out message";
        
        // create a handler to test that the in and out content match
        // expected result from getMessage()
        ExchangeHandler provider = new BaseHandler() {
            public void handleMessage(Exchange exchange) {
                Assert.assertEquals(
                		exchange.getMessage().getContent(), 
                        inMsgContent);
                
                Message outMsg = exchange.createMessage();
                outMsg.setContent(outMsgContent);
                try {
                	exchange.send(outMsg);
                }
                catch (Exception ex) {
                    Assert.fail(ex.toString());
                }
            }
        };

        ExchangeHandler consumer = new BaseHandler() {
            public void handleMessage(Exchange exchange) {
                Assert.assertEquals(
                		exchange.getMessage().getContent(), 
                        outMsgContent);
            }
        };

        ServiceReference service = _domain.createInOutService(serviceName, provider);
        Exchange exchange = service.createExchange(consumer);
        Message inMsg = exchange.createMessage();
        inMsg.setContent(inMsgContent);
        exchange.send(inMsg);
    }

    @Test
    public void testExceptionOnSendOnFaultExchange() throws Exception {

        final QName serviceName = new QName("testExceptionOnSendOnFaultExchange");
        // Provide the service
        MockHandler provider = new MockHandler().forwardInToFault();
        ServiceReference service = _domain.createInOutService(serviceName, provider);

        // Consume the service
        MockHandler consumer = new MockHandler();
        Exchange exchange = service.createExchange(consumer);
        exchange.send(exchange.createMessage());

        // wait, since this is async
        provider.waitForOKMessage();
        consumer.waitForFaultMessage();

        // Now try send another message on the Exchange... should result in an IllegalStateException...
        try {
            exchange.send(exchange.createMessage());
        } catch(IllegalStateException e) {
        	boolean messageMatch = e.getMessage().contains("SWITCHYARD014020");
            Assert.assertTrue(messageMatch);
        }
    }

    @Test
    public void testExceptionOnNoConsumerOnInOut() throws Exception {

        QName serviceName = new QName("testNoNPEOnNoConsumer");
        MockHandler provider = new MockHandler() {
            @Override
            public void handleMessage(Exchange exchange) throws HandlerException {
                throw new HandlerException("explode");
            }
        };

        ServiceReference service = _domain.createInOutService(serviceName, provider);

        try {
            // Don't provide a consumer...
            service.createExchange();
            Assert.fail("Should not be able to create an InOut without specifying a reply handler");
        } catch (RuntimeException e) {
            // exception expected
        }
    }

    @Test
    public void testNoNPEOnInOnlyFault() throws Exception {

        QName serviceName = new QName("testNoNPEOnNoConsumer");
        MockHandler provider = new MockHandler() {
            @Override
            public void handleMessage(Exchange exchange) throws HandlerException {
                throw new HandlerException("explode");
            }
        };

        ServiceReference service = _domain.createInOnlyService(serviceName, provider);

        // Don't provide a consumer...
        Exchange exchange = service.createExchange();

        exchange.send(exchange.createMessage());
    }
    
    @Test
    public void testFaultWithNoHandler() throws Exception {

        final QName serviceName = new QName("testFaultWithNoHandler");
        // Provide the service
        MockHandler provider = new MockHandler() {
            @Override
            public void handleMessage(Exchange exchange) throws HandlerException {
                throw new HandlerException("Fault With No Handler!");
            }
        };
        ServiceReference service = _domain.createInOnlyService(serviceName, provider);

        // Consume the service
        Exchange exchange = service.createExchange();
        exchange.send(exchange.createMessage());

        // Make sure the exchange is in fault status
        Assert.assertEquals(ExchangeState.FAULT, exchange.getState());
    }
    
    @Test
    public void testFaultTransformSequence() throws Exception {
        final QName serviceName = new QName("testFaultTransformSequence");
        // Provide the service
        MockHandler provider = new MockHandler() {
            @Override
            public void handleMessage(Exchange exchange) throws HandlerException {
                Message fault = exchange.createMessage();
                fault.setContent(new Exception("testFaultTransformSequence"));
                exchange.sendFault(fault);
            }
        };
        InOutOperation providerContract = new InOutOperation("faultOp", 
                JavaTypes.toMessageType(String.class),   // input
                JavaTypes.toMessageType(String.class),   // output
                JavaTypes.toMessageType(Exception.class));  // fault
        InOutOperation consumerContract = new InOutOperation("faultOp", 
                JavaTypes.toMessageType(String.class),   // input
                JavaTypes.toMessageType(String.class),   // output
                JavaTypes.toMessageType(String.class));  // fault
        _domain.registerService(serviceName, new InOutService(providerContract), provider);
        _domain.getTransformerRegistry().addTransformer(new ExceptionToStringTransformer());
        ServiceReference service = _domain.registerServiceReference(serviceName, new InOutService(consumerContract));

        // Consume the service
        Exchange exchange = service.createExchange(new MockHandler());
        exchange.send(exchange.createMessage());

        // Make sure the exchange is in fault status
        Assert.assertEquals(String.class, exchange.getMessage().getContent().getClass());
        Assert.assertEquals(exchange.getMessage().getContent(), "testFaultTransformSequence");
    }
    
    class EventCounter implements EventObserver {
        int initiatedCount;
        int completedCount;
        public void notify(EventObject event) {
            if (event instanceof ExchangeInitiatedEvent) {
                ++initiatedCount;
            } else if (event instanceof ExchangeCompletionEvent) {
                ++completedCount;
            }
        }
    };

}

class ExceptionToStringTransformer extends BaseTransformer<Exception, String> {

    @Override
    public String transform(Exception from) {
        return from.getMessage();
    }
    
}
