/**
 * Copyright (C) 2004-2011 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.ui;


import java.awt.*;
import java.awt.event.MouseEvent;

import java.net.URL;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;

import org.jivesoftware.MainWindow;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StanzaError.Condition;
import org.jivesoftware.smackx.vcardtemp.packet.VCard;
import org.jivesoftware.smackx.iqlast.packet.LastActivity;
import org.jivesoftware.smackx.iqlast.LastActivityManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.gateways.transports.Transport;
import org.jivesoftware.sparkimpl.plugin.gateways.transports.TransportUtils;
import org.jivesoftware.sparkimpl.settings.Sizes;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.NONE;
import static java.awt.GridBagConstraints.NORTHWEST;
import static java.awt.GridBagConstraints.SOUTHWEST;
import static java.awt.GridBagConstraints.WEST;
import static java.time.format.FormatStyle.MEDIUM;
import static java.time.format.FormatStyle.SHORT;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.jivesoftware.smack.packet.Presence.Mode.available;
import static org.jivesoftware.spark.ChatManager.TESTING_JID;
import static org.jivesoftware.spark.util.XEP0392Utils.colorOfContact;

/**
 * Represents the UI for the "ToolTip" functionality in the ContactList.
 *
 * @author Derek DeMoro
 */
public class ContactInfoWindow extends JPanel {
    private static final Color COLOR_TEXT = Color.gray;
    private final JLabel nicknameLabel = new JLabel();
    private final JTextArea statusLabel = new JTextArea();
    private final JLabel idleLabel = new JLabel();
    private final JLabel fullJIDLabel = new JLabel();
    private final JLabel avatarLabel = new JLabel();
    private final JLabel iconLabel = new JLabel();
    private final JLabel titleLabel = new JLabel();
    private final JLabel phoneLabel = new JLabel();
    private final JTextArea clientResourcesLabel = new JTextArea();

    private ContactItem contactItem;

    private final JWindow window = new JWindow();

    private static ContactInfoWindow singleton;

    public static ContactInfoWindow getInstance() {
        if (singleton != null) {
            return singleton;
        }
        synchronized (ContactInfoWindow.class) {
            singleton = new ContactInfoWindow();
            return singleton;
        }
    }

