/*
 * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Flamingo Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.pushingpixels.flamingo.api.common;

import java.awt.Dimension;
import java.io.InputStream;
import java.util.*;

import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import org.pushingpixels.flamingo.api.common.icon.EmptyResizableIcon;
import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;

/**
 * Panel that hosts file-related command buttons with progress indication and
 * cancellation capabilities.
 * 
 * @author Kirill Grouchnikov
 * @param <T>
 *            Type tag.
 */
public abstract class AbstractFileViewPanel<T> extends JCommandButtonPanel {
	/**
	 * Maps from file name to the buttons.
	 */
	protected Map<String, JCommandButton> buttonMap;

	/**
	 * Progress listener to report back on loaded images.
	 */
	protected ProgressListener progressListener;

	/**
	 * Contains the buttons with completely loaded images.
	 */
	protected Set<JCommandButton> loadedSet;

	/**
	 * The main worker that loads the images off EDT.
	 */
	private SwingWorker<Void, Leaf> mainWorker;

	/**
	 * Information on the specific file. Depending on the actual type of the
	 * file repository, the property map will have different keys.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static class Leaf {
		/**
		 * Leaf name.
		 */
		protected String leafName;

		/**
		 * Stream with the contents of the leaf file.
		 */
		protected InputStream leafStream;

		/**
		 * Leaf property map.
		 */
		protected Map<String, Object> leafProps;

		/**
		 * Creates a new leaf.
		 * 
		 * @param leafName
		 *            Leaf name.
		 * @param leafStream
		 *            Stream with the contents of the leaf file.
		 */
		public Leaf(String leafName, InputStream leafStream) {
			this.leafName = leafName;
			this.leafStream = leafStream;
			this.leafProps = new HashMap<String, Object>();
		}

		/**
		 * Returns the leaf name.
		 * 
		 * @return Leaf name.
		 */
		public String getLeafName() {
			return leafName;
		}

		/**
		 * Returns the stream with the contents of the leaf file.
		 * 
		 * @return Stream with the contents of the leaf file.
		 */
		public InputStream getLeafStream() {
			return leafStream;
		}

		/**
		 * Returns the leaf property with the specified name.
		 * 
		 * @param propName
		 *            Property name.
		 * @return Leaf property with the specified name.
		 */
		public Object getLeafProp(String propName) {
			return this.leafProps.get(propName);
		}

		/**
		 * Sets the leaf property with the specified name.
		 * 
		 * @param propName
		 *            Property name.
		 * @param propValue
		 *            Property value.
		 */
		public void setLeafProp(String propName, Object propValue) {
			this.leafProps.put(propName, propValue);
		}

