/**
 * Copyright (C) 2004-2010 Jive Software. All rights reserved.
 *
 * 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 org.jivesoftware.spark.plugin.ofmeet;

import de.mxro.process.ProcessListener;
import de.mxro.process.Spawn;
import de.mxro.process.XProcess;
import org.jitsi.util.OSUtils;
import org.jivesoftware.Spark;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.spark.ChatManager;
import org.jivesoftware.spark.SessionManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.plugin.Plugin;
import org.jivesoftware.spark.preference.PreferenceManager;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.ChatRoomListener;
import org.jivesoftware.spark.ui.GlobalMessageListener;
import org.jivesoftware.spark.util.log.Log;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.parts.Localpart;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static javax.swing.JOptionPane.YES_OPTION;

public class SparkMeetPlugin implements Plugin, ChatRoomListener, GlobalMessageListener
{
    /**
     * Online conference URL e.g. <code>https://meet.jit.si/Lobby</code>.
     * Can be configured in preferences or retrived from server if configured there.
     * By default, it will be <code>https://yourxmppdomain:7443/ofmeet/</code>.
     */
    public String url = null;

    /** Key: BareJid  for groups or chats, EntityJid for 1-1 chats */
    private final Map<EntityJid, ChatRoomDecorator> decorators = new HashMap<>();
    private String electronExePath = null;
    private File electronHomePath = null;
    private XProcess electronThread = null;
    private JPanel inviteAlert;

    @Override
    public void initialize()
    {
        ProviderManager.addIQProvider(QueryRequest.ELEMENT, QueryRequest.NAMESPACE, new QueryRequest.Provider());
        getElectronPath();

        String server = SparkManager.getSessionManager().getServerAddress().toString();
        String port = "7443";
        url = "https://" + server + ":" + port + "/ofmeet/";

        SparkMeetPreference preference = new SparkMeetPreference(this);
        PreferenceManager prefManager = SparkManager.getPreferenceManager();
        prefManager.addPreference(preference);
        Properties props = (Properties) prefManager.getPreferenceData(SparkMeetPreference.NAMESPACE);
        if (props.getProperty("url") != null) {
            url = props.getProperty("url");
            Log.debug("Meet plugin: url from setting is " + url);
        }

        initializeOnlineMeetings();

        ChatManager chatManager = SparkManager.getChatManager();
        chatManager.addChatRoomListener(this);
        chatManager.addGlobalMessageListener(this);
    }

    private void initializeOnlineMeetings() {
        SessionManager sessionManager = SparkManager.getSessionManager();
        DomainBareJid serverJid = sessionManager.getServerAddress();
        DiscoverInfo discoverInfo = sessionManager.getDiscoveredInfos().get(serverJid);
        if (discoverInfo == null) {
            return;
        }

        boolean jitsiAvailable = discoverInfo.containsFeature("urn:xmpp:http:online-meetings#jitsi");
        boolean galeneAvailable = discoverInfo.containsFeature("urn:xmpp:http:online-meetings#galene");
        boolean ohunAvailable = discoverInfo.containsFeature("urn:xmpp:http:online-meetings#ohun");

        String sUrl = null;
        if (jitsiAvailable) {
            sUrl = getServerUrl("jitsi");
        } else if (galeneAvailable) {
            sUrl = getServerUrl("galene");
        } else if (ohunAvailable) {
            sUrl = getServerUrl("ohun");
        }

        if (sUrl != null) {
            url = sUrl;
        }
    }

    private String getServerUrl(String app) {
		String serverUrl = null;
        try {
            QueryRequest request = new QueryRequest(app);
            request.setTo(SparkManager.getSessionManager().getServerAddress());
            request.setType(IQ.Type.get);
            QueryRequest response = SparkManager.getConnection().sendIqRequestAndWaitForResponse(request);
            Log.debug("SparkMeet response: url=" + response.url);
            if (response.url != null) serverUrl = response.url + "/";

        } catch (Exception e) {
            Log.error("Unable to get meet url from server for app type " + app);
        }
		return serverUrl;
	}

	public void commit(String url) {
		this.url = url;
    }

    @Override
    public void shutdown()
    {
        try
        {
            SparkManager.getChatManager().removeChatRoomListener(this);
            ProviderManager.removeIQProvider(QueryRequest.ELEMENT, QueryRequest.NAMESPACE);

            if (electronThread != null) electronThread.destory();
            electronThread = null;
        }
        catch(Exception e)
        {
            Log.error("shutdown ", e);
        }
        electronHomePath = null;
        electronExePath = null;
        url = null;
        //TODO remove buttons
        decorators.clear();
    }

    @Override
    public void messageReceived(ChatRoom room, Message message) {
        try {
            Localpart roomId = room.getJid().getLocalpart();
            String body = message.getBody();
            int pos = body.indexOf("https://");
            if (pos != -1 && (body.contains("/" + roomId + "-") || body.contains("meeting"))) {
                String meetUrl = message.getBody().substring(pos);
                showInvitationAlert(meetUrl, room);
            }
        } catch (Exception ignored) {
        }
    }

    private void showInvitationAlert(final String meetUrl, final ChatRoom room)
    {
        // Got an offer to start a new meet. So, make sure that a chat is
        // started with the other
        // user and show an invite panel.

        inviteAlert = new JPanel();
        inviteAlert.setLayout(new BorderLayout());

        JPanel invitePanel = new JPanel();
        invitePanel.setPreferredSize(new Dimension(24, 24));
        inviteAlert.add(invitePanel, BorderLayout.WEST);
        JPanel content = new JPanel(new BorderLayout());

        content.add(new JLabel("Join audio/conference conference ..."), BorderLayout.CENTER);
        JPanel buttonPanel = new JPanel();

        // The accept button. When clicked, accept the meet offer.
        final JButton acceptButton = new JButton("Accept");
        final JButton declineButton = new JButton("Decline");

        acceptButton.addActionListener(e -> {
            // Hide the response panel. TODO: make this work.
            room.getTranscriptWindow().remove(inviteAlert);
            inviteAlert.remove(1);
            inviteAlert.add(new JLabel("Meeting at " + meetUrl), BorderLayout.CENTER);
            declineButton.setEnabled(false);
            acceptButton.setEnabled(false);

            openURL(meetUrl);
        });
        buttonPanel.add(acceptButton);

        // The decline button. When clicked, reject the meet offer.

        declineButton.addActionListener(e -> {
            // Hide the response panel. TODO: make this work.
            room.getTranscriptWindow().remove(inviteAlert);
            declineButton.setVisible(false);
            acceptButton.setVisible(false);
        });
        buttonPanel.add(declineButton);
        content.add(buttonPanel, BorderLayout.SOUTH);
        inviteAlert.add(content, BorderLayout.CENTER);

        // Add the response panel to the transcript window.
        room.getTranscriptWindow().addComponent(inviteAlert);
    }

    @Override
    public void messageSent(ChatRoom room, Message message) {
    }

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

    @Override
    public void uninstall()
    {
    }

    public void handleClick(ChatRoom room, String url)
    {
        if (electronThread != null)
        {
            electronThread.destory();
            electronThread = null;
            return;
        }

        sendInvite(room, url);
        openURL(url);
    }

    public void openURL(String newUrl)
    {
        if (!electronHomePath.exists()) {
            if (JOptionPane.showConfirmDialog(SparkManager.getMainWindow(), SparkMeetResource.getString("install.electron")) != YES_OPTION) {
                return;
            }
            checkNatives();
        }

        try {
            electronThread = Spawn.startProcess(electronExePath + " --ignore-certificate-errors " + newUrl, electronHomePath, new ProcessListener() {

                @Override
                public void onOutputLine(final String line) {
                    if (line.isEmpty()) {
                        return;
                    }
                    Log.debug(line);
                }

                @Override
                public void onProcessQuit(int code) {
                    electronThread = null;
                }

                @Override
                public void onErrorLine(final String line) {
                    if (!line.contains("Corrupt JPEG data"))
                    {
                        Log.warning("Electron error " + line);
                    }
                }

                @Override
                public void onError(final Throwable t) {
                    Log.warning("Electron error", t);
                }
            });
        } catch (Exception t) {
            Log.error("Error opening url " + newUrl, t);
        }
    }

    @Override
    public void chatRoomClosed(ChatRoom chatroom)
    {
        EntityJid roomId = chatroom.getJid();
        Log.debug("chatRoomClosed:  " + roomId);

        ChatRoomDecorator decorator = decorators.remove(roomId);
        if (decorator != null) {
            decorator.finished();
        }

        if (electronThread != null)
        {
            electronThread.destory();
            electronThread = null;
        }
    }

    @Override
    public void chatRoomOpened(final ChatRoom room)
    {
        EntityJid roomId = room.getJid();
        decorators.put(roomId, new ChatRoomDecorator(room, this));
    }

    private void checkNatives()
    {
        Log.debug("Install Electron");
                try
                {
                        //noinspection ResultOfMethodCallIgnored
                        electronHomePath.mkdirs();

                        String jarFileSuffix = null;

                        if(OSUtils.IS_LINUX32)
                        {
                            jarFileSuffix = "-linux-ia32.zip";
                        }
                        else if(OSUtils.IS_LINUX64)
                        {
                            jarFileSuffix = "-linux-x64.zip";
                        }
                        else if(OSUtils.IS_WINDOWS32)
                        {
                            jarFileSuffix = "-win32-ia32.zip";
                        }
                        else if(OSUtils.IS_WINDOWS64)
                        {
                            jarFileSuffix = "-win32-x64.zip";
                        }
                        else if(OSUtils.IS_MAC)
                        {
                            jarFileSuffix = "-darwin-x64.zip";
                        }

                        InputStream inputStream = new URL("https://github.com/electron/electron/releases/download/v37.2.3/electron-v37.2.3" + jarFileSuffix).openStream();
                        ZipInputStream zipIn = new ZipInputStream(inputStream);
                        ZipEntry entry = zipIn.getNextEntry();

                        while (entry != null)
                        {
                            try
                            {
                                String filePath = electronHomePath + File.separator + entry.getName();

                                Log.debug("writing file..." + filePath);

                                if (!entry.isDirectory())
                                {
                                    File file = new File(filePath);
                                    file.setReadable(true, true);
                                    file.setWritable(true, true);
                                    file.setExecutable(true, true);

                                    new File(file.getParent()).mkdirs();

                                    extractFile(zipIn, filePath);
                                }
                                zipIn.closeEntry();
                                entry = zipIn.getNextEntry();
                            }
                            catch(Exception e) {
                                Log.error("Error", e);
                            }
                        }
                        zipIn.close();

                        Log.debug("Native lib folder created and natives extracted");
                }
                catch (Exception e)
                {
                    Log.error(e.getMessage(), e);
                }
    }

    private void getElectronPath() {
        String nativeLibsJarPath = Spark.getSparkUserHome() + File.separator + "plugins" + File.separator + "meet" + File.separator + "lib";
        electronHomePath = new File(nativeLibsJarPath, "native");
        electronExePath = new File(electronHomePath, "electron").getAbsolutePath();
    }

    private void extractFile(ZipInputStream zipIn, String filePath) throws IOException
    {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[4096];
        int read;

        while ((read = zipIn.read(bytesIn)) != -1)
        {
            bos.write(bytesIn, 0, read);
        }
        bos.close();
    }

    private void sendInvite(ChatRoom room, String url)
    {
        room.sendMessage(url);
    }
}
