/**
 * Copyright (C) 2004-2011 Jive Software. All rights reserved.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 org.jivesoftware.spark.plugin.jingle;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import org.jivesoftware.resource.Res;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingleold.JingleSession;
import org.jivesoftware.smackx.jingleold.JingleSessionStatePending;
import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingleold.media.PayloadType;
import org.jivesoftware.smackx.jingleold.nat.TransportCandidate;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.component.FileDragLabel;
import org.jivesoftware.spark.phone.PhoneManager;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.ChatRoomClosingListener;
import org.jivesoftware.spark.ui.ContactItem;
import org.jivesoftware.spark.ui.ContactList;
import org.jivesoftware.spark.util.SwingTimerTask;
import org.jivesoftware.spark.util.TaskEngine;
import org.jivesoftware.spark.util.log.Log;
import org.jxmpp.jid.EntityFullJid;

import static org.jivesoftware.spark.plugin.jingle.Ringing.*;

/**
 * Handles UI controls for outgoing jingle calls.
 */
public class OutgoingCall extends JPanel implements JingleSessionListener, ChatRoomClosingListener {
    private static final long WAIT_FOR_MEDIA_DELAY = 20000;
    private final FileDragLabel imageLabel = new FileDragLabel();
    private final JLabel titleLabel = new JLabel();
    private final JLabel fileLabel = new JLabel();
    private final CallButton cancelButton = new CallButton();
    private JingleSession session;
    private JingleRoom jingleRoom;
    private ChatRoom chatRoom;
    private boolean established = false;
    private boolean mediaReceived = false;
    private TimerTask mediaReceivedTask;

    /**
     * Creates a new instance of OutgoingCall.
     */
    public OutgoingCall() {
        setLayout(new GridBagLayout());

        setBackground(new Color(250, 249, 242));
        add(imageLabel, new GridBagConstraints(0, 0, 1, 3, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));

        add(titleLabel, new GridBagConstraints(1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));
        titleLabel.setFont(new Font("Dialog", Font.BOLD, 11));
        titleLabel.setForeground(new Color(211, 174, 102));

        cancelButton.setText(Res.getString("cancel"));
        add(cancelButton, new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0));

