/**
 * 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.roar.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;

import javax.swing.*;
import javax.swing.border.Border;

import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.spark.component.RolloverButton;
import org.jivesoftware.spark.util.ImageCombiner;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.settings.Sizes;

import static org.apache.commons.lang3.Strings.CS;
import static org.jivesoftware.spark.util.StringUtils.replaceMe;

/**
 * Class to show tosters in multiplatform
 * 
 * @author daniele piras
 */
public class SparkToaster {

    /**
     * The default Hand cursor.
     */
    public static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR);

    /**
     * The default Text Cursor.
     */
    public static final Cursor DEFAULT_CURSOR = new Cursor(
	    Cursor.DEFAULT_CURSOR);

    // Width of the toster
    private int toasterWidth = 200;

    // Height of the toster
    private int toasterHeight = 150;

    // Step for the toaster
    private int step = 20;

    // Step time
    private int stepTime = 20;

    // Show time
    private int displayTime = 3000;

    // Current number of toaster...
    private int currentNumberOfToaster = 0;

    // Last opened toaster
    private int maxToaster = 0;

    // Font used to display message
    private Font font;

    // Color for border
    private Color borderColor;

    // Color for toaster
    private Color toasterColor;

    // Set message color
    private Color messageColor;

    // Set the margin
    private int margin;

    // Flag that indicate if use alwaysOnTop or not.
    // method always on top start only SINCE JDK 5 !
    private final boolean useAlwaysOnTop;

    private String title;
    private Border border;
    private Action customAction;
    private Window window;
    private final JPanel mainPanel = new JPanel();
    private TitleLabel titleLabel;
    private boolean hideable = true;

    /**
     * Constructor to initialized toaster component...
     */
    public SparkToaster() {
	// Set default font...
	font = new Font("Dialog", Font.PLAIN, 11);

	// Border color
	borderColor = new Color(245, 153, 15);
	toasterColor = Color.WHITE;
	messageColor = Color.BLACK;
	useAlwaysOnTop = true;
    }

    /**
     * Class that represent a single toaster
     * 
     * @author daniele piras
     */
    class SingleToaster extends javax.swing.JWindow {

	// Text area for the message
	private final JTextArea message = new JTextArea();

	public SingleToaster() {
	    initComponents();
	}

	/**
	 * Function to initialized components
	 */
	private void initComponents() {
	    message.setFont(getToasterMessageFont());

	    mainPanel.setBackground(Color.white);
	    message.setOpaque(false);
	    mainPanel.setLayout(new GridBagLayout());
	    message.setMargin(new Insets(2, 2, 2, 2));
	    message.setLineWrap(true);
	    message.setWrapStyleWord(true);

	    message.setForeground(getMessageColor());
	    titleLabel = new TitleLabel(getTitle(), true);
	    titleLabel.setForeground(new Color(87, 166, 211));
	    titleLabel.setFont(new Font("Dialog", Font.BOLD, 13));

	    mainPanel.add(titleLabel,
		    new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0,
			    GridBagConstraints.NORTHWEST,
			    GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0,
				    0), 0, 0));

	    titleLabel.getCloseButton().addActionListener( e -> {
            setVisible(false);
            dispose();
        } );

	    if (border != null) {
		mainPanel.setBorder(border);
	    }

	    message.setForeground(Color.BLACK);
	    message.setOpaque(false);
	    getContentPane().add(mainPanel);

	    mainPanel.addMouseListener(new PaneMouseListener());
	    message.addMouseListener(new PaneMouseListener());

	    pack();
	    setSize(toasterWidth, toasterHeight);
	    mainPanel
		    .setBorder(BorderFactory.createLineBorder(Color.lightGray));
	}

	/**
	 * Start toaster animation...
	 */
	public void animate() {
	    (new Animation(this)).start();
	}

	private class PaneMouseListener extends MouseAdapter {
	    @Override
        public void mouseClicked(MouseEvent e) {
		if (customAction != null) {
		    customAction.actionPerformed(null);
		}

		if (hideable) {
		    setVisible(false);
		    dispose();
		}
	    }

	    @Override
        public void mouseEntered(MouseEvent e) {
		message.setCursor(HAND_CURSOR);
		setCursor(HAND_CURSOR);
	    }

	    @Override
        public void mouseExited(MouseEvent e) {
		message.setCursor(DEFAULT_CURSOR);
		setCursor(DEFAULT_CURSOR);
	    }
	}
    }

    /**
     * Class that manage the animation
     */
    class Animation extends Thread {
        private final SingleToaster toaster;

	public Animation(SingleToaster toaster) {
	    this.toaster = toaster;
	}

	/**
	 * Animate vertically the toaster. The toaster could be moved from
	 * bottom to upper or to upper to bottom
	 * 
	 * @throws InterruptedException
	 *             if animation is interrupted
	 */
	protected void animateVertically(int posx, int fromY, int toY)
		throws InterruptedException {
	    toaster.setLocation(posx, fromY);
	    if (toY < fromY) {
		for (int i = fromY; i > toY; i -= step) {
		    toaster.setLocation(posx, i);
		    Thread.sleep(stepTime);
		}
	    } else {
		for (int i = fromY; i < toY; i += step) {
		    toaster.setLocation(posx, i);
		    Thread.sleep(stepTime);
		}
	    }
	    toaster.setLocation(posx, toY);
	    toaster.invalidate();
	    toaster.validate();
	    toaster.repaint();
	}

	@Override
    public void run() {
	    try {
		boolean animateFromBottom = true;
		GraphicsEnvironment ge = GraphicsEnvironment
			.getLocalGraphicsEnvironment();
		Rectangle screenRect = ge.getMaximumWindowBounds();
		int screenHeight = screenRect.height;
		int startYPosition;
		int stopYPosition;
		if (screenRect.y > 0) {
		    animateFromBottom = false; // Animate from top!
		}

		int maxToasterInSceen = screenHeight / toasterHeight;
		int posx = screenRect.width - toasterWidth - 1;
		toaster.setLocation(posx, screenHeight);
		try {
		    EventQueue.invokeAndWait( () -> toaster.setVisible(true) );
		} catch (Exception e) {
		    Log.error(e);
		}
		if (useAlwaysOnTop) {
		    toaster.setAlwaysOnTop(true);
		}

		if (animateFromBottom) {
		    startYPosition = screenHeight;
		    stopYPosition = startYPosition - toasterHeight - 1;
		    if (currentNumberOfToaster > 0) {
			stopYPosition = stopYPosition
				- (maxToaster % maxToasterInSceen * toasterHeight);
		    } else {
			maxToaster = 0;
		    }
		} else {
		    startYPosition = screenRect.y - toasterHeight;
		    stopYPosition = screenRect.y;
		    if (currentNumberOfToaster > 0) {
			stopYPosition = stopYPosition
				+ (maxToaster % maxToasterInSceen * toasterHeight);
		    } else {
			maxToaster = 0;
		    }
		}

		currentNumberOfToaster++;
		maxToaster++;

		animateVertically(posx, startYPosition, stopYPosition);
		Thread.sleep(displayTime);
		animateVertically(posx, stopYPosition, startYPosition);

		currentNumberOfToaster--;
		toaster.setVisible(false);
		toaster.dispose();
	    } catch (Exception e) {
		Log.error(e);
	    }
	}
    }

    /**
     * Show a toaster with the specified message and the associated icon.
     * 
     * @param icon
     *            Icon to show in toaster popup.
     * @param msg
     *            Message to show in toaster popup.
     */
    public void showToaster(Icon icon, String msg) {
	SingleToaster singleToaster = new SingleToaster();
	final JScrollPane pane = new JScrollPane(singleToaster.message);
	pane.setOpaque(false);
	pane.setBorder(BorderFactory.createEmptyBorder());
	pane.getViewport().setBackground(Color.white);
	mainPanel.add(pane, new GridBagConstraints(1, 2, 3, 1, 1.0, 1.0,
		GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2,
			5, 2, 5), 0, 0));

	if (icon != null) {
	    titleLabel.setIcon(icon);
	}
    if (CS.startsWith(msg, "/me ")) {
        msg = replaceMe(msg, getTitle());
	    singleToaster.message.setForeground(Color.MAGENTA);
	}
	singleToaster.message.setText(msg);
	singleToaster.message.setCaretPosition(0);
	singleToaster.animate();
	window = singleToaster;
    }

    /**
     * Show a toaster with the specified message and the associated icon.
     * 
     * @param title
     *            Title to use in toaster popup
     * @param comp
     *            Component to add to toaster popup
     */
    public void showToaster(final String title, final Component comp) {
	SingleToaster singleToaster = new SingleToaster();
	mainPanel.add(comp, new GridBagConstraints(1, 2, 3, 1, 1.0, 1.0,
		GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2,
			0, 0, 0), 0, 0));

	titleLabel.setTitle(title);
	singleToaster.animate();
	window = singleToaster;
    }

    public void showToaster(Icon icon) {
	SingleToaster singleToaster = new SingleToaster();
	if (icon != null) {
	    titleLabel.setIcon(icon);
	}
	singleToaster.animate();
	window = singleToaster;
    }

    public void showToaster() {
	SingleToaster singleToaster = new SingleToaster();
	singleToaster.animate();
	window = singleToaster;
    }

    /**
     * Show a toaster with the specified message.
     * 
     * @param msg
     *            Message to display.
     */
    public void showToaster(String msg) {
	showToaster(null, msg);
    }

    public Font getToasterMessageFont() {
	return font;
    }

    public void setToasterMessageFont(Font f) {
	font = f;
    }

    public Color getBorderColor() {
	return borderColor;
    }

    public void setBorderColor(Color borderColor) {
	this.borderColor = borderColor;
    }

    public int getDisplayTime() {
	return displayTime;
    }

    public void setDisplayTime(int displayTime) {
	this.displayTime = displayTime;
    }

    public int getMargin() {
	return margin;
    }

    public void setMargin(int margin) {
	this.margin = margin;
    }

    public Color getMessageColor() {
	return messageColor;
    }

    public void setMessageColor(Color messageColor) {
	this.messageColor = messageColor;
    }

    public int getStep() {
	return step;
    }

    public void setStep(int step) {
	this.step = step;
    }

    public int getStepTime() {
	return stepTime;
    }

    public void setStepTime(int stepTime) {
	this.stepTime = stepTime;
    }

    public Color getToasterColor() {
	return toasterColor;
    }

    public void setToasterColor(Color toasterColor) {
	this.toasterColor = toasterColor;
    }

    public int getToasterHeight() {
	return toasterHeight;
    }

    public void setToasterHeight(int toasterHeight) {
	this.toasterHeight = toasterHeight;
    }

    public int getToasterWidth() {
	return toasterWidth;
    }

    public void setToasterWidth(int toasterWidth) {
	this.toasterWidth = toasterWidth;
    }

    public String getTitle() {
	return title;
    }

    public void setTitle(String title) {
	this.title = title;
    }

    public Border getBorder() {
	return border;
    }

    public void setBorder(Border border) {
	this.border = border;
    }

    public void setCustomAction(Action action) {
	this.customAction = action;
    }

    public void setComponent(Component comp) {
	mainPanel.add(comp, new GridBagConstraints(1, 2, 3, 1, 1.0, 1.0,
		GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(2,
			5, 2, 5), 0, 0));
    }

    public void close() {
	if (window != null) {
	    window.dispose();
	}
    }

    public Window getWindow() {
	return window;
    }

    public void hideTitle() {
	titleLabel.setVisible(false);
    }

    static class TitleLabel extends JPanel {
	private final JLabel label;
	private final RolloverButton closeButton;

	public TitleLabel(String text, final boolean showCloseIcon) {
	    setLayout(new GridBagLayout());
	    label = new JLabel(text);
	    label.setFont(new Font("Dialog", Font.BOLD, 11));
	    label.setHorizontalTextPosition(JLabel.LEFT);
	    label.setHorizontalAlignment(JLabel.LEFT);

	    add(label, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0,
		    GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
		    new Insets(5, 5, 5, 5), 0, 0));

	    closeButton = new RolloverButton(
		    SparkRes.getImageIcon(SparkRes.Icon.CLOSE_IMAGE));
	    if (showCloseIcon) {
		add(closeButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
			GridBagConstraints.EAST, GridBagConstraints.NONE,
			new Insets(5, 5, 5, 5), 0, 0));
	    }

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

	public void setIcon(Icon icon) {
        try {
            if (icon.getIconHeight() > Sizes.Toaster.ICON || icon.getIconWidth() > Sizes.Toaster.ICON) {
                Image image = ImageCombiner.iconToImage(icon);
                label.setIcon(new ImageIcon(image.getScaledInstance(-1, Sizes.Toaster.ICON, Image.SCALE_SMOOTH)));
            } else{
                label.setIcon(icon);
            }
        } catch (Exception e) {
            Log.warning("Unable to set icon in toaster", e);
        }
	}

	public RolloverButton getCloseButton() {
	    return closeButton;
	}

	public void setTitle(String title) {
	    label.setText(title);
	}

	@Override
    public void paintComponent(Graphics g) {
	    final Image backgroundImage = Default.getImageIcon(
		    Default.TOP_BOTTOM_BACKGROUND_IMAGE).getImage();
	    double scaleX = getWidth()
		    / (double) backgroundImage.getWidth(null);
	    double scaleY = getHeight()
		    / (double) backgroundImage.getHeight(null);
	    AffineTransform xform = AffineTransform.getScaleInstance(scaleX,
		    scaleY);
	    ((Graphics2D) g).drawImage(backgroundImage, xform, this);
	}
    }

    public void setHidable(boolean hideable) {
	this.hideable = hideable;
    }
}
