/*
 * 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.gui.main.call.conference;

import java.awt.*;

import javax.swing.*;
import javax.swing.text.*;

import net.java.sip.communicator.impl.gui.main.call.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.resources.*;
import net.java.sip.communicator.util.skin.*;

import org.jitsi.service.neomedia.*;

/**
 * The basic panel used to render any conference participant. Meant to be
 * extended for <tt>CallPeer</tt>s and <tt>ConferenceMember</tt>s.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 * @author Lyubomir Marinov
 */
public abstract class BasicConferenceParticipantPanel<T>
    extends TransparentPanel
    implements Skinnable
{
    /**
     * The avatar icon height.
     */
    private static final int AVATAR_HEIGHT = 50;

    /**
     * The avatar icon width.
     */
    private static final int AVATAR_WIDTH = 50;

    /**
     * Background color.
     */
    private static final Color bgColor = new Color(110, 110, 110);

    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = 0L;

    /**
     * The <tt>CallRenderer</tt> which (directly or indirectly) initialized this
     * instance and which uses it to depict the associated
     * conference participant.
     */
    private final SwingCallRenderer callRenderer;

    /**
     * The status of the peer
     */
    private final JLabel callStatusLabel = new JLabel();

    /**
     * Main panel constraints.
     */
    private final GridBagConstraints constraints = new GridBagConstraints();

    /**
     * The component responsible for displaying an error message.
     */
    private JTextComponent errorMessageComponent;

    /**
     * True if the avatar icon was changed and is no more default.
     */
    private boolean iconChanged = false;

    /**
     * The label showing the image of the participant.
     */
    private final JLabel imageLabel = new JLabel();

    /**
     * Indicates if we're in a video interface.
     */
    private final boolean isVideo;

    /**
     * The name bar.
     */
    private final JPanel nameBar
        = new TransparentPanel(new GridBagLayout());

    /**
     * The constraints used to layout the name bar.
     */
    private final GridBagConstraints nameBarConstraints
        = new GridBagConstraints();

    /**
     * The label showing the name of the participant.
     */
    private final JLabel nameLabel = new JLabel();

    /**
     * The conference participant which is depicted by this instance. If it is a
     * Call instance, this instance represents the local peer/user.
     */
    protected final T participant;

    /**
     * The panel containing all peer details.
     */
    private final TransparentPanel peerDetailsPanel = new TransparentPanel();

    /**
     * The right details panel.
     */
    private final TransparentPanel rightDetailsPanel
        = new TransparentPanel(new GridLayout(0, 1));

    /**
     * Security imageID.
     */
    private ImageID securityImageID = ImageLoader.SECURE_OFF_CONF_CALL;

    /**
     * The security status of the peer
     */
    protected SecurityStatusLabel securityStatusLabel = null;

    /**
     * The component showing the sound level of the participant.
     */
    private SoundLevelIndicator soundIndicator;


    /**
     * The status bar of the participant panel.
     */
    private final JPanel statusBar
        = new TransparentPanel(new GridBagLayout());

    /**
     * The status bar constraints.
     */
    private final GridBagConstraints statusBarConstraints
        = new GridBagConstraints();

    /**
     * The panel containing the title of the participant.
     */
    private final JPanel titleBar = new CallTitlePanel(new GridBagLayout());

    /**
     * Indicates if video indicator is enabled for this participant.
     */
    private boolean isVideoIndicatorEnabled = false;

    /**
     * The image of the participant
     */
    private Image participantImage;

    /**
     * Initializes a new <tt>BasicConferenceParticipantPanel</tt> instance which
     * is to depict a specific conference participant.
     *
     * @param callRenderer the renderer for the call
     * @param participant participant
     * @param isVideo indicates if we're in a video interface
     */
    public BasicConferenceParticipantPanel(
            SwingCallRenderer callRenderer,
            T participant,
            boolean isVideo)
    {
        this.callRenderer = callRenderer;
        this.participant = participant;
        this.isVideo = isVideo;

        constraints.anchor = GridBagConstraints.CENTER;

        if (isVideo)
            initVideoConferencePanel();
        else
            initAudioConferencePanel();
    }

    /**
     * Adds the given <tt>component</tt> to the center below the sound bar.
     * @param component the component to add
     */
    public void addToCenter(Component component)
    {
        rightDetailsPanel.add(component);
    }

    /**
     * Adds the given <tt>component</tt> to the name bar.
     * @param component the component to add
     */
    public void addToNameBar(Component component)
    {
        nameBarConstraints.gridx = nameBarConstraints.gridx + 1;
        nameBarConstraints.weightx = 0f;
        nameBarConstraints.insets = new Insets(0, 5, 0, 0);

        this.nameBar.add(component, nameBarConstraints);
    }

    /**
     * Adds the given <tt>component</tt> to the status bar.
     * @param component the component to add
     */
    public void addToStatusBar(Component component)
    {
        statusBarConstraints.gridx = statusBarConstraints.gridx + 1;
        statusBarConstraints.weightx = 0f;
        statusBarConstraints.insets = new Insets(0, 5, 0, 5);

        this.statusBar.add(component, statusBarConstraints);
    }

    /**
     * Gets the <tt>CallPanel</tt> which contains this instances and uses it to
     * depict the associated conference participant.
     *
     * @return the <tt>CallPanel</tt> which contains this instances and uses it
     * to depict the associated conference participant
     */
    public CallPanel getCallPanel()
    {
        return getCallRenderer().getCallContainer();
    }

    /**
     * Gets the <tt>CallRenderer</tt> which (directly or indirectly) initialized
     * this instance and which uses it to depict the associated conference
     * participant.
     *
     * @return the <tt>CallRenderer</tt> which (directly or indirectly)
     * initialized this instance and which uses it to depict the associated
     * conference participant
     */
    public SwingCallRenderer getCallRenderer()
    {
        return callRenderer;
    }

    /**
     * Gets the conference participant depicted by this instance.
     *
     * @return the conference participant depicted by this instance
     */
    public T getParticipant()
    {
        return participant;
    }

    /**
     * Gets the name of the participant.
     * @return returns the name of the participant
     */
    public String getParticipantName()
    {
        return nameLabel.getText();
    }

    private void initAudioConferencePanel()
    {
        soundIndicator
            = new SoundLevelIndicator(
                    SoundLevelChangeEvent.MIN_LEVEL,
                    SoundLevelChangeEvent.MAX_LEVEL);

        soundIndicator.setPreferredSize(new Dimension(80, 30));

        this.setLayout(new GridBagLayout());
        this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        this.initTitleBar();

        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.weightx = 1;
        constraints.weighty = 0;
        constraints.insets = new Insets(0, 0, 0, 0);
        this.add(titleBar, constraints);

        this.initPeerDetailsPanel();

        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridx = 0;
        constraints.gridy = 1;
        constraints.weightx = 1;
        constraints.weighty = 0;
        constraints.insets = new Insets(0, 0, 0, 0);
        this.add(peerDetailsPanel, constraints);
    }

    /**
     * Initializes the details panel for the peer.
     */
    private void initPeerDetailsPanel()
    {
        peerDetailsPanel.setLayout(new GridBagLayout());
        peerDetailsPanel.setBackground(new Color(255, 255, 255));

        setParticipantIcon(null, false);

        constraints.fill = GridBagConstraints.NONE;
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.insets = new Insets(5, 8, 5, 0);

        peerDetailsPanel.add(imageLabel, constraints);

        constraints.fill = GridBagConstraints.BOTH;
        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.weightx = 1f;
        constraints.weighty = 0;
        constraints.insets = new Insets(5, 10, 5, 10);

        rightDetailsPanel.add(soundIndicator);
        peerDetailsPanel.add(rightDetailsPanel, constraints);
    }

    /**
     * Creates <tt>SecurityStatusLabel</tt> and adds it to status bar.
     */
    public void initSecurityStatusLabel()
    {
        securityStatusLabel = new SecurityStatusLabel();
        securityStatusLabel.setSecurityOff();
        addToStatusBar(securityStatusLabel);
    }
    
    /**
     * Initializes the title bar.
     */
    private void initTitleBar()
    {
        titleBar.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 8));

        nameLabel.setForeground(Color.WHITE);
        nameBar.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
        nameBarConstraints.gridx = 0;
        nameBarConstraints.gridy = 0;
        nameBarConstraints.weightx = 1f;
        nameBar.add(nameLabel, nameBarConstraints);

        statusBar.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
        callStatusLabel.setForeground(Color.WHITE);
        statusBarConstraints.gridx = 0;
        statusBarConstraints.gridy = 0;
        statusBarConstraints.weightx = 1f;
        statusBar.add(callStatusLabel, statusBarConstraints);

        GridBagConstraints constraints = new GridBagConstraints();

        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.weightx = 1f;
        constraints.anchor = GridBagConstraints.WEST;
        titleBar.add(nameBar, constraints);

        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.weightx = 1f;
        constraints.anchor = GridBagConstraints.EAST;
        titleBar.add(statusBar, constraints);
    }

    /**
     * Initializes video conference specific panel.
     */
    private void initVideoConferencePanel()
    {
        this.setLayout(new GridBagLayout());
        this.setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));

        this.initTitleBar();

        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.weightx = 1;
        constraints.weighty = 0;
        constraints.insets = new Insets(0, 0, 0, 0);
        this.add(titleBar, constraints);
    }

    /**
     * Reloads default avatar icon.
     */
    public void loadSkin()
    {
        if(!iconChanged)
        {
            setParticipantIcon(null, false);
        }
        
        if(securityStatusLabel != null)
        {
            securityStatusLabel.setIcon(
                    new ImageIcon(ImageLoader.getImage(securityImageID)));
        }
    }

    /**
     * Overrides {@link JComponent#paintComponent(Graphics)} in order to
     * customize the background of this panel.
     *
     * @param g the <tt>Graphics</tt> object used for painting
     */
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        // If we're in a video interface, we have nothing more to do here.
        if (isVideo)
            return;

        g = g.create();
        try
        {
            AntialiasingManager.activateAntialiasing(g);

            g.setColor(bgColor);
            g.fillRoundRect(
                5, 5, this.getWidth() - 10, this.getHeight() - 10, 10, 10);
        }
        finally
        {
            g.dispose();
        }
    }

    /**
     * Indicates that the security has gone off.
     *
     * @param evt Details about the event that caused this message.
     */
    public void securityOff(CallPeerSecurityOffEvent evt)
    {
        if(securityStatusLabel == null)
            return;
        if (evt.getSessionType() == CallPeerSecurityStatusEvent.AUDIO_SESSION)
        {
            securityStatusLabel.setText("");
            securityStatusLabel.setSecurityOff();
            if (securityStatusLabel.getBorder() == null)
                securityStatusLabel.setBorder(
                    BorderFactory.createEmptyBorder(2, 5, 2, 3));
        }
    }

    /**
     * Indicates that the security is turned on.
     * <p>
     * Sets the secured status icon to the status panel and initializes/updates
     * the corresponding security details.
     *
     * @param evt Details about the event that caused this message.
     */
    public void securityOn(CallPeerSecurityOnEvent evt)
    {
        if(securityStatusLabel == null)
            return;
        // If the securityOn is called without a specific event, we'll just set
        // the security label status to on.
        if (evt == null)
        {
            securityStatusLabel.setSecurityOn();
            return;
        }

        SrtpControl srtpControl = evt.getSecurityController();

        if (!srtpControl.requiresSecureSignalingTransport()
                || getCallRenderer()
                    .getCallContainer()
                    .getCallConference()
                    .getCalls()
                    .get(0)
                    .getProtocolProvider()
                    .isSignalingTransportSecure())
        {
            if (srtpControl instanceof ZrtpControl)
            {
                securityStatusLabel.setText("zrtp");

                if (!((ZrtpControl) srtpControl).isSecurityVerified())
                    securityStatusLabel.setSecurityPending();
                else
                    securityStatusLabel.setSecurityOn();
            }
            else
                securityStatusLabel.setSecurityOn();
        }
    }

    /**
     * Indicates that the security status is pending confirmation.
     */
    public void securityPending()
    {
        securityStatusLabel.setSecurityPending();
    }

    /**
     * Sets the reason of a call failure if one occurs. The renderer should
     * display this reason to the user.
     * @param reason the reason to display
     */
    protected void setErrorReason(final String reason)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    setErrorReason(reason);
                }
            });
            return;
        }

        if (errorMessageComponent == null)
        {
            errorMessageComponent = new JTextPane();

            JTextPane textPane = (JTextPane) errorMessageComponent;
            textPane.setEditable(false);
            textPane.setOpaque(false);

            StyledDocument doc = textPane.getStyledDocument();

            MutableAttributeSet standard = new SimpleAttributeSet();
            StyleConstants.setFontFamily(standard,
                                        textPane.getFont().getFamily());
            StyleConstants.setFontSize(standard, 12);
            doc.setParagraphAttributes(0, 0, standard, true);

            addToCenter(errorMessageComponent);
            this.revalidate();
        }

        errorMessageComponent.setText(reason);

        if (isVisible())
        {
            Window parentWindow = SwingUtilities.getWindowAncestor(errorMessageComponent);
            if (parentWindow != null)
                parentWindow.pack();
            errorMessageComponent.repaint();
        }

    }

    /**
     * Sets the image of the participant.
     * @param image the image to set
     */
    public void setParticipantImage(byte[] image)
    {
        setParticipantIcon(image, true);
    }

    /**
     * Sets the name of the participant.
     * @param participantName the name of the participant
     */
    public void setParticipantName(String participantName)
    {
        nameLabel.setText(participantName);
    }

    /**
     * Sets the state of the participant.
     * @param participantState the state of the participant
     */
    public void setParticipantState(final String participantState)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    setParticipantState(participantState);
                }
            });
            return;
        }

        callStatusLabel.setText(participantState.toLowerCase());
    }

    /**
     * Sets the background color of the title panel.
     * @param color the background color to set
     */
    protected void setTitleBackground(Color color)
    {
        titleBar.setBackground(color);
    }

    /**
     * Updates the sound level bar to reflect the new sound level value.
     * @param soundLevel the new sound level value
     */
    public void updateSoundBar(int soundLevel)
    {
        if (soundIndicator != null)
            soundIndicator.updateSoundLevel(soundLevel);
    }

    /**
     * Enables or disabled video indicator in this conference participant
     * panel.
     *
     * @param enable <tt>true</tt> to enable video indicator, <tt>false</tt> -
     * otherwise
     */
    public void enableVideoIndicator(boolean enable)
    {
        isVideoIndicatorEnabled = enable;

        setParticipantIcon(null, true);
    }

    /**
     * Sets the participant icon.
     *
     * @param image the image to set as an icon. If null will use the last set
     * image or the default one
     * @param changed indicates if this is a change of the icon
     */
    private void setParticipantIcon(byte[] image, boolean changed)
    {
        if (image != null && image.length > 0)
            participantImage
                = ImageUtils.getScaledRoundedIcon(  image,
                                                    AVATAR_WIDTH,
                                                    AVATAR_HEIGHT).getImage();
        else if (participantImage == null)
            participantImage
                = ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)
                    .getScaledInstance(
                            AVATAR_WIDTH,
                            AVATAR_HEIGHT,
                            Image.SCALE_SMOOTH);

        Image videoIndicatorImage = null;

        if (isVideoIndicatorEnabled)
            videoIndicatorImage
                = ImageLoader.getImage(ImageLoader.CONFERENCE_VIDEO_INDICATOR);

        Icon avatarIcon = null;
        if (videoIndicatorImage != null && participantImage != null)
            avatarIcon = new ImageIcon(ImageLoader.getImage(
                participantImage,
                videoIndicatorImage,
                participantImage.getWidth(null)
                    - videoIndicatorImage.getWidth(null) + 5,
                participantImage.getHeight(null)
                    - videoIndicatorImage.getHeight(null) + 5));
        else if (participantImage != null)
            avatarIcon = new ImageIcon(participantImage);

        if (avatarIcon != null)
        {
            imageLabel.setIcon(avatarIcon);

            if (changed)
                iconChanged = true;
        }
    }
}
