/*
 *  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.vysper.xmpp.extension.xep0124;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.createControl;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;

import java.io.IOException;
import java.util.Collections;

import javax.servlet.http.HttpServletRequest;

import org.apache.vysper.xml.fragment.XMLElement;
import org.apache.vysper.xmpp.addressing.EntityImpl;
import org.apache.vysper.xmpp.authorization.SASLMechanism;
import org.apache.vysper.xmpp.protocol.NamespaceURIs;
import org.apache.vysper.xmpp.protocol.SessionStateHolder;
import org.apache.vysper.xmpp.protocol.StanzaProcessor;
import org.apache.vysper.xmpp.server.ServerFeatures;
import org.apache.vysper.xmpp.server.ServerRuntimeContext;
import org.apache.vysper.xmpp.server.SessionContext;
import org.apache.vysper.xmpp.stanza.Stanza;
import org.apache.vysper.xmpp.stanza.StanzaBuilder;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationListener;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.SAXException;

public class BoshHandlerTest {

    private IMocksControl mocksControl;

    private ServerRuntimeContext serverRuntimeContext;

    private BoshHandler boshHandler;

    @Before
    public void setUp() throws Exception {
        mocksControl = createControl();
        serverRuntimeContext = mocksControl.createMock(ServerRuntimeContext.class);
        boshHandler = new BoshHandler();
        boshHandler.setServerRuntimeContext(serverRuntimeContext);
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testProcess() throws IOException, SAXException {
        // test session creation
        HttpServletRequest httpServletRequest = mocksControl.createMock(HttpServletRequest.class);
        expect(serverRuntimeContext.getNextSessionId()).andReturn("200");
        expect(serverRuntimeContext.getServerEnitity()).andReturn(new EntityImpl(null, "vysper.org", null));
        expect(serverRuntimeContext.getDefaultXMLLang()).andReturn("en");
        Continuation continuation = mocksControl.createMock(Continuation.class);
        expect(httpServletRequest.getAttribute(Continuation.ATTRIBUTE)).andReturn(continuation);
        expectLastCall().atLeastOnce();
        continuation.setTimeout(anyLong());
        Capture<BoshRequest> br = new Capture<BoshRequest>();
        continuation.setAttribute(eq("request"), EasyMock.<BoshRequest> capture(br));
        continuation.addContinuationListener(EasyMock.<ContinuationListener> anyObject());
        continuation.suspend();

        ServerFeatures serverFeatures = mocksControl.createMock(ServerFeatures.class);
        expect(serverRuntimeContext.getServerFeatures()).andReturn(serverFeatures);
        expect(serverFeatures.getAuthenticationMethods()).andReturn(Collections.<SASLMechanism> emptyList());

        Capture<BoshResponse> captured = new Capture<BoshResponse>();
        continuation.setAttribute(eq("response"), EasyMock.<BoshResponse> capture(captured));
        continuation.resume();
        mocksControl.replay();

        Stanza boshRequest = createSessionRequest();
        boshHandler.process(httpServletRequest, boshRequest);
        mocksControl.verify();
        
        assertEquals(httpServletRequest, br.getValue().getHttpServletRequest());
        assertEquals(boshRequest, br.getValue().getBody());

        Stanza response = new XMLUtil(new String(captured.getValue().getContent())).parse();
        assertNotNull(response);
        assertEquals("body", response.getName());
        assertEquals(NamespaceURIs.XEP0124_BOSH, response.getNamespaceURI());
        assertEquals("200", response.getAttributeValue("sid"));
        assertEquals("vysper.org", response.getAttributeValue("from"));
        assertEquals("60", response.getAttributeValue("wait"));
        assertEquals("1", response.getAttributeValue("hold"));
        assertEquals("1.9", response.getAttributeValue("ver"));
        assertEquals(1, response.getInnerElements().size());
        XMLElement streamFeatures = response.getInnerElements().get(0);
        assertEquals("features", streamFeatures.getName());
        assertEquals(NamespaceURIs.HTTP_ETHERX_JABBER_ORG_STREAMS, streamFeatures.getNamespaceURI());
        assertEquals(1, streamFeatures.getInnerElements().size());
        XMLElement saslMechanisms = streamFeatures.getInnerElements().get(0);
        assertEquals("mechanisms", saslMechanisms.getName());
        assertEquals(NamespaceURIs.URN_IETF_PARAMS_XML_NS_XMPP_SASL, saslMechanisms.getNamespaceURI());

        // test session retrieval, retrieves the session above identified by sid=200
        mocksControl.reset();
        expect(httpServletRequest.getAttribute(Continuation.ATTRIBUTE)).andReturn(continuation);
        expectLastCall().atLeastOnce();
        continuation.setTimeout(anyLong());
        continuation.suspend();
        continuation.setAttribute(eq("request"), EasyMock.<BoshRequest> capture(br));
        continuation.addContinuationListener(EasyMock.<ContinuationListener> anyObject());
        StanzaProcessor stanzaProcessor = mocksControl.createMock(StanzaProcessor.class);
        expect(serverRuntimeContext.getStanzaProcessor()).andReturn(stanzaProcessor);
        Capture<Stanza> stanzaCaptured = new Capture<Stanza>();
        stanzaProcessor.processStanza(eq(serverRuntimeContext), EasyMock.<SessionContext> anyObject(),
                EasyMock.<Stanza> capture(stanzaCaptured), EasyMock.<SessionStateHolder> anyObject());
        mocksControl.replay();
        boshRequest = createSaslRequest();
        boshHandler.process(httpServletRequest, boshRequest);
        mocksControl.verify();
        
        assertEquals(httpServletRequest, br.getValue().getHttpServletRequest());
        assertEquals(boshRequest, br.getValue().getBody());
        
        Stanza stanza = stanzaCaptured.getValue();
        assertNotNull(stanza);
        assertEquals("auth", stanza.getName());
        assertEquals(NamespaceURIs.URN_IETF_PARAMS_XML_NS_XMPP_SASL, stanza.getNamespaceURI());
        assertEquals("DIGEST-MD5", stanza.getAttributeValue("mechanism"));
    }

    @Test
    public void testGetEmptyResponse() {
        Stanza response = boshHandler.getEmptyResponse();
        assertNotNull(response);
        assertEquals("body", response.getName());
        assertEquals(NamespaceURIs.XEP0124_BOSH, response.getNamespaceURI());
        assertEquals(0, response.getAttributes().size());
        assertEquals(0, response.getInnerElements().size());
    }

    @Test
    public void testWrapStanza() {
        StanzaBuilder stanzaBuilder = new StanzaBuilder("iq", NamespaceURIs.JABBER_CLIENT);
        Stanza stanza = stanzaBuilder.build();
        Stanza body = boshHandler.wrapStanza(stanza);
        assertNotNull(body);
        assertEquals("body", body.getName());
        assertEquals(NamespaceURIs.XEP0124_BOSH, body.getNamespaceURI());
        assertEquals(1, body.getInnerElements().size());
        assertEquals("iq", body.getInnerElements().get(0).getName());
        assertEquals(NamespaceURIs.JABBER_CLIENT, body.getInnerElements().get(0).getNamespaceURI());
    }

    @Test
    public void testMergeResponses() {
        Stanza response1 = createPingStanzaResponse("vysper.org", "user1@vysper.org/resource", "100");
        Stanza response2 = createPingStanzaResponse("vysper.org", "user1@vysper.org/resource", "101");
        assertEquals(response1, boshHandler.mergeResponses(response1, null));
        assertEquals(response1, boshHandler.mergeResponses(null, response1));
        Stanza merged = boshHandler.mergeResponses(response1, response2);
        assertNotNull(merged);
        assertEquals("body", merged.getName());
        assertEquals(NamespaceURIs.XEP0124_BOSH, merged.getNamespaceURI());
        assertEquals(2, merged.getInnerElements().size());
        assertEquals(response1.getInnerElements().get(0), merged.getInnerElements().get(0));
        assertEquals(response2.getInnerElements().get(0), merged.getInnerElements().get(1));
    }

    private Stanza createSessionRequest() {
        StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
        body.addAttribute("rid", "100");
        body.addAttribute("to", "vysper.org");
        body.addAttribute(NamespaceURIs.XML, "lang", "en");
        body.addAttribute("wait", "60");
        body.addAttribute("hold", "1");
        body.addAttribute("ver", "1.9");
        body.addAttribute(NamespaceURIs.URN_XMPP_XBOSH, "version", "1.0");
        return body.build();
    }

    private Stanza createSaslRequest() {
        StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
        body.addAttribute("rid", "101");
        body.addAttribute("sid", "200");
        body.startInnerElement("auth", NamespaceURIs.URN_IETF_PARAMS_XML_NS_XMPP_SASL)
                .addAttribute("mechanism", "DIGEST-MD5").endInnerElement();
        return body.build();
    }

    private static Stanza createPingStanzaResponse(String from, String to, String id) {
        StanzaBuilder body = new StanzaBuilder("body", NamespaceURIs.XEP0124_BOSH);
        body.startInnerElement("iq", NamespaceURIs.JABBER_CLIENT).addAttribute("from", from)
                .addAttribute("type", "result").addAttribute("to", to).addAttribute("id", id);
        body.endInnerElement();
        return body.build();
    }

}