/*
 * 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.presence;

import java.awt.*;
import java.awt.image.*;

import javax.swing.*;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.event.*;
import net.java.sip.communicator.impl.gui.lookandfeel.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.plugin.desktoputil.presence.avatar.*;
import net.java.sip.communicator.service.globaldisplaydetails.*;
import net.java.sip.communicator.service.globaldisplaydetails.event.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.gui.Container;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.skin.*;

import org.apache.commons.lang3.StringUtils;
import org.jitsi.utils.*;

/**
 * The panel shown on the top of the contact list. It contains user name,
 * current status menu and the avatar of the user.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class AccountStatusPanel
    extends TransparentPanel
    implements  RegistrationStateChangeListener,
                PluginComponentListener,
                GlobalDisplayDetailsListener,
                Skinnable
{
    /**
     * Class id key used in UIDefaults.
     */
    private static final String uiClassID =
        AccountStatusPanel.class.getName() +  "OpaquePanelUI";

    /**
     * Property name to disable the avatar change menu.
     */
    private static final String PNAME_DISABLE_AVATAR_MENU
        = "net.java.sip.communicator.gui.DISABLE_AVATAR_MENU";

    /**
     * Adds the ui class to UIDefaults.
     */
    static
    {
        UIManager.getDefaults().put(uiClassID,
            SIPCommOpaquePanelUI.class.getName());
    }

    /**
     * The desired height of the avatar.
     */
    private static final int AVATAR_ICON_HEIGHT = 50;

    /**
     * The desired width of the avatar.
     */
    private static final int AVATAR_ICON_WIDTH = 50;

    /**
     * The image object storing the avatar.
     */
    private final FramedImage accountImageLabel;

    /**
     * The label showing the name of the user.
     */
    private final JLabel accountNameLabel
        = new EmphasizedLabel(
                GuiActivator
                    .getResources().getI18NString("service.gui.ACCOUNT_ME"));

    /**
     * The background color property.
     */
    private Color bgColor;

    /**
     * The background image property.
     */
    private Image logoBgImage;

    /**
     * The combo box containing status menu.
     */
    private final GlobalStatusSelectorBox statusComboBox;

    /**
     * TexturePaint used to paint background image.
     */
    private TexturePaint texture;

    /**
     * The tool bar plug-in container.
     */
    private final TransparentPanel toolbarPluginPanel;

    /**
     * The south plug-in container.
     */
    private final TransparentPanel southPluginPanel;

    /**
     * Keep reference to plugin container or it will loose its
     * listener.
     */
    private final PluginContainer southPluginContainer;

    /**
     * Keep reference to plugin container or it will loose its
     * listener.
     */
    private final PluginContainer mainToolbarPluginContainer;

    /**
     * When setting global status message, the message will be displayed in this
     * label.
     */
    private final JLabel statusMessageLabel = new JLabel();

    /**
     * This is the panel that contains display name, status message, status
     * selector box and plugins.
     */
    private final TransparentPanel rightPanel = new TransparentPanel();

    /**
     * Creates an instance of <tt>AccountStatusPanel</tt> by specifying the
     * main window, where this panel is added.
     */
    public AccountStatusPanel()
    {
        super(new BorderLayout(10, 0));

        FramedImageWithMenu imageWithMenu
            = new FramedImageWithMenu(
                    new ImageIcon(
                            ImageLoader
                                .getImage(ImageLoader.DEFAULT_USER_PHOTO)),
                    AVATAR_ICON_WIDTH,
                    AVATAR_ICON_HEIGHT);
        if (!GuiActivator.getConfigurationService().getBoolean(
            PNAME_DISABLE_AVATAR_MENU, false))
        {
            imageWithMenu.setPopupMenu(new SelectAvatarMenu(imageWithMenu));
        }

        this.accountImageLabel = imageWithMenu;

        accountNameLabel.setFont(
            accountNameLabel.getFont().deriveFont(12f));
        accountNameLabel.setOpaque(false);

        statusComboBox = new GlobalStatusSelectorBox(this);
        // Align status combo box with account name field.
        statusComboBox.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

        TransparentPanel statusToolsPanel
            = new TransparentPanel(new BorderLayout(0, 0));

        SIPCommMenuBar statusMenuBar = new SIPCommMenuBar();
        statusMenuBar.add(statusComboBox);
        statusToolsPanel.add(statusMenuBar, BorderLayout.WEST);

        toolbarPluginPanel
            = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));

        mainToolbarPluginContainer = new PluginContainer(toolbarPluginPanel,
                            Container.CONTAINER_MAIN_TOOL_BAR);

        statusToolsPanel.add(toolbarPluginPanel, BorderLayout.EAST);

        statusMessageLabel.setFont(
            statusMessageLabel.getFont().deriveFont(9f));
        statusMessageLabel.setForeground(Color.GRAY);


        rightPanel.setLayout(new BorderLayout(0, 0));
        rightPanel.add(accountNameLabel, BorderLayout.NORTH);
        rightPanel.add(statusMessageLabel, BorderLayout.CENTER);
        rightPanel.add(statusToolsPanel, BorderLayout.SOUTH);

        this.add(accountImageLabel, BorderLayout.WEST);
        this.add(rightPanel, BorderLayout.CENTER);

        southPluginPanel = new TransparentPanel(new BorderLayout());

        southPluginContainer = new PluginContainer(
            southPluginPanel,
            Container.CONTAINER_ACCOUNT_SOUTH);

        this.add(southPluginPanel, BorderLayout.SOUTH);

        loadSkin();

        GuiActivator.getUIService().addPluginComponentListener(this);

        GlobalDisplayDetailsService gDService
            = GuiActivator.getGlobalDisplayDetailsService();
        if(gDService != null)
        {
            gDService.addGlobalDisplayDetailsListener(this);

            String globalDisplayName = gDService.getGlobalDisplayName();

            if(StringUtils.isNotEmpty(globalDisplayName))
                accountNameLabel.setText(globalDisplayName);
        }
    }

    /**
     * Updates status message.
     * @param text
     */
    void setStatusMessage(String text)
    {
        if(text == null)
        {
            statusMessageLabel.setText("");
            rightPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
        }
        else
        {
            rightPanel.setBorder(null);
            statusMessageLabel.setText(text);
        }
    }

    /**
     * Adds the account given by <tt>protocolProvider</tt> in the contained
     * status combo box.
     * @param protocolProvider the <tt>ProtocolProviderService</tt>
     * corresponding to the account to add
     */
    public void addAccount(final ProtocolProviderService protocolProvider)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    addAccount(protocolProvider);
                }
            });
            return;
        }

        statusComboBox.addAccount(protocolProvider);

        protocolProvider.addRegistrationStateChangeListener(this);
    }

    /**
     * Removes the account given by <tt>protocolProvider</tt> from the contained
     * status combo box.
     * @param protocolProvider the <tt>ProtocolProviderService</tt>
     * corresponding to the account to remove
     */
    public void removeAccount(final ProtocolProviderService protocolProvider)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    removeAccount(protocolProvider);
                }
            });
            return;
        }

        if (containsAccount(protocolProvider))
        {
            statusComboBox.removeAccount(protocolProvider);
            protocolProvider.removeRegistrationStateChangeListener(this);
        }
    }

    /**
     * Checks if an account corresponding to the given <tt>protocolProvider</tt>
     * is contained in the contained status combo box.
     * @param protocolProvider the <tt>ProtocolProviderService</tt>
     * corresponding to the account to check for
     * @return <tt>true</tt> to indicate that an account corresponding to the
     * given <tt>protocolProvider</tt> is contained in the status box,
     * <tt>false</tt> - otherwise
     */
    public boolean containsAccount(ProtocolProviderService protocolProvider)
    {
        return statusComboBox.containsAccount(protocolProvider);
    }

    /**
     * Updates the current status of the <tt>protocolProvider</tt> with the
     * <tt>newStatus</tt>. If status is null uses the current status.
     * @param protocolProvider the <tt>ProtocolProviderService</tt> to update
     * @param newStatus the new status to set
     */
    public void updateStatus(final ProtocolProviderService protocolProvider,
                             final PresenceStatus newStatus)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    updateStatus(protocolProvider, newStatus);
                }
            });
            return;
        }

        if(newStatus != null)
            statusComboBox.updateStatus(protocolProvider, newStatus);
        else
            statusComboBox.updateStatus(protocolProvider);
    }

    /**
     * Updates the current status of the <tt>protocolProvider</tt>.
     * @param protocolProvider the <tt>ProtocolProviderService</tt> to update
     */
    public void updateStatus( ProtocolProviderService protocolProvider)
    {
        updateStatus(protocolProvider, null);
    }

    /**
     * Updates the image that is shown.
     * @param img the new image.
     */
    public void updateImage(ImageIcon img)
    {
        accountImageLabel.setImageIcon(img.getImage());
        accountImageLabel.setMaximumSize(
            new Dimension(AVATAR_ICON_WIDTH, AVATAR_ICON_HEIGHT));
        revalidate();
        repaint();
    }

    /**
     * Starts connecting user interface for the given <tt>protocolProvider</tt>.
     * @param protocolProvider the <tt>ProtocolProviderService</tt> to start
     * connecting for
     */
    public void startConnecting(final ProtocolProviderService protocolProvider)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    startConnecting(protocolProvider);
                }
            });
            return;
        }

        statusComboBox.startConnecting(protocolProvider);
    }

    /**
     * Stops connecting user interface for the given <tt>protocolProvider</tt>.
     * @param protocolProvider the <tt>ProtocolProviderService</tt> to stop
     * connecting for
     */
    public void stopConnecting(final ProtocolProviderService protocolProvider)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    stopConnecting(protocolProvider);
                }
            });
            return;
        }

        statusComboBox.stopConnecting(protocolProvider);
    }

    /**
     * Returns <tt>true</tt> if there are selected status selector boxes,
     * otherwise returns <tt>false</tt>.
     * @return <tt>true</tt> if there are selected status selector boxes,
     * otherwise returns <tt>false</tt>
     */
    public boolean hasSelectedMenus()
    {
        return statusComboBox.hasSelectedMenus();
    }

    /**
     * Paints this component.
     * @param g the <tt>Graphics</tt> object used for painting
     */
    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        if (logoBgImage != null)
        {
            Graphics2D g2 = (Graphics2D) g;

            g.setColor(bgColor);
            g2.setPaint(texture);
            g2.fillRect(0, 0, this.getWidth(), this.getHeight());

            g.drawImage(
                logoBgImage,
                this.getWidth() - logoBgImage.getWidth(null),
                0,
                null);
        }
    }

    /**
     * Indicates that a plug-in component is registered to be added in a
     * container. If the plug-in component in the given event is registered for
     * this container then we add it.
     * @param event <tt>PluginComponentEvent</tt> that notified us
     */
    public void pluginComponentAdded(PluginComponentEvent event)
    {
        PluginComponentFactory pluginComponent =
            event.getPluginComponentFactory();
        Container containerID = pluginComponent.getContainer();
        /*
        // avoid early creating of components by calling getComponent
        Object component = pluginComponent.getComponent();

        if (!(component instanceof Component))
            return;
        */

        if (containerID.equals(Container.CONTAINER_MAIN_TOOL_BAR)
           || containerID.equals(Container.CONTAINER_ACCOUNT_SOUTH))
        {
            this.revalidate();
            this.repaint();
        }
    }

    /**
     * Indicates that a plug-in component is registered to be removed from a
     * container. If the plug-in component in the given event is registered for
     * this container then we remove it.
     * @param event <tt>PluginComponentEvent</tt> that notified us
     */
    public void pluginComponentRemoved(PluginComponentEvent event)
    {
        PluginComponentFactory pluginComponent =
            event.getPluginComponentFactory();
        Container pluginContainer = pluginComponent.getContainer();
        /*Object component = pluginComponent.getComponent();

        if (!(component instanceof Component))
            return;
        */

        if (pluginContainer.equals(Container.CONTAINER_MAIN_TOOL_BAR)
            || pluginContainer.equals(Container.CONTAINER_ACCOUNT_SOUTH))
        {
            this.revalidate();
            this.repaint();
        }
    }

    /**
     * Called whenever a new avatar is defined for one of the protocols that we
     * have subscribed for.
     *
     * @param event the event containing the new image
     */
    public void globalDisplayNameChanged(
            final GlobalDisplayNameChangeEvent event)
    {
        if (!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    globalDisplayNameChanged(event);
                    return;
                }
            });
            return;
        }

        String displayName = event.getNewDisplayName();

        if(StringUtils.isNotEmpty(displayName))
            accountNameLabel.setText(displayName);
    }

    /**
     * Called whenever a new avatar is defined for one of the protocols that we
     * have subscribed for.
     *
     * @param event the event containing the new image
     */
    public void globalDisplayAvatarChanged(
            final GlobalAvatarChangeEvent event)
    {
        if (!SwingUtilities.isEventDispatchThread())
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    globalDisplayAvatarChanged(event);
                    return;
                }
            });

        byte[] avatarImage = event.getNewAvatar();

        // If there is no avatar image set, then displays the default one.
        if(avatarImage != null)
        {
            accountImageLabel.setImageIcon(avatarImage);
        }
    }

    /**
     * Updates account information when a protocol provider is registered.
     * @param evt the <tt>RegistrationStateChangeEvent</tt> that notified us
     * of the change
     */
    public void registrationStateChanged(RegistrationStateChangeEvent evt)
    {
        ProtocolProviderService protocolProvider = evt.getProvider();

        // There is nothing we can do when account is registering,
        // will set only connecting state later.
        // While dispatching the registering if the state of the provider
        // changes to registered we may end with client logged off
        // this may happen if registered is coming too quickly after registered
        // Dispatching registering is doing some swing stuff which
        // is scheduled in EDT and so can be executing when already registered
        if (evt.getNewState().equals(RegistrationState.REGISTERING))
        {
            startConnecting(protocolProvider);
        }
        else
            this.updateStatus(protocolProvider);
    }

    /**
     * Loads images for the account status panel.
     */
    public void loadSkin()
    {
        bgColor
            = new Color(GuiActivator.getResources()
                .getColor("service.gui.LOGO_BAR_BACKGROUND"));

        logoBgImage
            = ImageLoader.getImage(ImageLoader.WINDOW_TITLE_BAR);

        // texture
        BufferedImage bgImage
            = ImageLoader.getImage(ImageLoader.WINDOW_TITLE_BAR_BG);
        texture
            = new TexturePaint(
                    bgImage,
                    new Rectangle(
                            0,
                            0,
                            bgImage.getWidth(null),
                            bgImage.getHeight(null)));

        GuiActivator.getUIService().addPluginComponentListener(this);

        byte[] avatar = null;

        if(GuiActivator.getGlobalDisplayDetailsService() != null)
        {
            avatar = GuiActivator.getGlobalDisplayDetailsService()
                .getGlobalDisplayAvatar();
        }

        if (avatar == null || avatar.length <= 0)
            accountImageLabel.setImageIcon(ImageLoader
                .getImage(ImageLoader.DEFAULT_USER_PHOTO));
        else
            accountImageLabel.setImageIcon(avatar);
    }

    /**
     * Returns the name of the L&F class that renders this component.
     *
     * @return the string "TreeUI"
     * @see JComponent#getUIClassID
     * @see UIDefaults#getUI
     */
    @Override
    public String getUIClassID()
    {
        if(ConfigurationUtils.isTransparentWindowEnabled())
            return uiClassID;
        else
            return super.getUIClassID();
    }
}
