/*
 * 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.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.concurrent.*;

import javax.media.*;
import javax.media.MediaException;
import javax.media.protocol.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.plugin.desktoputil.TransparentPanel;
import net.java.sip.communicator.util.*;

import org.jitsi.impl.neomedia.*;
import org.jitsi.impl.neomedia.device.*;
import org.jitsi.service.audionotifier.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.codec.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.neomedia.event.*;
import org.jitsi.service.resources.*;
import org.jitsi.util.OSUtils;
import org.jitsi.util.swing.*;

/**
 * Implements <tt>MediaConfigurationService</tt> i.e. represents a factory of
 * user interface which allows the user to configure the media-related
 * functionality of the application.
 *
 * @author Lyubomir Marinov
 * @author Damian Minkov
 * @author Yana Stamcheva
 * @author Boris Grozev
 * @author Vincent Lucas
 */
public class MediaConfigurationImpl
    implements ActionListener,
               MediaConfigurationService
{
    /**
     * Creates a new listener to combo box and affect changes to the audio level
     * indicator. The level indicator is updated via a thread in order to avoid
     * deadlock of the user interface.
     */
    private class AudioLevelListenerThread
        implements ActionListener,
                   HierarchyListener
    {
        /**
         * Listener to update the audio level indicator.
         */
        private final SimpleAudioLevelListener audioLevelListener
            = new SimpleAudioLevelListener()
            {
                public void audioLevelChanged(int level)
                {
                    soundLevelIndicator.updateSoundLevel(level);
                }
            };

        /**
         * The audio system used to get and set the sound devices.
         */
        private AudioSystem audioSystem;

        /**
         * The combo box used to select the device the user wants to use.
         */
        private JComboBox comboBox;

        /**
         * The current capture device.
         */
        private AudioMediaDeviceSession deviceSession;

        /**
         * The new device chosen by the user and that we need to initialize as
         * the new capture device.
         */
        private AudioMediaDeviceSession deviceSessionToSet;

        /**
         * The indicator which determines whether
         * {@link #setDeviceSession(AudioMediaDeviceSession)} is to be invoked
         * when {@link #deviceSessionToSet} is <tt>null</tt>.
         */
        private boolean deviceSessionToSetIsNull;

        /**
         * The <tt>ExecutorService</tt> which is to asynchronously invoke
         * {@link #setDeviceSession(AudioMediaDeviceSession)} with
         * {@link #deviceSessionToSet}.
         */
        private final ExecutorService setDeviceSessionExecutor
            = Executors.newSingleThreadExecutor();

        private final Runnable setDeviceSessionTask
            = new Runnable()
            {
                public void run()
                {
                    AudioMediaDeviceSession deviceSession = null;
                    boolean deviceSessionIsNull = false;

                    synchronized (AudioLevelListenerThread.this)
                    {
                        if ((deviceSessionToSet != null)
                                || deviceSessionToSetIsNull)
                        {
                            /*
                             * Invoke #setDeviceSession(AudioMediaDeviceSession)
                             * outside the synchronized block to avoid a GUI
                             * deadlock.
                             */
                            deviceSession = deviceSessionToSet;
                            deviceSessionIsNull = deviceSessionToSetIsNull;
                            deviceSessionToSet = null;
                            deviceSessionToSetIsNull = false;
                        }
                    }

                    if ((deviceSession != null) || deviceSessionIsNull)
                    {
                        /*
                         * XXX The method blocks on Mac OS X for Bluetooth
                         * devices which are paired but disconnected.
                         */
                        setDeviceSession(deviceSession);
                    }
                }
            };

        /**
         * The sound level indicator used to show the effectiveness of the
         * capture device.
         */
        private SoundLevelIndicator soundLevelIndicator;

        /**
         *  Provides an handler which reads the stream into the
         *  "transferHandlerBuffer".
         */
        private final BufferTransferHandler transferHandler
            = new BufferTransferHandler()
            {
                public void transferData(PushBufferStream stream)
                {
                    try
                    {
                        stream.read(transferHandlerBuffer);
                    }
                    catch (IOException ioe)
                    {
                    }
                }
            };

        /**
         * The buffer used for reading the capture device.
         */
        private final Buffer transferHandlerBuffer = new Buffer();

        /**
         * Creates a new listener to combo box and affect changes to the audio
         * level indicator.
         *
         * @param audioSystem The audio system used to get and set the sound
         * devices.
         * @param comboBox The combo box used to select the device the user
         * wants to use.
         * @param soundLevelIndicator The sound level indicator used to show the
         * effectiveness of the capture device.
         */
        public AudioLevelListenerThread(
                AudioSystem audioSystem,
                JComboBox comboBox,
                SoundLevelIndicator soundLevelIndicator)
        {
            init(audioSystem, comboBox, soundLevelIndicator);
        }

        /**
         * Refresh combo box when the user click on it.
         *
         * @param ev The click on the combo box.
         */
        public void actionPerformed(ActionEvent ev)
        {
            synchronized (this)
            {
                deviceSessionToSet = null;
                deviceSessionToSetIsNull = true;
                setDeviceSessionExecutor.execute(setDeviceSessionTask);
            }

            CaptureDeviceInfo cdi;

            if (comboBox == null)
            {
                cdi
                    = soundLevelIndicator.isShowing()
                        ? audioSystem.getSelectedDevice(
                                AudioSystem.DataFlow.CAPTURE)
                        : null;
            }
            else
            {
                Object selectedItem
                    = soundLevelIndicator.isShowing()
                        ? comboBox.getSelectedItem()
                        : null;

                cdi
                    = (selectedItem
                            instanceof
                                DeviceConfigurationComboBoxModel.CaptureDevice)
                        ? ((DeviceConfigurationComboBoxModel.CaptureDevice)
                                selectedItem)
                            .info
                        : null;
            }

            if (cdi != null)
            {
                for (MediaDevice md: mediaService.getDevices(
                            MediaType.AUDIO,
                            MediaUseCase.ANY))
                {
                    if (md instanceof AudioMediaDeviceImpl)
                    {
                        AudioMediaDeviceImpl amd = (AudioMediaDeviceImpl) md;

                        if (cdi.equals(amd.getCaptureDeviceInfo()))
                        {
                            try
                            {
                                MediaDeviceSession deviceSession
                                    = amd.createSession();
                                boolean deviceSessionIsSet = false;

                                try
                                {
                                    if (deviceSession instanceof
                                            AudioMediaDeviceSession)
                                    {
                                        synchronized (this)
                                        {
                                            deviceSessionToSet
                                                = (AudioMediaDeviceSession)
                                                    deviceSession;
                                            deviceSessionToSetIsNull
                                                = (deviceSessionToSet == null);
                                            setDeviceSessionExecutor.execute(
                                                    setDeviceSessionTask);
                                        }
                                        deviceSessionIsSet = true;
                                    }
                                }
                                finally
                                {
                                    if (!deviceSessionIsSet)
                                        deviceSession.close();
                                }
                            }
                            catch (Throwable t)
                            {
                                if (t instanceof ThreadDeath)
                                    throw (ThreadDeath) t;
                            }
                            break;
                        }
                    }
                }
            }
        }

        public void hierarchyChanged(HierarchyEvent ev)
        {
            if ((ev.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0)
            {
                SwingUtilities.invokeLater(
                        new Runnable()
                        {
                            public void run()
                            {
                                actionPerformed(null);
                            }
                        });
            }
        }

        /**
         * Creates a new listener to combo box and affect changes to the audio
         * level indicator.
         *
         * @param audioSystem The audio system used to get and set the sound
         * devices.
         * @param comboBox The combo box used to select the device the user
         * wants to use.
         * @param soundLevelIndicator The sound level indicator used to show the
         * effectiveness of the capture device.
         */
        public void init(
                AudioSystem audioSystem,
                JComboBox comboBox,
                SoundLevelIndicator soundLevelIndicator)
        {
            this.audioSystem = audioSystem;

            if (this.comboBox != comboBox)
            {
                if (this.comboBox != null)
                    this.comboBox.removeActionListener(this);
                this.comboBox = comboBox;
                if (comboBox != null)
                    comboBox.addActionListener(this);
            }

            if (this.soundLevelIndicator != soundLevelIndicator)
            {
                if (this.soundLevelIndicator != null)
                    this.soundLevelIndicator.removeHierarchyListener(this);
                this.soundLevelIndicator = soundLevelIndicator;
                if (soundLevelIndicator != null)
                    soundLevelIndicator.addHierarchyListener(this);
            }
        }

        /**
         * Sets the new capture device used by the audio level indicator.
         *
         * @param deviceSession The new capture device used by the audio level
         * indicator.
         */
        private void setDeviceSession(AudioMediaDeviceSession deviceSession)
        {
            if (this.deviceSession == deviceSession)
                return;

            if (this.deviceSession != null)
            {
                try
                {
                    this.deviceSession.close();
                }
                finally
                {
                    this.deviceSession.setLocalUserAudioLevelListener(null);
                    soundLevelIndicator.resetSoundLevel();
                }
            }

            this.deviceSession = deviceSession;

            if (deviceSession != null)
            {
                deviceSession.setContentDescriptor(
                        new ContentDescriptor(ContentDescriptor.RAW));
                deviceSession.setLocalUserAudioLevelListener(
                        audioLevelListener);

                deviceSession.start(MediaDirection.SENDONLY);

                try
                {
                    DataSource dataSource = deviceSession.getOutputDataSource();

                    dataSource.connect();

                    PushBufferStream[] streams
                        = ((PushBufferDataSource) dataSource).getStreams();

                    for (PushBufferStream stream : streams)
                        stream.setTransferHandler(transferHandler);

                    dataSource.start();
                }
                catch (Throwable t)
                {
                    if (t instanceof ThreadDeath)
                        throw (ThreadDeath) t;
                }
            }
        }
    }

    /**
     * Renders the available resolutions in the combo box.
     */
    private static class ResolutionCellRenderer
        extends DefaultListCellRenderer
    {
        /**
         * The serialization version number of the
         * <tt>ResolutionCellRenderer</tt> class. Defined to the value of
         * <tt>0</tt> because the <tt>ResolutionCellRenderer</tt> instances do
         * not have state of their own.
         */
        private static final long serialVersionUID = 0L;

        /**
         * Sets readable text describing the resolution if the selected
         * value is null we return the string "Auto".
         *
         * @param list
         * @param value
         * @param index
         * @param isSelected
         * @param cellHasFocus
         * @return Component
         */
        @Override
        public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus)
        {
            // call super to set backgrounds and fonts
            super.getListCellRendererComponent(
                    list,
                    value,
                    index,
                    isSelected,
                    cellHasFocus);

            // now just change the text
            if(value == null)
                setText("Auto");
            else if(value instanceof Dimension)
            {
                Dimension d = (Dimension)value;

                setText(((int) d.getWidth()) + "x" + ((int) d.getHeight()));
            }
            return this;
        }
    }

    /**
     * Wrapper for the device list field.
     */
    private static class DeviceComboBoxField
    {
        /**
         * The combo box with the devices.
         */
        private JComboBox deviceComboBox = null;

        /**
         * The <tt>JList</tt> with the devices.
         */
        private JList deviceList = null;

        /**
         * The current component that displays the list with the devices.
         */
        private Component deviceComponent;

        /**
         * The listener for the field.
         */
        private Listener listener;

        /**
         * Model for the field.
         */
        final DeviceConfigurationComboBoxModel model;

        /**
         * Constructs <tt>DeviceComboBoxField</tt> instance.
         * @param type the type of the configuration panel
         * @param devicePanel the container of the field.
         */
        public DeviceComboBoxField(final int type, Container devicePanel)
        {
            model = new DeviceConfigurationComboBoxModel(
                mediaService.getDeviceConfiguration(),
                type);

            if(!OSUtils.IS_WINDOWS
                || type != DeviceConfigurationComboBoxModel.VIDEO)
            {
                deviceComboBox = new JComboBox();
                deviceComboBox.setEditable(false);
                deviceComboBox.setModel(model);
                devicePanel.add(deviceComboBox);
                deviceComponent = deviceComboBox;
            }
            else
            {
                deviceList = new JList();
                deviceList.setModel(model);
                JScrollPane listScroller = new JScrollPane(deviceList);
                listScroller.setPreferredSize(new Dimension(200, 38));
                deviceList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
                deviceList.setLayoutOrientation(JList.VERTICAL);
                deviceList.setVisibleRowCount(-1);
                deviceList.setSelectedValue(model.getSelectedItem(), true);
                devicePanel.add(listScroller);
                deviceComponent = deviceList;
            }
        }

        /**
         * Returns the field component
         * @return the field component
         */
        public Component getComponent()
        {
            return deviceComponent;
        }

        /**
         * Returns the selected device
         * @return the selected device
         */
        public Object getSelectedItem()
        {
            return (deviceComboBox != null)?
                deviceComboBox.getSelectedItem() : deviceList.getSelectedValue();
        }

        /**
         * Adds a listener to the field.
         * @param listener the listener to be added.
         */
        public void addListener(final Listener listener)
        {
            this.listener = listener;
            if(deviceComboBox != null)
            {
                deviceComboBox.addActionListener(new ActionListener()
                {

                    @Override
                    public void actionPerformed(ActionEvent e)
                    {
                        listener.onAction();
                    }
                });
            }
            else
            {
                deviceList.addListSelectionListener(new ListSelectionListener()
                {

                    @Override
                    public void valueChanged(ListSelectionEvent e)
                    {
                        model.setSelectedItem(deviceList.getSelectedValue());
                        listener.onAction();
                    }
                });
            }
        }

        /**
         * Interface for the listener attached to the field.
         */
        public static interface Listener
        {
            public void onAction();
        }
    }

    /**
     * Indicates if the Devices settings configuration tab
     * should be disabled, i.e. not visible to the user.
     */
    private static final String DEVICES_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.devicesconfig.DISABLED";

    /**
     * Indicates if the Audio/Video encodings configuration tab
     * should be disabled, i.e. not visible to the user.
     */
    private static final String ENCODINGS_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.encodingsconfig.DISABLED";

    /**
     * The <tt>Logger</tt> used by the <tt>MediaConfigurationServiceImpl</tt>
     * class for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(MediaConfigurationImpl.class);

    /**
     * The <tt>MediaService</tt> implementation used by
     * <tt>MediaConfigurationImpl</tt>.
     */
    private static final MediaServiceImpl mediaService
        = NeomediaActivator.getMediaServiceImpl();

    /**
     * The name of the sound file used to test the playback and the notification
     * devices.
     */
    private static final String TEST_SOUND_FILENAME_PROP
        = "net.java.sip.communicator.impl.neomedia.TestSoundFilename";

    /**
     * Indicates if the Video/More Settings configuration tab
     * should be disabled, i.e. not visible to the user.
     */
    private static final String VIDEO_MORE_SETTINGS_DISABLED_PROP
        = "net.java.sip.communicator.impl.neomedia.videomoresettingsconfig.DISABLED";

    /**
     * The preferred width of all panels.
     */
    private final static int WIDTH = 350;

    /**
     * Creates the video advanced settings.
     *
     * @return video advanced settings panel.
     */
    private static Component createVideoAdvancedSettings()
    {
        ResourceManagementService resources = NeomediaActivator.getResources();

        final DeviceConfiguration deviceConfig =
            mediaService.getDeviceConfiguration();

        TransparentPanel centerPanel =
            new TransparentPanel(new GridBagLayout());
        centerPanel.setMaximumSize(new Dimension(WIDTH, 150));

        JButton resetDefaultsButton = new JButton(
            resources.getI18NString(
                    "impl.media.configform.VIDEO_RESET"));
        JPanel resetButtonPanel = new TransparentPanel(
                new FlowLayout(FlowLayout.RIGHT));
        resetButtonPanel.add(resetDefaultsButton);

        final JPanel centerAdvancedPanel
            = new TransparentPanel(new BorderLayout());
        centerAdvancedPanel.add(centerPanel, BorderLayout.NORTH);
        centerAdvancedPanel.add(resetButtonPanel, BorderLayout.SOUTH);

        GridBagConstraints constraints = new GridBagConstraints();
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.NORTHWEST;
        constraints.insets = new Insets(5, 5, 0, 0);
        constraints.gridx = 0;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.gridy = 0;

        centerPanel.add(new JLabel(
            resources.getI18NString("impl.media.configform.VIDEO_RESOLUTION")),
            constraints);
        constraints.gridy = 1;
        constraints.insets = new Insets(0, 0, 0, 0);
        final JCheckBox frameRateCheck = new SIPCommCheckBox(
            resources.getI18NString("impl.media.configform.VIDEO_FRAME_RATE"));
        centerPanel.add(frameRateCheck, constraints);
        constraints.gridy = 2;
        constraints.insets = new Insets(5, 5, 0, 0);
        centerPanel.add(new JLabel(
            resources.getI18NString(
                    "impl.media.configform.VIDEO_PACKETS_POLICY")),
            constraints);
        constraints.gridy = 3;
        centerPanel.add(new JLabel(
            resources.getI18NString(
                    "impl.media.configform.VIDEO_BITRATE")),
            constraints);

        constraints.weightx = 1;
        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.insets = new Insets(5, 0, 0, 5);
        Object[] resolutionValues
            = new Object[DeviceConfiguration.SUPPORTED_RESOLUTIONS.length + 1];
        System.arraycopy(DeviceConfiguration.SUPPORTED_RESOLUTIONS, 0,
                        resolutionValues, 1,
                        DeviceConfiguration.SUPPORTED_RESOLUTIONS.length);
        final JComboBox sizeCombo = new JComboBox(resolutionValues);
        sizeCombo.setRenderer(new ResolutionCellRenderer());
        sizeCombo.setEditable(false);
        centerPanel.add(sizeCombo, constraints);

        // default value is 20
        final JSpinner frameRate = new JSpinner(new SpinnerNumberModel(
            20, 5, 30, 1));
        frameRate.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                deviceConfig.setFrameRate(
                        ((SpinnerNumberModel)frameRate.getModel())
                            .getNumber().intValue());
            }
        });
        constraints.gridy = 1;
        constraints.insets = new Insets(0, 0, 0, 5);
        centerPanel.add(frameRate, constraints);

        frameRateCheck.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                if(frameRateCheck.isSelected())
                {
                    deviceConfig.setFrameRate(
                        ((SpinnerNumberModel)frameRate.getModel())
                            .getNumber().intValue());
                }
                else // unlimited framerate
                    deviceConfig.setFrameRate(-1);

                frameRate.setEnabled(frameRateCheck.isSelected());
            }
        });

        int videoMaxBandwith = deviceConfig.getVideoRTPPacingThreshold();
        // Accord the current value with the maximum allowed value. Fixes
        // existing configurations that have been set to a number larger than
        // the advised maximum value.
        videoMaxBandwith = ((videoMaxBandwith > 999) ? 999 : videoMaxBandwith);

        final JSpinner videoMaxBandwidth = new JSpinner(new SpinnerNumberModel(
            videoMaxBandwith,
            1, 999, 1));
        videoMaxBandwidth.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                deviceConfig.setVideoRTPPacingThreshold(
                        ((SpinnerNumberModel) videoMaxBandwidth.getModel())
                                .getNumber().intValue());
            }
        });
        constraints.gridx = 1;
        constraints.gridy = 2;
        constraints.insets = new Insets(0, 0, 5, 5);
        centerPanel.add(videoMaxBandwidth, constraints);

        final JSpinner videoBitrate = new JSpinner(new SpinnerNumberModel(
            deviceConfig.getVideoBitrate(),
            1, Integer.MAX_VALUE, 1));
        videoBitrate.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                deviceConfig.setVideoBitrate(
                        ((SpinnerNumberModel) videoBitrate.getModel())
                                .getNumber().intValue());
            }
        });
        constraints.gridy = 3;
        centerPanel.add(videoBitrate, constraints);

        resetDefaultsButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                // reset to defaults
                sizeCombo.setSelectedIndex(0);
                frameRateCheck.setSelected(false);
                frameRate.setEnabled(false);
                frameRate.setValue(20);
                // unlimited framerate
                deviceConfig.setFrameRate(-1);
                videoMaxBandwidth.setValue(
                        DeviceConfiguration.DEFAULT_VIDEO_RTP_PACING_THRESHOLD);
                videoBitrate.setValue(
                        DeviceConfiguration.DEFAULT_VIDEO_BITRATE);
            }
        });

        // load selected value or auto
        Dimension videoSize = deviceConfig.getVideoSize();

        if((videoSize.getHeight() != DeviceConfiguration.DEFAULT_VIDEO_HEIGHT)
                && (videoSize.getWidth()
                        != DeviceConfiguration.DEFAULT_VIDEO_WIDTH))
            sizeCombo.setSelectedItem(deviceConfig.getVideoSize());
        else
            sizeCombo.setSelectedIndex(0);
        sizeCombo.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                Dimension selectedVideoSize
                    = (Dimension) sizeCombo.getSelectedItem();

                if(selectedVideoSize == null)
                {
                    // the auto value, default one
                    selectedVideoSize
                        = new Dimension(
                                DeviceConfiguration.DEFAULT_VIDEO_WIDTH,
                                DeviceConfiguration.DEFAULT_VIDEO_HEIGHT);
                }
                deviceConfig.setVideoSize(selectedVideoSize);
            }
        });

        frameRateCheck.setSelected(
            deviceConfig.getFrameRate()
                != DeviceConfiguration.DEFAULT_VIDEO_FRAMERATE);
        frameRate.setEnabled(frameRateCheck.isSelected());

        if(frameRate.isEnabled())
            frameRate.setValue(deviceConfig.getFrameRate());

        return centerAdvancedPanel;
    }

    /**
     * Creates the video container.
     * @param noVideoComponent the container component.
     * @return the video container.
     */
    private static JComponent createVideoContainer(Component noVideoComponent)
    {
        return new VideoContainer(noVideoComponent, false);
    }

    /**
     * Creates preview for the (video) device in the video container.
     *
     * @param device the device
     * @param videoContainer the video container
     * @throws IOException a problem accessing the device
     * @throws MediaException a problem getting preview
     */
    private static void createVideoPreview(
            CaptureDeviceInfo device,
            JComponent videoContainer)
        throws IOException,
               MediaException
    {
        videoContainer.removeAll();

        videoContainer.revalidate();
        videoContainer.repaint();

        if (device == null)
            return;

        for (MediaDevice mediaDevice
                : mediaService.getDevices(MediaType.VIDEO, MediaUseCase.ANY))
        {
            if(((MediaDeviceImpl) mediaDevice).getCaptureDeviceInfo().equals(
                    device))
            {
                Dimension videoContainerSize
                    = videoContainer.getPreferredSize();
                Component preview
                    = (Component)
                        mediaService.getVideoPreviewComponent(
                                mediaDevice,
                                videoContainerSize.width,
                                videoContainerSize.height);

                if (preview != null)
                    videoContainer.add(preview);
                break;
            }
        }
    }

    /**
     * The mnemonic for a type.
     * @param type audio or video type.
     * @return the mnemonic.
     */
    private static char getDisplayedMnemonic(int type)
    {
        switch (type)
        {
        case DeviceConfigurationComboBoxModel.AUDIO:
            return NeomediaActivator.getResources().getI18nMnemonic(
                "impl.media.configform.AUDIO");
        case DeviceConfigurationComboBoxModel.VIDEO:
            return NeomediaActivator.getResources().getI18nMnemonic(
                "impl.media.configform.VIDEO");
        default:
            throw new IllegalArgumentException("type");
        }
    }

    /**
     * A label for a type.
     * @param type the type.
     * @return the label.
     */
    private static String getLabelText(int type)
    {
        switch (type)
        {
        case DeviceConfigurationComboBoxModel.AUDIO:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO");
        case DeviceConfigurationComboBoxModel.AUDIO_CAPTURE:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO_IN");
        case DeviceConfigurationComboBoxModel.AUDIO_NOTIFY:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO_NOTIFY");
        case DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.AUDIO_OUT");
        case DeviceConfigurationComboBoxModel.VIDEO:
            return NeomediaActivator.getResources().getI18NString(
                "impl.media.configform.VIDEO");
        default:
            throw new IllegalArgumentException("type");
        }
    }

    /**
     * Used to move encoding options.
     *
     * @param table the table with encodings
     * @param up move direction.
     */
    private static void move(JTable table, boolean up)
    {
        int index
            = ((EncodingConfigurationTableModel) table.getModel()).move(
                    table.getSelectedRow(),
                    up);

        table.getSelectionModel().setSelectionInterval(index, index);
    }

    /**
     * The thread which updates the capture device as selected by the user. This
     * prevent the UI to lock while changing the device.
     */
    private AudioLevelListenerThread audioLevelListenerThread = null;

    /**
     * The button used to play a sound in order to test notification devices.
     */
    private JButton notificationPlaySoundButton;

    /**
     * The combo box used to selected the notification device.
     */
    private JComboBox notifyCombo;

    /**
     * The combo box used to selected the playback device.
     */
    private JComboBox playbackCombo;

    /**
     * The button used to play a sound in order to test playback device.
     */
    private JButton playbackPlaySoundButton;

    /**
     * Indicates that one of the contained in this panel buttons has been
     * clicked.
     * @param e the <tt>ActionEvent</tt> that notified us
     */
    public void actionPerformed(ActionEvent e)
    {
        boolean isPlaybackEvent = (e.getSource() == playbackPlaySoundButton);

        // If the user clicked on one pley sound button.
        if(isPlaybackEvent
                || e.getSource() == notificationPlaySoundButton)
        {
            AudioNotifierService audioNotifServ
                = NeomediaActivator.getAudioNotifierService();
            String testSoundFilename
                = NeomediaActivator.getConfigurationService()
                    .getString(
                            TEST_SOUND_FILENAME_PROP,
                            NeomediaActivator.getResources().getSoundPath(
                                "TEST_SOUND")
                            );
            SCAudioClip sound = audioNotifServ.createAudio(
                    testSoundFilename,
                    isPlaybackEvent);
            sound.play();
        }
        // If the selected item of the playback or notify combobox has changed.
        else if(e.getSource() == playbackCombo
                || e.getSource() == notifyCombo)
        {
            DeviceConfigurationComboBoxModel.CaptureDevice device
                = (DeviceConfigurationComboBoxModel.CaptureDevice)
                    ((JComboBox) e.getSource()).getSelectedItem();

            boolean isEnabled = (device.info != null);
            if(e.getSource() == playbackCombo)
            {
                playbackPlaySoundButton.setEnabled(isEnabled);
            }
            else
            {
                notificationPlaySoundButton.setEnabled(isEnabled);
            }
        }
    }

    /**
     * Returns the audio configuration panel.
     *
     * @return the audio configuration panel
     */
    public Component createAudioConfigPanel()
    {
        return createControls(DeviceConfigurationComboBoxModel.AUDIO);
    }

    /**
     * Creates the UI controls which are to control the details of a specific
     * <tt>AudioSystem</tt>.
     *
     * @param audioSystem the <tt>AudioSystem</tt> for which the UI controls to
     * control its details are to be created
     * @param container the <tt>JComponent</tt> into which the UI controls which
     * are to control the details of the specified <tt>audioSystem</tt> are to
     * be added
     */
    public void createAudioSystemControls(
            final AudioSystem audioSystem,
            JComponent container)
    {
        GridBagConstraints cnstrnts = new GridBagConstraints();

        cnstrnts.anchor = GridBagConstraints.NORTHWEST;
        cnstrnts.fill = GridBagConstraints.HORIZONTAL;
        cnstrnts.weighty = 0;

        int audioSystemFeatures = audioSystem.getFeatures();
        boolean featureNotifyAndPlaybackDevices
            = ((audioSystemFeatures
                    & AudioSystem.FEATURE_NOTIFY_AND_PLAYBACK_DEVICES)
                != 0);

        cnstrnts.gridx = 0;
        cnstrnts.insets = new Insets(3, 0, 3, 3);
        cnstrnts.weightx = 0;

        cnstrnts.gridy = 0;
        container.add(new JLabel(getLabelText(
            DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)), cnstrnts);
        if (featureNotifyAndPlaybackDevices)
        {
            cnstrnts.gridy = 2;
            container.add(new JLabel(getLabelText(
                DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)), cnstrnts);
            cnstrnts.gridy = 3;
            container.add(new JLabel(getLabelText(
                DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)), cnstrnts);
        }

        cnstrnts.gridx = 1;
        cnstrnts.insets = new Insets(3, 3, 3, 0);
        cnstrnts.weightx = 1;

        JComboBox captureCombo = null;

        if (featureNotifyAndPlaybackDevices)
        {
            captureCombo = new JComboBox();
            captureCombo.setEditable(false);
            captureCombo.setModel(
                    new DeviceConfigurationComboBoxModel(
                            mediaService.getDeviceConfiguration(),
                            DeviceConfigurationComboBoxModel.AUDIO_CAPTURE));
            cnstrnts.gridy = 0;
            container.add(captureCombo, cnstrnts);
        }

        int anchor = cnstrnts.anchor;
        SoundLevelIndicator capturePreview
            = new SoundLevelIndicator(
                    SimpleAudioLevelListener.MIN_LEVEL,
                    SimpleAudioLevelListener.MAX_LEVEL);

        cnstrnts.anchor = GridBagConstraints.CENTER;
        cnstrnts.gridy = (captureCombo == null) ? 0 : 1;
        container.add(capturePreview, cnstrnts);
        cnstrnts.anchor = anchor;

        cnstrnts.gridy = GridBagConstraints.RELATIVE;

        if (featureNotifyAndPlaybackDevices)
        {
            playbackCombo = new JComboBox();
            playbackCombo.setEditable(false);
            playbackCombo.setModel(
                    new DeviceConfigurationComboBoxModel(
                            mediaService.getDeviceConfiguration(),
                            DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK));
            playbackCombo.addActionListener(this);
            container.add(playbackCombo, cnstrnts);

            notifyCombo = new JComboBox();
            notifyCombo.setEditable(false);
            notifyCombo.setModel(
                    new DeviceConfigurationComboBoxModel(
                            mediaService.getDeviceConfiguration(),
                            DeviceConfigurationComboBoxModel.AUDIO_NOTIFY));
            notifyCombo.addActionListener(this);
            container.add(notifyCombo, cnstrnts);
        }

        int[] checkBoxAudioSystemFeatures
            = new int[]
                    {
                        AudioSystem.FEATURE_ECHO_CANCELLATION,
                        AudioSystem.FEATURE_DENOISE,
                        AudioSystem.FEATURE_AGC
                    };

        for (int i = 0; i < checkBoxAudioSystemFeatures.length; i++)
        {
            final int f = checkBoxAudioSystemFeatures[i];

            if ((f & audioSystemFeatures) != 0)
            {
                String textKey;
                boolean selected;

                switch (f)
                {
                case AudioSystem.FEATURE_AGC:
                    textKey = "impl.media.configform.AUTOMATICGAINCONTROL";
                    selected = audioSystem.isAutomaticGainControl();
                    break;
                case AudioSystem.FEATURE_DENOISE:
                    textKey = "impl.media.configform.DENOISE";
                    selected = audioSystem.isDenoise();
                    break;
                case AudioSystem.FEATURE_ECHO_CANCELLATION:
                    textKey = "impl.media.configform.ECHOCANCEL";
                    selected = audioSystem.isEchoCancel();
                    break;
                default:
                    continue;
                }

                final SIPCommCheckBox checkBox
                    = new SIPCommCheckBox(
                            NeomediaActivator.getResources().getI18NString(
                                    textKey));

                /*
                 * First set the selected one, then add the listener in order to
                 * avoid saving the value when using the default one and only
                 * showing to user without modification.
                 */
                checkBox.setSelected(selected);
                checkBox.addItemListener(
                        new ItemListener()
                        {
                            public void itemStateChanged(ItemEvent e)
                            {
                                boolean b = checkBox.isSelected();

                                switch (f)
                                {
                                case AudioSystem.FEATURE_AGC:
                                    audioSystem.setAutomaticGainControl(b);
                                    break;
                                case AudioSystem.FEATURE_DENOISE:
                                    audioSystem.setDenoise(b);
                                    break;
                                case AudioSystem.FEATURE_ECHO_CANCELLATION:
                                    audioSystem.setEchoCancel(b);
                                    break;
                                }
                            }
                        });
                container.add(checkBox, cnstrnts);
            }
        }

        // Adds the play buttons for testing playback and notification devices.
        cnstrnts.gridx = 2;
        cnstrnts.insets = new Insets(3, 3, 3, 0);
        cnstrnts.weightx = 0;

        if (featureNotifyAndPlaybackDevices)
        {
            // Playback play sound button.
            cnstrnts.gridy = 2;
            playbackPlaySoundButton
                = new JButton(new ImageIcon(NeomediaActivator.getResources()
                            .getImageInBytes(
                                "plugin.notificationconfig.PLAY_ICON")));
            playbackPlaySoundButton.setMinimumSize(new Dimension(30,30));
            playbackPlaySoundButton.setPreferredSize(new Dimension(30,30));
            if(((DeviceConfigurationComboBoxModel.CaptureDevice)
                        playbackCombo.getSelectedItem()).info == null)
            {
                playbackPlaySoundButton.setEnabled(false);
            }
            playbackPlaySoundButton.setOpaque(false);
            playbackPlaySoundButton.addActionListener(this);
            container.add(playbackPlaySoundButton, cnstrnts);

            // Notification play sound button.
            cnstrnts.gridy = 3;
            notificationPlaySoundButton
                = new JButton(new ImageIcon(NeomediaActivator.getResources()
                            .getImageInBytes(
                                "plugin.notificationconfig.PLAY_ICON")));
            notificationPlaySoundButton.setMinimumSize(new Dimension(30,30));
            notificationPlaySoundButton.setPreferredSize(new Dimension(30,30));
            if(((DeviceConfigurationComboBoxModel.CaptureDevice)
                        notifyCombo.getSelectedItem()).info == null)
            {
                notificationPlaySoundButton.setEnabled(false);
            }
            notificationPlaySoundButton.setOpaque(false);
            notificationPlaySoundButton.addActionListener(this);
            container.add(notificationPlaySoundButton, cnstrnts);
        }

        if (audioLevelListenerThread == null)
        {
            audioLevelListenerThread
                = new AudioLevelListenerThread(
                        audioSystem,
                        captureCombo,
                        capturePreview);
        }
        else
        {
            audioLevelListenerThread.init(
                    audioSystem,
                    captureCombo,
                    capturePreview);
        }
    }

    /**
     * Creates basic controls for a type (AUDIO or VIDEO).
     *
     * @param type the type.
     * @return the build Component.
     */
    private Component createBasicControls(final int type)
    {
        final boolean setAudioSystemIsDisabled
            = (type == DeviceConfigurationComboBoxModel.AUDIO)
                && NeomediaActivator.getConfigurationService().getBoolean(
                        MediaServiceImpl.DISABLE_SET_AUDIO_SYSTEM_PNAME,
                        false);
        final DeviceComboBoxField deviceComboBox;
        final Container devicePanel;

        if (setAudioSystemIsDisabled)
        {
            deviceComboBox = null;
            devicePanel = null;
        }
        else
        {
            JLabel deviceLabel = new JLabel(getLabelText(type));

            deviceLabel.setDisplayedMnemonic(getDisplayedMnemonic(type));

            devicePanel
                = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));
            devicePanel.setMaximumSize(new Dimension(WIDTH, 25));
            devicePanel.add(deviceLabel);

            deviceComboBox = new DeviceComboBoxField(type, devicePanel);
            deviceLabel.setLabelFor(deviceComboBox.getComponent());
        }

        final JPanel deviceAndPreviewPanel
            = new TransparentPanel(new BorderLayout());
        int preferredDeviceAndPreviewPanelHeight;

        switch (type)
        {
        case DeviceConfigurationComboBoxModel.AUDIO:
            preferredDeviceAndPreviewPanelHeight
                = (devicePanel == null) ? 200 : 245;
            break;
        case DeviceConfigurationComboBoxModel.VIDEO:
            preferredDeviceAndPreviewPanelHeight = 305;
            break;
        default:
            preferredDeviceAndPreviewPanelHeight = 0;
            break;
        }
        if (preferredDeviceAndPreviewPanelHeight > 0)
        {
            deviceAndPreviewPanel.setPreferredSize(
                    new Dimension(WIDTH, preferredDeviceAndPreviewPanelHeight));
        }
        if (devicePanel != null)
            deviceAndPreviewPanel.add(devicePanel, BorderLayout.NORTH);

        final DeviceComboBoxField.Listener deviceComboBoxActionListener
            = new DeviceComboBoxField.Listener()
            {
                public void onAction()
                {
                    boolean revalidateAndRepaint = false;

                    for (int i = deviceAndPreviewPanel.getComponentCount() - 1;
                            i >= 0;
                            i--)
                    {
                        Component c = deviceAndPreviewPanel.getComponent(i);

                        if (c != devicePanel)
                        {
                            deviceAndPreviewPanel.remove(i);
                            revalidateAndRepaint = true;
                        }
                    }

                    Component preview = null;

                    if ((deviceComboBox == null)
                            || ((deviceComboBox.getSelectedItem() != null)
                                    && deviceComboBox.getComponent().isShowing()))
                    {
                        preview
                            = createPreview(
                                    type,
                                    deviceComboBox,
                                    deviceAndPreviewPanel.getPreferredSize());
                    }

                    if (preview != null)
                    {
                        deviceAndPreviewPanel.add(preview, BorderLayout.CENTER);
                        revalidateAndRepaint = true;
                    }

                    if (revalidateAndRepaint)
                    {
                        deviceAndPreviewPanel.revalidate();
                        deviceAndPreviewPanel.repaint();
                    }
                }

            };

        if (deviceComboBox != null)
            deviceComboBox.addListener(deviceComboBoxActionListener);

        /*
         * We have to initialize the controls to reflect the configuration at
         * the time of creating this instance. Additionally, because the
         * preview will stop when it and its associated controls become
         * unnecessary, we have to restart it when the mentioned controls become
         * necessary again. We'll address the two goals described by pretending
         * there's a selection in the combo box when user interface becomes
         * displayable.
         */
        deviceAndPreviewPanel.addHierarchyListener(
                new HierarchyListener()
                {
                    public void hierarchyChanged(HierarchyEvent event)
                    {
                        if ((event.getChangeFlags()
                                    & HierarchyEvent.SHOWING_CHANGED)
                                != 0)
                        {
                            SwingUtilities.invokeLater(
                                    new Runnable()
                                    {
                                        public void run()
                                        {
                                            deviceComboBoxActionListener
                                                .onAction();
                                        }
                                    });
                        }
                    }
                });

        return deviceAndPreviewPanel;
    }

    /**
     * Creates all the controls (including encoding) for a type(AUDIO or VIDEO)
     *
     * @param type the type.
     * @return the build Component.
     */
    private Component createControls(int type)
    {
        ConfigurationService cfg = NeomediaActivator.getConfigurationService();

        Component devicesComponent = null;
        Component encodingsComponent = null;
        Component videoComponent = null;

        int compCount = 0;

        if (cfg == null || !cfg.getBoolean(DEVICES_DISABLED_PROP, false))
        {
            compCount++;
            devicesComponent = createBasicControls(type);
        }
        if (cfg == null || !cfg.getBoolean(ENCODINGS_DISABLED_PROP, false))
        {
            compCount++;
            encodingsComponent = createEncodingControls(type, null);
        }
        if ((type == DeviceConfigurationComboBoxModel.VIDEO)
                && ((cfg == null)
                    || !cfg.getBoolean(
                            VIDEO_MORE_SETTINGS_DISABLED_PROP,
                            false)))
        {
            compCount++;
            videoComponent = createVideoAdvancedSettings();
        }

        ResourceManagementService res = NeomediaActivator.getResources();
        Container container;

        // If we only have one configuration form we don't need to create a
        // tabbed pane.
        if (compCount < 2)
        {
            container = new TransparentPanel(new BorderLayout());

            if (devicesComponent != null)
                container.add(devicesComponent);
            else if (encodingsComponent != null)
                container.add(encodingsComponent);
            else if (videoComponent != null)
                container.add(videoComponent);
        }
        else
        {
            container = new SIPCommTabbedPane();

            SIPCommTabbedPane tabbedPane = (SIPCommTabbedPane) container;
            int index = 0;

            if (devicesComponent != null)
            {
                tabbedPane.insertTab(
                        res.getI18NString("impl.media.configform.DEVICES"),
                        null,
                        devicesComponent,
                        null,
                        index);
                index = 1;
            }
            if (encodingsComponent != null)
            {
                if (tabbedPane.getTabCount() >= 1)
                    index = 1;
                tabbedPane.insertTab(
                        res.getI18NString("impl.media.configform.ENCODINGS"),
                        null,
                        encodingsComponent,
                        null,
                        index);
            }
            if (videoComponent != null)
            {
                if (tabbedPane.getTabCount() >= 2)
                    index = 2;
                tabbedPane.insertTab(
                        res.getI18NString(
                            "impl.media.configform.VIDEO_MORE_SETTINGS"),
                        null,
                        videoComponent,
                        null,
                        index);
            }
        }

        return container;
    }

    /**
     * Creates Component for the encodings of type(AUDIO or VIDEO).
     *
     * @param type the type, either DeviceConfigurationComboBoxModel.AUDIO or
     * DeviceConfigurationComboBoxModel.AUDIO
     * @param encodingConfiguration The <tt>EncodingConfiguration</tt> instance
     * to use. If null, it will use the current encoding configuration from
     * the media service.
     * @return the component.
     */
    private Component createEncodingControls(int type,
            EncodingConfiguration encodingConfiguration)
    {
        if(encodingConfiguration == null)
        {
            encodingConfiguration
                    = mediaService.getCurrentEncodingConfiguration();
        }

        ResourceManagementService resources = NeomediaActivator.getResources();
        String key;

        final JTable table = new JTable();
        table.setShowGrid(false);
        table.setTableHeader(null);
        table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer()
        {
            @Override
            public Component getTableCellRendererComponent(JTable rtable,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column)
            {
                Component component = super.getTableCellRendererComponent(
                    rtable, value, isSelected, hasFocus, row, column);
                component.setEnabled(rtable != null && rtable.isEnabled());
                return component;
            }
        });

        key = "impl.media.configform.UP";
        final JButton upButton = new JButton(resources.getI18NString(key));
        upButton.setMnemonic(resources.getI18nMnemonic(key));
        upButton.setOpaque(false);

        key = "impl.media.configform.DOWN";
        final JButton downButton = new JButton(resources.getI18NString(key));
        downButton.setMnemonic(resources.getI18nMnemonic(key));
        downButton.setOpaque(false);

        Container buttonBar = new TransparentPanel(new GridLayout(0, 1));
        buttonBar.add(upButton);
        buttonBar.add(downButton);

        Container parentButtonBar = new TransparentPanel(new BorderLayout());
        parentButtonBar.add(buttonBar, BorderLayout.NORTH);

        table.setModel(new EncodingConfigurationTableModel(type,
                encodingConfiguration));
        /*
         * The first column contains the check boxes which enable/disable their
         * associated encodings and it doesn't make sense to make it wider than
         * the check boxes.
         */
        TableColumnModel tableColumnModel = table.getColumnModel();
        TableColumn tableColumn = tableColumnModel.getColumn(0);
        tableColumn.setMaxWidth(tableColumn.getMinWidth());

        final ListSelectionListener tableSelectionListener =
            new ListSelectionListener()
            {
                public void valueChanged(ListSelectionEvent event)
                {
                    if (table.getSelectedRowCount() == 1)
                    {
                        int selectedRow = table.getSelectedRow();
                        if (selectedRow > -1)
                        {
                            upButton.setEnabled(selectedRow > 0);
                            downButton.setEnabled(selectedRow < (table
                                .getRowCount() - 1));
                            return;
                        }
                    }
                    upButton.setEnabled(false);
                    downButton.setEnabled(false);
                }
            };
        table.getSelectionModel().addListSelectionListener(
            tableSelectionListener);
        tableSelectionListener.valueChanged(null);

        ActionListener buttonListener = new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                Object source = event.getSource();
                boolean up;
                if (source == upButton)
                    up = true;
                else if (source == downButton)
                    up = false;
                else
                    return;

                move(table, up);
            }
        };
        upButton.addActionListener(buttonListener);
        downButton.addActionListener(buttonListener);

        Container container = new TransparentPanel(new BorderLayout())
        {
            @Override
            public void setEnabled(boolean enabled)
            {
                super.setEnabled(enabled);
                table.setEnabled(enabled);
                if (enabled)
                {
                    tableSelectionListener.valueChanged(null);
                }
                else
                {
                    upButton.setEnabled(false);
                    downButton.setEnabled(false);
                }
            }
        };
        container.setPreferredSize(new Dimension(WIDTH, 100));
        container.setMaximumSize(new Dimension(WIDTH, 100));

        container.add(new JScrollPane(table), BorderLayout.CENTER);
        container.add(parentButtonBar, BorderLayout.EAST);
        return container;
    }

    /**
     * Returns a component for encodings configuration for the given
     * <tt>mediaType</tt>
     *
     * @param mediaType Either <tt>MediaType.AUDIO</tt> or
     * <tt>MediaType.VIDEO</tt>
     * @param encodingConfiguration The <tt>EncodingConfiguration</tt> instance
     * to use. If null, it will use the current encoding configuration from
     * the media service.
     * @return The component for encodings configuration.
     */
    public Component createEncodingControls(
            MediaType mediaType,
            EncodingConfiguration encodingConfiguration)
    {
        if(encodingConfiguration == null)
        {
            encodingConfiguration
                = mediaService.getCurrentEncodingConfiguration();
        }

        int deviceConfigurationComboBoxModelType;

        switch (mediaType)
        {
        case AUDIO:
            deviceConfigurationComboBoxModelType
                = DeviceConfigurationComboBoxModel.AUDIO;
            break;
        case VIDEO:
            deviceConfigurationComboBoxModelType
                = DeviceConfigurationComboBoxModel.VIDEO;
            break;
        default:
            throw new IllegalArgumentException("mediaType");
        }

        return
            createEncodingControls(
                    deviceConfigurationComboBoxModelType,
                    encodingConfiguration);
    }

    /**
     * Initializes a new <tt>Component</tt> which.is to preview and/or allow
     * detailed configuration of an audio or video <tt>DeviceSystem</tt>.
     *
     * @param type either {@link DeviceConfigurationComboBoxModel#AUDIO} or
     * {@link DeviceConfigurationComboBoxModel#VIDEO}
     * @param comboBox the <tt>JComboBox</tt> which lists the available
     * alternatives and the selection which is to be previewed. May be
     * <tt>null</tt> in the case of audio in which case it is assumed that the
     * user is not allowed to set the <tt>AudioSystem</tt> to be used and the
     * selection is determined by the <tt>DeviceConfiguration</tt> of the
     * <tt>MediaService</tt>.
     * @param prefSize the preferred size to be applied to the preview
     * @return a new <tt>Component</tt> which is to preview and/or allow
     * detailed configuration of the <tt>DeviceSystem</tt> identified by
     * <tt>type</tt> and <tt>comboBox</tt>
     */
    private Component createPreview(
            int type,
            DeviceComboBoxField comboBox,
            Dimension prefSize)
    {
        JComponent preview = null;

        if (type == DeviceConfigurationComboBoxModel.AUDIO)
        {
            AudioSystem audioSystem = null;

            /*
             * If the Audio System combo box is disabled (i.e. the user is not
             * allowed to set the AudioSystem to be used), the current
             * AudioSystem (specified by the DeviceConfiguration of the
             * MediaService) is to be configured.
             */
            if ((comboBox == null) || !comboBox.getComponent().isEnabled())
            {
                audioSystem
                    = mediaService.getDeviceConfiguration().getAudioSystem();
            }
            else
            {
                Object selectedItem = comboBox.getSelectedItem();

                if (selectedItem instanceof AudioSystem)
                {
                    audioSystem = (AudioSystem) selectedItem;

                    AudioSystem mediaServiceDeviceConfigurationAudioSystem
                        = mediaService
                            .getDeviceConfiguration()
                                .getAudioSystem();

                    if (audioSystem
                            != mediaServiceDeviceConfigurationAudioSystem)
                    {
                        logger.warn(
                                "JComboBox.selectedItem is not identical to"
                                    + " MediaService.deviceConfiguration.audioSystem!");
                    }
                }
            }

            if ((audioSystem != null)
                    && !NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(
                            audioSystem.getLocatorProtocol()))
            {
                preview = new TransparentPanel(new GridBagLayout());
                createAudioSystemControls(audioSystem, preview);
            }
            else
            {
                /*
                 * If there are AudioSystems other than "None" and they have all
                 * not been reported as available, then each of them failed to
                 * detect any devices whatsoever.
                 */
                AudioSystem[] audioSystems = AudioSystem.getAudioSystems();

                if ((audioSystems != null) && (audioSystems.length != 1))
                {
                    AudioSystem[] availableAudioSystems
                        = mediaService
                            .getDeviceConfiguration()
                                .getAvailableAudioSystems();

                    if ((availableAudioSystems != null)
                            && (availableAudioSystems.length == 1))
                    {
                        String noAvailableAudioDevice
                            = NeomediaActivator.getResources().getI18NString(
                                "impl.media.configform.NO_AVAILABLE_AUDIO_DEVICE");

                        preview = new TransparentPanel(new GridBagLayout());
                        preview.add(new JLabel(noAvailableAudioDevice));
                    }
                }
            }
        }
        else if (type == DeviceConfigurationComboBoxModel.VIDEO)
        {
            JLabel noPreview
                = new JLabel(
                        NeomediaActivator.getResources().getI18NString(
                                "impl.media.configform.NO_PREVIEW"));

            noPreview.setHorizontalAlignment(SwingConstants.CENTER);
            noPreview.setVerticalAlignment(SwingConstants.CENTER);

            preview = createVideoContainer(noPreview);
            preview.setPreferredSize(prefSize);

            Object selectedItem = comboBox.getSelectedItem();
            CaptureDeviceInfo device = null;
            if (selectedItem
                    instanceof
                        DeviceConfigurationComboBoxModel.CaptureDevice)
                device
                    = ((DeviceConfigurationComboBoxModel.CaptureDevice)
                            selectedItem)
                        .info;

            Exception exception;
            try
            {
                createVideoPreview(device, preview);
                exception = null;
            }
            catch (IOException ex)
            {
                exception = ex;
            }
            catch (MediaException ex)
            {
                exception = ex;
            }
            if (exception != null)
            {
                logger.error(
                        "Failed to create preview for device " + device,
                        exception);
            }
        }

        return preview;
    }

    /**
     * Returns the video configuration panel.
     *
     * @return the video configuration panel
     */
    public Component createVideoConfigPanel()
    {
        return createControls(DeviceConfigurationComboBoxModel.VIDEO);
    }

    /**
     * Returns the <tt>MediaService</tt> instance.
     *
     * @return the <tt>MediaService</tt> instance
     */
    public MediaService getMediaService()
    {
        return mediaService;
    }
}
