//
//  ========================================================================
//  Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.osgi.test;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;

/**
 * Helper methods for pax-exam tests
 */
public class TestOSGiUtil
{

    protected static Bundle getBundle(BundleContext bundleContext, String symbolicName)
    {
            Map<String,Bundle> _bundles = new HashMap<String, Bundle>();
            for (Bundle b : bundleContext.getBundles())
            {
                Bundle prevBundle = _bundles.put(b.getSymbolicName(), b);
                String err = prevBundle != null ? "2 versions of the bundle " + b.getSymbolicName()
                                                + " "
                                                + b.getHeaders().get("Bundle-Version")
                                                + " and "
                                                + prevBundle.getHeaders().get("Bundle-Version") : "";
                                                Assert.assertNull(err, prevBundle);
            }
        return _bundles.get(symbolicName);
    }

    protected static void assertActiveBundle(BundleContext bundleContext, String symbolicName) throws Exception
    {
        Bundle b = getBundle(bundleContext, symbolicName);
        Assert.assertNotNull(b);
        Assert.assertEquals(b.getSymbolicName() + " must be active.", Bundle.ACTIVE, b.getState());
    }

    protected static void assertActiveOrResolvedBundle(BundleContext bundleContext, String symbolicName) throws Exception
    {
        Bundle b = getBundle(bundleContext, symbolicName);
        Assert.assertNotNull(b);
        if (b.getHeaders().get("Fragment-Host") == null) diagnoseNonActiveOrNonResolvedBundle(b);
        Assert.assertTrue(b.getSymbolicName() + " must be active or resolved. It was " + b.getState(),
                          b.getState() == Bundle.ACTIVE || b.getState() == Bundle.RESOLVED);
    }

    protected static void assertAllBundlesActiveOrResolved(BundleContext bundleContext)
    {
        for (Bundle b : bundleContext.getBundles())
        {
            if (b.getState() == Bundle.INSTALLED)
            {
                diagnoseNonActiveOrNonResolvedBundle(b);
            }
            Assert.assertTrue("Bundle: " + b
                              + " (state should be "
                              + "ACTIVE["
                              + Bundle.ACTIVE
                              + "] or RESOLVED["
                              + Bundle.RESOLVED
                              + "]"
                              + ", but was ["
                              + b.getState()
                              + "])", (b.getState() == Bundle.ACTIVE) || (b.getState() == Bundle.RESOLVED));
        }
    }

    protected static boolean diagnoseNonActiveOrNonResolvedBundle(Bundle b)
    {
        if (b.getState() != Bundle.ACTIVE && b.getHeaders().get("Fragment-Host") == null)
        {
            try
            {
                System.err.println("Trying to start the bundle " + b.getSymbolicName() + " that was supposed to be active or resolved.");
                b.start();
                System.err.println(b.getSymbolicName() + " did start");
                return true;
            }
            catch (Throwable t)
            {
                System.err.println(b.getSymbolicName() + " failed to start");
                t.printStackTrace(System.err);
                return false;
            }
        }
        System.err.println(b.getSymbolicName() + " was already started");
        return false;
    }

    protected static void debugBundles(BundleContext bundleContext)
    {
        Map<String, Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
        System.err.println("Active " + Bundle.ACTIVE);
        System.err.println("RESOLVED " + Bundle.RESOLVED);
        System.err.println("INSTALLED " + Bundle.INSTALLED);
        for (Bundle b : bundleContext.getBundles())
        {
            bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
            System.err.println("    " + b.getSymbolicName() + " " + b.getLocation() + " " + b.getVersion()+ " " + b.getState());
        }
    }
   
    protected static ServiceReference[] getServices (String service, BundleContext bundleContext) throws Exception
    {
       return bundleContext.getAllServiceReferences(service, null);
    }

    protected static SslContextFactory newSslContextFactory()
    {
        SslContextFactory sslContextFactory = new SslContextFactory(true);
        sslContextFactory.setEndpointIdentificationAlgorithm("");
        return sslContextFactory;
    }

    protected static void testHttpServiceGreetings(BundleContext bundleContext, String protocol, int port) throws Exception
    {
        assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.boot");

        assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.httpservice");
        assertActiveBundle(bundleContext, "org.eclipse.equinox.http.servlet");

        // in the OSGi world this would be bad code and we should use a bundle
        // tracker.
        // here we purposely want to make sure that the httpService is actually
        // ready.
        ServiceReference sr = bundleContext.getServiceReference(HttpService.class.getName());
        Assert.assertNotNull("The httpServiceOSGiBundle is started and should " + "have deployed a service reference for HttpService", sr);
        HttpService http = (HttpService) bundleContext.getService(sr);
        http.registerServlet("/greetings", new HttpServlet()
        {
            private static final long serialVersionUID = 1L;

            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
            {
                resp.getWriter().write("Hello");
            }
        }, null, null);

        // now test the servlet
        HttpClient client = protocol.equals("https") ? new HttpClient(newSslContextFactory()) : new HttpClient();
        try
        {
            client.start();
            ContentResponse response = client.GET(protocol + "://127.0.0.1:" + port + "/greetings");
            Assert.assertEquals(HttpStatus.OK_200, response.getStatus());

            String content = new String(response.getContent());
            Assert.assertEquals("Hello", content);
        }
        finally
        {
            client.stop();
        }
    }

    public static int findFreePort(String systemProperty)
    {
        String freeport = System.getProperty(systemProperty);
        if (freeport!=null)
            return Integer.valueOf(freeport);
        
        try (ServerSocket socket = new ServerSocket(0))
        {
            socket.setReuseAddress(true);
            int port = socket.getLocalPort();
            System.setProperty(systemProperty,Integer.toString(port));
            return port;
        }
        catch (IOException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    
    public static void main(String... args)
    {
        int freeport = TestOSGiUtil.findFreePort("test");
        System.err.println("Found Free port="+freeport);

        
        try (ServerSocket socket = new ServerSocket(TestOSGiUtil.findFreePort("test")))
        {
            System.err.println("reused port="+socket.getLocalPort());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        
    }
}