        cancelButton.setForeground(new Color(73, 113, 196));
        cancelButton.setFont(new Font("Dialog", Font.BOLD, 11));
        cancelButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196)));

        setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.white));
    }

    /**
     * Handles a new outgoing call.
     *
     * @param session  the JingleSession for the outgoing call.
     * @param chatRoom the room the session is associated with.
     * @param jid      the users jid.
     */
    public void handleOutgoingCall(final JingleSession session, ChatRoom chatRoom, final EntityFullJid jid) throws SmackException {
        this.chatRoom = chatRoom;
        JingleStateManager.getInstance().addJingleSession(chatRoom, JingleStateManager.JingleRoomState.ringing);

        chatRoom.addClosingListener(this);
        session.addListener(this);
        cancelButton.setVisible(true);

        this.session = session;
        // Start the call
        try {
            this.session.startOutgoing();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        fileLabel.setText(jid.toString());

        ContactList contactList = SparkManager.getWorkspace().getContactList();
        ContactItem contactItem = contactList.getContactItemByJID(jid);
        titleLabel.setText(JingleResources.getString("label.outgoing.voicechat", contactItem.getNickname()));


        cancelButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                cancel();
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                cancelButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
            }

            @Override
            public void mouseExited(MouseEvent e) {
                cancelButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }
        });

        makeClickable(imageLabel);
        makeClickable(titleLabel);

        // Notify state change
        SparkManager.getChatManager().notifySparkTabHandlers(chatRoom);
        updateOutgoingCallPanel();
    }

    /**
     * Updates the UI to reflect the current state.
     */
    private void updateOutgoingCallPanel() {
        if (session == null || session.isClosed()) {
        } else if (session instanceof JingleSession) {
            showAlert(false);
            if (session.getSessionState() instanceof JingleSessionStatePending) {
                titleLabel.setText("Calling user. Please wait…");
                cancelButton.setVisible(true);
            }
        }
    }

    /**
     * Called when the call has been answered.
     * Will append the JingleRoom to the associated ChatRoom.
     */
    private void showCallAnsweredState() {
        final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a");
        titleLabel.setText("Voice chat started on " + formatter.format(new Date()));
        cancelButton.setVisible(false);
        stopRinging();

        jingleRoom = new JingleRoom(session, chatRoom);
        chatRoom.getChatPanel().add(jingleRoom, new GridBagConstraints(1, 1, 1, 1, 0.05, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
        chatRoom.getChatPanel().invalidate();
        chatRoom.getChatPanel().validate();
        chatRoom.getChatPanel().repaint();
        // Add state
        JingleStateManager.getInstance().addJingleSession(chatRoom, JingleStateManager.JingleRoomState.inJingleCall);
        // Notify state change
        SparkManager.getChatManager().notifySparkTabHandlers(chatRoom);
    }

    /**
     * Called when the call has ended.
     */
    private void showCallEndedState(String reason) {
        titleLabel.setText(reason);

        showAlert(true);
        cancelButton.setVisible(false);
        stopRinging();

        if (chatRoom != null && jingleRoom != null) {
            chatRoom.getChatPanel().remove(jingleRoom);
            chatRoom.getChatPanel().invalidate();
            chatRoom.getChatPanel().validate();
            chatRoom.getChatPanel().repaint();
        }
        // Add state
        JingleStateManager.getInstance().removeJingleSession(chatRoom);
        // Notify state change
        SparkManager.getChatManager().notifySparkTabHandlers(chatRoom);
    }

    private void makeClickable(final JComponent component) {
        component.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                component.setCursor(new Cursor(Cursor.HAND_CURSOR));
            }

            @Override
            public void mouseExited(MouseEvent e) {
                component.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }
        });
    }

    /**
     * Changes the background color. If alert is true, the background will reflect that the ui
     * needs attention.
     *
     * @param alert true to notify users that their attention is needed.
     */
    private void showAlert(boolean alert) {
        if (alert) {
            titleLabel.setForeground(new Color(211, 174, 102));
            setBackground(new Color(250, 249, 242));
        } else {
            setBackground(new Color(239, 245, 250));
            titleLabel.setForeground(new Color(65, 139, 179));
        }
    }

    /**
     * Call to cancel phone conversation.
     */
    public void cancel() {
        if (session != null) {
            try {
                session.terminate();
                session = null;
            } catch (Exception e) {
                Log.error(e);
            }
        }
        final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a");
        showCallEndedState("Voice chat ended on " + formatter.format(new Date()));
    }

    @Override
    public void closing() {
        if (session != null) {
            try {
                session.terminate();
            } catch (Exception e) {
                Log.error(e);
            }
        }
        JingleStateManager.getInstance().removeJingleSession(chatRoom);
    }

    @Override
    public void sessionMediaReceived(JingleSession jingleSession, String participant) {
        mediaReceived = true;
        TaskEngine.getInstance().cancelScheduledTask(mediaReceivedTask);
        showCallAnsweredState();
    }

    @Override
    public void sessionEstablished(PayloadType payloadType, TransportCandidate transportCandidate, TransportCandidate transportCandidate1, JingleSession jingleSession) {
        established = true;
        mediaReceivedTask = new SwingTimerTask() {
            @Override
            public void doRun() {
                if (mediaReceived) {
                    return;
                }
                if (session != null) {
                    try {
                        session.terminate("No Media Received. This may be caused by firewall configuration problems.");
                    } catch (Exception e) {
                        Log.error(e);
                    }
                }
            }
        };
        TaskEngine.getInstance().schedule(mediaReceivedTask, WAIT_FOR_MEDIA_DELAY, WAIT_FOR_MEDIA_DELAY);
        SwingUtilities.invokeLater(() -> updateOutgoingCallPanel());
    }

    @Override
    public void sessionDeclined(String string, JingleSession jingleSession) {
        showCallEndedState("The Session was rejected.");
        SwingUtilities.invokeLater(() -> updateOutgoingCallPanel());
    }

    @Override
    public void sessionRedirected(String string, JingleSession jingleSession) {
        SwingUtilities.invokeLater(() -> updateOutgoingCallPanel());
    }

    @Override
    public void sessionClosed(String string, JingleSession jingleSession) {
        if (jingleSession instanceof JingleSession) {
            if (established && mediaReceived) {
                final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a");
                showCallEndedState("Voice chat ended on " + formatter.format(new Date()));
            } else {
                showCallEndedState("Session closed due to " + string);
            }
        }
        if (PhoneManager.isUseStaticLocator() && PhoneManager.isUsingMediaLocator()) {
            PhoneManager.setUsingMediaLocator(false);
        }
        SwingUtilities.invokeLater(() -> updateOutgoingCallPanel());
    }

    @Override
    public void sessionClosedOnError(XMPPException xmppException, JingleSession jingleSession) {
        showCallEndedState("Voice chat ended due: " + xmppException.getMessage());
        if (PhoneManager.isUseStaticLocator() && PhoneManager.isUsingMediaLocator()) {
            PhoneManager.setUsingMediaLocator(false);
        }

        SwingUtilities.invokeLater(() -> updateOutgoingCallPanel());
    }

    private static class CallButton extends JButton {
        public CallButton() {
            decorate();
        }

        /**
         * Decorates the button with the approriate UI configurations.
         */
        private void decorate() {
            setBorderPainted(false);
            setOpaque(true);

            setContentAreaFilled(false);
            setMargin(new Insets(1, 1, 1, 1));
        }

    }
}
