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

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;

import javax.swing.*;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.plugin.desktoputil.*;
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.util.*;
import org.jitsi.utils.*;

/**
 * The dialog created when an incoming call is received.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class ReceivedCallDialog
    extends PreCallDialog
    implements ActionListener,
               CallListener,
               Skinnable
{
    /**
     * The incoming call to render.
     */
    private final Call incomingCall;

    /**
     * The list of resolvers.
     */
    private List<DisplayNameAndImageChangeListener> detailsResolvers
        = new ArrayList<DisplayNameAndImageChangeListener>();

    /**
     * Creates a <tt>ReceivedCallDialog</tt> by specifying the associated call.
     *
     * @param call The associated with this dialog incoming call.
     * @param video if the call is a video call
     * @param existingCall true to answer the call in an existing call (thus
     * obtaining a conference call)
     * @param desktopStreaming whether the incoming call is desktop streaming
     */
    public ReceivedCallDialog(
        Call call,
        boolean video,
        boolean existingCall,
        boolean desktopStreaming)
    {
        super(GuiActivator.getResources()
            .getSettingsString("service.gui.APPLICATION_NAME")
            + " "
            + (desktopStreaming ?
                GuiActivator.getResources()
                    .getI18NString("service.gui.INCOMING_SCREEN_SHARE_STATUS")
                    .toLowerCase()
             : GuiActivator.getResources()
                    .getI18NString("service.gui.INCOMING_CALL_STATUS")
                    .toLowerCase())
            , video, existingCall);

        this.incomingCall = call;

        OperationSetBasicTelephony<?> basicTelephony
            = call.getProtocolProvider().getOperationSet(
                    OperationSetBasicTelephony.class);

        basicTelephony.addCallListener(this);

        initCallLabel(getCallLabels());
    }

    /**
     * Initializes the label of the received call.
     *
     * @param callLabel The label to initialize.
     */
    private void initCallLabel(final JLabel callLabel[])
    {
        Iterator<? extends CallPeer> peersIter = incomingCall.getCallPeers();

        String textAddress = "";
        String textAccount = "";

        ImageIcon imageIcon =
            ImageUtils.scaleIconWithinBounds(ImageLoader
                .getImage(ImageLoader.DEFAULT_USER_PHOTO), 40, 45);

        // we use a table to store peers and so far resolved names
        // in order to be able to reconstruct the text to display if we
        // receive the display name later
        Hashtable<CallPeer, String> peerNamesTable
            = new Hashtable<CallPeer, String>();

        while (peersIter.hasNext())
        {
            final CallPeer peer = peersIter.next();

            String peerAddress = getPeerDisplayAddress(peer);

            textAccount = peer.getProtocolProvider().getAccountID()
                .getDisplayName();

            DisplayNameAndImageChangeListener listener
                = new DisplayNameAndImageChangeListener(peer, peerNamesTable);
            detailsResolvers.add(listener);

            String displayName =
                CallManager.getPeerDisplayName(peer, listener);

            if(displayName != null)
                peerNamesTable.put(peer, displayName);

            if(StringUtils.isNotEmpty(peerAddress))
                textAddress = callLabel[2].getText()
                    + trimPeerAddressToUsername(peerAddress);

            // More peers.
            if (peersIter.hasNext())
            {
                textAddress += ", ";
            }
            else
            {
                byte[] image = CallManager.getPeerImage(peer);

                if (image != null && image.length > 0)
                    imageIcon = ImageUtils.getScaledRoundedIcon(image, 50, 50);
            }
        }

        // will update callLabel[1] with the already found names
        updateTextDisplayName(peerNamesTable);

        callLabel[0].setIcon(imageIcon);

        callLabel[2].setText(textAddress);
        callLabel[2].setForeground(Color.GRAY);

        if(textAccount != null)
        {
            callLabel[3].setText(
                GuiActivator.getResources().getI18NString("service.gui.TO")
                + " " + textAccount);
        }
    }

    /**
     * Uses a table mapping peer to name to update call label display name.
     * @param peerNamesTable the peer to name mapping
     */
    private void updateTextDisplayName(
        final Hashtable<CallPeer, String> peerNamesTable)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    updateTextDisplayName(peerNamesTable);
                }
            });
            return;
        }

        boolean hasMorePeers = false;
        String textDisplayName = "";

        Iterator<? extends CallPeer> peersIter = incomingCall.getCallPeers();

        JLabel label = getCallLabels()[1];

        while (peersIter.hasNext())
        {
            final CallPeer peer = peersIter.next();

            // More peers.
            if (peersIter.hasNext())
            {
                textDisplayName = label.getText()
                    + peerNamesTable.get(peer) + ", ";

                hasMorePeers = true;
            }
            // Only one peer.
            else
            {
                textDisplayName = GuiActivator.getResources()
                    .getI18NString("service.gui.IS_CALLING",
                        new String[]{ peerNamesTable.get(peer) });
            }
        }

        if (hasMorePeers)
            textDisplayName = GuiActivator.getResources()
                .getI18NString("service.gui.ARE_CALLING",
                    new String[]{textDisplayName});

        label.setText(textDisplayName);
    }

    /**
     * {@inheritDoc}
     *
     * When the <tt>Call</tt> depicted by this dialog is (remotely) ended,
     * close/dispose of this dialog.
     *
     * @param event a <tt>CallEvent</tt> which specifies the <tt>Call</tt> that
     * has ended
     */
    public void callEnded(CallEvent event)
    {
        if (event.getSourceCall().equals(incomingCall))
            dispose();
    }

    @Override
    public void dispose()
    {
        // no more queries needed, if they are still running
        for(DisplayNameAndImageChangeListener listener : detailsResolvers)
        {
            listener.setInterested(false);
        }

        try
        {
            OperationSetBasicTelephony<?> basicTelephony
                = incomingCall.getProtocolProvider().getOperationSet(
                        OperationSetBasicTelephony.class);

            basicTelephony.removeCallListener(this);
        }
        finally
        {
            super.dispose();
        }
    }

    /**
     * Indicates that an incoming call has been received.
     */
    public void incomingCallReceived(CallEvent event) {}

    /**
     * Indicates that an outgoing call has been created.
     */
    public void outgoingCallCreated(CallEvent event) {}

    /**
     * Answers the call when the call button has been pressed.
     */
    @Override
    public void callButtonPressed()
    {
        CallManager.answerCall(incomingCall);
    }

    /**
     * Answers the call in an existing call when the existing call
     * button has been pressed.
     */
    @Override
    public void mergeCallButtonPressed()
    {
        CallManager.answerCallInFirstExistingCall(incomingCall);
    }

    /**
     * Answers the call when the call button has been pressed.
     */
    @Override
    public void videoCallButtonPressed()
    {
        CallManager.answerVideoCall(incomingCall);
    }

    /**
     * Hangups the call when the call button has been pressed.
     */
    @Override
    public void hangupButtonPressed()
    {
        CallManager.hangupCall(incomingCall);
    }

    /**
     * A informative text to show for the peer. If display name and
     * address are the same return null.
     * @param peer the peer.
     * @return the text contain address.
     */
    private String getPeerDisplayAddress(CallPeer peer)
    {
        String peerAddress = peer.getAddress();

        if(StringUtils.isBlank(peerAddress))
            return null;
        else
        {
            return
                peerAddress.equalsIgnoreCase(peer.getDisplayName())
                    ? null
                    : peerAddress;
        }
    }

    /**
     * Removes the domain/server part from the address only if it is enabled.
     * @param peerAddress peer address to change.
     * @return username part of the address.
     */
    private String trimPeerAddressToUsername(String peerAddress)
    {
        if(ConfigurationUtils.isHideDomainInReceivedCallDialogEnabled())
        {
            if(peerAddress != null && !peerAddress.startsWith("@"))
            {
                return peerAddress.split("@")[0];
            }
        }

        return peerAddress;
    }

    /**
     * Listens for display name update and image update, some searches for
     * display name are slow, so we add a listener to update them when
     * result comes in.
     */
    private class DisplayNameAndImageChangeListener
        implements CallManager.DetailsResolveListener
    {
        /**
         * The call peer we are interested in.
         */
        private CallPeer peer;

        /**
         * The table with all discovered peer names.
         */
        private Hashtable<CallPeer, String> peerNamesTable;

        /**
         * By default we are interested in events.
         */
        private boolean interested = true;

        /**
         * Constructs.
         * @param peer
         * @param peerNamesTable
         */
        private DisplayNameAndImageChangeListener(
            CallPeer peer,
            Hashtable<CallPeer, String> peerNamesTable)
        {
            this.peer = peer;
            this.peerNamesTable = peerNamesTable;
        }

        @Override
        public void displayNameUpdated(String displayName)
        {
            if(displayName != null)
            {
                peerNamesTable.put(peer, displayName);

                updateTextDisplayName(peerNamesTable);
            }
        }

        @Override
        public void imageUpdated(byte[] image)
        {
            if(image != null)
                ImageUtils.setScaledLabelImage(
                    getCallLabels()[0], image, 50, 50);
        }

        /**
         * Are we interested.
         * @return
         */
        @Override
        public boolean isInterested()
        {
            return interested;
        }

        /**
         * Changes the interested value.
         * @param value
         */
        public void setInterested(boolean value)
        {
            this.interested = value;
        }
    }
}
