/**
 * 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.sparkimpl.plugin.phone;

import org.jivesoftware.spark.phone.client.PhoneClient;
import org.jivesoftware.spark.phone.client.action.PhoneActionIQProvider;
import org.jivesoftware.spark.phone.client.event.*;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.phone.PhoneManager;
import org.jivesoftware.spark.plugin.ContextMenuListener;
import org.jivesoftware.spark.plugin.Plugin;
import org.jivesoftware.spark.ui.*;
import org.jivesoftware.spark.ui.rooms.ChatRoomImpl;
import org.jivesoftware.spark.util.*;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.alerts.SparkToaster;
import org.jivesoftware.sparkimpl.preference.media.MediaPreference;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import org.jxmpp.jid.BareJid;

import javax.swing.*;
import java.awt.event.*;
import java.util.TimerTask;

/**
 * Adds phone-pbx capabilities within Spark.
 * Users can make calls, take incoming calls and be notified of presence changes
 * @author Derek DeMoro
 */
public class PhonePlugin implements Plugin {
    public static PhoneClient phoneClient;
    public static Presence offPhonePresence;
    public static Presence onPhonePresence;
    private DialPanel dialPanel;
    //    private Alert incomingDialog;
    private JFrame dialDialog;

    public static PhoneClient getPhoneClient() {
        return phoneClient;
    }

    @Override
    public void initialize() {
        ProviderManager.addExtensionProvider("phone-event", "http://jivesoftware.com/xmlns/phone", new PhoneEventPacketExtensionProvider());
        ProviderManager.addIQProvider("phone-action", "http://jivesoftware.com/xmlns/phone", new PhoneActionIQProvider());

        MediaPreference preferences = new MediaPreference();
        SparkManager.getPreferenceManager().addPreference(preferences);

        final XMPPConnection con = SparkManager.getConnection();
        SwingWorker worker = new SwingWorker() {
            @Override
            public Object construct() {
                try {
                    phoneClient = new PhoneClient(con);
                    // Add BaseListener
                    phoneClient.addEventListener(new PhoneListener());
                } catch (Exception e) {
                    // Ignore because the user does not have support.
                    Log.warning(e.getMessage());
                }
                return phoneClient;
            }

            @Override
            public void finished() {
                if (phoneClient != null) {
                    setupPhoneSystem();
                }
            }
        };

        worker.start();
    }

