/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.component.cxf.transport;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.namespace.QName;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Holder;
import javax.xml.ws.WebServiceException;

import org.apache.camel.CamelContext;
import org.apache.camel.NoSuchEndpointException;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.cxf.transport.CamelDestination.ConsumerProcessor;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.DefaultExchange;
import org.apache.camel.wsdl_first.Person;
import org.apache.camel.wsdl_first.UnknownPersonFault;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.transport.ConduitInitiator;
import org.apache.cxf.transport.ConduitInitiatorManager;
import org.apache.cxf.transport.MessageObserver;
import org.easymock.EasyMock;
import org.junit.Test;

public class CamelDestinationTest extends CamelTransportTestSupport {
    private Message destMessage;

    protected RouteBuilder createRouteBuilder() {
        return new RouteBuilder() {
            public void configure() {
                onException(RuntimeCamelException.class).handled(true).to("mock:error");
                from("direct:Producer").to("direct:EndpointA");
            }
        };
    }

    protected CamelContext createCamelContext() throws Exception {
        return new DefaultCamelContext();
    }

    @Test
    public void testCamelDestinationConfiguration() throws Exception {
        QName testEndpointQName = new QName("http://camel.apache.org/camel-test", "port");
        // set up the bus with configure file
        SpringBusFactory bf = new SpringBusFactory();
        BusFactory.setDefaultBus(null);
        Bus bus = bf.createBus("/org/apache/camel/component/cxf/transport/CamelDestination.xml");
        BusFactory.setDefaultBus(bus);

        endpointInfo.setAddress("camel://direct:EndpointA");
        endpointInfo.setName(testEndpointQName);
        CamelDestination destination = new CamelDestination(null, bus, null, endpointInfo);

        assertEquals("{http://camel.apache.org/camel-test}port.camel-destination", destination.getBeanName());
        CamelContext context = destination.getCamelContext();

        assertNotNull("The camel context which get from camel destination is not null", context);
        assertEquals("Get the wrong camel context", context.getName(), "dest_context");
        assertEquals("The camel context should has two routers", context.getRoutes().size(), 2);
        bus.shutdown(false);
    }

    public CamelDestination setupCamelDestination(EndpointInfo endpointInfo, boolean send) throws IOException {
        ConduitInitiator conduitInitiator = EasyMock.createMock(ConduitInitiator.class);
        CamelDestination camelDestination = new CamelDestination(context, bus, conduitInitiator, endpointInfo);
        if (send) {
            // setMessageObserver
            observer = new MessageObserver() {
                public void onMessage(Message m) {
                    Exchange exchange = new ExchangeImpl();
                    exchange.setInMessage(m);
                    m.setExchange(exchange);
                    destMessage = m;
                }
            };
            camelDestination.setMessageObserver(observer);
        }
        return camelDestination;
    }

    @Test
    public void testGetTransportFactoryFromBus() throws Exception {
        Bus bus = BusFactory.getDefaultBus();
        assertNotNull(bus.getExtension(ConduitInitiatorManager.class)
            .getConduitInitiator(CamelTransportFactory.TRANSPORT_ID));
    }

    @Test
    public void testOneWayDestination() throws Exception {
        destMessage = null;
        inMessage = null;
        EndpointInfo conduitEpInfo = new EndpointInfo();
        conduitEpInfo.setAddress("camel://direct:Producer");
        CamelConduit conduit = setupCamelConduit(conduitEpInfo, true, false);
        Message outMessage = new MessageImpl();
        CamelDestination destination = null;
        try {
            endpointInfo.setAddress("camel://direct:EndpointA");
            destination = setupCamelDestination(endpointInfo, true);
            // destination.activate();
        } catch (IOException e) {
            assertFalse("The CamelDestination activate should not through exception ", false);
            e.printStackTrace();
        }
        sendoutMessage(conduit, outMessage, true, "HelloWorld");

        // just verify the Destination inMessage
        assertTrue("The destiantion should have got the message ", destMessage != null);
        verifyReceivedMessage(destMessage, "HelloWorld");
        destination.shutdown();
    }



    private void verifyReceivedMessage(Message inMessage, String content) throws IOException {
        ByteArrayInputStream bis = (ByteArrayInputStream)inMessage.getContent(InputStream.class);
        byte bytes[] = new byte[bis.available()];
        bis.read(bytes);
        String reponse = new String(bytes);
        assertEquals("The reponse date should be equals", content, reponse);
    }
    