		/**
		 * Returns the map of all the properties of this leaf.
		 * 
		 * @return Unmodifiable view of the map of all the properties of this
		 *         leaf.
		 */
		public Map<String, Object> getLeafProps() {
			return Collections.unmodifiableMap(this.leafProps);
		}
	}

	/**
	 * Creates a new panel.
	 * 
	 * @param startingDimension
	 *            Initial dimension for icons.
	 * @param progressListener
	 *            Progress listener to report back on loaded icons.
	 */
	public AbstractFileViewPanel(int startingDimension,
			ProgressListener progressListener) {
		super(startingDimension);
		this.buttonMap = new HashMap<String, JCommandButton>();
		this.progressListener = progressListener;
		this.loadedSet = new HashSet<JCommandButton>();

		this.setToShowGroupLabels(false);
	}

	/**
	 * Creates a new panel.
	 * 
	 * @param startingState
	 *            Initial state for icons.
	 * @param progressListener
	 *            Progress listener to report back on loaded icons.
	 */
	public AbstractFileViewPanel(CommandButtonDisplayState startingState,
			ProgressListener progressListener) {
		super(startingState);
		this.buttonMap = new HashMap<String, JCommandButton>();
		this.progressListener = progressListener;
		this.loadedSet = new HashSet<JCommandButton>();

		this.setToShowGroupLabels(false);
	}

	/**
	 * Sets the current entries to show. The current contents of the panel are
	 * discarded. For each matching entry determined by the
	 * {@link #toShowFile(StringValuePair)} call, a new {@link JCommandButton}
	 * hosting an the matching implementation of {@link ResizableIcon} is added
	 * to the panel.
	 * 
	 * @param leafs
	 *            Information on the entries to show in the panel.
	 */
	public void setFolder(final java.util.List<StringValuePair<T>> leafs) {
		this.removeAllGroups();
		this.addButtonGroup("");
		this.buttonMap.clear();
		int fileCount = 0;

		final Map<String, JCommandButton> newButtons = new HashMap<String, JCommandButton>();
		for (StringValuePair<T> leaf : leafs) {
			String name = leaf.getKey();
			if (!toShowFile(leaf))
				continue;

			int initialSize = currDimension;
			if (initialSize < 0)
				initialSize = currState.getPreferredIconSize();
			JCommandButton button = new JCommandButton(name,
					new EmptyResizableIcon(initialSize));
			button.setHorizontalAlignment(SwingUtilities.LEFT);
			button.setDisplayState(this.currState);
			if (this.currState == CommandButtonDisplayState.FIT_TO_ICON)
				button.updateCustomDimension(currDimension);

			this.addButtonToLastGroup(button);

			newButtons.put(name, button);
			buttonMap.put(name, button);
			fileCount++;
		}
		this.doLayout();
		this.repaint();

		final int totalCount = fileCount;
		this.mainWorker = new SwingWorker<Void, Leaf>() {
			@Override
			protected Void doInBackground() throws Exception {
				if ((totalCount > 0) && (progressListener != null)) {
					progressListener.onProgress(new ProgressEvent(
							AbstractFileViewPanel.this, 0, totalCount, 0));
				}
				for (final StringValuePair<T> leafPair : leafs) {
					if (isCancelled())
						break;
					final String name = leafPair.getKey();
					if (!toShowFile(leafPair))
						continue;
					InputStream stream = getLeafContent(leafPair.getValue());
					Leaf leaf = new Leaf(name, stream);
					leaf.setLeafProp("source", leafPair.getValue());
					for (Map.Entry<String, Object> propEntry : leafPair
							.getProps().entrySet()) {
						leaf.setLeafProp(propEntry.getKey(), propEntry
								.getValue());
					}
					publish(leaf);
				}
				return null;
			}

			@Override
			protected void process(List<Leaf> leaves) {
				for (final Leaf leaf : leaves) {
					final String name = leaf.getLeafName();
					InputStream stream = leaf.getLeafStream();
					Dimension dim = new Dimension(currDimension, currDimension);
					final ResizableIcon icon = getResizableIcon(leaf, stream,
							currState, dim);
					if (icon == null)
						continue;
					final JCommandButton commandButton = newButtons.get(name);
					commandButton.setIcon(icon);

					if (icon instanceof AsynchronousLoading) {
						((AsynchronousLoading) icon)
								.addAsynchronousLoadListener(new AsynchronousLoadListener() {
									@Override
                                    public void completed(boolean success) {
										synchronized (AbstractFileViewPanel.this) {
											if (loadedSet
													.contains(commandButton))
												return;
											loadedSet.add(commandButton);
											// loadedCount++;
											if (progressListener != null) {
												progressListener
														.onProgress(new ProgressEvent(
																AbstractFileViewPanel.this,
																0, totalCount,
																loadedSet
																		.size()));
												if (loadedSet.size() == totalCount) {
													progressListener
															.onProgress(new ProgressEvent(
																	AbstractFileViewPanel.this,
																	0,
																	totalCount,
																	totalCount));
												}
											}
										}
									}
								});
					}

					configureCommandButton(leaf, commandButton, icon);

					commandButton.setDisplayState(currState);
					if (currState == CommandButtonDisplayState.FIT_TO_ICON)
						commandButton.updateCustomDimension(currDimension);
				}
			}
		};
		mainWorker.execute();
	}

	/**
	 * Returns the number of loaded icons.
	 * 
	 * @return The number of loaded icons.
	 */
	public int getLoadedIconCount() {
		return this.loadedSet.size();
	}

	/**
	 * Cancels the pending processing.
	 */
	public void cancelMainWorker() {
		if (this.mainWorker == null)
			return;
		if (this.mainWorker.isDone() || this.mainWorker.isCancelled())
			return;
		this.mainWorker.cancel(false);
	}

	/**
	 * Returns the button map.
	 * 
	 * @return Unmodifiable view on the button map.
	 */
	public Map<String, JCommandButton> getButtonMap() {
		return Collections.unmodifiableMap(buttonMap);
	}

	/**
	 * Returns indication whether the specified file should be shown on this
	 * panel.
	 * 
	 * @param pair
	 *            Information on the file.
	 * @return <code>true</code> if the specified file should be shown on this
	 *         panel, <code>false</code> otherwise.
	 */
	protected abstract boolean toShowFile(StringValuePair<T> pair);

	/**
	 * Returns the icon for the specified parameters.
	 * 
	 * @param leaf
	 *            Information on the file.
	 * @param stream
	 *            Input stream with the file contents.
	 * @param state
	 *            Icon state.
	 * @param dimension
	 *            Icon dimension.
	 * @return File icon.
	 */
	protected abstract ResizableIcon getResizableIcon(Leaf leaf,
			InputStream stream, CommandButtonDisplayState state,
			Dimension dimension);

	/**
	 * Configures the specified command button. Can be used to wire additional
	 * behavior, such as tooltips or action listeners if the specific view panel
	 * implementation requires it.
	 * 
	 * @param leaf
	 *            Information on the file "behind" the button.
	 * @param button
	 *            Button to configure.
	 * @param icon
	 *            Button icon.
	 */
	protected abstract void configureCommandButton(Leaf leaf,
			JCommandButton button, ResizableIcon icon);

	/**
	 * Returns the input stream with the file contents.
	 * 
	 * @param leaf
	 *            Leaf (file behind a command button on this panel).
	 * @return Input stream with the file contents.
	 */
	protected abstract InputStream getLeafContent(T leaf);
}
