/*
 * Copyright 2009 OW2 Chameleon
 * 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.ow2.chameleon.core;

import java.io.File;
import java.io.IOException;

import junit.framework.TestCase;

import org.mockito.Mockito;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogReaderService;
import org.osgi.service.log.LogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Log Activator unit tests.
 * @author <a href="mailto:chameleon-dev@ow2.org">Chameleon Project Team</a>
 */
public class LogActivatorTest extends TestCase {

    /**
     * Reference Id.
     */
    private static final int REFERENCE_ID = 12;

    /**
     * A log activator.
     */
    private LogActivator logger;

    /**
     * Prepares the environment.
     * Create a log activator.
     * @see junit.framework.TestCase#setUp()
     */
    public void setUp() {
        Logger log;
        try {
            log = Chameleon.initializeLoggingSystem(null, null, null, null,
                    false);
            logger = new LogActivator(log);
        } catch (IOException e) {
            fail(e.getMessage());
        }

    }

    /**
     * Cleans the environment.
     * Delete log files.
     * @see junit.framework.TestCase#tearDown()
     */
    public void tearDown() {
        File html = new File("log/chameleon-log.html");
        html.delete();
        File log = new File("log/chameleon.log");
        log.delete();
        File dir = new File("log");
        dir.delete();
    }

    /**
     * Tries to log a couple of message using different level.
     */
    public void testSLF4J() {
        Logger log = LoggerFactory.getLogger("foo");
        log.debug("debug");
        log.info("info");
        log.warn("warning");
        log.error("error");
    }

    /**
     * Checks the propagation of OSGi log entries.
     */
    public void testLogging() {
        LogEntry le1 = getLogEntry(null, "1", LogService.LOG_INFO, null, null,
                null);
        logger.logged(le1);

        LogEntry le2 = getLogEntry(null, "2", LogService.LOG_DEBUG, null, null,
                null);
        logger.logged(le2);

        LogEntry le3 = getLogEntry("bundle-3", "3", LogService.LOG_WARNING,
                null, null, null);
        logger.logged(le3);

        LogEntry le4 = getLogEntry("bundle-4", "4", LogService.LOG_ERROR,
                "Bad news", null, null);
        logger.logged(le4);

        LogEntry le5 = getLogEntry("bundle-5", "5", LogService.LOG_INFO, null,
                new Long(REFERENCE_ID), null);
        logger.logged(le5);

        LogEntry le6 = getLogEntry("bundle-6", "6", LogService.LOG_INFO, null,
                new Long(REFERENCE_ID), "pid");
        logger.logged(le6);

        LogEntry le7 = getLogEntry("bundle-7", "7", LogService.LOG_INFO, "foo",
                new Long(REFERENCE_ID), "pid");
        logger.logged(le7);

        LogEntry le8 = getLogEntry(null, "8", LogService.LOG_DEBUG,
                "debug exception", null, null);
        logger.logged(le8);

        LogEntry le9 = getLogEntry(null, "9", LogService.LOG_WARNING,
                "debug exception", null, null);
        logger.logged(le9);

        LogEntry le10 = getLogEntry(null, "10", LogService.LOG_ERROR, null,
                null, null);
        logger.logged(le10);
    }

    /**
     * Creates a log entry.
     * @param name the name
     * @param message the message
     * @param level the level
     * @param exception the exception
     * @param refid the service id.
     * @param refpid the service pid.
     * @return a mock log entry.
     */
    private LogEntry getLogEntry(String name, String message, int level,
            String exception, Long refid, String refpid) {
        LogEntry le = (LogEntry) Mockito.mock(LogEntry.class);
        Mockito.when(le.getMessage()).thenReturn(message);
        if (exception == null) {
            Mockito.when(le.getException()).thenReturn(null);
        } else {
            Mockito.when(le.getException())
                    .thenReturn(new Exception(exception));
        }
        Mockito.when(new Integer(le.getLevel())).thenReturn(new Integer(level));

        if (name == null) {
            Mockito.when(le.getBundle()).thenReturn(null);
        } else {
            Bundle bundle = (Bundle) Mockito.mock(Bundle.class);
            Mockito.when(bundle.getSymbolicName()).thenReturn(name);
            Mockito.when(le.getBundle()).thenReturn(bundle);
        }

        if (refid == null) {
            Mockito.when(le.getServiceReference()).thenReturn(null);
        } else {
            ServiceReference sr = (ServiceReference) Mockito
                    .mock(ServiceReference.class);
            Mockito.when(sr.getProperty("service.pid")).thenReturn(refpid);
            Mockito.when(sr.getProperty("service.id")).thenReturn(refid);
            Mockito.when(le.getServiceReference()).thenReturn(sr);
        }

        return le;
    }

    /**
     * Simulates the availability of the log service.
     * @throws Exception if something wrong happens.
     */
    public void testStartWithServiceAvailable() throws Exception {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);

        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        Mockito.when(bc.getServiceReference(LogReaderService.class.getName()))
                .thenReturn(ref);
        Mockito.when(bc.getService(ref)).thenReturn(reader);

        // Start the test
        logger.start(bc);

        // Verify:
        // addServiceListener called once with logger
        ((LogReaderService) Mockito.verify(reader, Mockito.times(1)))
                .addLogListener(logger);

        // Service Listener registered
        ((BundleContext) Mockito.verify(bc, Mockito.times(1)))
                .addServiceListener(logger, "(" + Constants.OBJECTCLASS + "="
                        + LogReaderService.class.getName() + ")");