    private ContactInfoWindow() {
        setLayout(new GridBagLayout());
        setBackground(Color.white);
        add(avatarLabel, new GridBagConstraints(0, 1, 1, 4, 0, 0, NORTHWEST, NONE, new Insets(2, 2, 2, 2), 0, 0));
        add(iconLabel, new GridBagConstraints(1, 1, 1, 1, 0, 0, WEST, NONE, new Insets(2, 2, 0, 2), 0, 0));
        add(nicknameLabel, new GridBagConstraints(2, 1, 1, 1, 1, 0, WEST, HORIZONTAL, new Insets(2, 0, 0, 2), 0, 0));
        add(statusLabel, new GridBagConstraints(2, 2, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 0, 2), 0, 0));
        add(idleLabel, new GridBagConstraints(2, 3, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 0, 2), 0, 0));
        add(titleLabel, new GridBagConstraints(2, 4, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 2, 2), 0, 0));
        add(phoneLabel, new GridBagConstraints(2, 5, 1, 1, 1, 0, NORTHWEST, HORIZONTAL, new Insets(0, 0, 2, 2), 0, 0));
        add(fullJIDLabel, new GridBagConstraints(0, 6, 4, 1, 1, 1, SOUTHWEST, HORIZONTAL, new Insets(0, 2, 2, 2), 0, 0));
        add(clientResourcesLabel, new GridBagConstraints(0, 7, 4, 1, 1, 1, SOUTHWEST, HORIZONTAL, new Insets(0, 2, 2, 2), 0, 0));

        Font dialogFont = new Font("Dialog", Font.PLAIN, 12);
        nicknameLabel.setFont(new Font("Dialog", Font.BOLD, 14));
        statusLabel.setFont(dialogFont);
        statusLabel.setForeground(COLOR_TEXT);
        statusLabel.setLineWrap(true);
        statusLabel.setWrapStyleWord(true);
        statusLabel.setEditable(false);
        statusLabel.setBorder(null);
        idleLabel.setFont(dialogFont);
        idleLabel.setForeground(COLOR_TEXT);
        titleLabel.setFont(dialogFont);
        titleLabel.setForeground(COLOR_TEXT);
        phoneLabel.setFont(dialogFont);
        phoneLabel.setForeground(COLOR_TEXT);
        fullJIDLabel.setFont(dialogFont);
        fullJIDLabel.setForeground(COLOR_TEXT);
        fullJIDLabel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, COLOR_TEXT));
        clientResourcesLabel.setLineWrap(true);
        clientResourcesLabel.setWrapStyleWord(true);
        clientResourcesLabel.setEditable(false);
        phoneLabel.setBorder(null);

        setBorder(BorderFactory.createLineBorder(COLOR_TEXT, 1));
        window.getContentPane().add(this);

        final ContactList contactList = SparkManager.getWorkspace().getContactList();
        contactList.addContactListListener(new ContactListListener() {
            @Override
            public void contactItemClicked(ContactItem item) {
                hideWindow();
            }

            @Override
            public void contactItemDoubleClicked(ContactItem item) {
                hideWindow();
            }
        });
    }

    public synchronized void display(ContactGroup group, MouseEvent e) {
        int loc = group.getList().locationToIndex(e.getPoint());
        ContactItem item = group.getList().getModel().getElementAt(loc);
        if (item == null || item.getJid() == null || item.getJid().equals(TESTING_JID)) {
            return;
        }
        if (getContactItem() != null && getContactItem() == item) {
            return;
        }
        Point point = group.getList().indexToLocation(loc);
        contactItem = item;
        customizeUI();

        MainWindow mainWindow = SparkManager.getMainWindow();
        Point mainWindowLocation = mainWindow.getLocationOnScreen();
        Point listLocation = group.getList().getLocationOnScreen();

        int x = (int)mainWindowLocation.getX() + mainWindow.getWidth();
        int y = (int) listLocation.getY() + (int) point.getY();
        setWindowLocation(x, y);
        window.setFocusableWindowState(false);
        window.pack();
        window.setVisible(true);
    }

    public void setWindowLocation(int x, int y) {
        Point mainWindowLocation = SparkManager.getMainWindow().getLocationOnScreen();
        final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int actualX = x;
        int actualY = y - 5;
        if ((int) screenSize.getWidth() - getPreferredSize().getWidth() < x) {
            actualX = (int) mainWindowLocation.getX() - (int) getPreferredSize().getWidth();
        }
        // keep this away from bottom edge
        if (actualY + getHeight() > screenSize.height - 64) {
            actualY -= actualY + getHeight() - screenSize.height + 64;
        }
        // if width Spark Main Windows > width 0.9*screenSize we should put a window inside
        if (SparkManager.getMainWindow().getWidth() > screenSize.getWidth() * 0.9) {
            actualX = x - 310;
        }
        window.setLocation(actualX, actualY);
    }

    public void customizeUI() {
        // When the window was closed the contactItem is cleared
        if (contactItem == null) {
            return;
        }
        iconLabel.setIcon(contactItem.getIcon());
        nicknameLabel.setText(contactItem.getDisplayName());
        nicknameLabel.setForeground(colorOfContact(contactItem.getJid()));

        boolean isOnLeave = contactItem.getPresence() == null || contactItem.getPresence().getType() == Presence.Type.unavailable;
        boolean isAway = contactItem.getPresence().isAway();
        String status = contactItem.getStatus();
        if (isBlank(status)) {
            status = isOnLeave ? Res.getString("offline") : Res.getString("online");
        }
        statusLabel.setText(status);
        idleLabel.setText("");
        if (isOnLeave || isAway) {
            SwingUtilities.invokeLater(() -> retrieveIdleTime(isOnLeave));
        }
        showClientResources(isOnLeave, contactItem.getJid());

        Transport transport = TransportUtils.getTransport(contactItem.getJid().asDomainBareJid());
        String localPart = contactItem.getJid().getLocalpartOrThrow().asUnescapedString();
        if (transport != null) {
            fullJIDLabel.setIcon(transport.getIcon());
            fullJIDLabel.setText(transport.getCaption() + " - " + localPart);
        } else {
            fullJIDLabel.setText(localPart);
            fullJIDLabel.setIcon(null);
        }

        avatarLabel.setBorder(null);

        try {
            URL avatarURL = contactItem.getAvatarURL();
            ImageIcon icon = null;
            if (avatarURL != null) {
                icon = new ImageIcon(avatarURL);
            }
            if (icon != null && icon.getIconHeight() > 1) {
                icon = GraphicUtils.scaleImageIcon(icon, Sizes.Avatar.PROFILE, Sizes.Avatar.PROFILE);
            } else {
                icon = SparkRes.getImageIcon(SparkRes.Icon.DEFAULT_AVATAR_64x64_IMAGE);
            }
            avatarLabel.setIcon(icon);
            avatarLabel.setBorder(BorderFactory.createLineBorder(Color.lightGray, 1, true));
        } catch (Exception e) {
            Log.warning("Unable to update avatar in contact info window", e);
        }

        VCard vcard = SparkManager.getVCardManager().getVCard(contactItem.getJid());
        if (vcard != null) {
            String title = vcard.getField("TITLE");
            String phone = vcard.getPhoneWork("VOICE");
            titleLabel.setText(title);
            phoneLabel.setText(phone);
        }
    }

    private void showClientResources(boolean isOnLeave, BareJid contactJid) {
        if (isOnLeave) {
            clientResourcesLabel.setText("");
            return;
        }
        String clientResources = "";
        List<Presence> allPresences = SparkManager.getRoster().getAvailablePresences(contactJid);
        for (Presence p : allPresences) {
            clientResources += p.getFrom().getResourceOrEmpty() + " " +
                (p.getMode() != available ? p.getMode() : "") + " " +
                (p.getStatus() != null ? p.getStatus() : "") + "\n";
        }
        clientResourcesLabel.setText(clientResources);
    }

    private void retrieveIdleTime(boolean isOnLeave) {
        // When the window was closed the contactItem is cleared
        if (contactItem == null) {
            return;
        }
        //If user is offline or away, try to see last activity
        try {
            //If user is away (not offline), last activity request is sent to client
            Jid client = isOnLeave ? contactItem.getJid() : contactItem.getPresence().getFrom();
            LastActivity activity = LastActivityManager.getInstanceFor(SparkManager.getConnection()).getLastActivity(client);
            long idleTimeSec = activity.getIdleTime();
            if (idleTimeSec > 0) {
                String idleText = formatIdleTime(isOnLeave, idleTimeSec);
                idleLabel.setText(idleText);
            }
        } catch (XMPPException.XMPPErrorException e) {
            Condition condition = e.getStanzaError().getCondition();
            if (condition == Condition.feature_not_implemented || condition == Condition.service_unavailable || condition == Condition.subscription_required || condition == Condition.forbidden) {
                // ignore
                return;
            }
            Log.warning("Unable to get Last Activity from: " + contactItem + ": " + e);
        } catch (Exception e) {
            Log.warning("Unable to get Last Activity from: " + contactItem, e);
        }
    }

    private static String formatIdleTime(boolean isOnLeave, long idleTimeSec) {
        String idleText;
        if (isOnLeave) {
            ZoneId zone = ZoneId.systemDefault();
            ZonedDateTime now = ZonedDateTime.now(zone);
            ZonedDateTime idleDate = now.minusSeconds(idleTimeSec);
            boolean isToday = now.toLocalDate().equals(idleDate.toLocalDate());
            DateTimeFormatter formatter = isToday ? DateTimeFormatter.ofLocalizedTime(SHORT) : DateTimeFormatter.ofLocalizedDateTime(MEDIUM);
            String idleSince = idleDate.format(formatter);
            idleText = Res.getString("time.since") + " " + idleSince;
        } else {
            // is away
            String time = ModelUtil.getTimeFromLong(idleTimeSec);
            idleText = Res.getString("message.idle.for", time);
        }
        return idleText;
    }

    public ContactItem getContactItem() {
        return contactItem;
    }

    public void mouseExited(MouseEvent e) {
        Point point = e.getPoint();
        Dimension dim = window.getSize();
        int x = (int)point.getX();
        int y = (int)point.getY();
        boolean close = false;
        if (x < 0 || x >= dim.getWidth()) {
            close = true;
        }
        if (y < 0 || y >= dim.getHeight()) {
            close = true;
        }
        if (close) {
            window.setVisible(false);
            contactItem = null;
            hideWindow();
        }
    }

    protected void hideWindow() {
        window.setVisible(false);
        contactItem = null;
    }

}