    @Test
    public void testRoundTripDestination() throws Exception {

        inMessage = null;
        EndpointInfo conduitEpInfo = new EndpointInfo();
        conduitEpInfo.setAddress("camel://direct:Producer");
        // set up the conduit send to be true
        CamelConduit conduit = setupCamelConduit(conduitEpInfo, true, false);
        final Message outMessage = new MessageImpl();

        endpointInfo.setAddress("camel://direct:EndpointA");
        final CamelDestination destination = setupCamelDestination(endpointInfo, true);

        // set up MessageObserver for handling the conduit message
        MessageObserver observer = new MessageObserver() {
            public void onMessage(Message m) {
                try {
                    Exchange exchange = new ExchangeImpl();
                    exchange.setInMessage(m);
                    m.setExchange(exchange);
                    verifyReceivedMessage(m, "HelloWorld");
                    //verifyHeaders(m, outMessage);
                    // setup the message for
                    Conduit backConduit;
                    backConduit = getBackChannel(destination, m);
                    // wait for the message to be got from the conduit
                    Message replyMessage = new MessageImpl();
                    sendoutMessage(backConduit, replyMessage, true, "HelloWorld Response");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };
        MockEndpoint error = context.getEndpoint("mock:error", MockEndpoint.class);
        error.expectedMessageCount(0);
        //this call will active the camelDestination
        destination.setMessageObserver(observer);
        // set is one way false for get response from destination
        // need to use another thread to send the request message
        sendoutMessage(conduit, outMessage, false, "HelloWorld");
        // wait for the message to be got from the destination,
        // create the thread to handler the Destination incoming message

        verifyReceivedMessage(inMessage, "HelloWorld Response");
        error.assertIsSatisfied();
        destination.shutdown();
    }
    
    @Test
    public void testRoundTripDestinationWithFault() throws Exception {

        inMessage = null;
        EndpointInfo conduitEpInfo = new EndpointInfo();
        conduitEpInfo.setAddress("camel://direct:Producer");
        // set up the conduit send to be true
        CamelConduit conduit = setupCamelConduit(conduitEpInfo, true, false);
        final Message outMessage = new MessageImpl();

        endpointInfo.setAddress("camel://direct:EndpointA");
        final CamelDestination destination = setupCamelDestination(endpointInfo, true);
        destination.setCheckException(true);

        // set up MessageObserver for handling the conduit message
        MessageObserver observer = new MessageObserver() {
            public void onMessage(Message m) {
                try {
                    Exchange exchange = new ExchangeImpl();
                    exchange.setInMessage(m);
                    m.setExchange(exchange);
                    verifyReceivedMessage(m, "HelloWorld");
                    //verifyHeaders(m, outMessage);
                    // setup the message for
                    Conduit backConduit;
                    backConduit = getBackChannel(destination, m);
                    // wait for the message to be got from the conduit
                    Message replyMessage = new MessageImpl();
                    replyMessage.setContent(Exception.class, new RuntimeCamelException());
                    sendoutMessage(backConduit, replyMessage, true, "HelloWorld Fault");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };
        
        MockEndpoint error = context.getEndpoint("mock:error", MockEndpoint.class);
        error.expectedMessageCount(1);
        
        //this call will active the camelDestination
        destination.setMessageObserver(observer);
        // set is one way false for get response from destination
        // need to use another thread to send the request message
        sendoutMessage(conduit, outMessage, false, "HelloWorld");
        // wait for the message to be got from the destination,
        // create the thread to handler the Destination incoming message

        verifyReceivedMessage(inMessage, "HelloWorld Fault");
        error.assertIsSatisfied();
        
        destination.shutdown();
    }
    
    private Conduit getBackChannel(CamelDestination destination, Message m) throws IOException {
        return destination.getInbuiltBackChannel(m);
    }
    
    @Test
    public void testExceptionForwardedToExchange() throws IOException {
        final RuntimeException expectedException = new RuntimeException("We simulate an exception in CXF processing");
        
        DefaultCamelContext camelContext = new DefaultCamelContext();
        CamelDestination dest = EasyMock.createMock(CamelDestination.class);
        dest.incoming(EasyMock.isA(org.apache.camel.Exchange.class));
        EasyMock.expectLastCall().andThrow(expectedException);
        EasyMock.replay(dest);
        ConsumerProcessor consumerProcessor = dest.new ConsumerProcessor();
        
        // Send our dummy exchange and check that the exception that occurred on incoming is set
        DefaultExchange exchange = new DefaultExchange(camelContext);
        consumerProcessor.process(exchange);
        Exception exc = exchange.getException();
        assertNotNull(exc);
        assertEquals(expectedException, exc);
        EasyMock.verify(dest);
    }
    
    @Test
    public void testCAMEL4073() throws Exception {
        try {
            Endpoint.publish("camel://foo", new Person() {
                public void getPerson(Holder<String> personId, Holder<String> ssn, Holder<String> name)
                    throws UnknownPersonFault {
                }
            });
            fail("Should throw and Exception");
        } catch (WebServiceException ex) {
            Throwable c = ex.getCause();
            assertNotNull(c);
            assertTrue(c instanceof NoSuchEndpointException);
        }
    }

}