        ((BundleContext) Mockito.verify(bc, Mockito.times(1)))
                .getServiceReference(LogReaderService.class.getName());
    }

    /**
     * Simulates the unavailability of the log service.
     * @throws Exception if something wrong happens.
     */
    public void testStartWithServiceUnavailable() throws Exception {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);

        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        Mockito.when(bc.getServiceReference(LogReaderService.class.getName()))
                .thenReturn(null);
        Mockito.when(bc.getService(ref)).thenReturn(reader);

        // Start the test
        logger.start(bc);

        // Verify:
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);

        // Service Listener registered
        ((BundleContext) Mockito.verify(bc, Mockito.times(1)))
                .addServiceListener(logger, "(" + Constants.OBJECTCLASS + "="
                        + LogReaderService.class.getName() + ")");

        ((BundleContext) Mockito.verify(bc, Mockito.times(1)))
                .getServiceReference(LogReaderService.class.getName());
    }

    /**
     * Simulates the availability of the log service during the
     * stop phase.
     * @throws Exception if something wrong happens.
     */
    public void testStopMethodWithService() throws Exception {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);
        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        logger.setLogServiceReference(ref);

        Mockito.when(bc.getServiceReference(LogReaderService.class.getName()))
                .thenReturn(ref);
        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.stop(bc);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);
        ((LogReaderService) Mockito.verify(reader, Mockito.times(1)))
                .removeLogListener(logger);
        ((BundleContext) Mockito.verify(bc, Mockito.times(1)))
                .removeServiceListener(logger);

    }

    /**
     * Simulates the unavailability of the log service during
     * the stop phase.
     * @throws Exception if something wrong happens.
     */
    public void testStopMethodWithoutService() throws Exception {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);
        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        logger.setLogServiceReference(null);

        Mockito.when(bc.getServiceReference(LogReaderService.class.getName()))
                .thenReturn(null);
        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.stop(bc);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .removeLogListener(logger);
        ((BundleContext) Mockito.verify(bc, Mockito.times(1)))
                .removeServiceListener(logger);
    }

    /**
     * Simulates the arrival of a log service.
     */
    public void testServiceChangedArrivalNoService() {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);
        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        // ServiceEvent
        ServiceEvent ev = (ServiceEvent) Mockito.mock(ServiceEvent.class);
        Mockito.when(ev.getServiceReference()).thenReturn(ref);
        Mockito.when(new Integer(ev.getType())).thenReturn(
                new Integer(ServiceEvent.REGISTERED));

        logger.setBundleContext(bc);
        logger.setLogServiceReference(null);

        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.serviceChanged(ev);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(1)))
                .addLogListener(logger);

    }

    /**
     * Simulates the arrival of a log service despite alread one is there.
     */
    public void testServiceChangedArrivalWithAlreadyAService() {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);
        ServiceReference ref2 = (ServiceReference) Mockito
                .mock(ServiceReference.class);

        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        // ServiceEvent
        ServiceEvent ev = (ServiceEvent) Mockito.mock(ServiceEvent.class);
        Mockito.when(ev.getServiceReference()).thenReturn(ref2);
        Mockito.when(new Integer(ev.getType())).thenReturn(
                new Integer(ServiceEvent.REGISTERED));

        logger.setBundleContext(bc);
        logger.setLogServiceReference(ref);

        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.serviceChanged(ev);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);

    }

    /**
     * Simulates the departure of the used log service.
     */
    public void testServiceChangedDepartureWithTheUsedService() {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);

        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        // ServiceEvent
        ServiceEvent ev = (ServiceEvent) Mockito.mock(ServiceEvent.class);
        Mockito.when(ev.getServiceReference()).thenReturn(ref);
        Mockito.when(new Integer(ev.getType())).thenReturn(
                new Integer(ServiceEvent.UNREGISTERING));

        logger.setBundleContext(bc);
        logger.setLogServiceReference(ref);

        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.serviceChanged(ev);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);
        ((LogReaderService) Mockito.verify(reader, Mockito.times(1)))
                .removeLogListener(logger);
    }

    /**
     * Simulates the departure of a not used log service.
     */
    public void testServiceChangedDepartureWithAnotherService() {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);
        ServiceReference ref2 = (ServiceReference) Mockito
                .mock(ServiceReference.class);

        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        // ServiceEvent
        ServiceEvent ev = (ServiceEvent) Mockito.mock(ServiceEvent.class);
        Mockito.when(ev.getServiceReference()).thenReturn(ref2);
        Mockito.when(new Integer(ev.getType())).thenReturn(
                new Integer(ServiceEvent.UNREGISTERING));

        logger.setBundleContext(bc);
        logger.setLogServiceReference(ref);

        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.serviceChanged(ev);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .removeLogListener(logger);
    }

    /**
     * Simulates the departure of a log service despite no are used.
     */
    public void testServiceChangedDepartureWithNoService() {
        BundleContext bc = (BundleContext) Mockito.mock(BundleContext.class);
        ServiceReference ref = (ServiceReference) Mockito
                .mock(ServiceReference.class);

        LogReaderService reader = (LogReaderService) Mockito
                .mock(LogReaderService.class);

        // ServiceEvent
        ServiceEvent ev = (ServiceEvent) Mockito.mock(ServiceEvent.class);
        Mockito.when(ev.getServiceReference()).thenReturn(ref);
        Mockito.when(new Integer(ev.getType())).thenReturn(
                new Integer(ServiceEvent.UNREGISTERING));

        logger.setBundleContext(bc);
        logger.setLogServiceReference(null);

        Mockito.when(bc.getService(ref)).thenReturn(reader);

        logger.serviceChanged(ev);

        // Verify
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .addLogListener(logger);
        ((LogReaderService) Mockito.verify(reader, Mockito.times(0)))
                .removeLogListener(logger);
    }

}