    private void setupPhoneSystem() {
        // Add Dial Menu
        final JMenu viewMenu = SparkManager.getMainWindow().getMenuByName(Res.getString("menuitem.actions"));
        JMenuItem dialNumberMenu = new JMenuItem(SparkRes.getImageIcon(SparkRes.Icon.ON_PHONE_IMAGE));
        ResourceUtils.resButton(dialNumberMenu, Res.getString("button.dial.number"));

        // Add Listener
        dialNumberMenu.addActionListener(e -> {
            dialPanel = new DialPanel();
            dialPanel.getDialButton().addActionListener(e1 -> {
                String number = dialPanel.getNumberToDial();
                if (ModelUtil.hasLength(number)) {
                    dialPanel.setText(Res.getString("message.calling", number));
                    dialPanel.changeToRinging();
                    callExtension(number);
                }
            });

            dialDialog = PhoneDialog.invoke(dialPanel, Res.getString("title.dial.phone"), Res.getString("message.number.to.call"), null);
            dialPanel.getDialField().requestFocusInWindow();

            dialPanel.getDialField().addKeyListener(new KeyAdapter() {
                @Override
                public void keyPressed(KeyEvent e) {
                    if (e.getKeyChar() == KeyEvent.VK_ENTER) {
                        try {
                            String number = dialPanel.getNumberToDial();
                            if (ModelUtil.hasLength(number)) {
                                dialPanel.setText(Res.getString("message.calling", number));
                                dialPanel.changeToRinging();
                                callExtension(number);
                            }
                            e.consume();
                        } catch (Exception ex) {
                            Log.error(ex);
                        }
                    }
                }
            });
        });
        viewMenu.add(dialNumberMenu);

        // Add ChatRoomListener to call users based on JID
        SparkManager.getChatManager().addChatRoomListener(new ChatRoomListener() {
            @Override
            public void chatRoomOpened(final ChatRoom room) {
                if (room instanceof ChatRoomImpl) {
                    final ChatRoomButton callButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.Icon.TELEPHONE_24x24));
                    callButton.setToolTipText(Res.getString("tooltip.place.a.call"));
                    final ChatRoomImpl chatRoom = (ChatRoomImpl) room;
                    boolean phoneEnabled = false;
                    try {
                        phoneEnabled = phoneClient.isPhoneEnabled(chatRoom.getParticipantJID());
                    } catch (Exception e) {
                        Log.error(e);
                    }
                    if (phoneEnabled) {
                        room.addChatRoomButton(callButton);
                        callButton.addActionListener(e -> callJID(chatRoom.getParticipantJID()));
                    }
                }
            }
        });

        ContactList contactList = SparkManager.getWorkspace().getContactList();
        contactList.addContextMenuListener(new ContextMenuListener() {
            @Override
            public void poppingUp(Object object, final JPopupMenu popup) {
                if (!(object instanceof ContactItem)) {
                    return;
                }
                    final ContactItem item = (ContactItem) object;
                    boolean phoneEnabled = false;
                    try {
                        phoneEnabled = phoneClient.isPhoneEnabled(item.getJid());
                    } catch (Exception e) {
                        Log.error("There was an error retrieving phone information.", e);
                    }
                    // Add call action to contact popup
                    if (phoneEnabled) {
                        Action callAction = new AbstractAction() {
                            @Override
                            public void actionPerformed(ActionEvent e) {
                                callJID(item.getJid());
                            }
                        };

                        callAction.putValue(Action.NAME, "Call");
                        callAction.putValue(Action.SMALL_ICON, SparkRes.getImageIcon(SparkRes.Icon.ON_PHONE_IMAGE));
                        popup.add(callAction);
                    }
            }

            @Override
            public boolean handleDefaultAction(MouseEvent e) {
                return false;
            }
        });
    }

    private void displayRingUI(RingEvent event) {
        IncomingCall incomingCall = new IncomingCall();
        boolean idExists = false;
        if (ModelUtil.hasLength(event.getCallerIDName())) {
            incomingCall.setCallerName(event.getCallerIDName());
            idExists = true;
        }
        if (ModelUtil.hasLength(event.getCallerID())) {
            incomingCall.setCallerNumber(event.getCallerID());
            idExists = true;
        }
        if (!idExists) {
            incomingCall.setCallerName(Res.getString("message.no.caller.id"));
        }
        displayIncomingCallNotification(incomingCall);
    }

    private static void displayIncomingCallNotification(IncomingCall incomingCall) {
        if (!SettingsManager.getLocalPreferences().isDisableAsteriskToasterPopup()) {
            SparkToaster toasterManager = new SparkToaster();
            toasterManager.setTitle("Incoming Phone Call");
            toasterManager.setDisplayTime(15000);
            toasterManager.showToaster(SparkRes.getImageIcon(SparkRes.Icon.ON_PHONE_IMAGE));
            toasterManager.setComponent(incomingCall);
        }
    }


    public void callExtension(final String number) {
        final Runnable caller = () -> {
            try {
                phoneClient.dialByExtension(number);
            } catch (Exception e) {
                Log.error(e);
            }
        };
        TaskEngine.getInstance().submit(caller);
    }

    public void callJID(final BareJid jid) {
        final Runnable caller = () -> {
            try {
                phoneClient.dialByJID(jid);
            } catch (Exception e) {
                Log.error(e);
            }
        };
        TaskEngine.getInstance().submit(caller);
    }

    @Override
    public void shutdown() {
    }

    @Override
    public boolean canShutDown() {
        return true;
    }

    @Override
    public void uninstall() {
    }

    private class PhoneListener implements BasePhoneEventListener {
        @Override
        public void handleOnPhone(OnPhoneEvent event) {
            if (dialDialog != null) {
                dialDialog.setVisible(false);
            }
            // Get current presence if necessary.
            offPhonePresence = SparkManager.getWorkspace().getStatusBar().getPresence();
            // Send "on the phone" presence
            onPhonePresence = StanzaBuilder.buildPresence()
                .ofType(Presence.Type.available)
                .setStatus("On the phone")
                .setPriority(1)
                .setMode(Presence.Mode.away)
                .build();
            SparkManager.getSessionManager().changePresence(onPhonePresence);
        }

        @Override
        public void handleHangUp(HangUpEvent event) {
            onPhonePresence = null;
            if (dialDialog != null) {
                dialDialog.setVisible(false);
            }

/*
            if (offPhonePresence != null) {
                // Reconcile presence after phone call based on lock status
                if (((offPhonePresence.getMode() == Presence.Mode.away) && (!UserIdlePlugin.getDesktopLockStatus())
                    && (UserIdlePlugin.latestPresence != null)) && (!UserIdlePlugin.latestPresence.getStatus().contentEquals("On the Phone"))) {
                    SparkManager.getSessionManager().changePresence(UserIdlePlugin.latestPresence);
                    Log.debug("PhonePlugin: Setting presence from UserIdlePlugin");
                } else if ((UserIdlePlugin.getDesktopLockStatus()) && ((offPhonePresence.getStatus().equals("Online"))
                    || (offPhonePresence.getStatus().equals("Free to chat")))) {
                    Presence presence = StanzaBuilder.buildPresence()
                        .ofType(Presence.Type.available)
                        .setStatus(UserIdlePlugin.pref.getIdleMessage())
                        .setPriority(1)
                        .setMode(Presence.Mode.away)
                        .build();
                    SparkManager.getSessionManager().changePresence(presence);
                    Log.debug("PhonePlugin: Desktop is Locked - Setting presence from pref.idle message");
                } else if (UserIdlePlugin.getDesktopLockStatus() && (!offPhonePresence.isAway())) {
                    Presence presence = StanzaBuilder.buildPresence()
                        .ofType(Presence.Type.available)
                        .setStatus(offPhonePresence.getStatus())
                        .setPriority(1)
                        .setMode(Presence.Mode.away)
                        .build();
                    SparkManager.getSessionManager().changePresence(presence);
                    Log.debug("PhonePlugin: Desktop is Locked - Setting presence from user defined presence");
                } else {
                    // Set user to previous presence state when all phone calls are hung up.
                    SparkManager.getSessionManager().changePresence(offPhonePresence);
                    Log.debug("PhonePlugin: Setting Presence from PhonePlugin.");
                }
            } else {
                // If no previous state available, set status to Available
                Presence availablePresence = StanzaBuilder.buildPresence()
                    .ofType(Presence.Type.available)
                    .setStatus("Online")
                    .setPriority(1)
                    .setMode(Presence.Mode.available)
                    .build();

                SparkManager.getSessionManager().changePresence(availablePresence);
                Log.debug("no previous state available from Phone Plugin..setting to Online");
            }
*/
        }

        @Override
        public void handleRing(RingEvent event) {
            final TimerTask task = new SwingTimerTask() {
                @Override
                public void doRun() {
                    String callerID = event.getCallerID();
                    if (ModelUtil.hasLength(callerID)) {
                        String number = PhoneManager.getNumbersFromPhone(callerID);
                        if (PhoneManager.getInstance().containsCurrentCall(number)) {
                            return;
                        }
                    }
                    displayRingUI(event);
                }
            };
            TaskEngine.getInstance().schedule(task, 1000);
        }
    }

}
