/*
 * 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.main.account.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.util.skin.*;

import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.util.*;
import org.jitsi.util.swing.TransparentPanel;

import com.explodingpixels.macwidgets.*;

/**
 * The <tt>PreCallDialog</tt> is a dialog allowing to pick-up or hangup a call.
 * This is the parent dialog of the <tt>ReceivedCallDialog</tt> and the
 * <tt>ChooseCallAccountDialog</tt>.
 *
 * @author Yana Stamcheva
 */
public abstract class PreCallDialog
    implements ActionListener,
               Skinnable
{
    /**
     * List of PreCallDialog windows that are currently visible to the user.
     */
    private static final List<Window> visibleWindows = new ArrayList<Window>();

    /**
     * The call button name.
     */
    private static final String CALL_BUTTON = "CallButton";

    /**
     * The conference call button name.
     */
    private static final String CONF_CALL_BUTTON = "ConfCallButton";

    /**
     * The call button name.
     */
    private static final String VIDEO_CALL_BUTTON = "VideoCallButton";

    /**
     * The hangup button name.
     */
    private static final String HANGUP_BUTTON = "HangupButton";

    /**
     * Call button.
     */
    private SIPCommButton callButton;

    /**
     * Conference call button to answer the call in an existing call or
     * conference.
     */
    private SIPCommButton mergeCallButton;

    /**
     * Video call button.
     */
    private SIPCommButton videoCallButton;

    /**
     * HandUp button.
     */
    private SIPCommButton hangupButton;

    /**
     * Call label for display name.
     */
    private JLabel callLabelDisplayName;

    /**
     * Call label for address.
     */
    private JLabel callLabelAddress;

    /**
     * Call label for account.
     */
    private JLabel callLabelAccount;

    /**
     * The label that will contain the peer image.
     */
    private JLabel callLabelImage;

    /**
     * The combo box containing a list of accounts to choose from.
     */
    private JComboBox accountsCombo;

    /**
     * The window handling received calls.
     */
    private final Window preCallWindow;

    /**
     * If it is a video call.
     */
    private final boolean video;

    /**
     * If the call should be answered in an existing call.
     */
    private final boolean mergeCall;

    /**
     * Creates an instance of <tt>PreCallDialog</tt> by specifying the dialog
     * title.
     *
     * @param title the title of the dialog
     * @param video if it is a video call
     * @param existingCall true to answer call in an existing call
     */
    public PreCallDialog(String title, boolean video, boolean existingCall)
    {
        this(title, null, null, video, existingCall);
    }

    /**
     * Creates an instance of <tt>PreCallDialog</tt> by specifying the dialog
     * title and the text to show.
     *
     * @param title the title of the dialog
     * @param text the text to show
     * @param accounts the list of accounts to choose from
     */
    public PreCallDialog(String title, String text, Account[] accounts)
    {
        this(title, text, accounts, false, false);
    }

    /**
     * Creates an instance of <tt>PreCallDialog</tt> by specifying the dialog
     * title and the text to show.
     *
     * @param title the title of the dialog
     * @param text the text to show
     * @param accounts the list of accounts to choose from
     * @param video if it is a video call
     * @param existingCall true to answer call in an existing call
     */
    public PreCallDialog(String title, String text, Account[] accounts,
        boolean video, boolean existingCall)
    {
        preCallWindow = createPreCallWindow(title, text, accounts);

        if (video)
        {
            // Make sure there is a VIDEO MediaDevice and it is capable of
            // capture/sending
            MediaDevice mediaDevice
                = GuiActivator.getMediaService().getDefaultDevice(
                        MediaType.VIDEO,
                        MediaUseCase.CALL);

            if ((mediaDevice == null)
                    || !mediaDevice.getDirection().allowsSending())
                video = false;
        }

        this.video = video;
        this.mergeCall = existingCall;

        initComponents();
    }

    /**
     * Creates this received call window.
     *
     * @param title the title of the created window
     * @param text the text to show
     * @param accounts the list of accounts to choose from
     * @return the created window
     */
    private Window createPreCallWindow( String title,
                                        String text,
                                        Account[] accounts)
    {
        Window receivedCallWindow = null;

        if (OSUtils.IS_MAC)
        {
            HudWindow window = new HudWindow();
            window.hideCloseButton();

            JDialog dialog = window.getJDialog();
            dialog.setUndecorated(true);
            dialog.setTitle(title);

            receivedCallWindow = window.getJDialog();

            callLabelDisplayName = HudWidgetFactory.createHudLabel("");
            callLabelAddress = HudWidgetFactory.createHudLabel("");
            callLabelImage = HudWidgetFactory.createHudLabel("");

            if (accounts != null)
            {
                accountsCombo
                    = HudWidgetFactory.createHudComboBox(
                            new DefaultComboBoxModel(accounts));
            }
            else
                callLabelAccount = HudWidgetFactory.createHudLabel("");
        }
        else
        {
            SIPCommFrame frame = new SIPCommFrame(false);

            frame.setUndecorated(true);

            receivedCallWindow = frame;

            callLabelDisplayName = new JLabel();
            callLabelAddress = new JLabel();
            callLabelImage = new JLabel();

            if (accounts != null)
                accountsCombo = new JComboBox(accounts);
            else
                callLabelAccount = new JLabel();
        }

        if (text != null)
            callLabelDisplayName.setText(text);

        receivedCallWindow.setAlwaysOnTop(true);

        // Register the JFrame so the dialog window is able to get dragged
        // across the screen
        ComponentMover.registerComponent(receivedCallWindow);

        // prevents dialog window to get unwanted key events and when going
        // on top on linux, it steals focus and if we are accidentally
        // writing something and pressing enter a call get answered
        receivedCallWindow.setFocusableWindowState(false);

        return receivedCallWindow;
    }

    /**
     * Initializes all components in this panel.
     */
    private void initComponents()
    {
        JPanel mainPanel = new TransparentPanel(new BorderLayout(10, 0));

        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        // disable html rendering
        callLabelDisplayName.putClientProperty("html.disable", Boolean.TRUE);
        callLabelAddress.putClientProperty("html.disable", Boolean.TRUE);
        callLabelImage.putClientProperty("html.disable", Boolean.TRUE);

        if(callLabelAccount != null)
            callLabelAccount.putClientProperty("html.disable", Boolean.TRUE);

        JComponent buttonsPanel = new CallToolBar(false, true);

        callButton = new SIPCommButton();

        if(mergeCall)
            mergeCallButton = new SIPCommButton();

        if(video)
            videoCallButton = new SIPCommButton();

        hangupButton = new SIPCommButton();

        callButton.setName(CALL_BUTTON);
        hangupButton.setName(HANGUP_BUTTON);

        callButton.addActionListener(this);

        if(mergeCall)
        {
            mergeCallButton.setName(CONF_CALL_BUTTON);
            mergeCallButton.addActionListener(this);
        }

        hangupButton.addActionListener(this);

        if(video)
        {
            videoCallButton.setName(VIDEO_CALL_BUTTON);
            videoCallButton.addActionListener(this);
        }

        preCallWindow.add(mainPanel);

        mainPanel.add(callLabelImage, BorderLayout.WEST);

        JPanel labelsPanel = new TransparentPanel();
        labelsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));

        labelsPanel.setLayout(new BoxLayout(labelsPanel, BoxLayout.Y_AXIS));
        callLabelDisplayName.setAlignmentX(JLabel.LEFT_ALIGNMENT);
        labelsPanel.add(callLabelDisplayName);

        labelsPanel.add(Box.createVerticalStrut(3));
        callLabelAddress.setAlignmentX(JLabel.LEFT_ALIGNMENT);
        labelsPanel.add(callLabelAddress);

        if(callLabelAccount != null)
        {
            labelsPanel.add(Box.createVerticalStrut(3));
            callLabelAccount.setAlignmentX(JLabel.LEFT_ALIGNMENT);
            labelsPanel.add(callLabelAccount);
        }

        if (accountsCombo != null)
        {
            labelsPanel.add(Box.createVerticalStrut(3));
            accountsCombo.setAlignmentX(JLabel.LEFT_ALIGNMENT);
            labelsPanel.add(accountsCombo);
        }

        mainPanel.add(labelsPanel, BorderLayout.CENTER);

        // Loads skin resources.
        loadSkin();

        JPanel rightPanel = new TransparentPanel();
        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));

        rightPanel.setBorder(BorderFactory.createEmptyBorder(6, 0, 0, 0));

        buttonsPanel.add(callButton);

        if(mergeCall)
            buttonsPanel.add(mergeCallButton);

        if(video)
            buttonsPanel.add(videoCallButton);

        buttonsPanel.add(hangupButton);

        buttonsPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
        rightPanel.add(Box.createVerticalGlue());
        rightPanel.add(buttonsPanel);
        rightPanel.add(Box.createVerticalGlue());
        mainPanel.add(rightPanel, BorderLayout.EAST);
    }

    /**
     * Packs the content of this dialog.
     */
    public void pack()
    {
        preCallWindow.pack();
    }

    /**
     * Shows/hides this dialog.
     *
     * Synchronized to ensure that only one window can update the global
     * visibleWindows datastructure at a time.
     *
     * @param isVisible indicates if this dialog should be shown or hidden
     */
    public synchronized void setVisible(boolean isVisible)
    {
        if (isVisible)
        {
            preCallWindow.pack();

            // Set the visibility before setting the location. This is the only
            // reliable way of ensure that the location is respected.
            preCallWindow.setVisible(isVisible);

            if (visibleWindows.isEmpty())
            {
                // No existing visible windows displayed. Just center this one.
                preCallWindow.setLocationRelativeTo(null);
            }
            else
            {
                // Get the location of last displayed window and and the
                // height of it, so the this window appears directly underneath.
                Window last = visibleWindows.get(visibleWindows.size() - 1);
                int newX = last.getX();
                int newY = last.getY() + last.getHeight();

                preCallWindow.setLocation(newX, newY);
            }

            visibleWindows.add(preCallWindow);
        }
        else
        {
            // Remove the window from the list of visible windows. This also
            // occurs in dispose()
            visibleWindows.remove(preCallWindow);
            preCallWindow.setVisible(isVisible);
        }
    }

    /**
     * Indicates if this dialog is currently visible.
     *
     * @return <tt>true</tt> if this dialog is currently visible, <tt>false</tt>
     * otherwise
     */
    public boolean isVisible()
    {
        return preCallWindow.isVisible();
    }

    /**
     * Disposes this window.
     */
    public void dispose()
    {
        visibleWindows.remove(preCallWindow);
        preCallWindow.dispose();
    }

    /**
     * Returns the labels contained in this dialog.
     * The first is the label that will contain the image.
     * The second one is the one with the display name(s).
     * The third is the one that will hold the peer address(s).
     *
     * @return the call label contained in this dialog
     */
    public JLabel[] getCallLabels()
    {
        if(callLabelAccount == null)
            return new JLabel[]{
                callLabelImage, callLabelDisplayName, callLabelAddress};
        else
            return new JLabel[]{
                callLabelImage,
                callLabelDisplayName,
                callLabelAddress,
                callLabelAccount};
    }

    /**
     * Returns the accounts combo box contained in this dialog.
     *
     * @return the accounts combo box contained in this dialog
     */
    public JComboBox getAccountsCombo()
    {
        return accountsCombo;
    }

    /**
     * Reloads icons.
     */
    public void loadSkin()
    {
        callButton.setBackgroundImage(
            ImageLoader.getImage(ImageLoader.INCOMING_CALL_BUTTON_BG));
        callButton.setRolloverImage(
            ImageLoader.getImage(ImageLoader.INCOMING_CALL_BUTTON_ROLLOVER));
        callButton.setPressedImage(
            ImageLoader.getImage(ImageLoader.INCOMING_CALL_BUTTON_PRESSED));

        if (videoCallButton != null)
        {
            videoCallButton.setBackgroundImage(
                ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_BG));
            videoCallButton.setRolloverImage(
                ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_ROLLOVER));
            videoCallButton.setPressedImage(
                ImageLoader.getImage(ImageLoader.CALL_VIDEO_BUTTON_PRESSED));
        }

        if (mergeCallButton != null)
        {
            mergeCallButton.setBackgroundImage(
                ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON_BG));
            mergeCallButton.setRolloverImage(
                ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON_ROLLOVER));
            mergeCallButton.setPressedImage(
                ImageLoader.getImage(ImageLoader.MERGE_CALL_BUTTON_PRESSED));
        }

        hangupButton.setBackgroundImage(
            ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG));
        hangupButton.setRolloverImage(
            ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_ROLLOVER));
        hangupButton.setPressedImage(
            ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_PRESSED));
    }

    /**
     * Handles <tt>ActionEvent</tt>s triggered by pressing the call or the
     * hangup buttons.
     * @param e The <tt>ActionEvent</tt> to handle.
     */
    public void actionPerformed(ActionEvent e)
    {
        JButton button = (JButton) e.getSource();
        String buttonName = button.getName();

        if (buttonName.equals(CALL_BUTTON))
        {
            callButtonPressed();
        }
        else if (buttonName.equals(CONF_CALL_BUTTON))
        {
            mergeCallButtonPressed();
        }
        else if (buttonName.equals(VIDEO_CALL_BUTTON))
        {
            videoCallButtonPressed();
        }
        else if (buttonName.equals(HANGUP_BUTTON))
        {
            hangupButtonPressed();
        }

        dispose();
    }

    /**
     * Indicates that the call button has been pressed.
     */
    public abstract void callButtonPressed();

    /**
     * Indicates that the conference call button has been pressed.
     */
    public abstract void mergeCallButtonPressed();

    /**
     * Indicates that the hangup button has been pressed.
     */
    public abstract void hangupButtonPressed();

    /**
     * Indicates that the video call button has been pressed.
     */
    public abstract void videoCallButtonPressed();
}
