/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * 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 net.java.sip.communicator.impl.neomedia;

import java.util.*;

import net.java.sip.communicator.impl.neomedia.codec.video.h264.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.notification.*;
import net.java.sip.communicator.service.resources.*;
import net.java.sip.communicator.util.*;

import org.jitsi.impl.neomedia.*;
import org.jitsi.service.audionotifier.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.fileaccess.*;
import org.jitsi.service.libjitsi.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.packetlogging.*;
import org.jitsi.service.resources.*;
import org.osgi.framework.*;

/**
 * Implements <tt>BundleActivator</tt> for the neomedia bundle.
 *
 * @author Martin Andre
 * @author Emil Ivov
 * @author Lyubomir Marinov
 * @author Boris Grozev
 */
public class NeomediaActivator
    implements BundleActivator
{

    /**
     * Indicates if the audio configuration form should be disabled, i.e.
     * not visible to the user.
     */
    private static final String AUDIO_CONFIG_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.AUDIO_CONFIG_DISABLED";

    /**
     *  The audio configuration form used to define the capture/notify/playback
     *  audio devices.
     */
    private static ConfigurationForm audioConfigurationForm;

    /**
     * The audio notifier service.
     */
    private static AudioNotifierService audioNotifierService;

    /**
     * The context in which the one and only <tt>NeomediaActivator</tt> instance
     * has started executing.
     */
    private static BundleContext bundleContext;

    /**
     * Indicates if the call recording config form should be disabled, i.e.
     * not visible to the user.
     */
    private static final String CALL_RECORDING_CONFIG_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.callrecordingconfig.DISABLED";

    /**
     * The <tt>ConfigurationService</tt> registered in {@link #bundleContext}
     * and used by the <tt>NeomediaActivator</tt> instance to read and write
     * configuration properties.
     */
    private static ConfigurationService configurationService;

    /**
     * The name of the notification pop-up event displayed when the device
     * configration has changed.
     */
    public static final String DEVICE_CONFIGURATION_HAS_CHANGED
        = "DeviceConfigurationChanged";

    /**
     * The <tt>FileAccessService</tt> registered in {@link #bundleContext} and
     * used by the <tt>NeomediaActivator</tt> instance to safely access files.
     */
    private static FileAccessService fileAccessService;

    /**
     * Indicates if the H.264 configuration form should be disabled, i.e.
     * not visible to the user.
     */
    private static final String H264_CONFIG_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.h264config.DISABLED";

    /**
     * A {@link MediaConfigurationService} instance.
     */
    private static MediaConfigurationImpl mediaConfiguration;

    /**
     * The one and only <tt>MediaServiceImpl</tt> instance registered in
     * {@link #bundleContext} by the <tt>NeomediaActivator</tt> instance.
     */
    private static MediaServiceImpl mediaServiceImpl;

    /**
     * The name of the notification pop-up event displayed when a new device
     * is selected (for audio in, audio out or notifications).
     */
    public static final String NEW_SELECTED_DEVICE
        = "NewSelectedDevice";

    /**
     * The notifcation service to pop-up messages.
     */
    private static NotificationService notificationService;

    /**
     * The OSGi <tt>PacketLoggingService</tt> of {@link #mediaServiceImpl} in
     * {@link #bundleContext} and used for debugging.
     */
    private static PacketLoggingService packetLoggingService  = null;

    /**
     * The <tt>ResourceManagementService</tt> registered in
     * {@link #bundleContext} and representing the resources such as
     * internationalized and localized text and images used by the neomedia
     * bundle.
     */
    private static ResourceManagementService resources;

    /**
     * Indicates if the video configuration form should be disabled, i.e.
     * not visible to the user.
     */
    private static final String VIDEO_CONFIG_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.VIDEO_CONFIG_DISABLED";

    /**
     *  A listener to the click on the popup message concerning video device
     *  configuration changes.
     *  Disabled until video hotplug is not available.
     */
    //private VideoDeviceConfigurationListener
    //    videoDeviceConfigurationPropertyChangeListener;

    /**
     *  The video configuration form.
     */
    private static ConfigurationForm videoConfigurationForm;

    /**
     * Indicates if the ZRTP configuration form should be disabled, i.e.
     * not visible to the user.
     */
    private static final String ZRTP_CONFIG_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.zrtpconfig.DISABLED";

    /**
     *  Returns the audio configuration form used to define the
     *  capture/notify/playback audio devices.
     *
     *  @return The audio configuration form used to define the
     *  capture/notify/playback audio devices.
     */
    public static ConfigurationForm getAudioConfigurationForm()
    {
        return audioConfigurationForm;
    }

    /**
     * Returns the <tt>AudioService</tt> obtained from the bundle
     * context.
     * @return the <tt>AudioService</tt> obtained from the bundle
     * context
     */
    public static AudioNotifierService getAudioNotifierService()
    {
        if(audioNotifierService == null)
        {
            audioNotifierService
                = ServiceUtils.getService(
                        bundleContext,
                        AudioNotifierService.class);
        }
        return audioNotifierService;
    }

    /**
     * Returns the context in which the one and only <tt>NeomediaActivator</tt>
     * instance has started executing.
     *
     * @return The context in which the one and only <tt>NeomediaActivator</tt>
     * instance has started executing.
     */
    public static BundleContext getBundleContext()
    {
        return bundleContext;
    }

    /**
     * Returns a reference to a ConfigurationService implementation currently
     * registered in the bundle context or null if no such implementation was
     * found.
     *
     * @return a currently valid implementation of the ConfigurationService.
     */
    public static ConfigurationService getConfigurationService()
    {
        if (configurationService == null)
        {
            configurationService
                = ServiceUtils.getService(
                        bundleContext,
                        ConfigurationService.class);
        }
        return configurationService;
    }

    /**
     * Returns a reference to a FileAccessService implementation
     * currently registered in the bundle context or null if no such
     * implementation was found.
     *
     * @return a currently valid implementation of the
     * FileAccessService .
     */
    public static FileAccessService getFileAccessService()
    {
        if (fileAccessService == null)
        {
            fileAccessService
                = ServiceUtils.getService(
                        bundleContext,
                        FileAccessService.class);
        }
        return fileAccessService;
    }

    public static MediaConfigurationService getMediaConfiguration()
    {
        return mediaConfiguration;
    }

    /**
     * Gets the <tt>MediaService</tt> implementation instance registered by the
     * neomedia bundle.
     *
     * @return the <tt>MediaService</tt> implementation instance registered by
     * the neomedia bundle
     */
    public static MediaServiceImpl getMediaServiceImpl()
    {
        return mediaServiceImpl;
    }

    /**
     * Returns the <tt>NotificationService</tt> obtained from the bundle
     * context.
     *
     * @return The <tt>NotificationService</tt> obtained from the bundle
     * context.
     */
    public static NotificationService getNotificationService()
    {
        if(notificationService == null)
        {
            // Get the notification service implementation
            ServiceReference notifReference = bundleContext
                .getServiceReference(NotificationService.class.getName());

            notificationService = (NotificationService) bundleContext
                .getService(notifReference);

            if(notificationService != null)
            {
                // Register a popup message for a device configuration changed
                // notification.
                notificationService.registerDefaultNotificationForEvent(
                        DEVICE_CONFIGURATION_HAS_CHANGED,
                        net.java.sip.communicator.service.notification.NotificationAction.ACTION_POPUP_MESSAGE,
                        "Device configuration has changed",
                        null);

                // Register a popup message for a new device selected for audio
                // in, audio out or notifications.
                notificationService.registerDefaultNotificationForEvent(
                        NEW_SELECTED_DEVICE,
                        net.java.sip.communicator.service.notification.NotificationAction.ACTION_POPUP_MESSAGE,
                        "New selected device",
                        null);
            }
        }

        return notificationService;
    }

    /**
     * Returns a reference to the <tt>PacketLoggingService</tt> implementation
     * currently registered in the bundle context or null if no such
     * implementation was found.
     *
     * @return a reference to a <tt>PacketLoggingService</tt> implementation
     * currently registered in the bundle context or null if no such
     * implementation was found.
     */
    public static PacketLoggingService getPacketLogging()
    {
        if (packetLoggingService == null)
        {
            packetLoggingService
                = ServiceUtils.getService(
                        bundleContext,
                        PacketLoggingService.class);
        }
        return packetLoggingService;
    }

    /**
     * Gets the <tt>ResourceManagementService</tt> instance which represents the
     * resources such as internationalized and localized text and images used by
     * the neomedia bundle.
     *
     * @return the <tt>ResourceManagementService</tt> instance which represents
     * the resources such as internationalized and localized text and images
     * used by the neomedia bundle
     */
    public static ResourceManagementService getResources()
    {
        if (resources == null)
        {
            resources
                = ResourceManagementServiceUtils.getService(bundleContext);
        }
        return resources;
    }

    /**
     *  Returns the video configuration form.
     *
     *  @return The video configuration form.
     */
    public static ConfigurationForm getVideoConfigurationForm()
    {
        return videoConfigurationForm;
    }

    /**
     *  A listener to the click on the popup message concerning audio device
     *  configuration changes.
     */
    private AudioDeviceConfigurationListener
        audioDeviceConfigurationPropertyChangeListener;

    /**
     * The <tt>Logger</tt> used by the <tt>NeomediaActivator</tt> class and its
     * instances for logging output.
     */
    private final Logger logger = Logger.getLogger(NeomediaActivator.class);

    /**
     * Starts the execution of the neomedia bundle in the specified context.
     *
     * @param bundleContext the context in which the neomedia bundle is to start
     * executing
     * @throws Exception if an error occurs while starting the execution of the
     * neomedia bundle in the specified context
     */
    public void start(BundleContext bundleContext)
        throws Exception
    {
        if (logger.isDebugEnabled())
            logger.debug("Started.");

        NeomediaActivator.bundleContext = bundleContext;

        // MediaService
        mediaServiceImpl = (MediaServiceImpl) LibJitsi.getMediaService();

        bundleContext.registerService(
                MediaService.class.getName(),
                mediaServiceImpl,
                null);
        if (logger.isDebugEnabled())
            logger.debug("Media Service ... [REGISTERED]");

        mediaConfiguration = new MediaConfigurationImpl();
        bundleContext.registerService(
                MediaConfigurationService.class.getName(),
                getMediaConfiguration(),
                null);
        if (logger.isDebugEnabled())
            logger.debug("Media Configuration ... [REGISTERED]");

        ConfigurationService cfg = NeomediaActivator.getConfigurationService();
        Dictionary<String, String> mediaProps = new Hashtable<String, String>();

        mediaProps.put( ConfigurationForm.FORM_TYPE,
                        ConfigurationForm.GENERAL_TYPE);

        // If the audio configuration form is disabled don't register it.
        if ((cfg == null) || !cfg.getBoolean(AUDIO_CONFIG_DISABLED_PROP, false))
        {
            audioConfigurationForm
                = new LazyConfigurationForm(
                        AudioConfigurationPanel.class.getName(),
                        getClass().getClassLoader(),
                        "plugin.mediaconfig.AUDIO_ICON",
                        "impl.neomedia.configform.AUDIO",
                        3);

            bundleContext.registerService(
                    ConfigurationForm.class.getName(),
                    audioConfigurationForm,
                    mediaProps);

            // Initializes and registers the changed audio device configuration
            // event at the notification service.
            if (audioDeviceConfigurationPropertyChangeListener == null)
            {
                getNotificationService();

                audioDeviceConfigurationPropertyChangeListener
                    = new AudioDeviceConfigurationListener(
                            audioConfigurationForm);
                mediaServiceImpl
                    .getDeviceConfiguration()
                    .addPropertyChangeListener(
                            audioDeviceConfigurationPropertyChangeListener);
            }
        }

        // If the video configuration form is disabled don't register it.
        if ((cfg == null) || !cfg.getBoolean(VIDEO_CONFIG_DISABLED_PROP, false))
        {
            videoConfigurationForm
                = new LazyConfigurationForm(
                        VideoConfigurationPanel.class.getName(),
                        getClass().getClassLoader(),
                        "plugin.mediaconfig.VIDEO_ICON",
                        "impl.neomedia.configform.VIDEO",
                        4);

            bundleContext.registerService(
                    ConfigurationForm.class.getName(),
                    videoConfigurationForm,
                    mediaProps);

            // Initializes and registers the changed video device configuration
            // event at the notification service.
            // Disabled until video hotplug is not available.
            /*if (videoDeviceConfigurationPropertyChangeListener == null)
            {
                getNotificationService();

                videoDeviceConfigurationPropertyChangeListener
                    = new VideoDeviceConfigurationListener(
                            videoConfigurationForm);
                mediaServiceImpl
                    .getDeviceConfiguration()
                    .addPropertyChangeListener(
                            videoDeviceConfigurationPropertyChangeListener);
            }*/
        }

        // H.264
        // If the H.264 configuration form is disabled don't register it.
        if ((cfg == null) || !cfg.getBoolean(H264_CONFIG_DISABLED_PROP, false))
        {
            Dictionary<String, String> h264Props
                = new Hashtable<String, String>();

            h264Props.put(
                    ConfigurationForm.FORM_TYPE,
                    ConfigurationForm.ADVANCED_TYPE);
            bundleContext.registerService(
                    ConfigurationForm.class.getName(),
                    new LazyConfigurationForm(
                            ConfigurationPanel.class.getName(),
                            getClass().getClassLoader(),
                            "plugin.mediaconfig.VIDEO_ICON",
                            "impl.neomedia.configform.H264",
                            -1,
                            true),
                    h264Props);
        }

        // ZRTP
        // If the ZRTP configuration form is disabled don't register it.
        if ((cfg == null) || !cfg.getBoolean(ZRTP_CONFIG_DISABLED_PROP, false))
        {
            Dictionary<String, String> securityProps
                = new Hashtable<String, String>();

            securityProps.put( ConfigurationForm.FORM_TYPE,
                            ConfigurationForm.SECURITY_TYPE);
            bundleContext.registerService(
                ConfigurationForm.class.getName(),
                new LazyConfigurationForm(
                    SecurityConfigForm.class.getName(),
                    getClass().getClassLoader(),
                    "impl.media.security.zrtp.CONF_ICON",
                    "impl.media.security.zrtp.TITLE",
                    0),
                securityProps);
        }

        //we use the nist-sdp stack to make parse sdp and we need to set the
        //following property to make sure that it would accept java generated
        //IPv6 addresses that contain address scope zones.
        System.setProperty("gov.nist.core.STRIP_ADDR_SCOPES", "true");

        // AudioNotifierService
        AudioNotifierService audioNotifierService
            = LibJitsi.getAudioNotifierService();

        audioNotifierService.setMute(
                (cfg == null)
                    || !cfg.getBoolean(
                            "net.java.sip.communicator"
                                + ".impl.sound.isSoundEnabled",
                            true));
        bundleContext.registerService(
                AudioNotifierService.class.getName(),
                audioNotifierService,
                null);

        if (logger.isInfoEnabled())
            logger.info("Audio Notifier Service ...[REGISTERED]");

        // Call Recording
        // If the call recording configuration form is disabled don't continue.
        if ((cfg == null)
            || !(cfg.getBoolean(CALL_RECORDING_CONFIG_DISABLED_PROP, false)
                 || cfg.getBoolean("net.java.sip.communicator" +
                            ".impl.gui.main.call.HIDE_CALL_RECORD_BUTTON",
                                   false)))
        {
            Dictionary<String, String> callRecordingProps
                = new Hashtable<String, String>();

            callRecordingProps.put(
                    ConfigurationForm.FORM_TYPE,
                    ConfigurationForm.ADVANCED_TYPE);
            bundleContext.registerService(
                    ConfigurationForm.class.getName(),
                    new LazyConfigurationForm(
                            CallRecordingConfigForm.class.getName(),
                            getClass().getClassLoader(),
                            null,
                            "plugin.callrecordingconfig.CALL_RECORDING_CONFIG",
                            1100,
                            true),
                    callRecordingProps);
        }

        // Checks for update
        OpenH264Retriever.checkForUpdateAndDownload();
    }

    /**
     * Stops the execution of the neomedia bundle in the specified context.
     *
     * @param bundleContext the context in which the neomedia bundle is to stop
     * executing
     * @throws Exception if an error occurs while stopping the execution of the
     * neomedia bundle in the specified context
     */
    public void stop(BundleContext bundleContext)
        throws Exception
    {
        try
        {
            if(audioDeviceConfigurationPropertyChangeListener != null)
            {
                mediaServiceImpl
                    .getDeviceConfiguration()
                        .removePropertyChangeListener(
                                audioDeviceConfigurationPropertyChangeListener);
                audioDeviceConfigurationPropertyChangeListener.dispose();
                audioDeviceConfigurationPropertyChangeListener = null;
            }

            // Disabled until video hotplug is available.
            /*
            if(videoDeviceConfigurationPropertyChangeListener != null)
            {
                mediaServiceImpl
                    .getDeviceConfiguration()
                        .removePropertyChangeListener(
                                videoDeviceConfigurationPropertyChangeListener);
                videoDeviceConfigurationPropertyChangeListener.dispose();
                videoDeviceConfigurationPropertyChangeListener = null;
            }
            */
        }
        finally
        {
            configurationService = null;
            fileAccessService = null;
            mediaServiceImpl = null;
            resources = null;
        }
    }
}
