/**
 * Copyright (c) 2013 gootara.org <http://gootara.org>
 *
 * The MIT License (MIT)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.gootara.ios.image.util.ui;

import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferedImage;
import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Locale;

import javax.swing.JPanel;
import javax.swing.Timer;

import org.gootara.ios.image.util.IOSAssetCatalogs;
import org.gootara.ios.image.util.IOSIconAssetCatalogs;

/**
 * @author gootara.org
 *
 */
public class ImagePanel extends JPanel {
	private AssetImageGenerator generator;
	private IOSAssetCatalogs asset;
	private java.io.File imageFile;
	private int scalingType;
	private BufferedImage image, scaledImage;
	private String placeHolder, hyphenatorBoL, hyphenatorEoL;
	private Timer timer;
	private final Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR);
	private final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR);

	/**
	 * Constructor.
	 */
	public ImagePanel(AssetImageGenerator generator, IOSAssetCatalogs asset) {
		this(generator, asset, "");
	}

	/**
	 * Constructor with the placeholder.
	 *
	 * @param placeHolder placeHolder
	 */
	public ImagePanel(AssetImageGenerator generator, IOSAssetCatalogs asset, String placeHolder) {
		this.generator = generator;
		this.asset = asset;
		this.setCursor(HAND_CURSOR);
		this.image = null;
		this.scaledImage = null;
		this.setPlaceHolder(placeHolder);
		this.setHyphenator("", "");
		timer = new Timer(500, new ActionListener() {
			@Override public void actionPerformed(ActionEvent e) {
				try {
					if (ImagePanel.this.scaledImage != null) {
						ImagePanel.this.scaledImage.flush();
						ImagePanel.this.scaledImage = null;
					}
					if (ImagePanel.this.image == null) {
						return;
					}
					
					Dimension maximumSize = ImagePanel.this.getSize();
					if (ImagePanel.this.asset instanceof IOSIconAssetCatalogs) {
						int size = maximumSize.width > maximumSize.height ? maximumSize.height : maximumSize.width;
						ImagePanel.this.scaledImage = ImagePanel.this.generator.generateIconImage(ImagePanel.this.image, size, size, ImagePanel.this.asset.getIdiom().isAppleWatch());
					} else {
						Dimension preferredSize = ImagePanel.this.getPreferredSize();
						// Default is fit with aspect ratio.
						double p = maximumSize.getWidth() / preferredSize.getWidth();
						if (Math.floor(preferredSize.getWidth() * p) > maximumSize.getWidth() || Math.floor(preferredSize.getHeight() * p) > maximumSize.getHeight()) {
							p = maximumSize.getHeight() / preferredSize.getHeight();
						}
						if (scalingType == 0) {
							// No resizing(iPhone only)
							p = 1.0d;
						} else if (scalingType == 1) {
							// No resizing(iPhone & iPad)
							p = 1.0d;
						} else if (scalingType == 2) {
							// Fit to the screen height(iPhone only)
							p = maximumSize.getHeight() / preferredSize.getHeight();
						} else if (scalingType == 4) {
							p = (maximumSize.getWidth() < maximumSize.getHeight()) ? maximumSize.getHeight() / preferredSize.getHeight() : maximumSize.getWidth() / preferredSize.getWidth();
						}// else default
						double w = preferredSize.getWidth() * p;
						double h = preferredSize.getHeight() * p;
						if (scalingType == 5) {
							w = maximumSize.getWidth();
							h = maximumSize.getHeight();
						}
						ImagePanel.this.scaledImage = ImagePanel.this.generator.generateLaunchImage(ImagePanel.this.image, (int)Math.round(w), (int)Math.round(h), ImagePanel.this.asset);
					}
				} catch (Exception ex) {
					ex.printStackTrace();
				} finally {
					repaint();
					modifyTooltip();
					ImagePanel.this.setCursor(HAND_CURSOR);
				}
			}
		});
		timer.setRepeats(false);
		this.setScalingType(3);
		this.addComponentListener(new ComponentAdapter() {
			@Override public void componentResized(ComponentEvent e) {
				refresh();
			}
		});
	}

	/**
	 * Start resizing timer.
	 *
	 * @param delay
	 */
	private void startResizingTimer(int delay) {
		stopResizingTimer();
		if (this.image == null) {
			repaint();
			modifyTooltip();
			return;
		}
		timer.setDelay(delay);
		timer.start();
		this.setCursor(WAIT_CURSOR);
	}

	/**
	 * Stop resizing timer.
	 */
	private void stopResizingTimer() {
		if (timer.isRunning()) {
			timer.stop();
		}
		long l = System.currentTimeMillis();
		while (timer.isRunning()) {
			try {
				Thread.sleep(10);
				if (System.currentTimeMillis() - l > 1000) {
					break;
				}
			} catch (InterruptedException iex) {
				iex.printStackTrace();
			}
		}
	}

	/**
	 * Set image to display.
	 *
	 * @param image image
	 */
	public void setImage(BufferedImage image) {
		stopResizingTimer();
		if (this.image != null) {
			this.image.flush();
			this.image = null;
		}

		Dimension maximumSize = new Dimension(image.getWidth(), image.getHeight());
		if (maximumSize.getWidth() > 1024d || maximumSize.getHeight() > 1024d) {
			maximumSize = new Dimension(1024, 1024);
		}
		Dimension preferredSize = new Dimension(image.getWidth(), image.getHeight());
		double p = (maximumSize.getWidth() > maximumSize.getHeight()) ? maximumSize.getHeight() / preferredSize.getHeight() : maximumSize.getWidth() / preferredSize.getWidth();
		if (preferredSize.width != preferredSize.height) {
			double p2 = (preferredSize.width < preferredSize.height) ? maximumSize.getHeight() / preferredSize.getHeight() : maximumSize.getWidth() / preferredSize.getWidth();
			if (p2 < p) { p = p2; }
		}
		int w = (int) (preferredSize.getWidth() * p);
		int h = (int) (preferredSize.getHeight() * p);
		this.image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		Image img = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
		this.image.getGraphics().drawImage(img, 0, 0, this);
		img.flush();
		img = null;
		this.refresh();
	}

	/**
	 * Get image.
	 *
	 * @return image
	 */
	public Image getImage(){
		return this.image;
	}

	@Override public void paintComponent(Graphics g) {
		super.paintComponent(g);
		if(!isShowing()){
			return;
		}
		Graphics2D g2 = (Graphics2D)g;
		g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		if (image == null || scaledImage == null) {
			// Maybe padding, border and text-align will be implemented in some future.(but not need currently)
			FontMetrics fm = this.getFontMetrics(this.getFont());
			ArrayList<String> lines = this.getPlaceHolderLines(fm);
			int y = (this.getHeight() / 2) - ((fm.getHeight() * lines.size()) / 2) + fm.getAscent();
			for (String line : lines) {
				int x = (this.getWidth() - fm.stringWidth(line)) / 2;
				g.setColor(this.getForeground());
				g.drawString(line, x, y);
				y += fm.getHeight();
			}

		} else {
			int x = (int) ((this.getWidth() - this.scaledImage.getWidth(this)) / 2);
			int y = (int) ((this.getHeight() - this.scaledImage.getHeight(this)) / 2);
			g.drawImage(scaledImage, x, y, this);
		}
		g.setColor(this.getForeground());
		g.drawRect(0,  0, this.getWidth() - 1, this.getHeight() - 1);
	}

	@Override public Dimension getPreferredSize() {
		if (image == null) {
			return (new Dimension(0, 0));
		}
		return (new Dimension(image.getWidth(this), image.getHeight(this)));
	}

	@Override public void update(Graphics g) {
		paint(g);
	}

	/**
	 * Get the placeholder.
	 *
	 * @return placeHolder
	 */
	public String getPlaceHolder() {
		return placeHolder;
	}

	/**
	 * Set the placeholder.
	 *
	 * @param placeHolder set placeHolder
	 */
	public void setPlaceHolder(String placeHolder) {
		this.placeHolder = placeHolder;
	}

	/**
	 * Get the placeholder which is divided by width and words.
	 *
	 * @param fm FontMetrics
	 * @return the placeholder which is divided by width and words.
	 */
	public ArrayList<String> getPlaceHolderLines(FontMetrics fm) {
		// Separate by line.
		ArrayList<String> lines = new ArrayList<String>();
		if (this.getPlaceHolder() == null || this.getPlaceHolder().isEmpty()) return lines;
		String[] text = this.getPlaceHolder().split("[\r\n]");
		for (String t : text) {
			if (t.trim().isEmpty()) {
				lines.add("");
			} else {
				lines.addAll(this.splitText(t, fm));
			}
		}
		return lines;
	}

	/**
	 * Split text into display lines.
	 *
	 * @param text	text for split
	 * @param fm	FontMetrics
	 * @return	splitted text into display lines
	 */
	private ArrayList<String> splitText(String text, FontMetrics fm) {
		ArrayList<String> lines = new ArrayList<String>();
		StringBuilder line = new StringBuilder();
		Locale l = Locale.getDefault();
		BreakIterator boundary = BreakIterator.getWordInstance(l.equals(Locale.JAPAN) || l.equals(Locale.JAPANESE) ? l : Locale.US);
		boundary.setText(text);
		int startIndex = boundary.first();
		for (int endIndex = boundary.next(); endIndex != BreakIterator.DONE; startIndex = endIndex, endIndex = boundary.next()) {
			String word = text.substring(startIndex, endIndex);
			if (fm.stringWidth(line.toString()) + fm.stringWidth(word) > this.getWidth()) {
				// Very easy hyphenation. (just only one character)
				if (this.hyphenatorBoL != null && word.length() == 1 && this.hyphenatorBoL.indexOf(word.charAt(0)) >= 0) {
					line.append(word);
					word = new String();
				} else if (this.hyphenatorEoL != null && line.length() > 1 && this.hyphenatorEoL.indexOf(line.charAt(line.length() - 1)) >= 0) {
					word = line.substring(line.length() - 1).concat(word);
					line.setLength(line.length() - 1);
				}
				if (line.toString().replace('\u3000', ' ').trim().length() > 0) {
					lines.add(line.toString());
				}
				line.setLength(0);
			}
			line.append(word);
		}
		if (line.toString().replace('\u3000', ' ').trim().length() > 0) {
			lines.add(line.toString());
		}
		return lines;
	}

	/**
	 * Clear images.
	 */
	public void clear() {
		stopResizingTimer();
		if (this.image != null) {
			this.image.flush();
			this.image = null;
		}
		if (this.scaledImage != null) {
			this.scaledImage.flush();
			this.scaledImage = null;
		}
		this.imageFile = null;
		this.repaint();
		this.modifyTooltip();
		System.gc();
	}

	/**
	 * Set hyphenator.
	 *
	 * @param beginingOfLine
	 * @param endOfLine
	 */
	public void setHyphenator(String beginingOfLine, String endOfLine) {
		this.hyphenatorBoL = beginingOfLine;
		this.hyphenatorEoL = endOfLine;
	}

	/**
	 * @return imageFile
	 */
	public java.io.File getImageFile() {
		return imageFile;
	}

	/**
	 * @param imageFile set imageFile
	 */
	public void setImageFile(java.io.File imageFile) {
		this.imageFile = imageFile;
	}

	private void modifyTooltip() {
		if (placeHolder == null || placeHolder.trim().length() <= 0) {
			this.setToolTipText(null);
			return;
		}
		this.setToolTipText(image == null || scaledImage == null ? null : placeHolder);
	}

	/**
	 * @return scalingType
	 */
	public int getScalingType() {
		return scalingType;
	}

	/**
	 * @param scalingType set scalingType
	 */
	public void setScalingType(int scalingType) {
		if (this.scalingType != scalingType) {
			this.scalingType = scalingType;
			this.refresh();
		}
	}

	/**
	 * Refresh displayed image.
	 */
	public void refresh() {
		this.startResizingTimer(255);
	}
}